M5Atom_airqa/M5Atom_airqa.ino

690 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 <WiFiManager.h> // https://github.com/tzapu/WiFiManager
#include <Preferences.h> //Сохранение настроек хеша прошивки
#include <HTTPClient.h>
#include <ESP32httpUpdate.h> //Библиотека ОТА обновлений
#include "M5Atom.h" //Библиотека атома для функции Led и Кнопки, можно упразднить и убрать
#include <Ticker.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 <PubSubClient.h> //Mtqq
#include <ArduinoJson.h> //Упакова в JSon - удобная библиотека
//Наша кнопочка при нажатии на которую произойдет вызов wifi менеджера и перезагрузка в станцию
#define TRIGGER_PIN 39
unsigned int VersionSW = 14; //65536 Версия прошивки
WiFiManager wm; // обьект менеджера
WiFiManagerParameter custom_field;
Preferences OTApreferences; //Обьект хранения настроек хеша прошивки
Ticker RGBWTicker;
Adafruit_CCS811 ccs;
ClosedCube_HDC1080 hdc1080;
String JsonData;
StaticJsonDocument<200> doc;
WiFiClient espClient;
PubSubClient MqttClient(espClient);
IPAddress IpMqtt;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "0.ua.pool.ntp.org", 7200, 60000); //Собственно сервер времени смещение и частоат запроса, но он вручную
const PROGMEM char *mqttHostName = "cctv.automation.art"; //Хостнейм брокера
unsigned int mqttPort = 8889; //Порт брокера
const PROGMEM char *topicName = "/aastudio"; // +mac адресс девайса Префикс топика
const PROGMEM char *mqttLogin = "login", //Логин пароль - необходимо сменить код при connect()
*mqttPass = "password";
const char *mqttIPHost; //тут хранится IP хоста по хостнейму
unsigned long timingUpdate, timingReqSensor, 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;
String macc = ""; //Глобальное хранение мас адреса
float TempAv, HumAv, Eco2Av, TvocAv;
//Кусок кода поправить - так делать нельзя
#define NUM_AVER 5
float averageT; // перем. среднего
float valArrayT[NUM_AVER]; // массив
byte idxT = 0;
float averageH; // перем. среднего
float valArrayH[NUM_AVER]; // массив
byte idxH = 0;
float averageECO; // перем. среднего
float valArrayECO[NUM_AVER]; // массив
byte idxECO = 0;
float averageTVOC; // перем. среднего
float valArrayTVOC[NUM_AVER]; // массив
byte idxTVOC = 0;
bool mqttSendFlag = false;
// float arrT[5] = {0}, arrH[5] = {0};
// int arrECO[5] = {0}, tvoc[5] = {0};
int reqCounter = 0;
bool flagblink = true;
//Настройки
void setup()
{
M5.begin(true, false, true);
delay(50);
Wire.begin(25, 21); //Пины для I2c на ATOM
pinMode(23, OUTPUT); //Пин датчика для работы
digitalWrite(23, LOW);
WiFi.mode(WIFI_STA);
Serial.begin(115200);
Serial.setDebugOutput(true);
delay(3000);
Serial.println("\n Starting station");
pinMode(TRIGGER_PIN, INPUT);
// wm.resetSettings(); // wipe settings
// add a custom input field
int customFieldLength = 40;
// new (&custom_field) WiFiManagerParameter("customfieldid", "Custom Field Label", "Custom Field Value", customFieldLength,"placeholder=\"Custom Field Placeholder\"");
// test custom html input type(checkbox)
// new (&custom_field) WiFiManagerParameter("customfieldid", "Custom Field Label", "Custom Field Value", customFieldLength,"placeholder=\"Custom Field Placeholder\" type=\"checkbox\""); // custom html type
// test custom html(radio)
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);
// custom menu via array or vector
// menu tokens, "wifi","wifinoscan","info","param","close","sep","erase","restart","exit" (sep is seperator) (if param is in menu, params will not show up in wifi page!)
// const char* menu[] = {"wifi","info","param","sep","restart","exit"};
// wm.setMenu(menu,6);
std::vector<const char *> menu = {"wifi", "info", "param", "sep", "restart", "exit"};
wm.setMenu(menu);
// set dark theme
wm.setClass("invert");
//set static ip
// wm.setSTAStaticIPConfig(IPAddress(10,0,1,99), IPAddress(10,0,1,1), IPAddress(255,255,255,0)); // set static ip,gw,sn
// wm.setShowStaticFields(true); // force show static ip fields
// wm.setShowDnsFields(true); // force show dns field always
// wm.setConnectTimeout(20); // how long to try to connect for before continuing
wm.setConfigPortalTimeout(30); // auto close configportal after n seconds
// wm.setCaptivePortalEnable(false); // disable captive portal redirection
// wm.setAPClientCheck(true); // avoid timeout if client connected to softap
// wifi scan settings
// wm.setRemoveDuplicateAPs(false); // do not remove duplicate ap names (true)
// wm.setMinimumSignalQuality(20); // set min RSSI (percentage) to show in scans, null = 8%
// wm.setShowInfoErase(false); // do not show erase button on info page
// wm.setScanDispPerc(true); // show RSSI as percentage not graph icons
// wm.setBreakAfterConfig(true); // always exit configportal even if wifi save fails
bool res;
// res = wm.autoConnect(); // auto generated AP name from chipid
// res = wm.autoConnect("AutoConnectAP"); // anonymous ap
res = wm.autoConnect("AutoConnectAP", "password"); // Подключение к анонимной точке доступа
if (!res)
{
Serial.println("Failed to connect or hit timeout");
// ESP.restart();
}
else
{
Serial.println("connected...");
}
if (!ccs.begin()) //The device's I2C address is 0x5A
{
Serial.println("Failed to start sensor CCS811! ");
}
ccs.setTempOffset(8.6);
hdc1080.begin(0x40); //14 бит температура и влажность
hdc1080.setResolution(HDC1080_RESOLUTION_14BIT, HDC1080_RESOLUTION_14BIT);
//Установка коеффициентов каллибровки по MAC адресу
SetCallibrationCoeff();
//Запрос IP сервера MQTT и установка сервера
setMqttServer();
reqNtpTime();
}
//Установка сервера и порта
void setMqttServer()
{
Serial.println("Gethostbyname: ");
int err = WiFi.hostByName(mqttHostName, IpMqtt);
if (err == 1)
{
Serial.print("Ip address by Name: ");
Serial.println(IpMqtt);
delay(50);
MqttClient.setServer(IpMqtt, mqttPort);
MqttClient.setCallback(callback);
}
else
{
Serial.print("Error code gethostbyname: ");
Serial.println(err);
}
}
//Функция получения данных из 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 и установка локлаьного времени
void reqNtpTime()
{
timeClient.update();
setTime(timeClient.getEpochTime());
// Serial.println(timeClient.getEpochTime());
// Serial.println("<=ntp====now=>");
//time_t t = now();
// Serial.println(t);
}
//Нажатие кнопки для сброса
void checkButton()
{
Serial.println("Button Pressed");
// delay(3000);
// if (digitalRead(TRIGGER_PIN) == LOW) {
Serial.println("Erasing Config, restarting");
wm.resetSettings();
ESP.restart();
// }
// start portal w delay
Serial.println("Starting config portal");
wm.setConfigPortalTimeout(120);
if (!wm.startConfigPortal("AirQaPortal", "12345678")) //Логин и пароль точки доступа
{
Serial.println("failed to connect or hit timeout");
delay(3000);
// ESP.restart();
}
else
{
Serial.println("CLIENT connect");
}
}
String getParam(String name)
{
String value;
if (wm.server->hasArg(name))
{
value = wm.server->arg(name);
}
return value;
}
void saveParamCallback()
{
Serial.println("[CALLBACK] saveParamCallback fired");
Serial.println("PARAM customfieldid = " + getParam("customfieldid"));
}
//Обновление прошивки, происходит проверка и загрузка
//Делается Get запрос на хостинг проверяется хеш, если хеш
void OTAUpdate()
{
Serial.println("OTAUpdate()");
bool flagOTA = false;
String keyOTA;
String payload;
OTApreferences.begin("ota-config");
if (WiFi.status() == WL_CONNECTED)
{
HTTPClient http;
String getMacNow = getMacAddress();
String serverPath = "http://meteosence.s-host.net/airqa/airquality.php?meteopas=e93gme9hAt9nSWaV&mac=" + getMacNow + "&meteodata=gethash";
http.begin(serverPath.c_str());
int httpResponseCode = http.GET();
if (httpResponseCode > 0)
{
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
payload = http.getString();
Serial.println(payload);
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);
Serial.println("flagOTA = true;");
}
}
else
{
Serial.print("Error code: ");
Serial.println(httpResponseCode);
}
// Free resources
http.end();
}
else
{
Serial.println("WiFi Disconnected");
}
if (flagOTA == true)
{
flagOTA = false;
Serial.println("flagOTA = false;");
t_httpUpdate_return ret = ESPhttpUpdate.update("http://meteosence.s-host.net/airqa/airatoms.bin");
//После update ничего не происходит, такая вот особенность.
//Если все прошло хорошо, перезагрузка на новую прошивку
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");
delay(3000);
ESP.restart();
break;
case HTTP_UPDATE_NO_UPDATES:
Serial.println("HTTP_UPDATE_NO_UPDATES");
delay(3000);
ESP.restart();
break;
case HTTP_UPDATE_OK:
Serial.println("HTTP_UPDATE_OK");
delay(3000);
ESP.restart();
break;
}
}
OTApreferences.end();
}
//Функция для индикации Led
void ledset(char color, bool blink = false)
{
switch (color)
{
case 'g':
M5.dis.drawpix(0, 0xf00000); //Зеленый
ledset('r', true);
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;
}
if (blink == true)
{ //Таймер для LED
RGBWTicker.attach_ms(1000, ledBlinkTimer);
}
else
{
RGBWTicker.detach();
}
}
void ledBlinkTimer()
{
if (flagblink == true)
{
M5.dis.drawpix(0, 0xf00000);
flagblink = false;
}
else
{
M5.dis.clear();
flagblink = true;
}
}
//Получение мак адреса
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]
macc = getMacAddress();
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];
}
}
//Ниже отдельные функции средних арифметических для каждого параместра с историей
float middleArifmT(float newVal)
{
// Serial.print(newVal);
// Serial.print(" ");
// принимает новое значение
valArrayT[idxT] = newVal; // пишем каждый раз в новую ячейку
// for (int t = 0; t <= 6; t++)
// {
// Serial.print(valArrayT[t]);
// Serial.print(" ");
// }
if (++idxT >= NUM_AVER)
idxT = 0; // перезаписывая самое старое значение
averageT = 0; // обнуляем среднее
for (int i = 0; i < NUM_AVER; i++)
{
averageT += valArrayT[i]; // суммируем
}
averageT /= NUM_AVER; // делим
// Serial.println(averageT);
return averageT; // возвращаем
}
float middleArifmH(float newVal)
{ // принимает новое значение
valArrayH[idxH] = newVal; // пишем каждый раз в новую ячейку
if (++idxH >= NUM_AVER)
idxH = 0; // перезаписывая самое старое значение
averageH = 0; // обнуляем среднее
for (int i = 0; i < NUM_AVER; i++)
{
averageH += valArrayH[i]; // суммируем
}
averageH /= NUM_AVER; // делим
return averageH; // возвращаем
}
float middleArifmECO(float newVal)
{ // принимает новое значение
valArrayECO[idxECO] = newVal; // пишем каждый раз в новую ячейку
if (++idxECO >= NUM_AVER)
idxECO = 0; // перезаписывая самое старое значение
averageECO = 0; // обнуляем среднее
for (int i = 0; i < NUM_AVER; i++)
{
averageECO += valArrayECO[i]; // суммируем
}
averageECO /= NUM_AVER; // делим
return averageECO; // возвращаем
}
float middleArifmTVOC(float newVal)
{ // принимает новое значение
valArrayTVOC[idxTVOC] = newVal; // пишем каждый раз в новую ячейку
if (++idxTVOC >= NUM_AVER)
idxTVOC = 0; // перезаписывая самое старое значение
averageTVOC = 0; // обнуляем среднее
for (int i = 0; i < NUM_AVER; i++)
{
averageTVOC += valArrayTVOC[i]; // суммируем
}
averageTVOC /= NUM_AVER; // делим
return averageTVOC; // возвращаем
}
//Запрос данных с датчиков.
void reqSensorData()
{
float hdc1080Temp = 0, hdc1080Hum = 0;
int eco2 = 0, tvoc = 0;
if (ccs.available())
{
if (!ccs.readData())
{
eco2 = ccs.geteCO2();
tvoc = ccs.getTVOC();
}
else
{
eco2 = 0;
tvoc = 0;
}
}
hdc1080Temp = hdc1080.readTemperature() + callibrationT;
hdc1080Hum = hdc1080.readHumidity() + callibrationH;
// Serial.println("Original: ");
// Serial.println(hdc1080.readTemperature());
// Serial.println(hdc1080.readHumidity());
Serial.println("+calibration: ");
Serial.print(hdc1080Temp);
Serial.println(" ");
Serial.println(hdc1080Hum);
TempAv = middleArifmT(hdc1080Temp);
HumAv = middleArifmH(hdc1080Hum);
Eco2Av = middleArifmECO(eco2);
TvocAv = middleArifmTVOC(tvoc);
}
//Отправка данных по MQTT
void SendMqttReq()
{
Serial.println("Data in SendMqttReq()");
Serial.print(TempAv);
Serial.print(" ");
Serial.print(HumAv);
Serial.print(" ");
Serial.print(Eco2Av);
Serial.print(" ");
Serial.println(TvocAv);
char HumidityInt[8], TemperatureInt[8];
dtostrf(TempAv * 100, 4, 0, TemperatureInt);
dtostrf(HumAv * 100, 4, 0, HumidityInt);
doc["mac"] = String(getMacAddress());
doc["swver"] = VersionSW;
doc["t"] = atoi(TemperatureInt);
doc["h"] = atoi(HumidityInt);
doc["eco"] = (int)Eco2Av;
doc["tvoc"] = (int)TvocAv;
doc["time"] = now();
char resultString[200];
String JsonData = "";
serializeJson(doc, JsonData);
Serial.println(JsonData); //Вывод JSON строки в консоль
JsonData.toCharArray(resultString, JsonData.length() + 1);
String finishTopic = "";
finishTopic = String(topicName) + "/" + macc;
//Serial.println(finishTopic);
if (mqttSendFlag == true)
{
MqttClient.publish(finishTopic.c_str(), resultString);
//Serial.println("SentToTopic - ok");
}
}
//Переподключение при петери связи с MQTT
//10 раз проверили и вернулись в общий цикл что бы вдруг что втянуть обновления
void reconnectMqtt()
{
//clientId += String(random(0xffff), HEX);
//clientId.c_str()
//MqttClient.connect(macc.c_str(), mqttLogin, mqttPass
byte circle = 0;
while (!MqttClient.connected())
{
Serial.print("MQTT reconnect...");
circle++;
if (circle == 10)
{
break;
}
const char *clientId = macc.c_str();
//macc += String(random(0xffff), HEX);
if (MqttClient.connect(clientId))
{
String finishTopic = "";
finishTopic = String(topicName) + "/" + macc;
MqttClient.publish(finishTopic.c_str(), "Reconnect NOW");
Serial.print("Connect MQTT OK");
// client.subscribe("inTopic");
mqttSendFlag = true;
}
else
{
Serial.print("failed, rc=");
Serial.print(MqttClient.state());
mqttSendFlag = false;
delay(1000);
}
}
}
void loop()
{
//Проверка старта сервера
M5.update();
//Если не определен IP то и не будет отправки.
// 40 секунд и происходит сброс настроек WIFI
if (M5.Btn.wasReleasefor(40000))
{
ledset('r', true);
checkButton();
}
//По таймеру запруск обновления прошивки
if (millis() - timingUpdate > nextM5Update)
{
reqNtpTime();
OTAUpdate();
// Serial.print("OTAUpdate() - ");
// Serial.println(millis());
timingUpdate = millis();
}
//Таймер запроса данных с датчиков
if (millis() - timingReqSensor > nextReqSensor)
{
reqSensorData();
// Serial.print("reqSensorData() - ");
// Serial.println(millis());
timingReqSensor = millis();
}
//Таймер отправки данных в брокер
if (millis() - timingSendMqtt > nextMqttSend)
{
SendMqttReq();
timingSendMqtt = millis();
}
//Тут же проверка подключения к MQTT
if (!MqttClient.connected())
{
reconnectMqtt();
}
MqttClient.loop();
}