How-To Guide: Automate Dynamic Creation of Sensors from JSON Data Using Node-RED

:star2: Discover Real-Time Parking Availability in Ottawa! :star2:

Hello everyone! I’ve been working on an exciting project to help you find available parking spaces in our beautiful city of Ottawa. This project integrates live data from our City Traffic Cameras and National Highway Cams, along with dynamic parking lot information.

:red_car: What’s New?
After much trial and error, I’ve successfully set up a system that pulls CCTV footage and dynamically updates parking lot locations. This is especially useful during holidays and city events when new parking spots pop up. Thanks to Node-RED, new parking locations are automatically added to the system—it’s like magic! :smile:

:pushpin: How It Works:
The code snippet below shows how parking sensor entities are created in real-time, including details like address, latitude, longitude, parking type, capacity, and free spaces available. These entities are even mapped for easy viewing.

Node red code:

[{"id":"a74ca110b0a776a4","type":"ha-api","z":"4c2ffe5b61ffbf41","name":"Ottawa Parking Sensor(s) creator","server":"541ade28.b4a62","version":1,"debugenabled":true,"protocol":"http","method":"post","path":"/api/states/{{entity_id}}","data":"","dataType":"json","responseType":"json","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"results"}],"x":900,"y":480,"wires":[["9fb077989563eecd"]]},{"id":"58d1c04ff44585fd","type":"inject","z":"4c2ffe5b61ffbf41","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"5","topic":"","payload":"","payloadType":"date","x":140,"y":400,"wires":[["a8e736b8ce4f80be"]]},{"id":"9fb077989563eecd","type":"debug","z":"4c2ffe5b61ffbf41","name":"posted payload","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":980,"y":400,"wires":[]},{"id":"a8e736b8ce4f80be","type":"change","z":"4c2ffe5b61ffbf41","name":"","rules":[{"t":"set","p":"post","pt":"msg","to":"parkingpayload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":370,"y":400,"wires":[["12d14b17737b9663"]]},{"id":"12d14b17737b9663","type":"http request","z":"4c2ffe5b61ffbf41","name":"Ottawa Parking Host availability","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://traffic.ottawa.ca/service/parking","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":210,"y":500,"wires":[["cd9f6de6759e95c3"]]},{"id":"a49dc8dcc1e00965","type":"debug","z":"4c2ffe5b61ffbf41","name":"New Sensor data","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":850,"y":560,"wires":[]},{"id":"cd9f6de6759e95c3","type":"function","z":"4c2ffe5b61ffbf41","name":"Parse parking payload in loop","func":"\nlet address = '';\nlet address_cleaned = '';\nlet latitude = 0;\nlet longitude = 0;\nlet parkingtype = '';\nlet capacity = 0;\nlet freeSpaces = '';\nlet friendly_name = '';\nlet MapLocation = '';\n\n// for (var i = 0; i < msg.payload.parking_lots.length; i++) {\n// for (var i = 0; i < msg.payload.parking_lots.length; i++) {\n\nfor (var i = 0; i < msg.payload.parking_lots.length; i++) {\n  var newMsg = {};\n\taddress = msg.payload.parking_lots[i].address;\n\taddress_cleaned = address.replace(/[a-z]/gi, function (x) {return x.toUpperCase();});\n\taddress_cleaned = address_cleaned.replace(/ /gi, '_');\n\taddress_cleaned = address_cleaned.replace(/[^\\w\\s]/gi, '');\n\tnewMsg.entity_id = `sensor.Ottawa_parking_${address_cleaned}`;\n\t\n\tlatitude = msg.payload.parking_lots[i].latitude;\n    longitude = msg.payload.parking_lots[i].longitude;\n    parkingtype = msg.payload.parking_lots[i].type;\n    capacity = msg.payload.parking_lots[i].capacity;\n    freeSpaces = msg.payload.parking_lots[i].freeSpaces;\n    friendly_name = 'Parking at '+ address;\n    MapLocation = 'https://www.google.ca/maps/place/' + address;\n\t\t\t\n\tnewMsg.payload = {\n\t\tdata: {\t\t\t\n\t\t\tstate: freeSpaces,\n\t\t\t\n\t\t\ttype: \"sensor\",\n\t\t\tunique_id: friendly_name,\n\t\t\tattributes: {\n\t\t\t\tname: friendly_name,\n\t\t\t\t'address': address,\n\t\t\t\t'latitude': latitude,\n\t\t\t\t'longitude': longitude,\n\t\t\t\t'parkingtype': parkingtype,\n\t\t\t\t'capacity': capacity,\n\t\t\t\t'freeSpaces': freeSpaces,\n\t\t\t\t'MapLocation': MapLocation,\t\t\t\n\t\t\t\tfriendly_name: friendly_name,\n\t\t\t\ticon: 'mdi:parking'\n\t\t\t}\n\t\t}\n\t};\n  \tnode.send(newMsg);\t\n\taddress = '';\t\n\taddress_cleaned = '';\t\n\tlatitude = 0;\t\n\tlongitude = 0;\t\n\tparkingtype = '';\t\n\tcapacity = 0;\t\n\tfreeSpaces = '';\t\n\tfriendly_name = '';\n\tMapLocation = '';\n\n}\nreturn null;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":530,"y":500,"wires":[["a49dc8dcc1e00965","a74ca110b0a776a4"]]},{"id":"541ade28.b4a62","type":"server","name":"Home Assistant Ottawa","version":5,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"","connectionDelay":false,"cacheJson":true,"heartbeat":false,"heartbeatInterval":"","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true}]

Screenshot:

:mag: Preview:
Check out the live example below to see how it looks!

:vertical_traffic_light: Parking Location Metadata:
Here’s an example of the data you can expect:

:loudspeaker: Note:
Feel free to use this code to enhance your datasets and create dynamic entities. I’m excited to see what you’ll come up with! :blush:

P.S.:
I’m still working on some improvements, including:

  1. Adding clickable Google Maps links for easier navigation.
  2. Cleaning up the code for better performance.

Happy parking! :red_car::dash:
Happy Coding!:desktop_computer: :slight_smile:

Well, this is the most impressive thing i have seen in a while. Thanks a lot for sharing this, looks really impressive.

1 Like