ESP32:Примеры/Мониторинг данных датчика на большом расстоянии с помощью LoRa

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Черновик


Мониторинг данных датчика на большом расстоянии с помощью LoRa – передача данных о температуре и влажности почвы от наружного датчика

Это очень большой проект, поэтому мы поделили его на 5 частей:

В этом проекте мы создадим автономную мониторинговую систему, отправляющую приемнику, расположенному внутри помещения, информацию о влажности почвы и температуре. Для коммуникации между приемником и передатчиком будет использоваться технология LoRa. Приемник будет показывать данные от датчика на веб-сервере, а также записывать их на карту MicroSD, заодно помечая отметками времени.

В этом проекте будет задействовано несколько концептов, о которых рассказывалось в других руководствах по ESP32 на нашем сайте:

Обзор проекта

Этот проект будет состоять из LoRa-передатчика и LoRa-приемника. LoRa-передатчик, находящийся снаружи, будет отправлять LoRa-приемнику, находящемуся внутри, данные о влажности почвы и температуре. Преимущество технологии LoRa в том, что с ее помощью можно легко установить беспроводное соединение между двумя платами ESP32, находящимися на расстоянии более 100 метров друг от друга, тогда как WiFi и Bluetooth поддерживают передачу данных только на коротких дистанциях.

Кроме того, приемник будет записывать полученные данные на карту MicroSD, а также передавать самые последние данные на веб-сервер. Все это проиллюстрировано на схеме ниже.

LoRa-передатчик

Датчики

Для определения влажности почвы мы воспользуемся резистивным датчиком влажности почвы, показанным на фото ниже.

Для определения температуры мы воспользуемся водонепроницаемой версией температурного датчика DS18B20.

Питание

Для питания цепи мы воспользуемся перезаряжаемой литиевой батареей емкостью 3800 мАч.

Эта батарея будет заряжаться при помощи двух солнечных мини-панелей (5 вольт, 1.2 ватт) и модуля для зарядки литиевых батарей TP4056.

Модуль для зарядки литиевых батарей TP4056 показан на фото ниже.

Все эти устройства позволят нам сделать автономную систему для отправки LoRa-данных. Кроме того, вместе с каждой порцией данных о влажности почвы и температуре мы будем отправлять и информацию об уровне заряда батареи.

Режим глубокого сна

В целях экономии электроэнергии ESP32 будет находиться в режиме глубокого сна. Но каждые 30 минут она будет «просыпаться», считывать нужные данные и отправлять их по LoRa. После этого она вновь будет погружаться в режим глубокого сна.

LoRa-приемник

Запись данных

LoRa-приемник всегда будет прослушивать входящие LoRa-пакеты. Получив корректное сообщение, он сохранит его на карту MicroSD. Для сохранения данных на карту MicroSD мы будем использовать модуль, показанный ниже. Он коммуницирует с ESP32 при помощи протокола SPI.

Отметки времени

Получив корректное сообщение, LoRa-приемник запросит дату и время при помощи протокола NTPnetwork time protocol», т.е. «протокол сетевого времени»). В результате отметка времени будет добавлена в каждое LoRa-сообщение, сохраняемое на карту MicroSD.

Веб-сервер

LoRa-приемник также будет служить веб-сервером. Он будет показывать самые последние данные от датчиков (влажность почвы и температуру), уровень заряда батареи, время считывания этих данных и мощность LoRa-сигнала (RSSI).

Пользуйтесь веб-сервером как можно реже – лишь по мере необходимости.

Важно: После просмотра данных на веб-сервере браузер нужно закрыть, чтобы приемник смог получать новые LoRa-пакеты.

LoRa-передатчик на базе ESP32

Теперь давайте займемся созданием LoRa-передатчика. Он будет считывать с датчиков данные о влажности почвы и температуре, а затем передавать их по беспроводной связи LoRa-приемнику.

Необходимые компоненты

Вот список компонентов, необходимых для этого проекта:

Подготавливаем температурный датчик

Для считывания температуры мы будем использовать водонепроницаемую версию температурного датчика DS18B20 (он показан на фото ниже).

DS18B20 – это цифровой температурный датчик, подключающийся через шину 1-Wire. Это значит, что для его подключения понадобится очень простая цепь.

Подключаем температурный датчик DS18B20

Температурный датчик DS18B20 подключается очень просто (см. схему ниже).

На этой схеме изображена 36-контактная версия платы ESP32 DEVKIT DOIT V1. Если вы используете какую-то другую модель, обязательно сверьтесь с ее распиновкой.


Также в качестве подсказки можете использовать вот эту таблицу:

DS18B20 ESP32
VCC (красный провод) 3.3V
Передача данных (желтый провод) GPIO15 (через резистор на 10 кОм)
GND (черный провод) GND

Устанавливаем библиотеки

Перед загрузкой кода нам нужно установить в IDE Arduino две библиотеки – «OneWire» от Пола Стоффрегена и «Arduino Temperature Control» – необходимые для использования DS18B20.

Устанавливаем библиотеку «OneWire»

  1. Нажмите тут, чтобы скачать ZIP-архив библиотеки. Он должен скачаться в папку «Загрузки»;
  2. Распакуйте этот ZIP-архив. У вас должна получиться папка «OneWire-master»;
  3. Переименуйте эту папку на «OneWire»;
  4. Переместите эту папку в папку «libraries» (туда устанавливаются библиотеки для IDE Arduino), которая находится в папке, куда установлена ваша IDE Arduino;
  5. Наконец, перезапустите IDE Arduino.

Устанавливаем библиотеку «Arduino Temperature Control»

  1. Кликните тут, чтобы скачать ZIP-архив библиотеки. Он должен скачаться в папку «Загрузки»;
  2. Распакуйте этот ZIP-архив. У вас должна получиться папка «Arduino-Temperature-Control-Library-master»;
  3. Переименуйте ее на;
  4. Переместите папку «DallasTemperature» в папку «libraries» (туда устанавливаются библиотеки для IDE Arduino), которая находится в папке, куда установлена ваша IDE Arduino;
  5. Наконец, перезапустите IDE Arduino.

Подготавливаем датчик влажности почвы

Для определения влажности почвы мы воспользуемся резистивным датчиком влажности почвы (см. фото ниже).

Выходное напряжение этого датчика будет меняться в зависимости от влажности почвы. То есть, если почва...

  • Влажная, то выходное напряжение будет стремиться к минимальному значению «0» ( что означает, что влажность почвы составляет 100%);
  • Сухая, то выходное напряжение будет стремиться к максимальному значению «4095» (что означает, что влажность почвы составляет 0%).

По умолчанию разрешение аналоговых контактов ESP32 составляет 12 бит. Это значит, что вы будете получать значения в диапазоне между «0» (100% влажности) и «4095» (0% влажности).

Как повысить срок работы датчика влажности почвы

Дешевые датчики влажности почвы очень быстро окисляются и в итоге приходят в негодность.

Чтобы повысить срок работы датчика влажности почвы, подключать питание к нему нужно только в момент считывания данных. То есть вместо подключения контакта питания датчика к 3.3V мы запитаем его от GPIO-контакта. Таким образом, когда нам нужно будет прочесть данные с датчика, мы запитаем его, подав на GPIO-контакт значение «HIGH». Прочитав данные, мы снова подадим на этот GPIO-контакт значение «LOW».

Подключаем датчик влажности почвы

Подключаем датчик влажности почвы к ESP32 согласно схеме ниже.

На этой схеме изображена 36-контактная версия платы ESP32 DEVKIT DOIT V1. Если вы используете какую-то другую плату, обязательно сверьтесь с ее распиновкой.


Можете также использовать в качестве подсказки таблицу ниже:

Датчик влажности почвы ESP32
VCC GPIO12
GND GND
A0 GPIO26

