/* Питание 3,3 В Логические уровни 3,3 В Точность измерения влажности ± 2% Точность измерения температуры ± 0,2 °C Разрешающая способность показаний датчика HDC1080 14 бит Диапазон измерения концентрации CO2: 400 ... 8192 ppm Диапазон измерения концентрации летучих органических веществ 0 ... 1187 ppb */ #include // https://github.com/tzapu/WiFiManager #include //Сохранение настроек хеша прошивки #include #include //Библиотека ОТА обновлений #include "M5Atom.h" //Библиотека атома для функции Led и Кнопки, можно упразднить и убрать #include //Библиотека таймера для Led #include //Udp клиент #include //NTP запрос времени #include //Внутреннее время #include //Библиотека дял I2C #include "ClosedCube_HDC1080.h" //Температура влажность #include "Adafruit_CCS811.h" //eco2 Tvoc #include //Mtqq #include //Упакова в JSon - удобная библиотека #include //Наша кнопочка при нажатии на которую произойдет вызов wifi менеджера и перезагрузка в станцию #define TRIGGER_PIN 39 unsigned int VersionSW = 21; //65536 Версия прошивки //15 - добавлено то, се, забыл вообще дописать что добавленоSerial //19 - вывод в консоль всех действий, ошибки с обновлнеием - починил, прияногое мигание светодиодом, тест для поиска metrics. //21 - убран мак из вывода в топике WiFiManager wm; // обьект менеджера WiFiManagerParameter custom_field; Preferences OTApreferences; //Обьект хранения настроек хеша прошивки Ticker RGBWTicker; Adafruit_CCS811 ccs; ClosedCube_HDC1080 hdc1080; String JsonData; 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 *mqttHostName = "192.168.89.210"; //Хостнейм брокера metrics.local //192.168.89.210 unsigned int mqttPort = 1883; //Порт брокера 1883 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; byte errorID = 0; long rssi = 0; //int hall=0; //Кусок кода поправить - так делать нельзя #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 = "
One
Two
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 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("AirQaPortal", "12345678"); // Подключение к анонимной точке доступа if (!res) { Serial.println("Failed to connect or hit timeout"); // ESP.restart(); } else { Serial.println("Server AirQaPortal start"); } 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("======WHERE IS METRICS.LOCAL====="); mdns_init(); Serial.print("MDNS SAY: "); IPAddress ipaddr = MDNS.queryHost("metrics"); // .local omitted Serial.println(ipaddr.toString()); Serial.print("HOSTBYNAME SAY: "); int errtest = WiFi.hostByName("metrics", ipaddr); Serial.println(ipaddr.toString()); Serial.println("=======END TEST======"); int err = WiFi.hostByName(mqttHostName, IpMqtt); if (err == 1) { delay(50); MqttClient.setServer(IpMqtt, mqttPort); MqttClient.setCallback(callback); Serial.println("Set MQTT Server - OK"); } else { Serial.print("Error code hostByName(): "); 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(); uint32_t timeEpoch = timeClient.getEpochTime(); setTime(timeEpoch); Serial.print(timeEpoch); Serial.print(" <=ntp== ==device=> "); Serial.println(now()); if (timeEpoch < 100000) { errorID = 1; } } //Нажатие кнопки для сброса void 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"); delay(1000); // ESP.restart(); } else { Serial.println("Clien connected to AP"); } } 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")); } //Обновление прошивки, происходит проверка и загрузка void OTAUpdate() { Serial.println("OTAUpdate() START"); bool flagOTA = false; String keyOTA; String payload; OTApreferences.begin("ota-config"); if (WiFi.status() == WL_CONNECTED) { HTTPClient http; String getMacNow = getMacAddress(); //const char *clientId = macc.c_str(); String serverPath = "http://meteosence.s-host.net/airqa/airquality.php?meteopas=e93gme9hAt9nSWaV&mac=" + getMacNow + "&meteodata=gethash"; Serial.print("URL and GET request: "); Serial.println(serverPath); http.begin(serverPath.c_str()); int httpResponseCode = http.GET(); if (httpResponseCode > 0) { Serial.print("HTTP Response code: "); Serial.println(httpResponseCode); payload = http.getString(); Serial.print("Data from HTTP request (OTA Hash): "); Serial.println(payload); if (payload != "errno" || payload != "errfi") { keyOTA = OTApreferences.getString("md5HashOTA"); if (keyOTA.length() <= 0) { OTApreferences.putString("md5HashOTA", "undifined"); } Serial.print("md5HashOTA in device: "); keyOTA = OTApreferences.getString("md5HashOTA"); Serial.println(keyOTA); if (payload != keyOTA) { flagOTA = true; OTApreferences.putString("md5HashOTA", payload); Serial.println("flagOTA = true;"); } } else { Serial.print("Hosting return error: "); Serial.println(payload); errorID = 3; } } else { Serial.print("Error HTTP Response code: "); Serial.println(httpResponseCode); errorID = 2; } // Free resources http.end(); } else { Serial.println("WHY WiFi is 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) { M5.dis.setBrightness(100); //Половина яркости 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; } if (blink == true) { //Таймер для LED RGBWTicker.attach_ms(1000, ledBlinkTimer); } else { RGBWTicker.detach(); } } void ledBlinkTimer() { if (flagblink == true) { M5.dis.setBrightness(0); M5.dis.drawpix(0, 0xf00000); for (int b = 0; b <= 180; b++) { M5.dis.setBrightness(b); delay(7); } flagblink = false; } else { M5.dis.setBrightness(180); M5.dis.drawpix(0, 0xf00000); for (int b = 180; b >= 0; b--) { M5.dis.setBrightness(b); delay(7); } //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; errorID = 4; } } else { errorID = 5; } hdc1080Temp = hdc1080.readTemperature() + callibrationT; hdc1080Hum = hdc1080.readHumidity() + callibrationH; if(hdc1080Temp < -40 || hdc1080Temp > 60 ) { errorID = 6; } if(hdc1080Hum < 0 || hdc1080Temp > 100 ) { errorID = 7; } // Serial.println("Original data: "); // Serial.println(hdc1080.readTemperature()); // Serial.println(hdc1080.readHumidity()); // Serial.println("Calibration data: "); // Serial.print(hdc1080Temp); // Serial.println(" "); // Serial.println(hdc1080Hum); TempAv = middleArifmT(hdc1080Temp); HumAv = middleArifmH(hdc1080Hum); Eco2Av = middleArifmECO(eco2); TvocAv = middleArifmTVOC(tvoc); rssi = WiFi.RSSI(); // hall=hallRead(); if (hdc1080Hum > 50 || hdc1080Hum < 39) { ledset('r', true); } if (hdc1080Temp > 24 || hdc1080Temp < 19) { ledset('r', true); } if (tvoc > 500) { ledset('r', true); } if (eco2 > 1000) { ledset('r', true); } } //Отправка данных по 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["rssi"] = rssi; // doc["hall"]= hall; doc["error"] = errorID; //По умолчанию 0 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"); errorID=0; } } //Переподключение при петери связи с 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.println("Reconnect NOW"); errorID=8; // client.subscribe("inTopic"); mqttSendFlag = true; } else { Serial.print("failed сonnect to MQTT, rc="); Serial.println(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(); ledset('b', true); // Serial.print("reqSensorData() - "); // Serial.println(millis()); timingReqSensor = millis(); } //Таймер отправки данных в брокер if (millis() - timingSendMqtt > nextMqttSend) { SendMqttReq(); ledset('g', true); timingSendMqtt = millis(); } //Тут же проверка подключения к MQTT if (!MqttClient.connected()) { reconnectMqtt(); ledset('w', true); } // Serial.println("============="); // rssi = WiFi.RSSI(); // Serial.println(rssi); // Serial.println("============="); MqttClient.loop(); }