LoRa Receiver with RFM95

#include <RH_RF95.h> // RadioHead library for RFM95 LoRa

#define RFM95_CS 10 // Chip select pin for the RFM95
#define RFM95_RST 9 // Reset pin for the RFM95
#define RFM95_INT 2 // Interrupt pin for the RFM95

RH_RF95 rf95(RFM95_CS, RFM95_INT);

void setup() {
  Serial.begin(9600); // Start serial communication for output
  pinMode(RFM95_RST, OUTPUT); // Set RFM95 reset pin as output

  // Reset the RFM95 module
  digitalWrite(RFM95_RST, LOW);
  delay(10);
  digitalWrite(RFM95_RST, HIGH);
  delay(10);

  // Initialize the RF95 module with frequency (example: 915 MHz for USA)
  if (!rf95.init()) {
    Serial.println("RFM95 initialization failed.");
    while (1); // Stop if initialization fails
  }

  rf95.setFrequency(915.0); // Set frequency (adjust for your region)
  rf95.setTxPower(23, false); // Set transmission power
  rf95.setModeRx(); // Set the module to receive mode
}

void loop() {
  // Check if there's a received message
  if (rf95.available()) {
    uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; // Buffer for incoming message
    uint8_t len = sizeof(buf); // Length of the buffer

    if (rf95.recv(buf, &len)) { // Receive the message
      Serial.print("Received: ");
      Serial.write(buf, len); // Output the message to the serial monitor
      Serial.println(); // New line for readability
    } else {
      Serial.println("Receive failed"); // Error handling
    }
  }

  delay(100); // Small delay to avoid busy loop
}

  • This sketch initializes the RFM95 module and sets its frequency and transmission power.
  • It initializes the DS18B20 sensor to get temperature readings.
  • Every 15 minutes (900,000 milliseconds), it reads the temperature and sends it via LoRa.
  • The message is formatted to include the temperature reading in Celsius.
  • The sketch sends the message using the RadioHead library, waits for the packet to be sent, and then delays for 15 minutes before repeating the process.

Before uploading this sketch, ensure you’ve installed the RadioHead, OneWire, and DallasTemperature libraries. The frequency and transmission power settings may vary depending on your region and specific application. Make sure to use the appropriate frequency and transmission power for your hardware and regulations in your country.

The program provided will receive any LoRa message on the same frequency and with the same modulation settings (e.g., bandwidth, spreading factor, coding rate). It does not inherently restrict itself to specific senders or messages.

To ensure that this receiver program only listens for messages sent by a specific source, there are several strategies one could employ:

  1. Unique Identifiers: Modify the sender program to prepend a unique identifier to each message. Then, the receiver can check for this identifier before processing the message.
  2. Addressing: The RadioHead library has an addressing mechanism that allows you to assign unique addresses to each node. This way, the receiver only processes messages intended for its address.
  3. Encryption: If security is a concern, you can add encryption to ensure only authorized receivers can understand the messages. This would require additional libraries and complexity.

Here’s an updated version of the receiver program that looks for a specific identifier at the beginning of the message. This identifier could be a unique sequence of characters known to both the sender and receiver:

#include <RH_RF95.h> // RadioHead library for RFM95 LoRa

#define RFM95_CS 10 // Chip select pin for the RFM95
#define RFM95_RST 9 // Reset pin for the RFM95
#define RFM95_INT 2 // Interrupt pin for the RFM95

RH_RF95 rf95(RFM95_CS, RFM95_INT);

const char expectedIdentifier[] = "SENSOR01"; // Unique identifier for valid messages

void setup() {
  Serial.begin(9600); // Start serial communication for output
  pinMode(RFM95_RST, OUTPUT); // Set RFM95 reset pin as output

  // Reset the RFM95 module
  digitalWrite(RFM95_RST, LOW);
  delay(10);
  digitalWrite(RFM95_RST, HIGH);
  delay(10);

  // Initialize the RF95 module with frequency (adjust as needed)
  if (!rf95.init()) {
    Serial.println("RFM95 initialization failed.");
    while (1); // Stop if initialization fails
  }

  rf95.setFrequency(915.0); // Set frequency (adjust for your region)
  rf95.setTxPower(23, false); // Set transmission power
  rf95.setModeRx(); // Set the module to receive mode
}

