{"id":4249,"date":"2023-02-27T13:30:11","date_gmt":"2023-02-27T21:30:11","guid":{"rendered":"https:\/\/www.cloudacm.com\/?p=4249"},"modified":"2023-02-27T07:44:57","modified_gmt":"2023-02-27T15:44:57","slug":"water-level-monitoring-with-the-esp32-cam-module","status":"publish","type":"post","link":"https:\/\/www.cloudacm.com\/?p=4249","title":{"rendered":"Water Level Monitoring with the ESP32-Cam Module"},"content":{"rendered":"<p>In this post the ESP32-Cam module will be used to monitor water levels using a HC-SR04 ultrasonic ranging module . The readings will be published to a MQTT broker through the WiFi network. Node-Red will be interfaced with the broker and used to process and display the readings in its WebUI. Node-Red will also operate a pump once thresholds are reached through the broker. Video from the ESP32-Cam will be gathered by Node-Red and included in its WebUI.<\/p>\n<p>This project was created to deal with rain water flooding in a warehouse. Flooding had occurred after hours and went unnoticed causing equipment damage and a time consuming effort of clean up. The intent is to automatically run a pump to remove pooling water and notify staff. In addition, the operational health of the system is monitored and alerts staff if trouble occurs.<\/p>\n<p>Here is the Arduino IDE code for the ESP32-Cam module. It enables the following features on the ESP32-Cam module:<\/p>\n<ul>\n<li>OV2640 onboard camera<\/li>\n<li>WiFi networking client<\/li>\n<li>Webserver, used to stream camera video<\/li>\n<li>MQTT client, can publish and subscribe to a broker<\/li>\n<li>HC-SR04 interface using GPIO pins 1 and 3<\/li>\n<li>Readings are taken every 5 seconds<\/li>\n<\/ul>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\">\/* See these links for more details  *\/\r\n\/* https:\/\/node-red.blogspot.com\/2017\/12\/how-to-display-led-in-node-red-dashboard.html  *\/\r\n\/* https:\/\/fontawesomeicons.com\/  *\/\r\n\/* https:\/\/randomnerdtutorials.com\/esp32-cam-ov2640-camera-settings\/  *\/\r\n\/* https:\/\/nerdytechy.com\/guide-for-arduino-ultrasonic-sensor-hc-sr04\/  *\/\r\n\/* https:\/\/www.elec-cafe.com\/esp32-cam-blynk-ultrasonic-sensor-hc-sr04-with-line-notify\/  *\/\r\n\r\n\r\n\/\/ Declarations and Variables\r\n\r\n#include \"src\/OV2640.h\"\r\n#include &lt;WiFi.h&gt;\r\n#include &lt;WebServer.h&gt;\r\n#include &lt;WiFiClient.h&gt;\r\n#include &lt;PubSubClient.h&gt;\r\n\r\n#define ENABLE_WEBSERVER\r\n\r\n#define CAMERA_MODEL_AI_THINKER\r\n\r\n#include \"camera_pins.h\"  \/\/ Include in subfolder of code\r\n\r\nOV2640 cam;\r\n\r\nconst char* mqtt_server = \"&lt;IP_Address_of_Broker&gt;\";     \/\/ Put your MQTT Broker here\r\nconst char* ssid =     \"&lt;WiFi_SSID&gt;\";               \/\/ Put your SSID here\r\nconst char* password = \"&lt;WiFi_Password&gt;\";           \/\/ Put your PASSWORD here\r\n\r\nWiFiClient espClient;\r\nPubSubClient client(espClient);\r\nlong lastMsg = 0;\r\nchar msg[50];\r\nint value = 0;\r\nint up = 1;\r\n\r\n\/\/ Data Pins for Ultrasonic sensor\r\nconst int TRIG_PIN = 3;        \/\/GPIO 3 = U0RXD\r\nconst int ECHO_PIN = 1;        \/\/GPIO 1 = U0TXD\r\n\r\n\/\/ establish variables for duration of the ping, and the distance result\r\n\/\/ in inches and centimeters:\r\nlong duration, cm;\r\n\r\n\/\/ Web Server Function\r\n\r\n#ifdef ENABLE_WEBSERVER\r\n\r\nWebServer server(80);\r\n\r\nvoid handle_jpg_stream(void)\r\n{\r\n    WiFiClient client = server.client();\r\n    String response = \"HTTP\/1.1 200 OK\\r\\n\";\r\n    response += \"Content-Type: multipart\/x-mixed-replace; boundary=frame\\r\\n\\r\\n\";\r\n    server.sendContent(response);\r\n\r\n    while (1)\r\n    {\r\n        cam.run();\r\n        if (!client.connected())\r\n            break;\r\n        response = \"--frame\\r\\n\";\r\n        response += \"Content-Type: image\/jpeg\\r\\n\\r\\n\";\r\n        server.sendContent(response);\r\n\r\n        client.write((char *)cam.getfb(), cam.getSize());\r\n        server.sendContent(\"\\r\\n\");\r\n        if (!client.connected())\r\n            break;\r\n    }\r\n}\r\n\r\nvoid handle_jpg(void)\r\n{\r\n    WiFiClient client = server.client();\r\n\r\n    cam.run();\r\n    if (!client.connected())\r\n    {\r\n        return;\r\n    }\r\n    String response = \"HTTP\/1.1 200 OK\\r\\n\";\r\n    response += \"Content-disposition: inline; filename=capture.jpg\\r\\n\";\r\n    response += \"Content-type: image\/jpeg\\r\\n\\r\\n\";\r\n    server.sendContent(response);\r\n    client.write((char *)cam.getfb(), cam.getSize());\r\n}\r\n\r\nvoid handleNotFound()\r\n{\r\n    String message = \"Server is running!\\n\\n\";\r\n    message += \"URI: \";\r\n    message += server.uri();\r\n    message += \"\\nMethod: \";\r\n    message += (server.method() == HTTP_GET) ? \"GET\" : \"POST\";\r\n    message += \"\\nArguments: \";\r\n    message += server.args();\r\n    message += \"\\n\";\r\n    server.send(200, \"text\/plain\", message);\r\n}\r\n#endif\r\n\r\n\r\n\r\n\/\/ MQTT Functions\r\n\r\nvoid callback(char* topic, byte* message, unsigned int length) {\r\n  String messageTemp;\r\n  \r\n  for (int i = 0; i &lt; length; i++) {\r\n    messageTemp += (char)message[i];\r\n  }\r\n\r\n}\r\n\r\nvoid reconnect() {\r\n  \/\/ Loop until we're reconnected\r\n  while (!client.connected()) {\r\n    \/\/ Attempt to connect\r\n    if (client.connect(\"ESP32-Cam_WaterMonitor\")) {\r\n      \/\/ Subscribe\r\n      \/\/ Do you not subscribe to my methods?\r\n      \/\/ # for everything, or ESP32-Cam_WaterMonitor\/Pump for just the pump\r\n      client.subscribe(\"ESP32-Cam_WaterMonitor\/#\");\r\n    } else {\r\n      \/\/ Wait 5 seconds before retrying\r\n      delay(5000);\r\n    }\r\n  }\r\n}\r\n\r\n\r\n\r\n\/\/ Setup Function\r\n\r\nvoid setup()\r\n{\r\n\r\n    pinMode(TRIG_PIN,OUTPUT);\r\n    pinMode(ECHO_PIN,INPUT);\r\n\r\n    client.setServer(mqtt_server, 1883);\r\n    client.setCallback(callback);\r\n\r\n    camera_config_t config;\r\n    config.ledc_channel = LEDC_CHANNEL_0;\r\n    config.ledc_timer = LEDC_TIMER_0;\r\n    config.pin_d0 = Y2_GPIO_NUM;\r\n    config.pin_d1 = Y3_GPIO_NUM;\r\n    config.pin_d2 = Y4_GPIO_NUM;\r\n    config.pin_d3 = Y5_GPIO_NUM;\r\n    config.pin_d4 = Y6_GPIO_NUM;\r\n    config.pin_d5 = Y7_GPIO_NUM;\r\n    config.pin_d6 = Y8_GPIO_NUM;\r\n    config.pin_d7 = Y9_GPIO_NUM;\r\n    config.pin_xclk = XCLK_GPIO_NUM;\r\n    config.pin_pclk = PCLK_GPIO_NUM;\r\n    config.pin_vsync = VSYNC_GPIO_NUM;\r\n    config.pin_href = HREF_GPIO_NUM;\r\n    config.pin_sscb_sda = SIOD_GPIO_NUM;\r\n    config.pin_sscb_scl = SIOC_GPIO_NUM;\r\n    config.pin_pwdn = PWDN_GPIO_NUM;\r\n    config.pin_reset = RESET_GPIO_NUM;\r\n    config.xclk_freq_hz = 20000000;\r\n    config.pixel_format = PIXFORMAT_JPEG;\r\n    config.frame_size = FRAMESIZE_SVGA;\r\n    config.jpeg_quality = 10; \r\n    config.fb_count = 1;       \r\n  \r\n    cam.init(config);\r\n\r\n   sensor_t * s = esp_camera_sensor_get();\r\n   s-&gt;set_gain_ctrl(s, 1);      \/\/ 0 = disable , 1 = enable\r\n   s-&gt;set_agc_gain(s, 0);       \/\/ 0 to 30\r\n   s-&gt;set_brightness(s, 0);     \/\/ -2 to 2\r\n   s-&gt;set_contrast(s, 0);       \/\/ -2 to 2\r\n   s-&gt;set_saturation(s, 0);     \/\/ -2 to 2\r\n\r\n\/* see https:\/\/randomnerdtutorials.com\/esp32-cam-ov2640-camera-settings\/\r\n\r\n\r\n*\/    \r\n    IPAddress ip;\r\n\r\n    WiFi.mode(WIFI_STA);\r\n    WiFi.begin(ssid, password);\r\n    while (WiFi.status() != WL_CONNECTED)\r\n    {\r\n        delay(500);\r\n    }\r\n    ip = WiFi.localIP();\r\n\r\n#ifdef ENABLE_WEBSERVER\r\n    server.on(\"\/\", HTTP_GET, handle_jpg_stream);\r\n    server.on(\"\/jpg\", HTTP_GET, handle_jpg);\r\n    server.onNotFound(handleNotFound);\r\n    server.begin();\r\n#endif\r\n\r\n}\r\n\r\n\r\n\r\n\/\/ Main Loop Function\r\n\r\nvoid loop()\r\n{\r\n  \r\n  if (!client.connected()) {\r\n    reconnect();\r\n  }\r\n  client.loop();\r\n\r\n  long now = millis();\r\n  if (now - lastMsg &gt; 5000) {\r\n    lastMsg = now;\r\n\r\n    client.publish(\"ESP32-Cam_WaterMonitor\/Firmware\", \"ESP32-Cam_WaterMonitor_Ver_1\");     \r\n    String StringUptime = String(millis());\r\n    client.publish(\"ESP32-Cam_WaterMonitor\/Uptime\", StringUptime.c_str());\r\n    String StringHWAddress = String(WiFi.macAddress());\r\n    client.publish(\"ESP32-Cam_WaterMonitor\/HWAddress\", StringHWAddress.c_str());   \r\n    String StringWifiSignal = String(WiFi.RSSI());\r\n    client.publish(\"ESP32-Cam_WaterMonitor\/WifiSignal\",StringWifiSignal.c_str());   \r\n\r\n    MeasureLevel();  \r\n\r\n  }\r\n  \r\n  server.handleClient();\r\n\r\n}\r\n\r\n\r\n\r\nvoid MeasureLevel()\r\n{\r\n\r\n  \/\/ The HC-SR04 is triggered by a HIGH pulse of 2 or more microseconds.\r\n  \/\/ Give a short LOW pulse beforehand to ensure a clean HIGH pulse:\r\n  \r\n  digitalWrite(TRIG_PIN, LOW);\r\n  delayMicroseconds(2);\r\n  digitalWrite(TRIG_PIN, HIGH);\r\n  delayMicroseconds(10);\r\n  digitalWrite(TRIG_PIN, LOW);\r\n\r\n  duration = pulseIn(ECHO_PIN, HIGH);\r\n\r\n  \/\/ convert the time into a distance\r\n  cm = microsecondsToCentimeters(duration);\r\n\r\n  String MeasureLevelCM = String(cm);  \r\n  client.publish(\"ESP32-Cam_WaterMonitor\/Level\", MeasureLevelCM.c_str());        \r\n\r\n}\r\n\r\n\r\n\r\nlong microsecondsToCentimeters(long microseconds) {\r\n  \/\/ The speed of sound is 340 m\/s or 29 microseconds per centimeter.\r\n  \/\/ The ping travels out and back, so to find the distance of the object we\r\n  \/\/ take half of the distance traveled.\r\n  return microseconds \/ 29 \/ 2;\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>With the ESP32-Cam module code installed, the next step was assembling the hardware. The ESP32-Cam module was attached to its serial interface board which would be used to power the entire project. The OV2640 camera module was installed and double sided tape was used to fix it to the microSD slot enclosure. To prevent brownouts, a 1000uF capacitor was soldered between the 5 volt VCC pin and ground. In addition to this, a momentary switch was attached between ground and the reset ground. The ground reset pin is located next to GPIO 1. This is poorly documented and took continuity testing to locate it. The HC-SR04 ultrasonic ranging module TRIG pin was attached to GPIO 3 (U0RXD) and the ECHO pin was attached to GPIO 1 (U0TXD). The 5 volt VCC pin and ground pin completed the connections for the HC-SR04. A 3&#8243;x2&#8243;x1&#8243; plastic project box was used to house all of the components. It was simple to tool out the openings with a small utility blade.<\/p>\n<p><a href=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_Enclosure.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4250\" src=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_Enclosure.png\" alt=\"\" width=\"707\" height=\"620\" srcset=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_Enclosure.png 707w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_Enclosure-300x263.png 300w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_Enclosure-308x270.png 308w\" sizes=\"auto, (max-width: 707px) 100vw, 707px\" \/><\/a><\/p>\n<p>The completed project box was installed in its location, along with an external LED light source and a wireless 433Mhz temperature and humidity sensor. The light source would be reflected off any water accumulating and be visible with camera.<\/p>\n<p>Originally the design was going to use moisture sensors at set levels to determine flood levels. However this design was flawed because the moisture sensors have a short service life and require frequent replacement. The ultrasonic sensor reduces servicing and offers reliability and higher accuracy.<\/p>\n<p>Next is the MQTT broker and Node-Red. There isn&#8217;t much other than installing a MQTT broker service for that portion. I won&#8217;t get into security or access controls here. Suffice it to say, once a broker is up and running, not much else needs to be done.<\/p>\n<p>In contrast, Node-Red is the heavy lifter for this project. It presents the video feed from the ESP32-Cam on the Node-Red web interface using FFMpeg. It subscribes to the ESP32-Cam modules published feeds and displays that on the web UI. The HC-SR04 readings are published and Node-Red processes those values to display water depth. Those readings are also evaluated by Node-Red and it then publishes actions based on user input and defined thresholds. Besides external monitors to verify all is online and working, Node-Red also monitors the online status of the ESP32-Cam module. Here is a closer look at each flow in Node-Red.<\/p>\n<p><a href=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_FFMpegFLow.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-4252 size-full\" src=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_FFMpegFLow.png\" alt=\"\" width=\"1634\" height=\"155\" srcset=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_FFMpegFLow.png 1634w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_FFMpegFLow-300x28.png 300w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_FFMpegFLow-1024x97.png 1024w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_FFMpegFLow-768x73.png 768w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_FFMpegFLow-1536x146.png 1536w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_FFMpegFLow-604x57.png 604w\" sizes=\"auto, (max-width: 1634px) 100vw, 1634px\" \/><\/a><\/p>\n<p>Lets start with the video feed flow. The first node is an inject node that triggers at an interval every 10 seconds. The flow then goes to a change node that changes the msg.payload to a string that contains the path and name of an image file &#8220;\/usr\/share\/node-red-static\/ESP32-Cam_WaterMonitor.jpg&#8221;. This then feeds into an exec node that contains the following command.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">ffmpeg -y -i http:\/\/&lt;IP_Address_of_ESP32-Cam_WaterMonitor&gt; -vf \"pp=al, drawtext=fontfile=\/usr\/share\/fonts\/dejavu\/DejaVuSans-Bold.ttf: text='%{localtime\\}': fontcolor=white@0.8: fontsize=18: bordercolor=black@0.8: borderw=2: x=608: y=7\" -vframes 1 -f image2pipe -vcodec png -<\/pre>\n<p>That command runs FFMpeg and uses the ESP32-Cam as the source. The command also applies a video filter to draw text using a defined font, size, and weight at a designated position of the image. The text contains a timestamp that is overlayed on the image. The exec node then branches to 2 other nodes. Both of these are function nodes, one checks that the msg.payload.buffer is not empty, not in a fault, the other checks if the msg.payload.code value equals &#8220;1&#8221;, in a fault. If the flow payload is in a fault, then a read file node references a file and its path &#8220;\/usr\/share\/node-red-static\/ColorBars.jpg&#8221;. It&#8217;s output is set as &#8220;a single utf8 string&#8221; with encoding set as base64. If the flow payload is not in a fault, then a base64 node is used with an action of Encode as Base64 for the msg.payload. Both of theses nodes feed into a function template node where the msg.payload is fed into the following template.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">&lt;img width=\"360px\" height=\"240px\" src=\"data:image\/jpg;base64,{{{payload}}}\"&gt;<\/pre>\n<p>The final node in the flow is a dashboard template node. This places the msg.payload inside the template for displaying on the WebUI with this template code.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">&lt;div ng-bind-html=\"msg.payload\"&gt;&lt;\/div&gt;<\/pre>\n<p>As you can see, providing video feeds in the Node-Red WebUI is rather involving. Fortunately, processing the MQTT feed from the ESP32-Cam isn&#8217;t as much trouble. The Arduino code above had these lines that directs the ESP32-Cam to publish its uptime, wifi signal strength, firmware version, and wifi network mac address.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\">client.publish(\"ESP32-Cam_WaterMonitor\/Firmware\", \"ESP32-Cam_WaterMonitor_Ver_1\");     \r\nString StringUptime = String(millis());\r\nclient.publish(\"ESP32-Cam_WaterMonitor\/Uptime\", StringUptime.c_str());\r\nString StringHWAddress = String(WiFi.macAddress());\r\nclient.publish(\"ESP32-Cam_WaterMonitor\/HWAddress\", StringHWAddress.c_str());   \r\nString StringWifiSignal = String(WiFi.RSSI());\r\nclient.publish(\"ESP32-Cam_WaterMonitor\/WifiSignal\",StringWifiSignal.c_str());<\/pre>\n<p>The firmware topic is statically set text, whereas the other values are read during run time and published. Node red uses a mqtt in node to subscribe to the specific topic of each. Here is a list of them.<\/p>\n<ul>\n<li>ESP32-Cam_WaterMonitor\/Firmware<\/li>\n<li>ESP32-Cam_WaterMonitor\/HWAddress<\/li>\n<li>ESP32-Cam_WaterMonitor\/WifiSignal<\/li>\n<li>ESP32-Cam_WaterMonitor\/Uptime<\/li>\n<\/ul>\n<p><a href=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_MQTTFLow.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4255\" src=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_MQTTFLow.png\" alt=\"\" width=\"1501\" height=\"261\" srcset=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_MQTTFLow.png 1501w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_MQTTFLow-300x52.png 300w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_MQTTFLow-1024x178.png 1024w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_MQTTFLow-768x134.png 768w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_MQTTFLow-604x105.png 604w\" sizes=\"auto, (max-width: 1501px) 100vw, 1501px\" \/><\/a><\/p>\n<p>The top 3 flow directly into individual dashboard text nodes containing the label and value format {{msg.payload}}. The bottom uptime node flows into a function node to convert millis into seconds. Here is the code for that function.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">var millis = msg.payload;\r\nvar S = millis \/ 1000;\r\nvar seconds = Math.floor(S);\r\nmsg.payload = seconds;\r\nreturn msg;<\/pre>\n<p>The payload then flows to another function node that converts the seconds tally into a DD:HH:MM:SS readable format. Here is the code for that function.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">var totalNumberOfSeconds =  msg.payload;\r\nvar days = parseInt( totalNumberOfSeconds \/ 86400 );\r\nvar hours = parseInt (( totalNumberOfSeconds - ( days * 86400 )) \/ 3600  );\r\nvar minutes = parseInt ((totalNumberOfSeconds - ((hours * 3600)+( days * 86400 ))) \/ 60 );\r\nvar seconds = parseInt(totalNumberOfSeconds - ((hours * 3600) + (minutes * 60)+( days * 86400 )));\r\nvar result = (days &lt; 10 ? \"0\" + days : days) + \" Days \" + (hours &lt; 10 ? \"0\" + hours : hours) + \":\" + (minutes &lt; 10 ? \"0\" + minutes : minutes) + \":\" + (seconds  &lt; 10 ? \"0\" + seconds : seconds);\r\nmsg.payload=result;\r\nreturn msg;<\/pre>\n<p>This then flows into its dashboard text node in similar fashion as the above 3 mentioned earlier. The use of MQTT feeds on the ESP32-Cam module and in Node-Red is powerful and it has great potential beyond the scope of this project.<\/p>\n<p><a href=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_WebUIStats.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4256\" src=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_WebUIStats.png\" alt=\"\" width=\"409\" height=\"282\" srcset=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_WebUIStats.png 409w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_WebUIStats-300x207.png 300w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_WebUIStats-392x270.png 392w\" sizes=\"auto, (max-width: 409px) 100vw, 409px\" \/><\/a><\/p>\n<p>The next flow is a system status monitor of the ESP32-Cam module network connection. This flow starts with a ping node that has an interval of every 15 seconds. It then flows into a change node that replaces the msg.payload false or true values to string values of On or Off respectively. The flow then branches out in 2 directions with the first flow feeding a function node with the following code.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">msg.color = (msg.payload === \"On\")?\"red\":\"lime\";\r\nreturn msg;<\/pre>\n<p><a href=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_PingFLow.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4257\" src=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_PingFLow.png\" alt=\"\" width=\"1152\" height=\"89\" srcset=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_PingFLow.png 1152w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_PingFLow-300x23.png 300w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_PingFLow-1024x79.png 1024w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_PingFLow-768x59.png 768w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_PingFLow-604x47.png 604w\" sizes=\"auto, (max-width: 1152px) 100vw, 1152px\" \/><\/a><\/p>\n<p>This then flows into a dashboard text node with the value format set with these parameters.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">&lt;font color={{msg.color}} &gt;&lt;i class=\"fa fa-circle\" style=\"font-size:24px;\"&gt;&lt;\/i&gt;&lt;\/font&gt;<\/pre>\n<p>The label in this node is also defined. The final results are a LED like indicator on the WebUI next to the label. The other branch from the change node mentioned earlier is sent to a separate flow that sends alerts to staff.<\/p>\n<p>The Arduino code above also had a line that defined the mqtt published reading from the HC-SR04 ultrasonic ranging module.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">client.publish(\"ESP32-Cam_WaterMonitor\/Level\", MeasureLevelCM.c_str());<\/pre>\n<p><a href=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_WaterLevelFLow.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4259\" src=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_WaterLevelFLow.png\" alt=\"\" width=\"1307\" height=\"132\" srcset=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_WaterLevelFLow.png 1307w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_WaterLevelFLow-300x30.png 300w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_WaterLevelFLow-1024x103.png 1024w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_WaterLevelFLow-768x78.png 768w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_WaterLevelFLow-604x61.png 604w\" sizes=\"auto, (max-width: 1307px) 100vw, 1307px\" \/><\/a><\/p>\n<p>The Node-Red flow here start with a mqtt in node and it subscribes to the topic &#8220;ESP32-Cam_WaterMonitor\/Level&#8221;. This then flows into a change node converting the msg.payload into an expression $number(payload), thus converting the string into an integer. The flow branches in 2 directions. The second branch will be covered later in the action flow. The first continues to a function node where a formula is applied to convert distance to a baseline of zero, here is the code in that function.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">msg.payload = (msg.payload*(-1))+41;\r\nreturn msg;<\/pre>\n<p>The next node is a dashboard artless-guage node that displays the reading as a linear bar. The minimum and maximum range values are set here.<\/p>\n<p><a href=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_WaterLevelWebUI.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4260\" src=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_WaterLevelWebUI.png\" alt=\"\" width=\"415\" height=\"470\" srcset=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_WaterLevelWebUI.png 415w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_WaterLevelWebUI-265x300.png 265w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_WaterLevelWebUI-238x270.png 238w\" sizes=\"auto, (max-width: 415px) 100vw, 415px\" \/><\/a><\/p>\n<p>The last flow used in Node-Red will make use of context based variables. This will be the most complex flow thus far. Context based variables are an extremely powerful tool that can leverage the potential of Node-Red. Earlier on the HC-SR04 ultrasonic ranging module reading was converted to an integer. Here that value flows into a function node that processes the value with a formula and stores the reading as a variable. Here is the instruction in the node.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">msg.payload = (msg.payload*(-1))+41;\r\n\r\nvar reading=context.get(\"reading\");\r\n\r\nreading=msg.payload;\r\ncontext.set(\"reading\",reading);\r\n\r\nglobal.set(\"globalreading\",reading);\r\nreturn msg;<\/pre>\n<p>As the readings are updated on the ESP32-Cam module and published to the MQTT broker, Node-Red will receive those updated readings and store the new values into the variable defined. This will be useful because a separate flow will reference that variable.<\/p>\n<p>That separate flow will start with an inject node that is set to repeat at an interval every 3 seconds. It will then flow into a function node that gathers a host of variables and sets the msg.payload based on the variable values. Here is the function code.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">var bypass=global.get(\"globalbypass\");\r\nvar action=global.get(\"globalaction\");\r\nvar reading=global.get(\"globalreading\");\r\n\/\/ var pump=0;\r\n\r\nif (bypass==\"Auto\" &amp;&amp; reading&lt;=0)\r\n    pump=0;\r\nif (bypass==\"Auto\" &amp;&amp; reading&gt;=2)\r\n    pump=1;\r\n    \r\nif (bypass==\"Manual\" &amp;&amp; action==\"Off\")\r\n    pump=0;\r\nif (bypass==\"Manual\" &amp;&amp; action==\"On\")\r\n    pump=1;\r\n\r\nmsg.payload=pump;\r\nreturn msg;<\/pre>\n<p>This then flows into 2 separate function nodes that have the following code.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">\/\/ Pump On Operator \r\nvar pump=msg.payload;\r\n\r\nif (pump==1)\r\n    msg.payload=pump;\r\nelse { return }\r\n\r\nreturn msg;\r\n\r\n\/\/ Pump Off Operator \r\nvar pump=msg.payload;\r\n\r\nif (pump==0)\r\n    msg.payload=pump;\r\nelse { return }\r\n\r\nreturn msg;<\/pre>\n<p><a href=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_LogicFLow.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4261\" src=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_LogicFLow.png\" alt=\"\" width=\"1269\" height=\"703\" srcset=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_LogicFLow.png 1269w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_LogicFLow-300x166.png 300w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_LogicFLow-1024x567.png 1024w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_LogicFLow-768x425.png 768w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_LogicFLow-487x270.png 487w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2023\/02\/ESP32-Cam_HC-SR04_LogicFLow-1038x576.png 1038w\" sizes=\"auto, (max-width: 1269px) 100vw, 1269px\" \/><\/a><\/p>\n<p>The results of these nodes are passed to the mqtt broker. Parallel to this there are 2 dashboard button nodes, each of which publish a topic value to the mqtt broker. The end result is turn the water pump on or off.<\/p>\n<p>It was mentioned earlier that Node-Red was the heavy lifter and that was no exaggeration. The key takeaway is how context variables can be dynamically changed and referenced at any point in flows. Having that available is what makes controlling a water pump under any number of circumstances possible. Although it can be a difficult topic for some to grasp initially, it takes the burden off of the ESP32-Cam module and for that it&#8217;s invaluable. For more details, reference these links.<\/p>\n<p><a href=\"https:\/\/techexplorations.com\/guides\/esp32\/node-red-esp32-project\/node-red-messages-variables\/\">https:\/\/techexplorations.com\/guides\/esp32\/node-red-esp32-project\/node-red-messages-variables\/<\/a><br \/>\n<a href=\"https:\/\/nodered.org\/docs\/user-guide\/environment-variables\">https:\/\/nodered.org\/docs\/user-guide\/environment-variables<\/a><\/p>\n<p><iframe loading=\"lazy\" title=\"#190 New Ultrasonic Sensors: Comparison and Test (US42V2, JSN-SR04T, and US-100)\" width=\"640\" height=\"360\" src=\"https:\/\/www.youtube.com\/embed\/vGlKVRvnjws?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<p>https:\/\/youtu.be\/h6321UBATps<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this post the ESP32-Cam module will be used to monitor water levels using a HC-SR04 ultrasonic ranging module . The readings will be published to a MQTT broker through the WiFi network. Node-Red will be interfaced with the broker and used to process and display the readings in its WebUI. Node-Red will also operate a pump once thresholds are reached through the broker. Video from the ESP32-Cam will be gathered by Node-Red and included in its WebUI. This project&#8230;<\/p>\n<p class=\"read-more\"><a class=\"btn btn-default\" href=\"https:\/\/www.cloudacm.com\/?p=4249\"> Read More<span class=\"screen-reader-text\">  Read More<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12,3],"tags":[],"class_list":["post-4249","post","type-post","status-publish","format-standard","hentry","category-esp32-cam","category-rd"],"_links":{"self":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts\/4249","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4249"}],"version-history":[{"count":12,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts\/4249\/revisions"}],"predecessor-version":[{"id":4264,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts\/4249\/revisions\/4264"}],"wp:attachment":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4249"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4249"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4249"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}