{"id":90811,"date":"2019-11-20T13:47:43","date_gmt":"2019-11-20T13:47:43","guid":{"rendered":"https:\/\/randomnerdtutorials.com\/?p=90811"},"modified":"2025-03-15T11:31:14","modified_gmt":"2025-03-15T11:31:14","slug":"esp32-lora-sensor-web-server","status":"publish","type":"post","link":"https:\/\/randomnerdtutorials.com\/esp32-lora-sensor-web-server\/","title":{"rendered":"ESP32 LoRa Sensor Monitoring with Web Server (Long Range Communication)"},"content":{"rendered":"\n<p>In this project, you&#8217;ll build a sensor monitoring system using a TTGO LoRa32 SX1276 OLED board that sends temperature, humidity and pressure readings via LoRa radio to an ESP32 LoRa receiver. The receiver displays the latest sensor readings on a web server.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" fetchpriority=\"high\" decoding=\"async\" width=\"1200\" height=\"675\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/ESP32-LoRa32-TTGO-OLED-WEB-SERVER.jpg?resize=1200%2C675&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 LoRa Sensor Monitoring with Web Server Long Range Communication\" class=\"wp-image-90906\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/ESP32-LoRa32-TTGO-OLED-WEB-SERVER.jpg?w=1280&amp;quality=100&amp;strip=all&amp;ssl=1 1280w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/ESP32-LoRa32-TTGO-OLED-WEB-SERVER.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/ESP32-LoRa32-TTGO-OLED-WEB-SERVER.jpg?resize=768%2C432&amp;quality=100&amp;strip=all&amp;ssl=1 768w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/ESP32-LoRa32-TTGO-OLED-WEB-SERVER.jpg?resize=1024%2C576&amp;quality=100&amp;strip=all&amp;ssl=1 1024w\" sizes=\"(max-width: 1200px) 100vw, 1200px\" \/><\/figure><\/div>\n\n\n<p>With this project you&#8217;ll learn how to:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Send sensor readings via LoRa radio between two ESP32 boards;<\/li>\n\n\n\n<li>Add LoRa and Wi-Fi capabilities simultaneously to your projects (LoRa + Web Server on the same ESP32 board);<\/li>\n\n\n\n<li>Use the TTGO LoRa32 SX1276 OLED board or similar development boards for IoT projects.<\/li>\n<\/ul>\n\n\n\n<p><strong>Recommended reading:<\/strong> <a href=\"https:\/\/randomnerdtutorials.com\/ttgo-lora32-sx1276-arduino-ide\/\">TTGO LoRa32 SX1276 OLED Board: Getting Started with Arduino IDE<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Watch the Video Demonstration<\/h2>\n\n\n\n<p>Watch the video demonstration to see what you\u2019re going to build throughout this tutorial. <\/p>\n\n\n<p style=\"text-align:center\"><iframe width=\"720\" height=\"405\" src=\"https:\/\/www.youtube.com\/embed\/-6RWwo1iAKM?rel=0\" frameborder=\"0\" allowfullscreen><\/iframe><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Project Overview<\/h2>\n\n\n\n<p>The following image shows a high-level overview of the project we&#8217;ll build throughout this tutorial.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"730\" height=\"813\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/esp32-lora-oled-web-server-overview.png?resize=730%2C813&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Project Overview ESP32 LoRa Sender and ESP32 LoRa32 Receiver board\" class=\"wp-image-90889\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/esp32-lora-oled-web-server-overview.png?w=730&amp;quality=100&amp;strip=all&amp;ssl=1 730w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/esp32-lora-oled-web-server-overview.png?resize=269%2C300&amp;quality=100&amp;strip=all&amp;ssl=1 269w\" sizes=\"(max-width: 730px) 100vw, 730px\" \/><\/figure><\/div>\n\n\n<ul class=\"wp-block-list\">\n<li>The LoRa sender sends BME280 sensor readings via LoRa radio every 10 seconds;<\/li>\n\n\n\n<li>The LoRa receiver gets the readings and displays them on a web server;<\/li>\n\n\n\n<li>You can monitor the sensor readings by accessing the web server;<\/li>\n\n\n\n<li>The LoRa sender and the Lora receiver can be several hundred meters apart depending on their location. So, you can use this project to monitor sensor readings from your fields or greenhouses if they are a bit apart from your house;<\/li>\n\n\n\n<li>The LoRa receiver is running an asynchronous web server and the web page files are saved on the ESP32 filesystem (LittleFS);<\/li>\n\n\n\n<li>The LoRa receiver also shows the date and time the last readings were received. To get date and time, we use the <a href=\"https:\/\/randomnerdtutorials.com\/esp32-ntp-client-date-time-arduino-ide\/\">Network Time Protocol with the ESP32<\/a>.<\/li>\n<\/ul>\n\n\n\n<p><strong>For an introduction to LoRa communication:<\/strong> what&#8217;s LoRa, LoRa frequencies, LoRa applications and more, read our <a href=\"https:\/\/randomnerdtutorials.com\/esp32-lora-rfm95-transceiver-arduino-ide\/\">Getting Started ESP32 with LoRa using Arduino IDE<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Parts Required<\/h2>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><a href=\"https:\/\/makeradvisor.com\/tools\/ttgo-lora32-sx1276-esp32-oled\/\" target=\"_blank\" rel=\"noreferrer noopener\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"750\" height=\"422\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/10\/TTGO-LoRa-ESP32-Dev-Board.jpg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"TTGO LoRa32 SX1276 OLED board with antenna\" class=\"wp-image-90250\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/10\/TTGO-LoRa-ESP32-Dev-Board.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/10\/TTGO-LoRa-ESP32-Dev-Board.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/a><\/figure><\/div>\n\n\n<p>For this project, we&#8217;ll use the following components:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><a aria-label=\" (opens in a new tab)\" href=\"https:\/\/makeradvisor.com\/tools\/ttgo-lora32-sx1276-esp32-oled\/\" target=\"_blank\" rel=\"noreferrer noopener\">TTGO LoRa32 SX1276 OLED board<\/a> (2x):<\/strong> this is an ESP32 development board with a LoRa chip and a built-in OLED. You can use similar boards, or you can use an <a href=\"https:\/\/randomnerdtutorials.com\/esp32-lora-rfm95-transceiver-arduino-ide\/\">ESP32 + LoRa chip + OLED separately<\/a>.<\/li>\n\n\n\n<li><a aria-label=\" (opens in a new tab)\" href=\"https:\/\/randomnerdtutorials.com\/esp32-lora-rfm95-transceiver-arduino-ide\/\" target=\"_blank\" rel=\"noreferrer noopener\">BME280 temperature, humidity and pressure sensor<\/a>. You should be able to modify this project to use any other sensor.<\/li>\n<\/ul>\n\n\n\n<p>You&#8217;ll also need some <a href=\"https:\/\/makeradvisor.com\/tools\/jumper-wires-kit-120-pieces\/\" target=\"_blank\" rel=\"noreferrer noopener\" aria-label=\" (opens in a new tab)\">jumper wires<\/a> and a <a href=\"https:\/\/makeradvisor.com\/tools\/mb-102-solderless-breadboard-830-points\/\" target=\"_blank\" rel=\"noreferrer noopener\" aria-label=\" (opens in a new tab)\">breadboard<\/a>.<\/p>\n\n\n<p>You can use the preceding links or go directly to <a href=\"https:\/\/makeradvisor.com\/tools\/?utm_source=rnt&utm_medium=post&utm_campaign=post\" target=\"_blank\">MakerAdvisor.com\/tools<\/a> to find all the parts for your projects at the best price!<\/p><p style=\"text-align:center;\"><a href=\"https:\/\/makeradvisor.com\/tools\/?utm_source=rnt&utm_medium=post&utm_campaign=post\" target=\"_blank\"><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2017\/10\/header-200.png?w=1200&#038;quality=100&#038;strip=all&#038;ssl=1\"><\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Preparing the Arduino IDE<\/h2>\n\n\n\n<p>To program the TTGO LoRa32 SX1276 OLED boards we&#8217;ll use <a href=\"https:\/\/randomnerdtutorials.com\/installing-the-esp32-board-in-arduino-ide-windows-instructions\/\">Arduino IDE<\/a>. To upload files to the ESP32 filesystem, we&#8217;ll use the <a href=\"https:\/\/randomnerdtutorials.com\/arduino-ide-2-install-esp32-littlefs\/\" title=\"\">ESP32 LittleFS filesystem uploader plugin<\/a>.<\/p>\n\n\n\n<p><strong>So, before proceeding, you need to install the <a href=\"https:\/\/randomnerdtutorials.com\/installing-esp32-arduino-ide-2-0\/\" title=\"\">ESP32 boards<\/a> and the <a href=\"https:\/\/randomnerdtutorials.com\/arduino-ide-2-install-esp32-littlefs\/\" title=\"\">ESP32 filesystem uploader plugin<\/a><\/strong> in your Arduino IDE.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Installing libraries<\/h2>\n\n\n\n<p>For this project, you need to install several libraries.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">LoRa, BME280, OLED, and Web Server Libraries<\/h3>\n\n\n\n<p>The following libraries can be installed through the Arduino Library Manager. Go to <strong>Sketch <\/strong>> <strong>Include Library<\/strong>> <strong>Manage Libraries<\/strong> and search for the libraries&#8217; name.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>LoRa library: <a href=\"https:\/\/github.com\/sandeepmistry\/arduino-LoRa\" target=\"_blank\" rel=\"noreferrer noopener\">arduino-LoRa library by sandeep mistry<\/a> <\/li>\n\n\n\n<li>OLED libraries: <a href=\"https:\/\/github.com\/adafruit\/Adafruit_SSD1306\" target=\"_blank\" rel=\"noreferrer noopener\">Adafruit_SSD1306 library<\/a>&nbsp;and&nbsp;<a href=\"https:\/\/github.com\/adafruit\/Adafruit-GFX-Library\" target=\"_blank\" rel=\"noreferrer noopener\">Adafruit_GFX library<\/a> <\/li>\n\n\n\n<li>BME280 libraries: <a href=\"https:\/\/github.com\/adafruit\/Adafruit_BME280_Library\" target=\"_blank\" rel=\"noreferrer noopener\">Adafruit_BME280 library<\/a> and <a href=\"https:\/\/github.com\/adafruit\/Adafruit_Sensor\">Adafruit unified sensor library<\/a><\/li>\n\n\n\n<li>Web Server libraries: <a href=\"https:\/\/github.com\/ESP32Async\/ESPAsyncWebServer\" target=\"_blank\" rel=\"noopener\" title=\"\">ESPAsyncWebServer <\/a>(by ESP32Async) and <a href=\"https:\/\/github.com\/ESP32Async\/AsyncTCP\" target=\"_blank\" rel=\"noopener\" title=\"\">AsyncTCP <\/a>(by ESP32Async)<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">NTPClient Library<\/h3>\n\n\n\n<p>Everytime the LoRa receiver picks up a new a LoRa message, it will request the date and time from an NTP server so that we know when the last packet was received.<\/p>\n\n\n\n<p>For that we\u2019ll be using the&nbsp;<a href=\"https:\/\/github.com\/taranais\/NTPClient\" target=\"_blank\" rel=\"noreferrer noopener\" aria-label=\" (opens in a new tab)\">NTPClient library forked by Taranais<\/a>. Follow the next steps to install this library in your Arduino IDE:<\/p>\n\n\n\n<p class=\"rntbox rntcred\"><strong>IMPORTANT<\/strong>: we\u2019re not using the default NTPClient library. To follow this tutorial you need to install the library we recommend using the following steps.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><a href=\"https:\/\/github.com\/taranais\/NTPClient\/archive\/master.zip\">Click here to download the NTP Client library<\/a>. You should have a .zip folder in your&nbsp;<em>Downloads<\/em><\/li>\n\n\n\n<li>In your Arduino IDE, go to&nbsp;<strong>Sketch&nbsp;<\/strong>&gt;&nbsp;<strong>Include Library<\/strong>&nbsp;&gt;&nbsp;<strong>Add . ZIP library<\/strong>\u2026<\/li>\n\n\n\n<li>Select the .ZIP file of the library you just downloaded.<\/li>\n\n\n\n<li>The library will be installed after a few seconds.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">LoRa Sender<\/h2>\n\n\n\n<p>The LoRa Sender is connected to a <a href=\"https:\/\/randomnerdtutorials.com\/esp32-bme280-arduino-ide-pressure-temperature-humidity\/\">BME280 sensor<\/a> and sends temperature, humidity, and pressure readings every 10 seconds. You can change this period of time later in the code.<\/p>\n\n\n\n<p><strong>Recommended reading:<\/strong> <a href=\"https:\/\/randomnerdtutorials.com\/esp32-bme280-arduino-ide-pressure-temperature-humidity\/\">ESP32 with BME280 Sensor using Arduino IDE (Pressure, Temperature, Humidity)<\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">LoRa Sender Circuit<\/h3>\n\n\n\n<p>The BME280 we&#8217;re using communicates with the ESP32 using I2C communication protocol. Wire the sensor as shown in the next schematic diagram:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"591\" height=\"594\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-Sender-schematic-diagram-wiring-BME280.png?resize=591%2C594&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"TTGO LoRa32 SX1276 OLED board ESP32 Sender\" class=\"wp-image-90890\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-Sender-schematic-diagram-wiring-BME280.png?w=591&amp;quality=100&amp;strip=all&amp;ssl=1 591w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-Sender-schematic-diagram-wiring-BME280.png?resize=150%2C150&amp;quality=100&amp;strip=all&amp;ssl=1 150w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-Sender-schematic-diagram-wiring-BME280.png?resize=298%2C300&amp;quality=100&amp;strip=all&amp;ssl=1 298w\" sizes=\"(max-width: 591px) 100vw, 591px\" \/><\/figure><\/div>\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>BME280<\/strong><\/td><td><strong>ESP32<\/strong><\/td><\/tr><tr><td>VIN<\/td><td><span class=\"rnthl rntcred\">3.3 V<\/span><\/td><\/tr><tr><td>GND<\/td><td><span class=\"rnthl rntcblack\">GND<\/span><\/td><\/tr><tr><td>SCL<\/td><td><span class=\"rnthl rntclblue\">GPIO 13<\/span><\/td><\/tr><tr><td>SDA<\/td><td><span class=\"rnthl rntclgreen\">GPIO 21<\/span><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">LoRa Sender Code<\/h3>\n\n\n\n<p>The following code reads temperature, humidity and pressure from the BME280 sensor and sends the readings via LoRa radio. <\/p>\n\n\n\n<p>Copy the following code to your Arduino IDE.<\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-c\">\/*********\r\n  Rui Santos &amp; Sara Santos - Random Nerd Tutorials\r\n  Complete project details at https:\/\/RandomNerdTutorials.com\/esp32-lora-sensor-web-server\/\r\n  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.\r\n  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\r\n*********\/\r\n\r\n\/\/Libraries for LoRa\r\n#include &lt;SPI.h&gt;\r\n#include &lt;LoRa.h&gt;\r\n\r\n\/\/Libraries for OLED Display\r\n#include &lt;Wire.h&gt;\r\n#include &lt;Adafruit_GFX.h&gt;\r\n#include &lt;Adafruit_SSD1306.h&gt;\r\n\r\n\/\/Libraries for BME280\r\n#include &lt;Adafruit_Sensor.h&gt;\r\n#include &lt;Adafruit_BME280.h&gt;\r\n\r\n\/\/define the pins used by the LoRa transceiver module\r\n#define SCK 5\r\n#define MISO 19\r\n#define MOSI 27\r\n#define SS 18\r\n#define RST 14\r\n#define DIO0 26\r\n\r\n\/\/433E6 for Asia\r\n\/\/866E6 for Europe\r\n\/\/915E6 for North America\r\n#define BAND 866E6\r\n\r\n\/\/OLED pins\r\n#define OLED_SDA 4\r\n#define OLED_SCL 15 \r\n#define OLED_RST 16\r\n#define SCREEN_WIDTH 128 \/\/ OLED display width, in pixels\r\n#define SCREEN_HEIGHT 64 \/\/ OLED display height, in pixels\r\n\r\n\/\/BME280 definition\r\n#define SDA 21\r\n#define SCL 13\r\n\r\nTwoWire I2Cone = TwoWire(1);\r\nAdafruit_BME280 bme;\r\n\r\n\/\/packet counter\r\nint readingID = 0;\r\n\r\nint counter = 0;\r\nString LoRaMessage = &quot;&quot;;\r\n\r\nfloat temperature = 0;\r\nfloat humidity = 0;\r\nfloat pressure = 0;\r\n\r\nAdafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &amp;Wire, OLED_RST);\r\n\r\n\/\/Initialize OLED display\r\nvoid startOLED(){\r\n  \/\/reset OLED display via software\r\n  pinMode(OLED_RST, OUTPUT);\r\n  digitalWrite(OLED_RST, LOW);\r\n  delay(20);\r\n  digitalWrite(OLED_RST, HIGH);\r\n\r\n  \/\/initialize OLED\r\n  Wire.begin(OLED_SDA, OLED_SCL);\r\n  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3c, false, false)) { \/\/ Address 0x3C for 128x32\r\n    Serial.println(F(&quot;SSD1306 allocation failed&quot;));\r\n    for(;;); \/\/ Don't proceed, loop forever\r\n  }\r\n  display.clearDisplay();\r\n  display.setTextColor(WHITE);\r\n  display.setTextSize(1);\r\n  display.setCursor(0,0);\r\n  display.print(&quot;LORA SENDER&quot;);\r\n}\r\n\r\n\/\/Initialize LoRa module\r\nvoid startLoRA(){\r\n  \/\/SPI LoRa pins\r\n  SPI.begin(SCK, MISO, MOSI, SS);\r\n  \/\/setup LoRa transceiver module\r\n  LoRa.setPins(SS, RST, DIO0);\r\n\r\n  while (!LoRa.begin(BAND) &amp;&amp; counter &lt; 10) {\r\n    Serial.print(&quot;.&quot;);\r\n    counter++;\r\n    delay(500);\r\n  }\r\n  if (counter == 10) {\r\n    \/\/ Increment readingID on every new reading\r\n    readingID++;\r\n    Serial.println(&quot;Starting LoRa failed!&quot;); \r\n  }\r\n  Serial.println(&quot;LoRa Initialization OK!&quot;);\r\n  display.setCursor(0,10);\r\n  display.clearDisplay();\r\n  display.print(&quot;LoRa Initializing OK!&quot;);\r\n  display.display();\r\n  delay(2000);\r\n}\r\n\r\nvoid startBME(){\r\n  I2Cone.begin(SDA, SCL, 100000); \r\n  bool status1 = bme.begin(0x76, &amp;I2Cone);  \r\n  if (!status1) {\r\n    Serial.println(&quot;Could not find a valid BME280_1 sensor, check wiring!&quot;);\r\n    while (1);\r\n  }\r\n}\r\n\r\nvoid getReadings(){\r\n  temperature = bme.readTemperature();\r\n  humidity = bme.readHumidity();\r\n  pressure = bme.readPressure() \/ 100.0F;\r\n}\r\n\r\nvoid sendReadings() {\r\n  LoRaMessage = String(readingID) + &quot;\/&quot; + String(temperature) + &quot;&amp;&quot; + String(humidity) + &quot;#&quot; + String(pressure);\r\n  \/\/Send LoRa packet to receiver\r\n  LoRa.beginPacket();\r\n  LoRa.print(LoRaMessage);\r\n  LoRa.endPacket();\r\n  \r\n  display.clearDisplay();\r\n  display.setCursor(0,0);\r\n  display.setTextSize(1);\r\n  display.print(&quot;LoRa packet sent!&quot;);\r\n  display.setCursor(0,20);\r\n  display.print(&quot;Temperature:&quot;);\r\n  display.setCursor(72,20);\r\n  display.print(temperature);\r\n  display.setCursor(0,30);\r\n  display.print(&quot;Humidity:&quot;);\r\n  display.setCursor(54,30);\r\n  display.print(humidity);\r\n  display.setCursor(0,40);\r\n  display.print(&quot;Pressure:&quot;);\r\n  display.setCursor(54,40);\r\n  display.print(pressure);\r\n  display.setCursor(0,50);\r\n  display.print(&quot;Reading ID:&quot;);\r\n  display.setCursor(66,50);\r\n  display.print(readingID);\r\n  display.display();\r\n  Serial.print(&quot;Sending packet: &quot;);\r\n  Serial.println(readingID);\r\n  readingID++;\r\n}\r\n\r\nvoid setup() {\r\n  \/\/initialize Serial Monitor\r\n  Serial.begin(115200);\r\n  startOLED();\r\n  startBME();\r\n  startLoRA();\r\n}\r\nvoid loop() {\r\n  getReadings();\r\n  sendReadings();\r\n  delay(10000);\r\n}\r\n<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/github.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/raw\/master\/Projects\/ESP32\/ESP3_LoRa\/LoRa_Sender_BME280\/LoRa_Sender_BME280.ino\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How the Code Works<\/h3>\n\n\n\n<p>Start by including the necessary libraries for LoRa, OLED display and BME280 sensor.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/Libraries for LoRa\n#include &lt;SPI.h&gt;\n#include &lt;LoRa.h&gt;\n\n\/\/Libraries for OLED Display\n#include &lt;Wire.h&gt;\n#include &lt;Adafruit_GFX.h&gt;\n#include &lt;Adafruit_SSD1306.h&gt;\n\n\/\/Libraries for BME280\n#include &lt;Adafruit_Sensor.h&gt;\n#include &lt;Adafruit_BME280.h&gt;<\/code><\/pre>\n\n\n\n<p>Define the pins used by the LoRa transceiver module. We&#8217;re using the <a href=\"https:\/\/makeradvisor.com\/esp32-sx1276-lora-ssd1306-oled\/\" target=\"_blank\" rel=\"noreferrer noopener\" aria-label=\" (opens in a new tab)\">TTGO LoRa32 SX1276 OLED board V1.0<\/a> and these are the pins used by the LoRa chip:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/define the pins used by the LoRa transceiver module\n#define SCK 5\n#define MISO 19\n#define MOSI 27\n#define SS 18\n#define RST 14\n#define DIO0 26<\/code><\/pre>\n\n\n\n<p class=\"rntbox rntclblue\"><strong>Note: <\/strong> if you&#8217;re using another LoRa board, check the pins used by the LoRa transceiver chip.<\/p>\n\n\n\n<p>Select the LoRa frequency:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>#define BAND 866E6<\/code><\/pre>\n\n\n\n<p>Define the OLED pins.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>#define OLED_SDA 4\n#define OLED_SCL 15 \n#define OLED_RST 16<\/code><\/pre>\n\n\n\n<p>Define the OLED size.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>#define SCREEN_WIDTH 128 \/\/ OLED display width, in pixels\n#define SCREEN_HEIGHT 64 \/\/ OLED display height, in pixels<\/code><\/pre>\n\n\n\n<p>Define the pins used by the BME280 sensor.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/BME280 definition\n#define SDA 21\n#define SCL 13<\/code><\/pre>\n\n\n\n<p>Create an I2C instance for the BME280 sensor and a <span class=\"rnthl rntliteral\">bme<\/span> object.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>TwoWire I2Cone = TwoWire(1);\nAdafruit_BME280 bme;<\/code><\/pre>\n\n\n\n<p>Create some variables to hold the LoRa message, temperature, humidity, pressure and reading ID.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>int readingID = 0;\n\nint counter = 0;\nString LoRaMessage = \"\";\n\nfloat temperature = 0;\nfloat humidity = 0;\nfloat pressure = 0;<\/code><\/pre>\n\n\n\n<p>Create a <span class=\"rnthl rntliteral\">display<\/span> object for the OLED display.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &amp;Wire, OLED_RST);<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">setup()<\/h4>\n\n\n\n<p>In the <span class=\"rnthl rntliteral\">setup()<\/span>, we call several functions that were created previously in the code to initialize the OLED display, the BME280 and the LoRa transceiver module.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>void setup() {\n  Serial.begin(115200);\n  startOLED();\n  startBME();\n  startLoRA();\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">loop()<\/h4>\n\n\n\n<p>In the <span class=\"rnthl rntliteral\">loop()<\/span>, we call the <span class=\"rnthl rntliteral\">getReadings()<\/span> and <span class=\"rnthl rntliteral\">sendReadings()<\/span> functions that were also previously created. These functions are responsible for getting readings from the BME280 sensor, and to send those readings via LoRa, respectively.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>void loop() {\n  getReadings();\n  sendReadings();\n  delay(10000);\n}<\/code><\/pre>\n\n\n\n<p><strong>getReadings()<\/strong><\/p>\n\n\n\n<p>Getting sensor readings is as simple as using the <span class=\"rnthl rntliteral\">readTemperature()<\/span>, <span class=\"rnthl rntliteral\">readHumidity()<\/span>, and <span class=\"rnthl rntliteral\">readPressure()<\/span> methods on the <span class=\"rnthl rntliteral\">bme<\/span> object:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>void getReadings(){\n  temperature = bme.readTemperature();\n  humidity = bme.readHumidity();\n  pressure = bme.readPressure() \/ 100.0F;\n}<\/code><\/pre>\n\n\n\n<p><strong>sendReadings()<\/strong><\/p>\n\n\n\n<p>To send the readings via LoRa, we concatenate all the readings on a single variable, <span class=\"rnthl rntliteral\">LoRaMessage<\/span>:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>void sendReadings() {\n  LoRaMessage = String(readingID) + \"\/\" + String(temperature) + \"&amp;\" + String(humidity) + \"#\" + String(pressure);<\/code><\/pre>\n\n\n\n<p>Note that each reading is separated with a special character, so the receiver can easily identify each value. <\/p>\n\n\n\n<p>Then, send the packet using the following:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>LoRa.beginPacket();\nLoRa.print(LoRaMessage);\nLoRa.endPacket();<\/code><\/pre>\n\n\n\n<p>Each time we send a LoRa packet, we increase the <span class=\"rnthl rntliteral\">readingID<\/span> variable so that we have an idea on how many packets were sent. You can delete this variable if you want.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>readingID++;<\/code><\/pre>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">loop()<\/span> is repeated every 10000 milliseconds (10 seconds). So, new sensor readings are sent every 10 seconds. You can change this delay time if you want.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>delay(10000);<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Testing the LoRa Sender<\/h3>\n\n\n\n<p>Upload the code to your ESP32 LoRa Sender Board.<\/p>\n\n\n\n<p>Go to <strong>Tools <\/strong>&gt; <strong>Port<\/strong> and select the COM port it is connected to. Then, go to <strong>Tools <\/strong>&gt; <strong>Board <\/strong>and select the board you&#8217;re using. In our case, it&#8217;s the TTGO LoRa32-OLED V1.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"811\" height=\"574\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/selecting-lora-ttgo-oled-board.png?resize=811%2C574&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Arduino IDE selecting TTGO LoRa32-OLED-V1 board\" class=\"wp-image-90851\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/selecting-lora-ttgo-oled-board.png?w=811&amp;quality=100&amp;strip=all&amp;ssl=1 811w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/selecting-lora-ttgo-oled-board.png?resize=300%2C212&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/selecting-lora-ttgo-oled-board.png?resize=768%2C544&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 811px) 100vw, 811px\" \/><\/figure><\/div>\n\n\n<p>Finally, press the upload button.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"34\" height=\"29\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2016\/12\/arduino-ide-upload-button.png?resize=34%2C29&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Arduino IDE Upload button\" class=\"wp-image-65439\"\/><\/figure><\/div>\n\n\n<p>Open the Serial Monitor at a baud rate of 115200. You should get something as shown below.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"670\" height=\"381\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-Send-Sensor-Readings-Arduino-IDE-Serial-Monitor.png?resize=670%2C381&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Arduino IDE: ESP32 LoRa Sender Circuit Demonstration\" class=\"wp-image-90852\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-Send-Sensor-Readings-Arduino-IDE-Serial-Monitor.png?w=670&amp;quality=100&amp;strip=all&amp;ssl=1 670w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-Send-Sensor-Readings-Arduino-IDE-Serial-Monitor.png?resize=300%2C171&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 670px) 100vw, 670px\" \/><\/figure><\/div>\n\n\n<p>The OLED of your board should be displaying the latest sensor readings.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"422\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/ESP32-LoRa32-TTGO-OLED-Send-BME280-Readings-board.jpg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"TTGO LoRa32 SX1276 OLED board ESP32 Sender Circuit Schematic\" class=\"wp-image-90907\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/ESP32-LoRa32-TTGO-OLED-Send-BME280-Readings-board.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/ESP32-LoRa32-TTGO-OLED-Send-BME280-Readings-board.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p>Your LoRa Sender is ready. Now, let&#8217;s move on to the LoRa Receiver.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">LoRa Receiver<\/h2>\n\n\n\n<p>The LoRa Receiver gets incoming LoRa packets and displays the received readings on an asynchronous web server. Besides the sensor readings, we also display the last time those readings were received and the RSSI (received signal strength indicator).<\/p>\n\n\n\n<p>The following figure shows the web server we&#8217;ll build.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"450\" height=\"872\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-Web-Server-Sensor-Readings-ESP32-BME280.png?resize=450%2C872&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"TTGO LoRa32 board ESP32 Receiver Web Server Example\" class=\"wp-image-90908\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-Web-Server-Sensor-Readings-ESP32-BME280.png?w=450&amp;quality=100&amp;strip=all&amp;ssl=1 450w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-Web-Server-Sensor-Readings-ESP32-BME280.png?resize=155%2C300&amp;quality=100&amp;strip=all&amp;ssl=1 155w\" sizes=\"(max-width: 450px) 100vw, 450px\" \/><\/figure><\/div>\n\n\n<p>As you can see, it contains a background image and styles to make the web page more appealing. There are several ways to <a href=\"https:\/\/randomnerdtutorials.com\/display-images-esp32-esp8266-web-server\/\">display images on an ESP32 web server<\/a>. We&#8217;ll store the image on the ESP32 filesystem (LittleFS). We&#8217;ll also store the HTML file on LittleFS.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Organizing your Files<\/h3>\n\n\n\n<p>To build the web server you need three different files: the Arduino sketch, the HTML file and the image. The HTML file and the image should be saved inside a folder called <em><strong>data<\/strong> <\/em>inside the Arduino sketch folder, as shown below.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"715\" height=\"382\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-receiver-file-structure.png?resize=715%2C382&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 Filesystem plugin files structure organized data folder HTML jpg\" class=\"wp-image-90853\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-receiver-file-structure.png?w=715&amp;quality=100&amp;strip=all&amp;ssl=1 715w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-receiver-file-structure.png?resize=300%2C160&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 715px) 100vw, 715px\" \/><\/figure><\/div>\n\n\n<h3 class=\"wp-block-heading\">Creating the HTML File<\/h3>\n\n\n\n<p>Create an&nbsp;<em>index.html<\/em>&nbsp;file with the following content or&nbsp;<strong><a href=\"https:\/\/github.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/raw\/master\/Projects\/ESP32\/ESP3_LoRa\/ESP3_LoRa.zip\" target=\"_blank\" rel=\"noreferrer noopener\" aria-label=\"download all the project files here (opens in a new tab)\">download all the project files here<\/a><\/strong>:<\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-html\">&lt;!DOCTYPE HTML&gt;&lt;html&gt;\r\n&lt;head&gt;\r\n  &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;\r\n  &lt;link rel=&quot;icon&quot; href=&quot;data:,&quot;&gt;\r\n  &lt;title&gt;ESP32 (LoRa + Server)&lt;\/title&gt;\r\n  &lt;link rel=&quot;stylesheet&quot; href=&quot;https:\/\/use.fontawesome.com\/releases\/v5.7.2\/css\/all.css&quot; integrity=&quot;sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr&quot; crossorigin=&quot;anonymous&quot;&gt;\r\n  &lt;style&gt;\r\n    body {\r\n      margin: 0;\r\n      font-family: Arial, Helvetica, sans-serif;\r\n      text-align: center;\r\n    }\r\n    header {\r\n      margin: 0;\r\n      padding-top: 5vh;\r\n      padding-bottom: 5vh;\r\n      overflow: hidden;\r\n      background-image: url(winter);\r\n      background-size: cover;\r\n      color: white;\r\n    }\r\n    h2 {\r\n      font-size: 2.0rem;\r\n    }\r\n    p { font-size: 1.2rem; }\r\n    .units { font-size: 1.2rem; }\r\n    .readings { font-size: 2.0rem; }\r\n  &lt;\/style&gt;\r\n&lt;\/head&gt;\r\n&lt;body&gt;\r\n  &lt;header&gt;\r\n    &lt;h2&gt;ESP32 (LoRa + Server)&lt;\/h2&gt;\r\n    &lt;p&gt;&lt;strong&gt;Last received packet:&lt;br\/&gt;&lt;span id=&quot;timestamp&quot;&gt;%TIMESTAMP%&lt;\/span&gt;&lt;\/strong&gt;&lt;\/p&gt;\r\n    &lt;p&gt;LoRa RSSI: &lt;span id=&quot;rssi&quot;&gt;%RSSI%&lt;\/span&gt;&lt;\/p&gt;\r\n  &lt;\/header&gt;\r\n&lt;main&gt;\r\n  &lt;p&gt;\r\n    &lt;i class=&quot;fas fa-thermometer-half&quot; style=&quot;color:#059e8a;&quot;&gt;&lt;\/i&gt; Temperature: &lt;span id=&quot;temperature&quot; class=&quot;readings&quot;&gt;%TEMPERATURE%&lt;\/span&gt;\r\n    &lt;sup&gt;&amp;deg;C&lt;\/sup&gt;\r\n  &lt;\/p&gt;\r\n  &lt;p&gt;\r\n    &lt;i class=&quot;fas fa-tint&quot; style=&quot;color:#00add6;&quot;&gt;&lt;\/i&gt; Humidity: &lt;span id=&quot;humidity&quot; class=&quot;readings&quot;&gt;%HUMIDITY%&lt;\/span&gt;\r\n    &lt;sup&gt;&amp;#37;&lt;\/sup&gt;\r\n  &lt;\/p&gt;\r\n  &lt;p&gt;\r\n    &lt;i class=&quot;fas fa-angle-double-down&quot; style=&quot;color:#e8c14d;&quot;&gt;&lt;\/i&gt; Pressure: &lt;span id=&quot;pressure&quot; class=&quot;readings&quot;&gt;%PRESSURE%&lt;\/span&gt;\r\n    &lt;sup&gt;hpa&lt;\/sup&gt;\r\n  &lt;\/p&gt;\r\n&lt;\/main&gt;\r\n&lt;script&gt;\r\nsetInterval(updateValues, 10000, &quot;temperature&quot;);\r\nsetInterval(updateValues, 10000, &quot;humidity&quot;);\r\nsetInterval(updateValues, 10000, &quot;pressure&quot;);\r\nsetInterval(updateValues, 10000, &quot;rssi&quot;);\r\nsetInterval(updateValues, 10000, &quot;timestamp&quot;);\r\n\r\nfunction updateValues(value) {\r\n  var xhttp = new XMLHttpRequest();\r\n  xhttp.onreadystatechange = function() {\r\n    if (this.readyState == 4 &amp;&amp; this.status == 200) {\r\n      document.getElementById(value).innerHTML = this.responseText;\r\n    }\r\n  };\r\n  xhttp.open(&quot;GET&quot;, &quot;\/&quot; + value, true);\r\n  xhttp.send();\r\n}\r\n&lt;\/script&gt;\r\n&lt;\/body&gt;\r\n&lt;\/html&gt;\r\n<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/github.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/raw\/master\/Projects\/ESP32\/ESP3_LoRa\/LoRa_Receiver_Web_Server\/data\/index.html\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<p>We&#8217;ve also included the CSS styles on the HTML file as well as some JavaScript that is responsible for updating the sensor readings automatically.<\/p>\n\n\n\n<p>Something important to notice are the placeholders. The placeholders go between <strong>%<\/strong> signs: <span class=\"rnthl rntliteral\">%TIMESTAMP%<\/span>, <span class=\"rnthl rntliteral\">%TEMPERATURE%<\/span>, <span class=\"rnthl rntliteral\">%HUMIDITY%<\/span>, <span class=\"rnthl rntliteral\">%PRESSURE%<\/span> and <span class=\"rnthl rntliteral\">%RSSI%<\/span>.<\/p>\n\n\n\n<p>These placeholders will then be replaced with the actual values by the Arduino code.<\/p>\n\n\n\n<p>The styles are added between the <span class=\"rnthl rntliteral\"><span style=\"color: #333399;\">&lt;style&gt;<\/span><\/span> and <span class=\"rnthl rntliteral\"><span style=\"color: #333399;\">&lt;\/style&gt;<\/span><\/span> tags.<\/p>\n\n\n\n<pre class=\"wp-block-code language-html\"><code>&lt;style&gt;\n  body {\n    margin: 0;\n    font-family: Arial, Helvetica, sans-serif;\n    text-align: center;\n  }\n  header {\n    margin: 0;\n    padding-top: 10vh;\n    padding-bottom: 5vh;\n    overflow: hidden;\n    width: 100%;\n    background-image: url(winter.jpg);\n    background-size: cover;\n    color: white;\n  }\n  h2 {\n    font-size: 2.0rem;\n  }\n  p { font-size: 1.2rem; }\n  .units { font-size: 1.2rem; }\n  .readings { font-size: 2.0rem; }\n&lt;\/style&gt;<\/code><\/pre>\n\n\n\n<p>If you want a different image for your background, you just need to modify the following line to include your image&#8217;s name. In our case, it is called <em>winter.jpg<\/em>.<\/p>\n\n\n\n<pre class=\"wp-block-code language-css\"><code>background-image: url(winter.jpg);<\/code><\/pre>\n\n\n\n<p>The JavaScript goes between the <span class=\"rnthl rntliteral\"><span style=\"color: #333399;\">&lt;scritpt&gt;<\/span><\/span> and <span class=\"rnthl rntliteral\"><span style=\"color: #333399;\">&lt;\/script&gt;<\/span><\/span> tags.<\/p>\n\n\n\n<pre class=\"wp-block-code language-javascript\"><code>&lt;script&gt;\nsetInterval(updateValues(\"temperature\"), 5000);\nsetInterval(updateValues(\"humidity\"), 5000);\nsetInterval(updateValues(\"pressure\"), 5000);\nsetInterval(updateValues(\"rssi\"), 5000);\nsetInterval(updateValues(\"timeAndDate\"), 5000);\n\nfunction updateValues(value) {\n  console.log(value);\n  var xhttp = new XMLHttpRequest();\n  xhttp.onreadystatechange = function() {\n    if (this.readyState == 4 &amp;&amp; this.status == 200) {\n      document.getElementById(value).innerHTML = this.responseText;\n    }\n  };\n  xhttp.open(\"GET\", \"\/\" + value, true);\n  xhttp.send();\n}\n&lt;\/script&gt;<\/code><\/pre>\n\n\n\n<p>We won&#8217;t explain in detail how the HTML and CSS works, but a good place to learn is the <a rel=\"noreferrer noopener\" href=\"https:\/\/www.w3schools.com\/css\/default.asp\" target=\"_blank\">W3Schools website<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">LoRa Receiver Arduino Sketch<\/h3>\n\n\n\n<p>Copy the following code to your Arduino IDE or <a href=\"https:\/\/github.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/raw\/master\/Projects\/ESP32\/ESP3_LoRa\/ESP3_LoRa.zip\" target=\"_blank\" rel=\"noreferrer noopener\" aria-label=\" (opens in a new tab)\"><strong>download all the project files here<\/strong><\/a>. Then, you need to type your network credentials (SSID and password) to make it work. <\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-c\">\/*********\r\n  Rui Santos &amp; Sara Santos - Random Nerd Tutorials\r\n  Complete project details at https:\/\/RandomNerdTutorials.com\/esp32-lora-sensor-web-server\/\r\n  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.\r\n  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\r\n*********\/\r\n\/\/ Import Wi-Fi library\r\n#include &lt;WiFi.h&gt;\r\n#include &quot;ESPAsyncWebServer.h&quot;\r\n\r\n#include &lt;LittleFS.h&gt;\r\n\r\n\/\/Libraries for LoRa\r\n#include &lt;SPI.h&gt;\r\n#include &lt;LoRa.h&gt;\r\n\r\n\/\/Libraries for OLED Display\r\n#include &lt;Wire.h&gt;\r\n#include &lt;Adafruit_GFX.h&gt;\r\n#include &lt;Adafruit_SSD1306.h&gt;\r\n\r\n\/\/ Libraries to get time from NTP Server\r\n#include &lt;NTPClient.h&gt;\r\n#include &lt;WiFiUdp.h&gt;\r\n\r\n\/\/define the pins used by the LoRa transceiver module\r\n#define SCK 5\r\n#define MISO 19\r\n#define MOSI 27\r\n#define SS 18\r\n#define RST 14\r\n#define DIO0 26\r\n\r\n\/\/433E6 for Asia\r\n\/\/866E6 for Europe\r\n\/\/915E6 for North America\r\n#define BAND 866E6\r\n\r\n\/\/OLED pins\r\n#define OLED_SDA 4\r\n#define OLED_SCL 15 \r\n#define OLED_RST 16\r\n#define SCREEN_WIDTH 128 \/\/ OLED display width, in pixels\r\n#define SCREEN_HEIGHT 64 \/\/ OLED display height, in pixels\r\n\r\n\/\/ Replace with your network credentials\r\nconst char* ssid     = &quot;REPLACE_WITH_YOUR_SSID&quot;;\r\nconst char* password = &quot;REPLACE_WITH_YOUR_PASSWORD&quot;;\r\n\r\n\/\/ Define NTP Client to get time\r\nWiFiUDP ntpUDP;\r\nNTPClient timeClient(ntpUDP);\r\n\r\n\/\/ Variables to save date and time\r\nString formattedDate;\r\nString day;\r\nString hour;\r\nString timestamp;\r\n\r\n\r\n\/\/ Initialize variables to get and save LoRa data\r\nint rssi;\r\nString loRaMessage;\r\nString temperature;\r\nString humidity;\r\nString pressure;\r\nString readingID;\r\n\r\n\/\/ Create AsyncWebServer object on port 80\r\nAsyncWebServer server(80);\r\n\r\nAdafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &amp;Wire, OLED_RST);\r\n\r\n\/\/ Replaces placeholder with DHT values\r\nString processor(const String&amp; var){\r\n  \/\/Serial.println(var);\r\n  if(var == &quot;TEMPERATURE&quot;){\r\n    return temperature;\r\n  }\r\n  else if(var == &quot;HUMIDITY&quot;){\r\n    return humidity;\r\n  }\r\n  else if(var == &quot;PRESSURE&quot;){\r\n    return pressure;\r\n  }\r\n  else if(var == &quot;TIMESTAMP&quot;){\r\n    return timestamp;\r\n  }\r\n  else if (var == &quot;RRSI&quot;){\r\n    return String(rssi);\r\n  }\r\n  return String();\r\n}\r\n\r\n\/\/Initialize OLED display\r\nvoid startOLED(){\r\n  \/\/reset OLED display via software\r\n  pinMode(OLED_RST, OUTPUT);\r\n  digitalWrite(OLED_RST, LOW);\r\n  delay(20);\r\n  digitalWrite(OLED_RST, HIGH);\r\n\r\n  \/\/initialize OLED\r\n  Wire.begin(OLED_SDA, OLED_SCL);\r\n  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3c, false, false)) { \/\/ Address 0x3C for 128x32\r\n    Serial.println(F(&quot;SSD1306 allocation failed&quot;));\r\n    for(;;); \/\/ Don't proceed, loop forever\r\n  }\r\n  display.clearDisplay();\r\n  display.setTextColor(WHITE);\r\n  display.setTextSize(1);\r\n  display.setCursor(0,0);\r\n  display.print(&quot;LORA SENDER&quot;);\r\n}\r\n\r\n\/\/Initialize LoRa module\r\nvoid startLoRA(){\r\n  int counter;\r\n  \/\/SPI LoRa pins\r\n  SPI.begin(SCK, MISO, MOSI, SS);\r\n  \/\/setup LoRa transceiver module\r\n  LoRa.setPins(SS, RST, DIO0);\r\n\r\n  while (!LoRa.begin(BAND) &amp;&amp; counter &lt; 10) {\r\n    Serial.print(&quot;.&quot;);\r\n    counter++;\r\n    delay(500);\r\n  }\r\n  if (counter == 10) {\r\n    \/\/ Increment readingID on every new reading\r\n    Serial.println(&quot;Starting LoRa failed!&quot;); \r\n  }\r\n  Serial.println(&quot;LoRa Initialization OK!&quot;);\r\n  display.setCursor(0,10);\r\n  display.clearDisplay();\r\n  display.print(&quot;LoRa Initializing OK!&quot;);\r\n  display.display();\r\n  delay(2000);\r\n}\r\n\r\nvoid connectWiFi(){\r\n  \/\/ Connect to Wi-Fi network with SSID and password\r\n  Serial.print(&quot;Connecting to &quot;);\r\n  Serial.println(ssid);\r\n  WiFi.begin(ssid, password);\r\n  while (WiFi.status() != WL_CONNECTED) {\r\n    delay(500);\r\n    Serial.print(&quot;.&quot;);\r\n  }\r\n  \/\/ Print local IP address and start web server\r\n  Serial.println(&quot;&quot;);\r\n  Serial.println(&quot;WiFi connected.&quot;);\r\n  Serial.println(&quot;IP address: &quot;);\r\n  Serial.println(WiFi.localIP());\r\n  display.setCursor(0,20);\r\n  display.print(&quot;Access web server at: &quot;);\r\n  display.setCursor(0,30);\r\n  display.print(WiFi.localIP());\r\n  display.display();\r\n}\r\n\r\n\/\/ Read LoRa packet and get the sensor readings\r\nvoid getLoRaData() {\r\n  Serial.print(&quot;Lora packet received: &quot;);\r\n  \/\/ Read packet\r\n  while (LoRa.available()) {\r\n    String LoRaData = LoRa.readString();\r\n    \/\/ LoRaData format: readingID\/temperature&amp;soilMoisture#batterylevel\r\n    \/\/ String example: 1\/27.43&amp;654#95.34\r\n    Serial.print(LoRaData); \r\n    \r\n    \/\/ Get readingID, temperature and soil moisture\r\n    int pos1 = LoRaData.indexOf('\/');\r\n    int pos2 = LoRaData.indexOf('&amp;');\r\n    int pos3 = LoRaData.indexOf('#');\r\n    readingID = LoRaData.substring(0, pos1);\r\n    temperature = LoRaData.substring(pos1 +1, pos2);\r\n    humidity = LoRaData.substring(pos2+1, pos3);\r\n    pressure = LoRaData.substring(pos3+1, LoRaData.length());    \r\n  }\r\n  \/\/ Get RSSI\r\n  rssi = LoRa.packetRssi();\r\n  Serial.print(&quot; with RSSI &quot;);    \r\n  Serial.println(rssi);\r\n}\r\n\r\n\/\/ Function to get date and time from NTPClient\r\nvoid getTimeStamp() {\r\n  while(!timeClient.update()) {\r\n    timeClient.forceUpdate();\r\n  }\r\n  \/\/ The formattedDate comes with the following format:\r\n  \/\/ 2018-05-28T16:00:13Z\r\n  \/\/ We need to extract date and time\r\n  formattedDate = timeClient.getFormattedDate();\r\n  Serial.println(formattedDate);\r\n\r\n  \/\/ Extract date\r\n  int splitT = formattedDate.indexOf(&quot;T&quot;);\r\n  day = formattedDate.substring(0, splitT);\r\n  Serial.println(day);\r\n  \/\/ Extract time\r\n  hour = formattedDate.substring(splitT+1, formattedDate.length()-1);\r\n  Serial.println(hour);\r\n  timestamp = day + &quot; &quot; + hour;\r\n}\r\n\r\nvoid setup() { \r\n  \/\/ Initialize Serial Monitor\r\n  Serial.begin(115200);\r\n  startOLED();\r\n  startLoRA();\r\n  connectWiFi();\r\n  \r\n  if(!LittleFS.begin()){\r\n    Serial.println(&quot;An Error has occurred while mounting LittleFS&quot;);\r\n    return;\r\n  }\r\n  \/\/ Route for root \/ web page\r\n  server.on(&quot;\/&quot;, HTTP_GET, [](AsyncWebServerRequest *request){\r\n    request-&gt;send(LittleFS, &quot;\/index.html&quot;, String(), false, processor);\r\n  });\r\n  server.on(&quot;\/temperature&quot;, HTTP_GET, [](AsyncWebServerRequest *request){\r\n    request-&gt;send(200, &quot;text\/plain&quot;, temperature.c_str());\r\n  });\r\n  server.on(&quot;\/humidity&quot;, HTTP_GET, [](AsyncWebServerRequest *request){\r\n    request-&gt;send(200, &quot;text\/plain&quot;, humidity.c_str());\r\n  });\r\n  server.on(&quot;\/pressure&quot;, HTTP_GET, [](AsyncWebServerRequest *request){\r\n    request-&gt;send(200, &quot;text\/plain&quot;, pressure.c_str());\r\n  });\r\n  server.on(&quot;\/timestamp&quot;, HTTP_GET, [](AsyncWebServerRequest *request){\r\n    request-&gt;send(200, &quot;text\/plain&quot;, timestamp.c_str());\r\n  });\r\n  server.on(&quot;\/rssi&quot;, HTTP_GET, [](AsyncWebServerRequest *request){\r\n    request-&gt;send(200, &quot;text\/plain&quot;, String(rssi).c_str());\r\n  });\r\n  server.on(&quot;\/winter&quot;, HTTP_GET, [](AsyncWebServerRequest *request){\r\n    request-&gt;send(LittleFS, &quot;\/winter.jpg&quot;, &quot;image\/jpg&quot;);\r\n  });\r\n  \/\/ Start server\r\n  server.begin();\r\n  \r\n  \/\/ Initialize a NTPClient to get time\r\n  timeClient.begin();\r\n  \/\/ Set offset time in seconds to adjust for your timezone, for example:\r\n  \/\/ GMT +1 = 3600\r\n  \/\/ GMT +8 = 28800\r\n  \/\/ GMT -1 = -3600\r\n  \/\/ GMT 0 = 0\r\n  timeClient.setTimeOffset(0);\r\n}\r\n\r\nvoid loop() {\r\n  \/\/ Check if there are LoRa packets available\r\n  int packetSize = LoRa.parsePacket();\r\n  if (packetSize) {\r\n    getLoRaData();\r\n    getTimeStamp();\r\n  }\r\n}\r\n<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/github.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/raw\/master\/Projects\/ESP32\/ESP3_LoRa\/LoRa_Receiver_Web_Server\/LoRa_Receiver_Web_Server.ino\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How the Code Works<\/h3>\n\n\n\n<p>You start by including the necessary libraries. You need libraries to: <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>build the asynchronous web server;<\/li>\n\n\n\n<li>access the ESP32 filesystem (LittleFS);<\/li>\n\n\n\n<li>communicate with the LoRa chip;<\/li>\n\n\n\n<li>control the OLED display;<\/li>\n\n\n\n<li>get date and time from an NTP server.<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/ Import Wi-Fi library\n#include &lt;WiFi.h&gt;\n#include \"ESPAsyncWebServer.h\"\n\n#include &lt;LittleFS.h&gt;\n\n\/\/Libraries for LoRa\n#include &lt;SPI.h&gt;\n#include &lt;LoRa.h&gt;\n\n\/\/Libraries for OLED Display\n#include &lt;Wire.h&gt;\n#include &lt;Adafruit_GFX.h&gt;\n#include &lt;Adafruit_SSD1306.h&gt;\n\n\/\/ Libraries to get time from NTP Server\n#include &lt;NTPClient.h&gt;\n#include &lt;WiFiUdp.h&gt;<\/code><\/pre>\n\n\n\n<p>Define the pins used by the LoRa transceiver module.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>#define SCK 5\n#define MISO 19\n#define MOSI 27\n#define SS 18\n#define RST 14\n#define DIO0 26<\/code><\/pre>\n\n\n\n<p class=\"rntbox rntclblue\"><strong>Note: <\/strong> if you&#8217;re using another LoRa board, check the pins used by the LoRa transceiver chip.<\/p>\n\n\n\n<p>Define the LoRa frequency:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/433E6 for Asia\n\/\/866E6 for Europe\n\/\/915E6 for North America\n#define BAND 866E6<\/code><\/pre>\n\n\n\n<p>Set up the OLED pins:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>#define OLED_SDA 4\n#define OLED_SCL 15 \n#define OLED_RST 16\n#define SCREEN_WIDTH 128 \/\/ OLED display width, in pixels\n#define SCREEN_HEIGHT 64 \/\/ OLED display height, in pixels<\/code><\/pre>\n\n\n\n<p>Enter your network credentials in the following variables so that the ESP32 can connect to your local network.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>const char* ssid     = \"REPLACE_WITH_YOUR_SSID\";\nconst char* password = \"REPLACE_WITH_YOUR_PASSWORD\";<\/code><\/pre>\n\n\n\n<p>Define an NTP Client to get date and time:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>WiFiUDP ntpUDP;\nNTPClient timeClient(ntpUDP);<\/code><\/pre>\n\n\n\n<p>Create variables to save date and time:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>String formattedDate;\nString day;\nString hour;\nString timestamp;<\/code><\/pre>\n\n\n\n<p>More variables to store the sensor readings received via LoRa radio.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>int rssi;\nString loRaMessage;\nString temperature;\nString humidity;\nString pressure;\nString readingID;<\/code><\/pre>\n\n\n\n<p>Create an <span class=\"rnthl rntliteral\">AsyncWebServer<\/span> object called <span class=\"rnthl rntliteral\">server<\/span> on port 80.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>AsyncWebServer server(80);<\/code><\/pre>\n\n\n\n<p>Create an object called <span class=\"rnthl rntliteral\">display<\/span> for the OLED display:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>AsyncWebServer server(80);<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">processor()<\/h4>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">processor()<\/span> function is what will attribute values to the placeholders we\u2019ve created on the HTML file. <\/p>\n\n\n\n<p>It accepts as argument the placeholder and should return a String that will replace that placeholder.<\/p>\n\n\n\n<p>For example, if it finds the <span class=\"rnthl rntliteral\">TEMPERATURE<\/span> placeholder, it will return the <span class=\"rnthl rntliteral\">temperature<\/span> String variable.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/ Replaces placeholder with DHT values\nString processor(const String&amp; var){\n  \/\/Serial.println(var);\n  if(var == \"TEMPERATURE\"){\n    return temperature;\n  }\n  else if(var == \"HUMIDITY\"){\n    return humidity;\n  }\n  else if(var == \"PRESSURE\"){\n    return pressure;\n  }\n  else if(var == \"TIMESTAMP\"){\n    return timestamp;\n  }\n  else if (var == \"RRSI\"){\n    return String(rssi);\n  }\n  return String();\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">setup()<\/h4>\n\n\n\n<p>In the <span class=\"rnthl rntliteral\">setup()<\/span>, you initialize the OLED display, the LoRa communication, and connect to Wi-Fi.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>void setup() { \n  \/\/ Initialize Serial Monitor\n  Serial.begin(115200);\n  startOLED();\n  startLoRA();\n  connectWiFi();<\/code><\/pre>\n\n\n\n<p>You also initialize LittleFS:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>if(!LittleFS.begin()){\n  Serial.println(\"An Error has occurred while mounting LittleFS\");\n  return;\n}<\/code><\/pre>\n\n\n\n<p><strong>Async Web Server<\/strong><\/p>\n\n\n\n<p>The ESPAsyncWebServer library allows us to configure the routes where the server will be listening for incoming HTTP requests. <\/p>\n\n\n\n<p>For example, when a request is received on the route URL, we send the <em>index.html<\/em> file that is saved in the ESP32 LittleFS:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>server.on(\"\/\", HTTP_GET, &#091;](AsyncWebServerRequest *request){\n  request-&gt;send(LittleFS, \"\/index.html\", String(), false, processor);\n});<\/code><\/pre>\n\n\n\n<p>As mentioned previously, we added a bit of Javascript to the HTML file that is responsible for updating the web page every 10 seconds. When that happens, it makes a request on the <span class=\"rnthl rntliteral\"><strong>\/temperature<\/strong><\/span>, <span class=\"rnthl rntliteral\"><strong>\/humidity<\/strong><\/span>, <span class=\"rnthl rntliteral\"><strong>\/pressure<\/strong><\/span>, <span class=\"rnthl rntliteral\"><strong>\/timestamp<\/strong><\/span>, <span class=\"rnthl rntliteral\"><strong>\/rssi<\/strong><\/span> URLs.<\/p>\n\n\n\n<p>So, we need to handle what happens when we receive those requests. We simply need to send the <span class=\"rnthl rntliteral\">temperature<\/span>, <span class=\"rnthl rntliteral\">humidity<\/span>, <span class=\"rnthl rntliteral\">pressure<\/span>, <span class=\"rnthl rntliteral\">timestamp<\/span> and <span class=\"rnthl rntliteral\">rssi<\/span> variables. The variables should be sent in char format, that&#8217;s why we use the <span class=\"rnthl rntliteral\">.c_str()<\/span> method.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>server.on(\"\/temperature\", HTTP_GET, &#091;](AsyncWebServerRequest *request){\n  request-&gt;send_P(200, \"text\/plain\", temperature.c_str());\n});\nserver.on(\"\/humidity\", HTTP_GET, &#091;](AsyncWebServerRequest *request){\n  request-&gt;send_P(200, \"text\/plain\", humidity.c_str());\n});\nserver.on(\"\/pressure\", HTTP_GET, &#091;](AsyncWebServerRequest *request){\n  request-&gt;send_P(200, \"text\/plain\", pressure.c_str());\n});\nserver.on(\"\/timestamp\", HTTP_GET, &#091;](AsyncWebServerRequest *request){\n  request-&gt;send_P(200, \"text\/plain\", timestamp.c_str());\n});\nserver.on(\"\/rssi\", HTTP_GET, &#091;](AsyncWebServerRequest *request){\n  request-&gt;send_P(200, \"text\/plain\", String(rssi).c_str());\n});<\/code><\/pre>\n\n\n\n<p>Because we included an image in the web page, we&#8217;ll get a request &#8220;asking&#8221; for the image. So, we need to send the image that is saved on the ESP32 LittleFS.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>server.on(\"\/winter\", HTTP_GET, &#091;](AsyncWebServerRequest *request){\n  request-&gt;send(LittleFS, \"\/winter.jpg\", \"image\/jpg\");\n});<\/code><\/pre>\n\n\n\n<p>Finally, start the web server.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>server.begin();<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">NTPClient<\/h4>\n\n\n\n<p>Still in the <span class=\"rnthl rntliteral\">setup()<\/span>, create an NTP client to get the time from the internet.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>timeClient.begin();<\/code><\/pre>\n\n\n\n<p>The time is returned in GMT format, so if you need to adjust for your timezone, you can use the following:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/ Set offset time in seconds to adjust for your timezone, for example:\n\/\/ GMT +1 = 3600\n\/\/ GMT +8 = 28800\n\/\/ GMT -1 = -3600\n\/\/ GMT 0 = 0\ntimeClient.setTimeOffset(0);<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">loop()<\/h3>\n\n\n\n<p>In the <span class=\"rnthl rntliteral\">loop()<\/span>, we listen for incoming LoRa packets:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>int packetSize = LoRa.parsePacket();<\/code><\/pre>\n\n\n\n<p>If a new LoRa packet is available, we call the <span class=\"rnthl rntliteral\">getLoRaData()<\/span> and <span class=\"rnthl rntliteral\">getTimeStamp()<\/span> functions.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>if (packetSize) {\n  getLoRaData();\n  getTimeStamp();\n}<\/code><\/pre>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">getLoRaData()<\/span> function receives the LoRa message and splits it to get the different readings.<\/p>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">getTimeStamp()<\/span> function gets the time and date from the internet at the moment we receive the packet.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Uploading Code and Files<\/h3>\n\n\n\n<p>After inserting your network credentials, save your sketch. Then, in your Arduino IDE go to <strong>Sketch<\/strong> &gt; <strong>Show Sketch Folder<\/strong>, and create a folder called <strong><em>data<\/em><\/strong>. <\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"351\" height=\"271\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/06\/arduino-ide-show-sketch-folder.png?resize=351%2C271&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Arduino IDE Open Sketch Folder to create data folder\" class=\"wp-image-158892\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/06\/arduino-ide-show-sketch-folder.png?w=351&amp;quality=100&amp;strip=all&amp;ssl=1 351w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/06\/arduino-ide-show-sketch-folder.png?resize=300%2C232&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 351px) 100vw, 351px\" \/><\/figure><\/div>\n\n\n<p><strong>Inside that folder, you should have the HTML file and the image file.<\/strong><\/p>\n\n\n\n<p>After making sure you have all the needed files in the right directories, you need to upload the files to the ESP32 LittleFS filesystem.<\/p>\n\n\n\n<p>Press [<strong>Ctrl<\/strong>] + [<strong>Shift<\/strong>] + [<strong>P<\/strong>] on Windows or [<strong>\u2318<\/strong>] + [<strong>Shift<\/strong>] + [<strong>P<\/strong>] on MacOS to open the command palette. Search for the&nbsp;<strong>Upload LittleFS to Pico\/ESP8266\/ESP32<\/strong>&nbsp;command and click on it.<\/p>\n\n\n\n<p>If you don\u2019t have this option it&#8217;s because you didn\u2019t install the filesystem uploader plugin.<a href=\"https:\/\/randomnerdtutorials.com\/arduino-ide-2-install-esp32-littlefs\/\">&nbsp;Check this tutorial<\/a>.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><img loading=\"lazy\" decoding=\"async\" width=\"744\" height=\"401\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/06\/upload-files-little-fs-esp32-arduino-ide.png?resize=744%2C401&amp;quality=100&amp;strip=all&amp;ssl=1\" alt=\"ESP32 Sketch Data Upload LittleFS Arduino IDE\" class=\"wp-image-158893\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/06\/upload-files-little-fs-esp32-arduino-ide.png?w=744&amp;quality=100&amp;strip=all&amp;ssl=1 744w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/06\/upload-files-little-fs-esp32-arduino-ide.png?resize=300%2C162&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 744px) 100vw, 744px\" \/><\/figure><\/div>\n\n\n<p class=\"rntbox rntcred\"><strong>Important:&nbsp;<\/strong>make sure the Serial Monitor is closed before uploading to the filesystem. Otherwise, the upload will fail.<\/p>\n\n\n\n<p>After a few seconds, the files should be successfully uploaded to LittleFS.<\/p>\n\n\n\n<p>Now, upload the sketch to your board.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"36\" height=\"39\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2021\/05\/arduino-ide-2-upload-button.png?resize=36%2C39&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Arduino IDE 2 Upload Button\" class=\"wp-image-146269\"\/><\/figure><\/div>\n\n\n<p>Open the Serial Monitor at a baud rate of 115200.<\/p>\n\n\n\n<p>You should get the ESP32 IP address, and you should start receiving LoRa packets from the sender.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"670\" height=\"491\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-receiver-web-server-serial-monitor.png?resize=670%2C491&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 Arduino IDE Serial Monitor window\" class=\"wp-image-90902\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-receiver-web-server-serial-monitor.png?w=670&amp;quality=100&amp;strip=all&amp;ssl=1 670w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-receiver-web-server-serial-monitor.png?resize=300%2C220&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 670px) 100vw, 670px\" \/><\/figure><\/div>\n\n\n<p>You should also get the IP address displayed on the OLED.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"422\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/ESP32-LoRa32-TTGO-OLED-Web-Server-board-receiver.jpg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"TTGO LoRa32 SX1276 OLED board ESP32 Receiver Circuit Schematic web server\" class=\"wp-image-90909\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/ESP32-LoRa32-TTGO-OLED-Web-Server-board-receiver.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/ESP32-LoRa32-TTGO-OLED-Web-Server-board-receiver.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<h2 class=\"wp-block-heading\">Demonstration<\/h2>\n\n\n\n<p>Open a browser and type your ESP32 IP address. You should see the web server with the latest sensor readings.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"422\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/ESP32-LoRa-Web-Server-Demonstration.jpg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 LoRa + Web Server + Sensor readings\" class=\"wp-image-91055\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/ESP32-LoRa-Web-Server-Demonstration.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/ESP32-LoRa-Web-Server-Demonstration.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p>With these boards we were able to get a stable LoRa communication up to 180 meters (590 ft) in open field. These means that we can have the sender and receiver 180 meters apart and we\u2019re still able to get and check the readings on the web server.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"421\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-range-experiment.jpg?resize=750%2C421&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"LoRa32 SX1276 OLED Board Communication Range Experiment\" class=\"wp-image-91050\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-range-experiment.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/LoRa-range-experiment.jpg?resize=300%2C168&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p>Getting a stable communication at a distance of 180 meters with such low cost boards and without any further customization is really impressive. <\/p>\n\n\n\n<p>However, in a previous project using an <a href=\"https:\/\/makeradvisor.com\/tools\/rfm95-lora-transceiver-module\/\" target=\"_blank\" rel=\"noreferrer noopener\" aria-label=\"RFM95 SX1276 LoRa transceiver chip (opens in a new tab)\">RFM95 SX1276 LoRa transceiver chip<\/a> with an home made antenna, we got better results: more than 250 meters with many obstacles in between.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"227\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2018\/06\/lora-sender-circuit.jpg?resize=750%2C227&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"RFM95 LoRa SX1276 transceiver chip connected to an ESP32\" class=\"wp-image-63685\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2018\/06\/lora-sender-circuit.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2018\/06\/lora-sender-circuit.jpg?resize=300%2C91&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p>The communication range will really depend on your environment, the LoRa board you\u2019re using and many other variables.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>You can take this project further and build an off-the-grid monitoring system by adding solar panels and deep sleep to your LoRa sender. The following articles might help you do that:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/esp32-deep-sleep-arduino-ide-wake-up-sources\/\">ESP32 Deep Sleep with Arduino IDE and Wake Up Sources<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/randomnerdtutorials.com\/power-esp32-esp8266-solar-panels-battery-level-monitoring\/\">Power ESP32 with Solar Panels<\/a><\/li>\n\n\n\n<li><a aria-label=\"ESP32 with Built-in SX1276 LoRa and SSD1306 OLED Display (Review) (opens in a new tab)\" href=\"https:\/\/makeradvisor.com\/esp32-sx1276-lora-ssd1306-oled\/\" target=\"_blank\" rel=\"noreferrer noopener\">ESP32 with Built-in SX1276 LoRa and SSD1306 OLED Display (Review)<\/a><\/li>\n<\/ul>\n\n\n\n<p>You may also want to access your sensor readings from anywhere or plot them on a chart:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/visualize-esp32-esp8266-sensor-readings-from-anywhere\/\">Visualize Your Sensor Readings from Anywhere in the World (ESP32 + MySQL + PHP)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/randomnerdtutorials.com\/esp32-esp8266-plot-chart-web-server\/\">ESP32 Plot Sensor Readings in Real Time Charts \u2013 Web Server<\/a><\/li>\n<\/ul>\n\n\n\n<p>We hope you&#8217;ve found this project interesting. If you&#8217;d like to see more projects using LoRa radio, let us know in the comments&#8217; section.<\/p>\n\n\n\n<p>Thanks for reading.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this project, you&#8217;ll build a sensor monitoring system using a TTGO LoRa32 SX1276 OLED board that sends temperature, humidity and pressure readings via LoRa radio to an ESP32 LoRa &#8230; <\/p>\n<p class=\"read-more-container\"><a title=\"ESP32 LoRa Sensor Monitoring with Web Server (Long Range Communication)\" class=\"read-more button\" href=\"https:\/\/randomnerdtutorials.com\/esp32-lora-sensor-web-server\/#more-90811\" aria-label=\"Read more about ESP32 LoRa Sensor Monitoring with Web Server (Long Range Communication)\">CONTINUE READING \u00bb<\/a><\/p>\n","protected":false},"author":1,"featured_media":90906,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[281,276,277,299,264],"tags":[],"class_list":["post-90811","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-esp32-project","category-esp32","category-esp32-arduino-ide","category-0-esp32","category-project"],"aioseo_notices":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/11\/ESP32-LoRa32-TTGO-OLED-WEB-SERVER.jpg?fit=1280%2C720&quality=100&strip=all&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/90811","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/comments?post=90811"}],"version-history":[{"count":3,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/90811\/revisions"}],"predecessor-version":[{"id":167993,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/90811\/revisions\/167993"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/media\/90906"}],"wp:attachment":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/media?parent=90811"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/categories?post=90811"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/tags?post=90811"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}