Compare 2 sensors

I have an existing Arduino fan controller I am ready to replace with a NR based approach. I have several temperature/humidity sensors that I want to use to compare values and turn a fan on/off based on differential values. I believe the correct way to do this is by storing the sensor values with context variables as they come in and then check the difference either at intervals or as a new value is received. I have not seen a node that can do this so I'm assuming this will have to be done in a function node.

If there's a better way to do this I'd really appreciate suggestions before I go down the wrong rabbit hole.

In principle you are right. So you need to store your different sensor values in a context. Context can but must not be in a node context. In Node context you are right - you have to use a function node and must write code. But you can also store different values in flow context and do more logic in a flow instead of code. At the end it will be a mix .

I have the code set up and testing that stores the temperature/humidity values in context for the flow. I have some basic test of values and a timer in a function node. I'm going to be using X10 to turn the a device on/off via a HTTP request to my HomeGenie system. I'm not sure how to do that in a function node though. I see a few threads that suggest options such as node.send but I have not been able to get them to work. Can a simple example be posted?

Issuing the following in a web browser:
http://192.168.0.200/api/HomeAutomation.X10/C1/Control.On

will result in the module turning on and the following response:
ResponseValue "OK"

Example flow:
[{"id":"e22027f2.73ddd","type":"inject","z":"767e039c.16db54","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":110,"y":240,"wires":[["50aea946.38b88"]]},{"id":"50aea946.38b88","type":"function","z":"767e039c.16db54","name":"","func":"node.send(\"http://192.168.0.200/api/HomeAutomation.X10/C1/Control.On\");","outputs":1,"noerr":0,"initialize":"","finalize":"","x":260,"y":240,"wires":[[]]}]

This snippet works great but I don't know how to incorporate it into my flow. I would really just like a simple command within the function node, but, again, I haven't found a working approach in the forum so I'm now looking at a hacky approach unfortunately.

[{"id":"e55b116.af227f","type":"http request","z":"767e039c.16db54","name":"","method":"GET","ret":"txt","paytoqs":"ignore","url":"http://192.168.0.200/api/HomeAutomation.X10/C1/Control.On","tls":"","persist":false,"proxy":"","authType":"","x":290,"y":320,"wires":[["666628a3.4669b"]]},{"id":"5f38ddf.e3d6324","type":"inject","z":"767e039c.16db54","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":110,"y":320,"wires":[["e55b116.af227f"]]},{"id":"666628a3.4669b","type":"debug","z":"767e039c.16db54","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":470,"y":320,"wires":[]},{"id":"a2accd4c.033a8","type":"http request","z":"767e039c.16db54","name":"","method":"GET","ret":"txt","paytoqs":"ignore","url":"http://192.168.0.200/api/HomeAutomation.X10/C1/Control.Off","tls":"","persist":false,"proxy":"","authType":"","x":290,"y":380,"wires":[["dee67cf.e87b38"]]},{"id":"1c0bbe79.2b9062","type":"inject","z":"767e039c.16db54","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":110,"y":380,"wires":[["a2accd4c.033a8"]]},{"id":"dee67cf.e87b38","type":"debug","z":"767e039c.16db54","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":470,"y":380,"wires":[]}]

response:
image

Why are you using and coding in a function node? - NodeRed is a tool which should avoid coding. So in your second example you have used a HTTP-request node which I would recommend as well.

You need no function nodes at all - you can also use flow variables to store your humidity, temperature values. So please specify 1,2 sensors, which values and when should be what triggered - only then we can help you.

node.send is completely wrong - you need no async procedure - in your HTTP-Request you get the confirmation if the request was OK - but even this is only important - if you want to visualize the result of your HTTP request in your dashboard.

Here an example how to set your fan - when humidity and temperature exceed a certain level and you want to set your fan with HTTP. The inject node simulate your sensor