Подготавливаем трансивер-модуль RFM95

Данные, считанные с датчиков, будут отправляться находящемуся внутри приемнику при помощи технологии LoRa. Мы воспользуемся тем же трансивер-модулем, что и в руководстве «Прием и передача LoRa-данных при помощи ESP32». Соответственно, рекомендуем сначала ознакомиться с этим руководством, где объясняется, как этот модуль подготовить.

Подключаем трансивер-модуль LoRa (RFM95)

Подготовив трансивер-модуль RFM95, подключите его к цепи. Это модуль использует протокол SPI, поэтому нам нужно подключить его к контактам ESP32, которые по умолчанию заданы как SPI-контакты. Подключите трансивер-модуль RFM95 к цепи согласно схеме ниже.

На этой схеме изображена 36-контактная версия платы ESP32 DEVKIT DOIT V1. Если у вас какая-то другая модель, обязательно сверьтесь с ее распиновкой.


Можете использовать в качестве подсказки таблицу ниже:


RFM95 ESP32 RFM95 ESP32
ANA Антенна (не ESP32) GND -
GND GND DIO5 -
DIO3 - RESET GPIO14
DIO4 - NSS GPIO5
3.3V 3.3V SCK GPIO18
DIO0 GPIO2 MOSI GPIO23
DIO1 - MISO GPIO19
DIO2 - GND -

Устанавливаем библиотеку LoRa

Для отправки и получения LoRa-пакетов мы будем использовать библиотеку «arduino-LoRa», разработанную GitHub-пользователем sandeepmistry. Если вы ранее прошли руководство «Прием и передача LoRa-данных при помощи ESP32», то у вас эта библиотека должна быть уже установлена, и вы можете пропустить этот шаг. Если же нет, следуйте инструкциям ниже.

Откройте IDE Arduino и кликните на «Скетч» > «Подключить библиотеку» > «Управлять библиотеками...» (Sketch > Include Library > Manage Libraries), а затем вбейте в поиске «lora». Выберите библиотеку «LoRa» и установите ее.

Перед тем, как сделать систему с LoRa-передатчиком автономной, нам нужно проверить, все ли работает как надо. Позднее (после проверки) мы добавим к ней:

Как работает тот код

Чтобы лучше понимать этот код, рекомендуем сначала ознакомиться с этими руководствами:

Импортируем библиотеки

Во-первых, подключаем библиотеки, необходимые для работы LoRa-модуля:

#include <SPI.h>
#include <LoRa.h>

Во-вторых, подключаем библиотеки для считывания данных с температурного датчика DS18B20:

#include <OneWire.h>
#include <DallasTemperature.h>

Задаем контакты для трансивер-модуля RFM95

В следующих строчках мы задаем контакты, используемые трансивер-модулем RFM95. Если ваша ESP32 оснащена встроенным LoRa-модулем, то контакты, которые он использует, можно узнать в документации или при помощи надписей на плате.

#define ss 5
#define rst 14
#define dio0 2

После этого мы задаем строковую переменную «message», в которую будем сохранять LoRa-сообщение, отправляемое приемнику.

String message;

Сохраняем переменную «readingID» в RTC-память

Чтобы сохранить переменную «readingID» в RTC-память, перед ее объявлением нужно написать «RTC_DATA_ATTR». Сохраняя переменную в RTC-память, мы обеспечиваем, что это значение останется, даже если ESP32 переключится в режим глубокого сна.

RTC_DATA_ATTR int readingID = 0;

При помощи этой переменной мы будем отслеживать то, сколько раз были считаны данные. Благодаря этому мы сможем легко заметить, если передатчику не удастся отправить новые данные или если приемнику не удастся их получить.

Задаем время сна

В следующих строчках мы задаем время глубокого сна. В нашем проекте ESP32 будет просыпаться каждые 30 минут, что соответствует 1800 секундам.

// Задаем настройки для режима глубокого сна:
uint64_t uS_TO_S_FACTOR = 1000000;  // коэффициент перевода
                                    // микросекунд в секунды 
// Сон будет длиться 30 минут (0.5 часов или 1800 секунд):
uint64_t TIME_TO_SLEEP = 1800;

Чтобы поменять время глубокого сна, поменяйте значение в переменной «TIME_TO_SLEEP».

Задаем контакты, объекты и переменные для датчиков

Далее создаём несколько объектов, необходимых для температурного датчика. Он подключен к контакту GPIO15.

// Контакт для передачи данных
// будет подключен к контакту GPIO15 на ESP32:
#define ONE_WIRE_BUS 15
// Создаем объект «oneWire» для коммуникации с OneWire-устройством: 
OneWire oneWire(ONE_WIRE_BUS);
// Создаем объект температурного датчика
// и передаем ему объект «oneWire»:
DallasTemperature sensors(&oneWire);

Затем создаем переменные для контакта данных (GPIO26) и контакта питания (GPIO12) датчика влажности почвы. И создаем переменную «soilMoisture», где будут храниться сами данные о влажности почвы.

const int moisturePin = 26;
const int moisturePowerPin = 12;
int soilMoisture;

Переменные типа «float» (т.е. в них будут храниться значения с плавающей точкой) ниже будут использоваться для хранения температурных данных в градусах Цельсия и Фаренгейта.

float tempC;
float tempF;

Наконец, создаем переменную типа «float», где будет храниться уровень заряда батареи, и задаем контакт, с которого будет считываться эта информация. В нашем случае это GPIO27.

float batteryLevel;
const int batteryPin = 27;

setup()

Поскольку в этом скетче программируется переключение ESP32 в режим глубокого сна, весь код должен находиться в блоке setup(), потому что до блока loop() дело так и не дойдет.

Сначала переключаем контакт «moisturePowerPin» в режим «OUTPUT».

pinMode(moisturePowerPin, OUTPUT);

Далее активируем пробуждение ESP32 по таймеру (каждые 30 минут).

esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);

Запускаем температурный датчик DS18B20.

sensors.begin();

Настраиваем LoRa-модуль

Задаем контакты для трансивер-модуля LoRa.

LoRa.setPins(ss, rst, dio0);

Затем нам нужно инициализировать LoRa-модуль. Вам, возможно, нужно будет поменять в методе LoRa.begin() частоту LoRa-модуля, чтобы она соответствовала вашему региону.

