codetheweb / tuyapi

🌧 An easy-to-use API for devices that use Tuya's cloud services. Documentation: https://codetheweb.github.io/tuyapi.
MIT License
2.1k stars 342 forks source link

Payload returns nulls #338

Closed Nurffe closed 4 years ago

Nurffe commented 4 years ago

Describe the bug Null payload informations returned

To Reproduce

  1. Run async example node asyn.js (https://github.com/codetheweb/tuyapi)
    
    const TuyAPI = require('tuyapi');

const device = new TuyAPI({ id: 'xxxxxxxxxxxxxxxxxxxx', key: 'xxxxxxxxxxxxxxxx'});

let stateHasChanged = false;

// Find device on network device.find().then(() => { // Connect to device device.connect(); });

// Add event listeners device.on('connected', () => { console.log('Connected to device!'); });

device.on('disconnected', () => { console.log('Disconnected from device.'); });

device.on('error', error => { console.log('Error!', error); });

device.on('data', data => { console.log('Data from device:', data);

console.log(Boolean status of default property: ${data.dps['1']}.);

// Set default property to opposite if (!stateHasChanged) { device.set({set: !(data.dps['1'])});

// Otherwise we'll be stuck in an endless
// loop of toggling the state.
stateHasChanged = true;

} });

// Disconnect after 10 seconds setTimeout(() => { device.disconnect(); }, 10000);

2. Check data from device

**Expected behavior**
Not null values returned

**Debug Output**
Post the output of your program/app/script when run with the `DEBUG` environment variable set to `*`.  Example: `DEBUG=* node test.js`.  Copy the output and paste it below (in between the code fences):

node async.js Connected to device! Error! json obj data unvalid Data from device: { dps: { '1': null, '2': null, '3': null, '101': null, '102': null, '103': null } } Boolean status of default property: null. Data from device: { dps: { '1': true }, t: 1593283963 } Boolean status of default property: true. Disconnected from device.



**Desktop (please complete the following information):**
 - OS: Raspbian Buster
 - Node Version v10.21.0
 - HW techkin smartplugs https://www.amazon.de/gp/product/B07CDCYLQ6/
codetheweb commented 4 years ago

It was decided here (see linked issue thread) that for some applications returning a null payload for that specific error is acceptable. The behavior of your device looks similar to what's described in that thread.

beele commented 4 years ago

I have a device that returns these null values (an LSC ceiling light), but with the code like above I never get the second 'Data from device' line with actual correct information, I also cannot control the device. Is there any way to debug this? The tuya application can control the device fine.

I printed the device after connection and got this.

