Ethernet thermostat project repository - machine codes: Github
Version 1.0.4 of the Ethernet thermostat project is the latest free version update. Other bugs, bugs and security bugs will not be resolved!
Firmware for the Ethernet thermostat is fully available in English language.
Support the Ethernet thermostat project via PayPal. Support will allow you to add new features in the future and open the source code of the application: PayPal donate
Arduino Ethernet Wiznet W5100 / W5500 DS18B20 OneWire Dallas HTML Webserver WebSocket

Compatible control hardware for Ethernet thermostat:

Compatible Ethernet modules and shields for Ethernet thermostat:

Room thermostat - Arduino + Ethernet

Arduino is a handy embeeded platform that can be used, for example, to build a room thermostat, which we will show today. The implementation uses Arduino in combination with the Ethernet shield Wiznet W5100, which works in web server mode and can receive requests from clients in the network via HTTP protocol and send a response to them - HTML code. The thermostat is accessible from the LAN network in which it is located, while it is equipped with a web interface which is used to configure all elements of the thermostat. The web server allows the running of several independent HTML pages, which can be informative or even functional. The web server runs on port 80 - HTTP. The thermostat can automatically control the signaling relay for switching on the gas boiler via the output. It can thus replace an existing room thermostat. As a decision algorithm, the target temperature with hysteresis is used, which is compared with the measured temperature from the digital temperature sensor Dallas DS18B20. The resolution of the DS18B20 sensor when measuring is 12-bits, which is indicated by the resolution at 0.0625 ° C. Data via the OneWire bus can arrive in the microcontroller on demand in 500 to 1000 ms. The decision logic of the system is executed every 10 seconds independently of the web server, no keep-alive connection is required to execute the logic, the system thus operates autonomously and does not require the user's attention.

