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

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

Перевод: Максим Кузьмин (Cubewriter) Контакты:</br>* Skype: cubewriter</br>* E-mail: cubewriter@gmail.com</br>* Максим Кузьмин на freelance.ru
Проверка/Оформление/Редактирование: Мякишев Е.А.


Pixel Art Mini Meow Animated.gif Черновик


Содержание

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

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

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

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

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

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

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

Esp32 lora monitorweb server data project scheme.png

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

Esp32 lora monitor sensor long range 1.PNG

Датчики

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

Esp32 lora monitor sensor long range 2.PNG

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

Esp32 lora monitor sensor long range 3.PNG

Питание

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

Esp32 lora monitor sensor long range 4.PNG

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

Esp32 lora monitor sensor long range 5.PNG

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

Esp32 lora monitor sensor long range 6.PNG

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

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

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

LoRa-приемник

Esp32 lora monitor sensor long range 7.PNG

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

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

Esp32 lora monitor sensor long range 8.PNG

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

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

Веб-сервер

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

Esp32 lora monitor sensor long range 9.PNG

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

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

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

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

Esp32 lora monitor sensor long range 10.PNG

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

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

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

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

Esp32 lora monitor sensor long range 11.PNG

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

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

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

Esp32 lora monitor sensor long range 12.PNG
Pixel Art Mini Meow Animated.gif На этой схеме изображена 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.

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

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

Esp32 lora monitor sensor long range 13.PNG

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

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

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

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

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

Esp32 lora monitor sensor long range 14.PNG

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

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

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

Esp32 lora monitor sensor long range 15.PNG
Pixel Art Mini Meow Animated.gif На этой схеме изображена 36-контактная версия платы ESP32 DEVKIT DOIT V1. Если вы используете какую-то другую плату, обязательно сверьтесь с ее распиновкой.


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

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

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

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

Esp32 lora monitor sensor long range 16.PNG

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

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

Esp32 lora monitor sensor long range 17.PNG
Pixel Art Mini Meow Animated.gif На этой схеме изображена 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-модуля:

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

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

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

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

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

1 #define ss 5
2 #define rst 14
3 #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 секундам.

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

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

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

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

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

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

1 const int moisturePin = 26;
2 const int moisturePowerPin = 12;
3 int soilMoisture;

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

1 float tempC;
2 float tempF;

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

1 float batteryLevel;
2 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-модулю. Мы ограничим количество этих попыток десятью.

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

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

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

Это позволит не расходовать зря заряд батареи в безуспешных попытках инициализировать 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);

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

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

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

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

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

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

Функция sendReadings()

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

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

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

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

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

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

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

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

1 LoRa.beginPacket();
2 LoRa.print(message);
3 LoRa.endPacket();

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

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

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

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

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

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

Esp32 lora monitor sensor long range 18.PNG

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

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

Esp32 lora monitor sensor long range 19.PNG

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

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

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

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

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

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

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

Esp32 lora monitor sensor long range 20.PNG
Pixel Art Mini Meow Animated.gif На этой схеме изображена 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, нам нужен модуль, который показан на фото ниже.

Esp32 lora monitor sensor long range 21.PNG

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

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

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

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

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

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

Esp32 lora monitor sensor long range 22.PNG
Pixel Art Mini Meow Animated.gif На этой схеме изображена 36-контактная версия платы ESP32 DEVKIT DOIT V1. Если вы используете какую-то другую модель, обязательно сверьтесь с ее распиновкой.


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

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

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

Esp32 lora monitor sensor long range 23.PNG

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

Каждый раз, получая новое сообщение, 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-модуля:

1 #include <WiFi.h>
2 #include <SPI.h>
3 #include <LoRa.h>

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

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

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

1 #include "FS.h"
2 #include "SD.h"

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

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

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

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

1 WiFiUDP ntpUDP;
2 NTPClient timeClient(ntpUDP);

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

1 String formattedDate;
2 String dayStamp;
3 String timeStamp;

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

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

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

1 int rssi;
2 String loRaMessage;
3 String temperature;
4 String soilMoisture;
5 String batteryLevel;
6 String readingID;

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

#define SD_CS 15

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