TuyaDevice {
  _events: [Object: null prototype] {
    connected: [Function],
    disconnected: [Function],
    error: [Function],
    data: [AsyncFunction]
  },
  _eventsCount: 4,
  _maxListeners: undefined,
  device: {
    ip: 'SNIP',
    port: 6668,
    id: 'SNIP',
    gwID: 'SNIP',
    key: 'SNIP',
    productKey: 'SNIP',
    version: '3.3',
    parser: MessageParser {
      version: '3.3',
      cipher: [TuyaCipher],
      key: 'SNIP'
    }
  },
  foundDevices: [ { id: 'SNIP', ip: 'SNIP' } ],
  _connected: false,
  _responseTimeout: 5,
  _connectTimeout: 5,
  _pingPongPeriod: 10,
  _currentSequenceN: 0,
  _resolvers: {},
  _waitingForSetToResolve: false,
  client: Socket {
    connecting: true,
    _hadError: false,
    _parent: null,
    _host: null,
    _readableState: ReadableState {
      objectMode: false,
      highWaterMark: 16384,
      buffer: BufferList { head: null, tail: null, length: 0 },
      length: 0,
      pipes: null,
      pipesCount: 0,
      flowing: true,
      ended: false,
      endEmitted: false,
      reading: false,
      sync: true,
      needReadable: false,
      emittedReadable: false,
      readableListening: false,
      resumeScheduled: true,
      emitClose: false,
      autoDestroy: false,
      destroyed: false,
      defaultEncoding: 'utf8',
      awaitDrainWriters: null,
      multiAwaitDrain: false,
      readingMore: false,
      decoder: null,
      encoding: null,
      [Symbol(kPaused)]: false
    },
    readable: false,
    _events: [Object: null prototype] {
      end: [Function: onReadableStreamEnd],
      timeout: [Function],
      data: [Function],
      error: [Function],
      close: [Function],
      connect: [AsyncFunction]
    },
    _eventsCount: 6,
    _maxListeners: undefined,
    _writableState: WritableState {
      objectMode: false,
      highWaterMark: 16384,
      finalCalled: false,
      needDrain: false,
      ending: false,
      ended: false,
      finished: false,
      destroyed: false,
      decodeStrings: false,
      defaultEncoding: 'utf8',
      length: 0,
      writing: false,
      corked: 0,
      sync: true,
      bufferProcessing: false,
      onwrite: [Function: bound onwrite],
      writecb: null,
      writelen: 0,
      afterWriteTickInfo: null,
      bufferedRequest: null,
      lastBufferedRequest: null,
      pendingcb: 0,
      prefinished: false,
      errorEmitted: false,
      emitClose: false,
      autoDestroy: false,
      bufferedRequestCount: 0,
      corkedRequestsFree: [Object]
    },
    writable: true,
    allowHalfOpen: false,
    _sockname: null,
    _pendingData: null,
    _pendingEncoding: '',
    server: null,
    _server: null,
    timeout: 5000,
    [Symbol(asyncId)]: 12,
    [Symbol(kHandle)]: TCP {
      reading: false,
      onconnection: null,
      [Symbol(owner)]: [Circular]
    },
    [Symbol(kSetNoDelay)]: false,
    [Symbol(lastWriteQueueSize)]: 0,
    [Symbol(timeout)]: Timeout {
      _idleTimeout: 5000,
      _idlePrev: [TimersList],
      _idleNext: [Timeout],
      _idleStart: 4894,
      _onTimeout: [Function: bound ],
      _timerArgs: undefined,
      _repeat: null,
      _destroyed: false,
      [Symbol(refed)]: false,
      [Symbol(asyncId)]: 14,
      [Symbol(triggerId)]: 0
    },
    [Symbol(kBuffer)]: null,
    [Symbol(kBufferCb)]: null,
    [Symbol(kBufferGen)]: null,
    [Symbol(kCapture)]: false,
    [Symbol(kBytesRead)]: 0,
    [Symbol(kBytesWritten)]: 0
  },
  [Symbol(kCapture)]: false
}
Connected to device!
Error! json obj data unvalid
Data from device: {
  dps: {
    '1': null,
    '2': null,
    '3': null,
    '101': null,
    '102': null,
    '103': null
  }
}
Boolean status of default property: null.
codetheweb commented 4 years ago

@beele to get additional debug output, run your script with the environment variable DEBUG set to (i.e. `DEBUG= node script.js`).

tasict commented 4 years ago

I have the same issue.My device is Zigbee bridge with sub switch. The debug log is below

DEBUG=* node test.js