[{"id":"978e22d662423438","type":"inject","z":"53d8621916dd6725","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"sensor1/humidity","payload":"70","payloadType":"num","x":250,"y":620,"wires":[["88fb3ca6f35f8901"]]},{"id":"01b98e3215e500ac","type":"inject","z":"53d8621916dd6725","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"sensor1/temperature","payload":"30","payloadType":"num","x":240,"y":680,"wires":[["d9079bfc6068491b"]]},{"id":"88fb3ca6f35f8901","type":"change","z":"53d8621916dd6725","name":"","rules":[{"t":"move","p":"payload","pt":"msg","to":"payload.humidity","tot":"msg"},{"t":"set","p":"sensor1.humidity","pt":"flow","to":"payload.humidity","tot":"msg"},{"t":"set","p":"payload.temperature","pt":"msg","to":"sensor1.temperature","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":470,"y":620,"wires":[["47124d2ade94b4e7"]]},{"id":"d9079bfc6068491b","type":"change","z":"53d8621916dd6725","name":"","rules":[{"t":"move","p":"payload","pt":"msg","to":"payload.temperature","tot":"msg"},{"t":"set","p":"sensor1.temperature","pt":"flow","to":"payload.temperature","tot":"msg"},{"t":"set","p":"payload.humidity","pt":"msg","to":"sensor1.humidity","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":470,"y":680,"wires":[["5b0bcf563fc41b4f"]]},{"id":"47124d2ade94b4e7","type":"switch","z":"53d8621916dd6725","name":"temperature available?","property":"sensor1.temperature","propertyType":"flow","rules":[{"t":"nnull"}],"checkall":"true","repair":false,"outputs":1,"x":700,"y":620,"wires":[["4322aaa47791a844"]]},{"id":"5b0bcf563fc41b4f","type":"switch","z":"53d8621916dd6725","name":"humidity available?","property":"sensor1.humidity","propertyType":"flow","rules":[{"t":"nnull"}],"checkall":"true","repair":false,"outputs":1,"x":690,"y":680,"wires":[["4322aaa47791a844"]]},{"id":"4322aaa47791a844","type":"switch","z":"53d8621916dd6725","name":"temperature > 25 and humidity >60","property":"payload.temperature > 25 and payload.humidity >60","propertyType":"jsonata","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":980,"y":640,"wires":[["7c7f656138307d1e"],["d569348cd46cafae"]]},{"id":"d0fed0b1d7c5afbc","type":"inject","z":"53d8621916dd6725","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"sensor1/humidity","payload":"50","payloadType":"num","x":250,"y":580,"wires":[["88fb3ca6f35f8901"]]},{"id":"01aec6cc6c10c94b","type":"inject","z":"53d8621916dd6725","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"sensor1/temperature","payload":"25","payloadType":"num","x":240,"y":720,"wires":[["d9079bfc6068491b"]]},{"id":"d569348cd46cafae","type":"change","z":"53d8621916dd6725","name":"set off","rules":[{"t":"set","p":"url","pt":"msg","to":"http://192.168.0.200/api/HomeAutomation.X10/C1/Control.Off","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1210,"y":680,"wires":[["cf6ebcddd84fb097"]]},{"id":"7c7f656138307d1e","type":"change","z":"53d8621916dd6725","name":"set on","rules":[{"t":"set","p":"url","pt":"msg","to":"http://192.168.0.200/api/HomeAutomation.X10/C1/Control.On","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1210,"y":620,"wires":[["cf6ebcddd84fb097"]]},{"id":"cf6ebcddd84fb097","type":"http request","z":"53d8621916dd6725","name":"","method":"GET","ret":"txt","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","authType":"","senderr":false,"x":1390,"y":640,"wires":[["cf4fa62c8af4a053"]]},{"id":"cf4fa62c8af4a053","type":"debug","z":"53d8621916dd6725","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1570,"y":640,"wires":[]}]

This is without coding and using function nodes. I have used the flow context to compare humidity and temperature. So you expand the example with other sensors.

EDIT: changed set on msg.url

The original question was about comparing values from 2 separate senors to determine whether a fan should be turned on/off. Since the sensors are asynchronous I must use context to store the values and perform a comparison at a set period. To my knowledge, there is no way to do this with standard NR nodes. Function nodes are a standard component of NR and AFAIK a very commonly used node to do non-standard things (like working with context). Actually, I was recommended to use a function node the last time I had a query about context too.

I'm open to other options, though. I wasn't going to post my main function node I've been working on since it's not complete and very messy. But, if it helps here's what I've come up with so far.

[{"id":"8a0c4ff6.b20968","type":"link in","z":"767e039c.16db54","name":"Acurite Tower","links":["7d1a9097.b1af8"],"x":35,"y":60,"wires":[["e29418ae.d60608"]]},{"id":"e29418ae.d60608","type":"function","z":"767e039c.16db54","name":"","func":"switch (msg.payload[1].location)\n{\n case \"test1\":\n //node.warn(\"tag = test1\");\n flow.set(\"test1_tempF\", msg.payload[0].tempF, \"file\");\n flow.set(\"test1_humidity\", msg.payload[0].humidity, \"file\");\n flow.set(\"test1_moisture\", msg.payload[0].moisture, \"file\");\n break;\n \n case \"test2\":\n //node.warn(\"tag = test2\");\n flow.set(\"test2_tempF\", msg.payload[0].tempF, \"file\");\n flow.set(\"test2_humidity\", msg.payload[0].humidity, \"file\");\n flow.set(\"test2_moisture\", msg.payload[0].moisture, \"file\");\n break;\n \n}\n\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":170,"y":60,"wires":[[]]},{"id":"7a0916d4.5be38","type":"inject","z":"767e039c.16db54","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"*/1 0-23 * * *","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":120,"y":140,"wires":[["91b61372.06ea78"]]},{"id":"91b61372.06ea78","type":"function","z":"767e039c.16db54","name":"","func":"var temp_timer = 10; //10 minute\nvar temp_delta = 10; //10F delta\nvar humidity_timer = 10; //10 minute\nvar humidity_delta = 10; //10%RH delta\n\n//retrieve data\n// location \"test1\"\nvar test1_tempF = context.get(\"test1_tempF\",\"file\");\nif (typeof test1_tempF === \"undefined\") {\n test1_tempF = 60;\n}\nvar test1_humidity = context.get(\"test1_humidity\",\"file\");\nif (typeof test1_humidity === \"undefined\") {\n test1_humidity = 50;\n}\n\n// location \"test2\"\nvar test2_tempF = context.get(\"test2_tempF\",\"file\");\nif (typeof test2_tempF === \"undefined\") {\n test2_tempF = 60;\n}\nvar test2_humidity = context.get(\"test2_humidity\",\"file\");\nif (typeof test2_humidity === \"undefined\") {\n test2_humidity = 50;\n}\n\n\n//temp conrol, test1\nif (test1_tempF-temp_delta > test2_tempF)\n{\n //test1 is higher than test2 plus delta\n //turn fan on for x minutes or until false\n var test1_temp_timer = temp_timer;\n //turn on fan\n} else if (test1_temp_timer > 0) {\n test1_temp_timer = test1_timer-1;\n} else {\n test1_temp_timer = 0;\n //turn off fan\n}\n\n//humidity conrol, test1\nif (test1_humidity-humidity_delta > test2_humidity)\n{\n //test1 is higher than test2 plus delta\n //turn fan on for x minutes or until false\n var test1_humidity_timer = temp_timer;\n //turn on fan\n} else if (test1_timer > 0) {\n test1_humidity_timer = test1_timer-1;\n} else {\n test1_humidity_timer = 0;\n //turn off fan\n}\n\nreturn msg;\n\n\n\n\n// http://192.168.0.200/api/HomeAutomation.X10/C1/Control.On","outputs":1,"noerr":0,"initialize":"","finalize":"","x":270,"y":140,"wires":[[]]}]

My reply was submitted before seeing your suggestion flow. The flow you provided would work for a single sensor to determine if the temp/humidity were appropriate to turn a fan on/off. What I'm trying to do is use 2 sensors to determine if I want to turn a fan on/off. The fan exhausts my attic and I want to turn it on if the air in the attic is hot and/or humid. However, if the air conditions inside are better than outside, I don't want to bring in outside air.

I have an Arduino based controller now that does this but it's old and I have no way to see what the conditions are. I'm basically replacing it with a like controller that will be based on my RPi and wireless sensors instead. The logic isn't complete in the example I posted, but I just want to get something working based on differential temperatures and humidity.

So it is not relevant how you get to your decision - so I used your function node and create the url message.
So your function node will send the url directly:

[
    {
        "id": "36be653c1b613e2c",
        "type": "function",
        "z": "53d8621916dd6725",
        "name": "",
        "func": "var temp_timer = 10; //10 minute\nvar temp_delta = 10; //10F delta\nvar humidity_timer = 10; //10 minute\nvar humidity_delta = 10; //10%RH delta\n\n//retrieve data\n// location \"test1\"\nvar test1_tempF = context.get(\"test1_tempF\",\"file\");\nif (typeof test1_tempF === \"undefined\") {\n test1_tempF = 60;\n}\nvar test1_humidity = context.get(\"test1_humidity\",\"file\");\nif (typeof test1_humidity === \"undefined\") {\n test1_humidity = 50;\n}\n\n// location \"test2\"\nvar test2_tempF = context.get(\"test2_tempF\",\"file\");\nif (typeof test2_tempF === \"undefined\") {\n test2_tempF = 60;\n}\nvar test2_humidity = context.get(\"test2_humidity\",\"file\");\nif (typeof test2_humidity === \"undefined\") {\n test2_humidity = 50;\n}\n\n\n//temp conrol, test1\nif (test1_tempF-temp_delta > test2_tempF)\n{\n //test1 is higher than test2 plus delta\n //turn fan on for x minutes or until false\n var test1_temp_timer = temp_timer;\n //turn on fan\n msg.url=\"http://192.168.0.200/api/HomeAutomation.X10/C1/Control.On\";\n} else if (test1_temp_timer > 0) {\n test1_temp_timer = test1_timer-1;\n} else {\n test1_temp_timer = 0;\n //turn off fan\n  msg.url=\"http://192.168.0.200/api/HomeAutomation.X10/C1/Control.Off\";\n}\n\n//humidity conrol, test1\nif (test1_humidity-humidity_delta > test2_humidity)\n{\n //test1 is higher than test2 plus delta\n //turn fan on for x minutes or until false\n var test1_humidity_timer = temp_timer;\n //turn on fan\n  msg.url=\"http://192.168.0.200/api/HomeAutomation.X10/C1/Control.On\";\n} else if (test1_timer > 0) {\n test1_humidity_timer = test1_timer-1;\n} else {\n test1_humidity_timer = 0;\n //turn off fan\n  msg.url=\"http://192.168.0.200/api/HomeAutomation.X10/C1/Control.Off\";\n}\n\nreturn msg;\n\n\n\n\n// http://192.168.0.200/api/HomeAutomation.X10/C1/Control.On",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 940,
        "y": 1160,
        "wires": [
            [
                "73fa904b06f8c1c7"
            ]
        ]
    },
    {
        "id": "73fa904b06f8c1c7",
        "type": "http request",
        "z": "53d8621916dd6725",
        "name": "",
        "method": "GET",
        "ret": "txt",
        "paytoqs": "ignore",
        "url": "",
        "tls": "",
        "persist": false,
        "proxy": "",
        "authType": "",
        "senderr": false,
        "x": 1130,
        "y": 1160,
        "wires": [
            [
                "065f570cc60b7dc2"
            ]
        ]
    },
    {
        "id": "065f570cc60b7dc2",
        "type": "debug",
        "z": "53d8621916dd6725",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 1310,
        "y": 1160,
        "wires": []
    }
]

I only added the msg.url under your comments:

 //turn off fan
  msg.url="http://192.168.0.200/api/HomeAutomation.X10/C1/Control.Off";

The logic itself is yours - and I didn't care about.

You can do something like you have done with a large function block but you are not using the best features of NR

You can use the Change nodes and the switch nodes to interfact with Context variables

The Change node can set and read context variables, the switch nodes can read them and make decisions based on values etc

In my case i will typcially have a flow that brings the incoming sensor values in (through MQTT etc) and will then use a Change node to set them as Global context values

My flows that then need them either grab them from context using another change node and put them into a message - or a switch node will check them and make brancing decisions.

In Your case where you want to compare multiple values - you just chain mutliple switch nodes in a line.

Obviously at some point it becomes more sensible to write this as a function node - but for simplistic operations i try and keep to the visual side of things

I woud say 90% of the control of my home automation is done with visual nodes rather than function blocks

Craig

If you can put your function code as listed into the correct format (between the triple ticks) i will have a look and see how hard it would be to do it with change and switch nodes

Craig

This is the function node. I have another function node that gets the sensor values and dumps them to global context which could be done with switch/change nodes.

var temp_timer = 10.0; //10 minute minimum run time
var temp_delta = 10.0; //10F delta between sensors
var temp_threshold = 60.0; //80F minimum temp
var humidity_timer = 10.0; //10 minute
var humidity_delta = 10.0; //10%RH delta

var N_fan_on  = "http://192.168.0.200/api/HomeAutomation.X10/G1/Control.On";
var N_fan_off = "http://192.168.0.200/api/HomeAutomation.X10/G1/Control.Off";


//retrieve data
// location "N_Attic"
var N_Attic_tempF = flow.get("N_Attic_tempF","file");
if (typeof N_Attic_tempF === "undefined") {
    N_Attic_tempF = 60.0;
}
var N_Attic_humidity = flow.get("N_Attic_humidity","file");
if (typeof N_Attic_humidity === "undefined") {
    N_Attic_humidity = 50.0;
}

// location "Outside"
var Outside_tempF = flow.get("Outside_tempF","file");
if (typeof Outside_tempF === "undefined") {
    Outside_tempF = 60.0;
}
var Outside_humidity = flow.get("Outside_humidity","file");
if (typeof Outside_humidity === "undefined") {
    Outside_humidity = 50.0;
}

//N_fan context
var N_fan_temp_timer = context.get("N_fan_temp_timer","file");
if (typeof N_fan_temp_timer === "undefined") {
    N_fan_temp_timer = 0.0;
    context.set("N_fan_temp_timer", N_fan_temp_timer, "file");
}
var N_fan_state = context.get("N_fan_state","file");
if (typeof N_fan_state === "undefined") {
    N_fan_state = 0.0;
    context.set("N_fan_state", N_fan_state, "file");
}


//temp conrol, N_Attic
if ( ((N_Attic_tempF-temp_delta) > Outside_tempF)  &&
      (N_Attic_tempF>temp_threshold))  {
    node.warn("temp high");
    context.set("N_fan_temp_timer", temp_timer, "file");
    context.set("N_fan_state", 1.0, "file");
    msg.url=N_fan_on;
}
else if ((N_fan_state == 1) &&
         (N_fan_temp_timer > 0.0)) {
    node.warn("timer running");
    context.set("N_fan_temp_timer", N_fan_temp_timer-1.0, "file");
}
else if (N_fan_state == 1) {
    node.warn("timer elapsed");
    context.set("N_fan_state", 0.0, "file");
    context.set("N_fan_temp_timer", 0.0, "file");
    msg.url=N_fan_off;
}

return msg;

This is not the complete function as I have only implemented temperature right now. I intend to have humidity as well which may end up using moisture content of the air (derived from humidity and temperature). It will be similar to the temperature code so after confirming this works as intended I'll expand. As of this afternoon, I can take the outside sensor and put it in the fridge to drive the temperature delta large enough for the code to trigger on/off of the fan module.

Yeah well there is nothing hard in there - i would move all of that to visual nodes - much easier to maintan on an ongoing basis

I too had a similar path to yourself - my initial home automation system was written on Picaxe micros (using basic) and then evolved to using Arduinos as they came on the scene and 20x4 LCD panels

I then wanted external visualisation so implemented a monitoring system in NR to present a dashboard of what was going on and limited control and then slowly migrated all the logic across to NR and retired the Arduinos.

Initially i replicated the functionality of the Arduino Code in Function Code blocks as it was easy to take XYZ function and translate the inputs and outputs and code that up in Javascript - later i then went back and started converting each of the functions into as much Visual programming using Nodes as i could.

Everywhere you have an IF - substitute this with a switch node - if you have an embedded/Nested IF then that switch node option leads to another switch node

There are lots of timer nodes to do something for x period of time - my favourite one is MyTimeout by Neil Cherry

In terms of your http commands - instead of embedding them - do a hhtp out node - one for on one for off and put the URL into each of those and call them from the appropriate places.

Give it a go you will be pleasantly surprised how easy it is once you get started.

Craig

BTW - X10 (as i am sure you know) never really went anywhere in OZ, would suggest it would be a fairly cheap exercise to replace the X10 power control with a couple of smart sockets (shelly or sonoff) that could be driven by MQTT from your code

As a further aside - you can purchase from Sonoff (what was the TH10/16) but they have now replaced with a newer fancier unit - flash this with Tasmota and using Tasmota rules this can do all of this for you (take temperature and humidity measurements and automatically turn on and off an inbuilt relay (to control the fan etc)

Craig

I took a look at using switch and change nodes for the storing of sensor data to context and it's actually not as hard as I thought and it does look a little cleaner. I'll have to look at what would be required to do the same for the fan control.

[{"id":"8a0c4ff6.b20968","type":"link in","z":"767e039c.16db54","name":"Acurite Tower","links":["7d1a9097.b1af8"],"x":55,"y":80,"wires":[["5111ec88.e7f4bc","32997971.21353e"]]},{"id":"b4bd3207.7fa32","type":"debug","z":"767e039c.16db54","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":570,"y":80,"wires":[]},{"id":"5111ec88.e7f4bc","type":"debug","z":"767e039c.16db54","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":150,"y":20,"wires":[]},{"id":"32997971.21353e","type":"switch","z":"767e039c.16db54","name":"","property":"payload[1].location","propertyType":"msg","rules":[{"t":"eq","v":"N_Attic","vt":"str"},{"t":"eq","v":"S_Attic","vt":"str"},{"t":"eq","v":"Outside","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":170,"y":80,"wires":[["9a842204.a0376","de445b7e.20965"],["de445b7e.20965","136f6945.3711af"],["de445b7e.20965","3e7a0036.621e7"]]},{"id":"9a842204.a0376","type":"change","z":"767e039c.16db54","name":"N_Attic sensor","rules":[{"t":"set","p":"#:(file)::N_Attic_tempF","pt":"flow","to":"payload[0].tempF","tot":"msg"},{"t":"set","p":"#:(file)::N_Attic_humidity","pt":"flow","to":"payload[0].humidity","tot":"msg"},{"t":"set","p":"#:(file)::N_Attic_moisture","pt":"flow","to":"payload[0].moisture","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":380,"y":40,"wires":[["b4bd3207.7fa32"]]},{"id":"de445b7e.20965","type":"debug","z":"767e039c.16db54","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":370,"y":160,"wires":[]},{"id":"136f6945.3711af","type":"change","z":"767e039c.16db54","name":"S_Attic sensor","rules":[{"t":"set","p":"#:(file)::S_Attic_tempF","pt":"flow","to":"payload[0].tempF","tot":"msg"},{"t":"set","p":"#:(file)::S_Attic_humidity","pt":"flow","to":"payload[0].humidity","tot":"msg"},{"t":"set","p":"#:(file)::S_Attic_moisture","pt":"flow","to":"payload[0].moisture","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":380,"y":80,"wires":[["b4bd3207.7fa32"]]},{"id":"3e7a0036.621e7","type":"change","z":"767e039c.16db54","name":"Outside sensor","rules":[{"t":"set","p":"#:(file)::Outside_tempF","pt":"flow","to":"payload[0].tempF","tot":"msg"},{"t":"set","p":"#:(file)::Outside_humidity","pt":"flow","to":"payload[0].humidity","tot":"msg"},{"t":"set","p":"#:(file)::Outside_moisture","pt":"flow","to":"payload[0].moisture","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":380,"y":120,"wires":[["b4bd3207.7fa32"]]}]

X10 is no longer a standard I would recommend for new users. The modules required to make a stable system are not easily sourced. Once you have a stable system though, it does work as well if not better than many of the wireless protocols. No system is perfect of course.

I have tried rolling my own controllers/devices with Arduino, ESP8266, Sonoff, etc. I have one device (a simple temp/humidity/pressure sensor with nodeMCU+BME280) and one controller (attic fan controller via Arduino Uno + 3x DHT11). The funny thing is, my most successful/stable device has no control really. I modified my shower fan so that it's variable speed depending on occupancy. When the light is switched on, a relay switches to using a rheostat (simple light dimmer) so that the overly effective exhaust fan doesn't make the shower too drafty in the winter.

After I modified to using change/switch nodes to store the sensor values to context I have gone back to review @mickym2's example again. Initially I was unclear on how this would help but now I see that this snippet can be used to both store and trigger the control code. If the data isn't available in context, it will drop out. I would have to make it a little more detailed and add timers, but I may be able to use a similar form.

Yep exactly - spend your time building safeguards and warnings in there instead of cutting and debugging raw code.

There is definitely a place for function nodes - just needs a brain switch if you have come from a traditional coding environment into using visual nodes

If you get stuck throw your stuff up and someone will help out

Craig

Here is a bit of a warning code another member and i did the other day - basically if he does not get a reading from a sensor for a set period of time will send him out a warning email - you could adapt to your own flow - if no temp and humidity readings come in for 10 minutes then send out an email etc

[{"id":"4116634517e3306e","type":"inject","z":"f26be17e8371ed49","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"cmnd/Eferm/RESTART","payload":"1","payloadType":"num","x":1147.999927520752,"y":474.66662216186523,"wires":[["4e38f555c6d9198d"]]},{"id":"4e38f555c6d9198d","type":"ui_button","z":"f26be17e8371ed49","name":"","group":"efd7b7c424179c82","order":1,"width":0,"height":0,"passthru":true,"label":"REBOOT","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"1","payloadType":"str","topic":"cmnd/Eferm/RESTART","topicType":"str","x":1033.1404609680176,"y":401.260461807251,"wires":[["91cf12dafda44aa6"]]},{"id":"2554e0f8a04882c5","type":"trigger","z":"f26be17e8371ed49","name":"","op1":"East sensor working","op2":"On","op1type":"str","op2type":"str","duration":"20","extend":true,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":2,"x":808.999927520752,"y":388.66662216186523,"wires":[["a546c0789ab294c3","cfa791f0c5562210","ef91a1be43e2d90b"],["e7f599241080d140","4e38f555c6d9198d","39f3f941ec91fe0d"]]},{"id":"e7f599241080d140","type":"debug","z":"f26be17e8371ed49","name":"sensor bad - after trigger node","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":827.999927520752,"y":454.66662216186523,"wires":[]},{"id":"a546c0789ab294c3","type":"debug","z":"f26be17e8371ed49","name":"sensor good","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":828.6542472839355,"y":338.8375415802002,"wires":[]},{"id":"cfa791f0c5562210","type":"e-mail","z":"f26be17e8371ed49","server":"smtp.gmail.com","port":"465","secure":true,"tls":true,"name":"","dname":"","x":1021.201099395752,"y":363.09362411499023,"wires":[]},{"id":"9103d59c0ba7bc4f","type":"link in","z":"f26be17e8371ed49","name":"","links":["7c4cbc1a995ee8c7"],"x":637.499927520752,"y":385.66662216186523,"wires":[["2554e0f8a04882c5"]]},{"id":"91cf12dafda44aa6","type":"mqtt out","z":"f26be17e8371ed49","name":"","topic":"cmnd/Eferm/RESTART","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"31a99116.50a74e","x":1264.7326164245605,"y":400.20378494262695,"wires":[]},{"id":"4ff491866d520281","type":"e-mail","z":"f26be17e8371ed49","server":"smtp.gmail.com","port":"465","secure":true,"tls":true,"name":"","dname":"","x":1427.999927520752,"y":614.6666221618652,"wires":[]},{"id":"7fa120dae191cb94","type":"gate","z":"f26be17e8371ed49","name":"","controlTopic":"control","defaultState":"open","openCmd":"open","closeCmd":"close","toggleCmd":"toggle","defaultCmd":"default","statusCmd":"status","persist":false,"storeName":"default","x":1307.999927520752,"y":614.6666221618652,"wires":[["4ff491866d520281","1501ea9b0bca97d8"]]},{"id":"39f3f941ec91fe0d","type":"mytimeout","z":"f26be17e8371ed49","name":"Close the Gate after a single message","outtopic":"","outsafe":"Sensor Failed","outwarning":"","outunsafe":"off","warning":"5","timer":"10","debug":false,"ndebug":false,"ignoreCase":false,"repeat":false,"again":false,"x":967.999927520752,"y":594.6666221618652,"wires":[["7fa120dae191cb94","32a287476e0c7b61"],["b4cecd141e2cf638"]]},{"id":"32a287476e0c7b61","type":"debug","z":"f26be17e8371ed49","name":"After Timer","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1287.999927520752,"y":554.6666221618652,"wires":[]},{"id":"b4cecd141e2cf638","type":"change","z":"f26be17e8371ed49","name":"Close the Gate","rules":[{"t":"set","p":"payload","pt":"msg","to":"Close","tot":"str"},{"t":"set","p":"topic","pt":"msg","to":"control","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1097.999927520752,"y":654.6666221618652,"wires":[["7fa120dae191cb94"]]},{"id":"f1906a28f60b7adc","type":"link out","z":"f26be17e8371ed49","name":"Fermenter (East) Out to Gate Control","mode":"link","links":["db77261593112c67","ced8b5b2d4b0a8dd"],"x":1532.999927520752,"y":314.66662216186523,"wires":[]},{"id":"ef91a1be43e2d90b","type":"change","z":"f26be17e8371ed49","name":"Open The Gate after a Good Temperature Read","rules":[{"t":"set","p":"payload","pt":"msg","to":"Open","tot":"str"},{"t":"set","p":"topic","pt":"msg","to":"control","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1257.999927520752,"y":314.66662216186523,"wires":[["f1906a28f60b7adc"]]},{"id":"7600e67381944b81","type":"inject","z":"f26be17e8371ed49","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":600,"y":320,"wires":[["2554e0f8a04882c5"]]},{"id":"1501ea9b0bca97d8","type":"debug","z":"f26be17e8371ed49","name":"After Gate","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1470,"y":580,"wires":[]},{"id":"efd7b7c424179c82","type":"ui_group","name":"East Fermenternew","tab":"a74ffe3d.4eb73","order":7,"disp":true,"width":24,"collapse":false},{"id":"31a99116.50a74e","type":"mqtt-broker","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"a74ffe3d.4eb73","type":"ui_tab","name":"Brewery","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

Craig

Thanks for the reply. Maybe I'm missing something but it appears that the code you provided is the same as what I posted earlier.

Aah sorry - it is !!!

Let me change it to what i meant to post - must have been caught in my clipboard !!)

Craig