while (!LoRa.begin(866E6) && counter < 10) {

Переменная «counter» нужна для того, чтобы код не застрял в бесконечном цикле попыток подключиться к LoRa-модулю. Мы ограничим количество этих попыток десятью.

while (!LoRa.begin(866E6) && counter < 10) {
 Serial.print(".");
 counter++;
 delay(500);
}

Если LoRa-модуль не удастся инициализировать с 10 попыток, мы увеличиваем на «1» значение в переменной «readingID» и возвращаемся в режим сна.

if (counter == 10) {
// C каждым новым считыванием 
// увеличиваем значение в «readingID» на единицу:
readingID++;
// Запускаем режим глубокого сна:
Serial.println("Failed to initialize LoRa. Going to sleep now");
           //  "Не удалось инициализировать LoRa.
           //   Переход в режим сна."
 esp_deep_sleep_start();
}

Это позволит не расходовать зря заряд батареи в безуспешных попытках инициализировать LoRa-модуль. Кроме того, увеличиваем значение в переменной «readingID», что позволит нам заметить, если LoRa-приемник не получит этот ID.

Задаем синхрослово

Трансивер-модули LoRa прослушивают все пакеты, отправляемые в их диапазоне, и им не важно, откуда приходят эти пакеты. Для того, чтобы приемник получал пакеты только от своего передатчика, нам необходимо задать синхрослово (варьируется в диапазоне от «0-0xFF»).

LoRa.setSyncWord(0xF3);

И приемник, и передатчик должны использовать одно и то же синхрослово. Благодаря этому приемник будет игнорировать все LoRa-пакеты, не содержащие этого синхрослова.

Считывание и отправка данных

Инициализировав LoRa-модуль, считываем данные от датчика.

getReadings();

Затем отправляем их по LoRa.

sendReadings();

Далее увеличиваем на «1» значение в переменной «readingID».

readingID++;

И переключаем ESP32 в режим глубокого сна.

esp_deep_sleep_start();

Функции getReadings() и sendReadings() были созданы для того, чтобы упростить этот код.

Функция getReadings()

Эта функция начинается с подключения питания к датчику влажности почвы путем переключения контакта «moisturePowerPin» в состояние «HIGH».

digitalWrite(moisturePowerPin, HIGH);

Затем запрашиваем температуру в градусах Цельсия и Фаренгейта.

sensors.requestTemperatures();
tempC = sensors.getTempCByIndex(0); // температура в Цельсиях
tempF = sensors.getTempFByIndex(0); // температура в Фаренгейтах

Считываем данные с датчика влажности почвы при помощи функции analogRead(). После этого отключаем питание от датчика, переключая контакт «moisturePowerPin» в состояние «LOW».

soilMoisture = analogRead(moisturePin);
digitalWrite(moisturePowerPin, LOW);

Наконец, считываем уровень заряда батареи с контакта «batteryPin». Эта часть проекта заработает позднее, когда мы подключим к нему цепь для мониторинга уровня заряда батареи. Сейчас же можно просто подключить этот контакт (GPIO27) к GND (и у вас всегда будет 0%).

batteryLevel = map(analogRead(batteryPin), 0.0f, 4095.0f, 0, 100);

Функция sendReadings()

Эта функция объединяет и записывает все считанные данные в одну переменную «message».

message = String(readingID) + "/" + String(tempC) + "&" +
          String(soilMoisture) + "#" + String(batteryLevel);

Обратите внимание, что мы разделяем разные виды считанных данных при помощи специальных символов, чтобы приемник смог без труда отделить их друг от друга.

По умолчанию температура будет отправляться в градусах Цельсия, но если вам нужны градусы Фаренгейта, вы можете закомментировать вот эти две строчки:

message = String(readingID) + "/" + String(tempC) + "&" +
          String(soilMoisture) + "#" + String(batteryLevel);

А затем раскомментировать вот эти две строчки:

//message = String(readingID) + "/" + String(tempF) + "&" +
//          String(soilMoisture) + "#" + String(batteryLevel);

Подготовив строковую переменную «message», отправляем LoRa-пакет.

LoRa.beginPacket();
LoRa.print(message);
LoRa.endPacket();

Вот и все, с кодом разобрались.

Загружаем и тестируем код

В этом коде содержится много команд Serial.print() – в целях выявить неполадки. Впрочем, после того как вы убедитесь, что все работает как надо, рекомендуем удалить или закомментировать все эти строчки – в целях экономии электроэнергии.

Теперь давайте загрузим код на ESP32. Перед этим убедитесь, что в IDE Arduino выбраны правильные плата и COM-порт.

Загрузив код, откройте монитор порта на скорости 115200 бод.

Нажмите на ESP32 на кнопку EN и проверьте, все ли правильно работает. В монитор порта должно прийти примерно такое сообщение.

LoRa-приемник на базе ESP32

Теперь давайте построим LoRa-приемник, а также протестируем коммуникацию между ним и LoRa-передатчиком.

Обзор проекта

Вот функции, которые будет выполнять LoRa-приемник:

  • Он будет получать LoRa-пакеты от LoRa-передатчика;
  • Он будет расшифровывать данные из полученного сообщения и сохранять их на карте MicroSD (с отметками времени);
  • Он также будет служить веб-сервером, показывающим в браузере самые последние данные, считанные от датчиков. Поэтому приемник должен быть подключен к интернету.

Необходимые компоненты

Вот список компонентов, которые понадобятся для этого проекта:

Подготавливаем трансивер-модуль LoRa

Сперва подключаем трансивер-модуль к ESP32 (как и в случае с передатчиком) согласно схеме ниже.

На этой схеме изображена 36-контактная версия платы ESP32 DEVKIT DOIT V1. Если вы используете какую-то другую модель, обязательно сверьтесь с ее распиновкой.


Можете также использовать для подсказки таблицу ниже:

RFM95 ESP32 RFM95 ESP32
ANA Антенна (не ESP32) GND -
GND GND DIO5 -
DIO3 - RESET GPIO14
DIO4 - NSS GPIO5
3.3V 3.3V SCK GPIO18
DIO0 GPIO2 MOSI GPIO23
DIO1 - MISO GPIO19
DIO2 - GND -

Подготавливаем модуль для карты MicroSD

Чтобы сохранять данные на карту MicroSD, нам нужен модуль, который показан на фото ниже.

Есть разные модели этого модуля от разных производителей, но все они работают примерно одинаково. Они коммуницируют через протокол SPI. Для коммуникации с картой MicroSD мы воспользуемся библиотекой «SD», установленной в IDE Arduino по умолчанию.

Форматируем карту MicroSD

Перед использованием карты MicroSD с ESP32 ее надо отформатировать. Для этого выполните следующее:

  • Вставьте карту MicroSD в компьютер. Перейдите в «Этот компьютер» и кликните по иконке карты MicroSD правой кнопкой мыши. В появившемся меню кликните на «Форматировать...»
  • Появится новое окно. Выберите «FAT32», нажмите «Начать» и следуйте подсказкам.

Подключаем модуль для карты MicroSD

Отформатировав карту MicroSD, вставьте ее в модуль. Затем подключите модуль к ESP32 согласно схеме ниже.

На этой схеме изображена 36-контактная версия платы ESP32 DEVKIT DOIT V1. Если вы используете какую-то другую модель, обязательно сверьтесь с ее распиновкой.


Можете также использовать таблицу ниже в качестве подсказки:

Модуль для карты MicroSD ESP32
3V3 3V3
CS GPIO15
MOSI GPIO23
CLK GPIO18
MISO GPIO19
GND GND

Вот и все, цепь для LoRa-приемника готова. Она должна выглядеть примерно вот так:

Считывание даты и времени

Каждый раз, получая новое сообщение, LoRa-приемник будет запрашивать у NTP-сервера дату и время, а затем добавлять эту информацию в логи к данным, считанным с датчиков.

Для этого нам понадобится форк библиотеки «NTPClient», разработанный пользователем taranais. Чтобы установить ее в IDE Arduino, сделайте следующее:

  1. Кликните тут, чтобы скачать ZIP-архив библиотеки. Он должен загрузиться в папку «Загрузки» на ПК;
  2. Распакуйте этот ZIP-архив. У вас должна получиться папка «NTPClient-master»;
  3. Переименуйте ее на «NTPClient»;
  4. Переместите папку «NTPClient» в папку «libraries» (туда устанавливаются библиотеки для IDE Arduino), которая находится внутри папки, куда установлена IDE Ardiuno;
  5. Наконец, перезапустите IDE Arduino.

Как работает код

Импортируем библиотеки

Сначала импортируем библиотеки, необходимые для WiFi и LoRa-модуля:

#include <WiFi.h>
#include <SPI.h>
#include <LoRa.h>

Библиотеки ниже позволяют запрашивать дату и время у NTP-сервера:

#include <NTPClient.h>
#include <WiFiUdp.h>

Эти библиотеки нужны для работы с модулем для карты MicroSD:

#include "FS.h"
#include "SD.h"

Задаем SSID и пароль для WiFi-сети

В следующих двух переменных нужно вписать SSID и пароль для своей локальной WiFi-сети, чтобы ESP32 могла к ней подключиться.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

В двух строчках ниже создаем NTP-клиента, с помощью которого будем запрашивать дату и время у NTP-сервера.

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);

