Sending data between drivers

I'm quite familiar with sending data between parent/child drivers, child drivers and parent apps, etc. What I want to do is send events from one driver to another.

sendEvent() has a method at the app level that lets me call it like sendEvent(dni, [map event]).

This lets me raise events on any device from an app that knows the DNI. That's well and good and all, but I would like to see this exact same thing available at the driver level.

Right now I have some drivers set up with a super convoluted system where the driver raises a location event, there's a 'helper app' that subscribes to location events, then uses sendEvent(dni, [map event]) to send events to NON-child devices.

Related, I would like to have subscribe() available at the driver level as well.

Ideally I would be able to do something like this in one driver:

On the receiving side do something like subscribe(DeviceEvent event, String attributeName, handlerMethod, Map options = null) and NOT have to use Location events. These spam the Location events tab in logs... raising a bunch of them causes a lot of clutter. Having something other than Location would be good.

Then on the sending side, I could just do something like sendDeviceEvent(DeviceEvent event, 'myEvent', [data: [myData: 'stuffHere']])

Basically I want a way to raise a hub-wide event in one driver and subscribe to it in another. Passing everything thought parent-child app-driver works fine for parent app with child drivers, but Hubitat would greatly benefit from being able to have drivers subscribe to "driver event streams" and not need parent apps just to act like intermediary. Requiring a parent app means you cant' add driver-to-driver communication to pre-existing drivers. They would need to be deleted and re-created as children under the app since there's also no form of 'adopt' available to a parent app.

Why do I want this feature? First, my Sonos Advanced app gets a bunch of state on the "primary/coordinator" speaker device that needs sent to the "follower" devices. Right now I'm using a pretty silly system of @Field objects to get DeviceWrapper handles in one driver so I can access commands on the other. Being able to just raise an event that would cause the followers to perform action would simplify this a good bit.

Secondly I'm working on the Hubitat drivers for Shelly devices. They have Bluetooth Gateway functionality on their devices, and they have some BLE devices that can send events, via any device with BLE Gateway functionality, to hubitat. Right now I'm using/abusing Location events to get the events coming in via the BLE Gateway on one device over to the other, and it requires a 'helper app' that exists solely to subscribe to Location events and send out events via sendEvent(dni, [map event]). Being able to do this device to device would GREATLY simplify this setup and allow MUCH better integration of the line of BLE devices from Shelly.

2 Likes

You don’t need the drivers to be children of the app. Add an input to the app with type of the capability you want. Then you can choose specific devices in the app page.

That requires both using an app, which I don’t want to need to do, and then going into the app and manually selecting devices, which I don’t want to do.

Being able to raise an event in one driver and be subscribed to it in another seems like something Hubitat could use overall and I certainly know I’d use it.

Edit: ideally users would be able to install Shelly devices, such as the 'Shelly Plug US' as an example, then, without needing to also install an app, be able to add a Shelly bluetooth device and have it 'just work'. No need to run a helper app. No need to go into the app and select their 'gateway' devices every time they add a new one. No need to raise a bunch of Location events for every bluetooth device event.

