Skip to content

Commit

Permalink
add diffusers
Browse files Browse the repository at this point in the history
  • Loading branch information
bwp91 committed Dec 28, 2023
1 parent 8022298 commit 5b45ede
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 1 deletion.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ This project tries to adhere to [Semantic Versioning](http://semver.org/). In pr
### Added

- Support for `H6004`, `H601D`, `H70A1`, `H706A` lights
- Support for `H7173`, `H7175` kettle
- Support for `H7173`, `H7175` kettles
- Support for `H7161`, `H7162` diffusers

### Changed

Expand Down
48 changes: 48 additions & 0 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1268,6 +1268,36 @@
}
}
},
"diffuserDevices": {
"title": "Diffuser Devices",
"description": "Optional settings for Govee Diffuser devices.",
"type": "array",
"items": {
"type": "object",
"properties": {
"label": {
"title": "Label",
"type": "string",
"description": "This setting is only used for config identification."
},
"deviceId": {
"title": "Device ID",
"type": "string",
"description": "Enter the 23 digit Govee Device ID to begin (e.g. 12:AB:A1:C5:A8:99:D2:17).",
"minLength": 23,
"maxLength": 23
},
"ignoreDevice": {
"type": "boolean",
"title": "Hide From HomeKit",
"description": "If true, this accessory will be removed and ignored from HomeKit.",
"condition": {
"functionBody": "return (model.diffuserDevices && model.diffuserDevices[arrayIndices] && model.diffuserDevices[arrayIndices].deviceId && model.diffuserDevices[arrayIndices].deviceId.length === 23);"
}
}
}
}
},
"kettleDevices": {
"title": "Kettle Devices",
"description": "Optional settings for Govee Kettle devices.",
Expand Down Expand Up @@ -1633,6 +1663,24 @@
}
]
},
{
"key": "diffuserDevices",
"expandable": true,
"title": "Diffuser Devices",
"description": "Optional settings for Govee Diffuser devices.",
"add": "Add Another Device",
"type": "array",
"items": [
{
"type": "fieldset",
"items": [
"diffuserDevices[].label",
"diffuserDevices[].deviceId",
"diffuserDevices[].ignoreDevice"
]
}
]
},
{
"key": "kettleDevices",
"expandable": true,
Expand Down
181 changes: 181 additions & 0 deletions lib/device/diffuser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import {
base64ToHex,
getTwoItemPosition,
hexToTwoItems,
parseError,
} 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;

// Rotation speed to value in {1, 2, ..., 8}
this.speed2Value = (speed) => Math.min(Math.max(parseInt(Math.round(speed / 10), 10), 1), 9);

// Speed codes
this.value2Code = {
1: 'MwUBAQAAAAAAAAAAAAAAAAAAADY=',
2: 'MwUBAgAAAAAAAAAAAAAAAAAAADU=',
3: 'MwUBAwAAAAAAAAAAAAAAAAAAADQ=',
4: 'MwUBBAAAAAAAAAAAAAAAAAAAADM=',
5: 'MwUBBQAAAAAAAAAAAAAAAAAAADI=',
6: 'MwUBBgAAAAAAAAAAAAAAAAAAADE=',
7: 'MwUBBwAAAAAAAAAAAAAAAAAAADA=',
8: 'MwUBCAAAAAAAAAAAAAAAAAAAAD8=',
9: 'MwUBCQAAAAAAAAAAAAAAAAAAAD4=',
};

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

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

// 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.On, this.cacheState === 'on');
}, 2000);
throw new this.hapErr(-70402);
}
}

async internalSpeedUpdate(value) {
try {
// Don't continue if the speed is <=10
if (value === 0 || value === 10) {
return;
}

// Get the single Govee value {1, 2, ..., 8}
const newValue = this.speed2Value(value);

// Don't continue if the speed value won't have effect
if (newValue * 10 === this.cacheSpeed) {
return;
}

// Get the scene code for this value
const newCode = this.value2Code[newValue];

this.accessory.log(newCode);

// Send the request to the platform sender function
await this.platform.sendDeviceUpdate(this.accessory, {
cmd: 'ptReal',
value: newCode,
});

// Cache the new state and log if appropriate
this.cacheSpeed = newValue * 10;
this.accessory.log(`${platformLang.curSpeed} [${newValue}]`);
} 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.RotationSpeed, this.cacheSpeed);
}, 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.On, this.cacheState === 'on');

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