Затем создаем строковые переменные, где будем хранить дату и время.

String formattedDate;
String dayStamp;
String timeStamp;

Далее задаем контакты, используемые трансивер-модулем LoRa. Если ваша ESP32 оснащена встроенным LoRa-модулем, поищите эти контакты в документации или в надписях на плате.

#define ss 5
#define rst 14
#define dio0 2

Затем инициализируем переменные для RSSI и расшифровки данных из LoRa-сообщения.

int rssi;
String loRaMessage;
String temperature;
String soilMoisture;
String batteryLevel;
String readingID;

Делаем CS-контактом карты MicroSD контакт GPIO15.

#define SD_CS 15

Строчки ниже необходимы для создания веб-сервера.

// Создаем объект для веб-сервера и задаем ему номер порта «80»:
WiFiServer server(80);

// Переменная для хранения HTTP-запроса:
String header;

Инициализируем трансивер-модуль LoRa

В блоке setup() инициализируем трансивер-модуль LoRa:

LoRa.setPins(ss, rst, dio0);
while (!LoRa.begin(866E6)) {
 Serial.println(".");
 delay(500);
}

Синхрослово должно быть таким же, как и у LoRa-передатчика, иначе приемник просто не сможет получить отправляемые им сообщения.

LoRa.setSyncWord(0xF3);

Также проверьте, правильная ли (т.е. соответствует ли она вашему региону) частота вписана в параметр метода LoRa.begin().

while (!LoRa.begin(866E6)) {

Подключаемся к WiFi

Во блоке кода ниже мы подключаем ESP32 к WiFi и пишем в мониторе порта ее IP-адрес.

// Подключаемся к WiFi-сети при помощи SSID и пароля:
Serial.print("Connecting to ");
         //  "Подключаемся к " 
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
}
// Печатаем локальный IP-адрес и запускаем веб-сервер:
Serial.println("");
Serial.println("WiFi connected.");  //  "Подключились к WiFi."
Serial.println("IP address: ");  //  "IP-адрес: "
Serial.println(WiFi.localIP());
server.begin();

Инициализируем NTP-клиента

Далее инициализируем NTP-клиента, чтобы узнать у NTP-сервера дату и время.

timeClient.begin();

Далее при помощи метода setTimeOffset() настраиваем часовой пояс.

timeClient.setTimeOffset(3600);

Вот несколько примеров для разных часовых поясов (относительно времени по Гринвичу):

  • GMT +3 = 10800 (московское время);
  • GMT +8 = 28800;
  • GMT -1 = -3600;
  • GMT 0 = 0;

Инициализируем модуль для карты MicroSD

Далее инициализируем карту MicroSD. Оператор if() ниже проверяет, правильно ли подключена SD-карта.

// инициализируем SD-карту:
  SD.begin(SD_CS);  
  if(!SD.begin(SD_CS)) {
    Serial.println("Card Mount Failed");
               //  "Монтирование SD-карты не удалось"
    return;
  }
  uint8_t cardType = SD.cardType();
  if(cardType == CARD_NONE) {
    Serial.println("No SD card attached");
               //  "SD-карта не подключена"
    return;
  }
  Serial.println("Initializing SD card...");
             //  "Инициализация SD-карты..."
  if (!SD.begin(SD_CS)) {
    Serial.println("ERROR - SD card initialization failed!");
               //  "ОШИБКА – инициализация SD-карты не удалась!"
    return;    // инициализация не удалась
  }

Затем пытается открыть файл «data.txt» на карте MicroSD.

File file = SD.open("/data.txt");

Если его не существует, то нам нужно его создать и записать туда заголовки для будущих данных.

writeFile(SD, "/data.txt", "Reading ID, Date, Hour, Temperature, Soil Moisture (0-4095), RSSI, Battery Level(0-100)\r\n");

Если такой файл существует, код продолжает выполняться дальше.

  else {
    Serial.println("File already exists");  
               //  "Файл уже существует"
  }

Наконец, закрываем файл.

file.close();

loop()

В блоке loop() проверяем, пришел ли LoRa-пакет. Если пришел, считываем данные из него, запрашиваем текущие дату и время (чтобы создать отметку времени) и записываем логи на карту MicroSD.

int packetSize = LoRa.parsePacket();
if (packetSize) {
 getLoRaData();
 getTimeStamp();
 logSDCard();
}

В блоке loop() также создаем веб-сервер, показывающий таблицу с самыми последними данными от датчиков и уровень заряда батареи. О том, как создать веб-сервер, читайте в других руководствах по ESP32 на нашем сайте (например, в этом).

Функция getLoRaData()

Эта функция считывает LoRa-пакет и сохраняет сообщение в переменную «LoRaData». Данные будут приходить в таком формате:

readingID/temperature&soilMoisture#batterylevel

Как видите, данные разделены друг от друга разными символами. Фрагмент кода ниже дробит информацию в переменной «LoRaData» на разные виды данных – ID пакета, температуру, влажность почвы и уровень заряда батареи.

int pos1 = LoRaData.indexOf('/');
int pos2 = LoRaData.indexOf('&');
int pos3 = LoRaData.indexOf('#');
readingID = LoRaData.substring(0, pos1);
temperature = LoRaData.substring(pos1 +1, pos2);
soilMoisture = LoRaData.substring(pos2+1, pos3);
batteryLevel = LoRaData.substring(pos3+1, LoRaData.length());

Также считываем RSSI, что будет давать нам примерное понимание об уровне LoRa-сигнала.

rssi = LoRa.packetRssi();

Функция getTimeStamp()

Эта функция считывает дату и время. Строчки ниже проверяют, корректную ли мы получили информацию о дате и времени.

while(!timeClient.update()) {
timeClient.forceUpdate();
}

Иногда NTP-клиент возвращает 1970 год. Чтобы этого не случилось, делаем принудительное обновление.

Преобразовываем дату и время в читаемый формат при помощи функции getFormattedDate().

formattedDate = timeClient.getFormattedDate();

Дата и время возвращаются вот в таком формате:

2018-04-30T16:00:13Z

Следовательно, нам нужно разделить эту строку, чтобы получить отдельно дату и отдельно время. Поэтому делаем следующее:

// Извлекаем дату:
int splitT = formattedDate.indexOf("T");
dayStamp = formattedDate.substring(0, splitT);
Serial.println(dayStamp);
// Извлекаем время:
timeStamp = formattedDate.substring(splitT+1, formattedDate.length()-1);
Serial.println(timeStamp);

Дата сохраняется в переменную «dayStamp», а время – в переменную «timeStamp».

logSDCard()

Функция logSDCard() объединяет всю эту информацию в строковую переменную «loRaMessage». Разные виды данных отделены друг от друга запятыми.

loRaMessage = String(readingID) + "," + String(dayStamp) + ", " + String(timeStamp) + "," + String(temperature) + ", " + String(soilMoisture) + "," + String(rssi) + ", " + String(batteryLevel) + "\r\n";

Затем с помощью строчки ниже записываем всю информацию в файл «data.txt» на карте MicroSD.

appendFile(SD, "/data.txt", loRaMessage.c_str());

Примечание: Функция appendFile() принимает в третьем параметре (для сообщения) только переменную с типом данных «const char». Поэтому мы используем здесь метод c_str(), чтобы преобразовать значение в переменной «loRaMessage» в этот тип данных.

Функции writeFile() и appendFile()

Эти две функции используются для записи и добавления данных на карту MicroSD. Они идут вместе со скетчами-примерами для библиотеки «SD», и менять их нельзя.

Загружаем код

Теперь загружаем этот код на ESP32 (но перед этим нужно убедиться, что в IDE Arduino выставлены правильные плата и COM-порт).

Тестируем LoRa-приемник