void loop() {
  // Check if there's a received message
  if (rf95.available()) {
    uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; // Buffer for incoming message
    uint8_t len = sizeof(buf); // Length of the buffer

    if (rf95.recv(buf, &len)) { // Receive the message
      buf[len] = '\0'; // Null-terminate the received message
      
      // Check if the message starts with the expected identifier
      if (strncmp((char*)buf, expectedIdentifier, strlen(expectedIdentifier)) == 0) {
        Serial.print("Received valid message: ");
        Serial.write(buf, len); // Output the message to the serial monitor
        Serial.println(); // New line for readability
      } else {
        Serial.println("Received invalid message");
      }
    } else {
      Serial.println("Receive failed"); // Error handling
    }
  }

  delay(100); // Small delay to avoid busy loop
}

LoRa transmitter with RFM95

This program does the following:

  • Connects to the RFM95 module for LoRa communication.
  • Reads temperature from the DS18B20 sensor.
  • Sends temperature data via LoRa every 15 minutes (900,000 milliseconds).
#include <RH_RF95.h> // RadioHead library for RFM95 LoRa
#include <OneWire.h> // OneWire communication
#include <DallasTemperature.h> // DS18B20 temperature sensor

#define RFM95_CS 10 // Chip select pin for the RFM95
#define RFM95_RST 9 // Reset pin for the RFM95
#define RFM95_INT 2 // Interrupt pin for the RFM95
#define ONE_WIRE_BUS 3 // DS18B20 sensor connected to pin 3

RH_RF95 rf95(RFM95_CS, RFM95_INT);
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

void setup() {
  Serial.begin(9600); // Serial for debugging
  pinMode(RFM95_RST, OUTPUT); // Set RFM95 reset pin as output

  // Reset the RFM95 module
  digitalWrite(RFM95_RST, LOW);
  delay(10);
  digitalWrite(RFM95_RST, HIGH);
  delay(10);

  // Initialize the RF95 module with frequency (example: 915 MHz for USA)
  if (!rf95.init()) {
    Serial.println("RFM95 initialization failed.");
    while (1); // Stop if initialization fails
  }

  rf95.setFrequency(915.0); // Set frequency (adjust for your region)
  rf95.setTxPower(23, false); // Set transmission power

  sensors.begin(); // Initialize the temperature sensor
}

void loop() {
  // Get the temperature reading
  sensors.requestTemperatures();
  float temperature = sensors.getTempCByIndex(0); // Temperature in Celsius

  // Create the message to send
  char msg[32];
  snprintf(msg, sizeof(msg), "Temp: %.2f °C", temperature);

  // Send the message via LoRa
  rf95.send((uint8_t *)msg, strlen(msg));
  rf95.waitPacketSent(); // Wait until packet is sent

  Serial.print("Sent: ");
  Serial.println(msg);

  // Wait 15 minutes before sending the next reading
  delay(900000); // 15 minutes in milliseconds
}

  • This sketch initializes the RFM95 module and sets its frequency and transmission power.
  • It initializes the DS18B20 sensor to get temperature readings.
  • Every 15 minutes (900,000 milliseconds), it reads the temperature and sends it via LoRa.
  • The message is formatted to include the temperature reading in Celsius.
  • The sketch sends the message using the RadioHead library, waits for the packet to be sent, and then delays for 15 minutes before repeating the process.

Before uploading this sketch, ensure you’ve installed the RadioHead, OneWire, and DallasTemperature libraries. The frequency and transmission power settings may vary depending on your region and specific application. Make sure to use the appropriate frequency and transmission power for your hardware and regulations in your country.

Arduino Webserver with W5500 Shield

For nostalgia reasons an arduino Webserver using the W5500 shield:

#include <Ethernet.h> // Library for W5500-based Ethernet shield
#include <OneWire.h>  // Library for OneWire communication
#include <DallasTemperature.h> // Library for DS18B20 temperature sensor

// Pin where the DS18B20 is connected
#define ONE_WIRE_BUS 2
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

EthernetServer server(80); // Ethernet server on port 80

void setup() {
  Serial.begin(9600); // Start the serial communication for debugging

  // Set up the Ethernet shield with a fixed MAC address and IP address
  byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // MAC address
  IPAddress ip(192, 168, 1, 177); // Local IP address (adjust as needed for your network)

  // Start the Ethernet and the server
  Ethernet.begin(mac, ip);
  server.begin();
  Serial.print("Server is at ");
  Serial.println(Ethernet.localIP());

  // Start the temperature sensor
  sensors.begin();
}

