maximkulkin / esp-homekit-demo

Demo of Apple HomeKit accessory server library
MIT License
805 stars 233 forks source link

Question - Linking TV InputSource to a Lightbulb #298

Closed mriksman closed 4 years ago

mriksman commented 4 years ago

Hey,

Any idea if it’s possible to link an InputSource to a Lightbulb?

I want to create an accessory for an Addressable LED Strip that has a list of animations programmed. It’d be cool if you had on the UI just the dimmer/light/colour, and then underneath, an InputSource list where you could list all your Animation names.

I created a single Accessory with Television, linked InputSource and Lightbulb. But I really don’t want the power button that comes with the Television service. Can InputSource be used by itself with Lightbulb?

PNG image

maximkulkin commented 4 years ago

I do not know where this screenshot comes from, but it does not look like Home.app, so I really do not know what it can do. But overall it sounds like a weird combination for the HomeKit.

mriksman commented 4 years ago

It’s the native Apple Home app.

It IS a weird combination hahah. But I’m open to suggestions for how else to provide a way to list/cycle through a bunch of animations for the WS2812 addressable LED strips.

maximkulkin commented 4 years ago

You need a custom iOS app that will a) use HomeKit APIs; b) understand your custom characteristics and provide UI for them. You can try some existing apps (e.g. "Eve for HomeKit"), they might be able to provide at least some UI (whereas stock Home.app do not display services it does not know).

NorthernMan54 commented 4 years ago

In my homebridge led strip plugin I created a bunch of buttons, each being a different strip animation. Was a bit cumbersome in the Home app, but allowed access to different strip animations, and for Xmas allowed for turning on selected animation via home app automations.

Sent from my iPad

On Jan 13, 2020, at 1:14 AM, Maxim Kulkin notifications@github.com wrote:

 You need a custom iOS app that will a) use HomeKit APIs; b) understand your custom characteristics and provide UI for them. You can try some existing apps (e.g. "Eve for HomeKit"), they might be able to provide at least some UI (whereas stock Home.app do not display services it does not know).

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or unsubscribe.

mriksman commented 4 years ago

I set LIGHTBULB to .primary=true and removed TELEVISION's .primary=true. I got exactly what I wanted!

image

maccoylton commented 4 years ago

Can you share a pointer to your code. I am interested the pice where you got it to display the animations as a selectable list .

mriksman commented 4 years ago

It's really all thanks to the example code from @maximkulkin and his esp-it-tv code.

I am having issues with naming the LIGHTBULB and TELEVISION though. It doesn't stick. Not sure what the issue is... Keeps reverting back to the values originally configured here HOMEKIT_CHARACTERISTIC(NAME, for both the LIGHTBULB and TELEVISION.

void on_input_configured_name(homekit_characteristic_t *ch, homekit_value_t value, void *arg);

void on_configured_name(homekit_characteristic_t *ch, homekit_value_t value, void *arg) {
//    sysparam_set_string("tv_name", value.string_value);
      printf("sysparam_set_string('tv_name', %s)", value.string_value); 
}

void on_active(homekit_characteristic_t *ch, homekit_value_t value, void *arg) {
//    ir_tv_send(ir_tv_power);
      printf("Animation activated? %d\n", value.int_value);
}

void on_active_identifier(homekit_characteristic_t *ch, homekit_value_t value, void *arg) {
    switch (value.int_value) {
    case 1:
//        ir_hdmi_switch_send(ir_hdmi_switch_input_1);
          printf("Animation 1 Selected\n");
    break;
    case 2:
//      ir_hdmi_switch_send(ir_hdmi_switch_input_2);
        printf("Animation 2 Selected\n");
        break;
    case 3:
//      ir_hdmi_switch_send(ir_hdmi_switch_input_3);
        printf("Animation 3 Selected\n");
        break;
    default:
        printf("Unknown active identifier: %d", value.int_value);
    }
}

homekit_characteristic_t input_source1_name = HOMEKIT_CHARACTERISTIC_(
    CONFIGURED_NAME, "Animation 1",
    .callback=HOMEKIT_CHARACTERISTIC_CALLBACK(on_input_configured_name)
);

