@veonua, Since this API relies on low-level resources, for consistency, you should be handling this process across device lifecycles.
In addition, I recommend you to try invoking resources through pcall / assert functions. In most cases, you’ll get a better reference of the error raised than directly invoking the function, e.g.:
local success, msg = pcall(driver.try_create_device, driver, device_table)
What I suggest is to handle this allocation procedure at the added lifecycle, since it will be called as a confirmation that the device was created successfully.
local function sum(x, y)
assert(x, 'missing param: x')
assert(y, 'missing param: y')
return x + y
end
local success, res = pcall(sum, 10)
-- This will trigger second assert
-- at sum and will return:
-- success: false
-- res: 'missing param: y'
To have a nice reference of how much of the Lua standard libraries are supported, I’d recommend you to check the Global Environment section of the Edge Reference.
This seems unusual. Can you share with me a scheme of the discovery flow you’re trying to achieve, so I can replicate the issue is a similar environment?
it’s a classical race condition. The discovery flow is sending a broadcast UDP package and then HTTP GET to all the devices that responded.
driver: who is online (UDP)
device1: me
device2: me
driver to device1: what is your token (HTTP)
driver to device2: what is your token (HTTP)
device1: token1
driver: ok try_create_device (device1)
device2: token2
driver: ok try_create_device (device2)
driver: added_handler(device1), save a token to the persistent storage !!! oops the last token is token2
driver: added_handler(device2), save a token... ok
@veonua I noticed that your code is influenced by the SSDP implementation in the lightbulb-lan-esp8266 sample, which is meant to discover only one device per scanning, or is that you forgot to commit the changes to actually follow the sequence you shared above?
Also, regarding the sequence you shared, I’d recommend you rely on the added_handler to request and allocate the token in the persistent storage, for example:
-- discovery flow for device1
driver: who is online (UDP)
device1: me
-- create it if metadata is
-- supported by driver
driver: ok try_create_device (device1)
-- added lifecycle handler invoked
driver: added_handler(device1), what is your token (HTTP to DNI)
device1: token1
driver: device:set_field(device.id) = token1
-- added lifecycle ends
it’s naive to think that driver developers are doing much better. so
-- discovery flow for device1
driver: who is online (UDP)
device1: me
-- create it if metadata is
-- supported by driver
driver: ok try_create_device (device1)
-- added lifecycle handler invoked
driver: added_handler(device1), what is your token (HTTP to DNI)
device1: {failed/not ready to response}
so we ended up with a device in the ST UI without token and all future interactions with the device will fail. So it makes sense to add the device only when it is ready.
You know… this made me realize that the lightbulb-lan-esp8266 isn’t that different from what you’re trying to achieve, so, I believe that the inconsistency you facing is because the key you’re using to store the token isn’t consistent enough, so:
Are you using the UUID from the USN header to store it?
It is for sure unique across the devices (even if they’re the same model)
Nevertheless, I’ll test this out right away, because maybe there’s an issue with the persistent storage that we’re not catching.
@veonua I’ve been tweaking a little bit the lightbulb-lan-esp8266 Sample to actually simulate the token request inside the fetch_device_info function, so, the token was included in the XML response (only for testing purposes) and the allocation proceeded there as well.
To allocate the token properly, I used the driver.datastorage as following:
driver: who is online (UDP - SSDP MSEARCH)
device1: me (SSDP RESPONSE)
driver: what is your token (HTTP - invoke fetch_device_info)
device1: XML response (include token)
driver: allocate token:
local key = device.location -- or any device-specific and unique value
driver.datastorage[key] = token
driver.datastorage:save()
driver: ok try_create_device (device1)
device2: me
... and so on
And, at the end the tokens were allocated this way:
This make sense for something like the token, but what about the IP address. We know the IP address at the time the device is discovered. How can it be persisted for later use by the device?
This is what I have done for now since these devices have static IPs. However, I suspect the instance name may be better if IPs can change. If the IP gets reused for other devices, they will look like duplicates during the scan. I guess you would then advise an IP lookup in an init/refresh handler?