void loop() {
  // Check for incoming client connections
  EthernetClient client = server.available();
  if (client) {
    Serial.println("New client connected");

    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        // If the request is complete (double newline), send the response
        if (c == '\n' && currentLineIsBlank) {
          sensors.requestTemperatures(); // Request temperature
          float temperature = sensors.getTempCByIndex(0); // Get temperature in Celsius

          // Send the HTTP response headers
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close"); // Connection will close after this response
          client.println();

          // Send the HTML content with temperature
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
          client.println("<h1>Temperature Monitor</h1>");
          client.print("<p>Temperature: ");
          client.print(temperature);
          client.println(" °C</p>");
          client.println("</html>");
          break;
        }

        if (c == '\n') {
          currentLineIsBlank = true;
        } else if (c != '\r') {
          currentLineIsBlank = false;
        }
      }
    }

    // Close the connection with the client
    client.stop();
    Serial.println("Client disconnected");
  }
}

  • This sketch initializes the Ethernet shield with a specified MAC address and a static IP address. You can adjust the IP address as needed to fit your network configuration.
  • It creates an Ethernet server on port 80 to listen for HTTP requests.
  • The sketch initializes the DS18B20 temperature sensor on the OneWire bus (connected to pin 2).
  • When a client connects, the code reads the temperature from the sensor and serves a simple HTML page showing the temperature in degrees Celsius.
  • The connection to the client is closed after sending the HTTP response.

Before uploading this sketch, ensure you have installed the Ethernet, OneWire, and DallasTemperature libraries. Adjust the IP address and MAC address as needed to suit your network configuration. If you’re using DHCP, you can remove the explicit IP address and use Ethernet.begin(mac) to get an IP address from your router.

Arduino Webserver for W5100 Shield

For Nostalgia’s sake: An Arduino Uno with a W5100 ethernetshield, serving a webpage with results of a DS18N20

#include <Ethernet.h> // Library for W5100-based Ethernet shield
#include <OneWire.h>  // Library for OneWire communication
#include <DallasTemperature.h> // Library for DS18B20 temperature sensor

// Pin where the DS18B20 is connected
#define ONE_WIRE_BUS 2
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

EthernetServer server(80); // Ethernet server on port 80

void setup() {
  // Start the serial communication for debugging
  Serial.begin(9600);

  // Set up the Ethernet shield with a fixed MAC address and IP address
  byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // MAC address
  IPAddress ip(192, 168, 1, 177); // Local IP address (adjust as needed for your network)

  // Start the Ethernet and the server
  Ethernet.begin(mac, ip);
  server.begin();
  Serial.print("Server is at ");
  Serial.println(Ethernet.localIP());

  // Start the temperature sensor
  sensors.begin();
}

void loop() {
  // Check for incoming client connections
  EthernetClient client = server.available();
  if (client) {
    Serial.println("New client");

    // Wait for data from the client
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        // If the request is complete (double newline), send the response
        if (c == '\n' && currentLineIsBlank) {
          // Get the temperature reading
          sensors.requestTemperatures(); // Request temperature
          float temperature = sensors.getTempCByIndex(0); // Get temperature in Celsius

          // Send the HTTP response headers
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close"); // The connection will close after this response
          client.println();

          // Send the HTML content
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
          client.println("<h1>Temperature Monitor</h1>");
          client.print("<p>Temperature: ");
          client.print(temperature);
          client.println(" °C</p>");
          client.println("</html>");
          break;
        }

        if (c == '\n') {
          currentLineIsBlank = true;
        } else if (c != '\r') {
          currentLineIsBlank = false;
        }
      }
    }

    // Close the connection with the client
    client.stop();
    Serial.println("Client disconnected");
  }
}

  • This sketch initializes the Ethernet shield with a MAC address and a static IP address. Adjust the IP address and MAC address to fit your network configuration.
  • It creates an Ethernet server listening on port 80 for HTTP requests.
  • The sketch initializes the DS18B20 temperature sensor on a OneWire bus (here on digital pin 2).
  • When a client connects, the code reads from the temperature sensor and serves a simple HTML page with the temperature in degrees Celsius.
  • The connection to the client is closed after sending the HTTP response.

With this sketch, the Ethernet shield serves as a basic web server that provides temperature data from the DS18B20 sensor. Before uploading, ensure the Ethernet, OneWire, and DallasTemperature libraries are installed in your Arduino IDE.