homekit_service_t input_source1 =
    HOMEKIT_SERVICE_(INPUT_SOURCE, .characteristics=(homekit_characteristic_t*[]){
        HOMEKIT_CHARACTERISTIC(NAME, "anim1"),
        HOMEKIT_CHARACTERISTIC(IDENTIFIER, 1),
        &input_source1_name,
        HOMEKIT_CHARACTERISTIC(INPUT_SOURCE_TYPE, HOMEKIT_INPUT_SOURCE_TYPE_HDMI),
        HOMEKIT_CHARACTERISTIC(IS_CONFIGURED, true),
        HOMEKIT_CHARACTERISTIC(CURRENT_VISIBILITY_STATE, HOMEKIT_CURRENT_VISIBILITY_STATE_SHOWN),
        NULL
    });

homekit_characteristic_t input_source2_name = HOMEKIT_CHARACTERISTIC_(
    CONFIGURED_NAME, "Animation 2",
    .callback=HOMEKIT_CHARACTERISTIC_CALLBACK(on_input_configured_name)
);

homekit_service_t input_source2 =
    HOMEKIT_SERVICE_(INPUT_SOURCE, .characteristics=(homekit_characteristic_t*[]){
        HOMEKIT_CHARACTERISTIC(NAME, "anim2"),
        HOMEKIT_CHARACTERISTIC(IDENTIFIER, 2),
        &input_source2_name,
        HOMEKIT_CHARACTERISTIC(INPUT_SOURCE_TYPE, HOMEKIT_INPUT_SOURCE_TYPE_HDMI),
        HOMEKIT_CHARACTERISTIC(IS_CONFIGURED, true),
        HOMEKIT_CHARACTERISTIC(CURRENT_VISIBILITY_STATE, HOMEKIT_CURRENT_VISIBILITY_STATE_SHOWN),
        NULL
    });

homekit_characteristic_t input_source3_name = HOMEKIT_CHARACTERISTIC_(
    CONFIGURED_NAME, "Animation 3",
    .callback=HOMEKIT_CHARACTERISTIC_CALLBACK(on_input_configured_name)
);

homekit_service_t input_source3 =
    HOMEKIT_SERVICE_(INPUT_SOURCE, .characteristics=(homekit_characteristic_t*[]){
        HOMEKIT_CHARACTERISTIC(NAME, "anim3"),
        HOMEKIT_CHARACTERISTIC(IDENTIFIER, 3),
        &input_source3_name,
        HOMEKIT_CHARACTERISTIC(INPUT_SOURCE_TYPE, HOMEKIT_INPUT_SOURCE_TYPE_HDMI),
        HOMEKIT_CHARACTERISTIC(IS_CONFIGURED, true),
        HOMEKIT_CHARACTERISTIC(CURRENT_VISIBILITY_STATE, HOMEKIT_CURRENT_VISIBILITY_STATE_SHOWN),
        NULL
    });

homekit_characteristic_t tv_name = HOMEKIT_CHARACTERISTIC_(
    CONFIGURED_NAME, "My TV",
    .callback=HOMEKIT_CHARACTERISTIC_CALLBACK(on_configured_name)
);

homekit_accessory_t *accessories[] = {
    HOMEKIT_ACCESSORY(.id=1, .category=homekit_accessory_category_television, .services=(homekit_service_t*[]){
        HOMEKIT_SERVICE(ACCESSORY_INFORMATION, .characteristics=(homekit_characteristic_t*[]){
            HOMEKIT_CHARACTERISTIC(NAME, "TV"),
            HOMEKIT_CHARACTERISTIC(MANUFACTURER, "HaPK"),
            HOMEKIT_CHARACTERISTIC(SERIAL_NUMBER, "1"),
            HOMEKIT_CHARACTERISTIC(MODEL, "ESP8266-1"),
            HOMEKIT_CHARACTERISTIC(FIRMWARE_REVISION, "0.1"),
            HOMEKIT_CHARACTERISTIC(IDENTIFY, led_identify),
            NULL
        }),
//        HOMEKIT_SERVICE(TELEVISION, .primary=true, .characteristics=(homekit_characteristic_t*[]) {
        HOMEKIT_SERVICE(TELEVISION, .characteristics=(homekit_characteristic_t*[]) {
            HOMEKIT_CHARACTERISTIC(NAME, "Television"),
            HOMEKIT_CHARACTERISTIC(
                ACTIVE, true,
                .callback=HOMEKIT_CHARACTERISTIC_CALLBACK(on_active)
            ),
            HOMEKIT_CHARACTERISTIC(
                ACTIVE_IDENTIFIER, 1,
                .callback=HOMEKIT_CHARACTERISTIC_CALLBACK(on_active_identifier)
            ),
            &tv_name,
            HOMEKIT_CHARACTERISTIC(
                SLEEP_DISCOVERY_MODE,
                HOMEKIT_SLEEP_DISCOVERY_MODE_ALWAYS_DISCOVERABLE
            ),
            NULL
        }, .linked=(homekit_service_t*[]) {
            &input_source1,
            &input_source2,
            &input_source3,
            NULL
        }),
        &input_source1,
        &input_source2,
        &input_source3,
        HOMEKIT_SERVICE(LIGHTBULB, .primary=true, .characteristics=(homekit_characteristic_t*[]){
            HOMEKIT_CHARACTERISTIC(NAME, "Sample LED"),
            HOMEKIT_CHARACTERISTIC(
                ON, false,
                .getter=led_on_get,
                .setter=led_on_set
            ),
            HOMEKIT_CHARACTERISTIC(
                BRIGHTNESS, 100,
                .getter = led_brightness_get,
                .setter = led_brightness_set
            ),
            HOMEKIT_CHARACTERISTIC(
                HUE, 0,
                .getter = led_hue_get,
                .setter = led_hue_set
            ),
            HOMEKIT_CHARACTERISTIC(
                SATURATION, 0,
                .getter = led_saturation_get,
                .setter = led_saturation_set
            ),
            NULL
        }),
        NULL
    }),
    NULL
};

