homebridge-tuya-plus 3.1.2

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.
Files changed (42) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  2. package/.github/ISSUE_TEMPLATE/new-device.md +24 -0
  3. package/.github/workflows/codeql-analysis.yml +67 -0
  4. package/.github/workflows/lint.yml +23 -0
  5. package/Changelog.md +63 -0
  6. package/LICENSE +21 -0
  7. package/Readme.MD +106 -0
  8. package/assets/Tuya-Plugin-Branding.png +0 -0
  9. package/bin/cli-decode.js +197 -0
  10. package/bin/cli-find.js +207 -0
  11. package/bin/cli.js +13 -0
  12. package/config-example.MD +43 -0
  13. package/config.schema.json +538 -0
  14. package/eslint.config.mjs +15 -0
  15. package/index.js +242 -0
  16. package/lib/AirConditionerAccessory.js +445 -0
  17. package/lib/AirPurifierAccessory.js +532 -0
  18. package/lib/BaseAccessory.js +290 -0
  19. package/lib/ConvectorAccessory.js +313 -0
  20. package/lib/CustomMultiOutletAccessory.js +111 -0
  21. package/lib/DehumidifierAccessory.js +301 -0
  22. package/lib/DoorbellAccessory.js +208 -0
  23. package/lib/EnergyCharacteristics.js +86 -0
  24. package/lib/GarageDoorAccessory.js +307 -0
  25. package/lib/MultiOutletAccessory.js +106 -0
  26. package/lib/OilDiffuserAccessory.js +480 -0
  27. package/lib/OutletAccessory.js +83 -0
  28. package/lib/RGBTWLightAccessory.js +234 -0
  29. package/lib/RGBTWOutletAccessory.js +296 -0
  30. package/lib/SimpleBlindsAccessory.js +299 -0
  31. package/lib/SimpleDimmer2Accessory.js +54 -0
  32. package/lib/SimpleDimmerAccessory.js +54 -0
  33. package/lib/SimpleFanAccessory.js +137 -0
  34. package/lib/SimpleFanLightAccessory.js +201 -0
  35. package/lib/SimpleHeaterAccessory.js +154 -0
  36. package/lib/SimpleLightAccessory.js +39 -0
  37. package/lib/SwitchAccessory.js +106 -0
  38. package/lib/TWLightAccessory.js +91 -0
  39. package/lib/TuyaAccessory.js +746 -0
  40. package/lib/TuyaDiscovery.js +165 -0
  41. package/lib/ValveAccessory.js +150 -0
  42. package/package.json +49 -0
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env node
2
+
3
+ const Proxy = require('http-mitm-proxy');
4
+ const EventEmitter = require('events');
5
+ const program = require('commander');
6
+ const QRCode = require('qrcode');
7
+ const path = require('path');
8
+ const os = require('os');
9
+ const JSON5 = require('json5');
10
+ const fs = require('fs-extra');
11
+
12
+ // Disable debug messages from the proxy
13
+ try {
14
+ require('debug').disable();
15
+ } catch(ex) {}
16
+
17
+ const ROOT = path.resolve(__dirname);
18
+
19
+ const pemFile = path.join(ROOT, 'certs', 'ca.pem');
20
+
21
+ let localIPs = [];
22
+ const ifaces = os.networkInterfaces();
23
+ Object.keys(ifaces).forEach(name => {
24
+ ifaces[name].forEach(network => {
25
+ if (network.family === 'IPv4' && !network.internal) localIPs.push(network.address);
26
+ });
27
+ });
28
+
29
+ const proxy = Proxy();
30
+ const emitter = new EventEmitter();
31
+
32
+ program
33
+ .name('tuya-lan find')
34
+ .option('--ip <ip>', 'IP address to listen for requests')
35
+ .option('-p, --port <port>', 'port the proxy should listen on', 8060)
36
+ .option('--schema', 'include schema in the output')
37
+ .parse(process.argv);
38
+
39
+
40
+ if (program.ip) {
41
+ if (localIPs.includes(program.ip)) localIPs = [program.ip];
42
+ else {
43
+ console.log(`The requested IP, ${program.ip}, is not a valid external IPv4 address. The valid options are:\n\t${localIPs.join('\n\t')}`);
44
+ process.exit();
45
+ }
46
+ }
47
+ if (localIPs.length > 1) {
48
+ console.log(`You have multiple network interfaces: ${localIPs.join(', ')}\nChoose one by passing it with the --ip parameter.\n\nExample: tuya-lan-find --ip ${localIPs[0]}`);
49
+ process.exit();
50
+ }
51
+ const localIPPorts = localIPs.map(ip => `${ip}:${program.port}`);
52
+
53
+ const escapeUnicode = str => str.replace(/[\u00A0-\uffff]/gu, c => "\\u" + ("000" + c.charCodeAt().toString(16)).slice(-4));
54
+
55
+ proxy.onError(function(ctx, err) {
56
+ switch (err.code) {
57
+ case 'ERR_STREAM_DESTROYED':
58
+ case 'ECONNRESET':
59
+ return;
60
+
61
+ case 'ECONNREFUSED':
62
+ console.error('Failed to intercept secure communications. This could happen due to bad CA certificate.');
63
+ return;
64
+
65
+ case 'EACCES':
66
+ console.error(`Permission was denied to use port ${program.port}.`);
67
+ return;
68
+
69
+ default:
70
+ console.error('Error:', err.code, err);
71
+ }
72
+ });
73
+
74
+ proxy.onRequest(function(ctx, callback) {
75
+ if (ctx.clientToProxyRequest.method === 'GET' && ctx.clientToProxyRequest.url === '/cert' && localIPPorts.includes(ctx.clientToProxyRequest.headers.host)) {
76
+ ctx.use(Proxy.gunzip);
77
+ console.log('Intercepted certificate request');
78
+
79
+ ctx.proxyToClientResponse.writeHeader(200, {
80
+ 'Accept-Ranges': 'bytes',
81
+ 'Cache-Control': 'public, max-age=0',
82
+ 'Content-Type': 'application/x-x509-ca-cert',
83
+ 'Content-Disposition': 'attachment; filename=cert.pem',
84
+ 'Content-Transfer-Encoding': 'binary',
85
+ 'Content-Length': fs.statSync(pemFile).size,
86
+ 'Connection': 'keep-alive',
87
+ });
88
+ //ctx.proxyToClientResponse.end(fs.readFileSync(path.join(ROOT, 'certs', 'ca.pem')));
89
+ ctx.proxyToClientResponse.write(fs.readFileSync(pemFile));
90
+ ctx.proxyToClientResponse.end();
91
+
92
+ return;
93
+
94
+ } else if (ctx.clientToProxyRequest.method === 'POST' && /tuya/.test(ctx.clientToProxyRequest.headers.host)) {
95
+ ctx.use(Proxy.gunzip);
96
+
97
+ ctx.onRequestData(function(ctx, chunk, callback) {
98
+ return callback(null, chunk);
99
+ });
100
+ ctx.onRequestEnd(function(ctx, callback) {
101
+ callback();
102
+ });
103
+
104
+ let chunks = [];
105
+ ctx.onResponseData(function(ctx, chunk, callback) {
106
+ chunks.push(chunk);
107
+ return callback(null, chunk);
108
+ });
109
+ ctx.onResponseEnd(function(ctx, callback) {
110
+ emitter.emit('tuya-config', Buffer.concat(chunks).toString());
111
+ callback();
112
+ });
113
+ }
114
+
115
+ return callback();
116
+ });
117
+
118
+ emitter.on('tuya-config', body => {
119
+ if (body.indexOf('tuya.m.my.group.device.list') === -1) return;
120
+ console.log('Intercepted config from Tuya');
121
+ let data;
122
+ const fail = (msg, err) => {
123
+ console.error(msg, err);
124
+ process.exit(1);
125
+ };
126
+ try {
127
+ data = JSON.parse(body);
128
+ } catch (ex) {
129
+ return fail('There was a problem decoding config:', ex);
130
+ }
131
+ if (!Array.isArray(data.result)) return fail('Couldn\'t find a valid result-set.');
132
+
133
+ let devices = [];
134
+ data.result.some(data => {
135
+ if (data && data.a === 'tuya.m.my.group.device.list') {
136
+ devices = data.result;
137
+ return true;
138
+ }
139
+ return false;
140
+ });
141
+
142
+ if (!Array.isArray(devices)) return fail('Couldn\'t find a good list of devices.');
143
+
144
+ console.log(`\nFound ${devices.length} device${devices.length === 1 ? '' : 's'}:`);
145
+
146
+ const foundDevices = devices.map(device => {
147
+ return {
148
+ name: device.name,
149
+ id: device.devId,
150
+ key: device.localKey,
151
+ pid: device.productId
152
+ }
153
+ });
154
+
155
+ if (program.schema) {
156
+ let schemas = [];
157
+ data.result.some(data => {
158
+ if (data && data.a === 'tuya.m.device.ref.info.my.list') {
159
+ schemas = data.result;
160
+ return true;
161
+ }
162
+ return false;
163
+ });
164
+
165
+ if (Array.isArray(schemas)) {
166
+ const defs = {};
167
+ schemas.forEach(schema => {
168
+ if (schema.id && schema.schemaInfo) {
169
+ defs[schema.id] = {};
170
+ if (schema.schemaInfo.schema) defs[schema.id].schema = escapeUnicode(schema.schemaInfo.schema);
171
+ if (schema.schemaInfo.schemaExt && schema.schemaInfo.schemaExt !== '[]') defs[schema.id].extras = escapeUnicode(schema.schemaInfo.schemaExt);
172
+ }
173
+ });
174
+ foundDevices.forEach(device => {
175
+ if (defs[device.pid]) device.def = defs[device.pid];
176
+ });
177
+ } else console.log('Didn\'t find schema definitions. You will need to identify the data-points manually if this is a new device.');
178
+ }
179
+
180
+ foundDevices.forEach(device => {
181
+ delete device.pid;
182
+ });
183
+
184
+ console.log(JSON5.stringify(foundDevices, '\n', 2));
185
+
186
+ setTimeout(() => {
187
+ process.exit(0);
188
+ }, 5000);
189
+ });
190
+
191
+ proxy.listen({port: program.port, sslCaDir: ROOT}, err => {
192
+ if (err) {
193
+ console.error('Error starting proxy: ' + err);
194
+ return setTimeout(() => {
195
+ process.exit(0);
196
+ }, 5000);
197
+ }
198
+ let {address, port} = proxy.httpServer.address();
199
+ if (address === '::' || address === '0.0.0.0') address = localIPs[0];
200
+
201
+ QRCode.toString(`http://${address}:${port}/cert`, {type: 'terminal'}, function(err, url) {
202
+ console.log(url);
203
+ console.log('\nFollow the instructions on https://github.com/AMoo-Miki/homebridge-tuya-lan/wiki/Setup-Instructions');
204
+ console.log(`Proxy IP: ${address}`);
205
+ console.log(`Proxy Port: ${port}\n\n`);
206
+ })
207
+ });
package/bin/cli.js ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+
3
+ const program = require('commander');
4
+ const path = require('path');
5
+ const fs = require('fs-extra');
6
+
7
+ const ROOT = path.resolve(__dirname);
8
+
9
+ program
10
+ .version('v' + fs.readJSONSync(path.join(ROOT, '../package.json')).version, '-v, --version', 'output package version')
11
+ .command('decode', 'decode a file or packet')
12
+ .command('find', 'find device id and key combinations', {isDefault: true})
13
+ .parse(process.argv);
@@ -0,0 +1,43 @@
1
+ ## Working Apps:
2
+ > Current apps working to get device ID's:
3
+ - uComen Home
4
+
5
+
6
+
7
+
8
+ ## Configurations
9
+ The configuration parameters to enable your devices would need to be added to `platforms` section of the Homebridge configuration file. Examples of device configs can be found on the [Supported Device Types](https://github.com/iRayanKhan/homebridge-tuya/wiki/Supported-Device-Types) page. Check out the [Common Problems](https://github.com/iRayanKhan/homebridge-tuya/wiki/Common-Problems) page for solutions or raise an issue if you face problems.
10
+ ```json5
11
+ {
12
+ ...
13
+ "platforms": [
14
+ ...
15
+ /* The block you need to enable this plugin */
16
+ {
17
+ "platform": "TuyaLan",
18
+ "devices": [
19
+ /* The block you need for each device */
20
+ {
21
+ "name": "Hallway Light",
22
+ "type": "SimpleLight",
23
+ "manufacturer": "Cotify",
24
+ "model": "Smart Wifi Bulb Socket E26",
25
+ "id": "011233455677899abbcd",
26
+ "key": "0123456789abcdef"
27
+ }
28
+ /* End of the device definition block */
29
+ ]
30
+ }
31
+ /* End of the block needed to enable this plugin */
32
+ ]
33
+ ...
34
+ }
35
+ ```
36
+ #### Parameters
37
+ * `name` (required) is anything you'd like to use to identify this device. You can always change the name from within the Home app.
38
+ * `type` (required) is a case-insensitive identifier that lets the plugin know how to handle your device. Find your device `type` on the [Supported Device Type List](https://github.com/iRayanKhan/homebridge-tuya/wiki/Supported-Device-Types) page.
39
+ * `manufacturer` and `model` are anything you like; the purpose of them is to help you identify the device.
40
+ * `id` (required) and `key` (required) are parameters for your device. If you don't have them, follow the steps found on the [Setup Instructions](https://github.com/iRayanKgan/homebridge-tuya/wiki/Setup-Instructions) page.
41
+ * `ip` needs to be added **_only_** if you face discovery issues. See [Common Problems](https://github.com/iRayanKhan/homebridge-tuya/wiki/Common-Problems) for more details.
42
+
43
+ > To find out which `id` belongs to which device, open the Tuya Smart app and check the `Device Information` by tapping the configuration icon of your devices; it is almost always a tiny icon on the top-right.