M5Atom_airqa/M5Atom_airqa.ino

728 lines
22 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
Питание 3,3 В
Логические уровни 3,3 В
Точность измерения влажности ± 2%
Точность измерения температуры ± 0,2 °C
Разрешающая способность показаний датчика HDC1080 14 бит
Диапазон измерения концентрации CO2: 400 ... 8192 ppm
Диапазон измерения концентрации летучих органических веществ 0 ... 1187 ppb
*/
#include "esp_sleep.h"
#include "driver/rtc_io.h"
#include <WiFiManager.h> // https://github.com/tzapu/WiFiManager
#include <Preferences.h> //Сохранение настроек хеша прошивки
#include <HTTPClient.h>
#include <ESP32httpUpdate.h> //Библиотека ОТА обновлений
#include "M5Atom.h" //Библиотека атома для функции Led и Кнопки, можно упразднить и убрать
#include <WiFiUdp.h> //Udp клиент
#include <NTPClient.h> //NTP запрос времени
#include <TimeLib.h> //Внутреннее время
#include <Wire.h> //Библиотека дял I2C
#include "ClosedCube_HDC1080.h" //Температура влажность
#include "Adafruit_CCS811.h" //eco2 Tvoc
//#include "SparkFunCCS811.h"
#include <PubSubClient.h> //Mtqq
#include <ArduinoJson.h> //Упакова в JSon - удобная библиотека
#include <ESPmDNS.h>
//Наша кнопочка при нажатии на которую произойдет вызов wifi менеджера и перезагрузка в станцию
#define TRIGGER_PIN 39
#define uS_TO_S_FACTOR 1000000
#define TIME_TO_SLEEP 30
#define GPIO_WAK 23
#define CCS811_ADDR 0x5B //Default I2C Address
//#define CCS811_ADDR 0x5A //Alternate I2C Address
unsigned int VersionSW = 225; //65536 Версия прошивки
//15 - добавлено то, се, забыл вообще дописать что добавленоSerial
//19 - вывод в консоль всех действий, ошибки с обновлнеием - починил, прияногое мигание светодиодом, тест для поиска metrics.
//21 - убран мак из вывода в топике
//22 - поправлено поиск сервера по metrics local.
//23 - добавленн BSID
//24 - полный передел всего
gpio_num_t pinWak = GPIO_NUM_23;
WiFiManager wm; // обьект менеджера
WiFiManagerParameter custom_field;
Preferences OTApreferences; //Обьект хранения настроек хеша прошивки
Adafruit_CCS811 ccs;
ClosedCube_HDC1080 hdc1080;
StaticJsonDocument<200> doc, other;
WiFiClient espClient;
PubSubClient MqttClient(espClient);
IPAddress IpMqtt;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "0.ua.pool.ntp.org", 7200, 60000); //Собственно сервер времени смещение и частоат запроса, но он вручную
const PROGMEM char *willmess = "{\"conn\":\"err\"}";
const char *mqttHostName = "cctv.automation.art"; //Хостнейм брокера metrics.local cctv.automation.art //192.168.89.210
unsigned int mqttPort = 8889; //Порт брокера 1883 8889
String getMacAddress();
String macc = getMacAddress();
String topicTemp = "aastudio/" + macc + "/data/0";
String willTopicTemp = "aastudio/" + macc + "/status";
const PROGMEM char *mqttLogin = "login", *mqttPass = "password";
char bufTopic[140];
char bufWillTopic[150];
const char *mqttIPHost;
RTC_DATA_ATTR unsigned long timingUpdate, timingSendMqtt; //Таймеры для millis()
int PROGMEM nextM5Update = 450000; //каждые 7.5 минут запрос обновления с сервера
int PROGMEM nextReqSensor = 10000; //опрос датчиков раз в 10 секунд
int PROGMEM nextMqttSend = 60000; //Отправка
//Поправочные коефициенты для датчиков
//[24A160474D14, 5002919F5450, 5002918A38CC]
//tcoeff[0,1,2]
//hcoeff[0,1,2]
float PROGMEM tcoeff[3] = {-11.18, -9.62, -10.6}; //Температура
float PROGMEM hcoeff[3] = {13.77, 14.07, 15.56}; //Влажность
float callibrationT = 0, callibrationH = 0;
float TempAv, HumAv, Eco2Av, TvocAv;
byte errorID = 0;
long rssi = 0;
#define NUM_AVER 5
float averageT, averageH, averageECO, averageTVOC; // перем. среднего
float valArrayT[NUM_AVER], valArrayH[NUM_AVER], valArrayECO[NUM_AVER], valArrayTVOC[NUM_AVER]; // массив
byte idxT = 0, idxH = 0, idxECO = 0, idxTVOC = 0;
bool mqttSendFlag = false;
int reqCounter = 0;
bool flagblink = true;
//Настройки
void setup()
{
btStop();
// esp_bt_controller_disable();
// adc_power_on();
M5.begin(true, false, true);
Wire.begin(25, 21); //Пины для I2c на ATOM
// pinMode(GPIO_WAK, OUTPUT); //Пин датчика для работы
// digitalWrite(GPIO_WAK, LOW);
// gpio_hold_en(pinWak);
// gpio_deep_sleep_hold_en();
// rtc_gpio_set_level (pin_MOSFET, 1); // GPIO ВЫСОКИЙ
// esp_sleep_pd_config (ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// esp_sleep_enable_timer_wakeup (60 * 1000 * 1000);
// esp_deep_sleep_start ();
pinMode(TRIGGER_PIN, INPUT);
hdc1080.begin(0x40); //14 бит температура и влажность
hdc1080.setResolution(HDC1080_RESOLUTION_11BIT, HDC1080_RESOLUTION_11BIT);
while (!ccs.begin() && !ccs.available()) //The device's I2C address is 0x5A
{
Serial.println("Failed BEGIN And Failed available ");
}
//ccs.setTempOffset(8.6);
//Установка коеффициентов каллибровки по MAC адресу
// SetCallibrationCoeff();
delay(15);
WiFi.mode(WIFI_STA);
Serial.begin(115200);
Serial.setDebugOutput(true);
topicTemp.toCharArray(bufTopic, topicTemp.length() + 1);
willTopicTemp.toCharArray(bufWillTopic, willTopicTemp.length() + 1);
Serial.println(bufTopic);
Serial.println(bufWillTopic);
// wm.resetSettings(); // wipe settings
// const char *custom_radio_str = "<br/><label for='customfieldid'>Custom Field Label</label><input type='radio' name='customfieldid' value='1' checked> One<br><input type='radio' name='customfieldid' value='2'> Two<br><input type='radio' name='customfieldid' value='3'> Three";
// new (&custom_field) WiFiManagerParameter(custom_radio_str); // custom html input
// wm.addParameter(&custom_field);
// wm.setSaveParamsCallback(saveParamCallback);
std::vector<const char *> menu = {"wifi", "info", "param", "sep", "restart", "exit"};
wm.setMenu(menu);
wm.setClass("invert");
//wm.setConfigPortalTimeout(60);
wm.setMinimumSignalQuality(20);
wm.setScanDispPerc(true);
wm.setWiFiAutoReconnect(true);
bool res;
res = wm.autoConnect("AirQaPortal", "12345678"); // Подключение к анонимной точке доступа
if (!res)
{
////////////Serial.println("Failed to connect or hit timeout");
ESP.restart();
}
else
{
////////////Serial.println("Server AirQaPortal start");
}
//Запрос IP сервера MQTT и установка сервера
setMqttServer();
reqNtpTime();
}
//Установка сервера и порта
void setMqttServer()
{
mdns_init();
IPAddress IpMqtt, ipaddr;
ipaddr = MDNS.queryHost(mqttHostName);
if (ipaddr.toString() == "0.0.0.0")
{
Serial.println("MDNS.queryHost==0.0.0.0");
int err = WiFi.hostByName(mqttHostName, IpMqtt);
if (err == 1)
{
setServCall(IpMqtt);
}
else
{
Serial.print("Error code hostByName(): ");
Serial.println(err);
}
}
else
{
setServCall(ipaddr);
}
}
int setServCall(IPAddress SetIpaddr)
{
//////Serial.println("setServCall");
//////Serial.println(SetIpaddr);
MqttClient.setServer(SetIpaddr, mqttPort);
// MqttClient.setCallback(callback);
////////Serial.println("Set MQTT Server - OK") ;
return 0;
}
//Функция получения данных из MQTT если мы подпишемся на топики
// void callback(char *topic, byte *payload, unsigned int length)
// {
// ////////////Serial.print("Message arrived [");
// ////////////Serial.print(topic);
// //////////Serial.print("] ");
// for (int i = 0; i < length; i++)
// {
// //////////Serial.print((char)payload[i]);
// }
// //////////Serial.println();
// }
//Запрос времени NTP и установка локлаьного времени
int reqNtpTime()
{
if (timeClient.update())
{
uint32_t timeEpoch = timeClient.getEpochTime();
setTime(timeEpoch);
Serial.print(timeEpoch);
Serial.print(" <=ntp== ==device=> ");
Serial.println(now());
if (timeEpoch < 100000)
{
Serial.println("Error Time");
}
}
return 0;
}
//Нажатие кнопки для сброса
int checkButton()
{
Serial.println("Button Pressed to RESET");
wm.resetSettings();
// ESP.restart();
//////////Serial.println("Starting config portal");
wm.setConfigPortalTimeout(120);
if (!wm.startConfigPortal("AirQaPortal", "12345678")) //Логин и пароль точки доступа
{
Serial.println("failed to connect or hit timeout");
ESP.restart();
}
else
{
Serial.println("Clien connected to AP");
}
return 0;
}
// {
// String value;
// if (wm.server->hasArg(name))
// {
// value = wm.server->arg(name);
// }
// return value;
// }
//Обновление прошивки, происходит проверка и загрузка
void OTAUpdate()
{
Serial.println("OTAUpdate() START");
bool flagOTA = false;
String keyOTA;
String payload;
if (WiFi.status() == WL_CONNECTED)
{
HTTPClient http;
String serverPath = "http://Dmeteosence.s-host.net/airqa/airquality.php?meteopas=e93gme9hAt9nSWaV&mac=" + macc + "&meteodata=gethash";
Serial.println(serverPath);
http.begin(serverPath.c_str());
int httpResponseCode = http.GET();
if (httpResponseCode > 0)
{
Serial.println(httpResponseCode);
payload = http.getString();
Serial.println(payload);
if (payload != "errno" || payload != "errfi")
{
OTApreferences.begin("ota-config");
keyOTA = OTApreferences.getString("md5HashOTA");
if (keyOTA.length() <= 0)
{
OTApreferences.putString("md5HashOTA", "undifined");
}
keyOTA = OTApreferences.getString("md5HashOTA");
if (payload != keyOTA)
{
flagOTA = true;
OTApreferences.putString("md5HashOTA", payload);
}
OTApreferences.end();
}
else
{
Serial.print("Host error: ");
Serial.println(payload);
errorID = 3;
}
}
else
{
Serial.println(httpResponseCode);
errorID = 2;
}
// Free resources
http.end();
}
else
{
////////Serial.println("WHY WiFi is Disconnected??");
}
if (flagOTA == true)
{
// flagOTA = false;
disconnectMQTT();
ESPhttpUpdate.rebootOnUpdate(true);
t_httpUpdate_return ret = ESPhttpUpdate.update("http://meteosence.s-host.net/airqa/airatoms.bin");
//После update ничего не происходит, такая вот особенность.
//Если все прошло хорошо, перезагрузка на новую прошивку
// Если указано значение true, модуль ESP перезагрузится после успешного завершения обновления. В случае ложного , модуль не перезагружается автоматически. Загруженная новая прошивка остается в стадии обновления флэш-памяти на модуле ESP.
// Процесс загрузки во время следующего запуска модуля путем сброса скопирует обновленное микропрограммное обеспечение в фактическую область программы, и запустится новая программа эскиза. Значение по умолчанию верно.
////////Serial.print("ret ");
Serial.println(ret);
switch (ret)
{
case HTTP_UPDATE_FAILED:
Serial.printf("HTTP_UPDATE_FAILD Error (%d): %s", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
Serial.println(ESPhttpUpdate.getLastError());
Serial.println(ESPhttpUpdate.getLastErrorString().c_str());
Serial.println("HTTP_UPDATE_FAILD Error");
ESP.restart();
break;
case HTTP_UPDATE_NO_UPDATES:
Serial.println("HTTP_UPDATE_NO_UPDATES");
ESP.restart();
break;
case HTTP_UPDATE_OK:
Serial.println("HTTP_UPDATE_OK");
ESP.restart();
break;
}
}
}
//Функция для индикации Led
void ledset(char color = 't', bool blink = false)
{
M5.dis.setBrightness(30); //Половина яркости
switch (color)
{
case 'g':
M5.dis.drawpix(0, 0xf00000); //Зеленый
break;
case 'r':
M5.dis.drawpix(0, 0x00f000); //Красный
break;
case 'b':
M5.dis.drawpix(0, 0x0000f0); //Синий
break;
case 'w':
M5.dis.drawpix(0, 0x707070); //Белый
break;
default:
M5.dis.clear();
break;
}
}
//Получение мак адреса
String getMacAddress()
{
uint8_t baseMac[6];
esp_read_mac(baseMac, ESP_MAC_WIFI_STA);
char baseMacChr[18] = {0};
sprintf(baseMacChr, "%02X%02X%02X%02X%02X%02X", baseMac[0], baseMac[1], baseMac[2], baseMac[3], baseMac[4], baseMac[5]);
return String(baseMacChr);
}
//Установка коефициентов калибровки
void SetCallibrationCoeff()
{
// ////////Serial.print("Mac: ");
// ////////Serial.println(getMacAddress());
//[24A160474D14, 5002919F5450, 5002918A38CC]
//tcoeff[0,1,2]
//hcoeff[0,1,2]
if (macc == "24A160474D14")
{
callibrationT = tcoeff[0];
callibrationH = hcoeff[0];
}
if (macc == "5002919F5450")
{
callibrationT = tcoeff[1];
callibrationH = hcoeff[1];
}
if (macc == "5002918A38CC")
{
callibrationT = tcoeff[2];
callibrationH = hcoeff[2];
}
}
//Запрос данных с датчиков.
void reqSensorData()
{
delay(15);
float hdc1080Temp = 0, hdc1080Hum = 0;
int eco2 = 0, tvoc = 0;
for (int g = 0; g <= 10; g++)
{
delay(1000);
if (ccs.available())
{
if (!ccs.readData())
{
// ccs.readAlgorithmResults();
eco2 = ccs.geteCO2();
tvoc = ccs.getTVOC();
Serial.println(ccs.geteCO2());
Serial.println(ccs.getTVOC());
Serial.print("baseline: ");
Serial.println(ccs.getBaseline());
Serial.print("CurrentSelected: ");
Serial.println(ccs.getCurrentSelected());
Serial.print("RawADCreading: ");
Serial.println(ccs.getRawADCreading());
}
else
{
Serial.println("==========ECO TVOC NONE READ");
}
}
else
{
eco2 = 0;
tvoc = 0;
Serial.println("==========ECO TVOC NONE AVAILABLE");
errorID = 5;
}
}
hdc1080Temp = hdc1080.readTemperature();
hdc1080Hum = hdc1080.readHumidity();
if (hdc1080Temp < -40 || hdc1080Temp > 60)
{
errorID = 6;
}
if (hdc1080Hum < 0 || hdc1080Temp > 100)
{
errorID = 7;
}
Serial.println("Original data: ");
Serial.print(hdc1080.readTemperature());
Serial.print(" ");
Serial.println(hdc1080.readHumidity());
Serial.println("Calibration data: ");
Serial.print(hdc1080Temp);
Serial.print(" ");
Serial.println(hdc1080Hum);
TempAv = hdc1080Temp;
HumAv = hdc1080Hum;
Eco2Av = eco2;
TvocAv = tvoc;
rssi = WiFi.RSSI();
}
int SendMqttReq(bool sendVal = true, bool sendStatus = true, byte statusConn = 1)
{
if (mqttSendFlag == true)
{
char resultString[200];
String JsonData = "";
rssi = WiFi.RSSI();
unsigned long timeNow = now();
if (sendVal == true)
{
doc["t"] = TempAv;
doc["h"] = HumAv;
doc["eco"] = (int)Eco2Av;
doc["tvoc"] = (int)TvocAv;
doc["ligh"] = (int)map(analogRead(33), 0, 4095, 0, 100);
doc["ts"] = timeNow;
serializeJson(doc, JsonData);
doc.remove("t");
doc.remove("h");
doc.remove("eco");
doc.remove("tvoc");
doc.remove("ligh");
doc.remove("ts");
doc.clear();
doc.garbageCollect();
Serial.println(JsonData); //Вывод JSON строки в консоль
JsonData.toCharArray(resultString, JsonData.length() + 1);
MqttClient.publish(bufTopic, resultString, true);
}
if (sendStatus == true)
{
const char *conn;
switch (statusConn)
{
case 1:
conn = "on";
break;
case 2:
conn = "off";
break;
case 3:
conn = "slp";
break;
default:
break;
}
doc["conn"] = conn;
doc["rssi"] = rssi;
doc["bsid"] = WiFi.BSSIDstr();
//doc["lasterror"] = errorID;
doc["ts"] = timeNow;
doc["sv"] = VersionSW;
JsonData = "";
serializeJson(doc, JsonData);
Serial.println(JsonData); //Вывод JSON строки в консоль
doc.clear();
doc.garbageCollect();
JsonData.toCharArray(resultString, JsonData.length() + 1);
MqttClient.publish(bufWillTopic, resultString, true);
}
}
return 0;
}
void reconnectMqtt()
{
byte circle = 0;
while (!MqttClient.connected())
{
////Serial.print("MQTT reconnect...");
circle++;
if (circle == 2)
{
break;
}
String maccrandom = macc + String(random(0xffff), HEX);
const char *clientId = maccrandom.c_str();
//const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage
////Serial.println(clientId);
if (MqttClient.connect(clientId, bufWillTopic, 2, true, willmess))
{
Serial.println("MqttClient.connect() - OK");
errorID = 8;
mqttSendFlag = true;
SendMqttReq(false, true, 1);
}
else
{
////Serial.print("MqttClient.state() = ");
////Serial.println(MqttClient.state());
/*
-4 : MQTT_CONNECTION_TIMEOUT - the server didn't respond within the keepalive time
-3 : MQTT_CONNECTION_LOST - the network connection was broken
-2 : MQTT_CONNECT_FAILED - the network connection failed
-1 : MQTT_DISCONNECTED - the client is disconnected cleanly
0 : MQTT_CONNECTED - the client is connected
1 : MQTT_CONNECT_BAD_PROTOCOL - the server doesn't support the requested version of MQTT
2 : MQTT_CONNECT_BAD_CLIENT_ID - the server rejected the client identifier
3 : MQTT_CONNECT_UNAVAILABLE - the server was unable to accept the connection
4 : MQTT_CONNECT_BAD_CREDENTIALS - the username/password were rejected
5 : MQTT_CONNECT_UNAUTHORIZED - the client was not authorized to connect
*/
mqttSendFlag = false;
delay(3000);
}
}
}
void loop()
{
//Проверка старта сервера
M5.update();
if (!MqttClient.connected())
{
reconnectMqtt();
ledset('w', true);
}
//Если не определен IP то и не будет отправки.
// 40 секунд и происходит сброс настроек WIFI
if (M5.Btn.isPressed())
{
ledset('r', true);
checkButton();
}
if (millis() - timingUpdate > nextM5Update)
{
reqNtpTime();
OTAUpdate();
timingUpdate = millis();
}
reqSensorData();
ledset('g', true);
SendMqttReq(true, true, 3);
MqttClient.loop();
disconnectMQTT();
delay(500);
goToSleep();
}
int disconnectMQTT()
{
MqttClient.disconnect();
return 0;
}
int goToSleep()
{
ledset();
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
// adc_power_off();
esp_wifi_stop();
// rtc_gpio_init(pinWak);
// rtc_gpio_set_direction(pinWak, RTC_GPIO_MODE_OUTPUT_ONLY);
// rtc_gpio_set_level(pinWak, 0);
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
esp_deep_sleep_start();
}