Copprhead - Notifications in Node-RED: I'm play...

Notifications in Node-RED: I'm playing with SK/Node-RED to control an external alarm buzzer. An alarm is triggered by a notification for a certain key. For that I'm using the 'notification' node and it works fine for changes. But when the key/notification goes into alarm state while Node-RED is not "there" (yet), and then Node-RED comes up, it won't be notified of the alarm since the notifications are sent only when the state changes. Ideally, the notification node would get the current state once when it starts up, and then the changes when they happen. This is not a big deal for keys that update every second (e.g. depth), but it is a problem for keys that change only very occasionally. (Same goes for the 'subscribe' node I think.) Is there something I'm missing? (I thought maybe I can query the current state on initialization, but I don't see a way how.)
33 Replies
Scott Bender
Scott Bender3mo ago
You can setup a flow to check the value a startup. But yeah, the notification node should have that ability (like the subscribe node does)
Copprhead
Copprhead3mo ago
How would I do that?
Scott Bender
Scott Bender3mo ago
[{"id":"dc9340f22f706579","type":"inject","z":"a433c102.47075","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":110,"y":1840,"wires":[["4d3bb1afbfc41b6e"]]},{"id":"4d3bb1afbfc41b6e","type":"function","z":"a433c102.47075","name":"function 1","func":"let app = global.get('app')\n\nlet notif = app.getSelfPath('notifications.electrical.bildge.value')\n\nlet state = 'normal'\n\nif ( notif ) {\n state = notif.state\n}\n\nreturn { payload: state , topic: 'notif state' }\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":300,"y":1840,"wires":[["fca4c99716d63a7b"]]},{"id":"fca4c99716d63a7b","type":"debug","z":"a433c102.47075","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":480,"y":1840,"wires":[]}]
[{"id":"dc9340f22f706579","type":"inject","z":"a433c102.47075","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":110,"y":1840,"wires":[["4d3bb1afbfc41b6e"]]},{"id":"4d3bb1afbfc41b6e","type":"function","z":"a433c102.47075","name":"function 1","func":"let app = global.get('app')\n\nlet notif = app.getSelfPath('notifications.electrical.bildge.value')\n\nlet state = 'normal'\n\nif ( notif ) {\n state = notif.state\n}\n\nreturn { payload: state , topic: 'notif state' }\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":300,"y":1840,"wires":[["fca4c99716d63a7b"]]},{"id":"fca4c99716d63a7b","type":"debug","z":"a433c102.47075","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":480,"y":1840,"wires":[]}]
So that’s an inject node that’s set to fire once at startup, then a function that gets the notification state and passes it on.
Copprhead
Copprhead3mo ago
Great, thank you. That works. Didn't know you had access to 'app', that's nice. Can I use wildcards in app.getSelfPath() to get an array? For example for checking if any notification is in alarm state?
Scott Bender
Scott Bender3mo ago
no Well… You can getSelfPath(‘notifications’) Then you can traverse them all.
Copprhead
Copprhead3mo ago
Just tried: let notifs = app.getSelfPath('notifications') node.warn(notifs.length) And that gives me 'undefined'.
Scott Bender
Scott Bender3mo ago
That’s going to be an object, not an array
Copprhead
Copprhead3mo ago
oh yeah Yes it works. But it's kinda hard to parse through all that to find the one (who knows how deep) that has alarm state and then go back up to get the key...
Scott Bender
Scott Bender3mo ago
Not simple, but definitely doable.
Tony
Tony3mo ago
Object.entries and Object.keys are handy for traversing nested objects. There are helpers in lodash if you already know the path to the desired value
Copprhead
Copprhead3mo ago
Thank you. Yes, doable. But it would always mean that I end up polling the state, instead of subscribing, right? So the generic "any alarm" approach would be a bit ugly. Might be better after all to explicitly define the keys I want to monitor for alarms and use notification (with initial polling).
Scott Bender
Scott Bender3mo ago
here's a function that traverses notifications and outputs messages for each notification
let app = global.get('app')

let notif = app.getSelfPath('notifications')

let states = []

getNotifications(notif, null, 'notifications', states)

return [states]

function getNotifications(object, prevPath, path, result)
{
Object.keys(object).forEach(key => {
let sub = object[key]
if ( key === 'state' && typeof sub === 'string')
{
result.push({payload: object, topic: prevPath})
}
else if ( sub != null && typeof sub === 'object' )
{
getNotifications(sub, path, `${path}.${key}`, result)
}
})
}
let app = global.get('app')

let notif = app.getSelfPath('notifications')

let states = []

getNotifications(notif, null, 'notifications', states)

return [states]

function getNotifications(object, prevPath, path, result)
{
Object.keys(object).forEach(key => {
let sub = object[key]
if ( key === 'state' && typeof sub === 'string')
{
result.push({payload: object, topic: prevPath})
}
else if ( sub != null && typeof sub === 'object' )
{
getNotifications(sub, path, `${path}.${key}`, result)
}
})
}
Copprhead
Copprhead3mo ago
Cool thank you! I added "&& sub == 'alarm'" and it does what I need 🙂 So I'll just run this every 1s inside an endless loop, or do you have a better suggestion? If it comes out empty, I'll stop the buzzer, if it has a result, I'll start it. Should be ok for now, can still refine later.
Scott Bender
Scott Bender3mo ago
I would just run that once at startup. And use the notification node to get any changes
Copprhead
Copprhead3mo ago
yeah but then I'd have know exactly which keys to check
Scott Bender
Scott Bender3mo ago
I think wildcards work with that node
Copprhead
Copprhead3mo ago
Oh yes it does. Good! Hmm, next problem. To turn off the alarm I used to wait for the notification for key xyz with state=normal. The equivalent with * would turn off the alarm when the first key goes to normal, and there could be other keys still in alarm, but my buzzer would turn off.
Scott Bender
Scott Bender3mo ago
You would need to keep some state. Like keep a count of the number of alarms. When it reaches zero, turn off the buzzer
Copprhead
Copprhead3mo ago
I guess that could be done. Not sure how reliable though. Performance-wise, is it very bad to poll with the function above vs. using notifications?
Scott Bender
Scott Bender3mo ago
It’s definitely much worse performance wise, but if your might not even notice it. I don’t see any reason keeping a count would not be reliable
Copprhead
Copprhead3mo ago
ok will try later, gotta go now. Thanks! Is there no way a notification can get lost? That would screw up the counter. There's another problem: I'm using the notification node now with * and state=alarm for alarming/increasing the counter, and with * and state=normal for cancelling the alarm/decreasing the counter. This generally works when I change my key into the alarm zone and back for testing. But sometimes I'm getting notifications for every "normal" state key (20+), even though these haven't been in alarm before, neither have subscribed to them in any way. Why is that? (Needless to say the counter is not going to work like that.) These "normal" notifications are for propulsion.* keys. And they only come when I have a logfile data connection active. When I recorded this data, I had an engine->N2K interface connected and the engine running. But the values were not in alarm, neither are they now shown as alarm (ever), only normal.
Scott Bender
Scott Bender3mo ago
Oh, right, didn’t think about that. It could happen the other way also. Let me see how I do this in the fusion plugin… Ok, I use a Map to store exceptions that are not in normal
Scott Bender
Scott Bender3mo ago
GitHub
signalk-fusion-stereo/index.js at master · sbender9/signalk-fusion-...
signalk-server-node plugin to control a Fusion stereo - sbender9/signalk-fusion-stereo
Copprhead
Copprhead3mo ago
Ok. Another question... I'm letting a led blink with a trigger. First message: turn led on, second message after 250ms: turn led off -> delay(250ms) ->start over. The led starts blinking regularly, but every now and then it "stutters", either the light phase or the dark phase are a bit too long. Is there anything I can do about it?
Scott Bender
Scott Bender3mo ago
Sorry, don’t know
Tony
Tony3mo ago
Polling is a pretty common pattern for checking the state for alarms. Grafana uses the same pattern for all data sources. It’s more process intensive but a lot more reliable
Scott Bender
Scott Bender3mo ago
Signalk deltas are extremely reliable, there’s no reason to do polling that I can think of. (except for this case where he needs to get the current state when he starts up)
Tony
Tony3mo ago
The other way that can be reliable is putting them in a message queue via a plugin. The subscriber would be required to process and signal to remove from the queue which would still stay with a pub/sub system. But a different runtime requires some polling mechanism
Scott Bender
Scott Bender3mo ago
In this case, node-red is running inside the server process. There’s no reason to go through all that.
Copprhead
Copprhead3mo ago
If the subscribe and notification nodes would send the current state when started, everything would be fine. The updates are working well and unnecessary (too many) updates are normally not a problem.
Scott Bender
Scott Bender3mo ago
The subscribe node does send the current state when started I need to add those same options the subscribe node has to the notification node
Copprhead
Copprhead3mo ago
Not sending anything by itself here, only when the key is updated. I added a subscribe node with my path, attached a debug, deployed. Got nothing. When I changed the value in data fiddler, got the change shown. Or the same value if not changed but resent. But on deploying I'm not getting anything from subscribe.
Scott Bender
Scott Bender3mo ago
What do you have "Mode" set to for the subscribe node?> hmm, yep, something is broken. that's not working anymore.