Try_create_device: though device may be "added", not always "init"ed

Thank you for providing this sample, I will share it with the team so they can replicate the issue and give us feedback.

1 Like

Great sample @rossetyler :+1:

Hi, @rossetyler. Sorry for the delay, the engineering team mentioned the following about your sample:

  • In the driver you aren’t using any sockets at all, so there are no calls to receive but you use both cosock.socket.sleep and cosock.socket.select
  • In this file it is not clear if one of those functions is getting called in the added lifecycle and that is blocking init.
  • You should check if the creation of the devices was successful using assert(driver.try_create_device(...)) or local success, err = driver.try_create_device(...). This way, you can see if there were immediate errors.
    • Especially because, in your sample, you’re calling try_create_device in a hot loop 100 times and this might be reaching the rate limit of devices created in a given timespan.

In this case, this number of devices was an example, or do you actually have drivers that create 100 devices? If so, could you share the details of that use case?

  • Any time something could block the current thread, you can avoid that entirely by calling cosock.spawn. So, something like cosock.spawn(function() local value, err = socket:receive() end) would allow the use of the device thread for any other device events. For example:

This sample shows how the device thread can be blocked


-- This driver will lock up any device thread by
-- yielding the thread into a deadlock because
-- init cannot be processed until added is complete
-- but added is relying on init to complete

local tx, rx = cosock.channel.new()
local function added(driver, device)
  -- wait for init
  local init, err = rx:receive()
end

local function init(driver, device)
  -- send init
  tx:send(device.id)
end

local driver = Driver("...", {
  lifecycle_handlers = {
    added = added,
    init = init,
  }
})

Considering the previous implementation, this is how we can avoid the blockage using cosock.spawn

local tx, rx = cosock.channel.new()
local function added(driver, device)
  cosock.spawn(function()
    local init, err = rx:receive()
    
  end)
  -- wait for init
end

local function init(driver, device)
  -- send init
  tx:send(device.id)
end

local driver = Driver("...", {
  lifecycle_handlers = {
    added = added,
    init = init,
  }
})

cc @blueyetisoftware

I have said most of this before …

There are two drivers that I have discussed

  • Legrand RFLC
  • Bug

Legrand RFLC tries to create ~35 devices all at once: one for the LC7001 lighting controller (bridge) and the rest for the lights that it controls.

Bug was created in an attempt to simply illustrate the issue.

Neither of these has an added lifecycle handler so neither could block my init lifecycle handler.
Yet my Legrand RFLC logs added events and some are not followed by inits.
Failed try_create_device calls and rate limiting do not explain this.

Legrand RFLC does use cosock sockets (see lc7001 module).
Bug does not use any cosock features nor does it explicitly block anywhere (see code).

An LC7001 lighting controller can control up to 100 lights (that is where 100 comes from). Mine, currently, only has ~34.

What is the rate limit on try_create_device calls?

Where else are rate limits going to bite me?
I am concerned about the refresh capability on my parent LC7001 controller that refreshes each of its children.
For each, it will make

  • device:online or device:offline

  • device:emit_event switch on or off

  • device:emit_event switch level
    So, refreshing ~33 dimmers will make ~100 such calls all at once.
    If this is a problem, how can I deal with it?

@nayelyz

Reviving this thread…I found nearly the same issue with driver switches. During creation, many developers have been working around this by slowing down the device creation. During a driver switch, this can’t be done. When switching my driver, I can see the added and driverSwitched called for all of the devices, but many of them miss their init calls. The hub has to be restarted to force the init to be called. The added and driverSwitched don’t do anything other than call device:set_field() so I don’t think they are blocking. Some of the fields are persisted.

Hi @rossetyler - I realize this thread is ancient, but I have a client with an LC7001 who spent many thousands investing in Legrand’s Adorne switches and would like to get them doing more automated things vs just used in the Legrand app. Is this Edge driver available for the public? Would be a huge problem solver for me. Hope you’re still around the forum!

That sounds like me.

Yes, but perhaps not in the form you want. I have not published a public channel that you can subscribe to. I no longer use my own private channel for such as I have bit the bullet and migrated away from Legrand Adorne (know anyone that wants a deal on thousands of dollars of such equipment?)

The code and build instructions for this SmartThings Edge Driver are public.

1 Like