Чтобы протестировать LoRa-приемник, откройте монитор порта на скорости 115200 бод. Затем нажмите на ESP32 кнопку EN и скопируйте из монитора порта ее IP-адрес.

Также проверьте, успешно ли инициализировались LoRa-трансивер и модуль для карты MicroSD. Если все работает как надо, давайте проверим, получает ли приемник сообщения от передатчика. Подключите питание к LoRa-передатчику, который мы собрали ранее.

После этого в мониторе порта LoRa-приемника должны появиться новые данные. Чтобы получить несколько новых сообщений, несколько раз нажмите на кнопку EN на ESP32-передатчике.

Самые последние данные можно узнать, получив доступ к веб-серверу. Впишите в адресную строку браузера IP-адрес ESP32 (который ранее скопировали из монитора порта) и нажмите на  ↵ Enter . В браузере должна появиться новая страница, на которой будут последние данные о температуре, влажности почвы, уровне заряда батареи, мощности сигнала и времени, когда они были считаны.

Закройте браузер и оставьте цепи передатчика/приемника включенными на несколько часов, чтобы протестировать, все ли работает правильно.

После этого тестового периода вставьте в компьютер карту MicroSD и откройте файл «data.txt».

На этом файле должно появиться несколько новых сообщений с данными от датчиков.

LoRa-передатчик на базе ESP32 (питание от солнечной энергии)

Теперь давайте подключим к LoRa-передатчику литиевую батарею, две солнечные панели, цепь для стабилизации напряжения и цепь для слежения за уровнем заряда батареи.

Перед тем, как продолжить, у вас уже должна быть готова цепь LoRa-передатчика. Она должна выглядеть следующим образом:

На этой схеме изображена 36-контактная версия платы ESP32 DEVKIT DOIT V1. Если вы используете какую-то другую модель, обязательно сверьтесь с ее распиновкой.


Примечание: Подключать LoRa-передатчик к солнечным панелям нужно только после того, как оба кода (и для приемника, и для передатчика) будут протестированы и вы убедились, что все работает как надо (в частности, что приемник успешно получает LoRa-сообщения).

Необходимые компоненты

Вот компоненты, которые нам понадобятся:

Подготавливаем солнечные панели и литиевую батарею

LoRa-передатчик создан с прицелом на автономность (т.е. независимость от подключения к энергосети). Чтобы запитать LoRa-передатчик от солнечных панелей, нам понадобится полностью заряженная литиевая батарея, батарейный отсек, 2 небольшие солнечные панели (5 вольт и минимум 1.2 ватт) и модуль для зарядки литиевых батарей TP4056.

В модуль для зарядки литиевых батарей TP4056 уже встроена защита, позволяющая обезопасить его от перенапряжения батареи и неправильно подключенных полюсов.

Подключаем солнечные панели параллельно

Чтобы батарея заряжалась быстрее, мы воспользуемся двумя параллельно подключенными солнечными панелями (вместо одной).

Чтобы подключить солнечные панели параллельно, спаяйте друг с другом их плюсовые и минусовые провода.

При параллельном подключении солнечных панелей у вас будет то же выходное напряжение, но сила тока будет в два раза больше (благодаря наличию двух панелей).

Подключаем модуль для зарядки TP4056

Подключаем солнечные панели к модулю для зарядки батарей TP4056 как показано на схеме ниже. Плюсовые провода нужно подключить к контакту IN+, а минусовые – к контакту IN-.

Затем подключите плюсовой порт батарейного отсека к контакту B+, а минусовой – к контакту B-.

Питание ESP32 будет осуществляться при помощи контактов OUT+ и OUT-. Впрочем, при полной зарядке выходное напряжение литиевых батарей будет достигать 4.2 вольта, а рекомендуемым рабочим напряжением для ESP32 являются 3.3 вольта.

Важно: Вы не можете подключить литиевую батарею к ESP32 напрямую. Вам понадобится стабилизатор напряжения.

Стабилизатор напряжения

Использовать обычный линейный стабилизатор для понижения напряжения с 4.2 до 3.3 вольт – не самая хорошая идея, потому что если батарея разрядится, например, до 3.7 вольт, этот стабилизатор просто перестанет работать из-за большого показателя напряжения отсечки.

Так что в нашем случае для стабилизации выходного напряжения понадобится LDO-стабилизатор.

Поискав LDO-стабилизаторы (от англ. «low dropout», что значит «малый перепад напряжения»; это сленговое выражение, означающее, что у таких стабилизаторов относительно мала разница между входным и выходным напряжением), я в итоге остановил свой выбор на MCP1700-3320E.

Но ему есть и хорошая альтернатива – HT7333-A.

Впрочем, подойдет и любой другой LDO-стабилизатор с похожими характеристиками – выходным напряжением (от 1.2 до 5 вольт), ток покоя (1.6 мкА), выходной силой тока (0.25 А) и падением напряжения (178 мВ). Ниже страница с характеристиками из документации к MCP1700.

Ниже показана распиновка MCP1700-3320E. У него есть контакты GND, Vin и Vout.

Для ослабления скачков напряжения к контактам GND и Vout LDO-стабилизатора также должны быть параллельно подключены керамический и электролитический конденсаторы.

Подключите стабилизатор напряжения к цепи согласно схеме ниже.

Внимание: При подключении электролитических конденсаторов нужно учитывать полярность. Провод, рядом с которым нарисована белая полоса, должен быть подключен к GND.

Из контакта Vout стабилизатора напряжения должно исходить 3.3 вольта. Именно от этого контакта будет питаться ESP32.

Измеряем выходное напряжение

Теперь давайте проверим, все ли работает правильно. Начните с измерения выходного напряжения на контактах IN- и IN+. Оно должно составлять примерно 5 вольт – это выходное напряжение солнечных панелей.

Если батареи полностью заряжены, на контактах OUT- и OUT+ должно быть примерно 4.2 вольта.

Эти 4.2 вольта будут подключены к цепи стабилизатора напряжения, благодаря чему на ESP32 будет подаваться напряжение около 3.3 вольт. Отсоединив литиевую батарею, подключите все эти компоненты к ESP32 согласно схеме ниже.

Цепь для слежения за напряжением батареи

Теперь давайте подготовим цепь для слежения за напряжением батареи. Нам понадобится делитель напряжения, потому что максимальное выходное напряжение литиевой батареи при полной зарядке составляет 4.2 вольта, а аналоговые контакты ESP32 способны выдерживать только до 3.3 вольт.

Для создания делителя напряжения нам понадобятся два резистора: на 27 кОм (он будет подключен к стабилизатору напряжения) и на 100 кОм (он будет подключен к GPIO27 на ESP32). Ранее мы уже настроили LoRa-передатчик таким образом, чтобы он мог считывать информацию о заряде батареи с контакта GPIO27. Поэтому нам осталось добавить в цепь делитель напряжения согласно схеме ниже.

Тестируем LoRa-передатчик, подключенный к солнечным панелям

LoRa-передатчик, наконец, готов. Настало время его протестировать.

Возьмите цепь LoRa-передатчика и выйдите наружу, где есть прямой солнечный свет. Подключите литиевую батарею к батарейному отсеку. У ESP32 должен загореться красный светодиод – это будет значить, что она обеспечивается электроэнергией.

Примечание: У модуля TP4056 красный светодиод загорается во время зарядки батареи, а синий – если батарея заряжена полностью.

Кроме того, у LoRa-приемника должен быть открыт монитор порта IDE Arduino. Нажмите на ESP32-передатчике на кнопку EN и проверьте, получает ли LoRa-приемник пакеты от LoRa-передатчика.

Оставьте вашу цепь на несколько часов, чтобы проверить, все ли работает как надо.

Если все в порядке, то поздравляем – проект готов!

Финальные тесты, демонстрация и анализ данных