TuyAPI Finding missing IP undefined or ID eb0f4c330d33b7f24bmugy +0ms TuyAPI Received UDP message. +551ms TuyAPI UDP data: +2ms TuyAPI { TuyAPI payload: { TuyAPI ip: '10.1.9.42', TuyAPI gwId: 'eb0f4c330d33b7f24bmugy', TuyAPI active: 2, TuyAPI ablilty: 0, TuyAPI encrypt: true, TuyAPI productKey: 'keyfa7hya4gfa7g9', TuyAPI version: '3.3' TuyAPI }, TuyAPI leftover: false, TuyAPI commandByte: 19, TuyAPI sequenceN: 0 TuyAPI } +0ms TuyAPI Connecting to 10.1.9.42... +3ms TuyAPI Socket connected. +7ms Connected to device! TuyAPI GET Payload: +4ms TuyAPI { TuyAPI gwId: 'eb0f4c330d33b7f24bmugy', TuyAPI devId: 'eb0f4c330d33b7f24bmugy', TuyAPI t: '1597896001', TuyAPI dps: {}, TuyAPI uid: 'eb0f4c330d33b7f24bmugy' TuyAPI } +0ms TuyAPI Received data: 000055aa000000010000000a0000002c0000000145b2ed8cecc9fb8e0dc3b1346ad0cc0200f8a330d1e17874c639501ff852b7e55262f6820000aa55 +14ms Error! json obj data unvalid TuyAPI Parsed: +2ms TuyAPI { TuyAPI payload: { TuyAPI dps: { TuyAPI '1': null, TuyAPI '2': null, TuyAPI '3': null, TuyAPI '101': null, TuyAPI '102': null, TuyAPI '103': null TuyAPI } TuyAPI }, TuyAPI leftover: false, TuyAPI commandByte: 10, TuyAPI sequenceN: 1 TuyAPI } +0ms Data from device: { dps: { '1': null, '2': null, '3': null, '101': null, '102': null, '103': null } } TuyAPI Disconnect +6s TuyAPI Socket closed: 10.1.9.42 +1ms

codetheweb commented 4 years ago

@tasict it looks like your code only attempts to get data once, what happens if you try a second time after the first fetch fails?

tasict commented 4 years ago

@codetheweb it looks the same.

TuyAPI Finding missing IP undefined or ID eb0f4c330d33b7f24bmugy +0ms TuyAPI Received UDP message. +683ms TuyAPI UDP data: +4ms TuyAPI { TuyAPI payload: { TuyAPI ip: '10.1.9.18', TuyAPI gwId: 'eb89a604dedb9d87e0rvig', TuyAPI active: 2, TuyAPI ablilty: 0, TuyAPI encrypt: true, TuyAPI productKey: 'nqvhejakb112obkn', TuyAPI version: '3.3' TuyAPI }, TuyAPI leftover: false, TuyAPI commandByte: 19, TuyAPI sequenceN: 0 TuyAPI } +1ms TuyAPI Received UDP message. +3s TuyAPI UDP data: +1ms TuyAPI { TuyAPI payload: { TuyAPI ip: '10.1.9.42', TuyAPI gwId: 'eb0f4c330d33b7f24bmugy', TuyAPI active: 2, TuyAPI ablilty: 0, TuyAPI encrypt: true, TuyAPI productKey: 'keyfa7hya4gfa7g9', TuyAPI version: '3.3' TuyAPI }, TuyAPI leftover: false, TuyAPI commandByte: 19, TuyAPI sequenceN: 0 TuyAPI } +0ms TuyAPI Connecting to 10.1.9.42... +3ms TuyAPI Socket connected. +12ms Connected to device! TuyAPI GET Payload: +2ms TuyAPI { TuyAPI gwId: 'eb0f4c330d33b7f24bmugy', TuyAPI devId: 'eb0f4c330d33b7f24bmugy', TuyAPI t: '1598254769', TuyAPI dps: {}, TuyAPI uid: 'eb0f4c330d33b7f24bmugy' TuyAPI } +1ms TuyAPI GET Payload: +4ms TuyAPI { TuyAPI gwId: 'eb0f4c330d33b7f24bmugy', TuyAPI devId: 'eb0f4c330d33b7f24bmugy', TuyAPI t: '1598254769', TuyAPI dps: {}, TuyAPI uid: 'eb0f4c330d33b7f24bmugy' TuyAPI } +1ms TuyAPI Received data: 000055aa000000010000000a0000002c0000000145b2ed8cecc9fb8e0dc3b1346ad0cc0200f8a330d1e17874c639501ff852b7e55262f6820000aa55 +9ms Error! json obj data unvalid TuyAPI Parsed: +1ms TuyAPI { TuyAPI payload: { TuyAPI dps: { TuyAPI '1': null, TuyAPI '2': null, TuyAPI '3': null, TuyAPI '101': null, TuyAPI '102': null, TuyAPI '103': null TuyAPI } TuyAPI }, TuyAPI leftover: false, TuyAPI commandByte: 10, TuyAPI sequenceN: 1 TuyAPI } +0ms Data from device: { dps: { '1': null, '2': null, '3': null, '101': null, '102': null, '103': null } } TuyAPI Received data: 000055aa000000020000000a0000002c0000000145b2ed8cecc9fb8e0dc3b1346ad0cc0200f8a330d1e17874c639501ff852b7e56da988170000aa55 +7ms Error! json obj data unvalid TuyAPI Parsed: +1ms TuyAPI { TuyAPI payload: { TuyAPI dps: { TuyAPI '1': null, TuyAPI '2': null, TuyAPI '3': null, TuyAPI '101': null, TuyAPI '102': null, TuyAPI '103': null TuyAPI } TuyAPI }, TuyAPI leftover: false, TuyAPI commandByte: 10, TuyAPI sequenceN: 2 TuyAPI } +0ms Data from device: { dps: { '1': null, '2': null, '3': null, '101': null, '102': null, '103': null } } TuyAPI Disconnect +6s TuyAPI Socket closed: 10.1.9.42 +1ms

