codetheweb / tuyapi

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

IP discovery binds to static port #114

Closed petetastic closed 5 years ago

petetastic commented 5 years ago

I used this API to control my Tuya sockets with a Raspberry Pi and was delighted to find that the control is instantaneous—as fast as a light switch—but I can't seem to do this for more than one light. Your doc states that we can't have more than one TCP connection per device but is there a way to maintain persistent connections to multiple devices? If not, we must connect each time, and the result is that it takes ~3-4 secs to turn a light off. How does the Tuya app do it?

Thanks!

codetheweb commented 5 years ago

As long as you're creating multiple TuyAPI objects, one for each device, all with persistentConnection enabled, it should work the same as if you were controlling one device as long as your network doesn't have something weird going on. What does your code look like?

petetastic commented 5 years ago

I am creating multiple objects, but anything after the first one fails, no matter what, with an error:

UnhandledPromiseRejectionWarning: Error: bind EADDRINUSE 0.0.0.0:6666

Here's a stripped down version of my code. So I created my own device class like this:

module.exports = class Device {
    constructor(name, tuyaData) {
        this.tuyaDevice = new TuyAPI(tuyaData);
        this.tuyaDevice.resolveId()
        .then(()=>{this.tuyaDevice.connect();})
    }
}

...and I create multiple devices like this:

for (var i=0; i<data.devices.length; i++) {
    devices[data.devices[i].name] = (new Device(
        data.devices[i].name,
        data.devices[i].tuyaData
    ));
}

Am I missing something simple?

Thanks

codetheweb commented 5 years ago

Oh, the issue is that the discovery process for finding IPs binds to the hard-coded port 6666. I need to change that so it's randomized.

Until I do so, try passing in the IP to your constructor, take out resolveId(), and see if it works.

EDIT: Looks like more investigation is needed. I'm not a network expert, but it appears Tuya devices only broadcast to 6666, so I'm not sure if that can be changed.

I would recemend making an array of Promises, then using something like described in this post to resolve them sequentially.

petetastic commented 5 years ago

Thanks. Sorry if if I'm misunderstanding, but what would this array of promises contain exactly?

codetheweb commented 5 years ago

Just the resolveId() functions.

It's not the only way to do it, but it's what I thought of first. 🤷‍♂️

petetastic commented 5 years ago

Nope, still doesn't work if I connect to them synchronously. I tried just doing two lights, one after the other like this:

devices["New York"].connect()
.then(() => {
    console.log("New York is Connected");
    devices["London"].connect()
    .then(() => {
        console.log("London is Connected");
    })
})

where connect is a class function that looks like:

async connect()
{
    try {
        this.tuyaDevice.resolveId()
        .then(()=>{
            this.tuyaDevice.connect();
        })
    } catch (error) {
        console.log("error",error);
    }
}

but I still get EADDRINUSE.

Hmmm. The app must have a way of connecting to them all at once. Maybe it connects on port 6666 and then cunningly switches port somehow as a sneaky way to give them all guaranteed different port numbers?

codetheweb commented 5 years ago

TuyAPI should release port 6666 after resolving the ID.

I think your promises are resolving in parallel, you can check this by adding a log statement to your connect() function:

async connect()
{
    try {
        this.tuyaDevice.resolveId()
        .then(()=>{
            this.tuyaDevice.connect();
        })
    } catch (error) {
        console.log("error",error);
    }
    console.log('Resolved.');
}

When run, your script should then have a delay between each printed 'Resolved'.

I'm guessing the changes you'll have to make are:

async connect()
{
    try {
        await this.tuyaDevice.resolveId();
        this.tuyaDevice.connect();
    } catch (error) {
        console.log("error",error);
    }
}
neojski commented 5 years ago

I have the same issue and #116 fixes it.

codetheweb commented 5 years ago

@petetastic I merged @neojski's PR in v3.1.5 if you want to test it now.