Итак, на этом этапе у вас должны быть готовы цепи для LoRa-приемника и LoRa-передатчика, и к LoRa-передатчику должны быть подключены солнечные панели.

Необходимые компоненты

Далее нам понадобится только один новый компонент – корпус (IP65/IP67) для LoRa-передатчика.

Подсоединяем корпус

Мы собрали обе цепи на контактных макетных платах, но если вам нужно более постоянное решение, их можно установить на печатные макетные платы.

Цепь LoRa-передатчика будет установлена снаружи, поэтому мы поместим ее в корпус IP65.

Этот тип корпуса помечен как «пыленепроницаемый», а также имеющий защиту от водяных брызг, струй и дождя.

Примечание: Если погода в вашем регионе отличается капризностью, вы можете выбрать более влагостойкий корпус вроде IP67 или IP68. Таблица ниже поможет разобраться, какой корпус подойдет вам лучше всего. «IP» – это аббревиатура для англ. «ingress protection», т.е. «класс защиты от попадания пыли и воды».

Вот так цепь LoRa-передатчика выглядит внутри корпуса.

Для закрепления контактной макетной платы на дне корпуса можно воспользоваться термоклеем. Солнечные панели крепятся снаружи на верхней части корпуса. Подготовив все, выберите, где хотите отслеживать температуру и влажность почвы, и закройте корпус. LoRa-передатчик готов!

LoRa-приемник будет находиться внутри, поэтому ему корпус не нужен.

Диапазон передачи данных

Мы разместили LoRa-передатчик в поле примерно в 130 метрах от LoRa-приемника и начали стабильно получать от него сообщения.

На этом расстоянии мы получали сообщения с RSSI примерно -110 (см. график ниже). Но в месте, где мы проводили свои тесты, хорошего сигнала можно добиться и на расстоянии до 250 метров.

Во время тестов на этом расстоянии приемник получил все отправленные ему LoRa-сообщения.

Энергопотребление

Система питания из солнечных панелей и литиевых батарей способна питать электроэнергией цепь LoRa-передатчика даже в облачные и дождливые дни.

Мы провели несколько тестов и обнаружили, что LoRa-передатчик может работать без света даже более 48 часов. В результате литиевая батарея почти разрядилась, но для того, чтобы зарядить ее до максимума, понадобилось всего около 2 часов на ярком солнце.

Если вы хотите, чтобы батарея заряжалась быстрее, к проекту нужно (параллельно) подключить еще больше солнечных панелей.

Емкость нашей батареи – 3800 мАч. Если ваш проект предусматривает, что система, возможно, будет много дней оставаться без солнца, то вы можете подобрать батарею с более высокой емкостью.

Поскольку наш проект позволяет считывать данные о заряде батареи, мы можем наблюдать за тем, как этот показатель меняется в течение дня.

Ночью батарея разряжается примерно до 92%. В шесть утра, во время восхода, батарея начинает заряжаться. К полудню она заряжается полностью, и так держится вплоть до 18:00. После этого вновь начинается разрядка. Во время тестовых дней уровень заряда батареи никогда не опускался ниже 92%. В те дни была облачная погода со слабыми ливнями.

Тестируем веб-сервер

Итак, когда вам нужно будет посмотреть самые последние данные от датчиков, откройте веб-браузер и введите в нем IP-адрес ESP32. В результате должен открыться веб-сервер на базе ESP32.

Как видите, LoRa-передатчик успешно прочел все данные – влажность почвы, температуру и заряд батареи – и передал их LoRa-приемнику, который вдобавок прочел мощность сигнала и показал все эти данные на этой веб-странице.

Заряд батареи

К цепи LoRa-передатчика также подключена цепь для слежения за напряжением батареи. Но знайте, что данные на аналоговых контактах ESP32 ведут себя нелинейно. Происходит это примерно так:

Это значит, что ESP32 не будет понимать разницу между 3.2 и 3.3 вольтами. Вы будете получать одно и то же значение – «4095», т.е. 100% заряда батареи. Если заряд батареи упадет ниже 75%, ESP32 перестанет работать.

Посмотрев данные, закройте браузер, чтобы LoRa-приемник снова смог получать LoRa-пакеты.

Влажность почвы

Значения влажности почвы будут варьироваться между «0» и «4095», где «0» – это максимальная влажность, а «4095» – это максимальная сухость. Чтобы этот показатель было проще воспринимать, исходные значения можно преобразовать в проценты.

Доступ к данным на карте MicroSD

После того, как система проработает несколько дней, вы можете собрать данные, накопившиеся на карте MicroSD. Вставьте ее в компьютер – на ней должен быть файл «data.txt». Содержимое этого файла можно скопировать, например, в Google Таблицы, а затем отделить эти данные друг от друга при помощи запятых. Для этого выберите столбец, в котором хранятся эти данные, и кликните на «Данные» > «Разделить на колонки...» Рядом со столбцом появится панель с надписью «Разделитель» – выберите в его выпадающем меню пункт «Запятая».

Все эти данные можно использовать для создания графиков. Ниже – данные о температуре за экспериментальный период.

А вот так выглядят результаты по влажности почвы.

Итого

Вот и все, проект готов!

Вот вещи, которым мы научились, работая над этим проектом:

  • Питание ESP32 от солнечных панелей;
  • Питание ESP32 от литиевых батарей;
  • Запись логов на карту MicroSD;
  • Запрос даты и времени у NTP-сервера.

Надеемся, вы нашли этот проект полезным, и он пригодится вам для мониторинга показателей в вашем поле.

Необходимое оборудование

LoRa-передатчик:

LoRa-приемник:

Инструменты, которые пригодятся для сборки этого проекта:

Код

Код для LoRa-передатчика

/*********
  Руи Сантос
  Более подробно о проекте на: http://randomnerdtutorials.com  
*********/

// Библиотеки для LoRa-модуля:
#include <SPI.h>            
#include <LoRa.h>

// Библиотеки для датчика DS18B20:
#include <OneWire.h>
#include <DallasTemperature.h>

// Задаем контакты для LoRa-модуля:
#define ss 5
#define rst 14
#define dio0 2

// Переменная для LoRa-сообщения:
String message;

// Переменная для счетчика, сколько раз считывались данные с датчиков;
// эта переменная будет храниться в RTC-памяти:
RTC_DATA_ATTR int readingID = 0;

// Задаем настройки для режима глубокого сна:
uint64_t uS_TO_S_FACTOR = 1000000;  // коэффициент перевода
                                    // микросекунд в секунды 
// Сон будет длиться 30 минут (0.5 часов или 1800 секунд):
uint64_t TIME_TO_SLEEP = 1800;

// Контакт для передачи данных
// будет подключен к контакту GPIO15 на ESP32:
#define ONE_WIRE_BUS 15
// Создаем объект «oneWire» для коммуникации с OneWire-устройством: 
OneWire oneWire(ONE_WIRE_BUS);
// Создаем объект температурного датчика
// и передаем ему объект «oneWire»:
DallasTemperature sensors(&oneWire);

// Переменные для датчика влажности почвы:
const int moisturePin = 26;
const int moisturePowerPin = 12;
int soilMoisture;

// Переменные для температурного датчика:
float tempC;
float tempF;

// Переменная, где будет храниться уровень заряда батареи,
// и константа для контакта, к которому подключена батарея:
float batteryLevel;
const int batteryPin = 27;