codetheweb commented 4 years ago

It's probably an issue with TuyAPI then @tasict.

Could you play around with the payload it's sending on lines 94 - 99 of index.js and see if removing parameters does anything? Try removing the dps parameter to start with.

tasict commented 4 years ago

*only remove dps* TuyAPI IP and ID are already both resolved. +0ms TuyAPI Connecting to 10.1.9.42... +7ms TuyAPI Socket connected. +6ms Connected to device! TuyAPI GET Payload: +3ms TuyAPI { TuyAPI gwId: 'eb0f4c330d33b7f24bmugy', TuyAPI devId: 'eb0f4c330d33b7f24bmugy', TuyAPI t: '1598579310', TuyAPI uid: 'eb0f4c330d33b7f24bmugy' TuyAPI } +0ms TuyAPI GET Payload: +9ms TuyAPI { TuyAPI gwId: 'eb0f4c330d33b7f24bmugy', TuyAPI devId: 'eb0f4c330d33b7f24bmugy', TuyAPI t: '1598579310', TuyAPI uid: 'eb0f4c330d33b7f24bmugy' TuyAPI } +0ms TuyAPI Received data: 000055aa000000010000000a0000002c000000010c233b4c2abd2e1f0e3a93e58522ebef6fce531ff98e92602e53a886fad6ea5b9cab5f1b0000aa55 +24ms TuyAPI Parsed: +3ms TuyAPI { TuyAPI payload: 'parse data error', TuyAPI leftover: false, TuyAPI commandByte: 10, TuyAPI sequenceN: 1 TuyAPI } +1ms Data from device: parse data error parse data error TuyAPI Received data: 000055aa000000020000000a0000002c000000010c233b4c2abd2e1f0e3a93e58522ebef6fce531ff98e92602e53a886fad6ea5ba360218e0000aa55 +15ms TuyAPI Parsed: +1ms TuyAPI { TuyAPI payload: 'parse data error', TuyAPI leftover: false, TuyAPI commandByte: 10, TuyAPI sequenceN: 2 TuyAPI } +0ms Data from device: parse data error TuyAPI Disconnect +10s TuyAPI Socket closed: 10.1.9.42 +2ms Remove all TuyAPI IP and ID are already both resolved. +0ms TuyAPI Connecting to 10.1.9.42... +6ms TuyAPI Socket connected. +14ms Connected to device! TuyAPI GET Payload: +3ms TuyAPI {} +0ms TuyAPI GET Payload: +7ms TuyAPI {} +0ms TuyAPI Received data: 000055aa000000010000000a0000002c000000010c233b4c2abd2e1f0e3a93e58522ebef6fce531ff98e92602e53a886fad6ea5b9cab5f1b0000aa55 +20ms TuyAPI Parsed: +3ms TuyAPI { TuyAPI payload: 'parse data error', TuyAPI leftover: false, TuyAPI commandByte: 10, TuyAPI sequenceN: 1 TuyAPI } +1ms Data from device: parse data error parse data error TuyAPI Received data: 000055aa000000020000000a0000002c000000010c233b4c2abd2e1f0e3a93e58522ebef6fce531ff98e92602e53a886fad6ea5ba360218e0000aa55 +23ms TuyAPI Parsed: +1ms TuyAPI { TuyAPI payload: 'parse data error', TuyAPI leftover: false, TuyAPI commandByte: 10, TuyAPI sequenceN: 2 TuyAPI } +0ms