1 // Создаем объект для веб-сервера и задаем ему номер порта «80»:
2 WiFiServer server(80);
3 
4 // Переменная для хранения HTTP-запроса:
5 String header;

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

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

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

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

LoRa.setSyncWord(0xF3);

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

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

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

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

 1 // Подключаемся к WiFi-сети при помощи SSID и пароля:
 2 Serial.print("Connecting to ");
 3          //  "Подключаемся к " 
 4 Serial.println(ssid);
 5 WiFi.begin(ssid, password);
 6 while (WiFi.status() != WL_CONNECTED) {
 7   delay(500);
 8   Serial.print(".");
 9 }
10 // Печатаем локальный IP-адрес и запускаем веб-сервер:
11 Serial.println("");
12 Serial.println("WiFi connected.");  //  "Подключились к WiFi."
13 Serial.println("IP address: ");  //  "IP-адрес: "
14 Serial.println(WiFi.localIP());
15 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-карта.

 1 // инициализируем SD-карту:
 2   SD.begin(SD_CS);  
 3   if(!SD.begin(SD_CS)) {
 4     Serial.println("Card Mount Failed");
 5                //  "Монтирование SD-карты не удалось"
 6     return;
 7   }
 8   uint8_t cardType = SD.cardType();
 9   if(cardType == CARD_NONE) {
10     Serial.println("No SD card attached");
11                //  "SD-карта не подключена"
12     return;
13   }
14   Serial.println("Initializing SD card...");
15              //  "Инициализация SD-карты..."
16   if (!SD.begin(SD_CS)) {
17     Serial.println("ERROR - SD card initialization failed!");
18                //  "ОШИБКА – инициализация SD-карты не удалась!"
19     return;    // инициализация не удалась
20   }

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

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

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

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

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

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

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

file.close();

loop()

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

1 int packetSize = LoRa.parsePacket();
2 if (packetSize) {
3  getLoRaData();
4  getTimeStamp();
5  logSDCard();
6 }

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

Функция getLoRaData()

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

readingID/temperature&soilMoisture#batterylevel

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

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

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

rssi = LoRa.packetRssi();

Функция getTimeStamp()

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

1 while(!timeClient.update()) {
2 timeClient.forceUpdate();
3 }

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

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

formattedDate = timeClient.getFormattedDate();

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

2018-04-30T16:00:13Z

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

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

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

logSDCard()

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

1 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-адрес.

Esp32 lora monitor sensor long range 24.PNG

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

Esp32 lora monitor sensor long range 25.PNG

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

Esp32 lora monitor sensor long range 26.PNG

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

Esp32 lora monitor sensor long range 27.PNG

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

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

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

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

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

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

Esp32 lora monitor sensor long range 29.PNG
Pixel Art Mini Meow Animated.gif На этой схеме изображена 36-контактная версия платы ESP32 DEVKIT DOIT V1. Если вы используете какую-то другую модель, обязательно сверьтесь с ее распиновкой.


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

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

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

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

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

Esp32 lora monitor sensor long range 30.PNG

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

Esp32 lora monitor sensor long range 31.PNG

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

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

Esp32 lora monitor sensor long range 32.PNG

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

Soldering of solar panels.png

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

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

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

Esp32 lora monitor sensor long range 33.PNG

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

Battery connecting.png

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

Esp32 lora monitor sensor long range 34.PNG

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

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

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

Esp32 lora monitor sensor long range 35.PNG

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

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

LDO regulator.png

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

Esp32 lora monitor sensor long range 36.PNG

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

Esp32 lora monitor sensor long range 37.PNG

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

Esp32 lora monitor sensor long range 38.PNG

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

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

LDO regulator scheme.png

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

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

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

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

IN- IN+.png

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

OUT- OUT+.png

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

Voltage measuring scheme.png

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

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

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

Battery monitoring scheme.png

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

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

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

Esp32 lora monitor sensor long range 39.PNG

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

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

Esp32 lora monitor sensor long range 40.PNG

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

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

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

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

Esp32 lora monitor sensor long range 41.PNG

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

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

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

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

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

Esp32 lora monitor sensor long range 42.PNG

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

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

Ingress protection.png

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

Esp32 lora monitor sensor long range 43.PNG

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

Esp32 lora monitor sensor long range 44.PNG

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

Esp32 lora monitor sensor long range 45.PNG

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

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

