diff --git a/.DS_Store b/.DS_Store index 2103f64..03b32c5 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.npmignore b/.npmignore index e0302ab..3f6d623 100644 --- a/.npmignore +++ b/.npmignore @@ -15,6 +15,8 @@ tsconfig.json # vscode .vscode +.DS_Store + # nodemon nodemon.json diff --git a/package-lock.json b/package-lock.json index c21af2c..28be6e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,26 +1,27 @@ { "name": "homebridge-tapo", - "version": "1.5.1", + "version": "1.5.2-beta0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "homebridge-tapo", - "version": "1.5.1", + "version": "1.5.2-beta0", "license": "Apache-2.0", "dependencies": { - "axios": "^0.21.1", - "fakegato-history": "^0.6.2", + "axios": "^1.7.7", + "cron": "^3.1.7", + "fakegato-history": "^0.6.5", "lodash.defaults": "^4.2.0", - "node-cron": "^3.0.3", "utf8": "^3.0.0", - "uuid": "8.3.2" + "uuid": "10.0.0" }, "devDependencies": { "@eslint/js": "^9.9.0", "@types/eslint__js": "^8.42.3", "@types/lodash.defaults": "^4.2.9", "@types/node": "^22.2.0", + "@types/utf8": "^3.0.3", "@types/uuid": "^10.0.0", "eslint": "^9.9.0", "homebridge": "^2.0.0-beta.0", @@ -448,6 +449,12 @@ "@types/lodash": "*" } }, + "node_modules/@types/luxon": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", + "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.7.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", @@ -458,6 +465,13 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/utf8": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/utf8/-/utf8-3.0.3.tgz", + "integrity": "sha512-+lqLGxWZsEe4Z6OrzBI7Ym4SMUTaMS5yOrHZ0/IL0bpIye1Qbs4PpobJL2mLDbftUXlPFZR7fu6d1yM+bHLX1w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", @@ -877,6 +891,12 @@ "dev": true, "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -894,12 +914,14 @@ } }, "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/balanced-match": { @@ -1092,6 +1114,18 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -1116,6 +1150,16 @@ "dev": true, "license": "MIT" }, + "node_modules/cron": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/cron/-/cron-3.1.7.tgz", + "integrity": "sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw==", + "license": "MIT", + "dependencies": { + "@types/luxon": "~3.4.0", + "luxon": "~3.4.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1223,6 +1267,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -1752,6 +1805,20 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/from": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", @@ -2748,6 +2815,15 @@ "node": "20 || >=22" } }, + "node_modules/luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -2786,6 +2862,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2866,18 +2963,6 @@ "dev": true, "license": "MIT" }, - "node_modules/node-cron": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", - "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", - "license": "ISC", - "dependencies": { - "uuid": "8.3.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -3182,6 +3267,12 @@ "node": ">= 0.8.0" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -4096,9 +4187,13 @@ "license": "MIT" }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "license": "MIT", "bin": { "uuid": "dist/bin/uuid" diff --git a/package.json b/package.json index 0d2799d..9169a5e 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,7 @@ "displayName": "Tapo P100 Plugin", "name": "homebridge-tapo", "type": "module", - "private": true, - "version": "1.5.1", + "version": "1.5.2", "description": "Homebridge Plugin for TP-Link Tapo P100 Plugs", "license": "Apache-2.0", "homepage": "https://github.com/apatsufas/homebridge-tapo-p100#readme", @@ -37,18 +36,18 @@ "tp-link" ], "dependencies": { - "axios": "^0.21.1", - "fakegato-history": "^0.6.2", + "axios": "^1.7.7", + "fakegato-history": "^0.6.5", "lodash.defaults": "^4.2.0", - "node-cron": "^3.0.3", "utf8": "^3.0.0", - "uuid": "8.3.2" + "uuid": "10.0.0" }, "devDependencies": { "@eslint/js": "^9.9.0", "@types/eslint__js": "^8.42.3", "@types/lodash.defaults": "^4.2.9", "@types/node": "^22.2.0", + "@types/utf8": "^3.0.3", "@types/uuid": "^10.0.0", "eslint": "^9.9.0", "homebridge": "^2.0.0-beta.0", diff --git a/src/.DS_Store b/src/.DS_Store index 82413e3..4e4265c 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/custom-characteristics/index.ts b/src/custom-characteristics/index.ts index 306e6a4..27490ea 100644 --- a/src/custom-characteristics/index.ts +++ b/src/custom-characteristics/index.ts @@ -3,9 +3,9 @@ import type { WithUUID, } from 'homebridge'; -import DefaultCharacteristicImport from './default-characteristic'; -import CurrentConsumptionCharacteristicImport from './currentConsumptionCharacteristic'; -import TotalConsumptionCharacteristicImport from './totalConsumptionCharacteristic'; +import DefaultCharacteristicImport from './default-characteristic.js'; +import CurrentConsumptionCharacteristicImport from './currentConsumptionCharacteristic.js'; +import TotalConsumptionCharacteristicImport from './totalConsumptionCharacteristic.js'; export default function characteristic( Characteristic: typeof CharacteristicClass, diff --git a/src/index.ts b/src/index.ts index 83802c7..dbf67ce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import { API } from 'homebridge'; -import TapoPlatform from './platform'; -import { PLATFORM_NAME } from './settings'; + +import { TapoPlatform } from './platform.js'; +import { PLATFORM_NAME } from './settings.js'; /** * This method registers the platform with Homebridge diff --git a/src/platform.ts b/src/platform.ts index b3e3df1..3fe6f82 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -10,24 +10,24 @@ import { Categories, } from 'homebridge'; -import { PLATFORM_NAME, PLUGIN_NAME } from './settings'; -import { P100Accessory } from './platformP100Accessory'; -import { parseConfig, TapoConfig } from './config'; -import { L510EAccessory } from './platformL510EAccessory'; -import { L530Accessory } from './platformL530Accessory'; +import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js'; +import { P100Accessory } from './platformP100Accessory.js'; +import { parseConfig, TapoConfig } from './config.js'; +import { L510EAccessory } from './platformL510EAccessory.js'; +import { L530Accessory } from './platformL530Accessory.js'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore import fakegato from 'fakegato-history'; -import { P110Accessory } from './platformP110Accessory'; -import Characteristics from './custom-characteristics'; -import { L520EAccessory } from './platformL520EAccessory'; +import { P110Accessory } from './platformP110Accessory.js'; +import Characteristics from './custom-characteristics/index.js'; +import { L520EAccessory } from './platformL520EAccessory.js'; /** * TapoPlatform * This class is the main constructor for your plugin, this is where you should * parse the user config and discover/register accessories with Homebridge. */ -export default class TapoPlatform implements DynamicPlatformPlugin { +export class TapoPlatform implements DynamicPlatformPlugin { public readonly Service: typeof Service; public readonly Characteristic: typeof Characteristic; public readonly FakeGatoHistoryService; @@ -48,7 +48,7 @@ export default class TapoPlatform implements DynamicPlatformPlugin { ) { this.Service = api.hap.Service; this.Characteristic = api.hap.Characteristic; - + this.log.debug('config.json: %j', config); this.config = parseConfig(config); this.log.debug('config: %j', this.config); diff --git a/src/platformL510EAccessory.ts b/src/platformL510EAccessory.ts index b88ae89..f182c0a 100644 --- a/src/platformL510EAccessory.ts +++ b/src/platformL510EAccessory.ts @@ -1,7 +1,7 @@ import { PlatformAccessory, CharacteristicValue, CharacteristicSetCallback, CharacteristicGetCallback, Logger } from 'homebridge'; -import TapoPlatform from './platform'; -import L510E from './utils/l510e'; -import { TPLinkPlatformAccessory } from './platformTPLinkAccessory'; +import type { TapoPlatform } from './platform.js'; +import L510E from './utils/l510e.js'; +import { TPLinkPlatformAccessory } from './platformTPLinkAccessory.js'; /** * L510E Accessory diff --git a/src/platformL520EAccessory.ts b/src/platformL520EAccessory.ts index 22a914b..c2f2454 100644 --- a/src/platformL520EAccessory.ts +++ b/src/platformL520EAccessory.ts @@ -1,7 +1,7 @@ import { PlatformAccessory, CharacteristicValue, CharacteristicSetCallback, CharacteristicGetCallback, Logger } from 'homebridge'; -import TapoPlatform from './platform'; -import L520E from './utils/l520e'; -import { TPLinkPlatformAccessory } from './platformTPLinkAccessory'; +import type { TapoPlatform } from './platform.js'; +import L520E from './utils/l520e.js'; +import { TPLinkPlatformAccessory } from './platformTPLinkAccessory.js'; /** * L510E Accessory diff --git a/src/platformL530Accessory.ts b/src/platformL530Accessory.ts index a809862..1c0ab43 100644 --- a/src/platformL530Accessory.ts +++ b/src/platformL530Accessory.ts @@ -1,8 +1,8 @@ import { PlatformAccessory, CharacteristicValue, CharacteristicSetCallback, CharacteristicGetCallback, Logger, AdaptiveLightingController } from 'homebridge'; -import TapoPlatform from './platform'; -import L530 from './utils/l530'; -import { TPLinkPlatformAccessory } from './platformTPLinkAccessory'; +import type { TapoPlatform } from './platform.js'; +import L530 from './utils/l530.js'; +import { TPLinkPlatformAccessory } from './platformTPLinkAccessory.js'; /** * L530 Accessory @@ -12,7 +12,6 @@ import { TPLinkPlatformAccessory } from './platformTPLinkAccessory'; export class L530Accessory extends TPLinkPlatformAccessory { private adaptiveLightingController!: AdaptiveLightingController; private readonly fakeGatoHistoryService?; - private lastMeasurement: number | null = null; constructor( public readonly log: Logger, @@ -87,11 +86,26 @@ export class L530Accessory extends TPLinkPlatformAccessory { .on('set', this.setSaturation.bind(this)) // SET - bind to the `setSaturation` method below .on('get', this.getSaturation.bind(this)); // GET - bind to the `getSaturation` method below + this.service.addOptionalCharacteristic(this.platform.customCharacteristics.CurrentConsumptionCharacteristic); + this.service.getCharacteristic(this.platform.customCharacteristics.CurrentConsumptionCharacteristic) + .on('get', this.getCurrentConsumption.bind(this)); + + this.service.addOptionalCharacteristic(this.platform.customCharacteristics.TotalConsumptionCharacteristic); + this.service.getCharacteristic(this.platform.customCharacteristics.TotalConsumptionCharacteristic) + .on('get', this.getTotalConsumption.bind(this)); + + /* this.service.addOptionalCharacteristic(this.platform.customCharacteristics.ResetConsumptionCharacteristic); + this.service.getCharacteristic(this.platform.customCharacteristics.ResetConsumptionCharacteristic) + .on('set', this.resetConsumption.bind(this));*/ + // Setup the adaptive lighting controller if available if (this.platform.api.versionGreaterOrEqual && this.platform.api.versionGreaterOrEqual('1.3.0-beta.23')) { this.log.debug('Enabling Adaptvie Lightning'); this.adaptiveLightingController = new platform.api.hap.AdaptiveLightingController( - this.service, + this.service, { + controllerMode: + this.platform.api.hap.AdaptiveLightingControllerMode.AUTOMATIC, + }, ); this.accessory.configureController(this.adaptiveLightingController); } @@ -344,23 +358,19 @@ export class L530Accessory extends TPLinkPlatformAccessory { this.tpLinkAccessory.getEnergyUsage().then((response) => { this.log.debug('Get Characteristic Power consumption ->', JSON.stringify(response)); if (response && response.power_usage) { - if(this.lastMeasurement){ - this.log.debug('Get Characteristic Power consumption ->', JSON.stringify(response)); - if (this.fakeGatoHistoryService ) { - this.fakeGatoHistoryService.addEntry({ - time: new Date().getTime() / 1000, - power: response.power_usage.today > 0 ? response.power_usage.today - this.lastMeasurement > 0 ? - response.power_usage.today - this.lastMeasurement : 0 : 0, - }); - } + this.log.debug('Get Characteristic Power consumption ->', JSON.stringify(response)); + if (this.fakeGatoHistoryService ) { + this.fakeGatoHistoryService.addEntry({ + time: new Date().getTime() / 1000, + power: this.tpLinkAccessory.getPowerConsumption().current, + }); } - this.lastMeasurement = response.power_usage.today; } }); setTimeout(()=>{ this.updateConsumption(); - }, 300000); + }, 600000); } /** @@ -415,7 +425,7 @@ export class L530Accessory extends TPLinkPlatformAccessory { this.log.debug('updateState called'); this.tpLinkAccessory.getDeviceInfo(true).then((response) => { if(response){ - this.log.info('Device Info: ' + JSON.stringify(response)); + this.log.debug('Device Info: ' + JSON.stringify(response)); const isOn = response.device_on; const saturation = response.saturation; const hue = response.hue; diff --git a/src/platformP100Accessory.ts b/src/platformP100Accessory.ts index df18bf7..6055f65 100644 --- a/src/platformP100Accessory.ts +++ b/src/platformP100Accessory.ts @@ -1,7 +1,7 @@ import { PlatformAccessory, CharacteristicGetCallback, Logger } from 'homebridge'; -import TapoPlatform from './platform'; -import P100 from './utils/p100'; -import { TPLinkPlatformAccessory } from './platformTPLinkAccessory'; +import type { TapoPlatform } from './platform.js'; +import P100 from './utils/p100.js'; +import { TPLinkPlatformAccessory } from './platformTPLinkAccessory.js'; /** * P100 Accessory diff --git a/src/platformP110Accessory.ts b/src/platformP110Accessory.ts index 71f2f0d..12c28f3 100644 --- a/src/platformP110Accessory.ts +++ b/src/platformP110Accessory.ts @@ -1,8 +1,8 @@ import { PlatformAccessory, CharacteristicValue, CharacteristicGetCallback, Logger } from 'homebridge'; -import TapoPlatform from './platform'; -import P110 from './utils/p110'; -import { TPLinkPlatformAccessory } from './platformTPLinkAccessory'; +import type { TapoPlatform } from './platform.js'; +import P110 from './utils/p110.js'; +import { TPLinkPlatformAccessory } from './platformTPLinkAccessory.js'; /** * P110 Accessory @@ -56,6 +56,14 @@ export class P110Accessory extends TPLinkPlatformAccessory{ .on('set', this.setOn.bind(this)) // SET - bind to the `setOn` method below .on('get', this.getOn.bind(this)); // GET - bind to the `getOn` method below + this.service.addOptionalCharacteristic(this.platform.customCharacteristics.CurrentConsumptionCharacteristic); + this.service.getCharacteristic(this.platform.customCharacteristics.CurrentConsumptionCharacteristic) + .on('get', this.getCurrentConsumption.bind(this)); + + this.service.addOptionalCharacteristic(this.platform.customCharacteristics.TotalConsumptionCharacteristic); + this.service.getCharacteristic(this.platform.customCharacteristics.TotalConsumptionCharacteristic) + .on('get', this.getTotalConsumption.bind(this)); + this.updateConsumption(); const interval = updateInterval ? updateInterval*1000 : 10000; @@ -69,19 +77,21 @@ export class P110Accessory extends TPLinkPlatformAccessory{ } private updateConsumption(){ + this.log.debug('updateConsumption called'); this.tpLinkAccessory.getEnergyUsage().then((response) => { + this.log.debug('Get Characteristic Power consumption ->', JSON.stringify(response)); if (this.fakeGatoHistoryService && response && response.current_power) { this.platform.log.debug('Get Characteristic Power consumption ->', response.current_power); this.fakeGatoHistoryService.addEntry({ time: new Date().getTime() / 1000, - power: response.current_power, + power: this.tpLinkAccessory.getPowerConsumption().current, }); } }); setTimeout(()=>{ this.updateConsumption(); - }, 300000); + }, 600000); } /** diff --git a/src/platformTPLinkAccessory.ts b/src/platformTPLinkAccessory.ts index 6986928..c08740e 100644 --- a/src/platformTPLinkAccessory.ts +++ b/src/platformTPLinkAccessory.ts @@ -1,6 +1,6 @@ import { CharacteristicGetCallback, CharacteristicSetCallback, CharacteristicValue, Logger, PlatformAccessory, Service } from 'homebridge'; -import TapoPlatform from './platform'; -import { TpLinkAccessory } from './utils/tplinkAccessory'; +import type { TapoPlatform } from './platform.js'; +import { TpLinkAccessory } from './utils/tplinkAccessory.js'; export abstract class TPLinkPlatformAccessory { @@ -108,6 +108,10 @@ export abstract class TPLinkPlatformAccessory { this.platform.log.debug('On is undefined -> set no response'); this.setNoResponse(); } + + setTimeout(()=>{ + this.updateState(interval); + }, interval); } else{ this.setNoResponse(); interval += 300000; diff --git a/src/utils/l510e.ts b/src/utils/l510e.ts index 25117f0..41acb4f 100644 --- a/src/utils/l510e.ts +++ b/src/utils/l510e.ts @@ -1,6 +1,6 @@ import { Logger } from 'homebridge'; -import { LightSysinfo } from '../homekit-device/types'; -import P100 from './p100'; +import { LightSysinfo } from '../homekit-device/types.js'; +import P100 from './p100.js'; export default class L510E extends P100 { diff --git a/src/utils/l520e.ts b/src/utils/l520e.ts index 10bbfcf..2ea87c3 100644 --- a/src/utils/l520e.ts +++ b/src/utils/l520e.ts @@ -1,6 +1,6 @@ import { Logger } from 'homebridge'; -import { ColorTempLightSysinfo } from '../homekit-device/types'; -import L510E from './l510e'; +import { ColorTempLightSysinfo } from '../homekit-device/types.js'; +import L510E from './l510e.js'; export default class L520E extends L510E { diff --git a/src/utils/l530.ts b/src/utils/l530.ts index 16f41e8..ac056f9 100644 --- a/src/utils/l530.ts +++ b/src/utils/l530.ts @@ -1,7 +1,7 @@ import { Logger } from 'homebridge'; -import { ColorLightSysinfo, ConsumptionInfo } from '../homekit-device/types'; -import L520E from './l520e'; -import { PowerUsage } from './powerUsage'; +import { ColorLightSysinfo, ConsumptionInfo } from '../homekit-device/types.js'; +import L520E from './l520e.js'; +import { PowerUsage } from './powerUsage.js'; export default class L530 extends L520E { @@ -74,7 +74,7 @@ export default class L530 extends L520E { if(response && response.result){ this._consumption = { total: response.result.power_usage.today / 1000, - current: this._consumption ? response.result.power_usage.today - this._consumption.current : 0, + current: this._consumption ? response.result.power_usage.today / this.toHours(response.result.time_usage.today) : 0, }; } else{ this._consumption = { @@ -85,7 +85,7 @@ export default class L530 extends L520E { return response.result; }).catch((error)=>{ - if(error.message.indexOf('9999') > 0){ + if(error.message && error.message.indexOf('9999') > 0){ return this.reconnect().then(()=>{ return this.handleKlapRequest(payload).then(()=>{ return true; @@ -100,7 +100,7 @@ export default class L530 extends L520E { if(response && response.result){ this._consumption = { total: response.result.power_usage.today / 1000, - current: this._consumption ? response.result.power_usage.today - this._consumption.current : 0, + current: this._consumption ? response.result.power_usage.today / this.toHours(response.result.time_usage.today) : 0, }; } else{ this._consumption = { @@ -111,7 +111,7 @@ export default class L530 extends L520E { return response.result; }).catch((error)=>{ - if(error.message.indexOf('9999') > 0){ + if(error.message && error.message.indexOf('9999') > 0){ return this.reconnect().then(()=>{ return this.handleRequest(payload).then(()=>{ return true; @@ -125,6 +125,16 @@ export default class L530 extends L520E { } public getPowerConsumption():ConsumptionInfo{ + if(!this.getSysInfo().device_on){ + return { + total: this._consumption.total, + current: 0, + }; + } return this._consumption; } + + private toHours(minutes: number):number{ + return minutes/60; + } } \ No newline at end of file diff --git a/src/utils/newTpLinkCipher.ts b/src/utils/newTpLinkCipher.ts index 8c99dbb..171a886 100644 --- a/src/utils/newTpLinkCipher.ts +++ b/src/utils/newTpLinkCipher.ts @@ -1,12 +1,12 @@ import { Logger } from 'homebridge'; +import crypto from 'crypto'; export default class NewTpLinkCipher{ // eslint-disable-next-line @typescript-eslint/no-explicit-any public iv: any; // eslint-disable-next-line @typescript-eslint/no-explicit-any public key: any; - // eslint-disable-next-line @typescript-eslint/no-require-imports - private crypto = require('crypto'); + private _crypto = crypto; // eslint-disable-next-line @typescript-eslint/no-explicit-any public sig: any; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -27,13 +27,13 @@ export default class NewTpLinkCipher{ data = Buffer.from(data, 'utf8'); } - const cipher = this.crypto.createCipheriv('aes-128-cbc', this.key, this.ivSeqPair()); + const cipher = this._crypto.createCipheriv('aes-128-cbc', this.key, this.ivSeqPair()); const cipherText = Buffer.concat([cipher.update(data), cipher.final()]); const seqBuffer = Buffer.alloc(4); seqBuffer.writeInt32BE(this.seq, 0); - const hash = this.crypto.createHash('sha256'); + const hash = this._crypto.createHash('sha256'); hash.update(Buffer.concat([this.sig, seqBuffer, cipherText])); const signature = hash.digest(); @@ -45,7 +45,7 @@ export default class NewTpLinkCipher{ } public decrypt(data: Buffer) { - const decipher = this.crypto.createDecipheriv( + const decipher = this._crypto.createDecipheriv( 'aes-128-cbc', this.key, this.ivSeqPair(), @@ -76,20 +76,20 @@ export default class NewTpLinkCipher{ private calculateKey(local_seed: Buffer, remote_seed: Buffer, auth_hash: Buffer) { const buf = Buffer.concat([Buffer.from('lsk'), local_seed, remote_seed, auth_hash]); - const hash = this.crypto.createHash('sha256').update(buf).digest(); + const hash = this._crypto.createHash('sha256').update(buf).digest(); this.key = hash.subarray(0, 16); } private calculateIvSeq(local_seed: Buffer, remote_seed: Buffer, auth_hash: Buffer) { const buf = Buffer.concat([Buffer.from('iv'), local_seed, remote_seed, auth_hash]); - const ivBuf = this.crypto.createHash('sha256').update(buf).digest(); + const ivBuf = this._crypto.createHash('sha256').update(buf).digest(); this.seq = ivBuf.subarray(-4).readInt32BE(0); this.iv = ivBuf.subarray(0, 12); } private calculateSig(local_seed: Buffer, remote_seed: Buffer, auth_hash: Buffer) { const payload = Buffer.concat([Buffer.from('ldk'), local_seed, remote_seed, auth_hash]); - this.sig = this.crypto.createHash('sha256').update(payload).digest().subarray(0, 28); + this.sig = this._crypto.createHash('sha256').update(payload).digest().subarray(0, 28); } private ivSeqPair() { diff --git a/src/utils/p100.ts b/src/utils/p100.ts index d1b0370..b5c16a4 100644 --- a/src/utils/p100.ts +++ b/src/utils/p100.ts @@ -1,18 +1,21 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-require-imports */ import { Logger } from 'homebridge'; -import { PlugSysinfo } from '../homekit-device/types'; -import TpLinkCipher from './tpLinkCipher'; +import { PlugSysinfo } from '../homekit-device/types.js'; +import TpLinkCipher from './tpLinkCipher.js'; import { v4 as uuidv4 } from 'uuid'; import { AxiosResponse } from 'axios'; -import NewTpLinkCipher from './newTpLinkCipher'; -import { TpLinkAccessory } from './tplinkAccessory'; +import NewTpLinkCipher from './newTpLinkCipher.js'; +import { TpLinkAccessory } from './tplinkAccessory.js'; +import axios from 'axios'; +import crypto from 'crypto'; +import utf8 from 'utf8'; export default class P100 implements TpLinkAccessory{ - private crypto = require('crypto'); - protected axios = require('axios'); - private utf8 = require('utf8'); + private _crypto = crypto; + protected _axios = axios; + private _utf8 = utf8; public is_klap = true; private encodedPassword!: string; @@ -21,6 +24,7 @@ export default class P100 implements TpLinkAccessory{ private publicKey!: string; protected ip: string; protected cookie!: string; + protected tplink_timeout!: number; protected token!: string; protected terminalUUID: string; private _plugSysInfo!: PlugSysinfo; @@ -108,15 +112,15 @@ export default class P100 implements TpLinkAccessory{ } private sha_digest_username(data: string): string { - const digest = this.crypto.createHash('sha1').update(data).digest('hex'); + const digest = this._crypto.createHash('sha1').update(data).digest('hex'); return digest; } private calc_auth_hash(username: string, password: string): Buffer { - const usernameDigest = this.crypto.createHash('sha1').update(Buffer.from(username.normalize('NFKC'))).digest(); - const passwordDigest = this.crypto.createHash('sha1').update(Buffer.from(password.normalize('NFKC'))).digest(); - const digest = this.crypto.createHash('sha256').update((Buffer.concat([usernameDigest, passwordDigest]))).digest(); + const usernameDigest = this._crypto.createHash('sha1').update(Buffer.from(username.normalize('NFKC'))).digest(); + const passwordDigest = this._crypto.createHash('sha1').update(Buffer.from(password.normalize('NFKC'))).digest(); + const digest = this._crypto.createHash('sha256').update((Buffer.concat([usernameDigest, passwordDigest]))).digest(); return digest; } @@ -124,7 +128,7 @@ export default class P100 implements TpLinkAccessory{ // Including publicKey and privateKey from // generateKeyPairSync() method with its // parameters - const { publicKey, privateKey } = this.crypto.generateKeyPairSync('rsa', { + const { publicKey, privateKey } = this._crypto.generateKeyPairSync('rsa', { publicKeyEncoding: { type: 'spki', format: 'pem', @@ -137,6 +141,7 @@ export default class P100 implements TpLinkAccessory{ }); this.privateKey = privateKey; + //@ts-ignore this.publicKey = publicKey.toString('utf8'); } @@ -160,18 +165,20 @@ export default class P100 implements TpLinkAccessory{ headers: headers, }; - await this.axios.post(URL, payload, config) + await this._axios.post(URL, payload, config) .then((res: AxiosResponse) => { this.log.debug('Received Handshake P100 on host response: ' + this.ip); - if (res.data.error_code) { - return this.handleError(res.data.error_code, '97'); + if (res.data.error_code || res.status !== 200) { + return this.handleError(res.data!.error_code ? res.data.error_code : res.status, '172'); } try { const encryptedKey = res.data.result.key.toString('utf8'); this.decode_handshake_key(encryptedKey); - this.cookie = res.headers['set-cookie'][0].split(';')[0]; + if(res.headers['set-cookie']){ + this.cookie = res.headers['set-cookie'][0].split(';')[0]; + } return; } catch (error) { return this.handleError(res.data.error_code, '106'); @@ -214,10 +221,10 @@ export default class P100 implements TpLinkAccessory{ timeout: this._timeout * 1000, }; - await this.axios.post(URL, securePassthroughPayload, config) + await this._axios.post(URL, securePassthroughPayload, config) .then((res: AxiosResponse) => { - if (res.data.error_code) { - return this.handleError(res.data.error_code, '146'); + if (res.data.error_code || res.status !== 200) { + return this.handleError(res.data!.error_code ? res.data.error_code : res.status, '226'); } const decryptedResponse = this.tpLinkCipher.decrypt(res.data.result.response); try { @@ -249,7 +256,6 @@ export default class P100 implements TpLinkAccessory{ }; if (this.cookie) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore headers.Cookie = this.cookie; } @@ -260,16 +266,19 @@ export default class P100 implements TpLinkAccessory{ headers: headers, params: params, }; - return this.axios.post(URL, data, config) + //@ts-ignore + return this._axios.post(URL, data, config) .then((res: AxiosResponse) => { this.log.debug('Received request on host response: ' + this.ip); - if (res.data.error_code) { - return this.handleError(res.data.error_code, '309'); + if (res.data.error_code || res.status !== 200) { + return this.handleError(res.data!.error_code ? res.data.error_code : res.status, '273'); } try { if (res.headers && res.headers['set-cookie']) { + this.log.debug('Handshake 1 cookie: ' + JSON.stringify(res.headers['set-cookie'][0])); this.cookie = res.headers['set-cookie'][0].split(';')[0]; + this.tplink_timeout = Number(res.headers['set-cookie'][0].split(';')[1]); } return res.data; } catch (error) { @@ -278,6 +287,9 @@ export default class P100 implements TpLinkAccessory{ }) .catch((error: Error) => { this.log.error('276 Error: ' + error.message); + if(error.message.indexOf('403') > -1){ + this.reAuthenticate(); + } return error; }); } @@ -285,10 +297,10 @@ export default class P100 implements TpLinkAccessory{ private decode_handshake_key(key: string) { const buff = Buffer.from(key, 'base64'); - const decoded = this.crypto.privateDecrypt( + const decoded = this._crypto.privateDecrypt( { key: this.privateKey, - padding: this.crypto.constants.RSA_PKCS1_PADDING, + padding: this._crypto.constants.RSA_PKCS1_PADDING, } , buff); @@ -302,7 +314,7 @@ export default class P100 implements TpLinkAccessory{ async handshake_new(): Promise { this.log.debug('Trying new habdshake'); - const local_seed = this.crypto.randomBytes(16); + const local_seed = this._crypto.randomBytes(16); await this.raw_request('handshake1', local_seed, 'arraybuffer').then((res) => { const remote_seed: Buffer = res.subarray(0, 16); @@ -310,13 +322,13 @@ export default class P100 implements TpLinkAccessory{ let auth_hash: any = undefined; const ah = this.calc_auth_hash(this.email, this.password); - const local_seed_auth_hash = this.crypto.createHash('sha256').update(Buffer.concat([local_seed, remote_seed, ah])).digest(); + const local_seed_auth_hash = this._crypto.createHash('sha256').update(Buffer.concat([local_seed, remote_seed, ah])).digest(); if (local_seed_auth_hash.toString('hex') === server_hash.toString('hex')) { this.log.debug('Handshake 1 successful'); auth_hash = ah; } - const req = this.crypto.createHash('sha256').update(Buffer.concat([remote_seed, local_seed, auth_hash])).digest(); + const req = this._crypto.createHash('sha256').update(Buffer.concat([remote_seed, local_seed, auth_hash])).digest(); return this.raw_request('handshake2', req, 'text').then((res) => { this.log.debug('Handshake 2 successful: ' + res); @@ -392,12 +404,11 @@ export default class P100 implements TpLinkAccessory{ headers: headers, timeout: this._timeout * 1000, }; - - return this.axios.post(URL, securePassthroughPayload, config) + //@ts-ignore + return this._axios.post(URL, securePassthroughPayload, config) .then((res:any) => { if (res.data.error_code) { if ((res.data.error_code === '9999' || res.data.error_code === 9999) && this._reconnect_counter <= 3) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore this.log.error(' Error Code: ' + res.data.error_code + ', ' + this.ERROR_CODES[res.data.error_code]); this.log.debug('Trying to reconnect...'); @@ -439,7 +450,6 @@ export default class P100 implements TpLinkAccessory{ }; if (this.cookie) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore headers.Cookie = this.cookie; } @@ -450,7 +460,8 @@ export default class P100 implements TpLinkAccessory{ headers: headers, params: { seq: data.seq.toString() }, }; - return this.axios.post(URL, data.encryptedPayload, config) + //@ts-ignore + return this._axios.post(URL, data.encryptedPayload, config) .then((res: AxiosResponse) => { if (res.data.error_code) { return this.handleError(res.data.error_code, '309'); @@ -472,11 +483,16 @@ export default class P100 implements TpLinkAccessory{ return this.getSysInfo(); } catch (error) { this.log.debug(this.newTpLinkCipher.decrypt(res.data)); + this.log.debug('Status: ' + res.status); return this.handleError(res.data.error_code, '480'); } }) .catch((error: Error) => { + this.log.debug('469 Error: ' + JSON.stringify(error)); this.log.error('469 Error: ' + error.message); + if(error.message.indexOf('403') > -1){ + this.reAuthenticate(); + } return error; }); @@ -546,12 +562,13 @@ export default class P100 implements TpLinkAccessory{ } protected handleError(errorCode: number | string, line: string): boolean { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore const errorMessage = this.ERROR_CODES[errorCode]; - this.log.error(line + ' Error Code: ' + errorCode + ', ' + errorMessage + ' ' + this.ip); if (typeof errorCode === 'number' && errorCode === 1003) { + this.log.info('Trying KLAP Auth'); this.is_klap = true; + } else{ + this.log.error(line + ' Error Code: ' + errorCode + ', ' + errorMessage + ' ' + this.ip); } return false; } @@ -561,7 +578,7 @@ export default class P100 implements TpLinkAccessory{ return this.handleRequest(payload).then((result) => { return result ? true : false; }).catch((error) => { - if (error.message.indexOf('9999') > 0 && this._reconnect_counter <= 3) { + if (error.message && error.message.indexOf('9999') > 0 && this._reconnect_counter <= 3) { return this.reconnect().then(() => { return this.handleRequest(payload).then((result) => { return result ? true : false; @@ -575,7 +592,7 @@ export default class P100 implements TpLinkAccessory{ return this.handleKlapRequest(payload).then((result) => { return result ? true : false; }).catch((error) => { - if (error.message.indexOf('9999') > 0 && this._reconnect_counter <= 3) { + if (error.message && error.message.indexOf('9999') > 0 && this._reconnect_counter <= 3) { return this.newReconnect().then(() => { return this.handleKlapRequest(payload).then((result) => { return result ? true : false; @@ -611,11 +628,10 @@ export default class P100 implements TpLinkAccessory{ timeout: this._timeout * 1000, }; - return this.axios.post(URL, securePassthroughPayload, config) + return this._axios.post(URL, securePassthroughPayload, config) .then((res: AxiosResponse) => { if (res.data.error_code) { if (res.data.error_code === '9999' || res.data.error_code === 9999 && this._reconnect_counter <= 3) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore this.log.error(' Error Code: ' + res.data.error_code + ', ' + this.ERROR_CODES[res.data.error_code]); this.log.debug('Trying to reconnect...'); @@ -640,7 +656,7 @@ export default class P100 implements TpLinkAccessory{ } }) .catch((error: Error) => { - return this.handleError(error.message, '372'); + return this.handleError(error.message, '656'); }); } return new Promise((resolve, reject) => { @@ -655,7 +671,7 @@ export default class P100 implements TpLinkAccessory{ return this.raw_request('request', data.encryptedPayload, 'arraybuffer', { seq: data.seq.toString() }).then((res) => { return JSON.parse(this.newTpLinkCipher.decrypt(res)); }).catch((error: Error) => { - return this.handleError(error.message, '372'); + return this.handleError(error.message, '671'); }); } return new Promise((resolve, reject) => { @@ -679,4 +695,23 @@ export default class P100 implements TpLinkAccessory{ return; }); } + + private reAuthenticate():void{ + if(this.is_klap){ + this.handshake_new().then(() => { + this.log.info('KLAP Authenticated successfully'); + }).catch(() => { + this.log.error('KLAP Handshake failed'); + this.is_klap = false; + }); + } else { + this.handshake().then(() => { + this.login().then(() => { + this.log.info('Authenticated successfully'); + }).catch(() => { + this.log.error('Login failed'); + }); + }); + } + } } \ No newline at end of file diff --git a/src/utils/p110.ts b/src/utils/p110.ts index e280bfc..c577e39 100644 --- a/src/utils/p110.ts +++ b/src/utils/p110.ts @@ -1,7 +1,7 @@ import { Logger } from 'homebridge'; -import { EnergyUsage } from './energyUsage'; -import P100 from './p100'; -import { ConsumptionInfo } from '../homekit-device/types'; +import { EnergyUsage } from './energyUsage.js'; +import P100 from './p100.js'; +import { ConsumptionInfo } from '../homekit-device/types.js'; export default class P110 extends P100 { @@ -28,7 +28,7 @@ export default class P110 extends P100 { return this.handleKlapRequest(payload).then((response)=>{ if(response && response.result){ this._consumption = { - current: response.result.current_power / 1000, + current: Math.ceil(response.result.current_power / 1000), total: response.result.today_energy / 1000, }; } else{ @@ -44,7 +44,7 @@ export default class P110 extends P100 { return this.handleRequest(payload).then((response)=>{ if(response && response.result){ this._consumption = { - current: response.result.current_power / 1000, + current: Math.ceil(response.result.current_power / 1000), total: response.result.today_energy / 1000, }; } else{ diff --git a/src/utils/powerUsage.ts b/src/utils/powerUsage.ts index ebfa4f8..2c2bc67 100644 --- a/src/utils/powerUsage.ts +++ b/src/utils/powerUsage.ts @@ -1,4 +1,4 @@ -import { Usage } from './usage'; +import { Usage } from './usage.js'; export class PowerUsage{ time_usage!:Usage; diff --git a/src/utils/tpLinkCipher.ts b/src/utils/tpLinkCipher.ts index d88fe8c..992178b 100644 --- a/src/utils/tpLinkCipher.ts +++ b/src/utils/tpLinkCipher.ts @@ -1,11 +1,11 @@ -/* eslint-disable @typescript-eslint/no-require-imports */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Logger } from 'homebridge'; +import crypto from 'crypto'; export default class TpLinkCipher{ public iv: any; public key: any; - private crypto = require('crypto'); + private _crypto = crypto; constructor(public readonly log: Logger, b_arr: any, b_arr2: any){ this.iv = b_arr2; @@ -18,14 +18,14 @@ export default class TpLinkCipher{ } public encrypt(data:string){ - const cipher = this.crypto.createCipheriv('aes-128-cbc', this.key, this.iv); + const cipher = this._crypto.createCipheriv('aes-128-cbc', this.key, this.iv); let encrypted = cipher.update(data, 'utf8', 'base64'); encrypted += cipher.final('base64'); return encrypted; } public decrypt(data: string){ - const decipher = this.crypto.createDecipheriv('aes-128-cbc', this.key, this.iv); + const decipher = this._crypto.createDecipheriv('aes-128-cbc', this.key, this.iv); let decrypted = decipher.update(data, 'base64', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; diff --git a/src/utils/tplinkAccessory.ts b/src/utils/tplinkAccessory.ts index f48895c..d399aa1 100644 --- a/src/utils/tplinkAccessory.ts +++ b/src/utils/tplinkAccessory.ts @@ -1,6 +1,6 @@ -import { ConsumptionInfo, PlugSysinfo } from '../homekit-device/types'; -import { EnergyUsage } from './energyUsage'; -import { PowerUsage } from './powerUsage'; +import { ConsumptionInfo, PlugSysinfo } from '../homekit-device/types.js'; +import { EnergyUsage } from './energyUsage.js'; +import { PowerUsage } from './powerUsage.js'; export interface TpLinkAccessory{ is_klap:boolean;