Skip to content

Commit 32b36db

Browse files
authored
add special handling for SET-as-GET requests that return DP_QUERY (#616)
1 parent 7f64b0e commit 32b36db

File tree

2 files changed

+81
-11
lines changed

2 files changed

+81
-11
lines changed

index.js

Lines changed: 81 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ class TuyaDevice extends EventEmitter {
177177
debug('GET needs to use SEND instead of DP_QUERY to get data');
178178
const setOptions = {
179179
dps: options.dps ? options.dps : 1,
180-
set: null
180+
set: null,
181+
isSetCallToGetData: true
181182
};
182183
data = await this.set(setOptions);
183184
}
@@ -233,18 +234,20 @@ class TuyaDevice extends EventEmitter {
233234
payload.cid = options.cid;
234235
}
235236

236-
debug('GET Payload:');
237+
debug('GET Payload (refresh):');
237238
debug(payload);
238239

240+
const sequenceN = ++this._currentSequenceN;
239241
// Create byte buffer
240242
const buffer = this.device.parser.encode({
241243
data: payload,
242244
commandByte: CommandType.DP_REFRESH,
243-
sequenceN: ++this._currentSequenceN
245+
sequenceN
244246
});
245247

246248
// Send request and parse response
247249
return new Promise((resolve, reject) => {
250+
this._expectRefreshResponseForSequenceN = sequenceN;
248251
// Send request
249252
this._send(buffer).then(async data => {
250253
if (data === 'json obj data unvalid') {
@@ -254,7 +257,8 @@ class TuyaDevice extends EventEmitter {
254257
// For schema there's currently no fallback options
255258
const setOptions = {
256259
dps: options.requestedDPS ? options.requestedDPS : this._dpRefreshIds,
257-
set: null
260+
set: null,
261+
isSetCallToGetData: true
258262
};
259263
data = await this.set(setOptions);
260264
}
@@ -283,6 +287,8 @@ class TuyaDevice extends EventEmitter {
283287
* if specified, use device id of zigbee gateway and cid of subdevice to set its property
284288
* @param {Boolean} [options.multiple=false]
285289
* Whether or not multiple properties should be set with options.data
290+
* @param {Boolean} [options.isSetCallToGetData=false]
291+
* Wether or not the set command is used to get data
286292
* @param {Object} [options.data={}] Multiple properties to set at once. See above.
287293
* @param {Boolean} [options.shouldWaitForResponse=true] see
288294
* [#420](https://github.com/codetheweb/tuyapi/issues/420) and
@@ -333,6 +339,14 @@ class TuyaDevice extends EventEmitter {
333339

334340
options.shouldWaitForResponse = typeof options.shouldWaitForResponse === 'undefined' ? true : options.shouldWaitForResponse;
335341

342+
// When set has only null values then it is used to get data
343+
if (!options.isSetCallToGetData) {
344+
options.isSetCallToGetData = true;
345+
Object.keys(dps).forEach(key => {
346+
options.isSetCallToGetData = options.isSetCallToGetData && dps[key] === null;
347+
});
348+
}
349+
336350
// Get time
337351
const timeStamp = parseInt(Date.now() / 1000, 10);
338352

@@ -378,16 +392,21 @@ class TuyaDevice extends EventEmitter {
378392
delete payload.data.t;
379393
}
380394

395+
if (options.shouldWaitForResponse && this._setResolver) {
396+
throw new Error('A set command is already in progress. Can not issue a second one that also should return a response.');
397+
}
398+
381399
debug('SET Payload:');
382400
debug(payload);
383401

384402
const commandByte = this.device.version === '3.4' ? CommandType.CONTROL_NEW : CommandType.CONTROL;
403+
const sequenceN = ++this._currentSequenceN;
385404
// Encode into packet
386405
const buffer = this.device.parser.encode({
387406
data: payload,
388407
encrypted: true, // Set commands must be encrypted
389408
commandByte,
390-
sequenceN: ++this._currentSequenceN
409+
sequenceN
391410
});
392411

393412
// Queue this request and limit concurrent set requests to one
@@ -398,6 +417,7 @@ class TuyaDevice extends EventEmitter {
398417
this._send(buffer);
399418
if (options.shouldWaitForResponse) {
400419
this._setResolver = resolve;
420+
this._setResolveAllowGet = options.isSetCallToGetData;
401421
} else {
402422
resolve();
403423
}
@@ -407,6 +427,9 @@ class TuyaDevice extends EventEmitter {
407427
}), this._responseTimeout * 2500, () => {
408428
// Only gets here on timeout so clear resolver function and emit error
409429
this._setResolver = undefined;
430+
this._setResolveAllowGet = undefined;
431+
delete this._resolvers[sequenceN];
432+
this._expectRefreshResponseForSequenceN = undefined;
410433

411434
this.emit(
412435
'error',
@@ -424,6 +447,7 @@ class TuyaDevice extends EventEmitter {
424447
* @returns {Promise<Any>} returned data for request
425448
*/
426449
_send(buffer) {
450+
const sequenceNo = this._currentSequenceN;
427451
// Retry up to 5 times
428452
return pRetry(() => {
429453
return new Promise((resolve, reject) => {
@@ -433,7 +457,7 @@ class TuyaDevice extends EventEmitter {
433457
this.client.write(buffer);
434458

435459
// Add resolver function
436-
this._resolvers[this._currentSequenceN] = data => resolve(data);
460+
this._resolvers[sequenceNo] = data => resolve(data);
437461
} catch (error) {
438462
reject(error);
439463
}
@@ -756,7 +780,36 @@ class TuyaDevice extends EventEmitter {
756780

757781
// Returned DP refresh response is always empty. Device respond with command 8 without dps 1 instead.
758782
if (packet.commandByte === CommandType.DP_REFRESH) {
759-
debug('Received DP_REFRESH empty response packet.');
783+
// If we did not get any STATUS packet, we need to resolve the promise.
784+
if (typeof this._setResolver === 'function') {
785+
debug('Received DP_REFRESH empty response packet without STATUS packet from set command - resolve');
786+
this._setResolver(packet.payload);
787+
788+
// Remove resolver
789+
this._setResolver = undefined;
790+
this._setResolveAllowGet = undefined;
791+
delete this._resolvers[packet.sequenceN];
792+
this._expectRefreshResponseForSequenceN = undefined;
793+
} else {
794+
// Call data resolver for sequence number
795+
if (packet.sequenceN in this._resolvers) {
796+
debug('Received DP_REFRESH response packet - resolve');
797+
this._resolvers[packet.sequenceN](packet.payload);
798+
799+
// Remove resolver
800+
delete this._resolvers[packet.sequenceN];
801+
this._expectRefreshResponseForSequenceN = undefined;
802+
} else if (this._expectRefreshResponseForSequenceN && this._expectRefreshResponseForSequenceN in this._resolvers) {
803+
debug('Received DP_REFRESH response packet without data - resolve');
804+
this._resolvers[this._expectRefreshResponseForSequenceN](packet.payload);
805+
806+
// Remove resolver
807+
delete this._resolvers[this._expectRefreshResponseForSequenceN];
808+
this._expectRefreshResponseForSequenceN = undefined;
809+
} else {
810+
debug('Received DP_REFRESH response packet - no resolver found for sequence number' + packet.sequenceN);
811+
}
812+
}
760813
return;
761814
}
762815

@@ -788,16 +841,33 @@ class TuyaDevice extends EventEmitter {
788841
}
789842

790843
// Status response to SET command
791-
792-
// 3.4 response sequenceN is not '0' just next TODO verify
793-
if (/* Former code: packet.sequenceN === 0 && */
844+
if (
794845
packet.commandByte === CommandType.STATUS &&
795846
typeof this._setResolver === 'function'
796847
) {
797848
this._setResolver(packet.payload);
798849

799850
// Remove resolver
800851
this._setResolver = undefined;
852+
this._setResolveAllowGet = undefined;
853+
delete this._resolvers[packet.sequenceN];
854+
this._expectRefreshResponseForSequenceN = undefined;
855+
return;
856+
}
857+
858+
// Status response to SET command which was used to GET data and returns DP_QUERY response
859+
if (
860+
packet.commandByte === CommandType.DP_QUERY &&
861+
typeof this._setResolver === 'function' &&
862+
this._setResolveAllowGet === true
863+
) {
864+
this._setResolver(packet.payload);
865+
866+
// Remove resolver
867+
this._setResolver = undefined;
868+
this._setResolveAllowGet = undefined;
869+
delete this._resolvers[packet.sequenceN];
870+
this._expectRefreshResponseForSequenceN = undefined;
801871
return;
802872
}
803873

@@ -807,6 +877,7 @@ class TuyaDevice extends EventEmitter {
807877

808878
// Remove resolver
809879
delete this._resolvers[packet.sequenceN];
880+
this._expectRefreshResponseForSequenceN = undefined;
810881
}
811882
}
812883

lib/message-parser.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ class MessageParser {
144144
throw new TypeError(`Packet missing payload: payload has length ${payloadSize}.`);
145145
}
146146

147-
148147
const packageFromDiscovery = (
149148
commandByte === CommandType.UDP ||
150149
commandByte === CommandType.UDP_NEW ||

0 commit comments

Comments
 (0)