Esp32 lora monitor sensor long range 46.PNG

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

RSSI chart.png

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

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

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

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

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

Esp32 lora monitor sensor long range 47.PNG

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

Esp32 lora monitor sensor long range 48.PNG

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

Battery level chart.png

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

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

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

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

Esp32 lora monitor sensor long range 49.PNG

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

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

Voltage vs ADC chart.png

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

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

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

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

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

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

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

Temperature chart.png

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

Soil moisture chart.png

Итого

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

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

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

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

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

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

LoRa-приемник:

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

Код

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

  1 /*********
  2   Руи Сантос
  3   Более подробно о проекте на: http://randomnerdtutorials.com  
  4 *********/
  5 
  6 // Библиотеки для LoRa-модуля:
  7 #include <SPI.h>            
  8 #include <LoRa.h>
  9 
 10 // Библиотеки для датчика DS18B20:
 11 #include <OneWire.h>
 12 #include <DallasTemperature.h>
 13 
 14 // Задаем контакты для LoRa-модуля:
 15 #define ss 5
 16 #define rst 14
 17 #define dio0 2
 18 
 19 // Переменная для LoRa-сообщения:
 20 String message;
 21 
 22 // Переменная для счетчика, сколько раз считывались данные с датчиков;
 23 // эта переменная будет храниться в RTC-памяти:
 24 RTC_DATA_ATTR int readingID = 0;
 25 
 26 // Задаем настройки для режима глубокого сна:
 27 uint64_t uS_TO_S_FACTOR = 1000000;  // коэффициент перевода
 28                                     // микросекунд в секунды 
 29 // Сон будет длиться 30 минут (0.5 часов или 1800 секунд):
 30 uint64_t TIME_TO_SLEEP = 1800;
 31 
 32 // Контакт для передачи данных
 33 // будет подключен к контакту GPIO15 на ESP32:
 34 #define ONE_WIRE_BUS 15
 35 // Создаем объект «oneWire» для коммуникации с OneWire-устройством: 
 36 OneWire oneWire(ONE_WIRE_BUS);
 37 // Создаем объект температурного датчика
 38 // и передаем ему объект «oneWire»:
 39 DallasTemperature sensors(&oneWire);
 40 
 41 // Переменные для датчика влажности почвы:
 42 const int moisturePin = 26;
 43 const int moisturePowerPin = 12;
 44 int soilMoisture;
 45 
 46 // Переменные для температурного датчика:
 47 float tempC;
 48 float tempF;
 49 
 50 // Переменная, где будет храниться уровень заряда батареи,
 51 // и константа для контакта, к которому подключена батарея:
 52 float batteryLevel;
 53 const int batteryPin = 27;
 54 
 55 void setup() {
 56   pinMode(moisturePowerPin, OUTPUT);
 57   
 58   // Запускаем последовательную коммуникацию (для отладки):
 59   Serial.begin(115200);
 60 
 61   // Настраиваем пробуждение из глубокого сна по таймеру:
 62   esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
 63 
 64   // Запускаем температурный датчик:
 65   sensors.begin();
 66   
 67   // Инициализируем LoRa.
 68   // Замените параметр в функции LoRa.begin(---E-)
 69   // на частоту, используемую в вашем регионе.
 70   // Примечание: в LoRa-приемнике должна быть задана та же частота.
 71   // - 433E6 для Азии
 72   // - 866E6 для Европы
 73   // - 915E6 для Северной Америки
 74   LoRa.setPins(ss, rst, dio0);
 75   Serial.println("initializing LoRa");  //  "Инициализируем LoRa"
 76   
 77   int counter = 0;  
 78   while (!LoRa.begin(866E6) && counter < 10) {
 79     Serial.print(".");
 80     counter++;
 81     delay(500);
 82   }
 83   if (counter == 10) {
 84     // C каждым новым считыванием 
 85     // увеличиваем значение в «readingID» на единицу:
 86     readingID++; 
 87     // Запускаем режим глубокого сна:
 88     Serial.println("Failed to initialize LoRa. Going to sleep now");
 89                //  "Не удалось инициализировать LoRa.
 90                //   Переход в режим сна."
 91     esp_deep_sleep_start();
 92   }
 93   // Cинхрослово (0xF3) должно быть таким же, как и у приемника.
 94   // С его помощью обеспечивается,
 95   // что вы не будете получать сообщения от других LoRa-трансиверов.
 96   // Диапазон для синхрослова: 0-0xFF.
 97   LoRa.setSyncWord(0xF3);
 98   Serial.println("LoRa initializing OK!");
 99              //  "Инициализация LoRa прошла успешно!"
100 
101   getReadings();
102   Serial.print("Battery level = ");
103            //  "Уровень заряда батареи = "
104   Serial.println(batteryLevel, 2);
105   Serial.print("Soil moisture = ");
106            //  "Влажность почвы = "
107   Serial.println(soilMoisture);
108   Serial.print("Temperature Celsius = ");
109            //  "Температура в градусах Цельсия = "
110   Serial.println(tempC);
111   Serial.print("Temperature Fahrenheit = ");
112            //  "температура в градусах Фаренгейта = "
113   Serial.println(tempF);
114   Serial.print("Reading ID = ");
115            //  "Который раз считываются данные = "
116   Serial.println(readingID);
117   
118   sendReadings();
119   Serial.print("Message sent = ");
120            //  "Отправленное сообщение = "
121   Serial.println(message);
122   
123   // Увеличиваем значение в «readingID» с каждым новым считыванием:
124   readingID++;
125   
126   // Запускаем режим глубокого сна:
127   Serial.println("DONE! Going to sleep now.");
128              //  "ГОТОВО! Переходим в режим сна."
129   esp_deep_sleep_start(); 
130 } 
131 
132 void loop() {
133   // ESP32 переключится в режим глубокого сна
134   // и никогда не доберется до блока loop().
135 }
136 
137 void getReadings() {
138   digitalWrite(moisturePowerPin, HIGH);
139    
140   // Измеряем температуру:
141   sensors.requestTemperatures(); 
142   tempC = sensors.getTempCByIndex(0); // температура в Цельсиях
143   tempF = sensors.getTempFByIndex(0); // температура в Фаренгейтах
144    
145   // Измеряем влажность почвы:
146   soilMoisture = analogRead(moisturePin);
147   digitalWrite(moisturePowerPin, LOW);
148 
149   // Измеряем уровень заряда батареи:
150   batteryLevel = map(analogRead(batteryPin), 0.0f, 4095.0f, 0, 100);
151 }
152 
153 // Это функция для отправки пакета данных:
154 void sendReadings() {
155   // Отправляем температуру в Цельсиях, 
156   // влажность почвы и уровень заряда:
157   message = String(readingID) + "/" + String(tempC) + "&" + 
158             String(soilMoisture) + "#" + String(batteryLevel);
159   // Раскомментируйте, если хотите
160   // отправлять температуру в Фаренгейтах:
161   //message = String(readingID) + "/" + String(tempF) + "&" + 
162   //          String(soilMoisture) + "#" + String(batteryLevel);
163   delay(1000);
164   LoRa.beginPacket();
165   LoRa.print(message);
166   LoRa.endPacket();
167 }

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

  1 /*********
  2   Руи Сантос
  3   Более подробно о проекте на: http://randomnerdtutorials.com  
  4 *********/
  5 
  6 // Импортируем библиотеки:
  7 #include <WiFi.h>
  8 #include <SPI.h>
  9 #include <LoRa.h>
 10 
 11 // Библиотеки для считывания времени и даты с NTP-сервера:
 12 #include <NTPClient.h>
 13 #include <WiFiUdp.h>
 14 
 15 // Библиотеки для SD-карты:
 16 #include "FS.h"
 17 #include "SD.h"
 18 
 19 // Вставьте тут SSID и пароль для своей WiFi-сети:
 20 const char* ssid     = "REPLACE_WITH_YOUR_SSID";
 21 const char* password = "REPLACE_WITH_YOUR_PASSWORD";
 22 
 23 // Создаем объекты, которые понадобятся для считывания даты и времени:
 24 WiFiUDP ntpUDP;
 25 NTPClient timeClient(ntpUDP);
 26 
 27 // Переменные, где будут храниться дата и время: 
 28 String formattedDate;
 29 String dayStamp;
 30 String timeStamp;
 31 
 32 // Задаем контакты для LoRa-модуля:
 33 #define ss 5
 34 #define rst 14
 35 #define dio0 2
 36 
 37 // Создаем переменные для считывания и записи LoRa-данных и RSSI:
 38 int rssi;
 39 String loRaMessage;
 40 String temperature;
 41 String soilMoisture;
 42 String batteryLevel;
 43 String readingID;
 44 
 45 // Задаем CS-контакт для модуля карты MicroSD:
 46 #define SD_CS 15
 47 
 48 // Создаем объект для веб-сервера и задаем ему номер порта «80»:
 49 WiFiServer server(80);
 50 
 51 // Переменная для хранения HTTP-запроса:
 52 String header;
 53 
 54 void setup() { 
 55   // Запускаем последовательную коммуникацию:
 56   Serial.begin(115200);
 57   
 58   // Инициализируем LoRa.
 59   // Задайте в параметре метода LoRa.begin(---E-) частоту,
 60   // которая соответствует вашему региону.
 61   // Примечание: она должна соответствовать частоте LoRa-передатчика.
 62   // - 433E6 для Азии
 63   // - 866E6 для Европы
 64   // - 915E6 для Северной Америки
 65   LoRa.setPins(ss, rst, dio0);
 66   while (!LoRa.begin(866E6)) {
 67     Serial.println(".");
 68     delay(500);
 69   }
 70   // Синхрослово (0xF3) должно быть таким же, как и у передатчика.
 71   // Благодаря синхрослову приемник не будет получать сообщения,
 72   // которые отправлены другими LoRa-трансиверами.
 73   // Диапазон для синхрослова – между «0-0xFF».
 74   LoRa.setSyncWord(0xF3);           
 75   Serial.println("LoRa Initializing OK!");
 76              //  "Инициализация LoRa прошла успешно!"
 77 
 78   // Подключаемся к WiFi-сети при помощи заданных ранее SSID и пароля:
 79   Serial.print("Connecting to ");
 80            //  "Подключаемся к "
 81   Serial.println(ssid);
 82   WiFi.begin(ssid, password);
 83   while (WiFi.status() != WL_CONNECTED) {
 84     delay(500);
 85     Serial.print(".");
 86   }
 87   // Печатаем локальный IP-адрес и запускаем веб-сервер:
 88   Serial.println("");
 89   Serial.println("WiFi connected.");  //  "Подключились к WiFi."
 90   Serial.println("IP address: ");  //  "IP-адрес: "
 91   Serial.println(WiFi.localIP());
 92   server.begin();
 93 
 94   // Инициализируем объект NTP-клиента, чтобы узнать время:
 95   timeClient.begin();
 96   // Задаем часовой пояс относительно времени по Гринвичу (GMT).
 97   // Вот несколько примеров (используются секунды):
 98   // - GMT +3 = 10800 (московское время)
 99   // - GMT +8 = 28800
100   // - GMT -1 = -3600
101   // - GMT 0 = 0
102   timeClient.setTimeOffset(3600);
103    
104   // инициализируем SD-карту:
105   SD.begin(SD_CS);  
106   if(!SD.begin(SD_CS)) {
107     Serial.println("Card Mount Failed");
108                //  "Монтирование SD-карты не удалось"
109     return;
110   }
111   uint8_t cardType = SD.cardType();
112   if(cardType == CARD_NONE) {
113     Serial.println("No SD card attached");
114                //  "SD-карта не подключена"
115     return;
116   }
117   Serial.println("Initializing SD card...");
118              //  "Инициализация SD-карты..."
119   if (!SD.begin(SD_CS)) {
120     Serial.println("ERROR - SD card initialization failed!");
121                //  "ОШИБКА – инициализация SD-карты не удалась!"
122     return;    // инициализация не удалась
123   }
124 
125   // Если файла «data.txt» не существует,
126   // создаем его на SD-карте и записываем туда
127   // заголовки для будущих данных (температуры, RSSI и т.д.):
128   File file = SD.open("/data.txt");
129   if(!file) {
130     Serial.println("File doesn't exist");
131                //  "Файл не существует"
132     Serial.println("Creating file...");
133                //  "Создаем файл..."
134     writeFile(SD, "/data.txt", "Reading ID, Date, Hour, Temperature, Soil Moisture (0-4095), RSSI, Battery Level(0-100)\r\n");
135   }
136   else {
137     Serial.println("File already exists");  
138                //  "Файл уже существует"
139   }
140   file.close();
141 }
142 
143 void loop() {
144   // Проверяем, пришел ли LoRa-пакет:
145   int packetSize = LoRa.parsePacket();
146   if (packetSize) {
147     getLoRaData();
148     getTimeStamp();
149     logSDCard();
150   }
151   WiFiClient client = server.available();   // Включаем прослушку 
152                                             // входящих клиентов 
153 
154   if (client) {                             // Если подключился
155                                             // новый клиент,
156     Serial.println("New Client.");          // пишем в монитор порта 
157                                             // сообщение об этом.
158     String currentLine = "";                // Создаем строку,
159                                             // куда будем сохранять
160                                             // данные от клиента.
161     while (client.connected()) {            // Цикл while()
162                                             // будет работать,
163                                             // пока клиент подключен.
164       if (client.available()) {             // Если у клиента
165                                             // есть байты,
166         char c = client.read();             // считываем байт,
167         Serial.write(c);                    // и печатаем его
168                                             // в монитор порта.
169         header += c;
170         if (c == '\n') {                    // если этот байт – 
171                                             // символ новой строки,
172           // и если у нас два символа новой строки подряд,
173           // то текущая строка пуста.
174           // Это конец HTTP-запроса клиента, т.е. пора слать ответ.
175           if (currentLine.length() == 0) {
176             // HTTP-заголовки всегда начинаются с кода ответа
177             // (например, с «HTTP/1.1 200 OK») и типа контента,
178             // чтобы клиент знал, что получает,
179             // а затем пишем пустую строку.
180             client.println("HTTP/1.1 200 OK");
181             client.println("Content-type:text/html");
182             client.println("Connection: close");
183                        //  "Соединение: отключено"
184             client.println();
185             
186             // Показываем веб-страницу:
187             client.println("<!DOCTYPE html><html>");
188             client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
189             client.println("<link rel=\"icon\" href=\"data:,\">");
190             // При помощи CSS задаем стиль для таблицы: 
191             client.println("<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial;}");
192             client.println("table { border-collapse: collapse; width:35%; margin-left:auto; margin-right:auto; }");
193             client.println("th { padding: 12px; background-color: #0043af; color: white; }");
194             client.println("tr { border: 1px solid #ddd; padding: 12px; }");
195             client.println("tr:hover { background-color: #bcbcbc; }");
196             client.println("td { border: none; padding: 12px; }");
197             client.println(".sensor { color:white; font-weight: bold; background-color: #bcbcbc; padding: 1px; }");
198             
199             // заголовок веб-страницы:
200             client.println("</style></head><body><h1>Soil Monitoring with LoRa </h1>");
201             client.println("<h3>Last update: " + timeStamp + "</h3>");
202             client.println("<table><tr><th>MEASUREMENT</th><th>VALUE</th></tr>");
203             client.println("<tr><td>Temperature</td><td><span class=\"sensor\">");
204             client.println(temperature);
205             client.println(" *C</span></td></tr>");  
206             // Раскомментируйте строчку ниже,
207             // если хотите переключиться на символ «*F»:
208             //client.println(" *F</span></td></tr>");  
209             client.println("<tr><td>Soil moisture (0-4095)</td><td><span class=\"sensor\">");
210             client.println(soilMoisture);
211             client.println("</span></td></tr>"); 
212             client.println("<tr><td>Battery level</td><td><span class=\"sensor\">");
213             client.println(batteryLevel);
214             client.println(" %</span></td></tr>"); 
215             client.println("<p>LoRa RSSI: " + String(rssi) + "</p>");
216             client.println("</body></html>");
217             
218             // HTTP-ответ заканчивается еще одной пустой строкой:
219             client.println();
220             // выходим из цикла while():
221             break;
222           } else {  // если получили символ новой строки,
223                     // то очищаем переменную «currentLine»
224             currentLine = "";
225           }
226         } else if (c != '\r') {  // если получили что-либо,
227                                  // кроме символа возврата каретки,
228           currentLine += c;      // добавляем это что-то
229                                  // в конец переменной «currentLine» 
230         }
231       }
232     }
233     // очищаем переменную «header»:
234     header = "";
235     // отключаем соединение:
236     client.stop();
237     Serial.println("Client disconnected.");
238                //  "Клиент отключился."
239     Serial.println("");
240   }
241 }
242 
243 // Считываем LoRa-пакет с данными от датчиков:
244 void getLoRaData() {
245   Serial.print("Lora packet received: ");
246            //  "LoRa-пакет получен: "
247   // Считываем пакет:
248   while (LoRa.available()) {
249     String LoRaData = LoRa.readString();
250     // Формат LoRa-данных:
251     // «readingID/temperature&soilMoisture#batterylevel»
252     // Например: «1/27.43&654#95.34».
253     Serial.print(LoRaData); 
254     
255     // Считываем ID пакета, температуру и влажность почвы:
256     int pos1 = LoRaData.indexOf('/');
257     int pos2 = LoRaData.indexOf('&');
258     int pos3 = LoRaData.indexOf('#');
259     readingID = LoRaData.substring(0, pos1);
260     temperature = LoRaData.substring(pos1 +1, pos2);
261     soilMoisture = LoRaData.substring(pos2+1, pos3);
262     batteryLevel = LoRaData.substring(pos3+1, LoRaData.length());    
263   }
264   // считываем RSSI:
265   rssi = LoRa.packetRssi();
266   Serial.print(" with RSSI ");  //  " Мощность LoRa-сигнала: " 
267   Serial.println(rssi);
268 }
269 
270 // Функция для считывания даты и времени из объекта «NTPClient»: 
271 void getTimeStamp() {
272   while(!timeClient.update()) {
273     timeClient.forceUpdate();
274   }
275   // Данные в «formattedDate» имеют формат «2018-05-28T16:00:13Z».
276   // Нам нужно извлечь оттуда дату и время:
277   formattedDate = timeClient.getFormattedDate();
278   Serial.println(formattedDate);
279 
280   // Извлекаем дату:
281   int splitT = formattedDate.indexOf("T");
282   dayStamp = formattedDate.substring(0, splitT);
283   Serial.println(dayStamp);
284   // Извлекаем время:
285   timeStamp = formattedDate.substring(splitT+1, formattedDate.length()-1);
286   Serial.println(timeStamp);
287 }
288 
289 // Записываем данные от датчиков на SD-карту:
290 void logSDCard() {
291   loRaMessage = String(readingID) + "," + String(dayStamp) + "," + String(timeStamp) + "," + 
292                 String(temperature) + "," + String(soilMoisture) + "," + String(rssi) + "," + String(batteryLevel) + "\r\n";
293   appendFile(SD, "/data.txt", loRaMessage.c_str());
294 }
295 
296 // Функция для записи на SD-карту заголовков (НЕ РЕДАКТИРУЙТЕ ЕЕ): 
297 void writeFile(fs::FS &fs, const char * path, const char * message) {
298   Serial.printf("Writing file: %s\n", path);
299             //  "Записываем заголовки в файл: "
300 
301   File file = fs.open(path, FILE_WRITE);
302   if(!file) {
303     Serial.println("Failed to open file for writing");
304                //  "Не удалось открыть файл для записи заголовков"
305     return;
306   }
307   if(file.print(message)) {
308     Serial.println("File written");
309                //  "Запись заголовков прошла успешно"
310   } else {
311     Serial.println("Write failed");
312                //  "Запись заголовков не удалась"
313   }
314   file.close();
315 }
316 
317 // Функция для записи на SD-карту
318 // данных от датчиков (НЕ РЕДАКТИРУЙТЕ ЕЕ):
319 void appendFile(fs::FS &fs, const char * path, const char * message) {
320   Serial.printf("Appending to file: %s\n", path);
321             //  "записываем LoRa-сообщение в файл: "
322 
323   File file = fs.open(path, FILE_APPEND);
324   if(!file) {
325     Serial.println("Failed to open file for appending");
326                //  "Не удалось открыть файл для записи LoRa-сообщения"
327     return;
328   }
329   if(file.print(message)) {
330     Serial.println("Message appended");
331                //  "LoRa-сообщение добавлено"
332   } else {
333     Serial.println("Append failed");
334                //  "Запись LoRa-сообщения не удалась"
335   }
336   file.close();
337 }

См.также

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