Arduino Webserver with ENC28J60

From the prehistory: an Arduino Uno with ENC28J60 Etherrnet card, serving a webpage. Just out of nostalgia if someone needs it

#include <UIPEthernet.h> // Library for ENC28J60 Ethernet shield
#include <OneWire.h>     // Library for OneWire communication
#include <DallasTemperature.h> // Library for DS18B20 temperature sensor

// Pin where the DS18B20 is connected
#define ONE_WIRE_BUS 2
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

EthernetServer server(80); // Ethernet server on port 80

void setup() {
  // Start the serial communication for debugging
  Serial.begin(9600);

  // Set up the Ethernet shield with a fixed MAC address and IP address
  byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // MAC address
  IPAddress ip(192, 168, 1, 177); // Local IP address (adjust as needed for your network)

  // Start the Ethernet and the server
  Ethernet.begin(mac, ip);
  server.begin();
  Serial.print("Server is at ");
  Serial.println(Ethernet.localIP());

  // Start the temperature sensor
  sensors.begin();
}

void loop() {
  // Check for incoming client connections
  EthernetClient client = server.available();
  if (client) {
    Serial.println("New client");

    // Wait for data from the client
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        // If the request is complete (double newline), send the response
        if (c == '\n' && currentLineIsBlank) {
          // Get the temperature reading
          sensors.requestTemperatures(); // Request temperature
          float temperature = sensors.getTempCByIndex(0); // Get temperature in Celsius

          // Send the HTTP response headers
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close"); // The connection will close after this response
          client.println();

          // Send the HTML content
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
          client.println("<h1>Temperature Monitor</h1>");
          client.print("<p>Temperature: ");
          client.print(temperature);
          client.println(" °C</p>");
          client.println("</html>");
          break;
        }

        if (c == '\n') {
          currentLineIsBlank = true;
        } else if (c != '\r') {
          currentLineIsBlank = false;
        }
      }
    }

    // Close the connection with the client
    client.stop();
    Serial.println("Client disconnected");
  }
}

  • This sketch initializes the Ethernet shield with a given MAC address and IP address. Adjust the IP address as needed for your network.
  • The OneWire library is used to communicate with the DS18B20 temperature sensor, and the DallasTemperature library is used to read the temperature in Celsius.
  • The Ethernet server listens on port 80 for incoming HTTP requests.
  • When a client connects, the sketch reads the incoming data and responds with a simple HTML page showing the temperature in degrees Celsius.
  • The sketch handles client connections by waiting for a complete HTTP request and then sending the temperature reading in the response.

To use this sketch, make sure you have installed the UIPEthernet, OneWire, and DallasTemperature libraries. You can install them via the Arduino Library Manager. Adjust the IP address and MAC address as needed to fit your network configuration.

Self updating OTA firmware for an ESP8266

Self updating Software for an ESP can easily be setup. As I hate reinventing the wheel, I found a code on Bakke-online, but sadly that did not work. The main reason seemed to be that the httpcalls he used were deprecated (his code is from 2017), but even after correcting those calls, I kept having problem, so I set out to rewrite the code. I do use his file organizing structure though. On a server I set up two files: a text file called (macnummer).version and a bin file with the new software called (macnummer).bin. The version file only has one line with the version of the new software.

What the software below does is it makes a WiFi connection, then does an http call to the version file, reads the number, compares it to the version number in the running program and if the number in the file is bigger than the number in the program, it does an update. The bin file and the version file I stored in a folder called fota (akin to what Bakke does). As I am on Linux, using an apache server, that fota directory should go into the 'var/www/html' folder.1

The program looks like this:

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>
WiFiClient client;

