/* Питание 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" #include #include #include "ClosedCube_HDC1080.h" #include "Adafruit_CCS811.h" //The device's I2C address is 0x5A #include #include //Наша кнопочка при нажатии на которую произойдет вызов wifi менеджера и перезагрузка в станцию #define TRIGGER_PIN 39 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; const PROGMEM char *mqttHostName = "cctv.automation.art"; unsigned int mqttPort = 8889; const PROGMEM char *topicName = "/aastudio"; // +mac адресс девайса const PROGMEM char *mqttLogin = "login", *mqttPass = "password"; const char *mqttIPHost; unsigned long timingUpdate, timingReqSensor, timingSendMqtt; 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.22, -9.62, -11.03}; float PROGMEM hcoeff[3] = {12.30, 13.34, 15.18}; float callibrationT = 0, callibrationH = 0; String macc = ""; float TempAv, HumAv, Eco2Av, TvocAv; #define NUM_AVER 6 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); 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("AutoConnectAP", "password"); // password protected ap if (!res) { Serial.println("Failed to connect or hit timeout"); // ESP.restart(); } else { //if you get here you have connected to the WiFi 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(); } 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); } } 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(); } void checkButton() { Serial.println("Button Pressed"); delay(3000); if (digitalRead(TRIGGER_PIN) == LOW) { Serial.println("Button Held"); 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")); } void OTAUpdate() { Serial.println("OTAUpdate()"); //http://meteosence.s-host.net/meteosence.php?meteopas=PdF4apD4i95xR5&meteodata=gethash 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=e93gme9hAt9nSWaV&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", "asshole"); } 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"); 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(); } 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) { // принимает новое значение valArrayT[idxT] = newVal; // пишем каждый раз в новую ячейку if (++idxT >= NUM_AVER) idxT = 0; // перезаписывая самое старое значение averageT = 0; // обнуляем среднее for (int i = 0; i < NUM_AVER; i++) { averageT += valArrayT[i]; // суммируем } averageT /= NUM_AVER; // делим 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; TempAv = middleArifmT(hdc1080Temp); HumAv = middleArifmH(hdc1080Hum); Eco2Av = middleArifmECO(eco2); TvocAv = middleArifmTVOC(tvoc); } void SendMqttReq() { char HumInt[8], TempInt[8]; dtostrf(HumAv * 100, 4, 0, HumInt); dtostrf(TempAv * 100, 4, 0, TempInt); doc["mac"] = String(getMacAddress()); doc["t"] = String(TempInt); doc["h"] = String(HumInt); doc["eco"] = (int)Eco2Av; doc["tvoc"] = (int)TvocAv; doc["time"]=millis(); 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"); } } void reconnectMqtt() { Serial.print("Attempting MQTT connection..."); //clientId += String(random(0xffff), HEX); //clientId.c_str() //MqttClient.connect(macc.c_str(), mqttLogin, mqttPass const char *clientId = macc.c_str(); //macc += String(random(0xffff), HEX); if (MqttClient.connect("AtomClient")) { 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; } } void loop() { //Проверка старта сервера M5.update(); MqttClient.loop(); //Если не определен IP то и не будет отправки. if (!MqttClient.connected()) { //Если клиент отключился, переподключение reconnectMqtt(); } // 40 секунд и происходит сброс настроек WIFI if (M5.Btn.wasReleasefor(40000)) { ledset('r', true); checkButton(); } //По таймеру запруск обновления - проверка хеша прошивки на сервере if (millis() - timingUpdate > nextM5Update) { 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(); } }