diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 74087926..6ed621df 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -47,7 +47,7 @@ jobs: uses: actions/checkout@v4 - name: Use Node.js 18.x - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 18.x cache: 'npm' @@ -94,7 +94,7 @@ jobs: uses: actions/checkout@v4 - name: Use Node.js 18.x - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 18.x cache: 'npm' diff --git a/docs/USAGE.md b/docs/USAGE.md index 80429095..54929d97 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -24,9 +24,13 @@ If you have any issues, questions or an idea for additional features, please tak ## Latest Changes ::: tip State of this document -This documentation refers to release [v3.7.0](https://github.com/openhab/openhab-google-assistant/releases/tag/v3.7.0) of [openHAB Google Assistant](https://github.com/openhab/openhab-google-assistant) published on 2023-10-10 +This documentation refers to release [v3.8.1](https://github.com/openhab/openhab-google-assistant/releases/tag/v3.8.1) of [openHAB Google Assistant](https://github.com/openhab/openhab-google-assistant) published on 2023-11-26 ::: +### v3.8.0 + +- Added `colorTemperatureInverted=true` option to [`SpecialColorLight`](#light-as-group-with-separate-controls) allowing users to invert the percentage to Kelvin conversion for the `lightColorTemperature` item + ### v3.7.0 - Adjusted [`Fan`](#fan-hood-airpurifier) to use `supportsFanSpeedPercent` option @@ -99,7 +103,12 @@ Color { ga="Light" [ colorTemperatureRange="2000,9000" ] } | **Device Type** | [Light](https://developers.home.google.com/cloud-to-cloud/guides/light) | | **Supported Traits** | [OnOff](https://developers.home.google.com/cloud-to-cloud/traits/onoff), [ColorSetting](https://developers.home.google.com/cloud-to-cloud/traits/colorsetting), [Brightness](https://developers.home.google.com/cloud-to-cloud/traits/brightness) | | **Supported Items** | Group as `SpecialColorLight` with the following members:
(optional) Number or Dimmer as `lightBrightness`
(optional) Number or Dimmer as `lightColorTemperature`
(optional) Color as `lightColor`
(optional) Switch as `lightPower` | -| **Configuration** | (optional) `colorUnit="percent/kelvin/mired"`
(optional) `checkState=true/false`
(optional) `colorTemperatureRange="minK,maxK"`
_Hint: if you want to use `lightColorTemperature`, you must either set `colorUnit` to `kelvin` or `mired` or define a `colorTemperatureRange`, because `colorUnit` defaults to `percent`_ | +| **Configuration** | (optional) `colorUnit="percent/kelvin/mired"`
(optional) `checkState=true/false`
(optional) `colorTemperatureRange="minK,maxK"`
(optional) `colorTemperatureInverted=true/false` | + +**Important Hint:** If you want to use `lightColorTemperature`, you must either set `colorUnit` to `kelvin` or `mired` or define a `colorTemperatureRange`, because `colorUnit` defaults to `percent`. + +If you use `colorUnit` as percentage values, the lowest color temperature (warm light) will be converted to 0%, and correspondingly the highest color temperature (cold light) will be converted to 100%. +If you need the inverted values for your device, you can set `colorTemperatureInverted=true`. This will convert low Kelvin values to high percentage values and vice versa. ```shell Group lightGroup { ga="SpecialColorLight" [ colorUnit="kelvin", colorTemperatureRange="2000,9000" ] } @@ -109,6 +118,14 @@ Color colorItem (lightGroup) { ga="lightColor" } Number colorTemperatureItem (lightGroup) { ga="lightColorTemperature" } ``` +Example of a light device where low Kelvin values (warm light) are represented by high percentage values in the color temperature item: + +```shell +Group lightGroup { ga="SpecialColorLight" [ colorUnit="percent", colorTemperatureRange="2000,6500", colorTemperatureInverted=true ] } +Dimmer brightnessItem (lightGroup) { ga="lightBrightness" } +Dimmer colorTemperatureItem (lightGroup) { ga="lightColorTemperature" } +``` + In case you want to control multiple lights using one device with Google Assistant, you can apply the following pattern: ```shell diff --git a/functions/commands/colorabsolutetemperature.js b/functions/commands/colorabsolutetemperature.js index f5859d1c..db63e6ef 100644 --- a/functions/commands/colorabsolutetemperature.js +++ b/functions/commands/colorabsolutetemperature.js @@ -42,7 +42,11 @@ class ColorAbsoluteTemperature extends DefaultCommand { return convertMired(params.color.temperature).toString(); } const { temperatureMinK, temperatureMaxK } = SpecialColorLight.getAttributes(item).colorTemperatureRange; - return (((params.color.temperature - temperatureMinK) / (temperatureMaxK - temperatureMinK)) * 100).toString(); + let percent = ((params.color.temperature - temperatureMinK) / (temperatureMaxK - temperatureMinK)) * 100; + if (SpecialColorLight.getColorTemperatureInverted(item)) { + percent = 100 - percent; + } + return percent.toString(); } catch (error) { return '0'; } diff --git a/functions/commands/default.js b/functions/commands/default.js index 2f4f72b8..9344e821 100644 --- a/functions/commands/default.js +++ b/functions/commands/default.js @@ -285,10 +285,10 @@ class DefaultCommand { typeof error.errorCode === 'string' ? error.errorCode : error.statusCode == 404 - ? 'deviceNotFound' - : error.statusCode == 400 - ? 'notSupported' - : 'deviceOffline' + ? 'deviceNotFound' + : error.statusCode == 400 + ? 'notSupported' + : 'deviceOffline' }); }); }); diff --git a/functions/devices/charger.js b/functions/devices/charger.js index dd60a751..c2f00111 100644 --- a/functions/devices/charger.js +++ b/functions/devices/charger.js @@ -36,7 +36,7 @@ class Charger extends DefaultDevice { state.isPluggedIn = members[member].state === 'ON'; break; case 'chargerCapacityRemaining': { - const capacity = Math.round(Number(members[member].state)); + const capacity = Math.round(parseFloat(members[member].state)); if (!config.unit || config.unit === 'PERCENTAGE') { let descCapacity = 'UNKNOWN'; if (capacity <= 10) { @@ -64,7 +64,7 @@ class Charger extends DefaultDevice { state.capacityUntilFull = [ { unit: config.unit || 'PERCENTAGE', - rawValue: Math.round(Number(members[member].state)) + rawValue: Math.round(parseFloat(members[member].state)) } ]; break; diff --git a/functions/devices/climatesensor.js b/functions/devices/climatesensor.js index 29aa0aed..c6cf7c64 100644 --- a/functions/devices/climatesensor.js +++ b/functions/devices/climatesensor.js @@ -56,7 +56,7 @@ class ClimateSensor extends DefaultDevice { state.temperatureSetpointCelsius = temperature; } if ('humidityAmbient' in members) { - const humidity = Math.round(Number(members.humidityAmbient.state)); + const humidity = Math.round(parseFloat(members.humidityAmbient.state)); state.humidityAmbientPercent = humidity; state.humiditySetpointPercent = humidity; } diff --git a/functions/devices/default.js b/functions/devices/default.js index 160785a2..96de4001 100644 --- a/functions/devices/default.js +++ b/functions/devices/default.js @@ -93,13 +93,13 @@ class DefaultDevice { itemType: itemType } }; - if (config.inverted === true) { + if (!!config.inverted === true) { metadata.customData.inverted = true; } - if (config.checkState === true) { + if (!!config.checkState === true) { metadata.customData.checkState = true; } - if (config.ackNeeded === true || config.tfaAck === true) { + if (!!config.ackNeeded === true || !!config.tfaAck === true) { metadata.customData.ackNeeded = true; } if (typeof config.pinNeeded === 'string' || typeof config.tfaPin === 'string') { diff --git a/functions/devices/dimmablelight.js b/functions/devices/dimmablelight.js index 6ef330e5..105bbb75 100644 --- a/functions/devices/dimmablelight.js +++ b/functions/devices/dimmablelight.js @@ -14,7 +14,7 @@ class DimmableLight extends DefaultDevice { } static getState(item) { - const brightness = Math.round(Number(item.state)) || 0; + const brightness = Math.round(parseFloat(item.state)) || 0; return { on: brightness > 0, brightness: brightness diff --git a/functions/devices/fan.js b/functions/devices/fan.js index 9afbf5d8..86f531e0 100644 --- a/functions/devices/fan.js +++ b/functions/devices/fan.js @@ -51,14 +51,15 @@ class Fan extends DefaultDevice { } static getState(item) { + const itemState = Math.round(parseFloat(item.state)); const state = { - on: Number(item.state) > 0 + on: itemState > 0 }; const config = this.getConfig(item); if (config && config.speeds) { - state.currentFanSpeedSetting = item.state.toString(); + state.currentFanSpeedSetting = itemState.toString(); } else { - state.currentFanSpeedPercent = Math.round(Number(item.state)); + state.currentFanSpeedPercent = itemState; } return state; } diff --git a/functions/devices/humiditysensor.js b/functions/devices/humiditysensor.js index 70ffe318..017229c9 100644 --- a/functions/devices/humiditysensor.js +++ b/functions/devices/humiditysensor.js @@ -24,7 +24,7 @@ class HumiditySensor extends DefaultDevice { } static getState(item) { - const state = Math.round(Number(item.state)); + const state = Math.round(parseFloat(item.state)); return { humidityAmbientPercent: state, humiditySetpointPercent: state diff --git a/functions/devices/openclosedevice.js b/functions/devices/openclosedevice.js index c7f3c5cd..a92c64be 100644 --- a/functions/devices/openclosedevice.js +++ b/functions/devices/openclosedevice.js @@ -30,7 +30,7 @@ class OpenCloseDevice extends DefaultDevice { let state = 0; const itemType = item.groupType || item.type; if (itemType === 'Rollershutter') { - state = Number(item.state); + state = Math.round(parseFloat(item.state)); } else { state = item.state === 'ON' || item.state === 'OPEN' ? 0 : 100; } diff --git a/functions/devices/sensor.js b/functions/devices/sensor.js index 79f52363..c0876eaf 100644 --- a/functions/devices/sensor.js +++ b/functions/devices/sensor.js @@ -32,15 +32,17 @@ class Sensor extends DefaultDevice { static getState(item) { const config = this.getConfig(item); - return { + const state = { currentSensorStateData: [ { name: config.sensorName, - currentSensorState: this.translateStateToGoogle(item), - rawValue: Number(item.state) || 0 + currentSensorState: this.translateStateToGoogle(item) } ] }; + const rawValue = parseFloat(item.state); + if (!isNaN(rawValue)) state.currentSensorStateData[0].rawValue = rawValue; + return state; } static translateStateToGoogle(item) { @@ -49,7 +51,7 @@ class Sensor extends DefaultDevice { const states = config.states.split(',').map((s) => s.trim()); for (const state of states) { const [key, value] = state.split('=').map((s) => s.trim()); - if (value == item.state) { + if (value === item.state || value === parseFloat(item.state).toFixed(0)) { return key; } } diff --git a/functions/devices/speaker.js b/functions/devices/speaker.js index f003b5ee..09cedf93 100644 --- a/functions/devices/speaker.js +++ b/functions/devices/speaker.js @@ -30,7 +30,7 @@ class Speaker extends DefaultDevice { static getState(item) { return { - currentVolume: Math.round(Number(item.state)) || 0 + currentVolume: Math.round(parseFloat(item.state)) || 0 }; } } diff --git a/functions/devices/specialcolorlight.js b/functions/devices/specialcolorlight.js index 90d6a56b..20382e98 100644 --- a/functions/devices/specialcolorlight.js +++ b/functions/devices/specialcolorlight.js @@ -54,7 +54,7 @@ class SpecialColorLight extends DefaultDevice { state.on = members[member].state === 'ON'; break; case 'lightBrightness': - state.brightness = Math.round(Number(members[member].state)) || 0; + state.brightness = Math.round(parseFloat(members[member].state)) || 0; if (!('lightPower' in members)) { state.on = state.brightness > 0; } @@ -83,18 +83,20 @@ class SpecialColorLight extends DefaultDevice { const colorUnit = this.getColorUnit(item); if (colorUnit === 'kelvin') { state.color = { - temperatureK: Math.round(Number(members[member].state)) + temperatureK: Math.round(parseFloat(members[member].state)) }; } else if (colorUnit === 'mired') { state.color = { - temperatureK: convertMired(Math.round(Number(members[member].state))) + temperatureK: convertMired(Math.round(parseFloat(members[member].state))) }; } else { const { temperatureMinK, temperatureMaxK } = this.getAttributes(item).colorTemperatureRange; + let percent = parseFloat(members[member].state); + if (this.getColorTemperatureInverted(item)) { + percent = 100 - percent; + } state.color = { - temperatureK: - temperatureMinK + - Math.round(((temperatureMaxK - temperatureMinK) / 100) * Number(members[member].state) || 0) + temperatureK: temperatureMinK + Math.round(((temperatureMaxK - temperatureMinK) / 100) * percent || 0) }; } } catch (error) { @@ -126,6 +128,10 @@ class SpecialColorLight extends DefaultDevice { const colorUnit = this.getConfig(item).colorUnit || 'percent'; return colorUnit.toLowerCase(); } + + static getColorTemperatureInverted(item) { + return !!this.getConfig(item).colorTemperatureInverted === true; + } } module.exports = SpecialColorLight; diff --git a/functions/devices/tv.js b/functions/devices/tv.js index 941ccd9d..2d6d2ca7 100644 --- a/functions/devices/tv.js +++ b/functions/devices/tv.js @@ -109,7 +109,7 @@ class TV extends DefaultDevice { state.playbackState = members[member].state; break; case 'tvVolume': - state.currentVolume = Math.round(Number(members[member].state)) || 0; + state.currentVolume = Math.round(parseFloat(members[member].state)) || 0; break; case 'tvChannel': state.channelNumber = members[member].state; diff --git a/functions/package-lock.json b/functions/package-lock.json index 8b90406d..5b491f07 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -1,12 +1,12 @@ { "name": "openhab.google-assistant-smarthome.cloud-function", - "version": "3.7.0", + "version": "3.8.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "openhab.google-assistant-smarthome.cloud-function", - "version": "3.7.0", + "version": "3.8.1", "license": "EPL-2.0", "dependencies": { "actions-on-google": "^3.0.0" diff --git a/functions/package.json b/functions/package.json index e3196a41..b9bfddb2 100644 --- a/functions/package.json +++ b/functions/package.json @@ -1,6 +1,6 @@ { "name": "openhab.google-assistant-smarthome.cloud-function", - "version": "3.7.0", + "version": "3.8.1", "description": "A Google Assistant, Actions on Google based implementation for openHAB", "repository": { "type": "git", diff --git a/package-lock.json b/package-lock.json index 09292172..67398844 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,24 +1,24 @@ { "name": "openhab.google-assistant-smarthome", - "version": "3.7.0", + "version": "3.8.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "openhab.google-assistant-smarthome", - "version": "3.7.0", + "version": "3.8.1", "license": "EPL-2.0", "dependencies": { "express": "^4.18.2" }, "devDependencies": { - "@types/jest": "^29.5.5", - "eslint": "^8.51.0", + "@types/jest": "^29.5.10", + "eslint": "^8.54.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.1", "jest": "^29.7.0", - "nock": "^13.3.4", - "prettier": "3.0.3" + "nock": "^13.3.8", + "prettier": "3.1.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -736,9 +736,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -782,9 +782,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.21.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", - "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -815,21 +815,21 @@ "dev": true }, "node_modules/@eslint/js": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", - "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -874,9 +874,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "node_modules/@istanbuljs/load-nyc-config": { @@ -1390,9 +1390,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.5", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.5.tgz", - "integrity": "sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg==", + "version": "29.5.10", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.10.tgz", + "integrity": "sha512-tE4yxKEphEyxj9s4inideLHktW/x6DwesIwWZ9NN1FKf9zbJYsnhBoA9vrHA/IuIOKwPa5PcFBNV4lpMIOEzyQ==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -1426,6 +1426,12 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1439,9 +1445,9 @@ } }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -2328,18 +2334,19 @@ } }, "node_modules/eslint": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", - "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.51.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.54.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -4186,12 +4193,6 @@ "node": ">=8" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4384,14 +4385,13 @@ } }, "node_modules/nock": { - "version": "13.3.4", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.4.tgz", - "integrity": "sha512-DDpmn5oLEdCTclEqweOT4U7bEpuoifBMFUXem9sA4turDAZ5tlbrEoWqCorwXey8CaAw44mst5JOQeVNiwtkhw==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.8.tgz", + "integrity": "sha512-96yVFal0c/W1lG7mmfRe7eO+hovrhJYd2obzzOZ90f6fjpeU/XNvd9cYHZKZAQJumDfhXgoTpkpJ9pvMj+hqHw==", "dev": true, "dependencies": { "debug": "^4.1.0", "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.21", "propagate": "^2.0.0" }, "engines": { @@ -4703,9 +4703,9 @@ } }, "node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz", + "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -4790,9 +4790,9 @@ } }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -6123,9 +6123,9 @@ "dev": true }, "@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -6155,9 +6155,9 @@ } }, "globals": { - "version": "13.21.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", - "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -6181,18 +6181,18 @@ } }, "@eslint/js": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", - "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", "dev": true }, "@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -6221,9 +6221,9 @@ "dev": true }, "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "@istanbuljs/load-nyc-config": { @@ -6649,9 +6649,9 @@ } }, "@types/jest": { - "version": "29.5.5", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.5.tgz", - "integrity": "sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg==", + "version": "29.5.10", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.10.tgz", + "integrity": "sha512-tE4yxKEphEyxj9s4inideLHktW/x6DwesIwWZ9NN1FKf9zbJYsnhBoA9vrHA/IuIOKwPa5PcFBNV4lpMIOEzyQ==", "dev": true, "requires": { "expect": "^29.0.0", @@ -6685,6 +6685,12 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, + "@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -6695,9 +6701,9 @@ } }, "acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true }, "acorn-jsx": { @@ -7321,18 +7327,19 @@ "dev": true }, "eslint": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", - "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.51.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.54.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -8686,12 +8693,6 @@ "p-locate": "^4.1.0" } }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -8838,14 +8839,13 @@ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, "nock": { - "version": "13.3.4", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.4.tgz", - "integrity": "sha512-DDpmn5oLEdCTclEqweOT4U7bEpuoifBMFUXem9sA4turDAZ5tlbrEoWqCorwXey8CaAw44mst5JOQeVNiwtkhw==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.8.tgz", + "integrity": "sha512-96yVFal0c/W1lG7mmfRe7eO+hovrhJYd2obzzOZ90f6fjpeU/XNvd9cYHZKZAQJumDfhXgoTpkpJ9pvMj+hqHw==", "dev": true, "requires": { "debug": "^4.1.0", "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.21", "propagate": "^2.0.0" }, "dependencies": { @@ -9074,9 +9074,9 @@ "dev": true }, "prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz", + "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==", "dev": true }, "prettier-linter-helpers": { @@ -9133,9 +9133,9 @@ } }, "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true }, "pure-rand": { diff --git a/package.json b/package.json index 80ff8061..3da4d6f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openhab.google-assistant-smarthome", - "version": "3.7.0", + "version": "3.8.1", "description": "A Google Assistant, Actions on Google based implementation for openHAB", "main": "functions/index.js", "repository": { @@ -35,12 +35,12 @@ "express": "^4.18.2" }, "devDependencies": { - "@types/jest": "^29.5.5", - "eslint": "^8.51.0", + "@types/jest": "^29.5.10", + "eslint": "^8.54.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.1", "jest": "^29.7.0", - "nock": "^13.3.4", - "prettier": "3.0.3" + "nock": "^13.3.8", + "prettier": "3.1.0" } } diff --git a/tests/commands/colorabsolutetemperature.test.js b/tests/commands/colorabsolutetemperature.test.js index c8e11bc0..62669e76 100644 --- a/tests/commands/colorabsolutetemperature.test.js +++ b/tests/commands/colorabsolutetemperature.test.js @@ -85,6 +85,21 @@ describe('ColorAbsoluteTemperature Command', () => { const device = { customData: { deviceType: 'SpecialColorLight' } }; expect(Command.convertParamsToValue(params, item, device)).toBe('500'); }); + + test('convertParamsToValue SpecialColorLight Percent Inverted', () => { + const item = { + metadata: { + ga: { + config: { + colorTemperatureRange: '1000,5000', + colorTemperatureInverted: true + } + } + } + }; + const device = { customData: { deviceType: 'SpecialColorLight' } }; + expect(Command.convertParamsToValue(params, item, device)).toBe('75'); + }); }); test('getResponseStates', () => { diff --git a/tests/devices/charger.test.js b/tests/devices/charger.test.js index 838441e3..4d8bd289 100644 --- a/tests/devices/charger.test.js +++ b/tests/devices/charger.test.js @@ -315,7 +315,7 @@ describe('Charger Device', () => { }, { name: 'CapacityRemaining', - state: '4000.123', + state: '4000.123 wh', metadata: { ga: { value: 'chargerCapacityRemaining' diff --git a/tests/devices/climatesensor.test.js b/tests/devices/climatesensor.test.js index f740bd2b..6f173e05 100644 --- a/tests/devices/climatesensor.test.js +++ b/tests/devices/climatesensor.test.js @@ -135,7 +135,7 @@ describe('ClimateSensor Device', () => { }, { name: 'Humidity', - state: '59.7', + state: '59.7 %', type: 'Number', metadata: { ga: { @@ -182,7 +182,7 @@ describe('ClimateSensor Device', () => { members: [ { name: 'Humidity', - state: '30.3', + state: '30.3 %', type: 'Number', metadata: { ga: { diff --git a/tests/devices/dimmablelight.test.js b/tests/devices/dimmablelight.test.js index bbcf2a7f..5713ee56 100644 --- a/tests/devices/dimmablelight.test.js +++ b/tests/devices/dimmablelight.test.js @@ -21,7 +21,7 @@ describe('DimmableLight Device', () => { }); test('getState', () => { - expect(Device.getState({ state: '50' })).toStrictEqual({ + expect(Device.getState({ state: '50 %' })).toStrictEqual({ on: true, brightness: 50 }); diff --git a/tests/devices/fan.test.js b/tests/devices/fan.test.js index a6048f18..7b0f28dd 100644 --- a/tests/devices/fan.test.js +++ b/tests/devices/fan.test.js @@ -85,13 +85,13 @@ describe('Fan Device', () => { }); test('getState', () => { - expect(Device.getState({ state: '50' })).toStrictEqual({ + expect(Device.getState({ state: '50 %' })).toStrictEqual({ currentFanSpeedPercent: 50, on: true }); expect( Device.getState({ - state: '50', + state: '50 upm', metadata: { ga: { config: { diff --git a/tests/devices/humiditysensor.test.js b/tests/devices/humiditysensor.test.js index a1c53388..3303d9d6 100644 --- a/tests/devices/humiditysensor.test.js +++ b/tests/devices/humiditysensor.test.js @@ -30,7 +30,7 @@ describe('HumiditySensor Device', () => { }); test('getState', () => { - expect(Device.getState({ state: '10.3' })).toStrictEqual({ + expect(Device.getState({ state: '9.6 %' })).toStrictEqual({ humidityAmbientPercent: 10, humiditySetpointPercent: 10 }); diff --git a/tests/devices/openclosedevice.test.js b/tests/devices/openclosedevice.test.js index 322b8143..ae5dbe43 100644 --- a/tests/devices/openclosedevice.test.js +++ b/tests/devices/openclosedevice.test.js @@ -84,7 +84,7 @@ describe('OpenCloseDevice Device', () => { test('getState Rollershutter', () => { const item = { type: 'Rollershutter', - state: '25' + state: '25 %' }; expect(Device.getState(item)).toStrictEqual({ openPercent: 75 diff --git a/tests/devices/sensor.test.js b/tests/devices/sensor.test.js index a978e567..e98cd83e 100644 --- a/tests/devices/sensor.test.js +++ b/tests/devices/sensor.test.js @@ -96,7 +96,7 @@ describe('Sensor Device', () => { } } }, - state: '10' + state: '10 ppm' }; expect(Device.getState(item)).toStrictEqual({ currentSensorStateData: [ @@ -109,6 +109,29 @@ describe('Sensor Device', () => { }); }); + test('getState with string state', () => { + const item = { + metadata: { + ga: { + config: { + sensorName: 'Sensor', + valueUnit: 'AQI', + states: 'good=good,moderate=moderate,poor=poor' + } + } + }, + state: 'moderate' + }; + expect(Device.getState(item)).toStrictEqual({ + currentSensorStateData: [ + { + currentSensorState: 'moderate', + name: 'Sensor' + } + ] + }); + }); + test('getState no matching state', () => { const item = { metadata: { diff --git a/tests/devices/speaker.test.js b/tests/devices/speaker.test.js index f6497d5e..5a331b53 100644 --- a/tests/devices/speaker.test.js +++ b/tests/devices/speaker.test.js @@ -56,7 +56,7 @@ describe('Speaker Device', () => { }); test('getState', () => { - expect(Device.getState({ state: '10' })).toStrictEqual({ + expect(Device.getState({ state: '10 %' })).toStrictEqual({ currentVolume: 10 }); expect(Device.getState({ state: '90' })).toStrictEqual({ diff --git a/tests/devices/specialcolorlight.test.js b/tests/devices/specialcolorlight.test.js index 02cf3378..e59afa16 100644 --- a/tests/devices/specialcolorlight.test.js +++ b/tests/devices/specialcolorlight.test.js @@ -335,14 +335,6 @@ describe('SpecialColorLight Device', () => { } }, members: [ - { - state: '50', - metadata: { - ga: { - value: 'lightBrightness' - } - } - }, { state: '2000.345', metadata: { @@ -354,34 +346,55 @@ describe('SpecialColorLight Device', () => { ] }; expect(Device.getState(item)).toStrictEqual({ - on: true, - brightness: 50, color: { temperatureK: 2000 } }); }); - test('getState mired', () => { + test('getState percent inverted', () => { const item = { type: 'Group', metadata: { ga: { value: 'LIGHT', config: { - colorUnit: 'mired' + colorTemperatureRange: '2000,5000', + colorTemperatureInverted: true } } }, members: [ { - state: '50', + state: '25', + type: 'Number', metadata: { ga: { - value: 'lightBrightness' + value: 'lightColorTemperature' } } - }, + } + ] + }; + expect(Device.getState(item)).toStrictEqual({ + color: { + temperatureK: 4250 + } + }); + }); + + test('getState mired', () => { + const item = { + type: 'Group', + metadata: { + ga: { + value: 'LIGHT', + config: { + colorUnit: 'mired' + } + } + }, + members: [ { state: '200', metadata: { @@ -393,8 +406,6 @@ describe('SpecialColorLight Device', () => { ] }; expect(Device.getState(item)).toStrictEqual({ - on: true, - brightness: 50, color: { temperatureK: 5000 } @@ -414,7 +425,7 @@ describe('SpecialColorLight Device', () => { }, members: [ { - state: '0', + state: '0 %', metadata: { ga: { value: 'lightBrightness' @@ -422,7 +433,7 @@ describe('SpecialColorLight Device', () => { } }, { - state: '80', + state: '80 K', metadata: { ga: { value: 'lightColorTemperature' diff --git a/tests/devices/tv.test.js b/tests/devices/tv.test.js index 0f9c5f29..4f73481c 100644 --- a/tests/devices/tv.test.js +++ b/tests/devices/tv.test.js @@ -499,7 +499,7 @@ describe('TV Device', () => { } }, { - state: '50', + state: '50 %', metadata: { ga: { value: 'tvVolume'