sajmonr / homebridge-hubspace

Homebridge plugin for controlling Hubspace accessories.
Apache License 2.0
9 stars 9 forks source link

Add Support for Hubspace Lightbulbs #18

Closed XenuIsWatching closed 1 year ago

XenuIsWatching commented 1 year ago

This adds support for Hubspace Lightbulbs. So far only tested on the BR30 Color CCT Light and A19 Color CCT Light. This also adds a Boolean in the configuration to instance a "second" virtual lightbulb in homekit which will be in the Temperature (Mired/Kelvin) Color Space. It was found that while in the RGB color mode. The whites while in the RGB color mode where not as white as white in the Temperature Color space. When ever the use makes a change to the temperature lightbulb it which change over the temperature color space and then send over the kelvin value.

This also fixes an issue with sending and receiving hex values that are greater than a single byte where the bytes were being sent in reverse order.

This also generalizes the function response to not conflict with the similar fan lights attributeIds which have the same functionClass name such as brightness.

TODOs:

XenuIsWatching commented 1 year ago

The only bulb that I've tested with are these

sajmonr commented 1 year ago

Thanks for your input! I don't have access to the lightbulb that you are talking about. Are you saying that the attribute IDs are the same as the IDs for the fan? For example, in your code attribute ID 4 is light color and in the original code it is light brightness. If that's the case, we will need to figure out how to reconcile that - I assumed that the attribute IDs are unique. Could you post your JSON output from the metadevice HTTP call, please? Thanks.

XenuIsWatching commented 1 year ago

Here it is with the 'id' censored with a5. https://gist.github.com/XenuIsWatching/c85b053269df38dce570a73f099b1645

sajmonr commented 1 year ago

Thanks. Looking at the response it seems that while the attribute IDs are indeed the same, they are in different "categories". So, it should not interfere with one another, and the device discovery function should pick up what functions are available based on the response from the API. I went out and bought the lightbulb so I should have more understanding of it.

XenuIsWatching commented 1 year ago

ok, I was able to get Hue and Saturation working for the Lightbulb there still is an issue (which i think is with concurrency) where If I'm swiping on the color wheel in the homekit app, values are sent over rapidly and error can be given

[Hubspace] The remote service returned an error. The device is not available

Maybe a lock? is needed around the http request to prevent the app from sending requests faster than the server can respond. Probably the server doesn't like multiple concurrent requests to set values being sent while it is still working one....

XenuIsWatching commented 1 year ago

experimenting with a lock, showed that they just all queue up around the http function and will eventually timeout because so many are sent that the server can handle...

Maybe a debounce approach would work better where if multiple requests are coming in for a specific function, it waits to finish the current POST request and then only sends the most recent request while abanding all the request that came in between...

XenuIsWatching commented 1 year ago

Thanks. Looking at the response it seems that while the attribute IDs are indeed the same, they are in different "categories". So, it should not interfere with one another, and the device discovery function should pick up what functions are available based on the response from the API. I went out and bought the lightbulb so I should have more understanding of it.

Where are you seeing they are are the same, under the feature "power", I see "attribute" with a "key" of 1 and "brightness" with a key of 2. I haven't seen how that fan looks... but that's different than what I saw in the existing code with a Key of "2" and "4" respectively.

sajmonr commented 1 year ago

experimenting with a lock, showed that they just all queue up around the http function and will eventually timeout because so many are sent that the server can handle...

Maybe a debounce approach would work better where if multiple requests are coming in for a specific function, it waits to finish the current POST request and then only sends the most recent request while abanding all the request that came in between...

I like the debounce approach. I was wondering the same thing when I was implementing the brightness feature of the light switch. However, in that case it didn't seem to cause any issues. But obviously there is way more values being sent while picking a color.

sajmonr commented 1 year ago

Thanks. Looking at the response it seems that while the attribute IDs are indeed the same, they are in different "categories". So, it should not interfere with one another, and the device discovery function should pick up what functions are available based on the response from the API. I went out and bought the lightbulb so I should have more understanding of it.

Where are you seeing they are are the same, under the feature "power", I see "attribute" with a "key" of 1 and "brightness" with a key of 2. I haven't seen how that fan looks... but that's different than what I saw in the existing code with a Key of "2" and "4" respectively.

What I meant by that is that the attribute IDs can be the same for different devices. For example, color temperature has attribute ID 3 and fan power has also attribute ID 3, however, they belong to different function class/function instance. I am currently implementing an outlet functionality and I am seeing the same behavior. The outlet has an on/off attribute ID 2 which is the same as light power attribute ID. However, since they are in different categories the device discovery service is able to differentiate between the two. Here is the device function definition for each:

    {
        type: DeviceFunction.LightPower,
        attributeId: 2,
        functionClass: 'power',
        functionInstanceName: 'light-power'
    },
    {
        type: DeviceFunction.OutletPower,
        attributeId: 2,
        functionClass: 'power'
    }
