blob: ca4636bcc9b7da8814da20f809c384e95930d779 [file] [log] [blame]
// Copyright 2022 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once
#include <cstdint>
#include "pw_bluetooth/assigned_uuids.h"
#include "pw_bluetooth/gatt/error.h"
#include "pw_bluetooth/gatt/server.h"
#include "pw_bluetooth/gatt/types.h"
#include "pw_span/span.h"
namespace pw::bluetooth_profiles {
// Device information to be exposed by the Device Information Service, according
// to the DIS spec 1.1. All fields are optional.
struct DeviceInfo {
// Bitmask of the fields present in the DeviceInfoService, each one
// corresponding to one of the possible characteristics in the Device
// Information Service.
enum class Field : uint16_t {
kManufacturerName = 1u << 0,
kModelNumber = 1u << 1,
kSerialNumber = 1u << 2,
kHardwareRevision = 1u << 3,
kFirmwareRevision = 1u << 4,
kSoftwareRevision = 1u << 5,
kSystemId = 1u << 6,
kRegulatoryInformation = 1u << 7,
kPnpId = 1u << 8,
};
// Manufacturer Name String
span<const std::byte> manufacturer_name;
// Model Number String
span<const std::byte> model_number;
// Serial Number String
span<const std::byte> serial_number;
// Hardware Revision String
span<const std::byte> hardware_revision;
// Firmware Revision String
span<const std::byte> firmware_revision;
// Software Revision String
span<const std::byte> software_revision;
// System ID
span<const std::byte> system_id;
// IEEE 11073-20601 Regulatory Certification Data List
span<const std::byte> regulatory_information;
// PnP ID
span<const std::byte> pnp_id;
};
// Helper operator| to allow combining multiple DeviceInfo::Field values.
static inline constexpr DeviceInfo::Field operator|(DeviceInfo::Field left,
DeviceInfo::Field right) {
return static_cast<DeviceInfo::Field>(static_cast<uint16_t>(left) |
static_cast<uint16_t>(right));
}
static inline constexpr bool operator&(DeviceInfo::Field left,
DeviceInfo::Field right) {
return (static_cast<uint16_t>(left) & static_cast<uint16_t>(right)) != 0;
}
// Shared implementation of the DeviceInfoService<> template class of elements
// that don't depend on the template parameters.
class DeviceInfoServiceImpl {
public:
DeviceInfoServiceImpl(const bluetooth::gatt::LocalServiceInfo& service_info,
span<const span<const std::byte>> values)
: service_info_(service_info), delegate_(values) {}
// Publish this service on the passed gatt::Server. The service may be
// published only on one Server at a time.
void PublishService(
bluetooth::gatt::Server* gatt_server,
Callback<
void(bluetooth::Result<bluetooth::gatt::Server::PublishServiceError>)>
result_callback);
protected:
using GattCharacteristicUuid = bluetooth::GattCharacteristicUuid;
// A struct for describing each one of the optional characteristics available.
struct FieldDescriptor {
DeviceInfo::Field field_value;
span<const std::byte> DeviceInfo::*field_pointer;
bluetooth::Uuid characteristic_type;
};
// List of all the fields / characteristics available in the DIS, mapping the
// characteristic UUID type to the corresponding field in the DeviceInfo
// struct.
static constexpr size_t kNumFields = 9;
static constexpr std::array<FieldDescriptor, kNumFields>
kCharacteristicFields = {{
{DeviceInfo::Field::kManufacturerName,
&DeviceInfo::manufacturer_name,
GattCharacteristicUuid::kManufacturerNameString},
{DeviceInfo::Field::kModelNumber,
&DeviceInfo::model_number,
GattCharacteristicUuid::kModelNumberString},
{DeviceInfo::Field::kSerialNumber,
&DeviceInfo::serial_number,
GattCharacteristicUuid::kSerialNumberString},
{DeviceInfo::Field::kHardwareRevision,
&DeviceInfo::hardware_revision,
GattCharacteristicUuid::kHardwareRevisionString},
{DeviceInfo::Field::kFirmwareRevision,
&DeviceInfo::firmware_revision,
GattCharacteristicUuid::kFirmwareRevisionString},
{DeviceInfo::Field::kSoftwareRevision,
&DeviceInfo::software_revision,
GattCharacteristicUuid::kSoftwareRevisionString},
{DeviceInfo::Field::kSystemId,
&DeviceInfo::system_id,
GattCharacteristicUuid::kSystemId},
{DeviceInfo::Field::kRegulatoryInformation,
&DeviceInfo::regulatory_information,
GattCharacteristicUuid::
kIeee1107320601RegulatoryCertificationDataList},
{DeviceInfo::Field::kPnpId,
&DeviceInfo::pnp_id,
GattCharacteristicUuid::kPnpId},
}};
private:
class Delegate : public bluetooth::gatt::LocalServiceDelegate {
public:
explicit Delegate(span<const span<const std::byte>> values)
: values_(values) {}
~Delegate() override = default;
void SetServicePtr(bluetooth::gatt::LocalService::Ptr service) {
local_service_ = std::move(service);
}
// LocalServiceDelegate overrides
void OnError(bluetooth::gatt::Error error) override;
void ReadValue(bluetooth::PeerId peer_id,
bluetooth::gatt::Handle handle,
uint32_t offset,
Function<void(bluetooth::Result<bluetooth::gatt::Error,
span<const std::byte>>)>&&
result_callback) override;
void WriteValue(bluetooth::PeerId /* peer_id */,
bluetooth::gatt::Handle /* handle */,
uint32_t /* offset */,
span<const std::byte> /* value */,
Function<void(bluetooth::Result<bluetooth::gatt::Error>)>&&
status_callback) override {
status_callback(bluetooth::gatt::Error::kUnlikelyError);
}
void CharacteristicConfiguration(bluetooth::PeerId /* peer_id */,
bluetooth::gatt::Handle /* handle */,
bool /* notify */,
bool /* indicate */) override {
// No indications or notifications are supported by this service.
}
void MtuUpdate(bluetooth::PeerId /* peer_id*/,
uint16_t /* mtu */) override {
// MTU is ignored.
}
private:
// LocalService smart pointer returned by the pw_bluetooth API once the
// service is published. This field is unused since we don't generate any
// Notification or Indication, but deleting this object unpublishes the
// service.
bluetooth::gatt::LocalService::Ptr local_service_;
// Device information values for the service_info_ characteristics. The
// characteristic Handle is the index into the values_ span.
span<const span<const std::byte>> values_;
};
// GATT service info.
const bluetooth::gatt::LocalServiceInfo& service_info_;
// Callback to be called after the service is published.
Callback<void(
bluetooth::Result<bluetooth::gatt::Server::PublishServiceError>)>
publish_service_callback_;
// The LocalServiceDelegate implementation.
Delegate delegate_;
};
// Device Information Service exposing only the subset of characteristics
// specified by the bitmask kPresentFields.
template <DeviceInfo::Field kPresentFields,
bluetooth::gatt::Handle kServiceHandle>
class DeviceInfoService : public DeviceInfoServiceImpl {
public:
// Handle used to reference this service from other services.
static constexpr bluetooth::gatt::Handle kHandle = kServiceHandle;
// Construct a DeviceInfoService exposing the values provided in the
// `device_info` for the subset of characteristics selected by kPresentFields.
// DeviceInfo fields for characteristics not selected by kPresentFields are
// ignored. The `device_info` reference doesn't need to be kept alive after
// the constructor returns, however the data pointed to by the various fields
// in `device_info` must be kept available while the service is published.
explicit constexpr DeviceInfoService(const DeviceInfo& device_info)
: DeviceInfoServiceImpl(kServiceInfo, span{values_}) {
size_t count = 0;
// Get the device information only for the fields we care about.
for (const auto& field : kCharacteristicFields) {
if (field.field_value & kPresentFields) {
values_[count] = device_info.*(field.field_pointer);
count++;
}
}
}
private:
// Return the total number of selected characteristics on this service.
static constexpr size_t NumCharacteristics() {
size_t ret = 0;
for (const auto& field : kCharacteristicFields) {
if (field.field_value & kPresentFields) {
ret++;
}
}
return ret;
}
static constexpr size_t kNumCharacteristics = NumCharacteristics();
// Factory method to build the list of characteristics needed for a given
// subset of fields.
static constexpr std::array<bluetooth::gatt::Characteristic,
kNumCharacteristics>
BuildServiceInfoCharacteristics() {
std::array<bluetooth::gatt::Characteristic, kNumCharacteristics> ret = {};
size_t count = 0;
for (const auto& field : kCharacteristicFields) {
if (field.field_value & kPresentFields) {
ret[count] = bluetooth::gatt::Characteristic{
.handle = bluetooth::gatt::Handle(count),
.type = field.characteristic_type,
.properties = bluetooth::gatt::CharacteristicPropertyBits::kRead,
.permissions = bluetooth::gatt::AttributePermissions{},
.descriptors = {},
};
count++;
}
}
return ret;
}
// Static constexpr array of characteristics for the current subset of fields
// kPresentFields.
static constexpr auto kServiceInfoCharacteristics =
BuildServiceInfoCharacteristics();
// GATT Service information.
static constexpr auto kServiceInfo = bluetooth::gatt::LocalServiceInfo{
.handle = kServiceHandle,
.primary = true,
.type = bluetooth::GattServiceUuid::kDeviceInformation,
.characteristics = kServiceInfoCharacteristics,
.includes = {},
};
// Storage std::array for the span<const std::byte> of values.
std::array<span<const std::byte>, kNumCharacteristics> values_;
};
} // namespace pw::bluetooth_profiles