bwp91 / homebridge-govee

Homebridge plugin to integrate Govee devices into HomeKit.
MIT License
463 stars 48 forks source link

Code editing proposal for adding base support to Smart Aroma Diffuser H7161 (and maybe also H7162) #692

Closed claudiomorabito closed 7 months ago

claudiomorabito commented 8 months ago

Hello, since Vocolinc (who natively supports Homekit) exposes their aroma diffuser as humidifier, I gave it a try and edited your code in my hoobs environment and exposed the H7161 (I bet it works also with H7162) device as an humidifier and it does work fine, at least just for turning it on and off and then using it in scenes and automations.

Screenshot 2023-12-29 alle 01 11 42

May I ask you if you can consider releasing a BETA version where you expose H7161 as an humidifier so that we can test it further?

Here is what I did but you will surely do it better since it's your code.

Added H7161 in lib/utils/constants.js file humidifier: ['H7140', 'H7141', 'H7142', 'H7143', 'H7160', 'H7161'],

Added H7161 in lib/device/index.js import deviceDiffuserH7161 from './diffuser-h7161.js'; (...) export default { deviceDiffuserH7161,

Added H7161 in platform.js in the initialiseDevice() method

 case 'H7161':
   devInstance = deviceTypes.deviceDiffuserH7161;
   break;

Created the file lib/device/diffuser-h7161.js by copying it from 7060 then edited it to fit the humidifier template

import { hs2rgb, rgb2hs } from '../utils/colour.js';
import {
  base64ToHex,
  generateCodeFromHexValues,
  generateRandomString,
  getTwoItemPosition,
  hexToDecimal,
  hexToTwoItems,
  parseError,
  sleep,
} from '../utils/functions.js';
import platformLang from '../utils/lang-en.js';

export default class {
  constructor(platform, accessory) {
    // Set up variables from the platform
    this.hapChar = platform.api.hap.Characteristic;
    this.hapErr = platform.api.hap.HapStatusError;
    this.hapServ = platform.api.hap.Service;
    this.platform = platform;

    // Set up variables from the accessory
    this.accessory = accessory;

    // Add the Humidifier service if it doesn't already exist
    this.service = this.accessory.getService(this.hapServ.HumidifierDehumidifier) || this.accessory.addService(this.hapServ.HumidifierDehumidifier);

    // Add the set handler to the Active characteristic
    this.service
      .getCharacteristic(this.hapChar.Active)
      .onSet(async (value) => this.internalStateUpdate(value));
    this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value ? 'on' : 'off';

    // Always return 50% relative humidity so it does not mess with other HK sensors
    this.service
      .getCharacteristic(this.hapChar.CurrentRelativeHumidity)
      .onGet(async () => {
        return 50;
      });

    // Output the customised options to the log
    const opts = JSON.stringify({});
    platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts);
  }

  async internalStateUpdate(value) {
    try {
      const newValue = value ? 'on' : 'off';

      // Don't continue if the new value is the same as before
      if (this.cacheState === newValue) {
        return;
      }

      // Send the request to the platform sender function
      await this.platform.sendDeviceUpdate(this.accessory, {
        cmd: 'stateHumi',
        value: value ? 1 : 0,
      });

      // Cache the new state and log if appropriate
      if (this.cacheState !== newValue) {
        this.cacheState = newValue;
        this.accessory.log(`${platformLang.curState} [${this.cacheState}]`);
      }
    } catch (err) {
      // Catch any errors during the process
      this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`);

      // Throw a 'no response' error and set a timeout to revert this after 2 seconds
      setTimeout(() => {
        this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on');
      }, 2000);
      throw new this.hapErr(-70402);
    }
  }

  externalUpdate(params) {
    // Check for an ON/OFF change
    if (params.state && params.state !== this.cacheState) {
      this.cacheState = params.state;
      this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on');

      // Log the change
      this.accessory.log(`${platformLang.curState} [${this.cacheState}]`);
    }
  }
}

The above was enough to make it work, it is exposed as a humidifier and it can be turned on or off (so I can add it to scenes and automations, I think that it is better than nothing for now). I did not implemented handlers for CurrentHumidifierDehumidifierState, TargetHumidifierDehumidifierState nor CurrentRelativeHumidity that I don't think this device has (those values/functions are not available neither in the Govee app for this diffuser).

The Govee App has some pre-defined light and music scenes but I see them as a native feature, like Nanoleaf's light scenes, that can be set up only from their app (or by customized scenes to be created with third party apps), so I don't expect to be able to control them via the home app.

By the way, I hope to have been helpful in someway (surely there could be something to do to fine-tune it before releasing it).

Which model is this? Need the Hxxxx number. H7161

Which type of device is this? Light, heater, leak sensor, etc. Smart Aroma Diffuser Pro

Which connection methods does this device have? Wifi? Bluetooth? LAN mode? All? Bluetooth and WiFi

Anything else? "model":"H7161" "groupId":14288562 "sku":"H7161" "spec":"" "versionHard":"1.02.00" "versionSoft":"1.00.12" "deviceName":"Smart Aroma Diffuser Pro" "bleName":"ihoment_H7161_364B" "waterShortageOnOff":1 "mcuSoftVersion":"1.00.08" "boilWaterCompletedNotiOnOff":1 "completionNotiOnOff":1 "autoShutDownOnOff":1 "skuUrl":"https://d1f2504ijhdyjw.cloudfront.net/sku-img/f360ab49a180a08f52446536f4c029f3-add_list_type_device_7161.png" "headOnImg":"" "headOffImg":"" "ext":"" "ic":0 "pactType":1 "pactCode":1 "share":0 "goodsType":146 "attributesId":65 "supportScene":0

claudiomorabito commented 8 months ago

Hello @bwp91 , I edited the diffuser.js code in the original post some hours ago, can you please check if it would fit the beta better than the previous one? The mode case block can be removed since the device works in auto mode only. Moreover I thought it would be better exposed as an humidifier (even if it has not an humidity sensor… maybe we can return a dummy 50% value ?) lice Vocolinc’s aroma diffusers, however I understand that with a fan there wouldn’t be any need to handle the relative humidity value and, of course, the final decision is up to you. :))

And thank you for the quick implementation and support, as I saw that a new beta was released just a few hours ago! :)

bwp91 commented 8 months ago

Is the request for the humidifier just so it has a nicer icon in the home app than a fan?

claudiomorabito commented 8 months ago

Is the request for the humidifier just so it has a nicer icon in the home app than a fan?

Hello, other than the icon, I was suggesting it because I saw that all the other aroma diffusers that have native HomeKit support (Vocolinc, Meross, Airversa) are exposed as humidifiers in HomeKit so I thought to give you that suggestion only for what it seems to be a sort of "standard" for that class of devices :)

But, as I said, it is just a suggestion. :)

Just in the case you think it could be feasible, maybe this code fragment could be useful to solve the relative humidity sensor missing value, since I tested that setting it to 50 doesn't make the device mess with the other humidity sensors (i.e.: the air conditioner's one) that are in the same room because Siri checks the lowest and the highest values and 40-50 is considered optimal.

    // Always return 50% relative humidity that is considered an optimal value
    this.service
      .getCharacteristic(this.hapChar.CurrentRelativeHumidity)
      .onGet(async () => {
        return 50;
      });

And just to spend a few more words on the device, I was watching some reviews on YouTube and it seems that also Alexa is only able to turn it on and off with voice commands. :)

Thanks again for the support, the fast implementation you've done and the great plugins you're releasing!

bwp91 commented 8 months ago

Hey, can you show a screenshot of the main device page for this model in the govee app? Does it have other controls apart from just on and off?

claudiomorabito commented 8 months ago

Hey, can you show a screenshot of the main device page for this model in the govee app? Does it have other controls apart from just on and off?

Hello, here there are the screenshots of the main device page. There is just an ON/OFF button and a "water shortage" sensor that just says that water is not enough (in this case, there is no water in the device at the moment).

Then, only if there is water in the device, there is the possibility to choose among some predefined scenes. Scenes can also be changed by pressing the only button available on the device.

The bottom controls are for scheduling, setting a timer for turning it off and enabling/disabling the led on the front of the device that indicates if it's on (white), off or there is no water (red).

Hope to have been helpful, please let me know should you need further details! :)

IMG_9230

IMG_9231

IMG_9232

bwp91 commented 8 months ago

I just pushed a new beta. And I change the type from a fan to a purifier.

i know a purifier is still not technically the correct accessory type, but with a few tiny hacks I have been able to implement this type in the past, working with a simple on/off, without all the extra unnecessary sensors (I don't like the idea of implementing accessory types with sensors that show incorrect data)

claudiomorabito commented 8 months ago

I just pushed a new beta. And I change the type from a fan to a purifier.

i know a purifier is still not technically the correct accessory type, but with a few tiny hacks I have been able to implement this type in the past, working with a simple on/off, without all the extra unnecessary sensors (I don't like the idea of implementing accessory types with sensors that show incorrect data)

great, thanks! it works fine! :))