If I’m not mistaken, the only way to find device state is to poll. That’s inefficient to poll 24/7 when device state is really rarely changing. Have you considered options like:
Server Sent Events (text/event-stream)
UPNP subscriptions
Where I can specify an HTTP endpoint (REST) call you make whenever state changes
That’s correct. The Local HTTP API only supports polling GET devices/x/state. To reduce the number of requests, you could poll GET / and check to see if the hash (_ object) changed and only then poll each device. – Definitely sub-optimal.
I suppose we could add a webhook. We had avoided it thus far because we need to be careful with Smart by Bond products where RAM is more limited—but thinking about it again, we can probably squeeze it in.
We could add an endpoint where you can specify one HTTP URL where any state updates will be sent to. It would be a PUT method with JSON data. Content length specified in the header.
Do you see it being a problem that only a single HTTP endpoint would be supported, and that the same endpoint be used for all devices on the Bond?
Checking the hash seems like a good idea, but yeah, a single endpoint would work. Perhaps the JSON could include the device id of the device that had a change?
The JSON would contain the topic (url) that changed, plus the new body of the resource (state). — It would be as if your code was a subscriber to the state topic(s).
Planned support for a "Hooks" feature on the Bond Local API:
api/hooks/+:
{
host: www.myhost.com
path: /statechange.cgi
port: 80
topic: devices/+/state
method: POST // may be overrided to PUT or PATCH or even GET
calls: 10 // total number of calls to the hook
errors: 2 // number of calls that errored (non-200 or connection timeout)
queue_size: 10 // number of outstanding resources
}
host/port/patch should point to an HTTP server that will handle the hook
topic should match the Bond API topic to watch. The following wildcards are supported:
+ [^/]*
# .*
Whenever a topic watched by at least one hook is updated, the topic name is added to the set of outstanding topics for that hook.
Whenver the queue is non-empty, hooks are serviced in a single-threaded, round-robin fashion.
The Bond calls the specified host with an HTTP POST method,
with JSON data being an array of updated resources:
[ {"t":"devices/aabbccdd/state", "b":{"light":1, "power":0}, {...}, ... ]
The resource objects contain the topic as metadata, and then the current value of the body.
Upon a 2XX reply code from the server, the Bond will remove the topic from the outstanding set for that hook. An error reply (500, 400, etc.), or a connection failure, will result in the hook being retried after a 5 sec.
DEL api/hooks - delete all hooks
POST api/hooks - create new hook
GET api/hooks/+ - get hook info and statistics
DEL api/hooks/+ - delete a hook
Looking that over it seems like it should work fairly well with a little effort. With a local Host IP, we can still preserve local control; however it should also allow cloud-based solutions for those that pursue that path?
As you know, I’ve been an advocate for any sort of subscription / webhooks option, and I really appreciate you all coming up with something feasible.
Your efforts interacting with the community here and trying to provide solutions for us all to build upon is admirable, and should be a lesson to other manufacturers in the smart home world.
I actually just wrote a strong recommendation against that in the nascent docs.
Hook:
properties:
host:
example: 192.168.1.102
type: string
description: |
IP address or domain name of the HTTP server that will handle the hooks.
It is strongly recommended that this server be within a trusted LAN.
Hooks use unsecured HTTP and may be attacked on the public internet
or other untrusted network.
We do plan a very similar Cloud-based API which will use Oauth2 + HTTPS, which would be much better suited for C2C integrations.
Just realized, will I have the ability to specify a query string in addition to a path? If you’re going to call a GET then I need a ?access_token= for the OAuth token. If it’s a POST then I need the ability to specify the Bearer token via a header. Will that be doable? Even when it is a local hook, it appears Hubitat still requires an OAuth token.
I’m considering, based on some other feedback from HA platforms, whether we should use a single TCP or Websocket, or possibly UDP callbacks for this instead. — UDP is particularly interesting due to low resource requirement and extremely low latency.
That would not work for hubitat as far as I am aware. It cannot listen on a tcp or udp port. To my knowledge it cannot act as a we socket server either. I dont believe smartthjngs gs supports it either? For HE I believe HTTP or polling are the only options but I could be wrong.
Actually, there’d be no need for callbacks! The client can simply connect to a TCP or UDP port (other than 80) and then a new “connection” is established, along which JSON messages can be exchanged.
Ah so BOND would be the server and I’d just leave a client socket open? That would work. UDP seems slightly problematic since things can arrive out of order. Off then on could be delivered as on then off and we’d be out of sync.
Re: UDP out-of-order issue, true in theory, and in practice over the internet. However, for local networks, there’s only (ever?) a single path and packets never really get out of order. A last-one-wins approach can give very effective, low-latency feedback.
That said, TCP is more flexible, avoids possible MTU issues, can be tunneled around with SSH. Probably superior.
Just curious is there a reason you believe tcp to be better? While I can make it work this requires me to rewrite my whole integration because apps can’t connect to a socket in Hubitat. I have to redesign everything to have a single parent device that will now communicate with bond. Doable but a lot of work