tasict commented 4 years ago

I found something interesting that the same code test with tuya wire zigbee hub and result is different.

/homebridge # DEBUG=* node gateway.js
TuyAPI IP and ID are already both resolved. +0ms TuyAPI Connecting to 10.1.9.28... +6ms TuyAPI Socket connected. +5ms Connected to device! TuyAPI GET Payload: +2ms TuyAPI { TuyAPI gwId: 'ebdd7925c7531bf4falakg', TuyAPI devId: 'ebd2e8be35c5cc00d3nkp9', TuyAPI t: '1599384049', TuyAPI dps: {}, TuyAPI uid: 'ebd2e8be35c5cc00d3nkp9' TuyAPI } +1ms TuyAPI Received data: 000055aa000000010000000a0000001c00000001a4fd046f973e79562bee4dfbd672a9ca03a3f1f40000aa55 +15ms TuyAPI Parsed: +2ms TuyAPI { TuyAPI payload: 'devid not found', TuyAPI leftover: false, TuyAPI commandByte: 10, TuyAPI sequenceN: 1 TuyAPI } +1ms Data from device: devid not found TuyAPI Pinging 10.1.9.28 +10s TuyAPI Received data: 000055aa00000000000000090000000c00000000b051ab030000aa55 +5ms TuyAPI Parsed: +0ms TuyAPI { payload: false, leftover: false, commandByte: 9, sequenceN: 0 } +1ms TuyAPI Pong from 10.1.9.28 +0ms TuyAPI Disconnect +5s TuyAPI Socket closed: 10.1.9.28 +2ms Disconnected from device.

kart-able commented 4 years ago

I meet the same problem than Nurfle who originated this thread. But I don't understand your reply "It was decided here (see linked issue thread) that for some applications returning a null payload for that specific error is acceptable." Acceptable?

codetheweb commented 4 years ago

@kart-able I can see both sides of the argument. Maybe a flag to control this behavior would make more sense?

kart-able commented 4 years ago

I had some hope in a solution because, even if it says json obj data unvalid, the switch goes on. But it can't go off for some reason...

vinodsr commented 4 years ago

@codetheweb I am also facing the same "json obj data unvalid" issue. It occurs when i connect for the first time and for every GET request. For SET request i am not getting any error. I am getting similar outputs of @tasict .

Can we find a solution for this. I can provide any debug logs if you need.

Cheers 👍

tsightler commented 4 years ago

I'm struggling with this as well. Newer devices seem to use a new command type (DP_QUERY_NEW perhaps?) and so I can't seem to get on demand DPS updates from these devices using get(). I can set(), and I can subscribe to data updates and get data if changes are made to the device, but get() always returns invalid data which means I can't get initial device state. I'm trying to research this in more detail in some other projects that implement tuya protocol which don't seem to have this problem.

tsightler commented 4 years ago