XenuIsWatching commented 1 year ago

Thanks. Looking at the response it seems that while the attribute IDs are indeed the same, they are in different "categories". So, it should not interfere with one another, and the device discovery function should pick up what functions are available based on the response from the API. I went out and bought the lightbulb so I should have more understanding of it.

Where are you seeing they are are the same, under the feature "power", I see "attribute" with a "key" of 1 and "brightness" with a key of 2. I haven't seen how that fan looks... but that's different than what I saw in the existing code with a Key of "2" and "4" respectively.

What I meant by that is that the attribute IDs can be the same for different devices. For example, color temperature has attribute ID 3 and fan power has also attribute ID 3, however, they belong to different function class/function instance. I am currently implementing an outlet functionality and I am seeing the same behavior. The outlet has an on/off attribute ID 2 which is the same as light power attribute ID. However, since they are in different categories the device discovery service is able to differentiate between the two. Here is the device function definition for each:

    {
        type: DeviceFunction.LightPower,
        attributeId: 2,
        functionClass: 'power',
        functionInstanceName: 'light-power'
    },
    {
        type: DeviceFunction.OutletPower,
        attributeId: 2,
        functionClass: 'power'
    }

Yes, but then it will match the fan brightness attribute as that comes sooner in DeviceFunctions as they both have the 'brightness' function class the same and they have different attribute ids.

sajmonr commented 1 year ago

Hmm. I see what you are saying - I did not look at the brightness class at all. It looks like I'll have to rewrite the discovery API to be more dynamic. I bought a power strip over the weekend while implementing a smart plug and was looking at the values and saw some limitations as well. 😔 I need to get more devices from HD to look at the values. When I was first implementing it I only had one device available to me and I assumed that the IDs are unique - which looks like that was a dumb assumption. I'll create a new issue for this, but I need to resolve this sooner than later. Please feel free to post your ideas there. I'm sure other devices will bring up more surprises. I'm also fairly certain that the rewrite will impact your work (hopefully it will make it easier). I apologize for it in advance.

XenuIsWatching commented 1 year ago

