[solved] Process string to object and back incl. storing data to only send out complete messages

Hi all,

I´m still new to node red. In the past couple of month I successfully connected my smarthome devices from ioBroker with node red to Amazon Alexa.
Since I want to switch to Homekit I am now trying to adapt my logic for Homekit.

I successfully adapted simple on/off as well as dimmable lights logic.
What I´m still trying to figure out is how to adapt the color lighting which is more complex.
To get this up and running I have three problems that need to be fixed:

1. Homekit to Target System
Homekit delivers values as hsv (Hue/Saturation/Brightness) as well as On/Off to control a color lamp.
Problem is, that Homekit only sends the complete set of values (hsv) if necessary and else only the changed values (e.g. only Brightness or only Hue or only Saturation or On or Off etc.).
All values are send as Objects (e.g. msg.payload.Brightness = 100).

Problem is that my target system always needs the complete set of values and it needs them as a string (e.g. "hsv(237,100,60)"), in order to change the status of the light.
I would basically need some kind of cache that stores all values and only sends the complete set as a string.

2. Set back to default value if switched off and on again:
Next problem is that Homekit stores the last config when switching on/off.
It would be great, if I could include a default value for hsv if the lamp was switched off and is switched on again.
If someone then changes the light to red but doesn´t know how to get back just has to turn it off and on to get the default lighting.

3. Update Homekit status if lighting was changed in targed system:
Last but not least I need to figure out on how to change the status messages of my smarthome system in order for Homekit to understand and show the correct status in Homekit.
My smarthome system sends to different kind of strings if tlighting is changed:
temp(47,4464) or hsv(237,100,60).
Both formats are used depending on the type of change (only brightness or also color).
Homekit only understands hsv and the values have to be seperate objects (e.g. msg.payload.Brightness).

Does anyone have any idea how to solve these problems?

Thx and brgds,

Claus

I created a function node with the below content to do something similar. It stores payload objects in a global context object array. When receiving data it checks if it can find the object in its storage (based on msg.payload.Name) and copies the received values on that object. Existing values which are not received, are left on the object. Finally, it outputs to object, which now is a combination off the existing values and the new values.

You can control the variable name used in the global context by setting msg.varname. Default is 'storage'

let varname = msg.varname || 'storage'
let storage = [];

storage = global.get(varname) || []

let store = storage.find(x => x.Name.toUpperCase() === msg.payload.Name.toUpperCase())
if (!store)
{
    store = { };
    storage.push(store)
}
Object.assign (store, msg.payload)

global.set(varname, storage); 

msg.payload = store;
return msg;

Thanks for the swift reply.
When I try the function I get an error message:
"TypeError: Cannot read property 'toUpperCase' of undefined"
Can you send me the code for the function toUpperCase?

Thx!

i suspect this is because the received message does not have msg.payload.Name set to a value. try using this inject node:

