RF433Mhz Transceiver

Introduction

The 433Mhz transmitter (FS1000A) and receiver (MX-RM-5V) modules are widely available online. The source code below used on the PICO to decode and transmit 433 Mhz signals originates from :  pico433mhz/src at main · AdrianCX/pico433mhz · GitHub 2 with more info at

GitHub - AdrianCX/pico433mhz: Simple raspberry pi pico 433mhz receiver/transmitter controlled via http 


The transmitter module is powered by the Pico's 5v output from VBUS. No additional antennas were used, but successfully transmits through exterior/interior walls without issue. 


Sniffing/Sending RF key codes

Start the receiver with an HTTP call to the Pico's ip address. Open a command prompt in Windows and enter:
curl -v "http://[ip-address]:80/receive"
after getting "200  OK" bring the RF Remote very close to the RF receiver module attached to the PICO and press the remote button to be sniffed a few times. After 15 seconds the it will return the key details.

To send the key code enter:
curl -v "http://[ip-address]:80/send/[code]/[protocol]/[pulselength]"

Preliminary Node Red Design

The UI to the PICO was through a  Raspberry pi running Node Red.The preliminary design utilized ui-switches, typically limited to on or off states, for controlling the RF plugs. Unfortunately, there was no indication in the user interface (UI) regarding the success of the HTTP request to the PICO. However, this issue was resolved in the subsequent iteration of the Flow by incorporating template nodes and CSS classes, enabling the addition of extra appearance states to the ui-switch icons. 

Node Red

Additional Nodes used: node-red-contrib-arp 

PICO mac address assigned to an Environmental Variable

The mac address is a device's fundamental physical address whereas its ip address on a network is not fixed, but it can be determined from its mac address.

The fixed mac address of the PICO is therefore stored as a Constant by assigning it to to a Group Environmental variable RFmac during Flow creation.


Automatic finding of the PICO ip

At boot-up the environmental variable RFmac is set to a Flow context scope in Set Flow RFmac Function node.

if (flow.get("RFmac")===undefined){
    flow.set("RFmac",env.get("RFmac"))}

return msg;

To automate the retrieval of the PICO IP address from the ARP node, the PICO MAC address needs to be provided as msg.payload.macs input. The Function node Set msg.payload.macs  adds the object macs to the payload by first adding an empty object to the msg.payload with JSON {} then uses a second rule to add and set msg.payload.macs to the value of flow.RFmac. The msg.payload output from the ARP node is an array of objects containing :

The IP address is extracted from an array by accessing the "ip" object within the array in the ARP node's msg.payload. The extracted IP address is then stored in flow.PICOip by assigning it the value msg.payload[0].ip in the Set flow.PICOip Change node.

To test the PICO connection with an inappropriate IP, you can use the "TEST corrupt PICOip connection" feature. This allows you to set an invalid IP address for the PICO for testing purposes.

If the RF HTTP Request fails to find the PICO's IP address because it has changed on the network, the Catch node (RF HTTP Request Error) is triggered. In such cases, the system automatically initiates the process of finding the IP address from the PICO's MAC address. 

The UI

The Main Flow

Flow Description

This flow controls a group of RF (radio frequency) plugs using a user interface. Here's an overview of its logic:

1. The program starts with a group node that contains multiple nodes related to controlling RF plugs.

2. Each RF plug is represented by a "ui_switch" node, which provides a switch interface to turn the plug on or off.

3. The "ui_switch" nodes are connected to an "http request" node, which sends an HTTP GET request to control the RF plugs, and to the "function" node Set State which sets the "ui-switch" appearance states. However these instructions are blocked by the "switch" node Stateless if the msg.StatusCode has already been set. This ensures that following an http request the feedback to the switch only affects the "ui-switch" change of state and does not propagate repeated http-requests. 

4. There is also a "Kill Switch" represented by another "ui_switch" node, which, when turned on, stops the flow of messages and disables the other switches.