void on_input_configured_name(homekit_characteristic_t *ch, homekit_value_t value, void *arg) {
    const char *input_param_name = NULL;
    if (ch == &input_source1_name) {
        input_param_name = "input1_name";
    } else if (ch == &input_source2_name) {
        input_param_name = "input2_name";
    } else if (ch == &input_source3_name) {
        input_param_name = "input3_name";
    }

    if (!input_param_name)
        return;

//    sysparam_set_string(input_param_name, value.string_value);
      printf("sysparam_set_string(%s, %s)", input_param_name, value.string_value);                  
}
mriksman commented 4 years ago

For anyone who sees this. Do NOT use the CHARACTERISTIC 'NAME' for Television (remove this line; HOMEKIT_CHARACTERISTIC(NAME, "Television"),). Seems the original services use NAME, and they are persistent between restarts of the device (where is this data stored!? Local to the iPhone? What happens when you 'invite' someone to your 'Home'?). TelevisionService uses CONFIGUREDNAME and must be saved to the devices memory and restored upon restart (much like the Input names).

maximkulkin commented 4 years ago

@mriksman NAME characteristics are read-only, while required CONFIGUREDNAME characteristic should be writable (that's why it's a separate characteristic). Accessory is supposed to persist CONFIGUREDNAME values across reboots.

mriksman commented 4 years ago

Looking at your esp-ir-tv code, you appear to save the CONFIGURED_NAME for the tv_name just like you save it for the input_source# names. Which lines up with what I am seeing.

I found that

        HOMEKIT_SERVICE(ACCESSORY_INFORMATION, .characteristics=(homekit_characteristic_t*[]){
            HOMEKIT_CHARACTERISTIC(NAME, "UseMACAddr"),

This name only appeared when first connecting to the device. Then, once paired, you'll see it for about 5 seconds on the tile on the Home app, before it gets replaced by the name of the service configured as Primary. I had this as my Primary service;

       HOMEKIT_SERVICE(LIGHTBULB, .primary=true, .characteristics=(homekit_characteristic_t*[]){
            HOMEKIT_CHARACTERISTIC(NAME, "LightbulbName"),

So my Accessory (with a Lightbulb and Television service) was now collectively named LightbulbName, the Lightbulb Service was named LightbulbName, and the Television Service was named according to this;

homekit_characteristic_t tv_name = HOMEKIT_CHARACTERISTIC_(
    CONFIGURED_NAME, "My TV",
    .callback=HOMEKIT_CHARACTERISTIC_CALLBACK(on_configured_name)
);

When I changed the Accessory Name OR the Lightbulb Service name - it persisted between reboots. When I changed Television name - it did not. I had to save and restore the name - just like you did in your code.

I found the Characteristic NAME to have no impact on the Television service;

        HOMEKIT_SERVICE(TELEVISION, .characteristics=(homekit_characteristic_t*[]) {
            HOMEKIT_CHARACTERISTIC(NAME, "TelevisionName"),

It simply didn't appear anywhere.

This is just my observation. I'd be curious to see what other people see.