diff --git a/scd30mh/scd30mh.ino b/scd30mh/scd30mh.ino new file mode 100644 index 0000000..68a5470 --- /dev/null +++ b/scd30mh/scd30mh.ino @@ -0,0 +1,622 @@ +/* +Отправка данных на MQTT про обновление +*/ + +#include // https://github.com/tzapu/WiFiManager +#include //Сохранение настроек хеша прошивки + +#include +#include //Библиотека ОТА обновлений +#include "M5Atom.h" //Библиотека атома для функции Led и Кнопки, можно упразднить и убрать + +#include //Udp клиент +#include //NTP запрос времени +#include //Внутреннее время +#include //Библиотека дял I2C + +#include //Mtqq +#include //Упакова в JSon - удобная библиотека +#include + +#include +#include "SparkFun_SCD30_Arduino_Library.h" //Click here to get the library: http://librarymanager/All#SparkFun_SCD30 + + +//Наша кнопочка при нажатии на которую произойдет вызов wifi менеджера и перезагрузка в станцию +#define TRIGGER_PIN 39 + +#define safetyPin 26 +#define safetyPinOutput 32 + +extern const unsigned char image_x[77], image_error[77], image_plus[77], image_connect[77], image_wifi[77]; + +unsigned int VersionSW = 13; //65536 Версия прошивки +byte errorID = 0; + + +WiFiManager wm; // обьект менеджера +Preferences OTApreferences; //Обьект хранения настроек хеша прошивки + +String JsonData; +StaticJsonDocument<200> doc; +SCD30 airSensor; + +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, timingReqSensor, timingSendMqtt; //Таймеры для millis() +int PROGMEM nextM5Update = 450000; //каждые 7.5 минут запрос обновления с сервера //опрос датчиков раз в 10 секунд +int PROGMEM nextMqttSend = 1000; //Отправка +byte typeSensor=0; +bool mqttSendFlag = false; +bool safetyStateNow = false, safetyStateOld = false; +int reqCounter = 0; +long rssi = 0; + +char bufTopic[140], bufWillTopic[150], bufAngleTopic[150]; + +//Настройки +void setup() +{ + +// 1й - Просто датчик scd30 +// 2й - Датчик золотой MH-z19 +if(macc=="" || macc==""){ typeSensor=1; } +else if(macc=="" || macc=="") {typeSensor=2;} + + +if(typeSensor==1){ + + Wire.begin(21,25); + + if (airSensor.begin() == false) + { + Serial.println("Air sensor not detected. Please check wiring. Freezing..."); + while (1) + ; + } + + airSensor.setMeasurementInterval(4); //Change number of seconds between measurements: 2 to 1800 (30 minutes) + + //My desk is ~1600m above sealevel + airSensor.setAltitudeCompensation(170); //Set altitude of the sensor in m + //Pressure in Boulder, CO is 24.65inHg or 834.74mBar +// airSensor.setAmbientPressure(835); //Current ambient pressure in mBar: 700 to 1200 + + float offset = airSensor.getTemperatureOffset(); + Serial.print("Current temp offset: "); + Serial.print(offset, 2); + Serial.println("C"); + + //airSensor.setTemperatureOffset(5); + +} + + M5.begin(true, false, true); + M5.dis.setBrightness(30); + delay(50); + pinMode(TRIGGER_PIN, INPUT); + + pinMode(safetyPinOutput, OUTPUT); // Установка пина на выход + digitalWrite(safetyPinOutput, HIGH); //Потому что он 3.3В а не 5 как на выходе рядом + pinMode(safetyPin, INPUT); //Пин датчика для работы + + Serial.begin(115200); + Serial.setDebugOutput(true); + + WiFi.mode(WIFI_STA); + + delay(100); + + Serial.println(topicTemp); + Serial.println(willTopicTemp); + + topicTemp.toCharArray(bufTopic, topicTemp.length() + 1); + willTopicTemp.toCharArray(bufWillTopic, willTopicTemp.length() + 1); + + disPreSet((uint8_t *)image_connect, 0, 0); //Идет настройка + + // wm.resetSettings(); // wipe settings + + 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.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"); + + 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=pGiy94nIoOftZ0Ik&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.println("Hosting return error HASH or error REQUEST"); + errorID = 2; + } + } + else + { + errorID = 3; + Serial.print("Error code HTTP: "); + Serial.println(httpResponseCode); + } + // Free resources + http.end(); + } + else + { + errorID = 4; + 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/safeatoms.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) + { + doc["val"] = (int)safetyStateNow; + doc["ts"] = timeNow; + + serializeJson(doc, JsonData); + + doc.remove("val"); + 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; + + 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 == 7) + { + 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); + } + } +} + +void loop() +{ + M5.update(); + + if (WiFi.status() != WL_CONNECTED) + { + Serial.println(WiFi.status()); + disPreSet((uint8_t *)image_wifi, 0, 0); + errorID = 6; + delay(3000); + ESP.restart(); + } + + if (!MqttClient.connected()) + { + reconnectMqtt(); + } + + if (millis() - timingSendMqtt > nextMqttSend) + { + if (digitalRead(safetyPin) == HIGH) + { + safetyStateNow = true; + disPreSet((uint8_t *)image_plus, 0, 0); + } + else + { + safetyStateNow = false; + disPreSet((uint8_t *)image_x, 0, 0); + } + + if (safetyStateNow != safetyStateOld) + { + SendMqttReq(); + safetyStateOld = safetyStateNow; + } + + 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; +} + +int disPreSet(uint8_t *image_what, int8_t x, int8_t y) +{ + // M5.dis.fillpix(0x000000); + // M5.dis.clear(); + delay(150); + M5.dis.displaybuff(image_what, x, y); + delay(200); + return 0; +} \ No newline at end of file