In terms of hardware, the project uses:
  • Arduino Uno
  • Ethernet shield Wiznet W5100 / Ethernet module Wiznet W5200-W5500
  • DS18B20 temperature sensor on the OneWire bus in a TO-92 housing, or in a waterproof version in an aluminum tube
  • Electromagnetic relay SRD-5VDC-SL-C / SSR relay OMRON G3MB-202P used for boiler switching (Active-LOW signal)
  • HTML pages running on Arduino:

  • / - root page containing the form, current listing of logic output for the relay, temperature
  • /action.html - processes values from the form, writes them to the EEPROM memory, redirects the user back to the root page
  • /get_data.json - distributes data on current temperature, reference temperature and hysteresis to a third party (computer, microcontroller, other client ...) in JSON format

  • The electromagnetic relay SRD-5VDC-SL-C, which is used in the project, allows switching up to 10A at 230V - maximum power 2300W. In case of switching a DC circuit (load) it is possible to switch 300W (10A at 30V DC max). The OMRON G3MB-202P SSR relay is also fully compatible for the wiring diagram, which is suitable only for non-inductive loads and exclusively for AC loads (DC circuit cannot open after switching on). Maximum switching power 460W (230V, 2A). The thermostat can be used all year round. In case of unnecessary control, the output can be physically disconnected and the thermostat can be used as an Ethernet thermometer to obtain data from the room where it is located.

    The web interface for the Ethernet thermostat allows you to:
  • View in real time the temperature from the DS18B20 sensor, the device uptime, the output value with dynamic change, the currently set configuration data for the thermostat, i. target temperature and hysteresis
  • Modify the target (reference) temperature from 5 to 50 °C with 0.25 °C step
  • Modify hysteresis from 0 to 10 °C with 0.25 °C step
  • Boiler ON / OFF control:
  • Example of ON / OFF of heating control - VISUALIZATION IS NOT PART OF THE PROJECT
  • The boiler is active as long as the target temperature + hysteresis is reached
  • The visualization of water temperatures shows the so-called heating run and subsequent cooling of the water until the heating activity is repeated, when the measured temperature is below the set target temperature - hysteresis
  • ZAP/VYP regulácia kotla s hysterézou

    The web interface is designed to adapt to larger and smaller screens. It is responsive, supports widescreen high-definition screens, but also mobile devices. The interface uses imported CSS styles of the Bootstrap framework from an external CDN server, which loads the client-side device when opening a page running on Arduino. Because the Arduino Uno is memory limited, it can only run pages a few kB in size. By importing CSS styles from an external server, it allows you to reduce the performance and memory load of Arduino. The software implementation (for Arduine Uno) uses about 70% of flash memory (32kB - 4kB Bootloader) and 44% of RAM memory (2kB). Static parts of a web page (HTML document header and footer, Bootstrap CSS linking, meta tags, HTTP response header, Content Type, form and more) are stored directly in Arduino's flash memory, which can significantly reduce the amount of RAM used for user-generated content. The web server is thus more stable and can handle multi-connection of several devices in the network at the same time. Consumption of the whole thermostat is up to 200 mA at 5V supply - below 1W.

    In order to keep the set values even after a power failure, they are stored in the EEPROM memory of the Arduino. The reference temperature is written to offset 10, hysteresis to offset 100. Each of the values occupies a maximum of 5B in the EEPROM memory + terminator. The EEPROM transcription limit is at the level of 100,000 transcripts. Data is overwritten only when the HTML form is submitted. The operation of the thermostat is thus extremely gentle on the EEPROM memory in order to maximize its service life. If the device has nothing stored on the mentioned EEPROM offsets at the first start-up, automatic writing will be performed with default values - reference: 20.25 ° C, hysteresis 0.25 ° C, so-called fail-safe solution so that the thermostat is immediately functional and ready for operation.

    Using the Refresh meta tag, the web server refreshes the entire page every 30 seconds, and the approximate time to refresh is also written to the HTML page via Javascript. By this time it is necessary to write the change for the thermostat, otherwise the input windows for numerical inputs to the form will be reset when the page is refreshed. Based on feedback from Android device users, the time for Refresh has been extended from 10 to 30 seconds. As the built-in Ethernet library does not include the use of an asynchronous web server (which can be used, for example, with the Espressif ESP8266 / ESP32 microcontrollers), it is necessary to rewrite the entire page. The dynamic data that is mainly changing is the current value of the output - On / Off , which informs the operator about the actual state of the output together with the color coding. Since the system logic is executed independently of the web server, the output may already be in a different state than currently currently listed in the web application. The change of output is immediately written to the UART monitor (115200 baud/s), for example. On the thermostat's website, the user will also find information about the device's uptime (how long it has been running), i. time in days, hours, minutes and seconds.

    The author of the Ethernet thermostat is not responsible for the functionality of the thermostat, boiler failure, electric shock due to improper installation of the thermostat in the network. The thermostat is distributed under the MIT license.
    Main page for modification of target temperature and hysteresis - example of switched on output:
    Sample data
  • Target temperature: 22.75 °C
  • Hysteresis: 0.25 °C
  • Measured temperature: 22.49 °C
  • Output: Turned on

  • The thermostat heats from a measured temperature of 22.49 ° C and below. If the temperature reaches 23.01 °C, the output is switched off, the signaling relay opens and the gas boiler stops heating. The heating and cooling of the room in which the measurements are performed takes place. The thermostat is not reactivated until the temperature reaches 22.49 °C or lower.

    Ethernet thermostat - Main dashboard - Arduino Process of processing the entered data (user redirection) with VALID and NOT VALID datas: Ethernet thermostat - processing HTML form with valid datas Ethernet thermostat - processing HTML form without valid datas JSON web server output in browser / client via websocket:
    Ethernet thermostat - JSON output
    Output to UART monitor - system logic + Ethernet adapter settings:
    Ethernet thermostat - UART

    Available libraries for microcontrollers (Arduino)

    Library archive (.zip) expand to C:/Users/[User]/Documents/Arduino/libraries
    Library name Library Function Download

    Library for AVR microcontrollers (ATmega) Arduino Uno / Nano / Mega. It allows communication with the Dallas DS18B20 sensor on the OneWire bus. Possibility of communication after normal or parasitic connection.


    Source code - Ethernet thermostat - automatic

    /*|HTTP webserver - Ethernet thermostat, Wiznet W5100 / W5500|*/
    /*|Arduino Uno / Nano / Mega compatible (R3)                 |*/
    /*|AUTHOR: Martin Chlebovec                                  |*/
    /*|EMAIL:                              |*/
    /*|DONATE:                               |*/
    #include <avr\wdt.h>
    #include <SPI.h>
    #include <Ethernet.h> //FOR Wiznet W5100
    //#include <Ethernet2.h> //FOR Wiznet W5500
    #include <EEPROM.h>
    #include <OneWire.h>
    #include <DallasTemperature.h>
    #define ONE_WIRE_BUS 5
    OneWire oneWire(ONE_WIRE_BUS);
    DallasTemperature sensorsA(&oneWire);
    const int rele = 6;
    byte mac[] = { 0xDE, 0xAD, 0xBB, 0xEF, 0xFE, 0xAE }; //physical mac address
    byte ip[] = { 192, 168, 4, 1 };
    EthernetServer server(80); //server port
    const char terminator1[2] = " ";
    const char terminator3[2] = "?";
    const char terminator4[2] = "&";
    const char terminator5[2] = "=";
    unsigned long cas = 0;
    long day = 86400000; // 86400000 milliseconds in a day
    long hour = 3600000; // 3600000 milliseconds in an hour
    long minute = 60000; // 60000 milliseconds in a minute
    long second =  1000; // 1000 milliseconds in a second
    float teplota;
    String stav;
    boolean isFloat(String tString) {
      String tBuf;
      boolean decPt = false;
      if (tString.charAt(0) == '+' || tString.charAt(0) == '-') tBuf = &tString[1];
      else tBuf = tString;
      for (int x = 0; x < tBuf.length(); x++)
        if (tBuf.charAt(x) == '.') {
          if (decPt) return false;
          else decPt = true;
        else if (tBuf.charAt(x) < '0' || tBuf.charAt(x) > '9') return false;
      return true;
    void writeString(char add, String data)
      int _size = data.length();
      int i;
      for (i = 0; i < _size; i++)
        EEPROM.write(add + i, data[i]);
      EEPROM.write(add + _size, '\0'); //Add termination null character for String Data
    String read_String(char add)
      int i;
      char data[100]; //Max 100 Bytes
      int len = 0;
      unsigned char k;
      k =;
      while (k != '\0' && len < 500) //Read until null character
        k = + len);
        data[len] = k;
      data[len] = '\0';
      return String(data);
    void setup() {
      String a = read_String(10);
      String b = read_String(100);
      if (a == "" || a == NULL) {
        writeString(100, "20.25");
      if (b == "" || b == NULL) {
        writeString(10, "0.25");
      pinMode(rele, OUTPUT);
      digitalWrite(rele, HIGH);
      // Ethernet.begin(mac); //FOR DHCP
      Ethernet.begin(mac, ip); //FOR STATIC IPv4
      Serial.println(F("Webapp created by: Martin Chlebovec"));
      Serial.println(F("Build 1.0.4 from 18. Sep 2021"));
      Serial.println(F("Ethernet thermostat IP address:"));
    void loop() {
      if ((millis() - cas) >= 10000 || cas == 0) {
        cas = millis();
        Serial.println(F("Ethernet thermostat IP address:"));
        teplota = sensorsA.getTempCByIndex(0);
        String referencia = read_String(10);
        String hystereza = read_String(100);
        float referencia_teplota = referencia.toFloat();
        float hystereza_teplota = hystereza.toFloat();
        float minus_hystereza_teplota = (-1 * hystereza_teplota);
        float rozdiel = referencia_teplota - teplota;
        if (rozdiel > hystereza_teplota) {
          Serial.println(F("Output on"));
          stav = "ON";
          digitalWrite(rele, LOW);
        } else if (rozdiel < minus_hystereza_teplota) {
          Serial.println(F("Output off"));
          stav = "OFF";
          digitalWrite(rele, HIGH);
        } else {
          Serial.println(F("Difference between target and actual temp is not above or below the hysteresis. The output state does not change."));
          Serial.print(F("Actual state of output: "));
      EthernetClient client = server.available();
      if (client) {
        while (client.connected()) {
          if (client.available()) {
            String line = client.readStringUntil('\n');
            char str[line.length() + 1];
            line.toCharArray(str, line.length());
            char *method;
            char *request;
            method = strtok(str, terminator1);
            request = strtok(NULL, terminator1);
            if (String(request) == "/") {
              int days = millis() / day ;                                //number of days
              unsigned int hours = (millis() % day) / hour;                       //the remainder from days division (in milliseconds) divided by hours, this gives the full hours
              unsigned int minutes = ((millis() % day) % hour) / minute ;         //and so on...
              unsigned int seconds = (((millis() % day) % hour) % minute) / second;
              client.println(F("HTTP/1.1 200 OK"));
              client.println(F("Content-Type: text/html"));
              client.println(F("<!DOCTYPE html>"));
              client.println(F("<script type='text/javascript'>"));
              client.println(F("window.smartlook||(function(d) {"));
              client.println(F("var o=smartlook=function(){ o.api.push(arguments)},h=d.getElementsByTagName('head')[0];"));
              client.println(F("var c=d.createElement('script');o.api=new Array();c.async=true;c.type='text/javascript';"));
              client.println(F("smartlook('init', '6beae97f98b9844b761672af23f38fc60b962338');"));
              client.println(F("<meta charset='utf-8'>"));
              client.println(F("<meta name='author' content='Martin Chlebovec'>"));
              client.println(F("<meta http-equiv='Refresh' content='30'; />"));
              client.println(F("<meta name='viewport' content='width=device-width, initial-scale=1'>"));
              client.println(F("<link rel='stylesheet' href=''>"));
              client.println(F("<script type='text/javascript'>"));
              client.println(F("var timeleft = 30;"));
              client.println(F("var downloadTimer = setInterval(function(){"));
              client.println(F("if(timeleft <= 0){"));
              client.println(F("document.getElementById(\"countdown\").innerHTML = \"Refresh...\";"));
              client.println(F("} else {"));
              client.println(F("document.getElementById(\"countdown\").innerHTML = timeleft + \" seconds to refresh\";"));
              client.println(F("timeleft -= 1;"));
              client.println(F("}, 1000);"));
              client.println(F("<title>HTTP webserver - Arduino + Ethernet</title>"));
              client.println(F("<center><h3>Enter data for webserver (will be stored in EEPROM):</h3>"));
              client.println(F("<form action='/action.html' method='get'>"));
              client.println("<b>Target temperature:</b><br><input type='text' id='fname' name='fname' value=" + read_String(10) + "><br>");
              client.println("<b>Hysteresis:</b><br><input type='text' id='fname2' name='fname2' value=" + read_String(100) + "><br>");
              client.println(F("<input type='submit' class='btn btn-success' value='Send'>"));
              if (stav == "ON") {
                client.println(F("<b><font color='green'>Output: ON</font></b>"));
              if (stav == "OFF") {
                client.println(F("<b><font color='red'>Output: OFF</font></b>"));
              client.println(F("<div id=\"countdown\"></div>"));
              client.print(F("<b>Actual temperature from DS18B20:</b> "));
              client.println(F(" °C"));
              client.print(F("<b>Uptime: </b>"));
              client.print(F(" "));
              client.print(F(" "));
              client.print(F(" "));
              client.println(F("<h3>Author: Martin Chlebovec - -</h3>"));
              client.println(F("<h4>Free version - 1.0.4 build: 18. Sep. 2021</h4>"));
            } else if (String(request) == "/get_data.json") {
              client.println(F("HTTP/1.1 200 OK"));
              client.println(F("Content-Type: application/json"));
            } else if (String(request) == "/favicon.ico") { //fix chybajuceho faviconu
            } else {
              String myString = String(request);
              if (myString.startsWith("/action.html")) {
                char* parameter;
                char* value;
                char* hodnota1;
                char* hodnota2;
                parameter = strtok(request, terminator3);
                value = strtok(NULL, terminator3);
                hodnota1 = strtok(value, terminator4);
                hodnota2 = strtok(NULL, terminator4);
                char* H_1;
                char* H_2;
                strtok(hodnota1, terminator5);
                H_1 = strtok(NULL, terminator5);
                strtok(hodnota2, terminator5);
                H_2 = strtok(NULL, terminator5);
                String first_param = String(H_1);
                String second_param = String(H_2);
                if (isFloat(first_param)) {
                  writeString(10, String(H_1));
                } else {
                  Serial.println(F("User input for key fname (Target temperature) is not a number!"));
                if (isFloat(second_param)) {
                  writeString(100, String(H_2));
                } else {
                  Serial.println(F("User input for key fname2 (Hysteresis) is not a number!"));
                client.println(F("HTTP/1.1 200 OK"));
                client.println(F("Content-Type: text/html"));
                client.println(F("<!DOCTYPE html>"));
                client.println(F("<meta charset='utf-8'>"));
                client.println(F("<meta http-equiv='Refresh' content='5; url=/' />"));
                client.println(F("<title>HTTP webserver - Arduino + Ethernet</title>"));
                client.println(F("<center><h3>Server received datas from HTML form:</h3>"));
                if (!isFloat(second_param) || !isFloat(second_param)) {
                  client.println(F("<h3><font color='red'>The data entered is not a number!!! Please try again after redirecting.</font></h3>"));
                } else {
                  client.println("<li><b>Target temperature: </b>" + String(H_1) + "</li>");
                  client.println("<li><b>Hysteresis: </b>" + String(H_2) + "</li>");
                client.println(F("<b>Redirecting ... Please wait</b></center>"));
              } else {
                client.println(F("HTTP/1.1 404 Not Found"));

    The implementation contains programs for the static / dynamic IPv4 address assigned to the Ethernet shield. The thermostat is only intended for indoor temperatures! (above 0 ° C), to which the system logic is adapted! The thermostat can replace the existing room thermostat, it is possible to temporarily replace the heater in the aquarium / terrarium to maintain a constant temperature.