From 8776949b10b40246c361939669b0e9bf6944560c Mon Sep 17 00:00:00 2001 From: Pierpaolo Follia Date: Sat, 4 Sep 2021 14:23:24 +0200 Subject: [PATCH] Refactored blinds handling (#47) * Fix and refactor blinds handling * Fix initial position * Fix position stare * Use internal device properties and update it with position * Other fixes * Fix position state * Fix position from time: set min and max * Major refactoring * Fix missing setPosition call * Fix test Fix timeout before stopping * No extra time * Fix test --- .eslintrc.js | 36 +- .gitignore | 1 + config.schema.json | 21 +- jest.config.js | 7 + package.json | 3 +- src/accessories/__tests__/blinds.test.ts | 190 ++ src/accessories/__tests__/thermostat.test.ts | 3 - src/accessories/__tests__/utils.ts | 1878 ++++++++++++++++++ src/accessories/blind.ts | 26 +- src/accessories/comelit.ts | 8 +- src/accessories/enhanced-blind.ts | 77 +- src/accessories/standard-blind.ts | 132 +- src/comelit-platform.ts | 49 +- src/utils.ts | 19 + tsconfig.json | 1 + yarn.lock | 486 ++--- 16 files changed, 2475 insertions(+), 462 deletions(-) create mode 100644 jest.config.js create mode 100644 src/accessories/__tests__/blinds.test.ts delete mode 100644 src/accessories/__tests__/thermostat.test.ts create mode 100644 src/accessories/__tests__/utils.ts create mode 100644 src/utils.ts diff --git a/.eslintrc.js b/.eslintrc.js index 4364c7e..97332d9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,31 +1,29 @@ module.exports = { env: { browser: false, - es6: true + es6: true, }, - extends: [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended" - ], + extends: ['eslint:recommended', 'plugin:@typescript-eslint/eslint-recommended'], globals: { - Atomics: "readonly", - SharedArrayBuffer: "readonly" + Atomics: 'readonly', + SharedArrayBuffer: 'readonly', }, - parser: "@typescript-eslint/parser", + parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 2018, - sourceType: "module" + sourceType: 'module', }, - plugins: ["@typescript-eslint"], + plugins: ['@typescript-eslint'], rules: { - "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": [ - "error", + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', { - vars: "all", - args: "after-used", - ignoreRestSiblings: false - } - ] - } + vars: 'all', + args: 'after-used', + argsIgnorePattern: '^_', + ignoreRestSiblings: false, + }, + ], + }, }; diff --git a/.gitignore b/.gitignore index ae4ebec..df2e151 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /node_modules/ .idea .npmrc +/coverage/ diff --git a/config.schema.json b/config.schema.json index ee5c08b..7c1d02a 100644 --- a/config.schema.json +++ b/config.schema.json @@ -51,8 +51,14 @@ "placeholder": "" } }, + "blind_opening_time": { + "title": "Time spent by the blind to get fully open from closed position (default 37 seconds)", + "type": "number", + "required": false, + "default": 37 + }, "blind_closing_time": { - "title": "Time spent by the blind to get fully closed from open position (default 35 seconds)", + "title": "Time spent by the blind to get fully closed from opened position (default 35 seconds)", "type": "number", "required": false, "default": 35 @@ -68,6 +74,12 @@ "type": "boolean", "default": false }, + "use_comelit_blind_timing": { + "title": "Use open/close time configured in Comelit HUB for all standard blinds", + "description": "If this is set to true, the opening and closing time for each blind are read from internal Comelit configuration (you can use official Comelit app to change them). This settings overrides global opening and closing time settings. Does not affect new blinds with position support", + "type": "boolean", + "default": false + }, "hide_lights": { "title": "Avoid mapping lights", "type": "boolean", @@ -112,6 +124,12 @@ "title": "Main settings", "items": ["name", "username", "password", "broker_url"] }, + { + "type": "fieldset", + "expandable": false, + "title": "Blinds settings", + "items": ["use_comelit_blind_timing", "blind_opening_time", "blind_closing_time"] + }, { "type": "fieldset", "expandable": true, @@ -120,7 +138,6 @@ "items": [ "hub_username", "hub_password", - "blind_closing_time", "keep_alive", "avoid_duplicates", "hide_lights", diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..d32cb05 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,7 @@ +// eslint-disable-next-line no-undef +module.exports = { + verbose: true, + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/__tests__/**/*.test.ts'], +}; diff --git a/package.json b/package.json index b0ce1c9..05a3d93 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ }, "dependencies": { "async-mqtt": "2.6.1", - "comelit-client": "2.3.3", + "comelit-client": "2.3.5", "express": "^4.17.1", "fakegato-history": "^0.6.1", "lodash": "4.17.21", @@ -62,6 +62,7 @@ "homebridge": "^1.3.1", "husky": "^4.2.3", "jest": "^24.9.0", + "ts-jest": "^26.4.4", "nock": "^12.0.2", "prettier": "^1.19.1", "pretty-quick": "^2.0.1", diff --git a/src/accessories/__tests__/blinds.test.ts b/src/accessories/__tests__/blinds.test.ts new file mode 100644 index 0000000..f99e461 --- /dev/null +++ b/src/accessories/__tests__/blinds.test.ts @@ -0,0 +1,190 @@ +import { StandardBlind } from '../standard-blind'; +import { ComelitPlatform, HubConfig } from '../../comelit-platform'; +import { HomebridgeAPI } from 'homebridge/lib/api'; +import { withPrefix } from 'homebridge/lib/logger'; +import { Categories } from 'homebridge'; +import { BlindDeviceData, ComelitClient } from 'comelit-client'; +import { PositionState } from '../hap'; +import { EnhancedBlind } from '../enhanced-blind'; +import { getPositionAsByte } from '../../utils'; +import { Blind } from '../blind'; + +const STD_BLIND_DEVICE_DATA: BlindDeviceData = { + id: 'DOM#BL#19.1', + type: 2, + sub_type: 7, + descrizione: 'Tapparella destra', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + open_status: '1', + num_modulo: '19', + num_uscita: '1', + openTime: '60', + closeTime: '60', + preferPosition: '', + enablePreferPosition: '0', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#BL#19.1', + placeId: 'GEN#PL#258', +}; + +const ENHANCED_BLIND_DEVICE_DATA: BlindDeviceData = { + id: 'DOM#BL#32.1', + type: 2, + sub_type: 31, + descrizione: 'Soggiorno', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + position: '255', + open_status: '0', + preferPosition: '127', + enablePreferPosition: '1', + num_modulo: '32', + num_uscita: '1', + openTime: '40', + closeTime: '37', + icon_id: '2', + isProtected: '0', + objectId: 'DOM#BL#32.1', + placeId: 'GEN#PL#221', +}; + +jest.useFakeTimers(); + +jest.mock('comelit-client', () => { + return { + ComelitClient: jest.fn().mockImplementation(() => { + return { toggleDeviceStatus: jest.fn(), setBlindPosition: jest.fn() }; + }), + }; +}); + +const config: HubConfig = { + platform: 'comelit', + username: 'string', + password: 'string', + hub_username: 'string', + hub_password: 'string', +}; + +describe('Blinds', () => { + it('should update std blind status', async () => { + const api = new HomebridgeAPI(); + const platform = new ComelitPlatform(withPrefix('test'), config, api); + const accessory = platform.createHapAccessory( + STD_BLIND_DEVICE_DATA, + Categories.WINDOW_COVERING, + STD_BLIND_DEVICE_DATA.id + ); + const client = new ComelitClient(() => jest.fn()); + const blind = new StandardBlind(platform, accessory, client); + const service = accessory.getService(platform.Service.WindowCovering); + const targetPosition = service.getCharacteristic(platform.Characteristic.TargetPosition); + const positionState = service.getCharacteristic(platform.Characteristic.PositionState); + const position = service.getCharacteristic(platform.Characteristic.CurrentPosition); + + expect(targetPosition.value).toBe(Blind.OPEN); + expect(position.value).toBe(Blind.OPEN); + expect(positionState.value).toBe(PositionState.STOPPED); + + await blind.setPosition(40, jest.fn); + expect(client.toggleDeviceStatus).toHaveBeenCalledTimes(1); + expect(client.toggleDeviceStatus).toHaveBeenCalledWith(STD_BLIND_DEVICE_DATA.id, 0); + blind.updateDevice({ + ...STD_BLIND_DEVICE_DATA, + type: 2, + sub_type: 7, + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '2', + powerst: '2', + open_status: '1', + }); + expect(setTimeout).toHaveBeenLastCalledWith( + expect.any(Function), + ((Blind.CLOSING_TIME * 1000) / 100) * 60 + ); + expect(positionState.value).toBe(PositionState.DECREASING); + // @ts-ignore + client.toggleDeviceStatus.mockClear(); + jest.runAllTimers(); + expect(client.toggleDeviceStatus).toHaveBeenCalledTimes(1); + expect(client.toggleDeviceStatus).toHaveBeenCalledWith(STD_BLIND_DEVICE_DATA.id, 1); + blind.updateDevice({ + ...STD_BLIND_DEVICE_DATA, + type: 2, + sub_type: 7, + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + open_status: '1', + }); + expect(positionState.value).toBe(PositionState.STOPPED); + }); + + it('should update enhanced blind status', async () => { + const api = new HomebridgeAPI(); + const platform = new ComelitPlatform(withPrefix('test'), config, api); + const accessory = platform.createHapAccessory( + ENHANCED_BLIND_DEVICE_DATA, + Categories.WINDOW_COVERING, + ENHANCED_BLIND_DEVICE_DATA.id + ); + const client = new ComelitClient(() => jest.fn()); + const blind = new EnhancedBlind(platform, accessory, client); + const service = accessory.getService(platform.Service.WindowCovering); + const positionState = service.getCharacteristic(platform.Characteristic.PositionState); + const targetPosition = service.getCharacteristic(platform.Characteristic.TargetPosition); + const position = service.getCharacteristic(platform.Characteristic.CurrentPosition); + + expect(targetPosition.value).toBe(Blind.CLOSED); + expect(position.value).toBe(Blind.CLOSED); + expect(positionState.value).toBe(PositionState.STOPPED); + + const callback = jest.fn; + await blind.setPosition(40, callback); + expect(client.setBlindPosition).toHaveBeenCalledTimes(1); + // expect(callback).toHaveBeenCalledTimes(1); + expect(client.setBlindPosition).toHaveBeenCalledWith( + ENHANCED_BLIND_DEVICE_DATA.id, + getPositionAsByte(40) + ); + expect(targetPosition.value).toBe(40); + blind.updateDevice({ + ...ENHANCED_BLIND_DEVICE_DATA, + type: 2, + sub_type: 31, + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '1', + powerst: '1', + position: '255', + open_status: '0', + preferPosition: '127', + enablePreferPosition: '1', + }); + expect(positionState.value).toBe(PositionState.INCREASING); + blind.updateDevice({ + ...ENHANCED_BLIND_DEVICE_DATA, + type: 2, + sub_type: 31, + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '1', + position: `${getPositionAsByte(40)}`, + open_status: '0', + preferPosition: '127', + enablePreferPosition: '1', + }); + + expect(positionState.value).toBe(PositionState.STOPPED); + expect(position.value).toBe(40); + }); +}); diff --git a/src/accessories/__tests__/thermostat.test.ts b/src/accessories/__tests__/thermostat.test.ts deleted file mode 100644 index 9d5cc58..0000000 --- a/src/accessories/__tests__/thermostat.test.ts +++ /dev/null @@ -1,3 +0,0 @@ -describe('Thermostat', () => { - it('should update thermostat status', async () => {}); -}); diff --git a/src/accessories/__tests__/utils.ts b/src/accessories/__tests__/utils.ts new file mode 100644 index 0000000..5583a78 --- /dev/null +++ b/src/accessories/__tests__/utils.ts @@ -0,0 +1,1878 @@ +const HOUSE_DEF = { + id: 'GEN#17#13#1', + type: 1001, + sub_type: 13, + descrizione: 'root', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + elements: [ + { + id: 'GEN#PL#3', + data: { + id: 'GEN#PL#3', + type: 1001, + sub_type: 13, + descrizione: 'Casa ', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + elements: [ + { + id: 'GEN#PL#255', + data: { + id: 'GEN#PL#255', + type: 1001, + sub_type: 13, + descrizione: 'Piano PT', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + elements: [ + { + id: 'GEN#PL#254', + data: { + id: 'GEN#PL#254', + type: 1001, + sub_type: 13, + descrizione: 'Ingresso', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + placeOrder: '0', + elements: [ + { + id: 'DOM#LT#4.1', + data: { + id: 'DOM#LT#4.1', + type: 3, + sub_type: 1, + descrizione: 'Luce esterna', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '4', + num_uscita: '1', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#4.1', + placeId: 'GEN#PL#254', + }, + }, + { + id: 'DOM#LT#34.5', + data: { + id: 'DOM#LT#34.5', + type: 3, + sub_type: 1, + descrizione: 'Luce caldaia', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '34', + num_uscita: '5', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#34.5', + placeId: 'GEN#PL#254', + }, + }, + ], + }, + }, + { + id: 'GEN#PL#256', + data: { + id: 'GEN#PL#256', + type: 1001, + sub_type: 13, + descrizione: 'Cantina ', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + placeOrder: '1', + elements: [ + { + id: 'DOM#LT#4.2', + data: { + id: 'DOM#LT#4.2', + type: 3, + sub_type: 1, + descrizione: 'Luce cantina', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '4', + num_uscita: '2', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#4.2', + placeId: 'GEN#PL#256', + }, + }, + ], + }, + }, + { + id: 'GEN#PL#269', + data: { + id: 'GEN#PL#269', + type: 1001, + sub_type: 13, + descrizione: 'Scala ', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + placeOrder: '2', + elements: [ + { + id: 'DOM#LT#4.3', + data: { + id: 'DOM#LT#4.3', + type: 3, + sub_type: 1, + descrizione: 'Luce scala', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '4', + num_uscita: '3', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#4.3', + placeId: 'GEN#PL#269', + }, + }, + ], + }, + }, + ], + }, + }, + { + id: 'GEN#PL#257', + data: { + id: 'GEN#PL#257', + type: 1001, + sub_type: 13, + descrizione: 'Piano 1', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + elements: [ + { + id: 'GEN#PL#258', + data: { + id: 'GEN#PL#258', + type: 1001, + sub_type: 13, + descrizione: 'Sala da pranzo', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + placeOrder: '3', + elements: [ + { + id: 'DOM#LT#4.4', + data: { + id: 'DOM#LT#4.4', + type: 3, + sub_type: 1, + descrizione: 'Applique sala da pranzo', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '4', + num_uscita: '4', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#4.4', + placeId: 'GEN#PL#258', + }, + }, + { + id: 'DOM#LT#5.3', + data: { + id: 'DOM#LT#5.3', + type: 3, + sub_type: 1, + descrizione: 'Luce sala da pranzo', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '5', + num_uscita: '3', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#5.3', + placeId: 'GEN#PL#258', + }, + }, + { + id: 'DOM#BL#19.1', + data: { + id: 'DOM#BL#19.1', + type: 2, + sub_type: 7, + descrizione: 'Tapparella destra', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + open_status: '1', + num_modulo: '19', + num_uscita: '1', + openTime: '60', + closeTime: '60', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#BL#19.1', + placeId: 'GEN#PL#258', + }, + }, + { + id: 'DOM#BL#20.1', + data: { + id: 'DOM#BL#20.1', + type: 2, + sub_type: 7, + descrizione: 'Tapparella sinistra', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + open_status: '1', + num_modulo: '20', + num_uscita: '1', + openTime: '60', + closeTime: '60', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#BL#20.1', + placeId: 'GEN#PL#258', + }, + }, + ], + }, + }, + { + id: 'GEN#PL#259', + data: { + id: 'GEN#PL#259', + type: 1001, + sub_type: 13, + descrizione: 'Soggiorno', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + placeOrder: '4', + elements: [ + { + id: 'DOM#LT#4.6', + data: { + id: 'DOM#LT#4.6', + type: 3, + sub_type: 1, + descrizione: 'Faretti soggiorno', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '4', + num_uscita: '6', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#4.6', + placeId: 'GEN#PL#259', + }, + }, + { + id: 'DOM#LT#4.8', + data: { + id: 'DOM#LT#4.8', + type: 3, + sub_type: 1, + descrizione: 'Applique soggiorno', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '4', + num_uscita: '8', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#4.8', + placeId: 'GEN#PL#259', + }, + }, + { + id: 'DOM#LT#4.5', + data: { + id: 'DOM#LT#4.5', + type: 3, + sub_type: 1, + descrizione: 'Led soggiorno', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '4', + num_uscita: '5', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#4.5', + placeId: 'GEN#PL#259', + }, + }, + { + id: 'DOM#LT#4.7', + data: { + id: 'DOM#LT#4.7', + type: 3, + sub_type: 1, + descrizione: 'Luce della nonna', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '4', + num_uscita: '7', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#4.7', + placeId: 'GEN#PL#259', + }, + }, + ], + }, + }, + { + id: 'GEN#PL#260', + data: { + id: 'GEN#PL#260', + type: 1001, + sub_type: 13, + descrizione: 'Cucina ', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + placeOrder: '5', + elements: [ + { + id: 'DOM#LT#5.1', + data: { + id: 'DOM#LT#5.1', + type: 3, + sub_type: 1, + descrizione: 'Luce cucina', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '5', + num_uscita: '1', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#5.1', + placeId: 'GEN#PL#260', + }, + }, + { + id: 'DOM#BL#21.1', + data: { + id: 'DOM#BL#21.1', + type: 2, + sub_type: 7, + descrizione: 'Tapparella cucina', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + open_status: '1', + num_modulo: '21', + num_uscita: '1', + openTime: '60', + closeTime: '60', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#BL#21.1', + placeId: 'GEN#PL#260', + }, + }, + ], + }, + }, + { + id: 'GEN#PL#261', + data: { + id: 'GEN#PL#261', + type: 1001, + sub_type: 13, + descrizione: 'Ripostiglio ', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + placeOrder: '6', + elements: [ + { + id: 'DOM#LT#5.5', + data: { + id: 'DOM#LT#5.5', + type: 3, + sub_type: 1, + descrizione: 'Luce ripostiglio', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '5', + num_uscita: '5', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#5.5', + placeId: 'GEN#PL#261', + }, + }, + ], + }, + }, + { + id: 'GEN#PL#262', + data: { + id: 'GEN#PL#262', + type: 1001, + sub_type: 13, + descrizione: 'Antibagno', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + placeOrder: '7', + elements: [ + { + id: 'DOM#LT#5.4', + data: { + id: 'DOM#LT#5.4', + type: 3, + sub_type: 1, + descrizione: 'Faretti antibagno', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '5', + num_uscita: '4', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#5.4', + placeId: 'GEN#PL#262', + }, + }, + ], + }, + }, + { + id: 'GEN#PL#263', + data: { + id: 'GEN#PL#263', + type: 1001, + sub_type: 13, + descrizione: 'Bagno zona giorno', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + placeOrder: '8', + elements: [ + { + id: 'DOM#LT#5.8', + data: { + id: 'DOM#LT#5.8', + type: 3, + sub_type: 1, + descrizione: 'Applique bagno giorno', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '1', + powerst: '1', + num_modulo: '5', + num_uscita: '8', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#5.8', + placeId: 'GEN#PL#263', + }, + }, + { + id: 'DOM#LT#5.7', + data: { + id: 'DOM#LT#5.7', + type: 3, + sub_type: 1, + descrizione: 'Specchiera bagno giorno', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '1', + powerst: '1', + num_modulo: '5', + num_uscita: '7', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#5.7', + placeId: 'GEN#PL#263', + }, + }, + { + id: 'DOM#BL#22.1', + data: { + id: 'DOM#BL#22.1', + type: 2, + sub_type: 7, + descrizione: 'Tapparella bagno giorno', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + open_status: '1', + num_modulo: '22', + num_uscita: '1', + openTime: '60', + closeTime: '60', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#BL#22.1', + placeId: 'GEN#PL#263', + }, + }, + ], + }, + }, + { + id: 'GEN#PL#264', + data: { + id: 'GEN#PL#264', + type: 1001, + sub_type: 13, + descrizione: 'Corridoio', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + placeOrder: '9', + elements: [ + { + id: 'DOM#LT#5.2', + data: { + id: 'DOM#LT#5.2', + type: 3, + sub_type: 1, + descrizione: 'Faretti corridoio', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '5', + num_uscita: '2', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#5.2', + placeId: 'GEN#PL#264', + }, + }, + ], + }, + }, + { + id: 'GEN#PL#265', + data: { + id: 'GEN#PL#265', + type: 1001, + sub_type: 13, + descrizione: 'Bagno zona notte', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + placeOrder: '10', + elements: [ + { + id: 'DOM#LT#6.6', + data: { + id: 'DOM#LT#6.6', + type: 3, + sub_type: 1, + descrizione: 'Applique bagno notte', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '6', + num_uscita: '6', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#6.6', + placeId: 'GEN#PL#265', + }, + }, + { + id: 'DOM#LT#6.7', + data: { + id: 'DOM#LT#6.7', + type: 3, + sub_type: 1, + descrizione: 'Specchiera bagno notte', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '6', + num_uscita: '7', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#6.7', + placeId: 'GEN#PL#265', + }, + }, + { + id: 'DOM#BL#23.1', + data: { + id: 'DOM#BL#23.1', + type: 2, + sub_type: 7, + descrizione: 'Tapparella bagno notte', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + open_status: '1', + num_modulo: '23', + num_uscita: '1', + openTime: '60', + closeTime: '60', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#BL#23.1', + placeId: 'GEN#PL#265', + }, + }, + { + id: 'DOM#LT#34.6', + data: { + id: 'DOM#LT#34.6', + type: 3, + sub_type: 1, + descrizione: 'Led Doccia', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '34', + num_uscita: '6', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#34.6', + placeId: 'GEN#PL#265', + }, + }, + ], + }, + }, + { + id: 'GEN#PL#266', + data: { + id: 'GEN#PL#266', + type: 1001, + sub_type: 13, + descrizione: 'Camera da letto', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + placeOrder: '11', + elements: [ + { + id: 'DOM#LT#6.1', + data: { + id: 'DOM#LT#6.1', + type: 3, + sub_type: 1, + descrizione: 'Luce camera da letto', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '6', + num_uscita: '1', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#6.1', + placeId: 'GEN#PL#266', + }, + }, + { + id: 'DOM#LT#6.3', + data: { + id: 'DOM#LT#6.3', + type: 3, + sub_type: 1, + descrizione: 'Piantana camera da letto', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '6', + num_uscita: '3', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#6.3', + placeId: 'GEN#PL#266', + }, + }, + { + id: 'DOM#BL#24.1', + data: { + id: 'DOM#BL#24.1', + type: 2, + sub_type: 7, + descrizione: 'Tapparella camera da letto', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + open_status: '1', + num_modulo: '24', + num_uscita: '1', + openTime: '60', + closeTime: '60', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#BL#24.1', + placeId: 'GEN#PL#266', + }, + }, + ], + }, + }, + { + id: 'GEN#PL#267', + data: { + id: 'GEN#PL#267', + type: 1001, + sub_type: 13, + descrizione: 'Camera Vittoria', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + placeOrder: '12', + elements: [ + { + id: 'DOM#LT#5.6', + data: { + id: 'DOM#LT#5.6', + type: 3, + sub_type: 1, + descrizione: 'Luce camera Vittoria', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '5', + num_uscita: '6', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#5.6', + placeId: 'GEN#PL#267', + }, + }, + { + id: 'DOM#LT#6.5', + data: { + id: 'DOM#LT#6.5', + type: 3, + sub_type: 1, + descrizione: 'Pixar', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '6', + num_uscita: '5', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#6.5', + placeId: 'GEN#PL#267', + }, + }, + ], + }, + }, + { + id: 'GEN#PL#268', + data: { + id: 'GEN#PL#268', + type: 1001, + sub_type: 13, + descrizione: 'Camera Beatrice', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + placeOrder: '13', + elements: [ + { + id: 'DOM#LT#6.4', + data: { + id: 'DOM#LT#6.4', + type: 3, + sub_type: 1, + descrizione: 'Luce camera Beatrice', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '6', + num_uscita: '4', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#6.4', + placeId: 'GEN#PL#268', + }, + }, + { + id: 'DOM#LT#6.2', + data: { + id: 'DOM#LT#6.2', + type: 3, + sub_type: 1, + descrizione: 'Stellina', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '6', + num_uscita: '2', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#6.2', + placeId: 'GEN#PL#268', + }, + }, + ], + }, + }, + { + id: 'GEN#PL#312', + data: { + id: 'GEN#PL#312', + type: 1001, + sub_type: 13, + descrizione: 'Terrazzo', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + schedZoneStatus: [0, 0, 0], + placeOrder: '14', + elements: [ + { + id: 'DOM#LT#6.8', + data: { + id: 'DOM#LT#6.8', + type: 3, + sub_type: 1, + descrizione: 'Luci esterne', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: '6', + num_uscita: '8', + icon_id: '0', + isProtected: '0', + objectId: 'DOM#LT#6.8', + placeId: 'GEN#PL#312', + }, + }, + ], + }, + }, + ], + }, + }, + { + id: 'DOM#CL#32.1', + data: { + id: 'DOM#CL#32.1', + type: 9, + sub_type: 16, + descrizione: 'Termostato piano terra', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '0', + num_modulo: 32, + num_ingresso: 1, + num_moduloIE: '0', + num_uscitaIE: '0', + num_moduloI: '34', + num_uscitaI: '3', + num_moduloE: '34', + num_uscitaE: '3', + num_moduloI_ana: '0', + num_uscitaI_ana: '0', + num_moduloE_ana: '0', + num_uscitaE_ana: '0', + soglia_man_inv: '190', + soglia_man_est: '240', + soglia_man_notte_inv: '200', + soglia_man_notte_est: '200', + soglia_semiauto: '200', + night_mode: '0', + soglia_auto_inv: '200', + soglia_auto_est: '200', + out_enable_inv: '0', + out_enable_est: '0', + dir_enable_inv: '0', + dir_enable_est: '0', + heatAutoFanDisable: '0', + coolAutoFanDisable: '0', + heatSwingDisable: '0', + coolSwingDisable: '0', + out_type_inv: '0', + out_type_est: '0', + temp_base_inv: '200', + temp_base_est: '200', + num_moduloUD: '0', + num_uscitaUD: '0', + num_moduloU: '0', + num_uscitaU: '0', + num_moduloD: '33', + num_uscitaD: '8', + num_moduloU_ana: '0', + num_uscitaU_ana: '0', + num_moduloD_ana: '0', + num_uscitaD_ana: '0', + out_enable_umi: '0', + out_enable_deumi: '0', + dir_enable_umi: '0', + dir_enable_deumi: '0', + humAutoFanDisable: '0', + dehumAutoFanDisable: '0', + humSwingDisable: '0', + dehumSwingDisable: '0', + out_type_umi: '0', + out_type_deumi: '0', + soglia_man_umi: '50', + soglia_man_deumi: '50', + soglia_man_notte_umi: '70', + soglia_man_notte_deumi: '50', + night_mode_umi: '0', + soglia_semiauto_umi: '50', + umi_base_umi: '0', + umi_base_deumi: '0', + coolLimitMax: '300', + coolLimitMin: '50', + heatLimitMax: '300', + heatLimitMin: '50', + icon_id: '0', + objectId: 'DOM#CL#32.1', + placeId: 'GEN#PL#3', + viewOnly: '0', + temperatura: '266', + auto_man: '6', + est_inv: '0', + soglia_attiva: '240', + semiauto_enabled: '0', + umidita: '59', + auto_man_umi: '6', + deumi_umi: '0', + soglia_attiva_umi: '50', + semiauto_umi_enabled: '0', + }, + }, + { + id: 'DOM#CL#31.1', + data: { + id: 'DOM#CL#31.1', + type: 9, + sub_type: 16, + descrizione: 'Termostato soggiorno', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '1', + num_modulo: 31, + num_ingresso: 1, + num_moduloIE: '0', + num_uscitaIE: '0', + num_moduloI: '7', + num_uscitaI: '1', + num_moduloE: '7', + num_uscitaE: '1', + num_moduloI_ana: '0', + num_uscitaI_ana: '0', + num_moduloE_ana: '0', + num_uscitaE_ana: '0', + soglia_man_inv: '215', + soglia_man_est: '265', + soglia_man_notte_inv: '200', + soglia_man_notte_est: '200', + soglia_semiauto: '220', + night_mode: '0', + soglia_auto_inv: '200', + soglia_auto_est: '200', + out_enable_inv: '0', + out_enable_est: '0', + dir_enable_inv: '0', + dir_enable_est: '0', + heatAutoFanDisable: '0', + coolAutoFanDisable: '0', + heatSwingDisable: '0', + coolSwingDisable: '0', + out_type_inv: '0', + out_type_est: '0', + temp_base_inv: '200', + temp_base_est: '200', + num_moduloUD: '0', + num_uscitaUD: '0', + num_moduloU: '0', + num_uscitaU: '0', + num_moduloD: '33', + num_uscitaD: '1', + num_moduloU_ana: '0', + num_uscitaU_ana: '0', + num_moduloD_ana: '0', + num_uscitaD_ana: '0', + out_enable_umi: '0', + out_enable_deumi: '0', + dir_enable_umi: '0', + dir_enable_deumi: '0', + humAutoFanDisable: '0', + dehumAutoFanDisable: '0', + humSwingDisable: '0', + dehumSwingDisable: '0', + out_type_umi: '0', + out_type_deumi: '0', + soglia_man_umi: '50', + soglia_man_deumi: '50', + soglia_man_notte_umi: '50', + soglia_man_notte_deumi: '50', + night_mode_umi: '0', + soglia_semiauto_umi: '50', + umi_base_umi: '0', + umi_base_deumi: '0', + coolLimitMax: '300', + coolLimitMin: '50', + heatLimitMax: '300', + heatLimitMin: '50', + icon_id: '0', + objectId: 'DOM#CL#31.1', + placeId: 'GEN#PL#3', + viewOnly: '0', + temperatura: '250', + auto_man: '2', + est_inv: '0', + soglia_attiva: '265', + out_value_inv: '0', + out_value_est: '0', + dir_out_inv: '0', + dir_out_est: '0', + semiauto_enabled: '0', + umidita: '47', + auto_man_umi: '2', + deumi_umi: '0', + soglia_attiva_umi: '50', + out_value_umi: '0', + out_value_deumi: '0', + dir_out_umi: '0', + dir_out_deumi: '0', + semiauto_umi_enabled: '0', + connectionStatus: 0, + }, + }, + { + id: 'DOM#CL#25.1', + data: { + id: 'DOM#CL#25.1', + type: 9, + sub_type: 16, + descrizione: 'Termostato cucina', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '4', + powerst: '1', + num_modulo: 25, + num_ingresso: 1, + num_moduloIE: '0', + num_uscitaIE: '0', + num_moduloI: '7', + num_uscitaI: '2', + num_moduloE: '7', + num_uscitaE: '2', + num_moduloI_ana: '0', + num_uscitaI_ana: '0', + num_moduloE_ana: '0', + num_uscitaE_ana: '0', + soglia_man_inv: '215', + soglia_man_est: '265', + soglia_man_notte_inv: '170', + soglia_man_notte_est: '270', + soglia_semiauto: '220', + night_mode: '0', + soglia_auto_inv: '200', + soglia_auto_est: '200', + out_enable_inv: '0', + out_enable_est: '0', + dir_enable_inv: '0', + dir_enable_est: '0', + heatAutoFanDisable: '0', + coolAutoFanDisable: '0', + heatSwingDisable: '0', + coolSwingDisable: '0', + out_type_inv: '0', + out_type_est: '0', + temp_base_inv: '200', + temp_base_est: '200', + num_moduloUD: '0', + num_uscitaUD: '0', + num_moduloU: '0', + num_uscitaU: '0', + num_moduloD: '33', + num_uscitaD: '2', + num_moduloU_ana: '0', + num_uscitaU_ana: '0', + num_moduloD_ana: '0', + num_uscitaD_ana: '0', + out_enable_umi: '0', + out_enable_deumi: '0', + dir_enable_umi: '0', + dir_enable_deumi: '0', + humAutoFanDisable: '0', + dehumAutoFanDisable: '0', + humSwingDisable: '0', + dehumSwingDisable: '0', + out_type_umi: '0', + out_type_deumi: '0', + soglia_man_umi: '50', + soglia_man_deumi: '55', + soglia_man_notte_umi: '70', + soglia_man_notte_deumi: '50', + night_mode_umi: '0', + soglia_semiauto_umi: '55', + umi_base_umi: '0', + umi_base_deumi: '0', + coolLimitMax: '300', + coolLimitMin: '50', + heatLimitMax: '300', + heatLimitMin: '50', + icon_id: '0', + objectId: 'DOM#CL#25.1', + placeId: 'GEN#PL#3', + viewOnly: '0', + temperatura: '252', + auto_man: '2', + est_inv: '0', + soglia_attiva: '265', + out_value_inv: '0', + out_value_est: '0', + dir_out_inv: '15', + dir_out_est: '20', + semiauto_enabled: '0', + umidita: '53', + auto_man_umi: '2', + deumi_umi: '0', + soglia_attiva_umi: '55', + out_value_umi: '0', + out_value_deumi: '0', + dir_out_umi: '0', + dir_out_deumi: '0', + semiauto_umi_enabled: '0', + }, + }, + { + id: 'DOM#CL#26.1', + data: { + id: 'DOM#CL#26.1', + type: 9, + sub_type: 16, + descrizione: 'Termostato bagno giorno', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '1', + num_modulo: 26, + num_ingresso: 1, + num_moduloIE: '0', + num_uscitaIE: '0', + num_moduloI: '7', + num_uscitaI: '3', + num_moduloE: '7', + num_uscitaE: '3', + num_moduloI_ana: '0', + num_uscitaI_ana: '0', + num_moduloE_ana: '0', + num_uscitaE_ana: '0', + soglia_man_inv: '200', + soglia_man_est: '265', + soglia_man_notte_inv: '200', + soglia_man_notte_est: '200', + soglia_semiauto: '230', + night_mode: '0', + soglia_auto_inv: '200', + soglia_auto_est: '200', + out_enable_inv: '0', + out_enable_est: '0', + dir_enable_inv: '0', + dir_enable_est: '0', + heatAutoFanDisable: '0', + coolAutoFanDisable: '0', + heatSwingDisable: '0', + coolSwingDisable: '0', + out_type_inv: '0', + out_type_est: '0', + temp_base_inv: '200', + temp_base_est: '200', + num_moduloUD: '0', + num_uscitaUD: '0', + num_moduloU: '0', + num_uscitaU: '0', + num_moduloD: '33', + num_uscitaD: '3', + num_moduloU_ana: '0', + num_uscitaU_ana: '0', + num_moduloD_ana: '0', + num_uscitaD_ana: '0', + out_enable_umi: '0', + out_enable_deumi: '0', + dir_enable_umi: '0', + dir_enable_deumi: '0', + humAutoFanDisable: '0', + dehumAutoFanDisable: '0', + humSwingDisable: '0', + dehumSwingDisable: '0', + out_type_umi: '0', + out_type_deumi: '0', + soglia_man_umi: '50', + soglia_man_deumi: '55', + soglia_man_notte_umi: '70', + soglia_man_notte_deumi: '50', + night_mode_umi: '0', + soglia_semiauto_umi: '50', + umi_base_umi: '0', + umi_base_deumi: '0', + coolLimitMax: '300', + coolLimitMin: '50', + heatLimitMax: '300', + heatLimitMin: '50', + icon_id: '0', + objectId: 'DOM#CL#26.1', + placeId: 'GEN#PL#3', + viewOnly: '0', + temperatura: '256', + auto_man: '2', + est_inv: '0', + soglia_attiva: '265', + out_value_inv: '0', + out_value_est: '0', + dir_out_inv: '0', + dir_out_est: '0', + semiauto_enabled: '0', + umidita: '47', + auto_man_umi: '2', + deumi_umi: '0', + soglia_attiva_umi: '55', + out_value_umi: '0', + out_value_deumi: '0', + dir_out_umi: '0', + dir_out_deumi: '0', + semiauto_umi_enabled: '0', + }, + }, + { + id: 'DOM#CL#27.1', + data: { + id: 'DOM#CL#27.1', + type: 9, + sub_type: 16, + descrizione: 'Termostato bagno notte', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '1', + num_modulo: 27, + num_ingresso: 1, + num_moduloIE: '0', + num_uscitaIE: '0', + num_moduloI: '7', + num_uscitaI: '4', + num_moduloE: '7', + num_uscitaE: '4', + num_moduloI_ana: '0', + num_uscitaI_ana: '0', + num_moduloE_ana: '0', + num_uscitaE_ana: '0', + soglia_man_inv: '200', + soglia_man_est: '265', + soglia_man_notte_inv: '200', + soglia_man_notte_est: '200', + soglia_semiauto: '240', + night_mode: '0', + soglia_auto_inv: '200', + soglia_auto_est: '200', + out_enable_inv: '0', + out_enable_est: '0', + dir_enable_inv: '0', + dir_enable_est: '0', + heatAutoFanDisable: '0', + coolAutoFanDisable: '0', + heatSwingDisable: '0', + coolSwingDisable: '0', + out_type_inv: '0', + out_type_est: '0', + temp_base_inv: '200', + temp_base_est: '200', + num_moduloUD: '0', + num_uscitaUD: '0', + num_moduloU: '0', + num_uscitaU: '0', + num_moduloD: '33', + num_uscitaD: '4', + num_moduloU_ana: '0', + num_uscitaU_ana: '0', + num_moduloD_ana: '0', + num_uscitaD_ana: '0', + out_enable_umi: '0', + out_enable_deumi: '0', + dir_enable_umi: '0', + dir_enable_deumi: '0', + humAutoFanDisable: '0', + dehumAutoFanDisable: '0', + humSwingDisable: '0', + dehumSwingDisable: '0', + out_type_umi: '0', + out_type_deumi: '0', + soglia_man_umi: '50', + soglia_man_deumi: '55', + soglia_man_notte_umi: '70', + soglia_man_notte_deumi: '50', + night_mode_umi: '0', + soglia_semiauto_umi: '55', + umi_base_umi: '0', + umi_base_deumi: '0', + coolLimitMax: '300', + coolLimitMin: '50', + heatLimitMax: '300', + heatLimitMin: '50', + icon_id: '0', + objectId: 'DOM#CL#27.1', + placeId: 'GEN#PL#3', + viewOnly: '0', + temperatura: '255', + auto_man: '2', + est_inv: '0', + soglia_attiva: '265', + out_value_inv: '0', + out_value_est: '0', + dir_out_inv: '0', + dir_out_est: '0', + semiauto_enabled: '0', + umidita: '48', + auto_man_umi: '2', + deumi_umi: '0', + soglia_attiva_umi: '55', + out_value_umi: '0', + out_value_deumi: '0', + dir_out_umi: '0', + dir_out_deumi: '0', + semiauto_umi_enabled: '0', + }, + }, + { + id: 'DOM#CL#28.1', + data: { + id: 'DOM#CL#28.1', + type: 9, + sub_type: 16, + descrizione: 'Termostato camera da letto', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '1', + num_modulo: 28, + num_ingresso: 1, + num_moduloIE: '0', + num_uscitaIE: '0', + num_moduloI: '7', + num_uscitaI: '5', + num_moduloE: '7', + num_uscitaE: '5', + num_moduloI_ana: '0', + num_uscitaI_ana: '0', + num_moduloE_ana: '0', + num_uscitaE_ana: '0', + soglia_man_inv: '200', + soglia_man_est: '265', + soglia_man_notte_inv: '170', + soglia_man_notte_est: '270', + soglia_semiauto: '230', + night_mode: '0', + soglia_auto_inv: '200', + soglia_auto_est: '200', + out_enable_inv: '0', + out_enable_est: '0', + dir_enable_inv: '0', + dir_enable_est: '0', + heatAutoFanDisable: '0', + coolAutoFanDisable: '0', + heatSwingDisable: '0', + coolSwingDisable: '0', + out_type_inv: '0', + out_type_est: '0', + temp_base_inv: '200', + temp_base_est: '200', + num_moduloUD: '0', + num_uscitaUD: '0', + num_moduloU: '0', + num_uscitaU: '0', + num_moduloD: '33', + num_uscitaD: '5', + num_moduloU_ana: '0', + num_uscitaU_ana: '0', + num_moduloD_ana: '0', + num_uscitaD_ana: '0', + out_enable_umi: '0', + out_enable_deumi: '0', + dir_enable_umi: '0', + dir_enable_deumi: '0', + humAutoFanDisable: '0', + dehumAutoFanDisable: '0', + humSwingDisable: '0', + dehumSwingDisable: '0', + out_type_umi: '0', + out_type_deumi: '0', + soglia_man_umi: '50', + soglia_man_deumi: '55', + soglia_man_notte_umi: '70', + soglia_man_notte_deumi: '50', + night_mode_umi: '0', + soglia_semiauto_umi: '55', + umi_base_umi: '0', + umi_base_deumi: '0', + coolLimitMax: '300', + coolLimitMin: '50', + heatLimitMax: '300', + heatLimitMin: '50', + icon_id: '0', + objectId: 'DOM#CL#28.1', + placeId: 'GEN#PL#3', + viewOnly: '0', + temperatura: '257', + auto_man: '2', + est_inv: '0', + soglia_attiva: '265', + out_value_inv: '0', + out_value_est: '0', + dir_out_inv: '15', + dir_out_est: '20', + semiauto_enabled: '0', + umidita: '47', + auto_man_umi: '2', + deumi_umi: '0', + soglia_attiva_umi: '55', + out_value_umi: '0', + out_value_deumi: '0', + dir_out_umi: '0', + dir_out_deumi: '0', + semiauto_umi_enabled: '0', + }, + }, + { + id: 'DOM#CL#30.1', + data: { + id: 'DOM#CL#30.1', + type: 9, + sub_type: 16, + descrizione: 'Termostato camera Vittoria', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '1', + num_modulo: 30, + num_ingresso: 1, + num_moduloIE: '0', + num_uscitaIE: '0', + num_moduloI: '7', + num_uscitaI: '7', + num_moduloE: '7', + num_uscitaE: '7', + num_moduloI_ana: '0', + num_uscitaI_ana: '0', + num_moduloE_ana: '0', + num_uscitaE_ana: '0', + soglia_man_inv: '220', + soglia_man_est: '260', + soglia_man_notte_inv: '170', + soglia_man_notte_est: '270', + soglia_semiauto: '230', + night_mode: '0', + soglia_auto_inv: '200', + soglia_auto_est: '200', + out_enable_inv: '0', + out_enable_est: '0', + dir_enable_inv: '0', + dir_enable_est: '0', + heatAutoFanDisable: '0', + coolAutoFanDisable: '0', + heatSwingDisable: '0', + coolSwingDisable: '0', + out_type_inv: '0', + out_type_est: '0', + temp_base_inv: '200', + temp_base_est: '200', + num_moduloUD: '0', + num_uscitaUD: '0', + num_moduloU: '0', + num_uscitaU: '0', + num_moduloD: '33', + num_uscitaD: '7', + num_moduloU_ana: '0', + num_uscitaU_ana: '0', + num_moduloD_ana: '0', + num_uscitaD_ana: '0', + out_enable_umi: '0', + out_enable_deumi: '0', + dir_enable_umi: '0', + dir_enable_deumi: '0', + humAutoFanDisable: '0', + dehumAutoFanDisable: '0', + humSwingDisable: '0', + dehumSwingDisable: '0', + out_type_umi: '0', + out_type_deumi: '0', + soglia_man_umi: '50', + soglia_man_deumi: '50', + soglia_man_notte_umi: '70', + soglia_man_notte_deumi: '50', + night_mode_umi: '0', + soglia_semiauto_umi: '50', + umi_base_umi: '0', + umi_base_deumi: '0', + coolLimitMax: '300', + coolLimitMin: '50', + heatLimitMax: '300', + heatLimitMin: '50', + icon_id: '0', + objectId: 'DOM#CL#30.1', + placeId: 'GEN#PL#3', + viewOnly: '0', + temperatura: '260', + auto_man: '2', + est_inv: '0', + soglia_attiva: '260', + out_value_inv: '0', + out_value_est: '0', + dir_out_inv: '15', + dir_out_est: '20', + semiauto_enabled: '0', + umidita: '39', + auto_man_umi: '2', + deumi_umi: '0', + soglia_attiva_umi: '50', + out_value_umi: '0', + out_value_deumi: '0', + dir_out_umi: '0', + dir_out_deumi: '0', + semiauto_umi_enabled: '0', + }, + }, + { + id: 'DOM#CL#29.1', + data: { + id: 'DOM#CL#29.1', + type: 9, + sub_type: 16, + descrizione: 'Termostato camera Beatrice', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '0', + powerst: '1', + num_modulo: 29, + num_ingresso: 1, + num_moduloIE: '0', + num_uscitaIE: '0', + num_moduloI: '7', + num_uscitaI: '6', + num_moduloE: '7', + num_uscitaE: '6', + num_moduloI_ana: '0', + num_uscitaI_ana: '0', + num_moduloE_ana: '0', + num_uscitaE_ana: '0', + soglia_man_inv: '215', + soglia_man_est: '265', + soglia_man_notte_inv: '170', + soglia_man_notte_est: '270', + soglia_semiauto: '230', + night_mode: '0', + soglia_auto_inv: '200', + soglia_auto_est: '200', + out_enable_inv: '0', + out_enable_est: '0', + dir_enable_inv: '0', + dir_enable_est: '0', + heatAutoFanDisable: '0', + coolAutoFanDisable: '0', + heatSwingDisable: '0', + coolSwingDisable: '0', + out_type_inv: '0', + out_type_est: '0', + temp_base_inv: '200', + temp_base_est: '200', + num_moduloUD: '0', + num_uscitaUD: '0', + num_moduloU: '0', + num_uscitaU: '0', + num_moduloD: '33', + num_uscitaD: '6', + num_moduloU_ana: '0', + num_uscitaU_ana: '0', + num_moduloD_ana: '0', + num_uscitaD_ana: '0', + out_enable_umi: '0', + out_enable_deumi: '0', + dir_enable_umi: '0', + dir_enable_deumi: '0', + humAutoFanDisable: '0', + dehumAutoFanDisable: '0', + humSwingDisable: '0', + dehumSwingDisable: '0', + out_type_umi: '0', + out_type_deumi: '0', + soglia_man_umi: '50', + soglia_man_deumi: '64', + soglia_man_notte_umi: '70', + soglia_man_notte_deumi: '50', + night_mode_umi: '0', + soglia_semiauto_umi: '55', + umi_base_umi: '0', + umi_base_deumi: '0', + coolLimitMax: '300', + coolLimitMin: '50', + heatLimitMax: '300', + heatLimitMin: '50', + icon_id: '0', + objectId: 'DOM#CL#29.1', + placeId: 'GEN#PL#3', + viewOnly: '0', + temperatura: '262', + auto_man: '2', + est_inv: '0', + soglia_attiva: '265', + out_value_inv: '0', + out_value_est: '0', + dir_out_inv: '15', + dir_out_est: '20', + semiauto_enabled: '0', + umidita: '49', + auto_man_umi: '2', + deumi_umi: '0', + soglia_attiva_umi: '64', + out_value_umi: '0', + out_value_deumi: '0', + dir_out_umi: '0', + dir_out_deumi: '0', + semiauto_umi_enabled: '0', + }, + }, + ], + }, + }, + { + id: 'DOM#CN#2.1', + data: { + id: 'DOM#CN#2.1', + type: 11, + sub_type: 15, + descrizione: 'Controllo carichi TA ', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '255', + powerst: '255', + num_modulo: '2', + num_uscita: '1', + label_value: 'Wh', + label_price: '€', + thr_tab: ['-1.000000', '-1.000000', '-1.000000', '-1.000000'], + price_thr_tab: ['-1.000000', '-1.000000', '-1.000000', '-1.000000'], + prod: '0', + count_div: '0.000000', + cost: '0.180000', + kCO2: '0.531000', + compare: '0', + groupOrder: '0', + icon_id: '0', + objectId: 'DOM#CN#2.1', + placeId: '', + toolbar_active: '0', + visible: '1', + instant_power: '1848.000000', + }, + }, + { + id: 'DOM#LC#2.1', + data: { + id: 'DOM#LC#2.1', + type: 10, + sub_type: 0, + descrizione: 'Condizionatore', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '1', + powerst: '1', + num_modulo: 2, + num_uscita: 1, + icon_id: '0', + objectId: 'DOM#LC#2.1', + placeId: '', + toolbar_active: '0', + instant_power: '1848', + num_module_TA: 34, + num_out_TA: 8, + description_TA: 'Controllo carichi TA ', + out_power: 60, + }, + }, + { + id: 'DOM#LC#2.2', + data: { + id: 'DOM#LC#2.2', + type: 10, + sub_type: 0, + descrizione: 'Lavastoviglie', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '1', + powerst: '1', + num_modulo: 2, + num_uscita: 2, + icon_id: '0', + objectId: 'DOM#LC#2.2', + placeId: '', + toolbar_active: '0', + instant_power: '0', + num_module_TA: 3, + num_out_TA: 3, + description_TA: 'Controllo carichi TA ', + out_power: 36, + }, + }, + { + id: 'DOM#LC#2.3', + data: { + id: 'DOM#LC#2.3', + type: 10, + sub_type: 0, + descrizione: 'Lavatrice', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '1', + powerst: '1', + num_modulo: 2, + num_uscita: 3, + icon_id: '0', + objectId: 'DOM#LC#2.3', + placeId: '', + toolbar_active: '0', + instant_power: '0', + num_module_TA: 3, + num_out_TA: 2, + description_TA: 'Controllo carichi TA ', + out_power: 36, + }, + }, + { + id: 'DOM#LC#2.4', + data: { + id: 'DOM#LC#2.4', + type: 10, + sub_type: 0, + descrizione: 'Forno', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '1', + powerst: '1', + num_modulo: 2, + num_uscita: 4, + icon_id: '0', + objectId: 'DOM#LC#2.4', + placeId: '', + toolbar_active: '0', + instant_power: '0', + num_module_TA: 3, + num_out_TA: 1, + description_TA: 'Controllo carichi TA ', + out_power: 46, + }, + }, + { + id: 'DOM#LC#2.5', + data: { + id: 'DOM#LC#2.5', + type: 10, + sub_type: 0, + descrizione: 'Induzione', + sched_status: '0', + sched_lock: '1970-01-01 01:00:00', + status: '1', + powerst: '1', + num_modulo: 2, + num_uscita: 5, + icon_id: '0', + objectId: 'DOM#LC#2.5', + placeId: '', + toolbar_active: '0', + instant_power: '0', + num_module_TA: 3, + num_out_TA: 4, + description_TA: 'Controllo carichi TA ', + out_power: 69, + }, + }, + ], +}; diff --git a/src/accessories/blind.ts b/src/accessories/blind.ts index 8439fea..bfe4307 100644 --- a/src/accessories/blind.ts +++ b/src/accessories/blind.ts @@ -1,20 +1,24 @@ import { ComelitAccessory } from './comelit'; -import { BlindDeviceData, ComelitClient, OBJECT_SUBTYPE } from 'comelit-client'; +import { BlindDeviceData, ComelitClient } from 'comelit-client'; import { ComelitPlatform } from '../comelit-platform'; import { CharacteristicEventTypes, PlatformAccessory, Service } from 'homebridge'; -import { PositionState } from './hap'; import { CharacteristicSetCallback } from 'hap-nodejs'; export abstract class Blind extends ComelitAccessory { static readonly OPEN = 100; static readonly CLOSED = 0; - static readonly OPENING_CLOSING_TIME = 35; // 35 seconds to open approx. We should have this in the config + static readonly CLOSING_TIME = 35; // 35 seconds to close approx. + static readonly OPENING_TIME = 37; // 37 seconds to open approx. protected coveringService: Service; protected positionState: number; - constructor(platform: ComelitPlatform, accessory: PlatformAccessory, client: ComelitClient) { + protected constructor( + platform: ComelitPlatform, + accessory: PlatformAccessory, + client: ComelitClient + ) { super(platform, accessory, client); } @@ -26,10 +30,12 @@ export abstract class Blind extends ComelitAccessory { this.accessory.getService(this.platform.Service.WindowCovering) || this.accessory.addService(this.platform.Service.WindowCovering); - this.coveringService.setCharacteristic(Characteristic.PositionState, PositionState.STOPPED); - this.coveringService.setCharacteristic(Characteristic.TargetPosition, Blind.OPEN); - this.coveringService.setCharacteristic(Characteristic.CurrentPosition, Blind.OPEN); - this.positionState = PositionState.STOPPED; + this.positionState = this.getPositionStateFromDeviceData(); + const position = this.getPositionFromDeviceData(); + + this.coveringService.setCharacteristic(Characteristic.PositionState, this.positionState); + this.coveringService.setCharacteristic(Characteristic.TargetPosition, position); + this.coveringService.setCharacteristic(Characteristic.CurrentPosition, position); this.coveringService .getCharacteristic(Characteristic.TargetPosition) @@ -46,4 +52,8 @@ export abstract class Blind extends ComelitAccessory { public abstract setPosition(position: number, callback: CharacteristicSetCallback): Promise; public abstract update(data: BlindDeviceData); + + protected abstract getPositionFromDeviceData(): number; + + protected abstract getPositionStateFromDeviceData(): number; } diff --git a/src/accessories/comelit.ts b/src/accessories/comelit.ts index e251c8d..e27166b 100644 --- a/src/accessories/comelit.ts +++ b/src/accessories/comelit.ts @@ -7,7 +7,6 @@ export abstract class ComelitAccessory { readonly accessory: PlatformAccessory; readonly log: Logger; readonly client: ComelitClient; - protected device: Readonly; services: Service[]; reachable: boolean; @@ -19,7 +18,6 @@ export abstract class ComelitAccessory { ) { this.platform = platform; this.accessory = accessory; - this.device = this.accessory.context as T; this.log = platform.log; this.client = client; this.services = this.initServices(); @@ -35,6 +33,10 @@ export abstract class ComelitAccessory { return []; } + get device(): Readonly { + return this.accessory.context as T; + } + identify(): void {} protected initAccessoryInformation(): Service { @@ -58,7 +60,7 @@ export abstract class ComelitAccessory { protected abstract update(data: T): void; updateDevice(data: T) { - this.device = { ...this.device, ...data }; + this.accessory.context = { ...this.accessory.context, ...data }; this.update(data); } } diff --git a/src/accessories/enhanced-blind.ts b/src/accessories/enhanced-blind.ts index 5d80ed4..9bd68f5 100644 --- a/src/accessories/enhanced-blind.ts +++ b/src/accessories/enhanced-blind.ts @@ -1,28 +1,9 @@ -import { BlindDeviceData, ComelitClient, ObjectStatus } from 'comelit-client'; +import { BlindDeviceData, ComelitClient } from 'comelit-client'; import { ComelitPlatform } from '../comelit-platform'; import { Callback, PlatformAccessory } from 'homebridge'; import { PositionState } from './hap'; import { Blind } from './blind'; - -/** - * Returns the position as a value between 0 and 255. - * Since Comelit system uses 0 for opened and 100 for closed, this function inverts the percentage to accommodate - * the value for Homekit, that uses 0 for closed nad 100 for fully opened. - * @param position number 0-100 - */ -function getPositionAsByte(position: number) { - return Math.round((100 - position) * 2.55); -} - -/** - * Returns the position as a value between 0 and 100 - * Since Comelit system uses 0 for opened and 100 for closed, this function inverts the percentage to accommodate - * the value for Homekit, that uses 0 for closed nad 100 for fully opened. - * @param position number 0-255 - */ -function getPositionAsPerc(position: string) { - return Math.round((100 - parseInt(position)) / 2.55); -} +import { getPositionAsByte, getPositionAsPerc } from '../utils'; export class EnhancedBlind extends Blind { constructor(platform: ComelitPlatform, accessory: PlatformAccessory, client: ComelitClient) { @@ -35,10 +16,7 @@ export class EnhancedBlind extends Blind { const currentPosition = this.coveringService.getCharacteristic(Characteristic.CurrentPosition) .value as number; this.log.info(`Setting position to ${position}%. Current position is ${currentPosition}`); - this.coveringService.setCharacteristic( - Characteristic.PositionState, - position > currentPosition ? PositionState.INCREASING : PositionState.DECREASING - ); + this.coveringService.getCharacteristic(Characteristic.TargetPosition).updateValue(position); await this.client.setBlindPosition(this.device.id, getPositionAsByte(position)); callback(); } catch (e) { @@ -51,32 +29,41 @@ export class EnhancedBlind extends Blind { const Characteristic = this.platform.Characteristic; const position = getPositionAsPerc(data.position); const status = parseInt(data.status); // can be 1 (increasing), 2 (decreasing) or 0 (stopped) + this.positionState = this.getPositionStateFromDeviceData(); + this.coveringService.getCharacteristic(Characteristic.TargetPosition).updateValue(position); + this.coveringService.getCharacteristic(Characteristic.CurrentPosition).updateValue(position); switch (status) { - case ObjectStatus.ON: - this.positionState = PositionState.INCREASING; + case 0: + this.coveringService + .getCharacteristic(Characteristic.PositionState) + .updateValue(PositionState.STOPPED); break; - case ObjectStatus.OFF: { - this.log.info( - `Blind is now at position ${position} (it was ${ - this.positionState === PositionState.DECREASING ? 'closing' : 'opening' - })` - ); - this.positionState = PositionState.STOPPED; - this.coveringService.getCharacteristic(Characteristic.TargetPosition).updateValue(position); + case 1: this.coveringService - .getCharacteristic(Characteristic.CurrentPosition) - .updateValue(position); + .getCharacteristic(Characteristic.PositionState) + .updateValue(PositionState.INCREASING); break; - } - case ObjectStatus.IDLE: - this.positionState = PositionState.DECREASING; + case 2: + this.coveringService + .getCharacteristic(Characteristic.PositionState) + .updateValue(PositionState.DECREASING); break; } + } - this.coveringService - .getCharacteristic(Characteristic.PositionState) - .updateValue(this.positionState); - this.coveringService.getCharacteristic(Characteristic.TargetPosition).updateValue(position); - this.coveringService.getCharacteristic(Characteristic.CurrentPosition).updateValue(position); + protected getPositionFromDeviceData(): number { + return getPositionAsPerc(this.device.position); + } + + protected getPositionStateFromDeviceData(): number { + const status = parseInt(this.device.status); // can be 1 (increasing), 2 (decreasing) or 0 (stopped) + switch (status) { + case 0: + return PositionState.STOPPED; + case 1: + return PositionState.INCREASING; + case 2: + return PositionState.DECREASING; + } } } diff --git a/src/accessories/standard-blind.ts b/src/accessories/standard-blind.ts index a35f062..b5a784b 100644 --- a/src/accessories/standard-blind.ts +++ b/src/accessories/standard-blind.ts @@ -1,26 +1,33 @@ -import { BlindDeviceData, ComelitClient, ObjectStatus } from 'comelit-client'; +import { BlindDeviceData, ComelitClient } from 'comelit-client'; import { ComelitPlatform } from '../comelit-platform'; -import { Callback, PlatformAccessory } from 'homebridge'; +import { Callback, PlatformAccessory, Service } from 'homebridge'; import { PositionState } from './hap'; import { Blind } from './blind'; +import { getPositionAsByte, getPositionAsPerc } from '../utils'; import Timeout = NodeJS.Timeout; export class StandardBlind extends Blind { - static readonly OPENING_CLOSING_TIME = 35; // 35 seconds to open approx. We should have this in the config - private timeout: Timeout; - private lastCommandTime: number; + private lastCommandTime: number = null; private readonly closingTime: number; + private readonly openingTime: number; constructor( platform: ComelitPlatform, accessory: PlatformAccessory, client: ComelitClient, + openingTime?: number, closingTime?: number ) { super(platform, accessory, client); - this.closingTime = (closingTime || Blind.OPENING_CLOSING_TIME) * 1000; - this.log.info(`Blind ${accessory.context.id} has closing time of ${this.closingTime}`); + this.closingTime = (closingTime || Blind.CLOSING_TIME) * 1000; + this.openingTime = (openingTime || Blind.OPENING_TIME) * 1000; + this.log.info(`Blind ${this.device.id} has closing time of ${this.closingTime}`); + } + + protected initServices(): Service[] { + this.accessory.context = { ...this.device, position: getPositionAsByte(Blind.OPEN) }; + return super.initServices(); } public async setPosition(position: number, callback: Callback) { @@ -34,17 +41,22 @@ export class StandardBlind extends Blind { const currentPosition = this.coveringService.getCharacteristic(Characteristic.CurrentPosition) .value as number; - const status = position < currentPosition ? ObjectStatus.OFF : ObjectStatus.ON; + const status = position < currentPosition ? 0 : 1; const delta = currentPosition - position; this.log.info( `Setting position to ${position}%. Current position is ${currentPosition}. Delta is ${delta}` ); if (delta !== 0) { + this.positionState = + position < currentPosition ? PositionState.DECREASING : PositionState.INCREASING; await this.client.toggleDeviceStatus(this.device.id, status); this.lastCommandTime = new Date().getTime(); + this.coveringService.getCharacteristic(Characteristic.TargetPosition).updateValue(position); + const time = status === 1 ? this.openingTime : this.closingTime; + const ms = (time * Math.abs(delta)) / 100; this.timeout = setTimeout(async () => { return this.resetTimeout(); - }, (this.closingTime * Math.abs(delta)) / 100); + }, ms); } callback(); } catch (e) { @@ -61,40 +73,26 @@ export class StandardBlind extends Blind { this.timeout = null; await this.client.toggleDeviceStatus( this.device.id, - this.positionState === PositionState.DECREASING ? ObjectStatus.ON : ObjectStatus.OFF + this.positionState === PositionState.DECREASING ? 1 : 0 ); // stop the blind } public update(data: BlindDeviceData) { - const Characteristic = this.platform.Characteristic; const status = parseInt(data.status); - const now = new Date().getTime(); + const statusDesc = status === 0 ? 'STOPPED' : status === 1 ? 'MOVING UP' : 'MOVING DOWN'; + const position = this.positionFromTime(); + const positionAsByte = getPositionAsByte(position); + this.log.debug(`Saved position ${getPositionAsPerc(`${positionAsByte}`)}% (${positionAsByte})`); + this.log.info(`Blind is now at position ${position} (status is ${statusDesc})`); switch (status) { - case ObjectStatus.ON: - this.lastCommandTime = now; - this.positionState = PositionState.INCREASING; + case 0: // stopped + this.blindStopped(positionAsByte, position); break; - case ObjectStatus.OFF: { - const position = this.positionFromTime(); - this.lastCommandTime = 0; - this.log.info( - `Blind is now at position ${position} (it was ${ - this.positionState === PositionState.DECREASING ? 'going down' : 'going up' - })` - ); - this.positionState = PositionState.STOPPED; - this.coveringService.getCharacteristic(Characteristic.TargetPosition).updateValue(position); - this.coveringService - .getCharacteristic(Characteristic.CurrentPosition) - .updateValue(position); - this.coveringService - .getCharacteristic(Characteristic.PositionState) - .updateValue(PositionState.STOPPED); + case 1: // going up + this.blindGoingUp(positionAsByte, position); break; - } - case ObjectStatus.IDLE: - this.lastCommandTime = now; - this.positionState = PositionState.DECREASING; + case 2: // going down + this.blindGoingDown(positionAsByte, position); break; } this.log.info( @@ -102,6 +100,64 @@ export class StandardBlind extends Blind { ); } + private blindGoingDown(positionAsByte: number, position: number) { + const now = new Date().getTime(); + const Characteristic = this.platform.Characteristic; + this.coveringService + .getCharacteristic(Characteristic.PositionState) + .updateValue(PositionState.DECREASING); + if (this.lastCommandTime) { + this.accessory.context = { ...this.device, position: positionAsByte }; + this.coveringService.getCharacteristic(Characteristic.CurrentPosition).updateValue(position); + } else { + this.lastCommandTime = now; // external command (physical button) + } + } + + private blindGoingUp(positionAsByte: number, position: number) { + const now = new Date().getTime(); + const Characteristic = this.platform.Characteristic; + this.coveringService + .getCharacteristic(Characteristic.PositionState) + .updateValue(PositionState.INCREASING); + if (this.lastCommandTime) { + this.accessory.context = { ...this.device, position: positionAsByte }; + this.coveringService.getCharacteristic(Characteristic.CurrentPosition).updateValue(position); + } else { + this.lastCommandTime = now; // external command (physical button) + } + } + + private blindStopped(positionAsByte: number, position: number) { + const Characteristic = this.platform.Characteristic; + this.coveringService + .getCharacteristic(Characteristic.PositionState) + .updateValue(PositionState.STOPPED); + if (this.lastCommandTime) { + this.accessory.context = { ...this.device, position: positionAsByte }; + this.lastCommandTime = 0; + this.coveringService.getCharacteristic(Characteristic.CurrentPosition).updateValue(position); + } else { + this.lastCommandTime = 0; + } + } + + protected getPositionFromDeviceData(): number { + return getPositionAsPerc(this.device.position); + } + + protected getPositionStateFromDeviceData(): number { + const status = parseInt(this.device.status); // can be 1 (increasing), 2 (decreasing) or 0 (stopped) + switch (status) { + case 1: + return PositionState.INCREASING; + case 0: + return PositionState.STOPPED; + case 2: + return PositionState.DECREASING; + } + } + private positionFromTime() { const Characteristic = this.platform.Characteristic; const now = new Date().getTime(); @@ -112,13 +168,13 @@ export class StandardBlind extends Blind { // Calculate the percentage of movement const deltaPercentage = Math.round(delta / (this.closingTime / 100)); this.log.info( - `Current position ${currentPosition}, delta is ${delta} (${deltaPercentage}%). State ${this.positionState}` + `Current position ${currentPosition}, delta is ${delta}ms (${deltaPercentage}%). State ${this.positionState}` ); if (this.positionState === PositionState.DECREASING) { // Blind is decreasing, subtract the delta - return currentPosition - deltaPercentage; + return Math.max(Blind.CLOSED, currentPosition - deltaPercentage); } // Blind is increasing, add the delta - return currentPosition + deltaPercentage; + return Math.min(StandardBlind.OPEN, currentPosition + deltaPercentage); } } diff --git a/src/comelit-platform.ts b/src/comelit-platform.ts index fa405db..24c185d 100644 --- a/src/comelit-platform.ts +++ b/src/comelit-platform.ts @@ -9,7 +9,14 @@ import { PlatformConfig, Service, } from 'homebridge'; -import { ComelitClient, DeviceData, HomeIndex, OBJECT_SUBTYPE, ROOT_ID } from 'comelit-client'; +import { + BlindDeviceData, + ComelitClient, + DeviceData, + HomeIndex, + OBJECT_SUBTYPE, + ROOT_ID, +} from 'comelit-client'; import express, { Express } from 'express'; import client, { register } from 'prom-client'; import * as http from 'http'; @@ -23,6 +30,7 @@ import { Irrigation } from './accessories/irrigation'; import { EnhancedBlind } from './accessories/enhanced-blind'; import { StandardBlind } from './accessories/standard-blind'; import Timeout = NodeJS.Timeout; +import { Blind } from './accessories/blind'; export interface HubConfig extends PlatformConfig { username: string; @@ -33,7 +41,9 @@ export interface HubConfig extends PlatformConfig { client_id?: string; export_prometheus_metrics?: boolean; exporter_http_port?: number; + blind_opening_time?: number; blind_closing_time?: number; + use_comelit_blind_timing?: boolean; keep_alive?: number; avoid_duplicates?: boolean; hide_lights?: boolean; @@ -61,6 +71,9 @@ expr.get('/metrics', async (req, res) => { } }); +const MIN_OPEN_CLOSE_TIME = 20; +const MAX_OPEN_CLOSE_TIME = 80; + export class ComelitPlatform implements DynamicPlatformPlugin { static KEEP_ALIVE_TIMEOUT = 120000; @@ -151,7 +164,7 @@ export class ComelitPlatform implements DynamicPlatformPlugin { return key; } - updateAccessory(id: string, data: DeviceData) { + updateAccessory(id: string, data: T) { try { const comelitAccessory = this.mappedAccessories.get(id); if (comelitAccessory) { @@ -211,16 +224,44 @@ export class ComelitPlatform implements DynamicPlatformPlugin { const accessory = this.createHapAccessory(deviceData, Categories.WINDOW_COVERING); // Enhanced blinds are able to set the position while standard ones are not (and we simulate it) const isEnhanced = deviceData.sub_type === OBJECT_SUBTYPE.ENHANCED_ELECTRIC_BLIND; + const openingTime = this.getOpeningTime(deviceData); + const closingTime = this.getClosingTime(deviceData); this.mappedAccessories.set( id, isEnhanced ? new EnhancedBlind(this, accessory, this.client) - : new StandardBlind(this, accessory, this.client, this.config.blind_closing_time) + : new StandardBlind(this, accessory, this.client, openingTime, closingTime) ); } }); } + public getClosingTime(deviceData: BlindDeviceData) { + try { + return this.config.use_comelit_blind_timing + ? Math.max( + MAX_OPEN_CLOSE_TIME, + Math.min(MIN_OPEN_CLOSE_TIME, parseInt(deviceData.closeTime)) + ) + : this.config.blind_closing_time; + } catch (e) { + return Blind.CLOSING_TIME; + } + } + + public getOpeningTime(deviceData: BlindDeviceData) { + try { + return this.config.use_comelit_blind_timing + ? Math.max( + MAX_OPEN_CLOSE_TIME, + Math.min(MIN_OPEN_CLOSE_TIME, parseInt(deviceData.openTime)) + ) + : this.config.blind_opening_time; + } catch (e) { + return Blind.OPENING_TIME; + } + } + private mapThermostats(homeIndex: HomeIndex) { const thermostatIds = [...homeIndex.thermostatsIndex.keys()]; this.log.info(`Found ${thermostatIds.length} thermostats`); @@ -278,7 +319,7 @@ export class ComelitPlatform implements DynamicPlatformPlugin { }); } - private createHapAccessory(deviceData: DeviceData, category: Categories, id?: string) { + public createHapAccessory(deviceData: DeviceData, category: Categories, id?: string) { const uuid = this.homebridge.hap.uuid.generate(id || deviceData.id); const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); const accessory = diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..92c17ca --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,19 @@ +/** + * Returns the position as a value between 0 and 255. + * Since Comelit system uses 0 for opened and 100 for closed, this function inverts the percentage to accommodate + * the value for Homekit, that uses 0 for closed nad 100 for fully opened. + * @param position number 0-100 + */ +export function getPositionAsByte(position: number): number { + return Math.round((100 - position) * 2.55); +} + +/** + * Returns the position as a value between 0 and 100 + * Since Comelit system uses 0 for opened and 100 for closed, this function inverts the percentage to accommodate + * the value for Homekit, that uses 0 for closed nad 100 for fully opened. + * @param position number 0-255 + */ +export function getPositionAsPerc(position: string): number { + return Math.round(100 - parseInt(position) / 2.55); +} diff --git a/tsconfig.json b/tsconfig.json index 83ea422..589ef2a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,5 +20,6 @@ "inlineSourceMap": true }, "lib": ["esnext"], + "exclude": ["**/__tests__/*"], "include": ["src"] } diff --git a/yarn.lock b/yarn.lock index 9279110..4c2556f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -389,6 +389,17 @@ "@types/yargs" "^15.0.0" chalk "^3.0.0" +"@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + "@leichtgewicht/ip-codec@^2.0.1": version "2.0.3" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz#0300943770e04231041a51bd39f0439b5c7ab4f0" @@ -486,6 +497,13 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + "@types/jest@^25.2.3": version "25.2.3" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.2.3.tgz#33d27e4c4716caae4eced355097a47ad363fdcaf" @@ -824,7 +842,7 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== -async-mqtt@2.6.1, async-mqtt@^2.6.1: +async-mqtt@2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/async-mqtt/-/async-mqtt-2.6.1.tgz#7cca37b0c766e00d7b0b33c9eb236e216ed06248" integrity sha512-EkXAwRzwMaPC6ji0EvNeM5OMe6VjMhEKVJJUN7gu/hGzkcDpZtaI34nUwdwCMbjQB3pnuSOHqQMFKsUpg+D8kA== @@ -1013,6 +1031,13 @@ braces@^2.3.1: split-string "^3.0.2" to-regex "^3.0.1" +braces@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + browser-process-hrtime@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" @@ -1036,6 +1061,13 @@ browserslist@^4.16.6: escalade "^3.1.1" node-releases "^1.1.75" +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -1048,7 +1080,7 @@ buffer-equal-constant-time@1.0.1: resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= -buffer-from@^1.0.0: +buffer-from@1.x, buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== @@ -1089,14 +1121,6 @@ call-bind@^1.0.0, call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" -callback-stream@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/callback-stream/-/callback-stream-1.1.0.tgz#4701a51266f06e06eaa71fc17233822d875f4908" - integrity sha1-RwGlEmbwbgbqpx/BcjOCLYdfSQg= - dependencies: - inherits "^2.0.1" - readable-stream "> 1.0.0 < 3.0.0" - callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -1248,17 +1272,17 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -comelit-client@2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/comelit-client/-/comelit-client-2.3.3.tgz#7b957a145db6df17c81d4b67b762d70f31a81b26" - integrity sha512-1V2QkHk40uZoexeeKYSU6r/apenTP7Ly2erXQMWlNNaHoS/lX2lzFQAJOljnPre9/rIMhjwvPQt+ZFt/a5PBZw== +comelit-client@2.3.5: + version "2.3.5" + resolved "https://registry.yarnpkg.com/comelit-client/-/comelit-client-2.3.5.tgz#b89270311bd2a1bd2bf5b93b42c008a147e1d902" + integrity sha512-TDepfVRu7oupILKLktewlRtSk6WvmIQM1xedpo9R6R6meUy5/PKFl464pin7QfyHB+uVBh0CAzarlWaKwtn1vw== dependencies: async-mqtt "2.6.1" atob "2.1.2" axios "0.21.1" chalk "3.0.0" lodash "4.17.21" - mqtt "4.2.1" + mqtt "4.2.8" mqtt-packet "6.6.0" querystring "0.2.0" typescript "3.9.7" @@ -1292,16 +1316,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - concat-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" @@ -1346,7 +1360,7 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -core-util-is@1.0.2, core-util-is@~1.0.0: +core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= @@ -1394,14 +1408,6 @@ cssstyle@^1.0.0: dependencies: cssom "0.3.x" -d@1, d@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" - integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== - dependencies: - es5-ext "^0.10.50" - type "^1.0.1" - dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -1548,16 +1554,6 @@ domexception@^1.0.1: dependencies: webidl-conversions "^4.0.2" -duplexify@^3.6.0: - version "3.7.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" - integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - duplexify@^4.1.1: version "4.1.2" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" @@ -1608,7 +1604,7 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: +end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -1668,63 +1664,6 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.50, es5-ext@~0.10.14: - version "0.10.53" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" - integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== - dependencies: - es6-iterator "~2.0.3" - es6-symbol "~3.1.3" - next-tick "~1.0.0" - -es6-iterator@~2.0.1, es6-iterator@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" - integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - -es6-map@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" - integrity sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA= - dependencies: - d "1" - es5-ext "~0.10.14" - es6-iterator "~2.0.1" - es6-set "~0.1.5" - es6-symbol "~3.1.1" - event-emitter "~0.3.5" - -es6-set@~0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" - integrity sha1-0rPsXU2ADO2BjbU40ol02wpzzLE= - dependencies: - d "1" - es5-ext "~0.10.14" - es6-iterator "~2.0.1" - es6-symbol "3.1.1" - event-emitter "~0.3.5" - -es6-symbol@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" - integrity sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc= - dependencies: - d "1" - es5-ext "~0.10.14" - -es6-symbol@^3.1.1, es6-symbol@~3.1.1, es6-symbol@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" - integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== - dependencies: - d "^1.0.1" - ext "^1.1.2" - escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -1889,14 +1828,6 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -event-emitter@~0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" - integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= - dependencies: - d "1" - es5-ext "~0.10.14" - event-target-shim@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" @@ -2001,13 +1932,6 @@ express@^4.17.1: utils-merge "1.0.1" vary "~1.1.2" -ext@^1.1.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.5.0.tgz#e93b97ae0cb23f8370380f6107d2d2b7887687ad" - integrity sha512-+ONcYoWj/SoQwUofMr94aGu05Ou4FepKi7N7b+O8T4jVfyIsZQV1/xeS8jpaBzF0csAk0KLXoHCxU7cKYZjo1Q== - dependencies: - type "^2.5.0" - extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -2023,7 +1947,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@^3.0.2, extend@~3.0.2: +extend@^3.0.2, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -2079,7 +2003,7 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -2135,6 +2059,13 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -2352,14 +2283,6 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - glob-parent@^5.0.0: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -2367,22 +2290,6 @@ glob-parent@^5.0.0: dependencies: is-glob "^4.0.1" -glob-stream@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4" - integrity sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ= - dependencies: - extend "^3.0.0" - glob "^7.1.1" - glob-parent "^3.1.0" - is-negated-glob "^1.0.0" - ordered-read-streams "^1.0.0" - pumpify "^1.3.5" - readable-stream "^2.1.5" - remove-trailing-separator "^1.0.1" - to-absolute-glob "^2.0.0" - unique-stream "^2.0.2" - glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.6: version "7.1.7" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" @@ -2449,7 +2356,7 @@ googleapis@>39.1.0: google-auth-library "^7.0.2" googleapis-common "^5.0.2" -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: version "4.2.8" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== @@ -2562,16 +2469,6 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -help-me@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/help-me/-/help-me-1.1.0.tgz#8f2d508d0600b4a456da2f086556e7e5c056a3c6" - integrity sha1-jy1QjQYAtKRW2i8IZVbn5cBWo8Y= - dependencies: - callback-stream "^1.0.2" - glob-stream "^6.1.0" - through2 "^2.0.1" - xtend "^4.0.0" - help-me@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/help-me/-/help-me-3.0.0.tgz#9803c81b5f346ad2bce2c6a0ba01b82257d319e8" @@ -2716,7 +2613,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -2771,14 +2668,6 @@ ipaddr.js@1.9.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== -is-absolute@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" - integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== - dependencies: - is-relative "^1.0.0" - is-windows "^1.0.1" - is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -2896,7 +2785,7 @@ is-extendable@^1.0.1: dependencies: is-plain-object "^2.0.4" -is-extglob@^2.1.0, is-extglob@^2.1.1: +is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= @@ -2916,13 +2805,6 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= - dependencies: - is-extglob "^2.1.0" - is-glob@^4.0.0, is-glob@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" @@ -2935,11 +2817,6 @@ is-map@^2.0.1, is-map@^2.0.2: resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== -is-negated-glob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" - integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= - is-negative-zero@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" @@ -2959,6 +2836,11 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -2974,13 +2856,6 @@ is-regex@^1.1.1, is-regex@^1.1.3: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-relative@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" - integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== - dependencies: - is-unc-path "^1.0.0" - is-set@^2.0.1, is-set@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" @@ -3026,13 +2901,6 @@ is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -is-unc-path@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" - integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== - dependencies: - unc-path-regex "^0.1.2" - is-weakmap@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" @@ -3043,7 +2911,7 @@ is-weakset@^2.0.1: resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83" integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw== -is-windows@^1.0.1, is-windows@^1.0.2: +is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== @@ -3053,7 +2921,7 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= -isarray@1.0.0, isarray@~1.0.0: +isarray@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -3458,6 +3326,18 @@ jest-util@^24.9.0: slash "^2.0.0" source-map "^0.6.0" +jest-util@^26.1.0: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" + integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + micromatch "^4.0.2" + jest-validate@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.9.0.tgz#0775c55360d173cd854e40180756d4ff52def8ab" @@ -3591,7 +3471,7 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -json5@^2.1.2: +json5@2.x, json5@^2.1.2: version "2.2.0" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== @@ -3728,7 +3608,7 @@ lodash.sortby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= -lodash@4.17.21, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20: +lodash@4.17.21, lodash@4.x, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -3755,6 +3635,11 @@ make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" +make-error@1.x: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + makeerror@1.0.x: version "1.0.11" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" @@ -3813,6 +3698,14 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" +micromatch@^4.0.2: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + mime-db@1.49.0: version "1.49.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" @@ -3855,6 +3748,11 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" +mkdirp@1.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + mkdirp@^0.5.1, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -3862,25 +3760,25 @@ mkdirp@^0.5.1, mkdirp@~0.5.1: dependencies: minimist "^1.2.5" -mqtt-packet@6.6.0: - version "6.6.0" - resolved "https://registry.yarnpkg.com/mqtt-packet/-/mqtt-packet-6.6.0.tgz#30126b6865992caaf223baae23c89e4061ad2e45" - integrity sha512-LvghnKMFC70hKWMVykmhJarlO5e7lT3t9s9A2qPCUx+lazL3Mq55U+eCV0eLi7/nRRQYvEUWo/2tTo89EjnCJQ== +mqtt-packet@6.10.0, mqtt-packet@^6.8.0: + version "6.10.0" + resolved "https://registry.yarnpkg.com/mqtt-packet/-/mqtt-packet-6.10.0.tgz#c8b507832c4152e3e511c0efa104ae4a64cd418f" + integrity sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA== dependencies: bl "^4.0.2" debug "^4.1.1" process-nextick-args "^2.0.1" -mqtt-packet@^6.3.2, mqtt-packet@^6.7.0, mqtt-packet@^6.8.0: - version "6.10.0" - resolved "https://registry.yarnpkg.com/mqtt-packet/-/mqtt-packet-6.10.0.tgz#c8b507832c4152e3e511c0efa104ae4a64cd418f" - integrity sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA== +mqtt-packet@6.6.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/mqtt-packet/-/mqtt-packet-6.6.0.tgz#30126b6865992caaf223baae23c89e4061ad2e45" + integrity sha512-LvghnKMFC70hKWMVykmhJarlO5e7lT3t9s9A2qPCUx+lazL3Mq55U+eCV0eLi7/nRRQYvEUWo/2tTo89EjnCJQ== dependencies: bl "^4.0.2" debug "^4.1.1" process-nextick-args "^2.0.1" -mqtt@*, mqtt@^4.1.0, mqtt@^4.2.6: +mqtt@*, mqtt@4.2.8, mqtt@^4.1.0: version "4.2.8" resolved "https://registry.yarnpkg.com/mqtt/-/mqtt-4.2.8.tgz#f0e54b138bcdaef6c55c547b3a4de9cf9074208c" integrity sha512-DJYjlXODVXtSDecN8jnNzi6ItX3+ufGsEs9OB3YV24HtkRrh7kpx8L5M1LuyF0KzaiGtWr2PzDcMGAY60KGOSA== @@ -3900,28 +3798,6 @@ mqtt@*, mqtt@^4.1.0, mqtt@^4.2.6: ws "^7.5.0" xtend "^4.0.2" -mqtt@4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/mqtt/-/mqtt-4.2.1.tgz#7dbc4a01b5188186db90919c6fce318692c77004" - integrity sha512-Iv893r+jWlo5GkNcPOfCGwW8M49IixwHiKLFFYTociEymSibUVCORVEjPXWPGzSxhn7BdlUeHicbRmWiv0Crkg== - dependencies: - base64-js "^1.3.0" - commist "^1.0.0" - concat-stream "^1.6.2" - debug "^4.1.1" - end-of-stream "^1.4.1" - es6-map "^0.1.5" - help-me "^1.0.1" - inherits "^2.0.3" - minimist "^1.2.5" - mqtt-packet "^6.3.2" - pump "^3.0.0" - readable-stream "^2.3.6" - reinterval "^1.1.0" - split2 "^3.1.0" - ws "^7.3.1" - xtend "^4.0.1" - mri@^1.1.4: version "1.1.6" resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.6.tgz#49952e1044db21dbf90f6cd92bc9c9a777d415a6" @@ -4003,11 +3879,6 @@ negotiator@0.6.2: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== -next-tick@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" - integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= - nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -4206,13 +4077,6 @@ optionator@^0.8.1, optionator@^0.8.3: type-check "~0.3.2" word-wrap "~1.2.3" -ordered-read-streams@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e" - integrity sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4= - dependencies: - readable-stream "^2.0.1" - os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -4320,11 +4184,6 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= - path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -4377,6 +4236,11 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= +picomatch@^2.2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" @@ -4474,7 +4338,7 @@ pretty-quick@^2.0.1: mri "^1.1.4" multimatch "^4.0.0" -process-nextick-args@^2.0.1, process-nextick-args@~2.0.0: +process-nextick-args@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== @@ -4484,7 +4348,7 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -prom-client@^13.1.0: +prom-client@13.2.0: version "13.2.0" resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-13.2.0.tgz#99d13357912dd400f8911b77df19f7b328a93e92" integrity sha512-wGr5mlNNdRNzEhRYXgboUU2LxHWIojxscJKmtG3R8f4/KiWqyYgXTLHs0+Ted7tG3zFT7pgHJbtomzZ1L0ARaQ== @@ -4517,14 +4381,6 @@ psl@^1.1.28: resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== -pump@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -4533,15 +4389,6 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -pumpify@^1.3.5: - version "1.5.1" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" - integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== - dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" - punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" @@ -4616,19 +4463,6 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" -"readable-stream@> 1.0.0 < 3.0.0", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.6, readable-stream@~2.3.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" @@ -4829,7 +4663,7 @@ rxjs@^6.6.0: dependencies: tslib "^1.9.0" -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.2, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -4886,18 +4720,18 @@ semver-regex@^3.1.2: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^7.3.2, semver@^7.3.4: +semver@7.x, semver@^7.3.2, semver@^7.3.4: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== dependencies: lru-cache "^6.0.0" +semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -5217,13 +5051,6 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" @@ -5328,22 +5155,6 @@ throat@^4.0.0: resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo= -through2-filter@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254" - integrity sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA== - dependencies: - through2 "~2.0.0" - xtend "~4.0.0" - -through2@^2.0.1, through2@~2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -5366,14 +5177,6 @@ tmpl@1.0.x: resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= -to-absolute-glob@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b" - integrity sha1-GGX0PZ50sIItufFFt4z/fQ98hJs= - dependencies: - is-absolute "^1.0.0" - is-negated-glob "^1.0.0" - to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -5394,6 +5197,13 @@ to-regex-range@^2.1.0: is-number "^3.0.0" repeat-string "^1.6.1" +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + to-regex@^3.0.1, to-regex@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" @@ -5424,6 +5234,22 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" +ts-jest@^26.4.4: + version "26.5.6" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.5.6.tgz#c32e0746425274e1dfe333f43cd3c800e014ec35" + integrity sha512-rua+rCP8DxpA8b4DQD/6X2HQS8Zy/xzViVYfEs2OQu68tkCuKLV0Md8pmX55+W24uRIyAsf/BajRfxOs+R2MKA== + dependencies: + bs-logger "0.x" + buffer-from "1.x" + fast-json-stable-stringify "2.x" + jest-util "^26.1.0" + json5 "2.x" + lodash "4.x" + make-error "1.x" + mkdirp "1.x" + semver "7.x" + yargs-parser "20.x" + tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -5483,16 +5309,6 @@ type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -type@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" - integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== - -type@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" - integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== - typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -5503,7 +5319,7 @@ typescript@3.9.7: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== -typescript@^4.1.3: +typescript@4.3.5: version "4.3.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== @@ -5518,11 +5334,6 @@ unbox-primitive@^1.0.1: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" -unc-path-regex@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" - integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= - union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" @@ -5533,14 +5344,6 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^2.0.1" -unique-stream@^2.0.2: - version "2.3.1" - resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.3.1.tgz#c65d110e9a4adf9a6c5948b28053d9a8d04cbeac" - integrity sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A== - dependencies: - json-stable-stringify-without-jsonify "^1.0.1" - through2-filter "^3.0.0" - universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" @@ -5581,7 +5384,7 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -util-deprecate@^1.0.1, util-deprecate@~1.0.1: +util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -5796,7 +5599,7 @@ ws@^5.2.0: dependencies: async-limiter "~1.0.0" -ws@^7.3.1, ws@^7.5.0: +ws@^7.5.0: version "7.5.3" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== @@ -5806,7 +5609,7 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== -xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1: +xtend@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== @@ -5826,6 +5629,11 @@ yaml@^1.10.0: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yargs-parser@20.x: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + yargs-parser@^13.1.2: version "13.1.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"