@petro-kushchak/homebridge-tuya-eve 1.0.11 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +38 -0
- package/.prettierrc +5 -2
- package/dist/index.js +2 -224
- package/dist/index.js.map +1 -1
- package/dist/lib/eveHistoryService.js +5 -3
- package/dist/lib/eveHistoryService.js.map +1 -1
- package/dist/tuyaSwitchAccessory.js +266 -0
- package/dist/tuyaSwitchAccessory.js.map +1 -0
- package/example.config.json +2 -1
- package/nodemon.json +12 -0
- package/package.json +7 -7
- package/src/index.ts +9 -0
- package/src/lib/EnergyCharacteristics.js +103 -0
- package/src/lib/eveHistoryService.ts +94 -0
- package/src/lib/homebridgeCallbacks.ts +21 -0
- package/src/tuyaSwitchAccessory.ts +336 -0
- package/tsconfig.json +23 -0
package/.eslintrc
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
{
|
2
|
+
"parser": "@typescript-eslint/parser",
|
3
|
+
"extends": [
|
4
|
+
"eslint:recommended",
|
5
|
+
"plugin:@typescript-eslint/eslint-recommended",
|
6
|
+
"plugin:@typescript-eslint/recommended" // uses the recommended rules from the @typescript-eslint/eslint-plugin
|
7
|
+
],
|
8
|
+
"parserOptions": {
|
9
|
+
"ecmaVersion": 2018,
|
10
|
+
"sourceType": "module"
|
11
|
+
},
|
12
|
+
"ignorePatterns": [
|
13
|
+
"dist"
|
14
|
+
],
|
15
|
+
"rules": {
|
16
|
+
"quotes": ["warn", "single"],
|
17
|
+
"indent": ["warn", 4, { "SwitchCase": 1 }],
|
18
|
+
"semi": ["off"],
|
19
|
+
"comma-dangle": ["warn", "always-multiline"],
|
20
|
+
"dot-notation": "off",
|
21
|
+
"eqeqeq": "warn",
|
22
|
+
"curly": ["warn", "all"],
|
23
|
+
"brace-style": ["warn"],
|
24
|
+
"prefer-arrow-callback": ["warn"],
|
25
|
+
"max-len": ["warn", 140],
|
26
|
+
"no-console": ["warn"], // use the provided Homebridge log method instead
|
27
|
+
"no-non-null-assertion": ["off"],
|
28
|
+
"comma-spacing": ["error"],
|
29
|
+
"no-multi-spaces": ["warn", { "ignoreEOLComments": true }],
|
30
|
+
"no-trailing-spaces": ["warn"],
|
31
|
+
"lines-between-class-members": ["warn", "always", {"exceptAfterSingleLine": true}],
|
32
|
+
"@typescript-eslint/explicit-function-return-type": "off",
|
33
|
+
"@typescript-eslint/no-non-null-assertion": "off",
|
34
|
+
"@typescript-eslint/explicit-module-boundary-types": "off",
|
35
|
+
"@typescript-eslint/semi": ["warn"],
|
36
|
+
"@typescript-eslint/member-delimiter-style": ["warn"]
|
37
|
+
}
|
38
|
+
}
|
package/.prettierrc
CHANGED
package/dist/index.js
CHANGED
@@ -1,228 +1,6 @@
|
|
1
1
|
"use strict";
|
2
|
-
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
-
};
|
5
|
-
const eveHistoryService_1 = require("./lib/eveHistoryService");
|
6
|
-
const tuyapi_1 = __importDefault(require("tuyapi"));
|
7
|
-
const EnergyCharacteristics_1 = __importDefault(require("./lib/EnergyCharacteristics"));
|
8
|
-
const homebridgeCallbacks_1 = require("./lib/homebridgeCallbacks");
|
9
|
-
let hap;
|
10
|
-
const ONOFF_DP = '1';
|
11
|
-
const AMP_DP = '18';
|
12
|
-
const WATT_DP = '19';
|
13
|
-
const VOLT_DP = '20';
|
14
|
-
const kHour = 60 * 60 * 1000;
|
15
|
-
/**
|
16
|
-
* Platform Accessory
|
17
|
-
* An instance of this class is created for each accessory your platform registers
|
18
|
-
* Each accessory may expose multiple services of different service types.
|
19
|
-
*/
|
20
|
-
class TuyaSwitchAccessoryAccessory {
|
21
|
-
constructor(logger, config, api) {
|
22
|
-
this.logger = logger;
|
23
|
-
this.config = config;
|
24
|
-
this.api = api;
|
25
|
-
this.amperes = 0;
|
26
|
-
this.watts = 0;
|
27
|
-
this.volts = 0;
|
28
|
-
this.inUse = false;
|
29
|
-
this.totalConsumption = 0;
|
30
|
-
this.resetTotal = 0;
|
31
|
-
hap = api.hap;
|
32
|
-
this.log = logger;
|
33
|
-
this.name = config.name;
|
34
|
-
this.displayName = this.name;
|
35
|
-
this.serial = config.serial;
|
36
|
-
this.id = config.id;
|
37
|
-
this.key = config.key;
|
38
|
-
this.model = config.model || 'TuyaSwitch';
|
39
|
-
this.updateInterval = config.updateInterval || 5000;
|
40
|
-
this.EnergyCharacteristics = EnergyCharacteristics_1.default.create(hap.Characteristic);
|
41
|
-
this.device = new tuyapi_1.default({
|
42
|
-
id: this.id,
|
43
|
-
key: this.key,
|
44
|
-
issueRefreshOnConnect: true,
|
45
|
-
});
|
46
|
-
// Set AccessoryInformation
|
47
|
-
this.informationService = new hap.Service.AccessoryInformation()
|
48
|
-
.setCharacteristic(hap.Characteristic.Name, this.name)
|
49
|
-
.setCharacteristic(hap.Characteristic.Manufacturer, 'Tuya')
|
50
|
-
.setCharacteristic(hap.Characteristic.Model, this.model)
|
51
|
-
.setCharacteristic(hap.Characteristic.SerialNumber, this.serial);
|
52
|
-
// create a new Thermostat service
|
53
|
-
this.service = new hap.Service.Switch(this.name);
|
54
|
-
this.service
|
55
|
-
.getCharacteristic(hap.Characteristic.On)
|
56
|
-
.on("get" /* CharacteristicEventTypes.GET */, (0, homebridgeCallbacks_1.callbackify)(this.getPowerOnOff.bind(this)))
|
57
|
-
.on("set" /* CharacteristicEventTypes.SET */, (0, homebridgeCallbacks_1.callbackify)(this.setPowerOnOff.bind(this)));
|
58
|
-
this.service.addOptionalCharacteristic(this.EnergyCharacteristics.Volts);
|
59
|
-
this.service.addOptionalCharacteristic(this.EnergyCharacteristics.Amperes);
|
60
|
-
this.service.addOptionalCharacteristic(this.EnergyCharacteristics.Watts);
|
61
|
-
this.service.addOptionalCharacteristic(this.EnergyCharacteristics.TotalConsumption);
|
62
|
-
this.service.addOptionalCharacteristic(this.EnergyCharacteristics.ResetTotal);
|
63
|
-
this.service
|
64
|
-
.getCharacteristic(this.EnergyCharacteristics.Volts)
|
65
|
-
.on('get', this.getVoltage.bind(this))
|
66
|
-
.updateValue(this.volts);
|
67
|
-
this.service
|
68
|
-
.getCharacteristic(this.EnergyCharacteristics.Amperes)
|
69
|
-
.on('get', this.getCurrent.bind(this))
|
70
|
-
.updateValue(this.amperes);
|
71
|
-
this.service
|
72
|
-
.getCharacteristic(this.EnergyCharacteristics.Watts)
|
73
|
-
.on('get', this.getConsumption.bind(this))
|
74
|
-
.updateValue(this.watts);
|
75
|
-
this.service
|
76
|
-
.getCharacteristic(this.EnergyCharacteristics.TotalConsumption)
|
77
|
-
.on('get', this.getTotalConsumption.bind(this));
|
78
|
-
// create handlers for required characteristics
|
79
|
-
this.historyService = new eveHistoryService_1.EveHistoryService(this, this.api, 'energy',
|
80
|
-
// this.historyFilename,
|
81
|
-
this.logger);
|
82
|
-
this.readTotalConsumption();
|
83
|
-
//device events
|
84
|
-
setTimeout(async () => {
|
85
|
-
this.device.on('connected', () => {
|
86
|
-
this.log('Connected to device!');
|
87
|
-
const lastTime = new Date().getTime();
|
88
|
-
setInterval(async () => {
|
89
|
-
const now = new Date().getTime();
|
90
|
-
const delta = (now - lastTime) / 1000;
|
91
|
-
const state = await this.device.get({ schema: true });
|
92
|
-
this.log(`Device state: ${JSON.stringify(state)}`);
|
93
|
-
this.updateState(state.dps);
|
94
|
-
const consumption = this.watts * delta; // W/s
|
95
|
-
this.totalConsumption += consumption / kHour;
|
96
|
-
this.historyService.addEntry({ time: now / 1000, power: this.watts });
|
97
|
-
const extra = this.historyService.getExtraPersistedData();
|
98
|
-
if (!extra) {
|
99
|
-
this.historyService.setExtraPersistedData({
|
100
|
-
totalConsumption: this.totalConsumption,
|
101
|
-
resetTotal: this.resetTotal,
|
102
|
-
});
|
103
|
-
}
|
104
|
-
else if (extra.totalConsumption !== this.totalConsumption ||
|
105
|
-
extra.resetTotal !== this.resetTotal) {
|
106
|
-
extra.totalConsumption = this.totalConsumption;
|
107
|
-
extra.resetTotal = this.resetTotal;
|
108
|
-
this.historyService.setExtraPersistedData(extra);
|
109
|
-
}
|
110
|
-
}, this.updateInterval);
|
111
|
-
});
|
112
|
-
this.device.on('disconnected', () => {
|
113
|
-
this.log('Disconnected to device!');
|
114
|
-
});
|
115
|
-
this.device.on('error', (error) => {
|
116
|
-
this.log(`Error ${error}!`);
|
117
|
-
});
|
118
|
-
this.device.on('data', (data) => {
|
119
|
-
this.log(`DATA ${JSON.stringify(data)}!`);
|
120
|
-
this.updateState(data.dps);
|
121
|
-
});
|
122
|
-
this.device.on('dp-refresh', (data) => {
|
123
|
-
this.log(`REFRESH ${JSON.stringify(data)}!`);
|
124
|
-
this.updateState(data.dps);
|
125
|
-
});
|
126
|
-
//device find&connect
|
127
|
-
await this.device.find();
|
128
|
-
await this.device.connect();
|
129
|
-
}, 1000);
|
130
|
-
}
|
131
|
-
getTotalConsumption(callback) {
|
132
|
-
callback(null, this.totalConsumption);
|
133
|
-
}
|
134
|
-
getResetTotal(callback) {
|
135
|
-
callback(null, this.resetTotal);
|
136
|
-
}
|
137
|
-
setResetTotal(value, callback) {
|
138
|
-
this.log.info(`setResetTotal: ${value}`);
|
139
|
-
this.resetTotal = value;
|
140
|
-
this.totalConsumption = 0;
|
141
|
-
callback(null, this.resetTotal);
|
142
|
-
}
|
143
|
-
getVoltage(callback) {
|
144
|
-
// this.updateState(this.device.state);
|
145
|
-
callback(null, this.volts);
|
146
|
-
}
|
147
|
-
getCurrent(callback) {
|
148
|
-
// this.updateState(this.device.state);
|
149
|
-
callback(null, this.amperes);
|
150
|
-
}
|
151
|
-
getConsumption(callback) {
|
152
|
-
// this.updateState(this.device.state);
|
153
|
-
callback(null, this.watts);
|
154
|
-
}
|
155
|
-
updateState(dps) {
|
156
|
-
this.amperes = dps[AMP_DP] ? dps[AMP_DP] / 1000 : 0;
|
157
|
-
this.watts = dps[WATT_DP] ? dps[WATT_DP] / 10 : 0;
|
158
|
-
this.volts = dps[VOLT_DP] ? dps[VOLT_DP] / 10 : 0;
|
159
|
-
this.inUse = dps[ONOFF_DP] ? dps[ONOFF_DP] : false;
|
160
|
-
this.log.info(`updated a:${this.amperes} w:${this.watts} v:${this.volts} on: ${this.inUse}`);
|
161
|
-
}
|
162
|
-
async setPowerOnOff(value) {
|
163
|
-
this.log.info(`SET ON: ${value}`);
|
164
|
-
await this.device.set({ set: value, dps: '1' });
|
165
|
-
}
|
166
|
-
async getPowerOnOff() {
|
167
|
-
const status = await this.device.get();
|
168
|
-
this.log.info(`GET ON: ${status.dps[1]}`);
|
169
|
-
return status.dps[1];
|
170
|
-
}
|
171
|
-
readTotalConsumption() {
|
172
|
-
this.historyService.readHistory((lastEntry, history, extra) => {
|
173
|
-
const lastItem = history.pop();
|
174
|
-
if (lastItem) {
|
175
|
-
this.log.info('History: last item: %s', lastItem);
|
176
|
-
}
|
177
|
-
else {
|
178
|
-
this.log.info('History: no data');
|
179
|
-
}
|
180
|
-
this.log.info('History: extra: %s', extra);
|
181
|
-
const totalConsumption = extra.totalConsumption
|
182
|
-
? extra.totalConsumption
|
183
|
-
: 0.0;
|
184
|
-
const resetTotal = extra.resetTotal
|
185
|
-
? extra.resetTotal
|
186
|
-
: 0; // Math.floor(Date.now() / 1000) - 978307200 // seconds since 01.01.2001
|
187
|
-
this.log.info(`totalConsumption: ${totalConsumption} resetTotal: ${resetTotal}`);
|
188
|
-
this.totalConsumption = totalConsumption;
|
189
|
-
this.resetTotal = resetTotal;
|
190
|
-
});
|
191
|
-
// try {
|
192
|
-
// const filepath = this.api ? this.api.user.storagePath() : './config';
|
193
|
-
// const filename = path.join(filepath, this.historyFilename);
|
194
|
-
// this.log.info(`Reading history: ${filename}`);
|
195
|
-
// const data = fs.readFileSync(filename, 'utf8');
|
196
|
-
// const jsonData = typeof data === 'object' ? data : JSON.parse(data);
|
197
|
-
// const totalConsumption =
|
198
|
-
// jsonData.extra && jsonData.extra.totalConsumption
|
199
|
-
// ? jsonData.extra.totalConsumption
|
200
|
-
// : 0.0;
|
201
|
-
// const resetTotal =
|
202
|
-
// jsonData.extra && jsonData.extra.resetTotal
|
203
|
-
// ? jsonData.extra.resetTotal
|
204
|
-
// : 0; // Math.floor(Date.now() / 1000) - 978307200 // seconds since 01.01.2001
|
205
|
-
// this.log.info(
|
206
|
-
// `totalConsumption: ${totalConsumption} resetTotal: ${resetTotal}`,
|
207
|
-
// );
|
208
|
-
// this.totalConsumption = totalConsumption;
|
209
|
-
// this.resetTotal = resetTotal;
|
210
|
-
// } catch (err) {
|
211
|
-
// this.log.error(`readTotalConsumption error: ${err}`);
|
212
|
-
// this.totalConsumption = 0;
|
213
|
-
// this.resetTotal = 0;
|
214
|
-
// }
|
215
|
-
}
|
216
|
-
getServices() {
|
217
|
-
return [
|
218
|
-
this.informationService,
|
219
|
-
this.service,
|
220
|
-
this.historyService.getService(),
|
221
|
-
];
|
222
|
-
}
|
223
|
-
}
|
2
|
+
const tuyaSwitchAccessory_1 = require("./tuyaSwitchAccessory");
|
224
3
|
module.exports = (api) => {
|
225
|
-
|
226
|
-
api.registerAccessory('homebridge-tuya-eve', 'TuyaSwitchEve', TuyaSwitchAccessoryAccessory);
|
4
|
+
api.registerAccessory('homebridge-tuya-eve', 'TuyaSwitchEve', tuyaSwitchAccessory_1.TuyaSwitchAccessory);
|
227
5
|
};
|
228
6
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,+DAA4D;AAK5D,iBAAS,CAAC,GAAQ,EAAE,EAAE;IAClB,GAAG,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,eAAe,EAAE,yCAAmB,CAAC,CAAC;AACvF,CAAC,CAAC"}
|
@@ -12,7 +12,10 @@ class EveHistoryService {
|
|
12
12
|
this.serviceType = serviceType;
|
13
13
|
this.logger = logger;
|
14
14
|
const FakeGatoHistoryService = (0, fakegato_history_1.default)(api);
|
15
|
-
this.historyService = new FakeGatoHistoryService(this.serviceType, this.accessory, {
|
15
|
+
this.historyService = new FakeGatoHistoryService(this.serviceType, this.accessory, {
|
16
|
+
storage: 'fs',
|
17
|
+
log: this.logger,
|
18
|
+
});
|
16
19
|
}
|
17
20
|
getService() {
|
18
21
|
return this.historyService;
|
@@ -27,8 +30,7 @@ class EveHistoryService {
|
|
27
30
|
this.historyService.addEntry(entry);
|
28
31
|
}
|
29
32
|
readHistory(lastEntryHandler) {
|
30
|
-
const storage = this.api
|
31
|
-
.globalFakeGatoStorage;
|
33
|
+
const storage = this.api.globalFakeGatoStorage;
|
32
34
|
if (!storage) {
|
33
35
|
this.logger.debug('Failed to access globalFakeGatoStorage');
|
34
36
|
return;
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"eveHistoryService.js","sourceRoot":"","sources":["../../src/lib/eveHistoryService.ts"],"names":[],"mappings":";;;;;;AAAA,wEAAwC;AAwBxC,MAAa,iBAAiB;
|
1
|
+
{"version":3,"file":"eveHistoryService.js","sourceRoot":"","sources":["../../src/lib/eveHistoryService.ts"],"names":[],"mappings":";;;;;;AAAA,wEAAwC;AAwBxC,MAAa,iBAAiB;IAG1B,YACY,SAA0B,EAC1B,GAAQ,EACR,WAAmB,EACnB,MAAe;QAHf,cAAS,GAAT,SAAS,CAAiB;QAC1B,QAAG,GAAH,GAAG,CAAK;QACR,gBAAW,GAAX,WAAW,CAAQ;QACnB,WAAM,GAAN,MAAM,CAAS;QAEvB,MAAM,sBAAsB,GAAG,IAAA,0BAAQ,EAAC,GAAG,CAAC,CAAC;QAC7C,IAAI,CAAC,cAAc,GAAG,IAAI,sBAAsB,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,EAAE;YAC/E,OAAO,EAAE,IAAI;YACb,GAAG,EAAE,IAAI,CAAC,MAAM;SACnB,CAAC,CAAC;IACP,CAAC;IAED,UAAU;QACN,OAAO,IAAI,CAAC,cAAyB,CAAC;IAC1C,CAAC;IAED,qBAAqB,CAAC,KAAK;QACtB,IAAI,CAAC,cAAiC,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC;IACzE,CAAC;IAED,qBAAqB;QACjB,OAAQ,IAAI,CAAC,cAAiC,CAAC,qBAAqB,EAAE,CAAC;IAC3E,CAAC;IAED,QAAQ,CAAC,KAA0B;QAC9B,IAAI,CAAC,cAAiC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC5D,CAAC;IAED,WAAW,CACP,gBAAyG;QAEzG,MAAM,OAAO,GAAK,IAAI,CAAC,GAAyC,CAAC,qBAAqB,CAAC;QAEvF,IAAI,CAAC,OAAO,EAAE;YACV,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;YAC5D,OAAO;SACV;QAED,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;QACjE,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC;YACT,OAAO,EAAE,IAAI,CAAC,cAAc;YAC5B,QAAQ,EAAE,UAAU,GAAG,EAAE,IAAI;gBACzB,IAAI,CAAC,GAAG,EAAE;oBACN,IAAI,IAAI,EAAE;wBACN,IAAI;4BACA,MAAM,aAAa,GAAG,MAAM,IAAI,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;4BACtF,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;4BACnD,MAAM,QAAQ,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;4BACpE,gBAAgB,CACZ,QAAQ,CAAC,SAAS,EAClB,QAAQ,CAAC,OAAgC,EACzC,QAAQ,CAAC,KAA4B,CACxC,CAAC;yBACL;wBAAC,OAAO,CAAC,EAAE;4BACR,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,qEAAqE,EAAE,CAAC,CAAC,CAAC;yBAC/F;qBACJ;iBACJ;qBAAM;oBACH,oBAAoB;oBACpB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,oDAAoD,EAAE,GAAG,CAAC,CAAC;iBAChF;YACL,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;SACf,CAAC,CAAC;IACP,CAAC;CACJ;AArED,8CAqEC"}
|
@@ -0,0 +1,266 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.TuyaSwitchAccessory = void 0;
|
7
|
+
const eveHistoryService_1 = require("./lib/eveHistoryService");
|
8
|
+
const tuyapi_1 = __importDefault(require("tuyapi"));
|
9
|
+
const EnergyCharacteristics_1 = __importDefault(require("./lib/EnergyCharacteristics"));
|
10
|
+
const homebridgeCallbacks_1 = require("./lib/homebridgeCallbacks");
|
11
|
+
const ONOFF_DP = '1';
|
12
|
+
const AMP_DP = '18';
|
13
|
+
const WATT_DP = '19';
|
14
|
+
const VOLT_DP = '20';
|
15
|
+
const kHour = 60 * 60 * 1000;
|
16
|
+
/**
|
17
|
+
* Platform Accessory
|
18
|
+
* An instance of this class is created for each accessory your platform registers
|
19
|
+
* Each accessory may expose multiple services of different service types.
|
20
|
+
*/
|
21
|
+
class TuyaSwitchAccessory {
|
22
|
+
constructor(logger, config, api) {
|
23
|
+
this.logger = logger;
|
24
|
+
this.config = config;
|
25
|
+
this.api = api;
|
26
|
+
this.amperes = 0;
|
27
|
+
this.watts = 0;
|
28
|
+
this.volts = 0;
|
29
|
+
this.inUse = false;
|
30
|
+
this.totalConsumption = 0;
|
31
|
+
this.resetTotal = 0;
|
32
|
+
this.log = logger;
|
33
|
+
this.name = config.name;
|
34
|
+
this.displayName = this.name;
|
35
|
+
this.serial = config.serial;
|
36
|
+
this.id = config.id;
|
37
|
+
this.key = config.key;
|
38
|
+
this.model = config.model || 'TuyaSwitch';
|
39
|
+
this.updateInterval = config.updateInterval || 5000;
|
40
|
+
this.type = config.type || 'switch';
|
41
|
+
this.device = new tuyapi_1.default({
|
42
|
+
id: this.id,
|
43
|
+
key: this.key,
|
44
|
+
issueRefreshOnConnect: true,
|
45
|
+
});
|
46
|
+
// Set AccessoryInformation
|
47
|
+
this.informationService = new api.hap.Service.AccessoryInformation()
|
48
|
+
.setCharacteristic(api.hap.Characteristic.Name, this.name)
|
49
|
+
.setCharacteristic(api.hap.Characteristic.Manufacturer, 'Tuya')
|
50
|
+
.setCharacteristic(api.hap.Characteristic.Model, this.model)
|
51
|
+
.setCharacteristic(api.hap.Characteristic.SerialNumber, this.serial);
|
52
|
+
this.EnergyCharacteristics = EnergyCharacteristics_1.default.create(api.hap.Characteristic);
|
53
|
+
switch (this.type) {
|
54
|
+
case 'switch':
|
55
|
+
this.service = new api.hap.Service.Switch(this.name);
|
56
|
+
break;
|
57
|
+
case 'valve':
|
58
|
+
this.service = new api.hap.Service.Valve(this.name);
|
59
|
+
break;
|
60
|
+
}
|
61
|
+
this.setupAccessoryServices();
|
62
|
+
this.service
|
63
|
+
.getCharacteristic(api.hap.Characteristic.On)
|
64
|
+
.on("get" /* CharacteristicEventTypes.GET */, (0, homebridgeCallbacks_1.callbackify)(this.getPowerOnOff.bind(this)))
|
65
|
+
.on("set" /* CharacteristicEventTypes.SET */, (0, homebridgeCallbacks_1.callbackify)(this.setPowerOnOff.bind(this)));
|
66
|
+
this.historyService = new eveHistoryService_1.EveHistoryService(this, this.api, 'energy',
|
67
|
+
// this.historyFilename,
|
68
|
+
this.logger);
|
69
|
+
//device events
|
70
|
+
this.setupTimeout = setInterval(async () => {
|
71
|
+
this.setupDevice();
|
72
|
+
}, 1000);
|
73
|
+
}
|
74
|
+
energyMonitoringEnabled() {
|
75
|
+
return this.type === 'switch';
|
76
|
+
}
|
77
|
+
setupAccessoryServices() {
|
78
|
+
if (!this.energyMonitoringEnabled()) {
|
79
|
+
return;
|
80
|
+
}
|
81
|
+
this.service.addOptionalCharacteristic(this.EnergyCharacteristics.Volts);
|
82
|
+
this.service.addOptionalCharacteristic(this.EnergyCharacteristics.Amperes);
|
83
|
+
this.service.addOptionalCharacteristic(this.EnergyCharacteristics.Watts);
|
84
|
+
this.service.addOptionalCharacteristic(this.EnergyCharacteristics.TotalConsumption);
|
85
|
+
this.service.addOptionalCharacteristic(this.EnergyCharacteristics.ResetTotal);
|
86
|
+
this.service
|
87
|
+
.getCharacteristic(this.EnergyCharacteristics.Volts)
|
88
|
+
.on('get', this.getVoltage.bind(this))
|
89
|
+
.updateValue(this.volts);
|
90
|
+
this.service
|
91
|
+
.getCharacteristic(this.EnergyCharacteristics.Amperes)
|
92
|
+
.on('get', this.getCurrent.bind(this))
|
93
|
+
.updateValue(this.amperes);
|
94
|
+
this.service
|
95
|
+
.getCharacteristic(this.EnergyCharacteristics.Watts)
|
96
|
+
.on('get', this.getConsumption.bind(this))
|
97
|
+
.updateValue(this.watts);
|
98
|
+
this.service
|
99
|
+
.getCharacteristic(this.EnergyCharacteristics.TotalConsumption)
|
100
|
+
.on('get', this.getTotalConsumption.bind(this));
|
101
|
+
// create handlers for required characteristics
|
102
|
+
this.readTotalConsumption();
|
103
|
+
}
|
104
|
+
async setupDevice() {
|
105
|
+
if (!this.setupTimeout) {
|
106
|
+
this.log('Connected to device!');
|
107
|
+
return;
|
108
|
+
}
|
109
|
+
this.device.on('connected', () => {
|
110
|
+
this.log('Connected to device!');
|
111
|
+
const lastTime = new Date().getTime();
|
112
|
+
setInterval(async () => {
|
113
|
+
try {
|
114
|
+
const state = await this.device.get({ schema: true });
|
115
|
+
this.log(`Device state: ${JSON.stringify(state)}`);
|
116
|
+
this.updateState(state.dps);
|
117
|
+
}
|
118
|
+
catch (ex) {
|
119
|
+
this.log(`Device state error: ${ex}`);
|
120
|
+
if (!this.device.isConnected()) {
|
121
|
+
this.log('Device disconnected... trying to reconnect');
|
122
|
+
await this.device.connect();
|
123
|
+
return;
|
124
|
+
}
|
125
|
+
}
|
126
|
+
if (this.energyMonitoringEnabled()) {
|
127
|
+
this.updateEnergyConsumption(lastTime);
|
128
|
+
}
|
129
|
+
}, this.updateInterval);
|
130
|
+
});
|
131
|
+
this.device.on('disconnected', () => {
|
132
|
+
this.log('Disconnected from device!');
|
133
|
+
});
|
134
|
+
this.device.on('error', (error) => {
|
135
|
+
this.log(`Error ${error}!`);
|
136
|
+
});
|
137
|
+
this.device.on('data', (data) => {
|
138
|
+
this.log(`DATA ${JSON.stringify(data)}!`);
|
139
|
+
this.updateState(data.dps);
|
140
|
+
});
|
141
|
+
this.device.on('dp-refresh', (data) => {
|
142
|
+
this.log(`REFRESH ${JSON.stringify(data)}!`);
|
143
|
+
this.updateState(data.dps);
|
144
|
+
});
|
145
|
+
try {
|
146
|
+
//device find&connect
|
147
|
+
await this.device.find();
|
148
|
+
await this.device.connect();
|
149
|
+
clearInterval(this.setupTimeout);
|
150
|
+
this.setupTimeout = null;
|
151
|
+
}
|
152
|
+
catch (ex) {
|
153
|
+
this.log(`Device connect error: ${ex}`);
|
154
|
+
if (!this.device.isConnected()) {
|
155
|
+
this.log('Device disconnected... trying to reconnect');
|
156
|
+
return;
|
157
|
+
}
|
158
|
+
}
|
159
|
+
}
|
160
|
+
updateEnergyConsumption(lastTime) {
|
161
|
+
const now = new Date().getTime();
|
162
|
+
const delta = (now - lastTime) / 1000;
|
163
|
+
const consumption = this.watts * delta; // W/s
|
164
|
+
this.totalConsumption += consumption / kHour;
|
165
|
+
this.historyService.addEntry({ time: now / 1000, power: this.watts });
|
166
|
+
const extra = this.historyService.getExtraPersistedData();
|
167
|
+
if (!extra) {
|
168
|
+
this.historyService.setExtraPersistedData({
|
169
|
+
totalConsumption: this.totalConsumption,
|
170
|
+
resetTotal: this.resetTotal,
|
171
|
+
});
|
172
|
+
}
|
173
|
+
else if (extra.totalConsumption !== this.totalConsumption || extra.resetTotal !== this.resetTotal) {
|
174
|
+
extra.totalConsumption = this.totalConsumption;
|
175
|
+
extra.resetTotal = this.resetTotal;
|
176
|
+
this.historyService.setExtraPersistedData(extra);
|
177
|
+
}
|
178
|
+
}
|
179
|
+
getTotalConsumption(callback) {
|
180
|
+
callback(null, this.totalConsumption);
|
181
|
+
}
|
182
|
+
getResetTotal(callback) {
|
183
|
+
callback(null, this.resetTotal);
|
184
|
+
}
|
185
|
+
setResetTotal(value, callback) {
|
186
|
+
this.log.info(`setResetTotal: ${value}`);
|
187
|
+
this.resetTotal = value;
|
188
|
+
this.totalConsumption = 0;
|
189
|
+
callback(null, this.resetTotal);
|
190
|
+
}
|
191
|
+
getVoltage(callback) {
|
192
|
+
// this.updateState(this.device.state);
|
193
|
+
callback(null, this.volts);
|
194
|
+
}
|
195
|
+
getCurrent(callback) {
|
196
|
+
// this.updateState(this.device.state);
|
197
|
+
callback(null, this.amperes);
|
198
|
+
}
|
199
|
+
getConsumption(callback) {
|
200
|
+
// this.updateState(this.device.state);
|
201
|
+
callback(null, this.watts);
|
202
|
+
}
|
203
|
+
updateState(dps) {
|
204
|
+
this.amperes = dps[AMP_DP] ? dps[AMP_DP] / 1000 : 0;
|
205
|
+
this.watts = dps[WATT_DP] ? dps[WATT_DP] / 10 : 0;
|
206
|
+
this.volts = dps[VOLT_DP] ? dps[VOLT_DP] / 10 : 0;
|
207
|
+
this.inUse = dps[ONOFF_DP] ? dps[ONOFF_DP] : false;
|
208
|
+
this.log.info(`updated a:${this.amperes} w:${this.watts} v:${this.volts} on: ${this.inUse}`);
|
209
|
+
}
|
210
|
+
async setPowerOnOff(value) {
|
211
|
+
this.log.info(`SET ON: ${value}`);
|
212
|
+
await this.device.set({ set: value, dps: '1' });
|
213
|
+
}
|
214
|
+
async getPowerOnOff() {
|
215
|
+
const status = await this.device.get();
|
216
|
+
this.log.info(`GET ON: ${status.dps[1]}`);
|
217
|
+
return status.dps[1];
|
218
|
+
}
|
219
|
+
readTotalConsumption() {
|
220
|
+
this.historyService.readHistory((lastEntry, history, extra) => {
|
221
|
+
const lastItem = history.pop();
|
222
|
+
if (lastItem) {
|
223
|
+
this.log.info('History: last item: %s', lastItem);
|
224
|
+
}
|
225
|
+
else {
|
226
|
+
this.log.info('History: no data');
|
227
|
+
}
|
228
|
+
this.log.info('History: extra: %s', extra);
|
229
|
+
const totalConsumption = extra.totalConsumption ? extra.totalConsumption : 0.0;
|
230
|
+
// Math.floor(Date.now() / 1000) - 978307200 // seconds since 01.01.2001
|
231
|
+
const resetTotal = extra.resetTotal ? extra.resetTotal : 0;
|
232
|
+
this.log.info(`totalConsumption: ${totalConsumption} resetTotal: ${resetTotal}`);
|
233
|
+
this.totalConsumption = totalConsumption;
|
234
|
+
this.resetTotal = resetTotal;
|
235
|
+
});
|
236
|
+
// try {
|
237
|
+
// const filepath = this.api ? this.api.user.storagePath() : './config';
|
238
|
+
// const filename = path.join(filepath, this.historyFilename);
|
239
|
+
// this.log.info(`Reading history: ${filename}`);
|
240
|
+
// const data = fs.readFileSync(filename, 'utf8');
|
241
|
+
// const jsonData = typeof data === 'object' ? data : JSON.parse(data);
|
242
|
+
// const totalConsumption =
|
243
|
+
// jsonData.extra && jsonData.extra.totalConsumption
|
244
|
+
// ? jsonData.extra.totalConsumption
|
245
|
+
// : 0.0;
|
246
|
+
// const resetTotal =
|
247
|
+
// jsonData.extra && jsonData.extra.resetTotal
|
248
|
+
// ? jsonData.extra.resetTotal
|
249
|
+
// : 0; // Math.floor(Date.now() / 1000) - 978307200 // seconds since 01.01.2001
|
250
|
+
// this.log.info(
|
251
|
+
// `totalConsumption: ${totalConsumption} resetTotal: ${resetTotal}`,
|
252
|
+
// );
|
253
|
+
// this.totalConsumption = totalConsumption;
|
254
|
+
// this.resetTotal = resetTotal;
|
255
|
+
// } catch (err) {
|
256
|
+
// this.log.error(`readTotalConsumption error: ${err}`);
|
257
|
+
// this.totalConsumption = 0;
|
258
|
+
// this.resetTotal = 0;
|
259
|
+
// }
|
260
|
+
}
|
261
|
+
getServices() {
|
262
|
+
return [this.informationService, this.service, this.historyService.getService()];
|
263
|
+
}
|
264
|
+
}
|
265
|
+
exports.TuyaSwitchAccessory = TuyaSwitchAccessory;
|
266
|
+
//# sourceMappingURL=tuyaSwitchAccessory.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"tuyaSwitchAccessory.js","sourceRoot":"","sources":["../src/tuyaSwitchAccessory.ts"],"names":[],"mappings":"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"}
|
package/example.config.json
CHANGED
package/nodemon.json
ADDED
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"displayName": "Homebridge Tuya Eve",
|
3
3
|
"name": "@petro-kushchak/homebridge-tuya-eve",
|
4
|
-
"version": "1.0.
|
4
|
+
"version": "1.0.12",
|
5
5
|
"description": "Homebridge plugin for TuyAPI (with Eve app energy consumption monitoring)",
|
6
6
|
"license": "MIT",
|
7
7
|
"repository": {
|
@@ -21,17 +21,17 @@
|
|
21
21
|
},
|
22
22
|
"keywords": [
|
23
23
|
"homebridge-plugin",
|
24
|
-
"
|
25
|
-
"
|
26
|
-
"
|
24
|
+
"tuya",
|
25
|
+
"tuyapi",
|
26
|
+
"valve",
|
27
27
|
"eve",
|
28
28
|
"fakegato"
|
29
29
|
],
|
30
30
|
"devDependencies": {
|
31
31
|
"@types/node": "^14.14.31",
|
32
|
-
"@typescript-eslint/eslint-plugin": "^
|
33
|
-
"@typescript-eslint/parser": "^
|
34
|
-
"eslint": "^
|
32
|
+
"@typescript-eslint/eslint-plugin": "^5.0",
|
33
|
+
"@typescript-eslint/parser": "^5.0",
|
34
|
+
"eslint": "^8.15.0",
|
35
35
|
"homebridge": "^1.3.1",
|
36
36
|
"nodemon": "^2.0.7",
|
37
37
|
"rimraf": "^3.0.2",
|
package/src/index.ts
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
import { API } from 'homebridge';
|
2
|
+
import { TuyaSwitchAccessory } from './tuyaSwitchAccessory';
|
3
|
+
|
4
|
+
/*
|
5
|
+
* Initializer function called when the plugin is loaded.
|
6
|
+
*/
|
7
|
+
export = (api: API) => {
|
8
|
+
api.registerAccessory('homebridge-tuya-eve', 'TuyaSwitchEve', TuyaSwitchAccessory);
|
9
|
+
};
|
@@ -0,0 +1,103 @@
|
|
1
|
+
// Thanks to homebridge-tplink-smarthome
|
2
|
+
|
3
|
+
class EnergyCharacteristicFactory {
|
4
|
+
create(Characteristic) {
|
5
|
+
class EnergyCharacteristic extends Characteristic {
|
6
|
+
constructor(displayName, UUID, props) {
|
7
|
+
super(displayName, UUID);
|
8
|
+
this.setProps(Object.assign({
|
9
|
+
format: Characteristic.Formats.FLOAT,
|
10
|
+
minValue: 0,
|
11
|
+
maxValue: 65535,
|
12
|
+
perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY],
|
13
|
+
}, props));
|
14
|
+
this.value = this.getDefaultValue();
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
class Amperes extends EnergyCharacteristic {
|
19
|
+
constructor() {
|
20
|
+
super('Amperes', Amperes.UUID, {
|
21
|
+
unit: 'A',
|
22
|
+
minStep: 0.001,
|
23
|
+
});
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
Amperes.UUID = 'E863F126-079E-48FF-8F27-9C2605A29F52';
|
28
|
+
|
29
|
+
class TotalConsumption extends EnergyCharacteristic {
|
30
|
+
constructor() {
|
31
|
+
super('Total Consumption', TotalConsumption.UUID, {
|
32
|
+
unit: 'kWh',
|
33
|
+
minStep: 0.001,
|
34
|
+
});
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
TotalConsumption.UUID = 'E863F10C-079E-48FF-8F27-9C2605A29F52';
|
39
|
+
|
40
|
+
class KilowattVoltAmpereHour extends EnergyCharacteristic {
|
41
|
+
constructor() {
|
42
|
+
super('Apparent Energy', KilowattVoltAmpereHour.UUID, {
|
43
|
+
format: Characteristic.Formats.UINT32,
|
44
|
+
unit: 'kVAh',
|
45
|
+
minStep: 1,
|
46
|
+
});
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
KilowattVoltAmpereHour.UUID = 'E863F127-079E-48FF-8F27-9C2605A29F52';
|
51
|
+
|
52
|
+
class VoltAmperes extends EnergyCharacteristic {
|
53
|
+
constructor() {
|
54
|
+
super('Apparent Power', VoltAmperes.UUID, {
|
55
|
+
format: Characteristic.Formats.UINT16,
|
56
|
+
unit: 'VA',
|
57
|
+
minStep: 1,
|
58
|
+
});
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
VoltAmperes.UUID = 'E863F110-079E-48FF-8F27-9C2605A29F52';
|
63
|
+
|
64
|
+
class Volts extends EnergyCharacteristic {
|
65
|
+
constructor() {
|
66
|
+
super('Volts', Volts.UUID, {
|
67
|
+
unit: 'V',
|
68
|
+
minStep: 0.1,
|
69
|
+
});
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
Volts.UUID = 'E863F10A-079E-48FF-8F27-9C2605A29F52';
|
74
|
+
|
75
|
+
class Watts extends EnergyCharacteristic {
|
76
|
+
constructor() {
|
77
|
+
super('Consumption', Watts.UUID, {
|
78
|
+
unit: 'W',
|
79
|
+
minStep: 0.1,
|
80
|
+
});
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
Watts.UUID = 'E863F10D-079E-48FF-8F27-9C2605A29F52';
|
85
|
+
|
86
|
+
|
87
|
+
class ResetTotal extends EnergyCharacteristic {
|
88
|
+
constructor() {
|
89
|
+
super('Reset Total', ResetTotal.UUID, {
|
90
|
+
format: Characteristic.Formats.UINT32,
|
91
|
+
unit: Characteristic.Units.seconds,
|
92
|
+
perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY, Characteristic.Perms.WRITE],
|
93
|
+
});
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
ResetTotal.UUID = 'E863F112-079E-48FF-8F27-9C2605A29F52';
|
98
|
+
|
99
|
+
return { Amperes, KilowattVoltAmpereHour, VoltAmperes, Volts, Watts, TotalConsumption, ResetTotal };
|
100
|
+
}
|
101
|
+
}
|
102
|
+
|
103
|
+
export default new EnergyCharacteristicFactory();
|
@@ -0,0 +1,94 @@
|
|
1
|
+
import fakegato from 'fakegato-history';
|
2
|
+
import { AccessoryPlugin, API, Logging, Service } from 'homebridge';
|
3
|
+
|
4
|
+
export interface HistoryServiceEntry extends Record<string, number> {
|
5
|
+
time: number;
|
6
|
+
}
|
7
|
+
|
8
|
+
export interface HistoryService {
|
9
|
+
getExtraPersistedData(): any;
|
10
|
+
setExtraPersistedData(extra: any): unknown;
|
11
|
+
addEntry(entry: HistoryServiceEntry): void;
|
12
|
+
}
|
13
|
+
|
14
|
+
export interface HistoryServiceStorageReaderOptions {
|
15
|
+
service: unknown;
|
16
|
+
callback: (err: unknown, data: string) => void;
|
17
|
+
}
|
18
|
+
|
19
|
+
export interface HistoryServiceStorage {
|
20
|
+
globalFakeGatoStorage: {
|
21
|
+
read: (options: HistoryServiceStorageReaderOptions) => void;
|
22
|
+
};
|
23
|
+
}
|
24
|
+
|
25
|
+
export class EveHistoryService {
|
26
|
+
private readonly historyService: unknown;
|
27
|
+
|
28
|
+
constructor(
|
29
|
+
private accessory: AccessoryPlugin,
|
30
|
+
private api: API,
|
31
|
+
private serviceType: string,
|
32
|
+
private logger: Logging,
|
33
|
+
) {
|
34
|
+
const FakeGatoHistoryService = fakegato(api);
|
35
|
+
this.historyService = new FakeGatoHistoryService(this.serviceType, this.accessory, {
|
36
|
+
storage: 'fs',
|
37
|
+
log: this.logger,
|
38
|
+
});
|
39
|
+
}
|
40
|
+
|
41
|
+
getService(): Service {
|
42
|
+
return this.historyService as Service;
|
43
|
+
}
|
44
|
+
|
45
|
+
setExtraPersistedData(extra) {
|
46
|
+
(this.historyService as HistoryService).setExtraPersistedData(extra);
|
47
|
+
}
|
48
|
+
|
49
|
+
getExtraPersistedData() {
|
50
|
+
return (this.historyService as HistoryService).getExtraPersistedData();
|
51
|
+
}
|
52
|
+
|
53
|
+
addEntry(entry: HistoryServiceEntry) {
|
54
|
+
(this.historyService as HistoryService).addEntry(entry);
|
55
|
+
}
|
56
|
+
|
57
|
+
readHistory(
|
58
|
+
lastEntryHandler: (lastEntry: string, history: HistoryServiceEntry[], extra: HistoryServiceEntry) => void,
|
59
|
+
) {
|
60
|
+
const storage = ((this.api as unknown) as HistoryServiceStorage).globalFakeGatoStorage;
|
61
|
+
|
62
|
+
if (!storage) {
|
63
|
+
this.logger.debug('Failed to access globalFakeGatoStorage');
|
64
|
+
return;
|
65
|
+
}
|
66
|
+
|
67
|
+
this.logger.debug('Reading data from globalFakeGatoStorage ...');
|
68
|
+
const thisAccessory = this.accessory;
|
69
|
+
storage.read({
|
70
|
+
service: this.historyService,
|
71
|
+
callback: function (err, data) {
|
72
|
+
if (!err) {
|
73
|
+
if (data) {
|
74
|
+
try {
|
75
|
+
const accessoryName = 'name' in thisAccessory ? thisAccessory['name'] : thisAccessory;
|
76
|
+
this.logger.debug('read data from', accessoryName);
|
77
|
+
const jsonFile = typeof data === 'object' ? data : JSON.parse(data);
|
78
|
+
lastEntryHandler(
|
79
|
+
jsonFile.lastEntry,
|
80
|
+
jsonFile.history as HistoryServiceEntry[],
|
81
|
+
jsonFile.extra as HistoryServiceEntry,
|
82
|
+
);
|
83
|
+
} catch (e) {
|
84
|
+
this.logger.debug('**ERROR fetching persisting data restart from zero - invalid JSON**', e);
|
85
|
+
}
|
86
|
+
}
|
87
|
+
} else {
|
88
|
+
// file don't exists
|
89
|
+
this.logger.debug('**ERROR fetching persisting data: file dont exists', err);
|
90
|
+
}
|
91
|
+
}.bind(this),
|
92
|
+
});
|
93
|
+
}
|
94
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
2
|
+
export function callbackify(task: (...taskArgs: any[]) => Promise<any>): any {
|
3
|
+
return (...args: any[]) => {
|
4
|
+
const onlyArgs: any[] = [];
|
5
|
+
let callback: Function = undefined;
|
6
|
+
|
7
|
+
for (const arg of args) {
|
8
|
+
if (typeof arg === 'function') {
|
9
|
+
callback = arg;
|
10
|
+
break;
|
11
|
+
}
|
12
|
+
onlyArgs.push(arg);
|
13
|
+
}
|
14
|
+
if (!callback) {
|
15
|
+
throw new Error('Missing callback parameter!');
|
16
|
+
}
|
17
|
+
task(...onlyArgs)
|
18
|
+
.then((data: any) => callback(undefined, data))
|
19
|
+
.catch((err: any) => callback(err));
|
20
|
+
};
|
21
|
+
}
|
@@ -0,0 +1,336 @@
|
|
1
|
+
import {
|
2
|
+
Service,
|
3
|
+
Logging,
|
4
|
+
AccessoryConfig,
|
5
|
+
API,
|
6
|
+
AccessoryPlugin,
|
7
|
+
CharacteristicValue,
|
8
|
+
CharacteristicEventTypes,
|
9
|
+
} from 'homebridge';
|
10
|
+
import { EveHistoryService, HistoryServiceEntry } from './lib/eveHistoryService';
|
11
|
+
|
12
|
+
import TuyaDevice from 'tuyapi';
|
13
|
+
import EnergyCharacteristicFactory from './lib/EnergyCharacteristics';
|
14
|
+
import { callbackify } from './lib/homebridgeCallbacks';
|
15
|
+
|
16
|
+
const ONOFF_DP = '1';
|
17
|
+
const AMP_DP = '18';
|
18
|
+
const WATT_DP = '19';
|
19
|
+
const VOLT_DP = '20';
|
20
|
+
const kHour = 60 * 60 * 1000;
|
21
|
+
|
22
|
+
/**
|
23
|
+
* Platform Accessory
|
24
|
+
* An instance of this class is created for each accessory your platform registers
|
25
|
+
* Each accessory may expose multiple services of different service types.
|
26
|
+
*/
|
27
|
+
export class TuyaSwitchAccessory implements AccessoryPlugin {
|
28
|
+
private readonly name: string;
|
29
|
+
private readonly serial: string;
|
30
|
+
private readonly model: string;
|
31
|
+
private readonly id: string;
|
32
|
+
private readonly key: string;
|
33
|
+
private readonly updateInterval: number;
|
34
|
+
private readonly log: Logging;
|
35
|
+
private readonly displayName: string;
|
36
|
+
|
37
|
+
private readonly service: Service;
|
38
|
+
private readonly informationService: Service;
|
39
|
+
private readonly historyService: EveHistoryService;
|
40
|
+
|
41
|
+
private readonly device: TuyaDevice;
|
42
|
+
|
43
|
+
private readonly EnergyCharacteristics;
|
44
|
+
|
45
|
+
private amperes = 0;
|
46
|
+
private watts = 0;
|
47
|
+
private volts = 0;
|
48
|
+
private inUse = false;
|
49
|
+
|
50
|
+
private totalConsumption = 0;
|
51
|
+
private resetTotal = 0;
|
52
|
+
|
53
|
+
private setupTimeout: NodeJS.Timeout;
|
54
|
+
private readonly type: string;
|
55
|
+
|
56
|
+
constructor(private logger: Logging, private config: AccessoryConfig, private api: API) {
|
57
|
+
this.log = logger;
|
58
|
+
|
59
|
+
this.name = config.name;
|
60
|
+
this.displayName = this.name;
|
61
|
+
this.serial = config.serial;
|
62
|
+
this.id = config.id;
|
63
|
+
this.key = config.key;
|
64
|
+
this.model = config.model || 'TuyaSwitch';
|
65
|
+
this.updateInterval = config.updateInterval || 5000;
|
66
|
+
this.type = config.type || 'switch';
|
67
|
+
|
68
|
+
this.device = new TuyaDevice({
|
69
|
+
id: this.id,
|
70
|
+
key: this.key,
|
71
|
+
issueRefreshOnConnect: true,
|
72
|
+
});
|
73
|
+
|
74
|
+
// Set AccessoryInformation
|
75
|
+
this.informationService = new api.hap.Service.AccessoryInformation()
|
76
|
+
.setCharacteristic(api.hap.Characteristic.Name, this.name)
|
77
|
+
.setCharacteristic(api.hap.Characteristic.Manufacturer, 'Tuya')
|
78
|
+
.setCharacteristic(api.hap.Characteristic.Model, this.model)
|
79
|
+
.setCharacteristic(api.hap.Characteristic.SerialNumber, this.serial);
|
80
|
+
|
81
|
+
this.EnergyCharacteristics = EnergyCharacteristicFactory.create(api.hap.Characteristic);
|
82
|
+
|
83
|
+
switch (this.type) {
|
84
|
+
case 'switch':
|
85
|
+
this.service = new api.hap.Service.Switch(this.name);
|
86
|
+
break;
|
87
|
+
case 'valve':
|
88
|
+
this.service = new api.hap.Service.Valve(this.name);
|
89
|
+
break;
|
90
|
+
}
|
91
|
+
|
92
|
+
this.setupAccessoryServices();
|
93
|
+
|
94
|
+
this.service
|
95
|
+
.getCharacteristic(api.hap.Characteristic.On)
|
96
|
+
.on(CharacteristicEventTypes.GET, callbackify(this.getPowerOnOff.bind(this)))
|
97
|
+
.on(CharacteristicEventTypes.SET, callbackify(this.setPowerOnOff.bind(this)));
|
98
|
+
|
99
|
+
this.historyService = new EveHistoryService(
|
100
|
+
this,
|
101
|
+
this.api,
|
102
|
+
'energy',
|
103
|
+
// this.historyFilename,
|
104
|
+
this.logger,
|
105
|
+
);
|
106
|
+
|
107
|
+
//device events
|
108
|
+
this.setupTimeout = setInterval(async () => {
|
109
|
+
this.setupDevice();
|
110
|
+
}, 1000);
|
111
|
+
}
|
112
|
+
|
113
|
+
private energyMonitoringEnabled(): boolean {
|
114
|
+
return this.type === 'switch';
|
115
|
+
}
|
116
|
+
|
117
|
+
setupAccessoryServices() {
|
118
|
+
if (!this.energyMonitoringEnabled()) {
|
119
|
+
return;
|
120
|
+
}
|
121
|
+
|
122
|
+
this.service.addOptionalCharacteristic(this.EnergyCharacteristics.Volts);
|
123
|
+
this.service.addOptionalCharacteristic(this.EnergyCharacteristics.Amperes);
|
124
|
+
this.service.addOptionalCharacteristic(this.EnergyCharacteristics.Watts);
|
125
|
+
this.service.addOptionalCharacteristic(this.EnergyCharacteristics.TotalConsumption);
|
126
|
+
this.service.addOptionalCharacteristic(this.EnergyCharacteristics.ResetTotal);
|
127
|
+
|
128
|
+
this.service
|
129
|
+
.getCharacteristic(this.EnergyCharacteristics.Volts)
|
130
|
+
.on('get', this.getVoltage.bind(this))
|
131
|
+
.updateValue(this.volts);
|
132
|
+
this.service
|
133
|
+
.getCharacteristic(this.EnergyCharacteristics.Amperes)
|
134
|
+
.on('get', this.getCurrent.bind(this))
|
135
|
+
.updateValue(this.amperes);
|
136
|
+
this.service
|
137
|
+
.getCharacteristic(this.EnergyCharacteristics.Watts)
|
138
|
+
.on('get', this.getConsumption.bind(this))
|
139
|
+
.updateValue(this.watts);
|
140
|
+
|
141
|
+
this.service
|
142
|
+
.getCharacteristic(this.EnergyCharacteristics.TotalConsumption)
|
143
|
+
.on('get', this.getTotalConsumption.bind(this));
|
144
|
+
|
145
|
+
// create handlers for required characteristics
|
146
|
+
|
147
|
+
this.readTotalConsumption();
|
148
|
+
}
|
149
|
+
|
150
|
+
async setupDevice() {
|
151
|
+
if (!this.setupTimeout) {
|
152
|
+
this.log('Connected to device!');
|
153
|
+
return;
|
154
|
+
}
|
155
|
+
|
156
|
+
this.device.on('connected', () => {
|
157
|
+
this.log('Connected to device!');
|
158
|
+
|
159
|
+
const lastTime = new Date().getTime();
|
160
|
+
setInterval(async () => {
|
161
|
+
try {
|
162
|
+
const state = await this.device.get({ schema: true });
|
163
|
+
this.log(`Device state: ${JSON.stringify(state)}`);
|
164
|
+
this.updateState(state.dps);
|
165
|
+
} catch (ex) {
|
166
|
+
this.log(`Device state error: ${ex}`);
|
167
|
+
if (!this.device.isConnected()) {
|
168
|
+
this.log('Device disconnected... trying to reconnect');
|
169
|
+
await this.device.connect();
|
170
|
+
return;
|
171
|
+
}
|
172
|
+
}
|
173
|
+
|
174
|
+
if (this.energyMonitoringEnabled()) {
|
175
|
+
this.updateEnergyConsumption(lastTime);
|
176
|
+
}
|
177
|
+
}, this.updateInterval);
|
178
|
+
});
|
179
|
+
|
180
|
+
this.device.on('disconnected', () => {
|
181
|
+
this.log('Disconnected from device!');
|
182
|
+
});
|
183
|
+
|
184
|
+
this.device.on('error', (error) => {
|
185
|
+
this.log(`Error ${error}!`);
|
186
|
+
});
|
187
|
+
|
188
|
+
this.device.on('data', (data) => {
|
189
|
+
this.log(`DATA ${JSON.stringify(data)}!`);
|
190
|
+
this.updateState(data.dps);
|
191
|
+
});
|
192
|
+
|
193
|
+
this.device.on('dp-refresh', (data) => {
|
194
|
+
this.log(`REFRESH ${JSON.stringify(data)}!`);
|
195
|
+
this.updateState(data.dps);
|
196
|
+
});
|
197
|
+
|
198
|
+
try {
|
199
|
+
//device find&connect
|
200
|
+
await this.device.find();
|
201
|
+
await this.device.connect();
|
202
|
+
clearInterval(this.setupTimeout);
|
203
|
+
this.setupTimeout = null;
|
204
|
+
} catch (ex) {
|
205
|
+
this.log(`Device connect error: ${ex}`);
|
206
|
+
if (!this.device.isConnected()) {
|
207
|
+
this.log('Device disconnected... trying to reconnect');
|
208
|
+
return;
|
209
|
+
}
|
210
|
+
}
|
211
|
+
}
|
212
|
+
|
213
|
+
private updateEnergyConsumption(lastTime: number) {
|
214
|
+
const now = new Date().getTime();
|
215
|
+
const delta = (now - lastTime) / 1000;
|
216
|
+
|
217
|
+
const consumption = this.watts * delta; // W/s
|
218
|
+
this.totalConsumption += consumption / kHour;
|
219
|
+
|
220
|
+
this.historyService.addEntry({ time: now / 1000, power: this.watts });
|
221
|
+
|
222
|
+
const extra = this.historyService.getExtraPersistedData();
|
223
|
+
if (!extra) {
|
224
|
+
this.historyService.setExtraPersistedData({
|
225
|
+
totalConsumption: this.totalConsumption,
|
226
|
+
resetTotal: this.resetTotal,
|
227
|
+
});
|
228
|
+
} else if (extra.totalConsumption !== this.totalConsumption || extra.resetTotal !== this.resetTotal) {
|
229
|
+
extra.totalConsumption = this.totalConsumption;
|
230
|
+
extra.resetTotal = this.resetTotal;
|
231
|
+
this.historyService.setExtraPersistedData(extra);
|
232
|
+
}
|
233
|
+
}
|
234
|
+
|
235
|
+
getTotalConsumption(callback) {
|
236
|
+
callback(null, this.totalConsumption);
|
237
|
+
}
|
238
|
+
|
239
|
+
getResetTotal(callback) {
|
240
|
+
callback(null, this.resetTotal);
|
241
|
+
}
|
242
|
+
|
243
|
+
setResetTotal(value, callback) {
|
244
|
+
this.log.info(`setResetTotal: ${value}`);
|
245
|
+
this.resetTotal = value;
|
246
|
+
this.totalConsumption = 0;
|
247
|
+
callback(null, this.resetTotal);
|
248
|
+
}
|
249
|
+
|
250
|
+
getVoltage(callback) {
|
251
|
+
// this.updateState(this.device.state);
|
252
|
+
callback(null, this.volts);
|
253
|
+
}
|
254
|
+
|
255
|
+
getCurrent(callback) {
|
256
|
+
// this.updateState(this.device.state);
|
257
|
+
callback(null, this.amperes);
|
258
|
+
}
|
259
|
+
|
260
|
+
getConsumption(callback) {
|
261
|
+
// this.updateState(this.device.state);
|
262
|
+
callback(null, this.watts);
|
263
|
+
}
|
264
|
+
|
265
|
+
updateState(dps) {
|
266
|
+
this.amperes = dps[AMP_DP] ? dps[AMP_DP] / 1000 : 0;
|
267
|
+
this.watts = dps[WATT_DP] ? dps[WATT_DP] / 10 : 0;
|
268
|
+
this.volts = dps[VOLT_DP] ? dps[VOLT_DP] / 10 : 0;
|
269
|
+
this.inUse = dps[ONOFF_DP] ? dps[ONOFF_DP] : false;
|
270
|
+
|
271
|
+
this.log.info(`updated a:${this.amperes} w:${this.watts} v:${this.volts} on: ${this.inUse}`);
|
272
|
+
}
|
273
|
+
|
274
|
+
async setPowerOnOff(value: CharacteristicValue): Promise<void> {
|
275
|
+
this.log.info(`SET ON: ${value}`);
|
276
|
+
await this.device.set({ set: value, dps: '1' });
|
277
|
+
}
|
278
|
+
|
279
|
+
async getPowerOnOff(): Promise<CharacteristicValue> {
|
280
|
+
const status = await this.device.get();
|
281
|
+
this.log.info(`GET ON: ${status.dps[1]}`);
|
282
|
+
return status.dps[1];
|
283
|
+
}
|
284
|
+
|
285
|
+
readTotalConsumption() {
|
286
|
+
this.historyService.readHistory(
|
287
|
+
(lastEntry: string, history: HistoryServiceEntry[], extra: HistoryServiceEntry) => {
|
288
|
+
const lastItem = history.pop();
|
289
|
+
if (lastItem) {
|
290
|
+
this.log.info('History: last item: %s', lastItem);
|
291
|
+
} else {
|
292
|
+
this.log.info('History: no data');
|
293
|
+
}
|
294
|
+
this.log.info('History: extra: %s', extra);
|
295
|
+
|
296
|
+
const totalConsumption = extra.totalConsumption ? extra.totalConsumption : 0.0;
|
297
|
+
// Math.floor(Date.now() / 1000) - 978307200 // seconds since 01.01.2001
|
298
|
+
const resetTotal = extra.resetTotal ? extra.resetTotal : 0;
|
299
|
+
|
300
|
+
this.log.info(`totalConsumption: ${totalConsumption} resetTotal: ${resetTotal}`);
|
301
|
+
this.totalConsumption = totalConsumption;
|
302
|
+
this.resetTotal = resetTotal;
|
303
|
+
},
|
304
|
+
);
|
305
|
+
|
306
|
+
// try {
|
307
|
+
// const filepath = this.api ? this.api.user.storagePath() : './config';
|
308
|
+
// const filename = path.join(filepath, this.historyFilename);
|
309
|
+
// this.log.info(`Reading history: ${filename}`);
|
310
|
+
// const data = fs.readFileSync(filename, 'utf8');
|
311
|
+
// const jsonData = typeof data === 'object' ? data : JSON.parse(data);
|
312
|
+
// const totalConsumption =
|
313
|
+
// jsonData.extra && jsonData.extra.totalConsumption
|
314
|
+
// ? jsonData.extra.totalConsumption
|
315
|
+
// : 0.0;
|
316
|
+
// const resetTotal =
|
317
|
+
// jsonData.extra && jsonData.extra.resetTotal
|
318
|
+
// ? jsonData.extra.resetTotal
|
319
|
+
// : 0; // Math.floor(Date.now() / 1000) - 978307200 // seconds since 01.01.2001
|
320
|
+
|
321
|
+
// this.log.info(
|
322
|
+
// `totalConsumption: ${totalConsumption} resetTotal: ${resetTotal}`,
|
323
|
+
// );
|
324
|
+
// this.totalConsumption = totalConsumption;
|
325
|
+
// this.resetTotal = resetTotal;
|
326
|
+
// } catch (err) {
|
327
|
+
// this.log.error(`readTotalConsumption error: ${err}`);
|
328
|
+
// this.totalConsumption = 0;
|
329
|
+
// this.resetTotal = 0;
|
330
|
+
// }
|
331
|
+
}
|
332
|
+
|
333
|
+
getServices(): Service[] {
|
334
|
+
return [this.informationService, this.service, this.historyService.getService()];
|
335
|
+
}
|
336
|
+
}
|
package/tsconfig.json
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
{
|
2
|
+
"compilerOptions": {
|
3
|
+
"allowJs": true,
|
4
|
+
"target": "ES2018",
|
5
|
+
"module": "commonjs",
|
6
|
+
"lib": [
|
7
|
+
"ES2015",
|
8
|
+
"ES2016",
|
9
|
+
"ES2017",
|
10
|
+
"ES2018"
|
11
|
+
],
|
12
|
+
"sourceMap": true,
|
13
|
+
"rootDir": "src",
|
14
|
+
"outDir": "dist",
|
15
|
+
"strict": false,
|
16
|
+
"esModuleInterop": true,
|
17
|
+
"forceConsistentCasingInFileNames": true,
|
18
|
+
"resolveJsonModule": true
|
19
|
+
},
|
20
|
+
"include": [
|
21
|
+
"src"
|
22
|
+
]
|
23
|
+
}
|