5. When the "Kill Switch" is turned on, a "change" node called "Disable Switches" sets the `enabled` property of the messages to `false`, effectively disabling the switches.

6. If the "Kill Switch" is turned off, a "switch" node called "Enable Switches" checks the type of the payload received. If it is a number, it means the switch is enabled, and the `enabled` property is set to `true`.

7. The "Enable Switches" node is connected to a "delay" node called "Switch off all," which introduces a delay of 50 milliseconds before proceeding.

8. After the delay, the messages from the "Switch off all" node are sent to an "http request" node to control the RF plugs.

9. The responses from the "http request" node are then passed to a "function" node called "Set State," which keeps track of the state of each switch and stores it in the flow context. [See Setting the State]

10. The "Set State" function node also responds with the current state and the `enabled` property when a "get state" message is received.

11. The state information is used to control the appearance of the switches in a user interface by modifying the CSS classes of the corresponding "ui_switch" nodes.

The appearance of the switches is controlled by the change and template nodes when a HTTP GET request is made and whether it is successful or not.

In the template node labeled "Auto Set flow.RFmac from Group env", the CSS styles defined in the template are applied based on the className property of the message. The UI switches associated with the corresponding Node IDs and their respective states are styled accordingly. For example, the SUCCESS class sets the switches to black when they are in the "on" state, the REQ class sets them to orange to indicate a request, and the FAIL class sets them to red when they are in the "off" state.

By dynamically changing the className property based on the outcome of the HTTP GET request, the appearance of the switches is updated in the Node-RED dashboard to provide visual feedback to the user.


The Template node is responsible for customizing the appearance of the UI switches in the Node-RED dashboard. It adds CSS styles to the switches based on their status, such as whether they are on or off, or if there is a request being made.

Here's an overview of the CSS classes and their corresponding styles defined in the template node:

By applying these CSS styles to the UI switches, the template node allows for visual feedback to indicate the state of the switches and their interaction with the underlying logic.

<style>


/* Specifying with selector for ON/OFF icons */

md-card.nr-dashboard-switch.SUCCESS ng-md-icon[icon="power"] {

    color:black;

}