OK, I think I've worked out how to deal with this for my code at least. After reading the mile long #246 thread, it seems that some devices don't return an invalid response and, after that, the proper data, however, some devices appear to never return valid data to a DP_QUERY. I see some libraries using a CONTROL_NEW command as a fallback, although I'm still not sure I fully understand it, but I couldn't work out any way to do that in TuyAPI without changing the code (I see no way to control the command type for get().

However, I did find that if I detect a null response to a get() that it's still possible get the device to send an update by using the send() command. It seems that if you issue a "send()" with a null payload to the DPS key you want to get(), you can cajole the device to at least send a data update with the value. So far this seems to work for me and the devices I have access to with this behavior. I just trap the invalid response, log it as invalid, issue a send() and wait for the data update. I have no idea if this works for all devices, but as I noted above, it seems OK for the ones I have.

If I manage to get more time perhaps I'll try to do some packet traces and then decode them and see if I can get more clarity on how the Tuya app itself deals with this, but I don't know if that time will come soon.

codetheweb commented 4 years ago

Thanks for looking into it @tsightler.

Maybe we could issue a SET command with a null payload by default to get the current state (assuming that works on all devices)?

tsightler commented 4 years ago

I have been wondering the same thing and have even considered doing that in my code, but I'm not 100% confident it works every time. I mean, it always appears to trigger at least a data update, but I don't always seem to get a response from the SET command itself. For my code this is OK because I trap all data updates to cache the DPS values, so the effective behavior is the same either way, but I need to dig a little more before I feel truly comfortable with this approach as the primary method.

tsightler commented 4 years ago

OK, maybe this was just a bug in my code as I had a place where I was attempting to parse the result of the SET as if it was a GET. Once I fixed that I can't find any cases where a SET does not return a valid result, so indeed.

I wish we had a response that was more clear instead of the weird response with all the null values. It took me quite a while to realize that TuyAPI itself was generating this response and not the device. Couldn't this response be pretty much anything? Like some JSON with "error:" and the actual response from the device. Sending random data that looks like it might have been a valid response just seems like a strange way to handle this case.

codetheweb commented 4 years ago

I wish we had a response that was more clear instead of the weird response with all the null values. It took me quite a while to realize that TuyAPI itself was generating this response and not the device. Couldn't this response be pretty much anything? Like some JSON with "error:" and the actual response from the device. Sending random data that looks like it might have been a valid response just seems like a strange way to handle this case.

Yeah, I agree. I'm going to change this behavior so it's an opt-in flag rather than doing it by default.

codetheweb commented 4 years ago

This behavior is now opt-in in v6.0.0.

tsightler commented 4 years ago

Thanks @codetheweb. Thinking about using SET instead of GET, I wonder if a better option would be to simply implement a fallback to using CONTROL_NEW instead. That seems to be the method that works for these devices. The CONTROL_NEW command seems to have a slightly different schema, basically no gwId: property, otherwise the same and change the command code. That seems to be what other implementations are doing such as:

https://redmine.telecom-bretagne.eu/projects/xaal/repository/entry/code/Python/branches/0.7/devices/protocols/Tuya/xaal/tuya/tuyaclient.py

Unless I'm reading that completely wrong (I'm quite worse at Python vs Javascript, which is perhaps a sad statement) it appears they just try DP_QUERY, if they detect the "json obj data unvalid" message they call fix_buggy_dp_query(), which uses CONTROL_NEW instead.

That's basically the same as I'm doing in my code, but I'm falling back to a SET with null, since TuyAPI abstacts the commands. I'll try to hack my local TuyAPI to use the CONTROL_NEW fallback and see if it works.

kueblc commented 4 years ago

Falling back to *_NEW is something I intended to implement but never quite got around to. You're on the right track.

codetheweb commented 4 years ago

@tsightler that xaal project looks pretty cool, surprised I haven't heard about it.

I'll try to hack my local TuyAPI to use the CONTROL_NEW fallback and see if it works.

👍

tsightler commented 4 years ago

My first attempt wasn't very successful. It seems that responses to CONTROL_NEW are received as command type STATUS, which appears to be the same as SEND function so I tried to implement this the same as SEND. Copied that function, modified it to send the CONTROL_NEW command in proper format, checked for a NULL response in the get() function and fall back to calling get_control_new(), and well, it seemed to work from a code perspective, but I never saw a STATUS from my device.

So I spent a bunch of time looking at this other implementations and trying to make sure that I was doing everything correctly. Couldn't see anything different, so decided to actually try some of those implementations on my device because of course I had only actually read the code, not actually tried any of them with this specific device that was the most problematic.

Well, guess what, those alternate implementations don't succeed on my device either. Tuyaface tries DP_QUERY, falls back to CONTROL_NEW, but still never gets a response device, so what that tells me is that CONTROL_NEW isn't supported on some devices either. I had seen notes in some implementations that CONTROL_NEW didn't always return the full data, but I hadn't seen any reports that there were cases where it didn't work at all. Oh well, back to the drawing board.

So now I'm starting to wonder if the fallback should indeed just be to SET null since, at least for the cases where a DPS key is specifically requested, this is easy to implement. For the "schema" case, it's a bigger issue and I'm not sure how that would work.

I guess maybe I need to do some packet captures from my app and decode them to see how the app is doing this, but there's some part of me that expects it might just cache the DPS values in the app and never needs to query the schema. Is there any already existing data on other ways to get the schema from newer Tuya devices?

codetheweb commented 4 years ago

I guess maybe I need to do some packet captures from my app and decode them to see how the app is doing this, but there's some part of me that expects it might just cache the DPS values in the app and never needs to query the schema.

Also possible it controls it through API calls to Tuya's cloud. The app seems to quite aggressively fall back on that if there's any type of problem communicating with a device locally.

Is there any already existing data on other ways to get the schema from newer Tuya devices?

Not that I'm aware of.

tsightler commented 4 years ago

Also possible it controls it through API calls to Tuya's cloud. The app seems to quite aggressively fall back on that if there's any type of problem communicating with a device locally.

Indeed, but I have all my automation devices on a separate VLAN/Wifi network so I just put my phone on that and firewall it from the internet, which leaves the app no choice, it's local control or nothing. That being said, I haven't tried that with this specific device yet.

tsightler commented 4 years ago

So, after hitting the wall with CONTROL_NEW for the time being, I decided the best short term option was to implement a fallback to SET for any case where GET returns null. This wouldn't help with the get schema case, but at least GET calls for specific DPS values would work in all cases. I wrote a quick check for the "json obj data unvalid" message and had it return "Device does not support schema command" for schema and otherwise try the SET null method. This worked great, fall back was transparent to the API consumer, and issuing a schema command returned a message that was at least useful to understand what was happening.

However, I did hit one pretty decent snag. The summary version is, if you issue a SET null to a DPS that does not exist on the device, the device does not send a reply at all, so the set call will just wait forever for a STATUS command that never comes. To me this seems like a bug in SET as it should almost certainly timeout I'd guess, after a couple of seconds at most. I see a _responseTimout parameter, but don't see it used anywhere. Was this just something that was never implemented?

tsightler commented 4 years ago

OK, implemented a simple timeout on the send function, seems to work. I'll submit an PR and the powers that be can review.

One thing I don't understand, in the SET command there's a flag "_waitingForSetToResolve". This starts off false, but the first time you issue a SET it's set to true. Ok, makes sense, we've issued a SET statement and we need to wait for a STATUS response with the response data so this flag is indicating that any CONTROL commands should be processed as a response to the SET. I see the flag used in the packetHandler as part of the conditions for handling the resolve. However, once this flag is set to true, it's never set false again, so what's the point really? It feels like the check for _setResolver being a function is probably good enough. Is this just some artifact of the past or am I completely misinterpreting this code? I don't really see how though, basically, it's set to true for the device and never reset back to false at any point so my lean is to just remove it completely.

codetheweb commented 4 years ago

I see a _responseTimout parameter, but don't see it used anywhere. Was this just something that was never implemented?

According to git blame the logic for that was removed in https://github.com/codetheweb/tuyapi/pull/143; although I couldn't tell you why. I blame past me for not writing a more descriptive commit message. 😛 It does probably make sense to add it back, but that should be a separate PR. Should have looked at your PR first, sorry. Should be fine.

It feels like the check for _setResolver being a function is probably good enough. Is this just some artifact of the past or am I completely misinterpreting this code? I don't really see how though, basically, it's set to true for the device and never reset back to false at any point so my lean is to just remove it completely.

You're correct, this is no longer used. I think its purpose was to prop up the spaghetti code that was/is the synchronous + event based functions.

codetheweb commented 4 years ago

Thanks to some awesome work by @tsightler in #363, behavior for this should now be slightly improved.

As long as you specify the DPS index when calling get() (omit it to default to 1 or get({dps: 5})), null payloads should no longer be returned in v6.0.1. I'm tentatively closing this, if anyone continues to have issues please let me know and I'll reopen it.