Alright, I ran into another surprise with the Light Bulb Color Modes. Apparenlty the white value, 0xFFFFFF, isn't as white as a white is on the kelvin/mired scale. There is an api to switch between these two scales with the function "color-mode", but it appears homekit isn't that friendly where it sends over hue and saturation for RGB lights (even when you're in the temperature wheel) and mired values for Whites-only lightbulbs. Playing around in Hubspace app show that these two color ""spaces"" are quite different. I played around a bit by adding and additional boolean config to have dual lightbulbs show up in homekit where one supports RGB and the other only has temperature options which changes it over to the "temperature" color mode. When ever the use makes an input on the color lightbulb, it switches the color mode to rgb and and then sends over rgb values, and when the user makes an input on the temperature lightbulb it switches to the temperature color mode and sends over the kelvin value.... It can make the home app appear cluttery... but I really can think of any other way to support both color spaces in a single bulb item due to the limitations with HomeKit. https://github.com/XenuIsWatching/homebridge-hubspace/tree/lightbulb-dual-color

sajmonr commented 1 year ago

The whole API seems weird. I'm sure they had their reasons for implementing it that way. I'm looking at the power strip that has 4 outlets and a main power switch, but only the main power switch is labeled as "power". The other outlets are labeled as "toggle-1", "toggle-2", etc... That makes it somewhat difficult to generalize it.

XenuIsWatching commented 1 year ago

alright, well... I believe I was able to refactor it to make it more generalized to not interfere with fans (but I don't have a fan to test). Are you able to test out this branch with you're fan? I've also taken it out of draft as I now believe it's ready

sajmonr commented 1 year ago

Hi, sorry for the late reply. We had some rush at work and on top of that I wasn't feeling well. Anyways, thanks for all the work. I'll try to look at it this weekend.

sajmonr commented 1 year ago

I just managed to look at your code. It's throwing errors on start - it looks like it has to do with the modifications to the function definitions. See the errors below:

[3/29/2023, 8:32:57 PM] Homebridge v1.6.0 (HAP v0.11.0) (Homebridge CAD8) is running on port 60281.
    at new Promise (<anonymous>)
    at Object.__awaiter (D:\repos\testing\homebridge-hubspace\node_modules\tslib\tslib.js:163:16)
    at Active.Characteristic.handleGetRequest (D:\repos\testing\homebridge-hubspace\node_modules\hap-nodejs\dist\lib\Characteristic.js:736:24)
    at Active.<anonymous> (D:\repos\testing\homebridge-hubspace\node_modules\hap-nodejs\src\lib\Characteristic.ts:3076:22)
[3/29/2023, 8:32:58 PM] [homebridge-hubspace] This plugin threw an error from the characteristic 'On': Unhandled error thrown inside read handler for characteristic: Failed to get function definition for 'power'. Each function requires to set a definition.. See https://homebridge.io/w/JtMGR for more info.[3/29/2023, 8:32:58 PM] [homebridge-hubspace] Error: Failed to get function definition for 'power'. Each function requires to set a definition.
    at getDeviceFunctionDef (D:\repos\testing\homebridge-hubspace\src\models\device-functions.ts:68:15)
    at LightAccessory.getOn (D:\repos\testing\homebridge-hubspace\src\accessories\light-accessory.ts:68:42)
    at On.<anonymous> (D:\repos\testing\homebridge-hubspace\node_modules\hap-nodejs\src\lib\Characteristic.ts:2399:32)
    at step (D:\repos\testing\homebridge-hubspace\node_modules\tslib\tslib.js:193:27)
    at Object.next (D:\repos\testing\homebridge-hubspace\node_modules\tslib\tslib.js:174:57)
    at D:\repos\testing\homebridge-hubspace\node_modules\tslib\tslib.js:167:75
    at new Promise (<anonymous>)
    at Object.__awaiter (D:\repos\testing\homebridge-hubspace\node_modules\tslib\tslib.js:163:16)
    at On.Characteristic.handleGetRequest (D:\repos\testing\homebridge-hubspace\node_modules\hap-nodejs\dist\lib\Characteristic.js:736:24)
    at On.<anonymous> (D:\repos\testing\homebridge-hubspace\node_modules\hap-nodejs\src\lib\Characteristic.ts:3076:22)

I also noticed there are some linter issues. You can run npm run prepublishOnly to check for those. I didn't look at anything else TBH. I need to start working on modifying the function definitions to be more flexible, however, unexpected things came up at work and I haven't even started on it. I thought I will have it done by now...

XenuIsWatching commented 1 year ago

I just managed to look at your code. It's throwing errors on start - it looks like it has to do with the modifications to the function definitions. See the errors below:

[3/29/2023, 8:32:57 PM] Homebridge v1.6.0 (HAP v0.11.0) (Homebridge CAD8) is running on port 60281.
    at new Promise (<anonymous>)
    at Object.__awaiter (D:\repos\testing\homebridge-hubspace\node_modules\tslib\tslib.js:163:16)
    at Active.Characteristic.handleGetRequest (D:\repos\testing\homebridge-hubspace\node_modules\hap-nodejs\dist\lib\Characteristic.js:736:24)
    at Active.<anonymous> (D:\repos\testing\homebridge-hubspace\node_modules\hap-nodejs\src\lib\Characteristic.ts:3076:22)
[3/29/2023, 8:32:58 PM] [homebridge-hubspace] This plugin threw an error from the characteristic 'On': Unhandled error thrown inside read handler for characteristic: Failed to get function definition for 'power'. Each function requires to set a definition.. See https://homebridge.io/w/JtMGR for more info.[3/29/2023, 8:32:58 PM] [homebridge-hubspace] Error: Failed to get function definition for 'power'. Each function requires to set a definition.
    at getDeviceFunctionDef (D:\repos\testing\homebridge-hubspace\src\models\device-functions.ts:68:15)
    at LightAccessory.getOn (D:\repos\testing\homebridge-hubspace\src\accessories\light-accessory.ts:68:42)
    at On.<anonymous> (D:\repos\testing\homebridge-hubspace\node_modules\hap-nodejs\src\lib\Characteristic.ts:2399:32)
    at step (D:\repos\testing\homebridge-hubspace\node_modules\tslib\tslib.js:193:27)
    at Object.next (D:\repos\testing\homebridge-hubspace\node_modules\tslib\tslib.js:174:57)
    at D:\repos\testing\homebridge-hubspace\node_modules\tslib\tslib.js:167:75
    at new Promise (<anonymous>)
    at Object.__awaiter (D:\repos\testing\homebridge-hubspace\node_modules\tslib\tslib.js:163:16)
    at On.Characteristic.handleGetRequest (D:\repos\testing\homebridge-hubspace\node_modules\hap-nodejs\dist\lib\Characteristic.js:736:24)
    at On.<anonymous> (D:\repos\testing\homebridge-hubspace\node_modules\hap-nodejs\src\lib\Characteristic.ts:3076:22)

I also noticed there are some linter issues. You can run npm run prepublishOnly to check for those. I didn't look at anything else TBH. I need to start working on modifying the function definitions to be more flexible, however, unexpected things came up at work and I haven't even started on it. I thought I will have it done by now...

darn... I assume this is with your fan. It probably has to deal with the LightPower and FanPower not being found in the script by getSupportedFunctionsFromResponse

Do you post your JSON output from the metadevice HTTP call?

XenuIsWatching commented 1 year ago

ok, I took a look at it and I noticed that I had the functionInstanceName set wrong for the light if it's a fan. Would you be able to try again?

I also got the prepublish issues fixed