M5Atom_airqa/scd30mh/scd30mh.ino

743 lines
21 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.

/*
Отправка данных на MQTT про обновление
*/
#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 <PubSubClient.h> //Mtqq
#include <ArduinoJson.h> //Упакова в JSon - удобная библиотека
#include <ESPmDNS.h>
#include "MHZ19.h"
#include <SoftwareSerial.h>
#include "SparkFun_SCD30_Arduino_Library.h" //Click here to get the library: http://librarymanager/All#SparkFun_SCD30
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET 4
//Наша кнопочка при нажатии на которую произойдет вызов wifi менеджера и перезагрузка в станцию
#define TRIGGER_PIN 39
#define RX_PIN 19
#define TX_PIN 22
#define BAUDRATE 9600
#define i2cSDA 25
#define i2cSCL 21
MHZ19 myMHZ19;
SoftwareSerial mySerial(RX_PIN, TX_PIN);
unsigned int VersionSW = 20;
//17 - локальный брокер
//19 - какие то траблы в офисе
//20 - Ручное удаление
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
WiFiManager wm; // обьект менеджера
Preferences OTApreferences; //Обьект хранения настроек хеша прошивки
String JsonData;
StaticJsonDocument<200> doc;
SCD30 airSens30;
WiFiClient espClient;
PubSubClient MqttClient(espClient);
IPAddress IpMqtt;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "192.168.89.210", 0, 20000); //Собственно сервер времени смещение и частоат запроса, но он вручную
const PROGMEM char *willmess = "{\"conn\":\"err\"}";
const char *mqttHostName = "metrics"; //Хостнейм брокера metrics.local cctv.automation.art //192.168.89.210
unsigned int mqttPort = 1883; //Порт брокера 1883 8889
String getMacAddress();
String macc = getMacAddress();
String topicTemp = "aastudio/sens/" + macc + "/data/0";
String willTopicTemp = "aastudio/sens/" + macc + "/status";
const PROGMEM char *mqttLogin = "AA_Lab", *mqttPass = "automation.art$";
const char *mqttIPHost; //тут хранится IP хоста по хостнейму
unsigned long timingUpdate=0, timingReqSensor=0, timingSendMqtt=0; //Таймеры для millis()
int PROGMEM nextM5Update = 450000, nextMqttSend = 60000; //каждые 7.5 минут запрос обновления с сервера
//Отправка
bool typeSensor = false, GlobalOled = false;
bool mqttSendFlag = false;
bool safetyStateNow = false, safetyStateOld = false;
int reqCounter = 0;
long rssi = 0;
int GlobalCo2 = 0;
char bufTopic[140], bufWillTopic[150], bufAngleTopic[150];
void ledset(byte color = 0)
{
M5.dis.setBrightness(60); //Половина яркости
switch (color)
{
case 2:
M5.dis.drawpix(0, 0xf00000); //Зеленый
break;
case 1:
M5.dis.drawpix(0, 0x00ff00); //Красный
break;
case 3:
M5.dis.drawpix(0, 0x0000ff); //Синий
break;
case 4:
M5.dis.drawpix(0, 0xffffff); //Белый
break;
case 5:
M5.dis.drawpix(0, 0xffec00); //желтый
break;
default:
M5.dis.clear();
break;
}
}
//Настройки
void setup()
{
Serial.begin(115200);
Serial.setDebugOutput(true);
M5.begin(true, false, true);
Wire.begin(32, 26);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C))
{ // Address 0x3D for 128x64
GlobalOled = false;
}
else
{
GlobalOled = true;
display.display();
display.clearDisplay();
}
// 1й - Просто датчик scd30
// 2й - Датчик золотой MH-z19
if (macc == "94B97EAD9EDC" || macc == "94B97E8ADF7C")
{
typeSensor = true;
}
else if (macc == "24A160542E48" || macc == "94B97E8ADF80")
{
typeSensor = false;
}
if (typeSensor == true)
{
Wire.begin(i2cSDA, i2cSCL);
if (airSens30.begin() == false)
{
Serial.println("Air sensor not detected. Please check wiring. Freezing...");
}
airSens30.setMeasurementInterval(10); //Change number of seconds between measurements: 2 to 1800 (30 minutes)
airSens30.setAltitudeCompensation(324); //Set altitude of the sensor in m
// airSens30.setAmbientPressure(835); //Current ambient pressure in mBar: 700 to 1200
float offset = airSens30.getTemperatureOffset();
Serial.print("Current temp offset: ");
Serial.print(offset, 2);
Serial.println("C");
//airSens30.setTemperatureOffset(5);
}
else
{
mySerial.begin(BAUDRATE);
myMHZ19.begin(mySerial);
myMHZ19.autoCalibration(false);
}
delay(50);
pinMode(TRIGGER_PIN, INPUT);
WiFi.mode(WIFI_STA);
delay(100);
Serial.println(topicTemp);
Serial.println(willTopicTemp);
topicTemp.toCharArray(bufTopic, topicTemp.length() + 1);
willTopicTemp.toCharArray(bufWillTopic, willTopicTemp.length() + 1);
// wm.resetSettings(); // wipe settings
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.setMinimumSignalQuality(10); // set min RSSI (percentage) to show in scans, null = 8%
wm.setScanDispPerc(true); // show RSSI as percentage not graph icons
wm.setWiFiAutoReconnect(true); // if true, enable autoreconnecting
bool res;
res = wm.autoConnect("Co2M5Portal", "12345678"); // Подключение к анонимной точке доступа
if (!res)
{
Serial.println("Failed to connect or hit timeout");
ESP.restart();
}
setMqttServer(mqttHostName, mqttPort);
if (reqNtpTime() == 1)
{
reqNtpTime();
}
}
IPAddress str2IP(String str)
{
IPAddress ret(getIpBlock(0, str), getIpBlock(1, str), getIpBlock(2, str), getIpBlock(3, str));
return ret;
}
int getIpBlock(int index, String str)
{
char separator = '.';
int found = 0;
int strIndex[] = {0, -1};
int maxIndex = str.length() - 1;
for (int i = 0; i <= maxIndex && found <= index; i++)
{
if (str.charAt(i) == separator || i == maxIndex)
{
found++;
strIndex[0] = strIndex[1] + 1;
strIndex[1] = (i == maxIndex) ? i + 1 : i;
}
}
return found > index ? str.substring(strIndex[0], strIndex[1]).toInt() : 0;
}
int setMqttServer(const char *mqttHostNameF, unsigned int mqttPortF)
{
mdns_init();
IPAddress ipaddr;
OTApreferences.begin("ota-config");
OTApreferences.putString("mqttip", "0");
delay(1000);
String keyIpMqtt = OTApreferences.getString("mqttip", "0");
Serial.print("keyIpMqtt: ");
Serial.println(keyIpMqtt);
if (keyIpMqtt == "0")
{
//Запрос по хостнейму .local
ipaddr = MDNS.queryHost(mqttHostNameF, 4000);
//Таймаут 4 секунды
//Если не получили по хостнейму МДНС
Serial.print("mdns: ");
Serial.println(ipaddr);
if (ipaddr.toString() == "0.0.0.0")
{
//Запрос к ДНС по адресу сайта
int err = WiFi.hostByName(mqttHostNameF, ipaddr);
////real internal timeout in lwip library is 14[s]
//Если получили по хостнейму
if (err == 1)
{
Serial.print("hostByName: ");
Serial.println(ipaddr);
MqttClient.setServer(ipaddr, mqttPortF);
OTApreferences.putString("mqttip", ipaddr.toString());
}
else
{
Serial.print("All method not FOUND IP");
}
}
else
{ //Если все хорошо
OTApreferences.putString("mqttip", ipaddr.toString());
MqttClient.setServer(ipaddr, mqttPortF);
}
}
else
{
MqttClient.setServer(str2IP(keyIpMqtt), mqttPortF);
}
OTApreferences.end();
return 0;
}
void setServCall(IPAddress SetIpaddr)
{
MqttClient.setServer(SetIpaddr, mqttPort);
// MqttClient.setCallback(callback);
}
//Функция получения данных из 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();
if (timeEpoch < 1600000000)
{
Serial.println("Error reqNtpTime() Time");
return 1;
}
setTime(timeEpoch);
// Serial.print(timeEpoch);
// Serial.print(" <=ntp== ==device=> ");
// Serial.println(now());
}
return 0;
}
//Нажатие кнопки для сброса
void checkButton()
{
Serial.println("Button RESET Pressed");
disconnectMQTT();
delay(100);
wm.resetSettings();
OTApreferences.begin("ota-config");
OTApreferences.putString("mqttip", "0");
OTApreferences.end();
ESP.restart();
}
//Обновление прошивки, происходит проверка и загрузка
//Делается 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 serverPath = "http://meteosence.s-host.net/airqa/airquality.php?meteopas=
&mac=" + macc + "&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);
if (payload != "errno" || payload != "errfi")
{
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 HTTP: ");
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/co2/co2.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");
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;
}
}
OTApreferences.end();
}
//Получение мак адреса
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);
}
//Отправка данных по MQTT
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();
// {
// "val": 1,
// "ts": 1617194420
// }
// Last device status (ts=59s ago):
// {
// "conn": "on",
// "ts": 1617204271,
// "exp": 1617204871
// }
if (sendVal == true)
{
if (typeSensor == true)
{
doc["t"] = airSens30.getTemperature();
doc["h"] = airSens30.getHumidity();
GlobalCo2 = airSens30.getCO2();
doc["co2"] = GlobalCo2;
doc["ts"] = timeNow;
serializeJson(doc, JsonData);
doc.remove("ts");
doc.remove("t");
doc.remove("h");
doc.remove("co2");
}
else
{
int CO2 = 0;
int8_t Tempz19 = 0;
/* note: getCO2() default is command "CO2 Unlimited". This returns the correct CO2 reading even
if below background CO2 levels or above range (useful to validate sensor). You can use the
usual documented command with getCO2(false) */
CO2 = myMHZ19.getCO2(); // Request CO2 (as ppm)
GlobalCo2 = CO2;
Serial.print("CO2 (ppm): ");
Serial.println(CO2);
Tempz19 = myMHZ19.getTemperature(); // Request Temperature (as Celsius)
Serial.print("Temperature (C): ");
Serial.println(Tempz19);
doc["t"] = Tempz19;
doc["co2"] = CO2;
doc["ts"] = timeNow;
serializeJson(doc, JsonData);
doc.remove("ts");
doc.remove("t");
doc.remove("co2");
}
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;
case 4:
conn = "upd";
break;
default:
break;
}
doc["conn"] = conn;
doc["rssi"] = rssi;
doc["bsid"] = WiFi.BSSIDstr();
doc["ts"] = timeNow;
doc["exp"] = timeNow + 600;
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;
}
//Переподключение при петери связи с MQTT
//7 раз проверили и вернулись в общий цикл что бы вдруг что втянуть обновления
void reconnectMqtt()
{
byte circle = 0;
while (!MqttClient.connected())
{
circle++;
if (circle == 5)
{
break;
}
String maccrandom = macc + String(random(0xffff), HEX);
const char *clientId = maccrandom.c_str();
if (MqttClient.connect(clientId, mqttLogin, mqttPass, bufWillTopic, 2, true, willmess))
{
Serial.println("Mqttconnect - OK");
mqttSendFlag = true;
SendMqttReq(false, true, 1);
}
else
{
Serial.print("Mqtt.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(500);
}
}
}
int printOled()
{
if (typeSensor == true)
{
Wire.begin(32, 26);
}
// if (display.begin(SSD1306_SWITCHCAPVCC, 0x3C))
// { // Address 0x3D for 128x64
Serial.println("PRINT OLED");
display.display();
delay(100);
// Clear the buffer
display.clearDisplay();
display.invertDisplay(true);
delay(500);
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(10, 20);
display.println(String(GlobalCo2)+String(" ppm"));
display.setTextSize(1);
display.setCursor(5, 5);
display.println(String("-CO2- "));
display.display(); // Show initial text
delay(100);
// }
if (typeSensor == true)
{
Wire.begin(i2cSDA, i2cSCL);
}
return 0;
}
void loop()
{
M5.update();
if (WiFi.status() != WL_CONNECTED)
{
Serial.println(WiFi.status());
ESP.restart();
}
if (!MqttClient.connected())
{
reconnectMqtt();
}
if (millis() - timingSendMqtt > nextMqttSend)
{
SendMqttReq();
if (GlobalCo2 <= 320)
{
ledset(4); //Белый
}
else if (GlobalCo2 > 320 && GlobalCo2 <= 1000)
{
ledset(2); //зеленый
}
else if (GlobalCo2 > 1000 && GlobalCo2 <= 2000)
{
ledset(5); //желтый
}
else if (GlobalCo2 > 2000 && GlobalCo2 <= 5000)
{
ledset(1); //красный
}
if (GlobalOled == true)
{
printOled();
}
timingSendMqtt = millis();
}
// 40 секунд и происходит сброс настроек WIFI
if (M5.Btn.wasReleasefor(40000))
{
checkButton();
}
//По таймеру запруск обновления прошивки
if (millis() - timingUpdate > nextM5Update)
{
reqNtpTime();
SendMqttReq(false, true, 4);
OTAUpdate();
SendMqttReq(false, true, 1);
timingUpdate = millis();
}
//Таймер отправки данных в брокер
MqttClient.loop();
}
int disconnectMQTT()
{
MqttClient.disconnect();
return 0;
}