md-card.nr-dashboard-switch.SUCCESS ng-md-icon[icon="power_settings_new"] {

    color:grey;



/* Or just the icon holder to address both ON/OFF*/

md-card.nr-dashboard-switch.REQ ng-md-icon {

    color:orange;

}

md-card.nr-dashboard-switch.FAIL ng-md-icon {

    color:red;

}


/* Or address every individual Node ID ON/OFF*/


[node-id="24bd5be440d33cda"].SUCCESS ui-icon[icon="power"]{

    background-color:yellow;

}

[node-id="24bd5be440d33cda"].REQ ui-icon{

    background-color:moccasin;

}

[node-id="24bd5be440d33cda"].FAIL ui-icon{

    background-color:lightpink;

}

/*

moccasin

*/

</style> 

Overall, this program provides a user interface to control RF plugs, with the ability to enable/disable switches, send HTTP requests to control the plugs, and update the UI based on the state of the switches and the success of th HTTP requests.

Setting the State

Code Explanation

This code handles the logic for managing the state of the three switches in the flow. 

    let state = flow.get('state') || false;

Retrieves the current state from the flow context using the key 'state'. If the state is not found (i.e., it is undefined or null), it assigns false as the default value to the state variable.


    let enabled = msg.enabled;

Assigns the value of the enabled property from the msg object to the enabled variable. The msg object seems to contain additional information relevant to the switches' behavior.

The next three if statements each check the value of msg.topic to determine which switch is being referenced, and update state  with the value of msg.payload. The updated state is then stored in the flow context with the key 'state[n]'.


    if (msg.payload == 'get state')

This condition checks if msg.payload is equal to the string 'get state'. If true, it implies that a request has been made to retrieve the state, and the function returns an object with two properties: 'payload' and 'enabled'. 

Otherwise, the code reaches the return statement having updated the state . In this case, the function returns without any value.

Overall, this code maintains the state of three switches and allows retrieval of the state along with the value of the enabled property when a message with 'get state' as the payload is received.

The Switch nodes either side of HTTP Request node

Source code

secrets.py 

ssid = '[network-id]'

password = '[network-password]'

config.py


webserver.py


rfdevice.py


PICO RF Plug  - Json

[{"id":"499986b708243758","type":"group","z":"7b8aeb13a838c3a9","name":"","style":{"label":true},"nodes":["7a1b2a8e3fa2892f","bcc7f0184044a95","8627665a21e7567e","24bd5be440d33cda","101466721f96e9e3","ba1dfaacfcb18043","b7df18a0978a4276","fbe48de11edf1844","7da542c359c88b1c","21fb58d982ffbe36","f3a0d2cf34bf1339","0dd2f232a0ae5707","1e1c12124eafa51d","b4ae72e69cb16ff0","870ab5ac5694608d","3e52cb8af3e6e67b","bef45f6e3242fe8a","989247450d943d5e","8e30af2313c0a6da","6a40bdb70a7f6913","20dbb5413d08b7c0","326e2fe94b41c22d","11a870bedd976ac7","8e9ab02b4db9369b","46de8be0c0575230","2ad8af6c7973b8df","0ebc69e2b0f31727","b3825ad25e4e15e7"],"env":[{"name":"RFmac","value":"28:CD:C1:07:D4:6D","type":"str"}],"x":54,"y":679,"w":1052,"h":842},{"id":"7a1b2a8e3fa2892f","type":"ui_switch","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"","label":"Plug-3","tooltip":"","group":"61e6d4c4.e7619c","order":2,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"switch_3","topicType":"str","style":"","onvalue":"14683469","onvalueType":"num","onicon":"power","oncolor":"red","offvalue":"14683461","offvalueType":"str","officon":"power_settings_new","offcolor":"red","animate":true,"className":"","x":670,"y":1320,"wires":[["326e2fe94b41c22d"]]},{"id":"bc4c7f0184044a95","type":"http request","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"RF http request","method":"GET","ret":"txt","paytoqs":"body","url":"http://{{ip}}/send/{{payload}}/1/306","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":640,"y":1060,"wires":[["b3825ad25e4e15e7"]]},{"id":"8627665a21e7567e","type":"ui_switch","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"","label":"Plug-2","tooltip":"","group":"61e6d4c4.e7619c","order":3,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"switch_2","topicType":"str","style":"","onvalue":"14683467","onvalueType":"num","onicon":"power","oncolor":"red","offvalue":"14683459","offvalueType":"str","officon":"power_settings_new","offcolor":"red","animate":true,"className":"","x":670,"y":1260,"wires":[["326e2fe94b41c22d"]]},{"id":"24bd5be440d33cda","type":"ui_switch","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"","label":"Plug-1","tooltip":"","group":"61e6d4c4.e7619c","order":4,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"switch_1","topicType":"str","style":"","onvalue":"14683471","onvalueType":"num","onicon":"power","oncolor":"red ","offvalue":"14683463","offvalueType":"str","officon":"power_settings_new","offcolor":"red ","animate":true,"className":"<\"fa-1.5x\">","x":670,"y":1200,"wires":[["326e2fe94b41c22d"]]},{"id":"101466721f96e9e3","type":"ui_switch","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"","label":"Kill Switch","tooltip":"","group":"61e6d4c4.e7619c","order":5,"width":0,"height":0,"passthru":false,"decouple":"false","topic":"topic","topicType":"msg","style":"","onvalue":"14683464","onvalueType":"num","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","animate":false,"className":"","x":170,"y":1480,"wires":[["b7df18a0978a4276"]]},{"id":"ba1dfaacfcb18043","type":"change","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"Disable Switches","rules":[{"t":"set","p":"payload","pt":"msg","to":"get state","tot":"str"},{"t":"set","p":"enabled","pt":"msg","to":"false","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":1380,"wires":[["21fb58d982ffbe36"]]},{"id":"b7df18a0978a4276","type":"switch","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"","property":"payload","propertyType":"msg","rules":[{"t":"istype","v":"number","vt":"number"},{"t":"istype","v":"boolean","vt":"boolean"}]"checkall":"true","repair":false,"outputs":2,"x":170,"y":1400,"wires":[["ba1dfaacfcb18043","7da542c359c88b1c"],["fbe48de11edf1844"]]},{"id":"fbe48de11edf1844","type":"change","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"Enable Switches","rules":[{"t":"set","p":"payload","pt":"msg","to":"get state","tot":"str"},{"t":"set","p":"enabled","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":1420,"wires":[["21fb58d982ffbe36"]]},{"id":"7da542c359c88b1c","type":"delay","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"Switch off all","pauseType":"delay","timeout":"50","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":210,"y":1060,"wires":[["8e9ab02b4db9369b"]]},{"id":"21fb58d982ffbe36","type":"function","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"Set State","func":"let state = flow.get('state') || false;\nlet enabled = msg.enabled;\n\nif (msg.topic == 'switch_1') {\n    state= msg.payload;\n    flow.set('state[0]', state);\n}\nif (msg.topic == 'switch_2') {\n    state = msg.payload;\n    flow.set('state[1]', state);\n}\nif (msg.topic == 'switch_3') {\n    state = msg.payload;\n    flow.set('state[2]', state);\n}\n\n\n// 'A msg.payload is only returned on receipt of 'get state' message\nif (msg.payload == 'get state') {\n    return {payload: state,enabled};\n}\nreturn;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":680,"y":1400,"wires":[["0dd2f232a0ae5707"]]},{"id":"f3a0d2cf34bf1339","type":"switch","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"","property":"parts.index","propertyType":"msg","rules":[{"t":"eq","v":"0","vt":"str"},{"t":"eq","v":"1","vt":"str"},{"t":"eq","v":"2","vt":"str"}],"checkall":"false","repair":false,"outputs":3,"x":490,"y":1260,"wires":[["24bd5be440d33cda"],["8627665a21e7567e"],["7a1b2a8e3fa2892f"]]},{"id":"0dd2f232a0ae5707","type":"split","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":330,"y":1260,"wires":[["f3a0d2cf34bf1339"]]},{"id":"1e1c12124eafa51d","type":"comment","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"Auto Set flow.RFmac from Group env ","info":"","x":230,"y":720,"wires":[]},{"id":"b4ae72e69cb16ff0","type":"inject","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":170,"y":760,"wires":[["870ab5ac5694608d"]]},{"id":"870ab5ac5694608d","type":"function","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"Set Flow RFmac","func":"if (flow.get(\"RFmac\")===undefined){\n    flow.set(\"RFmac\",env.get(\"RFmac\"))\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":400,"y":760,"wires":[["2ad8af6c7973b8df"]]},{"id":"3e52cb8af3e6e67b","type":"catch","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"RF http request Error","scope":["bc4c7f0184044a95"],"uncaught":false,"x":200,"y":880,"wires":[["2ad8af6c7973b8df"]]},{"id":"bef45f6e3242fe8a","type":"arp","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"ARP","macs":"","x":810,"y":880,"wires":[["989247450d943d5e"]]},{"id":"989247450d943d5e","type":"change","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"","rules":[{"t":"set","p":"PICOip","pt":"flow","to":"payload[0].ip","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1000,"y":880,"wires":[[]]},{"id":"8e30af2313c0a6da","type":"comment","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"No PICO RF Connection","info":"","x":190,"y":840,"wires":[]},{"id":"6a40bdb70a7f6913","type":"change","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"trash ip test","rules":[{"t":"set","p":"PICOip","pt":"flow","to":"192.168.1.49:80","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":970,"y":760,"wires":[[]]},{"id":"20dbb5413d08b7c0","type":"inject","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":800,"y":760,"wires":[["6a40bdb70a7f6913"]]},{"id":"326e2fe94b41c22d","type":"switch","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"Stateless","property":"statusCode","propertyType":"msg","rules":[{"t":"null"}],"checkall":"true","repair":false,"outputs":1,"x":980,"y":1260,"wires":[["21fb58d982ffbe36","8e9ab02b4db9369b"]]},{"id":"11a870bedd976ac7","type":"ui_template","z":"7b8aeb13a838c3a9","g":"499986b708243758","group":"61e6d4c4.e7619c","name":"","order":3,"width":0,"height":0,"format":"<style>\n\n/* Specifying with selector for ON/OFF icons */\nmd-card.nr-dashboard-switch.SUCCESS ng-md-icon[icon=\"power\"] {\n    color:black;\n}\nmd-card.nr-dashboard-switch.SUCCESS ng-md-icon[icon=\"power_settings_new\"] {\n    color:grey;\n} \n\n\n/* Or just the icon holder to address both ON/OFF*/\nmd-card.nr-dashboard-switch.REQ ng-md-icon {\n    color:orange;\n}\nmd-card.nr-dashboard-switch.FAIL ng-md-icon {\n    color:red;\n}\n\n/* Or address every individual Node ID ON/OFF*/\n\n[node-id=\"24bd5be440d33cda\"].SUCCESS ui-icon[icon=\"power\"]{\n    background-color:yellow;\n}\n[node-id=\"24bd5be440d33cda\"].REQ ui-icon{\n    background-color:moccasin;\n}\n[node-id=\"24bd5be440d33cda\"].FAIL ui-icon{\n    background-color:lightpink;\n}\n/*\nmoccasin\n*/\n</style> ","storeOutMessages":false,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","className":"","x":580,"y":1000,"wires":[[]]},{"id":"8e9ab02b4db9369b","type":"change","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"Requesting ","rules":[{"t":"set","p":"parts.index","pt":"msg","to":"(topic = \"switch_1\") ? 0 : (topic = \"switch_2\") ? 1 : 2","tot":"jsonata"},{"t":"set","p":"className","pt":"msg","to":"REQ","tot":"str"},{"t":"set","p":"statusCode","pt":"msg","to":"Request","tot":"str"},{"t":"set","p":"ip","pt":"msg","to":"PICOip","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":1060,"wires":[["bc4c7f0184044a95","f3a0d2cf34bf1339"]]},{"id":"46de8be0c0575230","type":"comment","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"TEST corrupt PICOip connection","info":"","x":890,"y":720,"wires":[]},{"id":"2ad8af6c7973b8df","type":"change","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"Set msg.payload.macs","rules":[{"t":"set","p":"payload","pt":"msg","to":"{}","tot":"json"},{"t":"set","p":"payload.macs","pt":"msg","to":"RFmac","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":600,"y":880,"wires":[["bef45f6e3242fe8a"]]},{"id":"0ebc69e2b0f31727","type":"comment","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"Convert Payload to Object ","info":"","x":610,"y":840,"wires":[]},{"id":"b3825ad25e4e15e7","type":"change","z":"7b8aeb13a838c3a9","g":"499986b708243758","name":"Success/Fail ","rules":[{"t":"set","p":"className","pt":"msg","to":"(statusCode = 200) ? 'SUCCESS' : 'FAIL'","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":850,"y":1060,"wires":[["f3a0d2cf34bf1339"]]},{"id":"61e6d4c4.e7619c","type":"ui_group","name":"RF Plugs","tab":"d680797a.2f8b88","order":1,"disp":true,"width":"5","collapse":false,"className":""},{"id":"d680797a.2f8b88","type":"ui_tab","name":"PICO","icon":"dashboard","disabled":false,"hidden":false}]