[{"id":"d429ac1fdfa67077","type":"inject","z":"c907dff3.438cc","name":"","props":[{"p":"payload"},{"p":"varname","v":"devicestest","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"Name\":\"test\",\"value\":3}","payloadType":"json","x":160,"y":2320,"wires":[["4b5f2193af02d704"]]}]

With a few more inject nodes and the function put in a subflow because I couldn't help myself :wink:

Inject 1, create test
Inject 2, update test to different value
Inject 3, create test2
Inject 4, update test2 with new attribute --> note in debug node that now the full test2 object is exported, including the value attribute which was part of the previous message

[{"id":"2be1427693d87db2","type":"subflow","name":"context","info":"Stores payload object in context based on payload.Name.\n\nIf object exists, the passed attributes of the object are updated. It always outputs the full object.\n\nName of the context variable can be set by passing msg.varname. Default is storage.","category":"","in":[{"x":450,"y":140,"wires":[{"id":"eb36dd7c8f94644f"}]}],"out":[{"x":740,"y":140,"wires":[{"id":"eb36dd7c8f94644f","port":0}]}],"env":[],"meta":{},"color":"#3FADB5","icon":"font-awesome/fa-database"},{"id":"eb36dd7c8f94644f","type":"function","z":"2be1427693d87db2","name":"","func":"let varname = msg.varname || 'storage'\nlet storage = [];\n\nstorage = global.get(varname) || []\n\n//let device = { Name: 'test', value : null}\nlet store = storage.find(x => x.Name.toUpperCase() === msg.payload.Name.toUpperCase())\nif (!store)\n{\n    store = { };\n    storage.push(store)\n}\nObject.assign (store, msg.payload)\n\nglobal.set(varname, storage); \n\nmsg.payload = store;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":590,"y":140,"wires":[[]],"info":"Stores payload object in context. If object exists, the passed attributes of the object are updated. It always outputs the full object.\n\nName of the context variable can be set by passing msg.varname. Context storage to use can be set by passing msg.storetype. Default context is flow."},{"id":"d429ac1fdfa67077","type":"inject","z":"c907dff3.438cc","name":"","props":[{"p":"payload"},{"p":"varname","v":"devicestest","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"Name\":\"test\",\"value\":3}","payloadType":"json","x":160,"y":2320,"wires":[["4b5f2193af02d704"]]},{"id":"a19dc139cf8f80d1","type":"debug","z":"c907dff3.438cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":520,"y":2320,"wires":[]},{"id":"4b5f2193af02d704","type":"subflow:2be1427693d87db2","z":"c907dff3.438cc","name":"","x":370,"y":2320,"wires":[["a19dc139cf8f80d1"]]},{"id":"327436565fa404c7","type":"inject","z":"c907dff3.438cc","name":"","props":[{"p":"payload"},{"p":"varname","v":"devicestest","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"Name\":\"test\",\"value\":5}","payloadType":"json","x":160,"y":2370,"wires":[["4b5f2193af02d704"]]},{"id":"1a81ef99a46d8337","type":"inject","z":"c907dff3.438cc","name":"","props":[{"p":"payload"},{"p":"varname","v":"devicestest","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"Name\":\"test2\",\"value\":3}","payloadType":"json","x":160,"y":2420,"wires":[["4b5f2193af02d704"]]},{"id":"6949669c6d3a6ced","type":"inject","z":"c907dff3.438cc","name":"","props":[{"p":"payload"},{"p":"varname","v":"devicestest","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"Name\":\"test2\",\"Battery\":{\"BatteryLevel\":100}}","payloadType":"json","x":160,"y":2470,"wires":[["4b5f2193af02d704"]]}]

Thx for the injection nodes now I get the error.
You use a clean declaration of the object incl. Name and Value.
Homekit only delivers a name/value combination (e.g. Brightness: 100) as object.
Do you have any idea on how to change the code in order to get it to work with what homekit delivers?

Thx!

I think I might be able to, but I don't run homekit... Can you share a few sample messages that I can work with?

Of course!

When I switch the light on I get:

msg.payload : Object
object
On: true
msg.payload : Object
object
Brightness: 100

When switching it off I get:

msg.payload : Object
object
On: false

When changing color (and already switched on) I get:

msg.payload : Object
object
Saturation: 68
msg.payload : Object
object
Hue: 255

And when I change the brightness I get:

msg.payload : Object
object
Brightness: 100

I hope that´s sufficient. It looks like this in the debug window:

mmmh, there really should be something consistent about it. I suspect topic has some meaningful information. Can you copy a few messages using the copy value button (when having set the debug node to output the complete message object), like in the example:
image
resulting in :
{"_msgid":"39678df7a140157a","payload":{"Name":"test","value":3},"varname":"devicestest"}

There is actually not much more Information included:
The object path for example is payload
The path to the object detail is payload.Brightness
The value within this is just {"Brightness":100}

So basically all possible object information:

{"On":true}
{"Brightness":100}
{"Saturation":68}
{"Hue":255}

Ok, so what you get is messages for the same device, which every time have part of the device state. This state is captured as an object in payload. You would like to get the combined properties out, even if you only receive one of them.

I have created a simple flow that has 4 inject nodes to inject each of the examples you provided. The messages go to a function node, which fetches a store object from it's context. It then copies the attributes of the payload object onto the retrieved store object, stores this in the context and outputs a message with a payload with all attributes that have been sent over time. I hope this is what you are looking for!

> [{"id":"16d7556083bcce80","type":"function","z":"c907dff3.438cc","name":"","func":"let store = context.get('store') || {}\n\nObject.assign (store, msg.payload)\n\ncontext.set('store', store); \n\nmsg.payload = store;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":560,"y":3470,"wires":[["d415aa19d5a3800d"]]},{"id":"a794e345154648a7","type":"inject","z":"c907dff3.438cc","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"Brightness\":100}","payloadType":"json","x":350,"y":3370,"wires":[["16d7556083bcce80"]]},{"id":"d415aa19d5a3800d","type":"debug","z":"c907dff3.438cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":800,"y":3450,"wires":[]},{"id":"ed11be410bb3be54","type":"inject","z":"c907dff3.438cc","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"On\":true}","payloadType":"json","x":320,"y":3420,"wires":[["16d7556083bcce80"]]},{"id":"72d0cc21f320b518","type":"inject","z":"c907dff3.438cc","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"Saturation\":68}","payloadType":"json","x":340,"y":3470,"wires":[["16d7556083bcce80"]]},{"id":"5ec9d1d1565e4627","type":"inject","z":"c907dff3.438cc","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"Hue\":255}","payloadType":"json","x":330,"y":3520,"wires":[["16d7556083bcce80"]]}]

Note: maybe the split and join nodes can help you achieve something similar

Works like a charm, thank you!! :slight_smile: :slightly_smiling_face:
I still have a question regarding the code though. Is it possible to include a default value for Hue and Saturation?

Reason: When I switch the light on it just sends the Brightness 100 and On = true value and no value for hue and sat. Also it would be usefull as some kind of fallback whenever the light was switched off and on again it uses its default state.

Thank you!

I'm glad it works!

If you change the first line of the function node to the below, it will use default values if it didn't already receive messages.

let store = context.get('store') || {"Brightness":100,"On":false,"Saturation":68,"Hue":255}

Enjoy!

Awesome, thank you! :slightly_smiling_face: