Advice please on best way to attack this

#1

Guys,

I have a value being sent across by MQTT (actually a string but then i convert it to an Int) - the number is a binary representation of the state of pins on a Arduino - each of the pins represents a state for a device to be in - such as 208 is Boiler on, circulating pump on, solar off, Valve 1 open, Valve 2 closed etc etc

There are a lot of permutations.

What i want to be able to do is track when i have a change of one of these - i have successfully captured the MQTT, turned it from string into an integer and then used the RBE node to get info on a state change - my challenge now is what to do with it once i have the state change - it would seem that i could use the switch node - but it seems like there would be a huge number of possibilities - would i be better doing a function node with a heap of switch statements - remembering that i can transition both ways - so i could go from having a boiler on, to off (which changes the value from 208 to 176, but i could also go from 208 to 200 if the boiler was on and a valve closed) - so it looks like i could have a LOT of switch statements - any ideas about how i could simplify this (without changing the data i output on the Arduino ?)

Craig

0 Likes

#2

It seems you would have to reverse the logic that creates the number. Which begs the question how do you create the number you send representing all the GPIO pins?

0 Likes

#3

If, in a function node you do

msg.payload = msg.payload..toString(2);
return msg;

That will convert your number back into a string, but in binary, so the value 208 will be converted to "11010000" Now you can reference the individual states using msg.payload[0], [1] etc. There is the complication that ideally you probably want it padded with zeros on the front to make it a fixed length. I haven't got time to look at how to do that at the moment, perhaps someone else will suggest the best way (possibly padStart()?)

1 Like

#4

Hi Colin,

This code should do the job. The second parameters is the total size of the binary string to be generated.

function padStart(dec,len){
let binary = dec.toString(2);
let padding = "0".repeat(len-binary.length);
return  padding+binary;
}

let pay = padStart(msg.payload,17)
msg.payload = pay;

return msg;

r-01

Flow:

[{"id":"bdd100a7.cade4","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"fd3158f4.0e6b68","type":"function","z":"bdd100a7.cade4","name":"PadStart - pad zeroes in front","func":"function padStart(dec,len){\nlet binary = dec.toString(2);\nlet padding = \"0\".repeat(len-binary.length);\nreturn  padding+binary;\n}\n\nlet pay = padStart(msg.payload,17)\nmsg.payload = pay;\n\nreturn msg;","outputs":1,"noerr":0,"x":520,"y":220,"wires":[["4019c994.4c8058"]]},{"id":"fa927000.137f","type":"inject","z":"bdd100a7.cade4","name":"","topic":"","payload":"208","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":290,"y":220,"wires":[["fd3158f4.0e6b68"]]},{"id":"4019c994.4c8058","type":"debug","z":"bdd100a7.cade4","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":750,"y":220,"wires":[]}]
0 Likes

#5

errr - isn't padStart built in these days ?

[{"id":"d8f5efb7.a44cb","type":"inject","z":"ae93e99e.884c98","name":"","topic":"","payload":"111","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":180,"wires":[["5d42bcd0.c33eb4"]]},{"id":"5d42bcd0.c33eb4","type":"function","z":"ae93e99e.884c98","name":"","func":"msg.payload = msg.payload.padStart(8,\"0\");\nreturn msg;","outputs":1,"noerr":0,"x":310,"y":180,"wires":[["11d9f374.54469d"]]},{"id":"11d9f374.54469d","type":"debug","z":"ae93e99e.884c98","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":470,"y":180,"wires":[]}]
1 Like

#6

Only since Node 7.0... so there are versions of Node we support with 0.19 that doesn't have it.

1 Like

#7

I am transitioning from an old Picaxe based system (writen in Basic) that is more than 10 years old. There is a function in picaxe basic to essentially read all the pins in one go and report it back as a binary representation. The issue is that this system was developed as a collabaritive effort with a friend who had a system crash a number of years ago and turns out his backup practices were non existent - so i have no source code to go back.

What i have been doing for the last year is slowly supplementing the picaxe and removing functionality by introducing an arduino into the data stream and having is take over inputs and outputs (one at a time). Slow going - but nearly there.

I then found Node-red this year and have been distracted in trying to put a nice interface onto the system - we are nearly finished the cold months here in Australia and i can then play on the system in isolation and hopefully remove the last of the Picaxe components from it.

Craig

0 Likes

#8

Oh wow - that is pretty cool and looks like ti might help - will start playing around with it.

Looked at using switch nodes last night - but there will be way too many of them to manage - might incorporate this Binary idea and look at a function with switch statements (does anyone know if i can nest switch statements ? so the match for one switch statement then has another switch statement built within it ?

Craig

0 Likes

#9

Hi Craig,

The short answer is yes, you can nest switch statements in JavaScript. However, depending on the complexity of your use case the code can become ugly and hard to maintain and troubleshoot. Perhaps you can tell us more about what would be the output of such function node with these nested statements ? How many outputs your function node would have and what the outputs would deliver after all ? I just want to understand if there is a simpler way to address this need.

0 Likes

#10

Can you show binary output message?

0 Likes

#11

Hey Andrei,

Thanks for taking the time to follow this one up.

Here is an example - the value 240 (Decimal representation of binary pins) represents - Circulating pump on, boiler on. So when my system is running in the middle of the night i will get an MQTT stream in of 240 every minute. When the buffer tank reaches temperature - it will signal to turn the boiler off and the MQTT stream will register that and start sending our 192 representing Circulating pump on, Boiler off,

So i need to be able to test for the case that

  1. Boiler on, Circulating pump on - then boiler off and report back the time this happened
  2. Circulating pump on only and then boiler turns on
  3. House meets the setpoint for heat and both turn off so i get a 176 value - and then all different permutations

I then have the added complication that some of the Binary numbers also represents valve states that are on and off - so i could actually have 255 as a value which would represent Boiler on, Circulating pump on and 3 zone valves on (as an example)

I can identify all the cases just fine, what is doing my head in is then the transitions between each case and the permutations that will introduce.

I was thinking that using Case/Switch might at least help me break it down into manageable chunks

Any others ideas very warmly accepted !

Craig

0 Likes

#12

The first thing I would do is to work out what the signals mean in the real world, things like boiler_state which can be On or Off, pump_state which can be On or Off so on for the rest of the data. Then send messages out to MQTT with topics like home/boiler_state, home/pump_state with values On or Off. From the point of view of this flow that is all it needs to do.
Then in another flow, or flows, pick up the MQTT values that are required for a particular bit of logic, work out what that means, and send back out to MQTT topics like home/boiler_request with value On/Off. Then in yet another flow handle all the output logic that picks up things like boiler_request and does whatever is required to switch it off.
By doing something like that above you are separating the input of data, the calculations of what to do, and outputing the actions necessary in to separate flows and it becomes much easier to think about how to do things. So when doing the input stage you don't need to think at all about what to do when things change, just work out how to get the data in. Also when something changes it becomes much easier to implement the change without messing up something else. If you think of a better algorithm for example that only affects that bit of the flows, there is no danger of messing up the input or output stages.

1 Like

#13

Hi Craig,

The approach that came to my mind is a mix of what you want to do and what Colin just suggested.

Instead of using a bunch of nested switch cases you could instead create an object that works as a table lookup. As Colin said it is good to engineer a solution that makes easier to implement any eventual change without messing up something else. It is much better to change an object than rebuilding a complex code with nested switch cases (and all those tricky breaks statements).

I understand that your RBE node will provide you with two numbers whenever there is a state change. If you combine these two numbers into a single string it could be used as a key to fetch the table lookup object. In the object, you can have a property that says what the change is about (eg. property status) and a property that is a function (therefore a method) that may be used to take actions to respond to the change.

Example of an object could be:

let def = {
    
    "176192": {
        "status": "pump turned on",
        "action": function () { node.warn("176192"); }
    },
    "176208": {
        "status": "pump turned on and boiler turned on and solar off",
        "action": function () { node.warn("176208"); }
    },
    "176240": {
        "status": "pump turned on and boiler turned on",
        "action": function () { node.warn("176240"); }
    },
    "240176": {
        "status":
            "pump turned off and boiler turned off",
        "action": function () { node.warn("240176"); }
    },
    "208192": {
        "status": "boiler turned on",
        "action": function () { node.warn("208192"); }
    }
};

Follows a flow that put it all together. The action I coded in the functions is only a display but you can do whatever you need, like build an MQTTT reply for instance.

[{"id":"e60c1848.624e28","type":"tab","label":"Flow 15","disabled":false,"info":""},{"id":"e4eefb9b.fe1b78","type":"inject","z":"e60c1848.624e28","name":"","topic":"","payload":"240176","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":250,"y":240,"wires":[["9cc5fb51.b4d948","4643eec9.d5e6f"]]},{"id":"5eb39148.2562f","type":"function","z":"e60c1848.624e28","name":"Create Object","func":"let def = {\n    \n    \"176192\": {\n        \"status\": \"pump turned on\",\n        \"action\": function () { node.warn(\"176192\"); }\n    },\n    \"176208\": {\n        \"status\": \"pump turned on and boiler turned on and solar off\",\n        \"action\": function () { node.warn(\"176208\"); }\n    },\n    \"176240\": {\n        \"status\": \"pump turned on and boiler turned on\",\n        \"action\": function () { node.warn(\"176240\"); }\n    },\n    \"240176\": {\n        \"status\":\n            \"pump turned off and boiler turned off\",\n        \"action\": function () { node.warn(\"240176\"); }\n    },\n    \"208192\": {\n        \"status\": \"boiler turned on\",\n        \"action\": function () { node.warn(\"208192\"); }\n    }\n};\n\nflow.set(\"def\", def);\n\nreturn msg;","outputs":1,"noerr":0,"x":340,"y":80,"wires":[[]]},{"id":"9cc5fb51.b4d948","type":"function","z":"e60c1848.624e28","name":"Display status change","func":"let d = flow.get(\"def\");\nnode.warn(d[msg.payload].status);\nreturn msg;","outputs":1,"noerr":0,"x":540,"y":240,"wires":[[]]},{"id":"4643eec9.d5e6f","type":"function","z":"e60c1848.624e28","name":"Execute action","func":"let d = flow.get(\"def\");\nd[msg.payload].action();\nreturn msg;","outputs":1,"noerr":0,"x":520,"y":300,"wires":[[]]},{"id":"598bc7b9.926518","type":"inject","z":"e60c1848.624e28","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":150,"y":80,"wires":[["5eb39148.2562f"]]}]
0 Likes

Logic error - can i get some input please?
#14

Can you give an example of a problematic case?

0 Likes

Function Buffer to Outputs
#15

Craig,

I like the way Colin has split up the incoming integer into individual bits -- I'm assuming that each of these bit values (0/1) map to the state (off/on) of a single component of the system (pump, boiler, valve, etc).

In order to make the downstream flows simpler, I usually prefer to map these "flags" to msg properties, which I can access by name later on. Here is how I would use a change node with a JSONata expression to turn the number 192 into a msg payload with 8 named properties:

image

The JSONata expression builds 2 arrays: one for each bit value, and one for the pin labels. The two arrays are then zipped together into a single object, using this syntax:

(
    $bits := $formatNumber($number($formatBase(payload, 2)), '00000000').$split('');
    $pins := ['Power','Pump','Boiler','Solar','Valve1','Valve2','Valve3','Valve4'];
    $zip($pins, $bits) {
        $[0]: $number($[1])
    }    
) 

At this point, each downstream node can pull just the information it needs to trigger other flows, populate a database, or for display on a dashboard. If the transitions between states are more important than the values themselves, I would look at feeding them into a State Machine node -- there are several good ones in the flows library...

3 Likes

#16

Hey guys - thanks for all the great suggestions - will read through them today and see which one i feel my current skill level is best able to comprehend and implement and will report back as a i progress. !

Once again thanks everyone for taking the time

I must say this is the most helpful forum i have ever been involved in and the people the friendliest

regards

Craig

0 Likes

#17

I think i like this approach (or at least i can understand most of it Andrei !) I assume that this is essentially an in memory table that is created at startup each time (based on your inject node for instance)

I might start down this track and use it to push the MQTT messages and see if i can get it to work !

thanks for all the help guys - very much appreciated

Craig

0 Likes

#18

Not sure exactly what route you are going down, but I suggest that at some point you split the data into a sequence of messages with separate topics for each system state (boilerOnState etc). Then you can feed that into a topic based RBE node so that you will only get messages for each topic when that state has changed, you can then send that to MQTT and subscribe to the topics in the flow that actions that state, and you will only get a message there when the value changes. If instead you combine everything into one message you will get a message every time anything changes and will later have to work out which items have changed.

0 Likes

#19

Colin,

Yep pretty much the realisation that i came to today when i started looking at this - essentially by following the sugestion from Andrei - I am identfiying every case and the transition between them. Once i have that i will use it to generate individual MQTT streams as you have pointed out. I will then combine that with the logic of breaking the flows down as much as possible into Receipt and Action states to make it easier to transition in the future.

Craig

0 Likes