// Check for some other scene/mode change
(params.commands || []).forEach((command) => {
const hexString = base64ToHex(command);
const hexParts = hexToTwoItems(hexString);

// Return now if not a device query update code
if (getTwoItemPosition(hexParts, 1) !== 'aa') {
return;
}

const deviceFunction = `${getTwoItemPosition(hexParts, 2)}${getTwoItemPosition(hexParts, 3)}`;

switch (deviceFunction) {
case '0500': { // mode
// Mode
const newModeRaw = getTwoItemPosition(hexParts, 4);
let newMode;
switch (newModeRaw) {
case '01': {
// Manual
newMode = 'manual';
break;
}
case '02': {
// Custom
newMode = 'custom';
break;
}
case '03': {
// Auto
newMode = 'auto';
break;
}
default:
return;
}
if (this.cacheMode !== newMode) {
this.cacheMode = newMode;
this.accessory.log(`${platformLang.curMode} [${this.cacheMode}]`);
}
break;
}
default:
this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`);
break;
}
});
}
}
2 changes: 2 additions & 0 deletions lib/device/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import deviceCoolerSingle from './cooler-single.js';
import deviceDehumidifier from './dehumidifier.js';
import deviceDiffuser from './diffuser.js';
import deviceFan from './fan.js';
import deviceHeaterSingle from './heater-single.js';
import deviceHeater1A from './heater1a.js';
Expand Down Expand Up @@ -36,6 +37,7 @@ import deviceValveSingle from './valve-single.js';
export default {
deviceCoolerSingle,
deviceDehumidifier,
deviceDiffuser,
deviceFan,
deviceHeaterSingle,
deviceHeater1A,
Expand Down
6 changes: 6 additions & 0 deletions lib/platform.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export default class {
case 'leakDevices':
case 'lightDevices':
case 'purifierDevices':
case 'diffuserDevices':
case 'switchDevices':
case 'thermoDevices':
if (Array.isArray(val) && val.length > 0) {
Expand Down Expand Up @@ -1012,6 +1013,11 @@ export default class {
}
doAWSPolling = true;
accessory = devicesInHB.get(uuid) || this.addAccessory(device);
} else if (platformConsts.models.diffuser.includes(device.model)) {
// Device is a diffuser
devInstance = deviceTypes.deviceDiffuser;
doAWSPolling = true;
accessory = devicesInHB.get(uuid) || this.addAccessory(device);
} else if (platformConsts.models.sensorButton.includes(device.model)) {
// Device is a button
devInstance = deviceTypes.deviceSensorButton;
Expand Down
4 changes: 4 additions & 0 deletions lib/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export default {
heaterDevices: [],
dehumidifierDevices: [],
humidifierDevices: [],
purifierDevices: [],
diffuserDevices: [],
kettleDevices: [],
iceMakerDevices: [],
platform: 'Govee',
Expand Down Expand Up @@ -93,6 +95,7 @@ export default {
humidifierDevices: ['label', 'deviceId', 'ignoreDevice'],
dehumidifierDevices: ['label', 'deviceId', 'ignoreDevice'],
purifierDevices: ['label', 'deviceId', 'ignoreDevice'],
diffuserDevices: ['label', 'deviceId', 'ignoreDevice'],
kettleDevices: [
'label',
'deviceId',
Expand Down Expand Up @@ -360,6 +363,7 @@ export default {
dehumidifier: ['H7150', 'H7151'],
humidifier: ['H7140', 'H7141', 'H7142', 'H7143', 'H7160'],
purifier: ['H7120', 'H7121', 'H7122', 'H7123', 'H7126'],
diffuser: ['H7161', 'H7162'],
iceMaker: ['H7172'],
sensorButton: ['H5122'],
sensorContact: ['H5123'],
Expand Down

0 comments on commit 5b45ede

Please sign in to comment.