foxxyz / loupedeck

Node.js API for Loupedeck Controllers
MIT License
87 stars 10 forks source link

Connection gets stuck without a sleep after connect #17

Closed Julusian closed 1 year ago

Julusian commented 1 year ago

Im getting some weird behaviour, that I'm not sure how to go about debugging. It only

My code is as follows:

const { LoupedeckDevice } = require('.')

const dev = new LoupedeckDevice()

dev.on('connect', () => {
    console.log('connected')

    for (let i = 0; i < 8; i++) {
        dev.setButtonColor({
            id: i === 0 ? 'circle' : ((i + '')),
            color: '#000000',
        })
    }

    // Clear the center screen
    dev.drawBuffer({
        id: 'center',
        width: 360,
        height: 270,
        x: 0,
        y: 0,
        buffer: Buffer.alloc(360 * 270 * 3),
    }).then(() => console.log('drawn clear'))
})

And it works, but the drawBuffer call doesnt resolve until something else happens. I requires me press something on the device, for the response to be received and it will continue. Also of note, is that the circle button did not change colour correctly, but the rest did. It stays the wrong colour even the pending draws finish.

Even more interesting, is that if I give it a few ms delay with setTimeout, then it appears to work reliably. So this seems like some race condition, but I can't see anything that would indicate as to why it needs that delay, or if there is anything the library could/should be waiting on instead of an arbitrary sleep.

Any ideas?

Julusian commented 1 year ago

Oh, so it does also get stuck even with that sleep sometimes...

Looking at wireshark from the official application, there is so much going on before it starts trying to draw anywhing. Perhaps some of that is necessary to help it start correctly?

Julusian commented 1 year ago

I think I 'solved' this by making sure to only have one packet in flight at a time. Or I can solve it by never waiting for any responses, and simply relying on order of commands being pushed into the serial buffer to ensure things happen as I expect. Doing this makes the drawing much smoother than limiting packets in flight. I havent checked how that behaves when doing things like getting the serialnumber

Looking at the initial 'handshake' done by the loupedeck software, and wireshark shows a very sequential order of operations, being call and response. I dont know if this is necessary, but it definitely helped.

But this isnt possible without modifications, because things like setting button color doesnt return a promise, so I cant wait for it. Is there a reason that only some send messages are tracked?


An unrelated discovery is I can see a command 0x2302 being sent in wireshark. with the payload 07 00 28 00 / 08 ff 24 14 / 09 00 00 00 / 0a 00 00 00 / 0b 00 00 00 / 0c 00 00 00 / 0d 1e 00 50 / 0e 1e 00 50. (slashes added based on a hunch) That looked suspiciously like setting the button colours in bulk. A quick test of sending 0x0b02 with 0x08, 0x00, 0xff, 0x00, 0x0a, 0xff, 0x00, 0xff confirms that I can change two buttons at once.

Note that 0b matches the length of this packet (8 payload, 2 'header', 1 responseId).

So I am pretty sure that the HEADERS map while it works, is an over simplification. The first byte of them is the length of the packet, with the next byte being the 'commandId'. This is visible if you look at VERSION_OUT and VERSION_IN, as they both end in 07.

foxxyz commented 1 year ago

Thank you for doing this additional investigation!

But this isnt possible without modifications, because things like setting button color doesnt return a promise, so I cant wait for it. Is there a reason that only some send messages are tracked?

I believe that's what the official software does, but it doesn't mean we can't. In fact, if the device supports it, I would be a fan of tracking all requests so they can be awaited. I will investigate later this week if the device is fine with that.

So I am pretty sure that the HEADERS map while it works, is an over simplification. The first byte of them is the length of the packet, with the next byte being the 'commandId'. This is visible if you look at VERSION_OUT and VERSION_IN, as they both end in 07.

Yes, you're right. I've noticed this pattern for many of the commands, but it wasn't always clear how to distinguish a request from a response. However, knowing that we generally are only and always dealing with responses when we process incoming data, there's definitely room for simplifying the way the message types are sent/received. I'll look into that as well.

Julusian commented 1 year ago

I believe that's what the official software does, but it doesn't mean we can't. In fact, if the device supports it, I would be a fan of tracking all requests so they can be awaited. I will investigate later this week if the device is fine with that.

In my testing it looks like the device does ack everything. So I am doing this now Some complexity arises because of needing to ensure only one thing is in flight at a time, which requires some queue to be added for commands. Which sounds simple, but drawing a buffer and refreshing wants to be treated as one operation in this queue.


As part of investigating this I did start implementing it and quickly found that the scale of changes I was making resulted in a mostly rewritten library. I did consider doing some of it in the application instead, but I need to use this in two similar but different applications, so that would have been a lot of duplicated code for me.

So I have decided to embrace this and publish it as an alternate library. I am not bothered by the potential extra work here, I was originally expecting to have to do a lot of reverse engineering to get to this point, and in my experience libraries like this tend to only need changes to keep dependencies updates and to support newer models. This also lets me do things my way (typescript, class per model). I've already done some untested implementations of the razer one, and live-s based on https://github.com/CommandPost/CommandPost/blob/develop/src/extensions/hs/loupedeck/init.lua

Thank you for all your help and responding so quickly to me on each issue I have opened, and for the good discussions.

foxxyz commented 1 year ago

Thank you too for the great discussion. You're always welcome to provide extra feedback, discussions or collaborations.

Hopefully one of these days I can get my hand on one of the other Loupedeck devices and do more compatibility and testing.