/*
* https://community.alexgyver.ru/threads/meteostancija-narodnogo-monitoringa.3529/post-72443
*
* Код с поддержкой DS18B20, BMP280, BMP680, SHT31, фоторезистора.
* Отладка проходит на ESP01S с перемычкой в 200 Ом на RST. (у меня заработало при 0 Ом <КЗ>
* Использованы все 4 GPIO. Два на квадратную шину, один на одинокую и один на ламповую аналоговую. И да, знаю что не распаян АЦП.
* Что куда подключать видно из кода. 18В20 использует подтяжку в 12к на плате. Также рекомендую между GND и CH_PD( он же En) поставить кнопку сброса, которая сможет очищать RTC память.
* От Serial пришлось отказаться в пользу отладки на TCP сервере на домашнем ПК из кода тоже понятно.
* О шлюхах плюшках: автоматическое определение, запоминание датчиков и отправка их названий на сервер только при первом включении. Эти параметры хранятся в энергозависимой RTC памяти и обнуляются при отключении питания.
* min Время работы с хорошим сигналом wifi без DS18B20 от 0,23 сек. С DS18B20 9бит от 0,27 сек. 12бит от 0,87сек.
* В Ардуино ИДЄЙЕ - Инструменты - CPU Fraquency выбрать 160Mzh, чуть ниже Flash Fraquency 80 Mzh. Это имеет смысл если не используется 18B20.
* ESPBCFF4D27FF48
* (C) https://community.alexgyver.ru/members/p-a-h-a.3791/
*/
#include <ESP8266WiFi.h>
//Настройки скетча:
#define debug false // Вывод отладочных сообщений в TCP сервер. Замедляет работу. false по умолчанию
#define Host "narodmon.ru" // "narodmon.ru" // "185.245.187.136"//"192.168.0.32"// можно запустить свой сервер с портом 8283 и смотреть во время отладки. Например у меня в линуксе консольная команда #nc (netcat) или в винде https://swiddler.com/ (не отображает кодировку кириллицы)
#define SendData true // Отправлять данные на сервер (для отладки). true по умолчанию
#define postingInterval 330e6 // интервал между отправками данных в секундах (330 сек=5,5 минут)
#define MinVccSleep 4e9 // Время спячки при севших батарейках. 0 - до передергивания питания.
#define Recall 60e6 // время переподключения если нет сети или соединения с хостом
#define VccDiode -177 // Падение напряжения (мВ) на диоде в питании ESP от литиевого АКБ для коректировки показаний напряжения питания. У меня показывает больше чем в реале, поэтому с минусом.
#define maxVCC 3650 // Максимальное напряжение питания. При превышении ЕСП не отключается а садит батарею.
#define minVCC 3000 // Минимальное реальное напряжение питания.
#define swSCL 3 // Пины подключения датчика BMx280/BME680 IO3
#define swSDA 1 // Пины подключения датчика BMx280/BME680
#define OneWireGpio 0 // GPIO к которому подключен DS18B20
#define DS18B20Resolution 9 // точность бит.DS18B20 от 9 до 12. Чем выше разрядность тем медленее работает. 9=0,5`С 10=0,25`С
#define MeasureLightPin 2 // сюда фоторезистор и на + а также сюда конденсатор 2,2 мкФ и на минус.
#define ssid "WiFi_name" //Имя WiFi сети
#define password "WiFi_pass" // Пароль WiFi сети
#define local_IP {192, 168, 1, 59}
#define gateway {192, 168, 1, 1}
#define subnet {255, 255, 255, 0}
#define primaryDNS {8, 8, 8, 8}
#define secondaryDNS {1, 1, 1, 1}
#include <Wire.h>
#define WireClock 400e3 // Разгоняем шину I2C в 4 раза до 400 кГц (только софтварная так может, хардварная 100e3)
#include <Adafruit_BMP280.h> // в менеджере библиотек стандартная + установить библиотеку adafruit unified sensor. В библиотеке Adafruit_BMP280.cpp закомментировать 105 строку delay(100);
Adafruit_BMP280 bmp;
#include <Adafruit_BME680.h>
#define SEALEVELPRESSURE_HPA (1013.25)
Adafruit_BME680 bme; // I2C
#define bmx280AddressI2C 0x76 // Адрес датчика. Может быть 0x77.
#include "Adafruit_SHT31.h"
Adafruit_SHT31 sht31 = Adafruit_SHT31();
bool enableHeater = false;
#include <DallasTemperature.h>
OneWire oneWire(OneWireGpio);
DallasTemperature DS18B20(&oneWire);
uint32_t WaitDS18B20; // время ожидания измерения DS18B20
String DebugBuf; //строка Debug info
void ICACHE_RAM_ATTR InterruptMeasureLightPin();//ставим функцию прерываний InterruptMeasureLightPin() в RAM память для стабильной работы
uint32_t RCtime1; // тут хранится начальное время RC цепи
volatile uint32_t RCtime2; // тут хранится конечное время RC цепи
ADC_MODE(ADC_VCC); // Будем измерять напряжение на VCC внутри МК
int VCC; //Напряжение батареи
uint32_t calculateCRC32(const uint8_t *data, size_t length);
struct { //Структура, хранящаяся в RTC памяти
uint32_t crc32;
uint32_t NumberOfTransmitted; // Номер переданных данных после последней подачи питания
uint32_t SendDataTime; // Время работы последней отправки
bool LightSensor_not_found;
bool bmx_not_found;
bool bme_not_found;
bool sht_not_found;
bool DS18B20_not_found;
DeviceAddress DS18B20Address;
byte DS18B20Count;
} rtcData;
//[I]Расчет CRC32 RTC памяти[/I]/
uint32_t calculateCRC32(const uint8_t *data, size_t length) { // Расчет CRC RTC памяти чтоб понять битые там данные или нет
uint32_t crc = 0xffffffff;
while (length--) {
uint8_t c = *data++;
for (uint32_t i = 0x80; i > 0; i >>= 1) {
bool bit = crc & 0x80000000;
if (c & i) {
bit = !bit;
}
crc <<= 1;
if (bit) {
crc ^= 0x04c11db7;
}
}
}
return crc;
}
//[I]Читаем данные из RTC памяти[/I]/
void RTCread() {
if (ESP.rtcUserMemoryRead(0, (uint32_t*) &rtcData, sizeof(rtcData))) {
uint32_t crcOfData = calculateCRC32((uint8_t*) &rtcData.NumberOfTransmitted, sizeof(rtcData) - sizeof(rtcData.crc32)); // Вычисляем crc32 начиная с адреса хранения переменной rtcData.NumberOfTransmitted до адресса размера в байтах структуры rtcData за исключением первой переменной, хранящей crc32
if (crcOfData != rtcData.crc32) { // При первом включении и при битых данных в RTC памяти
rtcData.NumberOfTransmitted = 1; //количество включений Назначаем данные по умолчанию при первом включении
rtcData.SendDataTime = 0; // время работы
rtcData.LightSensor_not_found = false;
rtcData.bmx_not_found = false;
rtcData.bme_not_found = false;
rtcData.sht_not_found = false;
rtcData.DS18B20Count = 0;
}
}
}
//[I]Обновляем данные в RTC памяти[/I]/
void RTCupdate() {
rtcData.NumberOfTransmitted++; // Увеличиваем количество срабатываний
rtcData.SendDataTime = millis(); // Запоминаем время передачи
rtcData.crc32 = calculateCRC32((uint8_t*) &rtcData.NumberOfTransmitted, sizeof(rtcData) - sizeof(rtcData.crc32));
ESP.rtcUserMemoryWrite(0, (uint32_t*) &rtcData, sizeof(rtcData)); // Write struct to RTC memory
}
//[I]процедура ожидания подключения к Wifi. длится в идеале 129мс[/I]/
void wificonect() {
int i = 0;
if (debug) DebugBuf += String(millis()) + "ms Time not wifi \n"; // 64 мс
while (WiFi.status() != WL_CONNECTED) {
delay(1);
i++; if (i > 200000) {
RTCupdate();
ESP.deepSleep(5e6, RF_NO_CAL); // СПИМ если не было соединения в течении 5 секунд. Если у вас плохой конект с wifi то следует увеличить число до 10000
}
}
if (debug) DebugBuf += String(millis()) + "ms wifi conected!\n\n\n";
}
void setup() {
if (debug) DebugBuf += "\n\nDebug info:\n" + String(millis()) + "ms void setup()\n";
WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS);
RTCread(); // Пока конектится wifi читаем переменные из RTC памяти что сохранили перед прошлым сном
// /*
if (rtcData.NumberOfTransmitted == 1) {
WiFi.begin(ssid, password); // Если уже было подключение хоть раз то ssid и pass хранятся во флеше и берутся от туда раньше чем скетч начнет исполнятся. К этому моменту соединение с wifi установлено, назначаются ip адреса
}
//*/
WiFi.begin(ssid, password);
if (rtcData.NumberOfTransmitted == 1 || !rtcData.bmx_not_found) { // Если датчик BMP280 был обнаружен или первый запуск
Wire.setClock(WireClock); // Разгоняем шину I2C в 4 раза до 400 кГц
Wire.begin(swSDA, swSCL);
}
if (rtcData.NumberOfTransmitted == 1 || !rtcData.bme_not_found) { // Если датчик BME680 был обнаружен или первый запуск
Wire.setClock(WireClock); // Разгоняем шину I2C в 4 раза до 400 кГц
Wire.begin(swSDA, swSCL);
}
if (rtcData.NumberOfTransmitted == 1 || !rtcData.sht_not_found) { // Если датчик BME680 был обнаружен или первый запуск
Wire.setClock(WireClock); // Разгоняем шину I2C в 4 раза до 400 кГц
Wire.begin(swSDA, swSCL);
}
Measure();// Запускаем измерения датчиков
SendToNarodmon();
while (VCC >= maxVCC && (millis() * 1e3 + 20e3) < postingInterval) { // Цикл разряда батарейки выполняется когда превышено напряжение и времени прошло на 20 секунд меньше чем до следующей передачи данных
VCC = ESP.getVcc() + VccDiode;
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
delay(10e3); // ждем 10 сек
}
RTCupdate();
ESP.deepSleep(postingInterval - millis() * 1e3, RF_NO_CAL); // спим до следующей передачи данных
}
void loop() {}
void Measure() { // Запуск измерения датчиков
//[I]-------------VCC-------------------[/I]/
VCC = ESP.getVcc() + VccDiode; // Корректировка напряжения АКБ
if ((VCC - VccDiode) < minVCC) {
RTCupdate();
ESP.deepSleep(MinVccSleep); // спим при низком питании
}
//[I]---------------DS18B20--------------[/I]/
DS18B20.begin(); //ds18b20
DS18B20.setWaitForConversion(false); // Асинхронный режим, т.е. функция requestTemperatures() не ждет 650 мс.
if (rtcData.NumberOfTransmitted == 1) { // При первом включении находим датчики DS18B20 и запоминаем их.
rtcData.DS18B20Count = DS18B20.getDeviceCount(); //поищем.
for (int i = 0; i < rtcData.DS18B20Count; i++) {
if (DS18B20.getAddress(rtcData.DS18B20Address, i)) DS18B20.setResolution(rtcData.DS18B20Address, DS18B20Resolution); // настроим.
}
if (debug) DebugBuf += String(millis()) + " getAddress\n";
}
if (rtcData.DS18B20Count) { // Если DS18B20 подключены то запрашиваем измерения
DS18B20.requestTemperatures(); // Начали измерение ds18b20
WaitDS18B20 = millis();
if (debug) DebugBuf += String(WaitDS18B20) + " DS18B20.requestTemperatures()\n";
}
//[I]-----Запуск измерения освещенности посредством времени RC цепи.--------[/I]/
if (rtcData.NumberOfTransmitted == 1 || !rtcData.LightSensor_not_found) { // Если датчик света обнаружен или это первый запуск
pinMode(MeasureLightPin, OUTPUT); // разряжаем конденсатор
delay(1);
attachInterrupt (digitalPinToInterrupt (MeasureLightPin), InterruptMeasureLightPin, CHANGE); // включаем прерывание которое сработает при заряде конденсатора до логической единицы
pinMode(MeasureLightPin, INPUT); // начало заряда
RCtime1 = micros(); // запоминаем время начала заряда конденсатора
if (digitalRead(MeasureLightPin)) {
rtcData.LightSensor_not_found = true;
detachInterrupt (digitalPinToInterrupt(MeasureLightPin));
}
}
//[I]-------------BMX280----------------[/I]/
if (rtcData.NumberOfTransmitted == 1) { // Если это первый запуск
if (!bmp.begin(bmx280AddressI2C, 0x58)) { // I2C адрес и chip ID. В зависимости от перемычки на плате адресс может быть 77
if (debug) DebugBuf += "bmp.begin failed\n"; // 64 мс
rtcData.bmx_not_found = true;
} else {
//[I]Устанавливаем параметры в соответствии с рекомендациями даташита по минимальному потреблению для Weather monitoring(lowest power). Измерения рекомендовано проводить 1р/минуту[/I]/
bmp.setSampling(Adafruit_BMP280::MODE_FORCED, /*MODE_SLEEP, MODE_FORCED, MODE_NORMAL Operating Mode. */
Adafruit_BMP280::SAMPLING_X1, /* Temp. oversampling 1.2.4.8.16*/
Adafruit_BMP280::SAMPLING_X1, /* Pressure oversampling */
Adafruit_BMP280::FILTER_X16); /*Filtering. FILTER_OFF X2.4.8.16*/
}
} else {
if (!rtcData.bmx_not_found) { // если датчик был обнаружен при первом запуске
if (!bmp.begin(bmx280AddressI2C, 0x58)) {
RTCupdate(); // при сбое датчика перезагружаемся
ESP.deepSleep(20e6, RF_NO_CAL);
}
}
}
//[I]-------------------------------------[/I]/
//[I]-------------BME680----------------[/I]/
if (rtcData.NumberOfTransmitted == 1) { // Если это первый запуск
if (!bme.begin()) {
if (debug) DebugBuf += "bme.begin failed\n"; // 64 мс
rtcData.bme_not_found = true;
} else {
//[I]Устанавливаем параметры в соответствии с рекомендациями даташита по минимальному потреблению для Weather monitoring(lowest power). Измерения рекомендовано проводить 1р/минуту[/I]/
bme.setTemperatureOversampling(BME680_OS_8X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_4X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320, 150); // 320*C for 150 ms*Filtering. FILTER_OFF X2.4.8.16*/
}
} else {
if (!rtcData.bme_not_found) { // если датчик был обнаружен при первом запуске
if (!bme.begin()) {
RTCupdate(); // при сбое датчика перезагружаемся
ESP.deepSleep(20e6, RF_NO_CAL);
}
}
}
//[I]-------------------------------------[/I]/
//[I]-------------SHT31----------------[/I]/
if (rtcData.NumberOfTransmitted == 1) { // Если это первый запуск
if (!sht31.begin(0x44)) {
if (debug) DebugBuf += "sht.begin failed\n"; // 64 мс
rtcData.sht_not_found = true;
} else {
//[I]Устанавливаем параметры [I]//
enableHeater = true;
sht31.heater(enableHeater);
}
} else {
if (!rtcData.sht_not_found) { // если датчик был обнаружен при первом запуске
if (!sht31.begin(0x44)) {
RTCupdate(); // при сбое датчика перезагружаемся
ESP.deepSleep(20e6, RF_NO_CAL);
}
}
}
//[I]-------------------------------------[/I]/
}
void InterruptMeasureLightPin() { // по прерыванию в момент заряда конденсатора запоминаем время
RCtime2 = micros();
}
void SendToNarodmon() { // Собственно формирование пакета и отправка.
String buf = "#ESP" + WiFi.macAddress() + "\n"; // mac адрес для авторизации датчика
buf.replace(":", ""); // удаляем двоеточия из мак адреса
String worcktime;
float WTime;
if (rtcData.NumberOfTransmitted == 1) { // При первом соединении передаем тип, показания и имена датчиков, при последующих только тип и показания
if (!rtcData.bmx_not_found) { // если bmx подключен то выводим с него данные
buf += "#TEMP280#" + String(bmp.readTemperature()) + "#Датчик температуры BMP280\n" + "#PRESS280#" + String(bmp.readPressure() / 133.322) + "#Датчик давления BMP280\n"; //показания температуры и давления датчика BMx280
}
if (!rtcData.bme_not_found) { // если bme подключен то выводим с него данные
buf += "#TEMP680#" + String(bme.readTemperature()) + "#Датчик температуры BME680\n" + "#PRESS680#" + String(bme.readPressure() / 133.322) + "#Датчик давления BME680\n" + "#HUMID680#" + String(bme.readHumidity()) + "#Датчик влажности BME680\n" + "#GAS680#" + String(bme.gas_resistance / 1000.0) + "#Датчик воздуха BME680\n"; //показания температуры и давления
}
if (!rtcData.sht_not_found) { // если sht подключен то выводим с него данные
buf += "#TEMP31#" + String(sht31.readTemperature()) + "#Датчик температуры SHT31\n" + "#HUMID31#" + String(sht31.readHumidity()) + "#Датчик влажности SHT31\n"; //показания температуры и давления
}
//Данные от ESP ( Напряжение питания,уровень wifi
buf += "#VCC#" + String(VCC) + "#Напряжение батареи\n"; // показания температуры
worcktime = String(rtcData.SendDataTime); // Время работы при предыдущей отправке
WTime = worcktime.toInt(); WTime /= 1000;
buf += "#WORKTIME#" + String(WTime) + "#Время передачи данных\n";
buf += "#WM#" + String(rtcData.NumberOfTransmitted) + "#№ показаний\n";
if (rtcData.DS18B20Count) {
WaitDS18B20 = millis() - WaitDS18B20;
if (debug) DebugBuf += "timeDS " + String(WaitDS18B20) + "\n" + "millis=" + String(millis()) + "\n"; //64 мс
int DelayResolutionTime = 750 / (1 << (12 - DS18B20Resolution));
if (DelayResolutionTime > WaitDS18B20)
delay(DelayResolutionTime - WaitDS18B20);
if (debug) DebugBuf += "millis=" + String(millis()) + "\n" + "DelayResolutionTime " + String(DelayResolutionTime) + "\n" + "DelayResolutionTime-WaitDS18B20=" + String(DelayResolutionTime - WaitDS18B20) + "\n";
for (int i = 0; i < rtcData.DS18B20Count; i++) { // перечисляем датчики 18b20 и их показания
DS18B20.getAddress(rtcData.DS18B20Address, i);
buf = buf + "#";
for (uint8_t i = 0; i < 8; i++) {
if (rtcData.DS18B20Address[i] < 16) buf = buf + "0"; // адрес датчика
buf = buf + String(rtcData.DS18B20Address[i], HEX);
}
buf = buf + "#" + String(DS18B20.getTempCByIndex(i)) + "#DS18B20 №" + String(i + 1) + "\n"; // и температура
}
}
} else {
if (!rtcData.bmx_not_found) { // если bmx подключен то выводим с него данные
buf += "#TEMP280#" + String(bmp.readTemperature()) + "\n" + "#PRESS280#" + String(bmp.readPressure() / 133.322) + "\n"; // показания температуры и давления
}
//---BME680---//
if (!rtcData.bme_not_found) { // если bmx подключен то выводим с него данные
unsigned long endTime = bme.beginReading();
if (endTime == 0) {
RTCupdate(); // при сбое датчика перезагружаемся
ESP.deepSleep(20e6, RF_NO_CAL);
return;
}
buf += "#TEMP680#" + String(bme.readTemperature()) + "\n" + "#PRESS680#" + String(bme.readPressure() / 133.322) + "\n" + "#HUMID680#" + String(bme.readHumidity()) + "\n" + "#GAS680#" + String(bme.gas_resistance / 1000.0) + "\n"; //показания температуры и давления
}
if (!rtcData.sht_not_found) { // если sht подключен то выводим с него данные
buf += "#TEMP31#" + String(sht31.readTemperature()) + "\n" + "#HUMID31#" + String(sht31.readHumidity()) + "\n"; //показания температуры и давления
}
//Данные от ESP ( Напряжение питания,уровень wifi
buf += "#VCC#" + String(VCC) + "\n"; //оказания температуры
worcktime = String(rtcData.SendDataTime); // Время работы при предыдущей отправке
WTime = worcktime.toInt(); WTime /= 1000;
buf += "#WORKTIME#" + String(WTime) + "\n";
buf += "#WM#" + String(rtcData.NumberOfTransmitted) + "\n";
if (rtcData.DS18B20Count) {
WaitDS18B20 = millis() - WaitDS18B20;
if (debug) DebugBuf += "timeDS " + String(WaitDS18B20) + "\n" + "millis=" + String(millis()) + "\n"; //64 мс
int DelayResolutionTime = 750 / (1 << (12 - DS18B20Resolution));
if (DelayResolutionTime > WaitDS18B20)
delay(DelayResolutionTime - WaitDS18B20);
if (debug) DebugBuf += "millis=" + String(millis()) + "\n" + "DelayResolutionTime " + String(DelayResolutionTime) + "\n" + "DelayResolutionTime-WaitDS18B20=" + String(DelayResolutionTime - WaitDS18B20) + "\n";
for (int i = 0; i < rtcData.DS18B20Count; i++) { //перечисляем датчики 18b20 и их показания
DS18B20.getAddress(rtcData.DS18B20Address, i);
buf = buf + "#";
for (uint8_t i = 0; i < 8; i++) {
if (rtcData.DS18B20Address[i] < 16) buf = buf + "0"; // адрес датчика
buf = buf + String(rtcData.DS18B20Address[i], HEX);
}
buf = buf + "#" + String(DS18B20.getTempCByIndex(i)) + "\n"; //и температура
}
}
if (debug) DebugBuf += String(millis()) + "DS18B20.getTempCByIndex(i)\n";
}
wificonect(); // ожидание установки соединения с сетью
WiFiClient client;
buf += "#WIFI#" + String(WiFi.RSSI()) + "\n"; // уровень WIFI сигнала
if (SendData) {
if (!client.connect(Host, 8283)) { // подключение
RTCupdate();
ESP.deepSleep(50e6, RF_NO_CAL); // Если не вышло подключится - засыпаем
}
}
if (!rtcData.LightSensor_not_found) { // Если датчик освещенности с конденсатором подключены
if (!RCtime2) { // Если конденсатор не успел зарядится
RCtime2 = micros();
}
// uint32_t Lux = constrain((RCtime2 - RCtime1), 1, 160000);//Double тип переменной
// float Lux = RCtime2 - RCtime1;//Double тип переменной
uint32_t Lux = RCtime2 - RCtime1;
// Lux = sqrt(Lux) * 10;
// Lux = map(Lux, 1, 1000000, 1, 1000000);//попугаи света.
//(1/(E3-150)^1.2)*20000000
// Lux = (Lux-150);
// Lux = pow(Lux, 1.2);
// Lux = (1 / Lux) * 27200000;
buf += "#LUX#" + String(Lux) + "#\n##\n";
} else buf += "##\n";
if (debug)buf += DebugBuf;
if (SendData) client.print(buf); // и отправляем данные кроме освещенности
if (debug) {
}
delay(10); // сделать 100 если нужен ответ или 10 если не нужен . Время активности увеличивается в 2 раза
if (SendData) {
while (client.available()) {
String line = client.readStringUntil('\r');
}
}
}