void setup() {
  pinMode(moisturePowerPin, OUTPUT);
  
  // Запускаем последовательную коммуникацию (для отладки):
  Serial.begin(115200);

  // Настраиваем пробуждение из глубокого сна по таймеру:
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);

  // Запускаем температурный датчик:
  sensors.begin();
  
  // Инициализируем LoRa.
  // Замените параметр в функции LoRa.begin(---E-)
  // на частоту, используемую в вашем регионе.
  // Примечание: в LoRa-приемнике должна быть задана та же частота.
  // - 433E6 для Азии
  // - 866E6 для Европы
  // - 915E6 для Северной Америки
  LoRa.setPins(ss, rst, dio0);
  Serial.println("initializing LoRa");  //  "Инициализируем LoRa"
  
  int counter = 0;  
  while (!LoRa.begin(866E6) && counter < 10) {
    Serial.print(".");
    counter++;
    delay(500);
  }
  if (counter == 10) {
    // C каждым новым считыванием 
    // увеличиваем значение в «readingID» на единицу:
    readingID++; 
    // Запускаем режим глубокого сна:
    Serial.println("Failed to initialize LoRa. Going to sleep now");
               //  "Не удалось инициализировать LoRa.
               //   Переход в режим сна."
    esp_deep_sleep_start();
  }
  // Cинхрослово (0xF3) должно быть таким же, как и у приемника.
  // С его помощью обеспечивается,
  // что вы не будете получать сообщения от других LoRa-трансиверов.
  // Диапазон для синхрослова: 0-0xFF.
  LoRa.setSyncWord(0xF3);
  Serial.println("LoRa initializing OK!");
             //  "Инициализация LoRa прошла успешно!"

  getReadings();
  Serial.print("Battery level = ");
           //  "Уровень заряда батареи = "
  Serial.println(batteryLevel, 2);
  Serial.print("Soil moisture = ");
           //  "Влажность почвы = "
  Serial.println(soilMoisture);
  Serial.print("Temperature Celsius = ");
           //  "Температура в градусах Цельсия = "
  Serial.println(tempC);
  Serial.print("Temperature Fahrenheit = ");
           //  "температура в градусах Фаренгейта = "
  Serial.println(tempF);
  Serial.print("Reading ID = ");
           //  "Который раз считываются данные = "
  Serial.println(readingID);
  
  sendReadings();
  Serial.print("Message sent = ");
           //  "Отправленное сообщение = "
  Serial.println(message);
  
  // Увеличиваем значение в «readingID» с каждым новым считыванием:
  readingID++;
  
  // Запускаем режим глубокого сна:
  Serial.println("DONE! Going to sleep now.");
             //  "ГОТОВО! Переходим в режим сна."
  esp_deep_sleep_start(); 
} 

void loop() {
  // ESP32 переключится в режим глубокого сна
  // и никогда не доберется до блока loop().
}

void getReadings() {
  digitalWrite(moisturePowerPin, HIGH);
   
  // Измеряем температуру:
  sensors.requestTemperatures(); 
  tempC = sensors.getTempCByIndex(0); // температура в Цельсиях
  tempF = sensors.getTempFByIndex(0); // температура в Фаренгейтах
   
  // Измеряем влажность почвы:
  soilMoisture = analogRead(moisturePin);
  digitalWrite(moisturePowerPin, LOW);

  // Измеряем уровень заряда батареи:
  batteryLevel = map(analogRead(batteryPin), 0.0f, 4095.0f, 0, 100);
}

// Это функция для отправки пакета данных:
void sendReadings() {
  // Отправляем температуру в Цельсиях, 
  // влажность почвы и уровень заряда:
  message = String(readingID) + "/" + String(tempC) + "&" + 
            String(soilMoisture) + "#" + String(batteryLevel);
  // Раскомментируйте, если хотите
  // отправлять температуру в Фаренгейтах:
  //message = String(readingID) + "/" + String(tempF) + "&" + 
  //          String(soilMoisture) + "#" + String(batteryLevel);
  delay(1000);
  LoRa.beginPacket();
  LoRa.print(message);
  LoRa.endPacket();
}

Код для LoRa-приемника

/*********
  Руи Сантос
  Более подробно о проекте на: http://randomnerdtutorials.com  
*********/

// Импортируем библиотеки:
#include <WiFi.h>
#include <SPI.h>
#include <LoRa.h>

// Библиотеки для считывания времени и даты с NTP-сервера:
#include <NTPClient.h>
#include <WiFiUdp.h>

// Библиотеки для SD-карты:
#include "FS.h"
#include "SD.h"

// Вставьте тут SSID и пароль для своей WiFi-сети:
const char* ssid     = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Создаем объекты, которые понадобятся для считывания даты и времени:
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);

// Переменные, где будут храниться дата и время: 
String formattedDate;
String dayStamp;
String timeStamp;

// Задаем контакты для LoRa-модуля:
#define ss 5
#define rst 14
#define dio0 2

// Создаем переменные для считывания и записи LoRa-данных и RSSI:
int rssi;
String loRaMessage;
String temperature;
String soilMoisture;
String batteryLevel;
String readingID;

// Задаем CS-контакт для модуля карты MicroSD:
#define SD_CS 15

// Создаем объект для веб-сервера и задаем ему номер порта «80»:
WiFiServer server(80);

// Переменная для хранения HTTP-запроса:
String header;

void setup() { 
  // Запускаем последовательную коммуникацию:
  Serial.begin(115200);
  
  // Инициализируем LoRa.
  // Задайте в параметре метода LoRa.begin(---E-) частоту,
  // которая соответствует вашему региону.
  // Примечание: она должна соответствовать частоте LoRa-передатчика.
  // - 433E6 для Азии
  // - 866E6 для Европы
  // - 915E6 для Северной Америки
  LoRa.setPins(ss, rst, dio0);
  while (!LoRa.begin(866E6)) {
    Serial.println(".");
    delay(500);
  }
  // Синхрослово (0xF3) должно быть таким же, как и у передатчика.
  // Благодаря синхрослову приемник не будет получать сообщения,
  // которые отправлены другими LoRa-трансиверами.
  // Диапазон для синхрослова – между «0-0xFF».
  LoRa.setSyncWord(0xF3);           
  Serial.println("LoRa Initializing OK!");
             //  "Инициализация LoRa прошла успешно!"

  // Подключаемся к WiFi-сети при помощи заданных ранее SSID и пароля:
  Serial.print("Connecting to ");
           //  "Подключаемся к "
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // Печатаем локальный IP-адрес и запускаем веб-сервер:
  Serial.println("");
  Serial.println("WiFi connected.");  //  "Подключились к WiFi."
  Serial.println("IP address: ");  //  "IP-адрес: "
  Serial.println(WiFi.localIP());
  server.begin();

  // Инициализируем объект NTP-клиента, чтобы узнать время:
  timeClient.begin();
  // Задаем часовой пояс относительно времени по Гринвичу (GMT).
  // Вот несколько примеров (используются секунды):
  // - GMT +3 = 10800 (московское время)
  // - GMT +8 = 28800
  // - GMT -1 = -3600
  // - GMT 0 = 0
  timeClient.setTimeOffset(3600);
   
  // инициализируем SD-карту:
  SD.begin(SD_CS);  
  if(!SD.begin(SD_CS)) {
    Serial.println("Card Mount Failed");
               //  "Монтирование SD-карты не удалось"
    return;
  }
  uint8_t cardType = SD.cardType();
  if(cardType == CARD_NONE) {
    Serial.println("No SD card attached");
               //  "SD-карта не подключена"
    return;
  }
  Serial.println("Initializing SD card...");
             //  "Инициализация SD-карты..."
  if (!SD.begin(SD_CS)) {
    Serial.println("ERROR - SD card initialization failed!");
               //  "ОШИБКА – инициализация SD-карты не удалась!"
    return;    // инициализация не удалась
  }

  // Если файла «data.txt» не существует,
  // создаем его на SD-карте и записываем туда
  // заголовки для будущих данных (температуры, RSSI и т.д.):
  File file = SD.open("/data.txt");
  if(!file) {
    Serial.println("File doesn't exist");
               //  "Файл не существует"
    Serial.println("Creating file...");
               //  "Создаем файл..."
    writeFile(SD, "/data.txt", "Reading ID, Date, Hour, Temperature, Soil Moisture (0-4095), RSSI, Battery Level(0-100)\r\n");
  }
  else {
    Serial.println("File already exists");  
               //  "Файл уже существует"
  }
  file.close();
}