void setup() {
   Serial.begin(115200);

    WiFi.begin("SSID", "PW");
   while (WiFi.status() != WL_CONNECTED) {
     delay(500);
     Serial.print(".");
   }
   Serial.println("\nConnected to Wi-Fi.");

    // Get the current firmware version
   const int currentFirmwareVersion = 1; // Update with your firmware version

    // Form the URL for checking firmware version
   String mac = "104000e7fe3f"; // or your MAC retrieval function
   String fwVersionURL = "http://192.168.1.133/fota/" + mac + ".version";

    HTTPClient httpClient;
   httpClient.begin(client, fwVersionURL); // Initialize with client and URL
   int httpCode = httpClient.GET(); // Perform HTTP GET request
   Serial.print("HTTP code: ");
   Serial.println(httpCode);

    if (httpCode == HTTP_CODE_OK) {
     String serverFirmwareVersionStr = httpClient.getString();
     int serverFirmwareVersion = serverFirmwareVersionStr.toInt();

     Serial.print("Current firmware version: ");
     Serial.println(currentFirmwareVersion);
     Serial.print("Server firmware version: ");
     Serial.println(serverFirmwareVersion);

      if (serverFirmwareVersion > currentFirmwareVersion) { // Check if an update is needed
       Serial.println("A new firmware version is available.");

        // Form the URL for the firmware binary
       String fwBinaryURL = "http://192.168.1.133/fota/" + mac + ".bin";

        // Perform the OTA update
       t_httpUpdate_return ret = ESPhttpUpdate.update(client, fwBinaryURL);

        if (ret == HTTP_UPDATE_OK) {
         Serial.println("OTA update successful.");
       } else {
         Serial.printf("OTA update failed: %s", ESPhttpUpdate.getLastErrorString().c_str());
       }
     } else {
       Serial.println("Firmware is up to date.");
     }
   } else {
     Serial.println("Failed to check firmware version.");
   }

    httpClient.end(); // Clean up HTTP client
 }

  void loop() {
   // Your code here
 }
 

The program does a one time test from the Setup, but the procedure can be called from anywhere. It is ideal for updating software in applications that go into deepsleep for some time. When it does do an OTA update, you may get a crash message in the monitor, just be patient and wait for the restart. The “.bin” file is obtained by going to “Sketch-Export compiled Binary” in the arduino IDE. The compiled file can then be found in the program directory of the arduino sketch book. It should then be copied to the “var/www/html/fota” directory and renamed with the MAC number of the ESP8266 that needs updating. Only after the new code has ben placed there, should you update the version number in the (macnr).version file.
In the program I have hardcoded the MAC number,but it is also possible to use a MAC retrieval code that extracts the MAC from the module that is used, making the code more universal. One might be tempted to use something like: WiFi.macAddress() after the WIFI connection is made but that will give the MAC in the format 5C:CF:7F:D3:35:C7. Clean that up with ‘sprintf’2:

void checkMAC(){
  uint8_t mac[6];
  char macAddr[14];
  WiFi.macAddress( mac );
  sprintf(macAddr, "%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  Serial.println(macAddr);
}

Mind you though that this code takes execution time. If you already know the macaddres of the board, it might be wiser to just hardcode it.

A word on subnets

If your update server and your ESP8266 are on different subnets in your network, you may encounter problems in the ESP8266 being able to reach the update server and you may need to reconfigure your network. In general though if your ESP8266 is on a lower subnet (e.g. 192.168.1.xxx) than your update server (e.g. 192.168.2.xxx) , there should not be a problem. also, when your update server is on a totally different network (say a remote internet server somewhere) you should also not expect any problems

  1. (If you are using Nginx, an HTTP request to the base IP address might map to a default directory like /usr/share/nginx/html. In Windows with IIS it is usually c:\Inetpub\wwwroot, if not: Start > Control Panel > Administrative Tools > Internet Information Services, and open up the Sites tab. Find the “Default Web Site” and right click and select Explore. That should open the directory for you.) ↩︎
  2. sprintf is a function in C and C++ that stands for “string print formatted.” It’s used to format and store a series of characters and values as a string.
    Here’s a basic overview of how it works:
    Syntax: The syntax for sprintf typically looks like this:
    int sprintf ( char * str, const char * format, ... );
    str is a pointer to a buffer where the resulting formatted string will be stored.
    format is a null-terminated string that contains a format string (similar to those used in printf), with optional format specifiers.
    The dots ... indicate that there can be multiple additional arguments after the format argument, which will be inserted into the formatted string according to the format specifiers in the format string. %02X in the format string is a format specifier. It specifies that the corresponding argument (each element of the mac[] array) should be formatted as a hexadecimal integer (X), padded with zeroes if necessary to ensure at least two characters (02). This means each byte of the MAC address will be represented by two hexadecimal characters.
    Usage: You provide a format string with placeholders for the additional arguments, and sprintf will replace these placeholders with the values of the additional arguments, formatting them according to the format specifiers.
    Return value: sprintf returns the number of characters written to the string (str), excluding the null-terminator. If an error occurs, it returns a negative value. ↩︎