Skip to content

Commit 8e3a2bf

Browse files
authored
Feature sequence n (#1)
Feature sequence n
2 parents fdc4f51 + 9ac099b commit 8e3a2bf

File tree

4 files changed

+1206
-1202
lines changed

4 files changed

+1206
-1202
lines changed

docs/index.html

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@
22
<html>
33
<head>
44
<meta charset='utf-8' />
5-
<title>@tuyapi/stub 0.1.2 | Documentation</title>
6-
<meta name='viewport' content='width=device-width,initial-scale=1'>
7-
<link href='assets/bass.css' type='text/css' rel='stylesheet' />
8-
<link href='assets/style.css' type='text/css' rel='stylesheet' />
9-
<link href='assets/github.css' type='text/css' rel='stylesheet' />
10-
<link href='assets/split.css' type='text/css' rel='stylesheet' />
5+
<title>@tuyapi/stub 0.1.3 | Documentation</title>
116
<meta name='description' content='🧪 A stub device implementation for local testing.'>
7+
<meta name='viewport' content='width=device-width,initial-scale=1'>
8+
<link href='assets/bass.css' rel='stylesheet' />
9+
<link href='assets/style.css' rel='stylesheet' />
10+
<link href='assets/github.css' rel='stylesheet' />
11+
<link href='assets/split.css' rel='stylesheet' />
1212
</head>
1313
<body class='documentation m0'>
1414
<div class='flex'>
1515
<div id='split-left' class='overflow-auto fs0 height-viewport-100'>
1616
<div class='py1 px2'>
1717
<h3 class='mb0 no-anchor'>@tuyapi/stub</h3>
18-
<div class='mb1'><code>0.1.2</code></div>
18+
<div class='mb1'><code>0.1.3</code></div>
1919
<input
2020
placeholder='Filter'
2121
id='filter-input'
@@ -114,10 +114,7 @@ <h3 class='fl m0' id='tuyastub'>
114114
<p>A stub implementation of the
115115
Tuya protocol for local testing.</p>
116116

117-
118-
119117
<div class='pre p1 fill-light mt0'>new TuyaStub(options: <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object">Object</a>)</div>
120-
121118

122119

123120

@@ -222,10 +219,7 @@ <h3 class='fl m0' id='tuyastub'>
222219

223220
<p>Starts the mocking server.</p>
224221

225-
226-
227222
<div class='pre p1 fill-light mt0'>startServer(port: <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number">Number</a>)</div>
228-
229223

230224

231225

@@ -266,6 +260,8 @@ <h3 class='fl m0' id='tuyastub'>
266260

267261

268262

263+
264+
269265
</section>
270266

271267
</div>
@@ -285,10 +281,7 @@ <h3 class='fl m0' id='tuyastub'>
285281

286282
<p>Call to cleanly exit.</p>
287283

288-
289-
290284
<div class='pre p1 fill-light mt0'>shutdown()</div>
291-
292285

293286

294287

@@ -311,6 +304,8 @@ <h3 class='fl m0' id='tuyastub'>
311304

312305

313306

307+
308+
314309

315310

316311

@@ -333,10 +328,7 @@ <h3 class='fl m0' id='tuyastub'>
333328

334329
<p>Starts the periodic UDP broadcast.</p>
335330

336-
337-
338331
<div class='pre p1 fill-light mt0'>startUDPBroadcast(options: <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object">Object</a>)</div>
339-
340332

341333

342334

@@ -413,6 +405,8 @@ <h3 class='fl m0' id='tuyastub'>
413405

414406

415407

408+
409+
416410
</section>
417411

418412
</div>
@@ -432,10 +426,7 @@ <h3 class='fl m0' id='tuyastub'>
432426

433427
<p>Sets a property of the mock device.</p>
434428

435-
436-
437429
<div class='pre p1 fill-light mt0'>setProperty(property: <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String">String</a>, value: any): any</div>
438-
439430

440431

441432

@@ -492,6 +483,8 @@ <h3 class='fl m0' id='tuyastub'>
492483

493484

494485

486+
487+
495488
</section>
496489

497490
</div>
@@ -511,10 +504,7 @@ <h3 class='fl m0' id='tuyastub'>
511504

512505
<p>Gets a property of the mock device.</p>
513506

514-
515-
516507
<div class='pre p1 fill-light mt0'>getProperty(property: <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String">String</a>): any</div>
517-
518508

519509

520510

@@ -562,6 +552,8 @@ <h3 class='fl m0' id='tuyastub'>
562552

563553

564554

555+
556+
565557
</section>
566558

567559
</div>
@@ -581,10 +573,7 @@ <h3 class='fl m0' id='tuyastub'>
581573

582574
<p>Gets entire state of the mock device.</p>
583575

584-
585-
586576
<div class='pre p1 fill-light mt0'>getState(): <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object">Object</a></div>
587-
588577

589578

590579

@@ -618,6 +607,8 @@ <h3 class='fl m0' id='tuyastub'>
618607

619608

620609

610+
611+
621612
</section>
622613

623614
</div>
@@ -637,10 +628,7 @@ <h3 class='fl m0' id='tuyastub'>
637628

638629
<p>Sets entire state of the mock device.</p>
639630

640-
641-
642631
<div class='pre p1 fill-light mt0'>setState(state: <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object">Object</a>): <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object">Object</a></div>
643-
644632

645633

646634

@@ -688,6 +676,8 @@ <h3 class='fl m0' id='tuyastub'>
688676

689677

690678

679+
680+
691681
</section>
692682

693683
</div>
@@ -698,8 +688,11 @@ <h3 class='fl m0' id='tuyastub'>
698688

699689

700690

691+
692+
701693
</section>
702694

695+
703696

704697
</div>
705698
</div>

index.js

Lines changed: 85 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
const net = require('net');
22
const dgram = require('dgram');
3-
const MessageParser = require('tuyapi/lib/message-parser');
4-
const Cipher = require('tuyapi/lib/cipher');
3+
const {MessageParser, CommandType} = require('tuyapi/lib/message-parser');
54
const debug = require('debug')('TuyaStub');
65

76
/**
@@ -24,7 +23,7 @@ class TuyaStub {
2423
this.id = options.id;
2524
this.key = options.key;
2625

27-
this.cipher = new Cipher({key: this.key, version: 3.1});
26+
this.parser = new MessageParser({key: this.key, version: 3.1});
2827
}
2928

3029
/**
@@ -74,7 +73,7 @@ class TuyaStub {
7473
options.interval = options.interval ? options.interval : 5;
7574

7675
// Encode broadcast
77-
const message = MessageParser.encode({data: {devId: this.id, gwId: this.id, ip: 'localhost'}, commandByte: 10});
76+
const message = this.parser.encode({data: {devId: this.id, gwId: this.id, ip: 'localhost'}, commandByte: CommandType.DP_QUERY});
7877

7978
// Create and bind socket
8079
this.broadcastSocket = dgram.createSocket({type: 'udp4', reuseAddr: true});
@@ -100,76 +99,89 @@ class TuyaStub {
10099
* @param {Buffer} data to handle
101100
*/
102101
_handleRequest(data) {
103-
const parsedData = MessageParser.parse(data);
102+
debug('Incoming packet(s):');
103+
debug(data.toString('hex'));
104104

105-
debug('Parsed request:');
106-
debug(parsedData);
107-
108-
if (parsedData.commandByte === 10) { // GET request
109-
// Check device ID
110-
if (parsedData.data.devId !== this.id) {
111-
throw new Error('devId of request does not match');
112-
}
113-
114-
const response = {
115-
data: {
116-
devId: this.id,
117-
gwId: this.id,
118-
dps: this.state
119-
},
120-
121-
commandByte: 10
122-
};
123-
124-
// Write response
125-
this.socket.write(MessageParser.encode(response));
126-
} else if (parsedData.commandByte === 7) { // SET request
127-
// Decrypt data
128-
const decryptedData = this.cipher.decrypt(parsedData.data);
129-
130-
debug('Decrypted data:');
131-
debug(decryptedData);
132-
133-
// Check device ID
134-
if (decryptedData.devId !== this.id) {
135-
throw new Error('devId of request does not match');
136-
}
137-
138-
// Check timestamp
139-
const now = Math.floor(Date.now() / 1000); // Seconds since epoch
105+
const parsedPackets = this.parser.parse(data);
140106

141-
// Timestamp difference must be no more than 10 seconds
142-
if (Math.abs(now - decryptedData.t) > 10) {
143-
throw new Error('Bad timestamp.');
107+
debug('Parsed request:');
108+
debug(parsedPackets);
109+
110+
parsedPackets.forEach(packet => {
111+
if (packet.commandByte === CommandType.DP_QUERY) { // GET request
112+
// Check device ID
113+
if (packet.payload.devId !== this.id) {
114+
throw new Error('devId of request does not match');
115+
}
116+
117+
const response = {
118+
data: {
119+
devId: this.id,
120+
gwId: this.id,
121+
dps: this.state
122+
},
123+
commandByte: 10,
124+
sequenceN: packet.sequenceN
125+
};
126+
127+
// Write response
128+
this.socket.write(this.parser.encode(response));
129+
} else if (packet.commandByte === CommandType.CONTROL) { // SET request
130+
// Decrypt data
131+
const decryptedData = packet.payload;
132+
133+
debug('Decrypted data:');
134+
debug(decryptedData);
135+
136+
// Check device ID
137+
if (decryptedData.devId !== this.id) {
138+
throw new Error('devId of request does not match');
139+
}
140+
141+
// Check timestamp
142+
const now = Math.floor(Date.now() / 1000); // Seconds since epoch
143+
144+
// Timestamp difference must be no more than 10 seconds
145+
if (Math.abs(now - decryptedData.t) > 10) {
146+
throw new Error('Bad timestamp.');
147+
}
148+
149+
// Set properties
150+
Object.keys(decryptedData.dps).forEach(property => {
151+
this.setProperty(property, decryptedData.dps[property]);
152+
});
153+
154+
// Responses for status updates have two parts
155+
const confirmChangeResponse = {
156+
data: {},
157+
commandByte: 7,
158+
sequenceN: packet.sequenceN
159+
};
160+
161+
this.socket.write(this.parser.encode(confirmChangeResponse));
162+
163+
const statusUpdateResponse = {
164+
data: {
165+
devId: this.id,
166+
gwId: this.id,
167+
dps: this.state
168+
},
169+
commandByte: 8
170+
};
171+
172+
this.socket.write(this.parser.encode(statusUpdateResponse));
173+
} else if (packet.commandByte === CommandType.HEART_BEAT) { // Heartbeat packet
174+
// Send response pong
175+
debug('Sending pong...');
176+
const buffer = this.parser.encode({
177+
data: Buffer.allocUnsafe(0),
178+
commandByte: CommandType.HEART_BEAT,
179+
sequenceN: packet.sequenceN
180+
});
181+
182+
this.socket.write(buffer);
144183
}
145-
146-
// Set properties
147-
Object.keys(decryptedData.dps).forEach(property => {
148-
this.setProperty(property, decryptedData.dps[property]);
149-
});
150-
151-
// Write response
152-
const response = {
153-
data: {
154-
devId: this.id,
155-
gwId: this.id,
156-
dps: this.state
157-
},
158-
159-
commandByte: 10
160-
};
161-
162-
this.socket.write(MessageParser.encode(response));
163-
} else if (parsedData.commandByte === 9) { // Heartbeat packet
164-
// Send response pong
165-
debug('Sending pong...');
166-
const buffer = MessageParser.encode({
167-
data: Buffer.allocUnsafe(0),
168-
commandByte: 9 // 0x09
169-
});
170-
171-
this.socket.write(buffer);
172-
}
184+
});
173185
}
174186

175187
/**
@@ -190,10 +202,10 @@ class TuyaStub {
190202
dps: this.state
191203
},
192204

193-
commandByte: 10
205+
commandByte: CommandType.CONTROL
194206
};
195207

196-
this.socket.write(MessageParser.encode(response));
208+
this.socket.write(this.parser.encode(response));
197209
}
198210

199211
return this.state[property];

0 commit comments

Comments
 (0)