void loop() {
  // Проверяем, пришел ли LoRa-пакет:
  int packetSize = LoRa.parsePacket();
  if (packetSize) {
    getLoRaData();
    getTimeStamp();
    logSDCard();
  }
  WiFiClient client = server.available();   // Включаем прослушку 
                                            // входящих клиентов 

  if (client) {                             // Если подключился
                                            // новый клиент,
    Serial.println("New Client.");          // пишем в монитор порта 
                                            // сообщение об этом.
    String currentLine = "";                // Создаем строку,
                                            // куда будем сохранять
                                            // данные от клиента.
    while (client.connected()) {            // Цикл while()
                                            // будет работать,
                                            // пока клиент подключен.
      if (client.available()) {             // Если у клиента
                                            // есть байты,
        char c = client.read();             // считываем байт,
        Serial.write(c);                    // и печатаем его
                                            // в монитор порта.
        header += c;
        if (c == '\n') {                    // если этот байт – 
                                            // символ новой строки,
          // и если у нас два символа новой строки подряд,
          // то текущая строка пуста.
          // Это конец HTTP-запроса клиента, т.е. пора слать ответ.
          if (currentLine.length() == 0) {
            // HTTP-заголовки всегда начинаются с кода ответа
            // (например, с «HTTP/1.1 200 OK») и типа контента,
            // чтобы клиент знал, что получает,
            // а затем пишем пустую строку.
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
                       //  "Соединение: отключено"
            client.println();
            
            // Показываем веб-страницу:
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            // При помощи CSS задаем стиль для таблицы: 
            client.println("<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial;}");
            client.println("table { border-collapse: collapse; width:35%; margin-left:auto; margin-right:auto; }");
            client.println("th { padding: 12px; background-color: #0043af; color: white; }");
            client.println("tr { border: 1px solid #ddd; padding: 12px; }");
            client.println("tr:hover { background-color: #bcbcbc; }");
            client.println("td { border: none; padding: 12px; }");
            client.println(".sensor { color:white; font-weight: bold; background-color: #bcbcbc; padding: 1px; }");
            
            // заголовок веб-страницы:
            client.println("</style></head><body><h1>Soil Monitoring with LoRa </h1>");
            client.println("<h3>Last update: " + timeStamp + "</h3>");
            client.println("<table><tr><th>MEASUREMENT</th><th>VALUE</th></tr>");
            client.println("<tr><td>Temperature</td><td><span class=\"sensor\">");
            client.println(temperature);
            client.println(" *C</span></td></tr>");  
            // Раскомментируйте строчку ниже,
            // если хотите переключиться на символ «*F»:
            //client.println(" *F</span></td></tr>");  
            client.println("<tr><td>Soil moisture (0-4095)</td><td><span class=\"sensor\">");
            client.println(soilMoisture);
            client.println("</span></td></tr>"); 
            client.println("<tr><td>Battery level</td><td><span class=\"sensor\">");
            client.println(batteryLevel);
            client.println(" %</span></td></tr>"); 
            client.println("<p>LoRa RSSI: " + String(rssi) + "</p>");
            client.println("</body></html>");
            
            // HTTP-ответ заканчивается еще одной пустой строкой:
            client.println();
            // выходим из цикла while():
            break;
          } else {  // если получили символ новой строки,
                    // то очищаем переменную «currentLine»
            currentLine = "";
          }
        } else if (c != '\r') {  // если получили что-либо,
                                 // кроме символа возврата каретки,
          currentLine += c;      // добавляем это что-то
                                 // в конец переменной «currentLine» 
        }
      }
    }
    // очищаем переменную «header»:
    header = "";
    // отключаем соединение:
    client.stop();
    Serial.println("Client disconnected.");
               //  "Клиент отключился."
    Serial.println("");
  }
}

// Считываем LoRa-пакет с данными от датчиков:
void getLoRaData() {
  Serial.print("Lora packet received: ");
           //  "LoRa-пакет получен: "
  // Считываем пакет:
  while (LoRa.available()) {
    String LoRaData = LoRa.readString();
    // Формат LoRa-данных:
    // «readingID/temperature&soilMoisture#batterylevel»
    // Например: «1/27.43&654#95.34».
    Serial.print(LoRaData); 
    
    // Считываем ID пакета, температуру и влажность почвы:
    int pos1 = LoRaData.indexOf('/');
    int pos2 = LoRaData.indexOf('&');
    int pos3 = LoRaData.indexOf('#');
    readingID = LoRaData.substring(0, pos1);
    temperature = LoRaData.substring(pos1 +1, pos2);
    soilMoisture = LoRaData.substring(pos2+1, pos3);
    batteryLevel = LoRaData.substring(pos3+1, LoRaData.length());    
  }
  // считываем RSSI:
  rssi = LoRa.packetRssi();
  Serial.print(" with RSSI ");  //  " Мощность LoRa-сигнала: " 
  Serial.println(rssi);
}

// Функция для считывания даты и времени из объекта «NTPClient»: 
void getTimeStamp() {
  while(!timeClient.update()) {
    timeClient.forceUpdate();
  }
  // Данные в «formattedDate» имеют формат «2018-05-28T16:00:13Z».
  // Нам нужно извлечь оттуда дату и время:
  formattedDate = timeClient.getFormattedDate();
  Serial.println(formattedDate);

  // Извлекаем дату:
  int splitT = formattedDate.indexOf("T");
  dayStamp = formattedDate.substring(0, splitT);
  Serial.println(dayStamp);
  // Извлекаем время:
  timeStamp = formattedDate.substring(splitT+1, formattedDate.length()-1);
  Serial.println(timeStamp);
}

// Записываем данные от датчиков на SD-карту:
void logSDCard() {
  loRaMessage = String(readingID) + "," + String(dayStamp) + "," + String(timeStamp) + "," + 
                String(temperature) + "," + String(soilMoisture) + "," + String(rssi) + "," + String(batteryLevel) + "\r\n";
  appendFile(SD, "/data.txt", loRaMessage.c_str());
}

// Функция для записи на SD-карту заголовков (НЕ РЕДАКТИРУЙТЕ ЕЕ): 
void writeFile(fs::FS &fs, const char * path, const char * message) {
  Serial.printf("Writing file: %s\n", path);
            //  "Записываем заголовки в файл: "

  File file = fs.open(path, FILE_WRITE);
  if(!file) {
    Serial.println("Failed to open file for writing");
               //  "Не удалось открыть файл для записи заголовков"
    return;
  }
  if(file.print(message)) {
    Serial.println("File written");
               //  "Запись заголовков прошла успешно"
  } else {
    Serial.println("Write failed");
               //  "Запись заголовков не удалась"
  }
  file.close();
}

// Функция для записи на SD-карту
// данных от датчиков (НЕ РЕДАКТИРУЙТЕ ЕЕ):
void appendFile(fs::FS &fs, const char * path, const char * message) {
  Serial.printf("Appending to file: %s\n", path);
            //  "записываем LoRa-сообщение в файл: "

  File file = fs.open(path, FILE_APPEND);
  if(!file) {
    Serial.println("Failed to open file for appending");
               //  "Не удалось открыть файл для записи LoRa-сообщения"
    return;
  }
  if(file.print(message)) {
    Serial.println("Message appended");
               //  "LoRa-сообщение добавлено"
  } else {
    Serial.println("Append failed");
               //  "Запись LoRa-сообщения не удалась"
  }
  file.close();
}

См.также

Внешние ссылки