Monday, September 29, 2025

Plotting Temperature with the ESP32C3 Dev Module and Node Red

  

-----

Microcontrollers are getting really cheap.  They were already cheap, but now they seem crazy cheap.  Even with onboard WiFi, Bluetooth, and a small OLED display we picked up this ESP32C3 Dev Module for about ~$2 USD; so we had to get two.   One turned into an extremely useful and accurate clock while this one will be a temperature logger.

-----

We used a DS18B20 temperature sensor.  The simple connection of the sensor to the ESP32C3 looks like this: 

 
-----
Now you're ready to use the Arduino IDE to upload the software sketch at the end of this post.  Basically the software polls the DS18B20 for a temperature reading every 60 seconds and posts it as a webpage. Our ESP32C3 is connected to our LAN at 192.168.1.67 so we see this in our web browser:
----
But wait, that's not all... We have Node Red running on a Raspberry PI and parse what this web page would look like every 60 seconds to graph the reading.  This isn't a Node Red tutorial, but the flow looks like this and we will post the flow below for you to import.
 

-----

So, what do you get?   A graph like this.  Note that we are charting two temperatures on our chart.  Your chart will only show the ESP32 line: 

 -----

Now for the software code we promised.   Here is the Node Red flow to import:

 [
    {
        "id": "e19a60a2f08ac386",
        "type": "inject",
        "z": "c0bb5756099d6dbc",
        "name": "Every 60 secs",
        "props": [],
        "repeat": "60",
        "crontab": "",
        "once": true,
        "onceDelay": "1",
        "topic": "",
        "x": 160,
        "y": 120,
        "wires": [
            [
                "018abb4b7bfb7e86",
                "41314d52d14f7623"
            ]
        ]
    },
    {
        "id": "41314d52d14f7623",
        "type": "http request",
        "z": "c0bb5756099d6dbc",
        "name": "",
        "method": "GET",
        "ret": "txt",
        "paytoqs": "ignore",
        "url": "http://192.168.1.67/",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 150,
        "y": 180,
        "wires": [
            [
                "2fb863c6f3188fd2"
            ]
        ]
    },
    {
        "id": "2fb863c6f3188fd2",
        "type": "function",
        "z": "c0bb5756099d6dbc",
        "name": "Parse ESP32 Temp",
        "func": "var payload = msg.payload;\nvar match = payload.match(/Temperature is: ([0-9.]+)/);\n\nif (match) {\n    msg.payload = parseFloat(match[1]);  // Fahrenheit\n    msg.topic = \"ESP32\";   // Add this line\n} else {\n    msg.payload = null;\n}\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 430,
        "y": 180,
        "wires": [
            [
                "ab11f8582b84df82",
                "e024d71190743b50",
                "e146810a6d814e42"
            ]
        ]
    },
    {
        "id": "ab11f8582b84df82",
        "type": "debug",
        "z": "c0bb5756099d6dbc",
        "name": "ESP32 TempF",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 880,
        "y": 180,
        "wires": []
    },
    {
        "id": "e024d71190743b50",
        "type": "ui_gauge",
        "z": "c0bb5756099d6dbc",
        "name": "",
        "group": "7",
        "order": 5,
        "width": 5,
        "height": 4,
        "gtype": "donut",
        "title": "ESP32 (°F)",
        "label": "°F",
        "format": "{{value}}",
        "min": "80",
        "max": "110",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ff0000"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 870,
        "y": 220,
        "wires": []
    },
    {
        "id": "7",
        "type": "ui_group",
        "name": "RasPI-3B",
        "tab": "6",
        "order": 1,
        "disp": true,
        "width": 16,
        "collapse": false,
        "className": ""
    },
    {
        "id": "6",
        "type": "ui_tab",
        "name": "Home",
        "icon": "dashboard",
        "order": 1
    }
]

-----

And here is the Arduino Sketch for the ESP32C3:

// ESP32-C3 Dev Module + onboard OLED 
// thermometer w/ DS18B20 data pin connected to GPIO 4
//
// OLED: Fahrenheit only (1 decimal place, no units)
// Serial Monitor: Celsius + Fahrenheit
// Web page: latest calibrated Fahrenheit reading with timestamp
//
// https://www.whiskeytangohotel.com/
// SEPT 2025

#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <WiFi.h>
#include "time.h"
#include <WebServer.h>

// WiFi credentials
const char* ssid     = "urssid";
const char* password = "urwifipasswrd";

// OLED setup
U8G2_SSD1306_72X40_ER_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

// Global counter
int readingCount = 0;

// DS18B20 setup
#define ONE_WIRE_BUS 4
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

// Timezone
const char* ntpServer = "pool.ntp.org";

// Latest reading
float latestTempF = 0;
time_t latestTime = 0;

// Web server
WebServer server(80);

void handleRoot() {
  char timeBuf[30];
  struct tm *tm_info = localtime(&latestTime);
  strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", tm_info);

  String html = "<html><head><title>ESP32-C3 Temp</title></head><body><pre>";
  html += timeBuf;
  html += " - Temperature is: ";
  html += String(latestTempF, 1);
  html += "</pre></body></html>";

  server.send(200, "text/html", html);
}

void setup() {
  // I2C pins for ESP32-C3 OLED dev board
  Wire.begin(5, 6);
  Wire.setClock(100000);
  delay(200);

  u8g2.begin();
  Serial.begin(115200);
  delay(200);

  sensors.begin();

  // Startup screen
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_6x10_tr);
  u8g2.drawStr(0, 15, "Temp Monitor");
  u8g2.sendBuffer();
  delay(2000);

  // Connect to WiFi
  WiFi.begin(ssid, password);
  u8g2.clearBuffer();
  u8g2.drawStr(0, 15, "Connecting WiFi...");
  u8g2.sendBuffer();
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  // NTP
  configTzTime("CST6CDT,M3.2.0/2,M11.1.0/2", ntpServer);

  // Start server
  server.on("/", handleRoot);
  server.begin();
  Serial.print("HTTP server started at: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  sensors.requestTemperatures();
  float tempC = sensors.getTempCByIndex(0);
  float tempF = tempC * 9.0 / 5.0 + 32.0;
  float calibrationOffsetF = 0.0;
  tempF += calibrationOffsetF;

  // Save latest reading
  time(&latestTime);
  latestTempF = tempF;

  // Serial output
  //readingCount++;  // If reading count is desired
  //Serial.print("#");
  //Serial.print(readingCount);

  char timeBuf[30];
  struct tm *tm_info = localtime(&latestTime);
  strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", tm_info);
  Serial.print(timeBuf);

  Serial.print(" > Temperature is: ");
  Serial.print(tempC);
  Serial.print("°C | ");
  Serial.print(tempF);
  Serial.println("°F");

  // OLED output
  char buf[10];
  snprintf(buf, sizeof(buf), "%.1f", tempF);
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_fur20_tf);
  u8g2.drawStr(0, 30, buf);
  u8g2.sendBuffer();

  server.handleClient(); // handle web requests

  delay(5000); // delay until next reading
}
-----