control4 / docs-driverworks

Home Page for the Driverworks SDK
Other
41 stars 17 forks source link

AddDynamicBinding does not connect binding #8

Closed DDevine closed 3 years ago

DDevine commented 3 years ago

I am creating a drivers that have an IDC Controller and an IDC Client. I do not know how many controllers and how many clients there will be ahead of time, so I am trying to use AddDynamicBinding and AddDevice to allow a driver to automatically commission the system.

The IDC Controller side of things seems to work just fine. I can create the binding anywhere ( eg. inside OnDriverLateInit) and it seems to work in any scenario.

Example IDC Controller.

-- This works fine
function OnDriverLateInit(driverInitType)
    C4:AddDynamicBinding(1, "CONTROL", true, "my IDC connected devices", "MY_IDC", false, true)
end

However, in an IDC Client I must run AddDynamicBinding outside of all functions. This is mentioned in the documentation https://control4.github.io/docs-driverworks-api/#adddynamicbinding and I can confirm this seems to be true when it comes to the IDC Client. When I do this with a static/known binding ID, everything works fine. But a static ID will not work for my use-case.

Because I am creating instances of the IDC Client using AddDevice I do not know the bindingID at the time the driver is loaded. When I try sending the binding ID directly to the device (with SendToDevice on a static binding) and then running AddDynamicBinding the new binding is added but is not connected to the to the IDC Controller. The "Connected To" / "Connections" fields in Composer Pro are empty and I cannot send/receive over the IDC.

Example IDC Client.

-- This works, just in the root of the driver with the known binding ID of 1. However, at this level I don't
-- have a way to know the dynamic binding id.
 C4:AddDynamicBinding(1, "CONTROL", true, "my IDC connection", "MY_IDC", false, true)

function OnDriverLateInit(driverInitType)
    -- This does not work, even with a static binding ID.
    C4:AddDynamicBinding(1, "CONTROL", true, "my IDC connection", "MY_IDC", false, true)
end

So it seems like you can create a dynamic binding anywhere, but if you want to connect to that dynamic binding you must do it when the driver is loaded.

Can someone confirm this behaviour? Is this a bug? Any work-arounds? Is there a way to create the connection on the newly created binding?

DDevine commented 3 years ago

A work-around I am considering is something like the following:

  1. In the IDC Client create dynamic bindings for an entire range of possible bindings (eg. 1 -> 64) on driver load.
  2. Ping the IDC Controller via each binding, and then when the correct one responds positively - that's the one to use.
  3. Remove all the non-relevant dynamic bindings so that ReceivedFromProxy is not fired from irrelevant controllers.
WWalshC4 commented 3 years ago

The architecture for Control4 bindings is not designed for many-to-many connections. A single provider can be connected to many consumers, and vice versa.

When you are designing for multiple controllers and clients, is a client bound to any one specific controller, or is it to be bound to all of them?

If a client is connected to just one controller, then you should be able to use the existing architecture, albeit with autobind disabled and instead using C4:Bind () dynamically instead in the response to AddDevice. I have actually already developed this for recent drivers and will update the sample drivers today to reflect this: watch this space!

If it's a true full-duplex many-to-many then there isn't a "simple" solution that arbitrarily scales without abandoning connections altogether and doing dynamic runtime discovery (using C4:GetDevicesByC4iName ()) of all of the client/controller devices in the project and doing direct C4:SendToDevice () commands to do the transit.

DDevine commented 3 years ago

I'm doing one-to-many (from a single controller to multiple clients - A client must only have a single controller) but to work around the initalisation issue each client must be initialised with all the possible IDC connections. Dynamically adding them after initalisation doesn't seem to work. I need multiple IDC controllers to be present in the project, but the IDC clients need to be specific to that controller.

Since I am passing a message with the proxy id in my comissioning message this is a simple way to know which connection to keep and the rest can be removed. No pinging or listing out drivers by name is needed. I have written the code but I didn't get a chance to test it today - I expect it to work though.

This might be a silly question but I have been unable to find the answer - why does autobind need to be disabled? I assumed that it should do nothing.

I did consider using C4:Bind() but it seemed overkill.

WWalshC4 commented 3 years ago

If you use autobind, then any added clients will autobind to the first controller in the project, regardless of which one added them.

I've updated the IDC code example (see the commit referenced above) so that autobind is removed, and the controller device adding the clients does the bind after the add is successful (and then waits a little before sending the init information to the client).

This is how it is deployed (and tested) in multiple released drivers in production now, and is how we will do this in similar scenarios going forward.

DDevine commented 3 years ago

I noticed now that isProvider is true in the example code for the IDC Client example in the first post . This is wrong but I don't think I made this mistake in my actual code. So now I understand why you must have thought I was doing many-to-many.

I'm a lot more confident with using C4:Bind as a solution with that example, thanks!

WWalshC4 commented 3 years ago

To answer the first question you asked though: yes, you should recreate any known bindings on driver startup before any OnDriverInit or OnDriverLateInit callbacks (i.e. in the main scope of the driver). The bindings must be recreated at this point in order for the ConnectionManager component of Director to reconnect bindings at startup that were previously connected. If they don't exist, they won't be rebound once the driver loads.

Where we have done this in the past, we have used PersistData (which is available immediately on driver main scope) to store the details for dynamic connections that should be recreated, where there is a non-standard number that should be created.