ESP32 LoRa: мониторинг датчиков с веб-сервером (дальняя связь)
В этом проекте вы создадите систему мониторинга датчиков с использованием платы TTGO LoRa32 SX1276 OLED, которая отправляет показания температуры, влажности и давления по радио LoRa на приёмник ESP32 LoRa. Приёмник отображает последние показания датчиков на веб-сервере.
В этом проекте вы научитесь:
Отправлять показания датчиков по радио LoRa между двумя платами ESP32;
Добавлять возможности LoRa и Wi-Fi одновременно в ваши проекты (LoRa + веб-сервер на одной плате ESP32);
Использовать плату TTGO LoRa32 SX1276 OLED или аналогичные платы разработки для проектов IoT.
Рекомендуемое чтение: TTGO LoRa32 SX1276 OLED Board: Getting Started with Arduino IDE
Видео-демонстрация
Посмотрите видео-демонстрацию, чтобы увидеть, что вы будете создавать в этом руководстве.
Обзор проекта
На следующем изображении показан общий обзор проекта, который мы создадим в этом руководстве.
Передатчик LoRa отправляет показания датчика BME280 по радио LoRa каждые 10 секунд;
Приёмник LoRa получает показания и отображает их на веб-сервере;
Вы можете отслеживать показания датчиков, обращаясь к веб-серверу;
Передатчик LoRa и приёмник LoRa могут находиться на расстоянии нескольких сотен метров друг от друга в зависимости от их расположения. Таким образом, вы можете использовать этот проект для мониторинга показаний датчиков с ваших полей или теплиц, если они находятся на некотором удалении от вашего дома;
Приёмник LoRa работает на асинхронном веб-сервере, а файлы веб-страницы сохраняются в файловой системе ESP32 (LittleFS);
Приёмник LoRa также показывает дату и время получения последних показаний. Для получения даты и времени мы используем Протокол сетевого времени (NTP) с ESP32.
Для введения в связь LoRa: что такое LoRa, частоты LoRa, применение LoRa и многое другое, прочитайте нашу статью Getting Started ESP32 with LoRa using Arduino IDE.
Необходимые компоненты
Для этого проекта мы будем использовать следующие компоненты:
`TTGO LoRa32 SX1276 OLED board <https://makeradvisor.com/tools/ttgo-lora32-sx1276-esp32-oled/>`_ (2 шт.): это плата разработки ESP32 с чипом LoRa и встроенным OLED-дисплеем. Вы можете использовать аналогичные платы, или вы можете использовать ESP32 + чип LoRa + OLED по отдельности.
Датчик температуры, влажности и давления BME280. Вы сможете модифицировать этот проект для использования любого другого датчика.
Вам также понадобятся соединительные провода и макетная плата.
Вы можете использовать ссылки выше или перейти непосредственно на MakerAdvisor.com/tools чтобы найти все компоненты для ваших проектов по лучшей цене!
Подготовка Arduino IDE
Для программирования плат TTGO LoRa32 SX1276 OLED мы будем использовать Arduino IDE. Для загрузки файлов в файловую систему ESP32 мы будем использовать плагин ESP32 LittleFS.
Поэтому, прежде чем продолжить, вам нужно установить платы ESP32 и плагин файловой системы ESP32 в вашу Arduino IDE.
Установка библиотек
Для этого проекта вам нужно установить несколько библиотек.
Библиотеки LoRa, BME280, OLED и веб-сервера
Следующие библиотеки можно установить через Arduino Library Manager. Перейдите в Sketch > Include Library > Manage Libraries и найдите библиотеки по имени.
Библиотека LoRa: arduino-LoRa library by sandeep mistry
Библиотеки OLED: Adafruit_SSD1306 library и Adafruit_GFX library
Библиотеки BME280: Adafruit_BME280 library и Adafruit unified sensor library
Библиотеки веб-сервера: ESPAsyncWebServer (by ESP32Async) и AsyncTCP (by ESP32Async)
Библиотека NTPClient
Каждый раз, когда приёмник LoRa получает новое сообщение LoRa, он запрашивает дату и время с NTP-сервера, чтобы мы знали, когда был получен последний пакет.
Для этого мы будем использовать библиотеку NTPClient, форк от Taranais. Выполните следующие шаги для установки этой библиотеки в Arduino IDE:
ВАЖНО: мы не используем стандартную библиотеку NTPClient. Чтобы следовать этому руководству, вам нужно установить библиотеку, которую мы рекомендуем, используя следующие шаги.
Нажмите здесь, чтобы скачать библиотеку NTP Client. У вас должна быть .zip папка в папке Downloads
В вашей Arduino IDE перейдите в Sketch > Include Library > Add .ZIP library…
Выберите .ZIP файл библиотеки, который вы только что скачали.
Библиотека будет установлена через несколько секунд.
Передатчик LoRa
Передатчик LoRa подключён к датчику BME280 и отправляет показания температуры, влажности и давления каждые 10 секунд. Вы можете изменить этот период времени позже в коде.
Рекомендуемое чтение: ESP32 with BME280 Sensor using Arduino IDE (Pressure, Temperature, Humidity)
Схема подключения передатчика LoRa
Датчик BME280, который мы используем, взаимодействует с ESP32 по протоколу I2C. Подключите датчик, как показано на следующей схеме:
BME280 |
ESP32 |
|---|---|
VIN |
3.3 V |
GND |
GND |
SCL |
GPIO 13 |
SDA |
GPIO 21 |
Код передатчика LoRa
Следующий код считывает температуру, влажность и давление с датчика BME280 и отправляет показания по радио LoRa.
Скопируйте следующий код в вашу Arduino IDE.
/*********
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp32-lora-sensor-web-server/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*********/
//Libraries for LoRa
#include <SPI.h>
#include <LoRa.h>
//Libraries for OLED Display
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
//Libraries for BME280
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
//define the pins used by the LoRa transceiver module
#define SCK 5
#define MISO 19
#define MOSI 27
#define SS 18
#define RST 14
#define DIO0 26
//433E6 for Asia
//866E6 for Europe
//915E6 for North America
#define BAND 866E6
//OLED pins
#define OLED_SDA 4
#define OLED_SCL 15
#define OLED_RST 16
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
//BME280 definition
#define SDA 21
#define SCL 13
TwoWire I2Cone = TwoWire(1);
Adafruit_BME280 bme;
//packet counter
int readingID = 0;
int counter = 0;
String LoRaMessage = "";
float temperature = 0;
float humidity = 0;
float pressure = 0;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RST);
//Initialize OLED display
void startOLED(){
//reset OLED display via software
pinMode(OLED_RST, OUTPUT);
digitalWrite(OLED_RST, LOW);
delay(20);
digitalWrite(OLED_RST, HIGH);
//initialize OLED
Wire.begin(OLED_SDA, OLED_SCL);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3c, false, false)) { // Address 0x3C for 128x32
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(0,0);
display.print("LORA SENDER");
}
//Initialize LoRa module
void startLoRA(){
//SPI LoRa pins
SPI.begin(SCK, MISO, MOSI, SS);
//setup LoRa transceiver module
LoRa.setPins(SS, RST, DIO0);
while (!LoRa.begin(BAND) && counter < 10) {
Serial.print(".");
counter++;
delay(500);
}
if (counter == 10) {
// Increment readingID on every new reading
readingID++;
Serial.println("Starting LoRa failed!");
}
Serial.println("LoRa Initialization OK!");
display.setCursor(0,10);
display.clearDisplay();
display.print("LoRa Initializing OK!");
display.display();
delay(2000);
}
void startBME(){
I2Cone.begin(SDA, SCL, 100000);
bool status1 = bme.begin(0x76, &I2Cone);
if (!status1) {
Serial.println("Could not find a valid BME280_1 sensor, check wiring!");
while (1);
}
}
void getReadings(){
temperature = bme.readTemperature();
humidity = bme.readHumidity();
pressure = bme.readPressure() / 100.0F;
}
void sendReadings() {
LoRaMessage = String(readingID) + "/" + String(temperature) + "&" + String(humidity) + "#" + String(pressure);
//Send LoRa packet to receiver
LoRa.beginPacket();
LoRa.print(LoRaMessage);
LoRa.endPacket();
display.clearDisplay();
display.setCursor(0,0);
display.setTextSize(1);
display.print("LoRa packet sent!");
display.setCursor(0,20);
display.print("Temperature:");
display.setCursor(72,20);
display.print(temperature);
display.setCursor(0,30);
display.print("Humidity:");
display.setCursor(54,30);
display.print(humidity);
display.setCursor(0,40);
display.print("Pressure:");
display.setCursor(54,40);
display.print(pressure);
display.setCursor(0,50);
display.print("Reading ID:");
display.setCursor(66,50);
display.print(readingID);
display.display();
Serial.print("Sending packet: ");
Serial.println(readingID);
readingID++;
}
void setup() {
//initialize Serial Monitor
Serial.begin(115200);
startOLED();
startBME();
startLoRA();
}
void loop() {
getReadings();
sendReadings();
delay(10000);
}
Как работает код
Начните с подключения необходимых библиотек для LoRa, OLED-дисплея и датчика BME280.
//Libraries for LoRa
#include <SPI.h>
#include <LoRa.h>
//Libraries for OLED Display
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
//Libraries for BME280
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
Определите пины, используемые модулем приёмопередатчика LoRa. Мы используем плату TTGO LoRa32 SX1276 OLED V1.0, и это пины, используемые чипом LoRa:
//define the pins used by the LoRa transceiver module
#define SCK 5
#define MISO 19
#define MOSI 27
#define SS 18
#define RST 14
#define DIO0 26
Примечание: если вы используете другую плату LoRa, проверьте пины, используемые чипом приёмопередатчика LoRa.
Выберите частоту LoRa:
#define BAND 866E6
Определите пины OLED.
#define OLED_SDA 4
#define OLED_SCL 15
#define OLED_RST 16
Определите размер OLED.
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
Определите пины, используемые датчиком BME280.
//BME280 definition
#define SDA 21
#define SCL 13
Создайте экземпляр I2C для датчика BME280 и объект bme.
TwoWire I2Cone = TwoWire(1);
Adafruit_BME280 bme;
Создайте несколько переменных для хранения сообщения LoRa, температуры, влажности, давления и ID чтения.
int readingID = 0;
int counter = 0;
String LoRaMessage = "";
float temperature = 0;
float humidity = 0;
float pressure = 0;
Создайте объект display для OLED-дисплея.
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RST);
setup()
В setup() мы вызываем несколько функций, которые были созданы ранее в коде, для инициализации OLED-дисплея, BME280 и модуля приёмопередатчика LoRa.
void setup() {
Serial.begin(115200);
startOLED();
startBME();
startLoRA();
}
loop()
В loop() мы вызываем функции getReadings() и sendReadings(), которые также были созданы ранее. Эти функции отвечают за получение показаний с датчика BME280 и отправку этих показаний по LoRa соответственно.
void loop() {
getReadings();
sendReadings();
delay(10000);
}
getReadings()
Получение показаний датчика так же просто, как использование методов readTemperature(), readHumidity() и readPressure() объекта bme:
void getReadings(){
temperature = bme.readTemperature();
humidity = bme.readHumidity();
pressure = bme.readPressure() / 100.0F;
}
sendReadings()
Для отправки показаний по LoRa мы объединяем все показания в одну переменную LoRaMessage:
void sendReadings() {
LoRaMessage = String(readingID) + "/" + String(temperature) + "&" + String(humidity) + "#" + String(pressure);
Обратите внимание, что каждое показание разделено специальным символом, чтобы приёмник мог легко идентифицировать каждое значение.
Затем отправьте пакет, используя следующее:
LoRa.beginPacket();
LoRa.print(LoRaMessage);
LoRa.endPacket();
Каждый раз, когда мы отправляем пакет LoRa, мы увеличиваем переменную readingID, чтобы иметь представление о том, сколько пакетов было отправлено. Вы можете удалить эту переменную, если хотите.
readingID++;
Цикл loop() повторяется каждые 10000 миллисекунд (10 секунд). Таким образом, новые показания датчика отправляются каждые 10 секунд. Вы можете изменить это время задержки, если хотите.
delay(10000);
Тестирование передатчика LoRa
Загрузите код на вашу плату ESP32 LoRa Sender.
Перейдите в Tools > Port и выберите COM-порт, к которому она подключена. Затем перейдите в Tools > Board и выберите используемую плату. В нашем случае это TTGO LoRa32-OLED V1.
Нажмите кнопку загрузки.
Откройте Serial Monitor на скорости 115200 бод. Вы должны получить что-то похожее на показанное ниже.
OLED вашей платы должен отображать последние показания датчика.
Ваш передатчик LoRa готов. Теперь перейдём к приёмнику LoRa.
Приёмник LoRa
Приёмник LoRa получает входящие пакеты LoRa и отображает полученные показания на асинхронном веб-сервере. Помимо показаний датчиков, мы также отображаем время последнего получения этих показаний и RSSI (индикатор уровня принятого сигнала).
На следующем рисунке показан веб-сервер, который мы создадим.
Как видите, он содержит фоновое изображение и стили, чтобы сделать веб-страницу более привлекательной. Существует несколько способов отображения изображений на веб-сервере ESP32. Мы сохраним изображение в файловой системе ESP32 (LittleFS). Мы также сохраним HTML-файл в LittleFS.
Организация файлов
Для создания веб-сервера вам понадобятся три разных файла: скетч Arduino, HTML-файл и изображение. HTML-файл и изображение должны быть сохранены внутри папки с именем data внутри папки скетча Arduino, как показано ниже.
Создание HTML-файла
Создайте файл index.html со следующим содержимым или скачайте все файлы проекта здесь:
<!DOCTYPE HTML><html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<title>ESP32 (LoRa + Server)</title>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
<style>
body {
margin: 0;
font-family: Arial, Helvetica, sans-serif;
text-align: center;
}
header {
margin: 0;
padding-top: 5vh;
padding-bottom: 5vh;
overflow: hidden;
background-image: url(winter);
background-size: cover;
color: white;
}
h2 {
font-size: 2.0rem;
}
p { font-size: 1.2rem; }
.units { font-size: 1.2rem; }
.readings { font-size: 2.0rem; }
</style>
</head>
<body>
<header>
<h2>ESP32 (LoRa + Server)</h2>
<p><strong>Last received packet:<br/><span id="timestamp">%TIMESTAMP%</span></strong></p>
<p>LoRa RSSI: <span id="rssi">%RSSI%</span></p>
</header>
<main>
<p>
<i class="fas fa-thermometer-half" style="color:#059e8a;"></i> Temperature: <span id="temperature" class="readings">%TEMPERATURE%</span>
<sup>°C</sup>
</p>
<p>
<i class="fas fa-tint" style="color:#00add6;"></i> Humidity: <span id="humidity" class="readings">%HUMIDITY%</span>
<sup>%</sup>
</p>
<p>
<i class="fas fa-angle-double-down" style="color:#e8c14d;"></i> Pressure: <span id="pressure" class="readings">%PRESSURE%</span>
<sup>hpa</sup>
</p>
</main>
<script>
setInterval(updateValues, 10000, "temperature");
setInterval(updateValues, 10000, "humidity");
setInterval(updateValues, 10000, "pressure");
setInterval(updateValues, 10000, "rssi");
setInterval(updateValues, 10000, "timestamp");
function updateValues(value) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById(value).innerHTML = this.responseText;
}
};
xhttp.open("GET", "/" + value, true);
xhttp.send();
}
</script>
</body>
</html>
Мы также включили CSS-стили в HTML-файл, а также немного JavaScript, который отвечает за автоматическое обновление показаний датчиков.
Важно обратить внимание на плейсхолдеры (заполнители). Плейсхолдеры заключены в знаки %: %TIMESTAMP%, %TEMPERATURE%, %HUMIDITY%, %PRESSURE% и %RSSI%.
Эти плейсхолдеры затем будут заменены фактическими значениями кодом Arduino.
Стили добавлены между тегами <style> и </style>.
<style>
body {
margin: 0;
font-family: Arial, Helvetica, sans-serif;
text-align: center;
}
header {
margin: 0;
padding-top: 10vh;
padding-bottom: 5vh;
overflow: hidden;
width: 100%;
background-image: url(winter.jpg);
background-size: cover;
color: white;
}
h2 {
font-size: 2.0rem;
}
p { font-size: 1.2rem; }
.units { font-size: 1.2rem; }
.readings { font-size: 2.0rem; }
</style>
Если вы хотите другое изображение для фона, вам просто нужно изменить следующую строку, чтобы включить имя вашего изображения. В нашем случае оно называется winter.jpg.
background-image: url(winter.jpg);
JavaScript находится между тегами <script> и </script>.
<script>
setInterval(updateValues("temperature"), 5000);
setInterval(updateValues("humidity"), 5000);
setInterval(updateValues("pressure"), 5000);
setInterval(updateValues("rssi"), 5000);
setInterval(updateValues("timeAndDate"), 5000);
function updateValues(value) {
console.log(value);
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById(value).innerHTML = this.responseText;
}
};
xhttp.open("GET", "/" + value, true);
xhttp.send();
}
</script>
Мы не будем подробно объяснять, как работает HTML и CSS, но хорошее место для изучения — сайт W3Schools.
Скетч Arduino для приёмника LoRa
Скопируйте следующий код в вашу Arduino IDE или скачайте все файлы проекта здесь. Затем вам нужно ввести ваши сетевые учётные данные (SSID и пароль), чтобы это работало.
/*********
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp32-lora-sensor-web-server/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*********/
// Import Wi-Fi library
#include <WiFi.h>
#include "ESPAsyncWebServer.h"
#include <LittleFS.h>
//Libraries for LoRa
#include <SPI.h>
#include <LoRa.h>
//Libraries for OLED Display
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// Libraries to get time from NTP Server
#include <NTPClient.h>
#include <WiFiUdp.h>
//define the pins used by the LoRa transceiver module
#define SCK 5
#define MISO 19
#define MOSI 27
#define SS 18
#define RST 14
#define DIO0 26
//433E6 for Asia
//866E6 for Europe
//915E6 for North America
#define BAND 866E6
//OLED pins
#define OLED_SDA 4
#define OLED_SCL 15
#define OLED_RST 16
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// Define NTP Client to get time
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);
// Variables to save date and time
String formattedDate;
String day;
String hour;
String timestamp;
// Initialize variables to get and save LoRa data
int rssi;
String loRaMessage;
String temperature;
String humidity;
String pressure;
String readingID;
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RST);
// Replaces placeholder with DHT values
String processor(const String& var){
//Serial.println(var);
if(var == "TEMPERATURE"){
return temperature;
}
else if(var == "HUMIDITY"){
return humidity;
}
else if(var == "PRESSURE"){
return pressure;
}
else if(var == "TIMESTAMP"){
return timestamp;
}
else if (var == "RRSI"){
return String(rssi);
}
return String();
}
//Initialize OLED display
void startOLED(){
//reset OLED display via software
pinMode(OLED_RST, OUTPUT);
digitalWrite(OLED_RST, LOW);
delay(20);
digitalWrite(OLED_RST, HIGH);
//initialize OLED
Wire.begin(OLED_SDA, OLED_SCL);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3c, false, false)) { // Address 0x3C for 128x32
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(0,0);
display.print("LORA SENDER");
}
//Initialize LoRa module
void startLoRA(){
int counter;
//SPI LoRa pins
SPI.begin(SCK, MISO, MOSI, SS);
//setup LoRa transceiver module
LoRa.setPins(SS, RST, DIO0);
while (!LoRa.begin(BAND) && counter < 10) {
Serial.print(".");
counter++;
delay(500);
}
if (counter == 10) {
// Increment readingID on every new reading
Serial.println("Starting LoRa failed!");
}
Serial.println("LoRa Initialization OK!");
display.setCursor(0,10);
display.clearDisplay();
display.print("LoRa Initializing OK!");
display.display();
delay(2000);
}
void connectWiFi(){
// Connect to Wi-Fi network with SSID and password
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// Print local IP address and start web server
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
display.setCursor(0,20);
display.print("Access web server at: ");
display.setCursor(0,30);
display.print(WiFi.localIP());
display.display();
}
// Read LoRa packet and get the sensor readings
void getLoRaData() {
Serial.print("Lora packet received: ");
// Read packet
while (LoRa.available()) {
String LoRaData = LoRa.readString();
// LoRaData format: readingID/temperature&soilMoisture#batterylevel
// String example: 1/27.43&654#95.34
Serial.print(LoRaData);
// Get readingID, temperature and soil moisture
int pos1 = LoRaData.indexOf('/');
int pos2 = LoRaData.indexOf('&');
int pos3 = LoRaData.indexOf('#');
readingID = LoRaData.substring(0, pos1);
temperature = LoRaData.substring(pos1 +1, pos2);
humidity = LoRaData.substring(pos2+1, pos3);
pressure = LoRaData.substring(pos3+1, LoRaData.length());
}
// Get RSSI
rssi = LoRa.packetRssi();
Serial.print(" with RSSI ");
Serial.println(rssi);
}
// Function to get date and time from NTPClient
void getTimeStamp() {
while(!timeClient.update()) {
timeClient.forceUpdate();
}
// The formattedDate comes with the following format:
// 2018-05-28T16:00:13Z
// We need to extract date and time
formattedDate = timeClient.getFormattedDate();
Serial.println(formattedDate);
// Extract date
int splitT = formattedDate.indexOf("T");
day = formattedDate.substring(0, splitT);
Serial.println(day);
// Extract time
hour = formattedDate.substring(splitT+1, formattedDate.length()-1);
Serial.println(hour);
timestamp = day + " " + hour;
}
void setup() {
// Initialize Serial Monitor
Serial.begin(115200);
startOLED();
startLoRA();
connectWiFi();
if(!LittleFS.begin()){
Serial.println("An Error has occurred while mounting LittleFS");
return;
}
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(LittleFS, "/index.html", String(), false, processor);
});
server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", temperature.c_str());
});
server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", humidity.c_str());
});
server.on("/pressure", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", pressure.c_str());
});
server.on("/timestamp", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", timestamp.c_str());
});
server.on("/rssi", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", String(rssi).c_str());
});
server.on("/winter", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(LittleFS, "/winter.jpg", "image/jpg");
});
// Start server
server.begin();
// Initialize a NTPClient to get time
timeClient.begin();
// Set offset time in seconds to adjust for your timezone, for example:
// GMT +1 = 3600
// GMT +8 = 28800
// GMT -1 = -3600
// GMT 0 = 0
timeClient.setTimeOffset(0);
}
void loop() {
// Check if there are LoRa packets available
int packetSize = LoRa.parsePacket();
if (packetSize) {
getLoRaData();
getTimeStamp();
}
}
Как работает код
Начните с подключения необходимых библиотек. Вам нужны библиотеки для:
построения асинхронного веб-сервера;
доступа к файловой системе ESP32 (LittleFS);
связи с чипом LoRa;
управления OLED-дисплеем;
получения даты и времени с NTP-сервера.
// Import Wi-Fi library
#include <WiFi.h>
#include "ESPAsyncWebServer.h"
#include <LittleFS.h>
//Libraries for LoRa
#include <SPI.h>
#include <LoRa.h>
//Libraries for OLED Display
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// Libraries to get time from NTP Server
#include <NTPClient.h>
#include <WiFiUdp.h>
Определите пины, используемые модулем приёмопередатчика LoRa.
#define SCK 5
#define MISO 19
#define MOSI 27
#define SS 18
#define RST 14
#define DIO0 26
Примечание: если вы используете другую плату LoRa, проверьте пины, используемые чипом приёмопередатчика LoRa.
Определите частоту LoRa:
//433E6 for Asia
//866E6 for Europe
//915E6 for North America
#define BAND 866E6
Настройте пины OLED:
#define OLED_SDA 4
#define OLED_SCL 15
#define OLED_RST 16
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
Введите ваши сетевые учётные данные в следующие переменные, чтобы ESP32 мог подключиться к вашей локальной сети.
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Определите NTP-клиент для получения даты и времени:
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);
Создайте переменные для сохранения даты и времени:
String formattedDate;
String day;
String hour;
String timestamp;
Ещё переменные для хранения показаний датчиков, полученных по радио LoRa.
int rssi;
String loRaMessage;
String temperature;
String humidity;
String pressure;
String readingID;
Создайте объект AsyncWebServer с именем server на порту 80.
AsyncWebServer server(80);
Создайте объект display для OLED-дисплея:
AsyncWebServer server(80);
processor()
Функция processor() – это то, что присваивает значения плейсхолдерам, которые мы создали в HTML-файле.
Она принимает в качестве аргумента плейсхолдер и должна возвращать String, который заменит этот плейсхолдер.
Например, если она находит плейсхолдер TEMPERATURE, она вернёт строковую переменную temperature.
// Replaces placeholder with DHT values
String processor(const String& var){
//Serial.println(var);
if(var == "TEMPERATURE"){
return temperature;
}
else if(var == "HUMIDITY"){
return humidity;
}
else if(var == "PRESSURE"){
return pressure;
}
else if(var == "TIMESTAMP"){
return timestamp;
}
else if (var == "RRSI"){
return String(rssi);
}
return String();
}
setup()
В setup() вы инициализируете OLED-дисплей, связь LoRa и подключаетесь к Wi-Fi.
void setup() {
// Initialize Serial Monitor
Serial.begin(115200);
startOLED();
startLoRA();
connectWiFi();
Вы также инициализируете LittleFS:
if(!LittleFS.begin()){
Serial.println("An Error has occurred while mounting LittleFS");
return;
}
Асинхронный веб-сервер
Библиотека ESPAsyncWebServer позволяет нам настраивать маршруты, по которым сервер будет прослушивать входящие HTTP-запросы.
Например, когда получен запрос по корневому URL, мы отправляем файл index.html, сохранённый в ESP32 LittleFS:
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(LittleFS, "/index.html", String(), false, processor);
});
Как упоминалось ранее, мы добавили немного JavaScript в HTML-файл, который отвечает за обновление веб-страницы каждые 10 секунд. Когда это происходит, он делает запрос по URL /temperature, /humidity, /pressure, /timestamp, /rssi.
Итак, нам нужно обработать, что происходит, когда мы получаем эти запросы. Нам просто нужно отправить переменные temperature, humidity, pressure, timestamp и rssi. Переменные должны быть отправлены в формате char, поэтому мы используем метод .c_str().
server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/plain", temperature.c_str());
});
server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/plain", humidity.c_str());
});
server.on("/pressure", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/plain", pressure.c_str());
});
server.on("/timestamp", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/plain", timestamp.c_str());
});
server.on("/rssi", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/plain", String(rssi).c_str());
});
Поскольку мы включили изображение в веб-страницу, мы получим запрос, «запрашивающий» изображение. Поэтому нам нужно отправить изображение, сохранённое в ESP32 LittleFS.
server.on("/winter", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(LittleFS, "/winter.jpg", "image/jpg");
});
Наконец, запустите веб-сервер.
server.begin();
NTPClient
Ещё в setup() создайте NTP-клиент для получения времени из интернета.
timeClient.begin();
Время возвращается в формате GMT, поэтому, если вам нужно настроить для вашего часового пояса, вы можете использовать следующее:
// Set offset time in seconds to adjust for your timezone, for example:
// GMT +1 = 3600
// GMT +8 = 28800
// GMT -1 = -3600
// GMT 0 = 0
timeClient.setTimeOffset(0);
loop()
В loop() мы прослушиваем входящие пакеты LoRa:
int packetSize = LoRa.parsePacket();
Если доступен новый пакет LoRa, мы вызываем функции getLoRaData() и getTimeStamp().
if (packetSize) {
getLoRaData();
getTimeStamp();
}
Функция getLoRaData() получает сообщение LoRa и разделяет его для получения различных показаний.
Функция getTimeStamp() получает время и дату из интернета в момент получения пакета.
Загрузка кода и файлов
После ввода ваших сетевых учётных данных сохраните скетч. Затем в вашей Arduino IDE перейдите в Sketch > Show Sketch Folder и создайте папку с именем data.
Внутри этой папки должны быть HTML-файл и файл изображения.
Убедившись, что все необходимые файлы находятся в правильных каталогах, вам нужно загрузить файлы в файловую систему ESP32 LittleFS.
Нажмите [Ctrl] + [Shift] + [P] в Windows или [Cmd] + [Shift] + [P] в MacOS, чтобы открыть палитру команд. Найдите команду Upload LittleFS to Pico/ESP8266/ESP32 и нажмите на неё.
Если у вас нет этой опции, значит вы не установили плагин загрузчика файловой системы. Проверьте это руководство.
Важно: убедитесь, что Serial Monitor закрыт перед загрузкой в файловую систему. В противном случае загрузка не удастся.
Через несколько секунд файлы должны быть успешно загружены в LittleFS.
Теперь загрузите скетч на вашу плату.
Откройте Serial Monitor на скорости 115200 бод.
Вы должны получить IP-адрес ESP32, и вы должны начать получать пакеты LoRa от передатчика.
Вы также должны увидеть IP-адрес, отображаемый на OLED.
Демонстрация
Откройте браузер и введите IP-адрес вашего ESP32. Вы должны увидеть веб-сервер с последними показаниями датчиков.
С этими платами нам удалось получить стабильную связь LoRa на расстоянии до 180 метров (590 футов) на открытом поле. Это означает, что мы можем расположить передатчик и приёмник на расстоянии 180 метров друг от друга и всё ещё иметь возможность получать и проверять показания на веб-сервере.
Получение стабильной связи на расстоянии 180 метров с такими дешёвыми платами и без дополнительных настроек действительно впечатляет.
Однако в предыдущем проекте, используя чип приёмопередатчика RFM95 SX1276 LoRa с самодельной антенной, мы получили лучшие результаты: более 250 метров с множеством препятствий между ними.
Дальность связи будет действительно зависеть от вашей среды, используемой платы LoRa и многих других переменных.
Заключение
Вы можете развить этот проект дальше и создать автономную систему мониторинга, добавив солнечные панели и глубокий сон к вашему передатчику LoRa. Следующие статьи могут вам в этом помочь:
Вы также можете захотеть получить доступ к показаниям датчиков из любой точки мира или отобразить их на графике:
Мы надеемся, что этот проект показался вам интересным.
Спасибо за чтение.
—
Источник: Rui Santos & Sara Santos – Random Nerd Tutorials