apple / HomeKitADK

Apache License 2.0
2.56k stars 232 forks source link

Characteristic database #67

Open maximkulkin opened 4 years ago

maximkulkin commented 4 years ago

Currently ADK requires you to define a number of (boilerplate) services and characteristics just to start writing the actual accessory logic which results in huge copy pasted declarations. For particular characteristic (let's say FirmwareRevision) all settings are the same every time, with exception of callbacks and maybe custom ranges (e.g. for TargetTemperature characteristic). It would make sense to automate filling most characteristic fields by using a set of macros: it's convenient, short and yet allows for customization. Here is an example of how this could be done (from my prototype):

#define HOMEKIT_SERVICE(iid_, name, ...) \
    { DEFINE_HOMEKIT_SERVICE_ ## name, .iid = iid_, ## __VA_ARGS__ }

#define HOMEKIT_CONCAT3(a, b, c) HOMEKIT_CONCAT3_(a, b, c)
#define HOMEKIT_CONCAT3_(a, b, c) a ## b ## c
#define HOMEKIT_CHARACTERISTIC_TYPE(name) HOMEKIT_CONCAT3(HAP, TYPEOF_HOMEKIT_CHARACTERISTIC_ ## name, Characteristic)

#define HOMEKIT_CHARACTERISTIC_(iid_, name, ...) \
    { DEFINE_HOMEKIT_CHARACTERISTIC_ ## name, .iid = iid_, ## __VA_ARGS__ }

#define HOMEKIT_CHARACTERISTIC(iid_, name, ...) \
    &(HOMEKIT_CHARACTERISTIC_TYPE(name)) HOMEKIT_CHARACTERISTIC_(iid_, name, ## __VA_ARGS__)

#define DEFINE_HOMEKIT_SERVICE_ACCESSORY_INFORMATION \
    .serviceType = &kHAPServiceType_AccessoryInformation, \
    .debugDescription = kHAPServiceDebugDescription_AccessoryInformation \

#define TYPEOF_HOMEKIT_CHARACTERISTIC_MANUFACTURER String
#define DEFINE_HOMEKIT_CHARACTERISTIC_MANUFACTURER \
    .format = kHAPCharacteristicFormat_String, \
    .characteristicType = &kHAPCharacteristicType_Manufacturer, \
    .debugDescription = kHAPCharacteristicDebugDescription_Manufacturer, \
    .properties = { \
        .readable = true, \
    }, \
    .constraints = { .maxLength = 64 }, \
    .callbacks = { .handleRead = HAPHandleAccessoryInformationManufacturerRead, }

const HAPService accessoryInformationService = HOMEKIT_SERVICE(0x01, ACCESSORY_INFORMATION,
    .characteristics = (const HAPCharacteristic* const[]) {
        HOMEKIT_CHARACTERISTIC(0x02, IDENTIFY),
        HOMEKIT_CHARACTERISTIC(0x03, MANUFACTURER),
        HOMEKIT_CHARACTERISTIC(0x04, MODEL),
        HOMEKIT_CHARACTERISTIC(0x05, ACCESSORY_NAME),
        HOMEKIT_CHARACTERISTIC(0x06, SERIAL_NUMBER),
        HOMEKIT_CHARACTERISTIC(0x07, FIRMWARE_REVISION),
        HOMEKIT_CHARACTERISTIC(0x08, HARDWARE_REVISION),
        HOMEKIT_CHARACTERISTIC(0x09, ADK_VERSION),
        NULL
    },
);

Here is example of more interesting service - a fan:

const HAPUInt8Characteristic fanActiveCharacteristic =
    HOMEKIT_CHARACTERISTIC_(0x42, ACTIVE,
        .callbacks.handleRead=HandleFanActiveRead,
        .callbacks.handleWrite=HandleFanActiveWrite,
    );

const HAPFloatCharacteristic fanRotationSpeedCharacteristic =
    HOMEKIT_CHARACTERISTIC_(0x43, ROTATION_SPEED,
        .callbacks.handleRead=HandleFanRotationSpeedRead,
        .callbacks.handleWrite=HandleFanRotationSpeedWrite,
    );

const HAPUInt8Characteristic fanSwingModeCharaceristic =
    HOMEKIT_CHARACTERISTIC_(0x44, SWING_MODE,
        .callbacks.handleRead=HandleFanSwingModeRead,
        .callbacks.handleWrite=HandleFanSwingModeWrite,
    );

const HAPService fanService = HOMEKIT_SERVICE(0x40, FAN,
    .name = "Fan",
    .characteristics = (const HAPCharacteristic* const[]) {
        HOMEKIT_CHARACTERISTIC(0x41, NAME),
        &fanActiveCharacteristic,
        &fanRotationSpeedCharacteristic,
        &fanSwingModeCharacteristic,
        NULL
    },
);

The above could be defined with all characteristics in-place right inside service declaration, but I think sometimes you need to raise an event on some characteristic outside it's read/write handler, so thus you need to have a pointer/reference to it.

So, ADK could have a library of field definitions for standard Apple services / characteristics. Plus, it is easy to create your own (just need to declare a bunch of defines).

maximkulkin commented 4 years ago

@aajain-com Is it something this repository would benefit from? I can work on implementation, just need a heads up.

aajain-com commented 4 years ago

@maximkulkin another approach would be to move the definition of accessory services and databases to a common set of files that can be easily reused in multiple applications.

maximkulkin commented 4 years ago

It's not only about reuse. It's about lowering barrier for creating applications: you're focusing more on business logic and less on re-typing information from HAP spec. I see no reason not to have ADK come with batteries included: it already comes with Apple standard characteristic UUIDs included, why not to go extra mile and provide value types, permissions and other stuff.

rojer commented 4 years ago

i agree, constructing even simple accessories is a bit of a pain.

accessory information protocol information, pairing - these get copied pretty much verbatim. in my project i created const globals for them and just reuse as needed. it's ugly but it's hidden away and people don't need to see it. for more dynamic stuff i wrote a set of C++ wrappers that hide the complications of the underlying structs for the most common cases (1, 2, 3). compared to what it was before, i think that's a big improvement in readability.

bottom line is - anything that can reduce the amount of boilerplate would be welcome.