The gateway devices would be able to raise events, preferably something new and not Location ones (so they don't pollute the Location events page). The Shelly Blu driver(s) would be able to subscribe to the same event stream and then in the callback for the subscription they could determine if the event is for them (ie, the MAC address on the BLE packet matches the MAC address specified in the driver preferences), and if it's for them, process sendEvent() as necessary.

The use case I would love for my Sonos Advanced app would be to update the "Group Devices" with events. So in Sonos Advanced every time you group/ungroup speakers the app looks at the current state of all the Sonos groups on the system and if a group exactly matches the group layout specified in a Sonos Advanced Group Device it sets that device as "on", otherwise it's "off".

For the player devices, I use a @Field to share a map of <String, DeviceWrapper>, so I can look up a device by its Rincon name and get a handle on a DeviceWrapper for the 'sibling' devices. This only works because all the player devices share a driver. The group devices have their own driver, and as such they can't share a @Field with the player devices. Consequently in order for a player device to update the state on a group device it has to make a trip up to the parent app and back down.

I've been fighting with this for weeks now, since it seems to cause 'can not obtain semaphore' errors, even tho there's nothing trying to obtain a semaphore. It seems as it the app is trying to wake 4+ children at once, running out of CPU threads and hanging for 120 seconds until killed. I'd expect an .each{} loop to wake the children one at a time, allowing them to do their thing and go back to sleep, but it appears that it doesn't do that. At least that's the only thing I can think as to why it keeps getting a semaphore error.

By being able to raise events instead, the group device drivers could be woken up individually as needed to process the events, rather than having the parent app try to wake them all at once.

It would also avoid waking the parent app and incurring the CPU usage that causes.

All around I think having a way to raise events in a driver and subscribe to them in another is certainly something with many use cases that would benefit Hubitat.

1 Like

It would need to be coded in both the sending and receiving drivers but you might look at using port 39501 and the device's parse method if the destination device's DNI is either the MAC or IP address (in hex, with no separators).

That would mean I'd be sending from Hubitat itself, so the receiving driver would need a DNI of Hubitat's IP/MAC. That might work for a single instance, maybe. But it certainly won't for adding dozens of bluetooth door and window sensors that would all need to be able to get events from the gateway devices.

Destination device is the only one that needs the DNI set correctly.

I hear what you’re asking for, but auto-wiring between devices isn’t part of the Hubitat model, and I can see lots of ways it could go wrong with unexpected behavior. If two devices are so truly linked that they should communicate 100% of the time, then parent-child device drivers is probably the appropriate model. (If they don’t communicate with each other outside of Hubitat)

Yeah, but if the sending device is Hubitat itself then port 39501 will see all the messages coming from Hubitat’s IP/MAC. So the destination DNI would be Hubitat’s MAC address. As far as I know you can only have one device getting port 39501 messages per DNI/MAC/hex encoded IP.

I must need more coffee this morn, what does the device event diagram look like? Hubitat would only be acting as a routing device the way I'm understanding you.

Maybe it’s not clear but I’m trying to get one device driver to send events to another. Not have a physical device send events to a device driver. That I can do easily with port 39501 or other methods.

That can’t work here. The use case is having one or more Bluetooth gateways raising events in the driver for the Bluetooth device. Parent child device would only work for a single gateway parent.

And I don’t see how it could “go wrong”.

If there’s a named event stream that I’m raising events in on one driver and subscribing to in another that won’t cause issues unless someone else writes a driver that subscribes to that same named event stream. As long as I’m using a unique name, and I could name it using a GUID for that matter, the chances of someone unintentionally using that same named event stream is basically zero. They would need to copy the name out of my driver code very intentionally and at that point we could assume they’re doing it knowingly.

I’m literally already accomplishing this, using location events, with a named attribute, and a helper app. I just want to eliminate needless overhead from needing a helper app and pollination of the location events tab by being able to raise my own named events that aren’t location events and subscribe to that directly in a driver, skipping the helper app.

I expect that is the point.... The reason apps can only access devices they did not create by using an input is so the control of that device access is entirely in the hands of the user, not the developer.

Don't get me wrong, I'd like to see something come of this, I just expect that may be a sticking point. Finding some way to control the access may be worth exploring.

Okay, light is finally dawning. Answer is that the capability does not exist at this time. Closest you have to a built-in capability is using MakerAPI as the intermediary and pushing the data as a device command parameter.

This is my understanding as well.

I don't see how this feature request would change that. The request here is two things. One, being able to raise generic named events.

Right now I can (and am) doing this:

That raise a Location event on the Hub that I can subscribe to in a "helper app":

That helper app in turns calls sendEvent() with a DNI specified in it, which INDEED does exactly what you're saying it can't do:

sendEvent() with a DNI in it like that causes a NON-input-selected, NON-child driver to get the event. You can do that from any app and send events to any driver like that, all you need to know is the DNI. And since the DNI for my app is all based on MAC address, and I send the MAC address in the event data, I can have my helper app raise events in any device. There's a lot of other things on Hubitat that use DNI based on MAC, or Z-wave ID, or ZigBee ID or other easily obtained DNI. I could absolutely write an app that would raise all sorts of events on devices that are not children of said app, and are not 'selected via input' for said app.

So the notion that a developer couldn't just raise events in random drivers if they so wanted is already not true. You definitely can do that. My Shelly drivers are currently doing it.

I just want to not need to do a whole bunch of extra steps with a 'helper app', using/misusing Location events, etc.

Fair points, there must be more nuance to some of the limitations I thought existed, because as you say, you can already achieve the outcome you want, just not in the manner you want.

Simply being able to subscribe to location events from a driver would eliminate the need for this helper app. That's one part of what this request is. If I could subscribe to location events in a driver, that alone would be a big step forward.

The other part is adding something similar to the Location event, but not actually being Location events. Maybe "Named events" or whatever HE devs want to call it. Functionally, it would work exactly the same as sendLocationEvent() does.

Let's call it sendNamedEvent() and I could call it like this sendNamedEvent(name: 'uniqueEventNameHere', value: 'myValue', data: [some:data]). This would in the background do exactly what the sendLocationEvent method does, but NOT put it in the Location Events tab on logs.

Then if I can subscribe in a driver, like so: subscribe('uniqueEventNameHere', 'handlerMethod') that's all that would be needed to get driver-to-driver event wiring working, without using Location events and a helper app.

Now, unless someone comes along and stuffs a subscribe('uniqueEventNameHere', 'handlerMethod') into their unrelated driver, the fact that I'm raising these events and subscribing to them will have ZERO impact on any other drivers on the system. You'd have to purposefully subscribe to the same named event as someone else to have conflicts.

And I could name my named event '67ec93d7-3455-4a2c-99e8-239732e50622', so the chances of someone else tossing a named event subscription in their code and somehow also using the exact same GUID based name is going to be about 1 in a billion billion billion. Could they do it purposefully? Sure. But at that point there's no harm, since they'd only be doing that purposefully if they wanted to have their driver informed of events being raised on that named event stream.

But again, all of this already exists on Hubitat. I'm already raising "named Location events", and anyone who has an app that does a subscribe on Location events using "shellyBLEButtonPushedEvents" for example would be getting events my drivers are raising. So there's already the possibility that someone might have subscriptions on 'named location events' like this and someone else's drivers could be injecting events on that subscription and causing conflict. So long as the developers don't do anything stupid like using non-unique names like "buttonEvents" that someone else would be likely to use it's fine. But that same issue already exists with the Location events 'attribute' thing, so it's nothing new there.

This feature request wouldn't allow for doing something that doesn't already exist. And it wouldn't create opportunity for conflicts that doesn't already exist. It just greatly streamlines and simplifies things, and eliminates 'polluting' the Location events tab.

Well, the one 'new' thing this would allow would be a device raising an event that another device could be subscribed to without needing to know the other device's DNI. Presently my "helper app" gets the DNI sent to it in the data object of the Location event, and the sending device "knows" the DNI since it's just based on the MAC address of the BLE device that sent the event originally. By using the MAC address as the DNI on the "receiving" device, I can correlate an inbound BLE message (with its MAC) to a DNI and have the helper app pass that along. Being able to raise "named events" and subscribe to that "named event stream" in other device drivers would allow for a device-to-device communication without needing to know the DNI of subscribing devices.

The subscribing devices would have to take the step of subscribing to the events, so it's not like this would allow raising events maliciously on other devices, as they wouldn't be subscribed. But again, right now I can have an app do something like this:

1..250.each{
  sendEvent(it, [name: 'switch', value: 'off'])
}

And that would cause every Z-wave device on someone's Hubitat to appear off. sendEvent(DNI. [Event]) already has a ton of capability for abuse if someone so wanted. But this feature request wouldn't affect that. In fact it would allow a more secure way to intentionally raise events from device to device, and eliminate the need for using it in an app the way I'm presently doing, and possibly opening up the door to just removing sendEvent(DNI. [Event]) as it indeed could be abused. With it removed, the only way a device could have sendEvent() called on it would be from a parent/child app/driver OR calling it itself in a callback method on this new 'named event subscription' method I'm requesting.

Just one man's opinion, and I have no special insight into the plans of Hubitat, but I tend to think there was a conscious decision to say that this line needs to exist, and doubt that this will change in the near future, if ever.

While apps can subscribe to any location event they choose they cannot, by design, subscribe to a device's events nor influence a device that has not been explicitly authorized by the user to them to do so. This architectural decision provides a level of protection by isolating the devices and thus the user's environment unless consciously authorized, from spurious decisions made by a random or rogue developer. Thus, should someone discover a way to transmit a nefarious application onto a hub, its actions would be very limited in scope.

3 Likes

But that's just not true.

sendEvent(DNI. [Map event]) already allows any app to raise events on any device. All you need to know is a DNI, and that's super duper easily guessed for things like Z-wave devices, which have DNIs that are simply 0-250 (in Hex).

And if you want to receive events, you can just subscribe to the websocket address of ws://hubIp:8080/eventsocket and you'll get every event on the hub.

Edit: Looks like you don't even need to guess the DNI for devices, it's obtainable on this URL: http://127.0.0.1:8080/hub2/devicesList

If you've got a malicious app/driver installed on your Hubitat, there's nothing preventing it from sending events to any device, and nothing preventing it from receiving every event on the Hubitat.

We'll have to see how Hubitat responds to your feature request. My take on the totality of responses is: don't be shocked if they decline. :smiley:

2 Likes