├── ESP14MES ├── ESP14MES.ino └── docs │ ├── ESP-14.jpg │ ├── ESP-14_V1.0.pdf │ ├── ESP-14_programming sch.png │ ├── ESP14 │ ├── 2017-05-11_01-14-10-.jpg │ ├── 2017-05-11_01-15-37-.jpg │ ├── 2017-05-11_01-15-40-.jpg │ ├── 2017-05-11_12-37-02-.jpg │ ├── 2017-05-11_12-37-51-.jpg │ ├── 2017-05-11_12-38-55-.jpg │ ├── 2017-05-11_12-38-59-.jpg │ ├── 2017-05-11_12-46-01-.jpg │ ├── 2017-05-11_12-46-14-.jpg │ ├── 2017-05-11_12-46-17-.jpg │ ├── 2017-05-11_12-46-22-.jpg │ ├── 2017-05-11_12-46-25-.jpg │ └── 2017-05-11_12-47-14-.jpg │ ├── STM8 pinout.png │ ├── en.DM00024550.pdf │ ├── esp8266-14_backside.jpg │ ├── esp8266-14_shieldremoved.jpg │ ├── esp8266-14_shieldremoved2.jpg │ ├── img1.jpg │ ├── img2.jpg │ ├── img3.jpg │ ├── img4.jpg │ ├── pins.jpg │ └── programming_sch.jpg ├── ESP8266AZ7798 ├── ESP8266AZ7798.config ├── ESP8266AZ7798.creator ├── ESP8266AZ7798.files ├── ESP8266AZ7798.includes ├── ESP8266AZ7798.ino ├── grafana.json ├── images │ ├── DSC_0065.JPG │ ├── DSC_0067.JPG │ └── DSC_2057.jpg ├── node-red.json └── upload.bat ├── ESP8266CO2PM25 ├── ESP8266CO2PM25.config ├── ESP8266CO2PM25.creator ├── ESP8266CO2PM25.files ├── ESP8266CO2PM25.includes ├── ESP8266CO2PM25.ino ├── doc │ ├── BST-BME280_DS001-11.pdf │ ├── MH-Z19 CO2 Ver1.0.pdf │ ├── PMS5003_LOGOELE.pdf │ ├── PMS7003 series data manua_English_V2.5.pdf │ ├── PMSA003_cn.pdf │ ├── PMSx003_Schematics.jpg │ ├── PSP126.pdf │ ├── Si1145-46-47.pdf │ ├── TDE2067.pdf │ ├── links.txt │ ├── mh-z19b-co2-ver1_0.pdf │ ├── plantower-pms-7003-sensor-data-sheet.pdf │ ├── plantower-pms5003-manual_v2-3.pdf │ ├── pms5003_pinout.png │ ├── pms7003_cn.pdf │ ├── pms7003pinout.jpg │ └── pms7003pins.jpg └── upload.cmd ├── ESP8266DISABLECPU └── ESP8266DISABLECPU.ino ├── ESP8266EASTRON ├── ESP8266EASTRON.config ├── ESP8266EASTRON.creator ├── ESP8266EASTRON.files ├── ESP8266EASTRON.includes ├── ESP8266EASTRON.ino ├── general.h ├── general.ino ├── images │ ├── DSC_0089.JPG │ ├── DSC_0091.JPG │ ├── DSC_0111.JPG │ ├── DSC_0113.JPG │ ├── DSC_0116.JPG │ ├── DSC_0119.JPG │ ├── DSC_0120.JPG │ ├── DSC_0121.JPG │ ├── DSC_0123.JPG │ ├── DSC_0126.JPG │ ├── DSC_0130.JPG │ ├── DSC_0167.jpg │ ├── DSC_0171.jpg │ ├── DSC_0175.jpg │ ├── d3-MQTT-Topic.png │ └── d3-MQTT-Topic2.png └── upload.cmd ├── README.md ├── create_general_symlink.bat ├── create_lib_symlink.bat ├── images ├── parts │ ├── ESP8266 Serial Wi-Fi ESP-01-1.jpg │ ├── ESP8266 Serial Wi-Fi ESP-01-2.jpg │ ├── ESP8266 Serial Wi-Fi ESP-01-3.jpg │ ├── ESP8266 Serial Wi-Fi ESP-01-4.jpg │ ├── ESP8266 Serial Wi-Fi ESP-01-5.jpg │ ├── ESP8266 Serial Wi-Fi ESP-01-6.jpg │ ├── ESP8266 Serial Wi-Fi ESP-01-7.jpg │ ├── ESP8266 Serial Wi-Fi ESP-01-8.jpg │ ├── Isolated-TTL-to-RS485-module-1.jpg │ ├── Isolated-TTL-to-RS485-module-2.jpg │ ├── Isolated-TTL-to-RS485-module-3.jpg │ ├── Isolated-TTL-to-RS485-module-4.jpg │ ├── Isolated-TTL-to-RS485-module-5.jpg │ ├── Isolated-TTL-to-RS485-module-6.jpg │ ├── Isolated-TTL-to-RS485-module-7.jpg │ ├── TTL To RS485 Adapter-1.jpg │ ├── TTL To RS485 Adapter-2.jpg │ ├── TTL To RS485 Adapter-3.jpg │ ├── TTL To RS485 Adapter-4.jpg │ ├── TTL To RS485 Adapter-5.jpg │ ├── TTL To RS485 Adapter-6.jpg │ ├── WeMos D1 Mini Pro 16MB-1.jpg │ ├── WeMos D1 Mini Pro 16MB-2.jpg │ ├── WeMos D1 Mini Pro 16MB-3.jpg │ └── WeMos D1 Mini Pro 16MB-4.jpg ├── sch_esp8266-AZ7798.png ├── sch_esp8266-AZ7798_nodemcu.png └── sch_esp8266-air-quality.png └── lib ├── BME280.cpp ├── BME280.h ├── HDC1080.cpp ├── HDC1080.h ├── autotimezone.cpp ├── autotimezone.h ├── az7798.cpp ├── az7798.h ├── eastron.h ├── emodbus.cpp ├── emodbus.h ├── etools.cpp ├── etools.h ├── modbuspoll.cpp ├── modbuspoll.h ├── pitimer.cpp ├── pitimer.h ├── pmsx003.cpp ├── pmsx003.h ├── si1145.cpp ├── si1145.h ├── xmqtt.cpp ├── xmqtt.h ├── xparam.cpp └── xparam.h /ESP14MES/ESP14MES.ino: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * ESP-14 weather module. 4 | * temperature/humidity/light/pressure to MQTT 5 | * HDC1080 - temperature + humidity, 6 | * BMP280 - pressure + temperature, 7 | * BH1750FVI - light, 8 | * ESP-14 - cpu module - ESP8266 + STM8003 9 | * 10 | * original repository here https://github.com/merlokk/SmartHome/tree/master/ESP14MES 11 | * (c) Oleg Moiseenko 2017 12 | */ 13 | 14 | 15 | #include 16 | #include // https://github.com/esp8266/Arduino 17 | #include // logger https://github.com/merlokk/xlogger 18 | // my libraries 19 | #include 20 | #include // timers 21 | #include 22 | #include 23 | #include "general.h" 24 | 25 | #define PROGRAM_VERSION "0.5" 26 | 27 | #define DEBUG // enable debugging 28 | #define DEBUG_SERIAL logger 29 | 30 | #define USE_DEEPSLEEP 31 | 32 | // device modbus address 33 | #define MODBUS_ADDRESS 42 34 | #define CONNECTED_OBJ esp14.Connected 35 | 36 | // max deep sleep time ~71 minutes. 37 | #define SLEEP_INTERVAL 60 // 10 sec 38 | #define LIGHTSLEEP_INTERVAL SLEEP_INTERVAL*1000 39 | #define DEEPSLEEP_INTERVAL LIGHTSLEEP_INTERVAL*1000 40 | 41 | // poll 42 | #define MILLIS_TO_POLL 15*1000 //max time to wait for poll 43 | // timers 44 | #define TID_POLL 0x0001 // timer UID for poll 45 | 46 | // LEDs and pins 47 | #define PIN_PGM 0 // programming pin 48 | #define LED1 12 // led 1 TODO: delete leds 49 | #define LED2 13 // led 2 50 | #define LEDON LOW 51 | #define LEDOFF HIGH 52 | 53 | #define DI_PD2 0x04 54 | #define DI_PD3 0x08 55 | #define DI_WIFISETUP DI_PD2 56 | #define DI_NODEEPSLEEP DI_PD3 57 | 58 | // objects 59 | xParam rtcParams; 60 | ModbusPoll esp14; 61 | piTimer ptimer; 62 | 63 | struct Esp14Var { 64 | int vLight; 65 | int vPressure; 66 | float vPrTemp; 67 | float vHumidity; 68 | float vTemp; 69 | uint16_t vDI; 70 | } var {}; 71 | 72 | void PollAndPublish(bool withInit = false) { 73 | // modbus poll 74 | if (withInit) { 75 | esp14.SetDeviceAddress(MODBUS_ADDRESS); 76 | esp14.SetLogger(&logger); 77 | DEBUG_PRINT(F("DeviceType: ")); 78 | DEBUG_PRINTLN(params[F("device_type")]); 79 | esp14.ModbusSetup(params[F("device_type")].c_str()); 80 | 81 | String str; 82 | esp14.getStrModbusConfig(str); 83 | DEBUG_PRINTLN(F("Modbus config:")); 84 | DEBUG_PRINTLN(str); 85 | } 86 | 87 | // poll 88 | esp14.Poll(POLL_INPUT_REGISTERS); 89 | if (!esp14.Connected) { 90 | DEBUG_EPRINTLN(F("Modbus device is not connected.")); 91 | 92 | delay(1500); 93 | // if cant connect to STM8 - do setup 94 | esp14.Poll(POLL_INPUT_REGISTERS); 95 | if (!esp14.Connected) { 96 | DEBUG_EPRINTLN(F("Modbus device is not connected. Start confog portal...")); 97 | wifiSetup(false); 98 | delay(1000); 99 | restart(); 100 | } 101 | 102 | if (!esp14.Connected) 103 | return; 104 | } 105 | 106 | // get values and normalize 107 | var.vLight = esp14.getWordValue(POLL_INPUT_REGISTERS, 0x00); 108 | var.vPressure = esp14.getWordValue(POLL_INPUT_REGISTERS, 0x01) + 60000; 109 | var.vPrTemp = (float)esp14.getWordValue(POLL_INPUT_REGISTERS, 0x02) / 100; 110 | var.vHumidity = (float)esp14.getWordValue(POLL_INPUT_REGISTERS, 0x03) / 10; 111 | var.vTemp = (float)esp14.getWordValue(POLL_INPUT_REGISTERS, 0x04) / 10; 112 | var.vDI = esp14.getWordValue(POLL_INPUT_REGISTERS, 0x05); 113 | 114 | DEBUG_PRINTLN(SF("Temp=") + String(var.vTemp)); 115 | 116 | // wait wifi to connect 117 | int cnt = 0; 118 | while (WiFi.status() != WL_CONNECTED && cnt < 60) { // 6 sec for connect 119 | delay(100); 120 | cnt ++; 121 | } 122 | if (WiFi.status() != WL_CONNECTED) { 123 | DEBUG_WPRINTLN(F("Wifi is not connected.")); 124 | return; 125 | } else { 126 | DEBUG_PRINTLN(SF("Wifi is connected. Timeout ms=") + String(cnt * 100)); 127 | } 128 | 129 | // mqtt init and pub 130 | if (withInit) { 131 | initMQTT("esp14"); 132 | } 133 | mqtt.handle(); 134 | 135 | // publish some system vars 136 | mqtt.BeginPublish(); 137 | mqttPublishRegularState(); 138 | 139 | // publish vars from configuration 140 | if (esp14.Connected) { 141 | mqtt.PublishState(SF("Light"), String(var.vLight)); 142 | mqtt.PublishState(SF("Pressure"), String(var.vPressure)); 143 | mqtt.PublishState(SF("PrTemp"), String(var.vPrTemp)); 144 | mqtt.PublishState(SF("Humidity"), String(var.vHumidity)); 145 | mqtt.PublishState(SF("Temp"), String(var.vTemp)); 146 | mqtt.PublishState(SF("DI"), String(var.vDI, HEX)); 147 | }; 148 | 149 | // ms from start 150 | mqtt.PublishState(SF("Uptimems"), String(millis())); 151 | 152 | // rtc clock 153 | int cyclesCnt = 0; 154 | rtcParams.LoadFromRTC(); 155 | rtcParams.GetParam(SF("Cycles"), cyclesCnt); 156 | cyclesCnt++; 157 | rtcParams.SetParam(SF("Cycles"), cyclesCnt); 158 | rtcParams.SaveToRTC(); 159 | mqtt.PublishState(SF("Cycles"), String(cyclesCnt)); 160 | 161 | mqtt.Commit(); 162 | } 163 | 164 | void setup() { 165 | Serial.setDebugOutput(false); 166 | Serial1.setDebugOutput(true); 167 | Serial.begin(115200); 168 | Serial1.begin(230400); 169 | 170 | pinMode(PIN_PGM, OUTPUT); 171 | digitalWrite(PIN_PGM, 1); 172 | 173 | sprintf(HARDWARE_ID, "%06X", ESP.getChipId()); 174 | bool goSleep = true; 175 | 176 | // serial ports delay 177 | delay(100); 178 | 179 | // start logger 180 | initLogger(); 181 | 182 | initPrintStartDebugInfo(); 183 | 184 | WiFi.hostname(HARDWARE_ID); 185 | 186 | // setup xparam lib 187 | initXParam(); 188 | 189 | // connect to default wifi 190 | WiFi.begin(); 191 | 192 | // delay for rs-485 ready 193 | delay(600); 194 | 195 | PollAndPublish(true); 196 | 197 | // disable sleep 198 | goSleep = (params[F("device_type")].length() != 0) && (params[F("mqtt_server")].length() != 0) && 199 | ((var.vDI & DI_NODEEPSLEEP) != 0); 200 | 201 | digitalWrite(PIN_PGM, 0); 202 | if (goSleep) { 203 | #ifdef USE_DEEPSLEEP 204 | DEBUG_PRINTLN(SF("Time from start ms=") + String(millis())); 205 | DEBUG_PRINTLN(F("Go to deep sleep...")); 206 | ESP.deepSleep(DEEPSLEEP_INTERVAL); 207 | delay(1000); 208 | #else 209 | DEBUG_PRINTLN(F("Go to light sleep...")); 210 | //wifi_station_disconnect(); 211 | //wifi_set_opmode(NULL_MODE); 212 | wifi_set_sleep_type(LIGHT_SLEEP_T); 213 | delay(LIGHTSLEEP_INTERVAL); 214 | wifi_set_sleep_type(NONE_SLEEP_T); 215 | delay(100); 216 | ESP.reset(); 217 | delay(200); 218 | #endif 219 | } 220 | 221 | ptimer.Add(TID_POLL, MILLIS_TO_POLL); 222 | 223 | // ArduinoOTA 224 | setupArduinoOTA(); 225 | } 226 | 227 | void loop() { 228 | digitalWrite(PIN_PGM, 1); 229 | 230 | ArduinoOTA.handle(); 231 | logger.handle(); 232 | 233 | yield(); 234 | 235 | if (ptimer.isArmed(TID_POLL)) { 236 | PollAndPublish(); 237 | 238 | // check if we need wifi setup 239 | if ((var.vDI & DI_WIFISETUP) == 0) { 240 | wifiSetup(false); 241 | delay(1000); 242 | restart(); 243 | } 244 | 245 | ptimer.Reset(TID_POLL); 246 | } 247 | 248 | digitalWrite(PIN_PGM, 0); 249 | 250 | delay(50); 251 | 252 | /* 253 | if ((var.vDI & DI_NODEEPSLEEP) != 0) { 254 | wifi_set_sleep_type(LIGHT_SLEEP_T); 255 | delay(LIGHTSLEEP_INTERVAL); 256 | wifi_set_sleep_type(NONE_SLEEP_T); 257 | delay(100); 258 | }*/ 259 | } 260 | -------------------------------------------------------------------------------- /ESP14MES/docs/ESP-14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/ESP-14.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/ESP-14_V1.0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/ESP-14_V1.0.pdf -------------------------------------------------------------------------------- /ESP14MES/docs/ESP-14_programming sch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/ESP-14_programming sch.png -------------------------------------------------------------------------------- /ESP14MES/docs/ESP14/2017-05-11_01-14-10-.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/ESP14/2017-05-11_01-14-10-.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/ESP14/2017-05-11_01-15-37-.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/ESP14/2017-05-11_01-15-37-.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/ESP14/2017-05-11_01-15-40-.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/ESP14/2017-05-11_01-15-40-.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/ESP14/2017-05-11_12-37-02-.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/ESP14/2017-05-11_12-37-02-.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/ESP14/2017-05-11_12-37-51-.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/ESP14/2017-05-11_12-37-51-.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/ESP14/2017-05-11_12-38-55-.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/ESP14/2017-05-11_12-38-55-.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/ESP14/2017-05-11_12-38-59-.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/ESP14/2017-05-11_12-38-59-.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/ESP14/2017-05-11_12-46-01-.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/ESP14/2017-05-11_12-46-01-.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/ESP14/2017-05-11_12-46-14-.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/ESP14/2017-05-11_12-46-14-.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/ESP14/2017-05-11_12-46-17-.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/ESP14/2017-05-11_12-46-17-.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/ESP14/2017-05-11_12-46-22-.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/ESP14/2017-05-11_12-46-22-.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/ESP14/2017-05-11_12-46-25-.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/ESP14/2017-05-11_12-46-25-.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/ESP14/2017-05-11_12-47-14-.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/ESP14/2017-05-11_12-47-14-.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/STM8 pinout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/STM8 pinout.png -------------------------------------------------------------------------------- /ESP14MES/docs/en.DM00024550.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/en.DM00024550.pdf -------------------------------------------------------------------------------- /ESP14MES/docs/esp8266-14_backside.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/esp8266-14_backside.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/esp8266-14_shieldremoved.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/esp8266-14_shieldremoved.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/esp8266-14_shieldremoved2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/esp8266-14_shieldremoved2.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/img1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/img1.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/img2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/img2.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/img3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/img3.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/img4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/img4.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/pins.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/pins.jpg -------------------------------------------------------------------------------- /ESP14MES/docs/programming_sch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP14MES/docs/programming_sch.jpg -------------------------------------------------------------------------------- /ESP8266AZ7798/ESP8266AZ7798.config: -------------------------------------------------------------------------------- 1 | // Add predefined macros for your project here. For example: 2 | // #define THE_ANSWER 42 3 | -------------------------------------------------------------------------------- /ESP8266AZ7798/ESP8266AZ7798.creator: -------------------------------------------------------------------------------- 1 | [General] 2 | -------------------------------------------------------------------------------- /ESP8266AZ7798/ESP8266AZ7798.files: -------------------------------------------------------------------------------- 1 | ../lib/autotimezone.cpp 2 | ../lib/autotimezone.h 3 | ../lib/az7798.cpp 4 | ../lib/az7798.h 5 | ../lib/pitimer.cpp 6 | ../lib/pitimer.h 7 | ../lib/xmqtt.cpp 8 | ../lib/xmqtt.h 9 | ../lib/xparam.cpp 10 | ../lib/xparam.h 11 | ESP8266AZ7798.ino 12 | general.h 13 | general.ino 14 | -------------------------------------------------------------------------------- /ESP8266AZ7798/ESP8266AZ7798.includes: -------------------------------------------------------------------------------- 1 | . 2 | ..\..\libraries\pubsubclient-2.6\src 3 | ..\..\libraries\WiFiManager 4 | ..\..\libraries\XLogger 5 | ..\..\libraries\Time 6 | ..\..\libraries\NtpClient 7 | ..\..\libraries\NtpClient\src 8 | ..\..\libraries\ArduinoJson 9 | ..\..\libraries\ArduinoJson\src 10 | ..\..\libraries\ 11 | ..\lib 12 | -------------------------------------------------------------------------------- /ESP8266AZ7798/ESP8266AZ7798.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * AZ7798 CO2 datalogger bridge. 3 | * 4 | * AZ7798 -> RS-232 TTL -> esp-8266 -> WiFi -> MQTT 5 | * 6 | * original repository here https://github.com/merlokk/SmartHome/tree/master/ESP8266AZ7788 7 | * (c) Oleg Moiseenko 2017 8 | */ 9 | #include 10 | #include // https://github.com/esp8266/Arduino 11 | #include // logger https://github.com/merlokk/xlogger 12 | // my libraries 13 | #include 14 | #include // timers 15 | #include 16 | #include 17 | #include "general.h" 18 | 19 | #define PROGRAM_VERSION "1.0" 20 | 21 | #define DEBUG // enable debugging 22 | #define DEBUG_SERIAL logger 23 | 24 | #define USE_SOFTWARE_SERIAL 25 | #ifdef USE_SOFTWARE_SERIAL 26 | #include 27 | SoftwareSerial azSerial(13, 15); // RX, TX (13,15) 28 | #endif 29 | 30 | // for "connected" 31 | #define CONNECTED_OBJ az.Connected() 32 | #define MQTT_DEFAULT_TOPIC "az7798" 33 | 34 | // LEDs and pins 35 | #define PIN_PGM 0 // programming pin and jumper 36 | #define LED1 5 // led 1 37 | #define LED2 4 // led 2 38 | #define LEDON LOW 39 | #define LEDOFF HIGH 40 | 41 | az7798 az; 42 | bool sentVersion = false; 43 | 44 | piTimer stimer; 45 | // poll 46 | #define MILLIS_TO_SEND 15*1000 //max time to wait 47 | // timers 48 | #define TID_SEND 0x0001 // timer UID for send data to mqtt 49 | 50 | /////////////////////////////////////////////////////////////////////////// 51 | // Setup() and loop() 52 | /////////////////////////////////////////////////////////////////////////// 53 | void setup() { 54 | Serial.setDebugOutput(false); 55 | Serial1.setDebugOutput(true); 56 | Serial1.begin(115200); // high speed logging port 57 | 58 | #ifdef USE_SOFTWARE_SERIAL 59 | azSerial.begin(9600); 60 | Stream *ser = &azSerial; 61 | #else 62 | Serial.begin(9600); 63 | Serial.swap(); 64 | Stream *ser = &Serial; 65 | #endif 66 | 67 | delay(1000); // wait for serial 68 | 69 | generalSetup(); 70 | 71 | //timer 72 | stimer.Add(TID_SEND, MILLIS_TO_SEND); 73 | 74 | // LED init 75 | pinMode(LED1, OUTPUT); 76 | pinMode(LED2, OUTPUT); 77 | digitalWrite(LED1, LEDOFF); 78 | digitalWrite(LED2, LEDOFF); 79 | 80 | az.begin(ser, &logger); 81 | 82 | // set password in work mode 83 | if (params[F("device_passwd")].length() > 0) 84 | logger.setPassword(params[F("device_passwd")].c_str()); 85 | 86 | ticker.detach(); 87 | digitalWrite(LED1, LEDOFF); 88 | } 89 | 90 | // the loop function runs over and over again forever 91 | void loop() { 92 | digitalWrite(LED1, LEDON); 93 | digitalWrite(LED2, LEDOFF); 94 | if (!generalLoop()) { 95 | digitalWrite(LED1, LEDOFF); 96 | return; 97 | } 98 | 99 | if (az.Connected()) { 100 | digitalWrite(LED2, LEDON); 101 | } 102 | 103 | yield(); 104 | 105 | az.handle(); 106 | 107 | yield(); 108 | 109 | if (stimer.isArmed(TID_SEND)) { 110 | // publish some system vars 111 | mqtt.BeginPublish(); 112 | mqttPublishRegularState(); 113 | 114 | // publish vars 115 | if (az.Connected()) { 116 | if (!sentVersion && az.GetVersion() != "") { 117 | mqtt.PublishState(SF("AZversion"), String(az.GetVersion())); 118 | sentVersion = true; 119 | } 120 | 121 | mqtt.PublishState(SF("Temp"), String(az.GetTemperature())); 122 | mqtt.PublishState(SF("Humidity"), String(az.GetHumidity())); 123 | mqtt.PublishState(SF("CO2"), String(az.GetCO2())); 124 | }; 125 | mqtt.Commit(); 126 | stimer.Reset(TID_SEND); 127 | } 128 | 129 | digitalWrite(LED1, LEDOFF); 130 | delay(200); 131 | } 132 | 133 | 134 | -------------------------------------------------------------------------------- /ESP8266AZ7798/grafana.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_INFLUX", 5 | "label": "influx", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "influxdb", 9 | "pluginName": "InfluxDB" 10 | } 11 | ], 12 | "__requires": [ 13 | { 14 | "type": "panel", 15 | "id": "alertlist", 16 | "name": "Alert List", 17 | "version": "5.0.0" 18 | }, 19 | { 20 | "type": "grafana", 21 | "id": "grafana", 22 | "name": "Grafana", 23 | "version": "5.1.3" 24 | }, 25 | { 26 | "type": "panel", 27 | "id": "grafana-clock-panel", 28 | "name": "Clock", 29 | "version": "0.0.9" 30 | }, 31 | { 32 | "type": "panel", 33 | "id": "graph", 34 | "name": "Graph", 35 | "version": "5.0.0" 36 | }, 37 | { 38 | "type": "datasource", 39 | "id": "influxdb", 40 | "name": "InfluxDB", 41 | "version": "5.0.0" 42 | }, 43 | { 44 | "type": "panel", 45 | "id": "singlestat", 46 | "name": "Singlestat", 47 | "version": "5.0.0" 48 | } 49 | ], 50 | "annotations": { 51 | "list": [ 52 | { 53 | "builtIn": 1, 54 | "datasource": "-- Grafana --", 55 | "enable": true, 56 | "hide": true, 57 | "iconColor": "rgba(0, 211, 255, 1)", 58 | "name": "Annotations & Alerts", 59 | "type": "dashboard" 60 | } 61 | ] 62 | }, 63 | "editable": true, 64 | "gnetId": null, 65 | "graphTooltip": 0, 66 | "id": null, 67 | "links": [], 68 | "panels": [ 69 | { 70 | "bgColor": null, 71 | "clockType": "24 hour", 72 | "countdownSettings": { 73 | "endCountdownTime": "2018-06-14T10:18:00.000Z", 74 | "endText": "00:00:00" 75 | }, 76 | "dateSettings": { 77 | "dateFormat": "DD-MM-YYYY", 78 | "fontSize": "20px", 79 | "fontWeight": "normal", 80 | "showDate": false 81 | }, 82 | "gridPos": { 83 | "h": 3, 84 | "w": 24, 85 | "x": 0, 86 | "y": 0 87 | }, 88 | "id": 6, 89 | "links": [], 90 | "mode": "time", 91 | "offsetFromUtc": null, 92 | "offsetFromUtcMinutes": null, 93 | "timeSettings": { 94 | "customFormat": "HH:mm:ss", 95 | "fontSize": "60px", 96 | "fontWeight": "normal" 97 | }, 98 | "title": "Time", 99 | "type": "grafana-clock-panel" 100 | }, 101 | { 102 | "cacheTimeout": null, 103 | "colorBackground": false, 104 | "colorValue": true, 105 | "colors": [ 106 | "#d44a3a", 107 | "#629e51", 108 | "#d44a3a" 109 | ], 110 | "datasource": "${DS_INFLUX}", 111 | "decimals": 1, 112 | "format": "celsius", 113 | "gauge": { 114 | "maxValue": 50, 115 | "minValue": 1, 116 | "show": true, 117 | "thresholdLabels": false, 118 | "thresholdMarkers": true 119 | }, 120 | "gridPos": { 121 | "h": 6, 122 | "w": 6, 123 | "x": 0, 124 | "y": 3 125 | }, 126 | "id": 9, 127 | "interval": null, 128 | "links": [], 129 | "mappingType": 1, 130 | "mappingTypes": [ 131 | { 132 | "name": "value to text", 133 | "value": 1 134 | }, 135 | { 136 | "name": "range to text", 137 | "value": 2 138 | } 139 | ], 140 | "maxDataPoints": 100, 141 | "nullPointMode": "connected", 142 | "nullText": null, 143 | "postfix": "", 144 | "postfixFontSize": "50%", 145 | "prefix": "", 146 | "prefixFontSize": "50%", 147 | "rangeMaps": [ 148 | { 149 | "from": "null", 150 | "text": "N/A", 151 | "to": "null" 152 | } 153 | ], 154 | "sparkline": { 155 | "fillColor": "rgba(31, 118, 189, 0.18)", 156 | "full": false, 157 | "lineColor": "rgb(31, 120, 193)", 158 | "show": true 159 | }, 160 | "tableColumn": "", 161 | "targets": [ 162 | { 163 | "alias": "Temperature", 164 | "groupBy": [ 165 | { 166 | "params": [ 167 | "$__interval" 168 | ], 169 | "type": "time" 170 | }, 171 | { 172 | "params": [ 173 | "null" 174 | ], 175 | "type": "fill" 176 | } 177 | ], 178 | "measurement": "mes", 179 | "orderByTime": "ASC", 180 | "policy": "default", 181 | "refId": "A", 182 | "resultFormat": "time_series", 183 | "select": [ 184 | [ 185 | { 186 | "params": [ 187 | "temperature" 188 | ], 189 | "type": "field" 190 | }, 191 | { 192 | "params": [], 193 | "type": "mean" 194 | } 195 | ] 196 | ], 197 | "tags": [] 198 | } 199 | ], 200 | "thresholds": "20,29", 201 | "title": "Temperature", 202 | "type": "singlestat", 203 | "valueFontSize": "80%", 204 | "valueMaps": [ 205 | { 206 | "op": "=", 207 | "text": "N/A", 208 | "value": "null" 209 | } 210 | ], 211 | "valueName": "current" 212 | }, 213 | { 214 | "cacheTimeout": null, 215 | "colorBackground": false, 216 | "colorValue": true, 217 | "colors": [ 218 | "#d44a3a", 219 | "#299c46", 220 | "#d44a3a" 221 | ], 222 | "datasource": "${DS_INFLUX}", 223 | "decimals": 1, 224 | "format": "humidity", 225 | "gauge": { 226 | "maxValue": 100, 227 | "minValue": 0, 228 | "show": true, 229 | "thresholdLabels": false, 230 | "thresholdMarkers": true 231 | }, 232 | "gridPos": { 233 | "h": 6, 234 | "w": 6, 235 | "x": 6, 236 | "y": 3 237 | }, 238 | "id": 10, 239 | "interval": null, 240 | "links": [], 241 | "mappingType": 1, 242 | "mappingTypes": [ 243 | { 244 | "name": "value to text", 245 | "value": 1 246 | }, 247 | { 248 | "name": "range to text", 249 | "value": 2 250 | } 251 | ], 252 | "maxDataPoints": 100, 253 | "nullPointMode": "connected", 254 | "nullText": null, 255 | "postfix": "", 256 | "postfixFontSize": "50%", 257 | "prefix": "", 258 | "prefixFontSize": "50%", 259 | "rangeMaps": [ 260 | { 261 | "from": "null", 262 | "text": "N/A", 263 | "to": "null" 264 | } 265 | ], 266 | "sparkline": { 267 | "fillColor": "rgba(31, 118, 189, 0.18)", 268 | "full": false, 269 | "lineColor": "rgb(31, 120, 193)", 270 | "show": true 271 | }, 272 | "tableColumn": "", 273 | "targets": [ 274 | { 275 | "alias": "Humidity", 276 | "groupBy": [ 277 | { 278 | "params": [ 279 | "$__interval" 280 | ], 281 | "type": "time" 282 | }, 283 | { 284 | "params": [ 285 | "null" 286 | ], 287 | "type": "fill" 288 | } 289 | ], 290 | "measurement": "mes", 291 | "orderByTime": "ASC", 292 | "policy": "default", 293 | "refId": "A", 294 | "resultFormat": "time_series", 295 | "select": [ 296 | [ 297 | { 298 | "params": [ 299 | "humidity" 300 | ], 301 | "type": "field" 302 | }, 303 | { 304 | "params": [], 305 | "type": "mean" 306 | } 307 | ] 308 | ], 309 | "tags": [] 310 | } 311 | ], 312 | "thresholds": "30,60", 313 | "title": "Humidity", 314 | "type": "singlestat", 315 | "valueFontSize": "80%", 316 | "valueMaps": [ 317 | { 318 | "op": "=", 319 | "text": "N/A", 320 | "value": "null" 321 | } 322 | ], 323 | "valueName": "current" 324 | }, 325 | { 326 | "cacheTimeout": null, 327 | "colorBackground": false, 328 | "colorValue": true, 329 | "colors": [ 330 | "#299c46", 331 | "rgba(237, 129, 40, 0.89)", 332 | "#d44a3a" 333 | ], 334 | "datasource": "${DS_INFLUX}", 335 | "format": "ppm", 336 | "gauge": { 337 | "maxValue": 4000, 338 | "minValue": 1, 339 | "show": true, 340 | "thresholdLabels": false, 341 | "thresholdMarkers": true 342 | }, 343 | "gridPos": { 344 | "h": 6, 345 | "w": 6, 346 | "x": 12, 347 | "y": 3 348 | }, 349 | "id": 11, 350 | "interval": null, 351 | "links": [], 352 | "mappingType": 1, 353 | "mappingTypes": [ 354 | { 355 | "name": "value to text", 356 | "value": 1 357 | }, 358 | { 359 | "name": "range to text", 360 | "value": 2 361 | } 362 | ], 363 | "maxDataPoints": 100, 364 | "nullPointMode": "connected", 365 | "nullText": null, 366 | "postfix": "", 367 | "postfixFontSize": "50%", 368 | "prefix": "", 369 | "prefixFontSize": "50%", 370 | "rangeMaps": [ 371 | { 372 | "from": "null", 373 | "text": "N/A", 374 | "to": "null" 375 | } 376 | ], 377 | "sparkline": { 378 | "fillColor": "rgba(31, 118, 189, 0.18)", 379 | "full": false, 380 | "lineColor": "rgb(31, 120, 193)", 381 | "show": true 382 | }, 383 | "tableColumn": "", 384 | "targets": [ 385 | { 386 | "alias": "CO2", 387 | "groupBy": [ 388 | { 389 | "params": [ 390 | "$__interval" 391 | ], 392 | "type": "time" 393 | }, 394 | { 395 | "params": [ 396 | "null" 397 | ], 398 | "type": "fill" 399 | } 400 | ], 401 | "measurement": "mes", 402 | "orderByTime": "ASC", 403 | "policy": "default", 404 | "refId": "A", 405 | "resultFormat": "time_series", 406 | "select": [ 407 | [ 408 | { 409 | "params": [ 410 | "co2" 411 | ], 412 | "type": "field" 413 | }, 414 | { 415 | "params": [], 416 | "type": "mean" 417 | } 418 | ] 419 | ], 420 | "tags": [] 421 | } 422 | ], 423 | "thresholds": "1000,2000", 424 | "title": "CO2", 425 | "type": "singlestat", 426 | "valueFontSize": "80%", 427 | "valueMaps": [ 428 | { 429 | "op": "=", 430 | "text": "N/A", 431 | "value": "null" 432 | } 433 | ], 434 | "valueName": "current" 435 | }, 436 | { 437 | "gridPos": { 438 | "h": 13, 439 | "w": 6, 440 | "x": 18, 441 | "y": 3 442 | }, 443 | "id": 13, 444 | "limit": 10, 445 | "links": [], 446 | "onlyAlertsOnDashboard": false, 447 | "show": "current", 448 | "sortOrder": 1, 449 | "stateFilter": [], 450 | "title": "Alerts", 451 | "type": "alertlist" 452 | }, 453 | { 454 | "alert": { 455 | "conditions": [ 456 | { 457 | "evaluator": { 458 | "params": [ 459 | 1500 460 | ], 461 | "type": "gt" 462 | }, 463 | "operator": { 464 | "type": "and" 465 | }, 466 | "query": { 467 | "params": [ 468 | "A", 469 | "2m", 470 | "now" 471 | ] 472 | }, 473 | "reducer": { 474 | "params": [], 475 | "type": "last" 476 | }, 477 | "type": "query" 478 | } 479 | ], 480 | "executionErrorState": "alerting", 481 | "frequency": "60s", 482 | "handler": 1, 483 | "name": "CO2 alert", 484 | "noDataState": "no_data", 485 | "notifications": [] 486 | }, 487 | "aliasColors": {}, 488 | "bars": false, 489 | "dashLength": 10, 490 | "dashes": false, 491 | "datasource": "${DS_INFLUX}", 492 | "fill": 1, 493 | "gridPos": { 494 | "h": 7, 495 | "w": 18, 496 | "x": 0, 497 | "y": 9 498 | }, 499 | "hideTimeOverride": false, 500 | "id": 2, 501 | "legend": { 502 | "alignAsTable": false, 503 | "avg": false, 504 | "current": true, 505 | "hideEmpty": false, 506 | "hideZero": false, 507 | "max": false, 508 | "min": false, 509 | "rightSide": false, 510 | "show": true, 511 | "total": false, 512 | "values": true 513 | }, 514 | "lines": true, 515 | "linewidth": 2, 516 | "links": [], 517 | "nullPointMode": "null", 518 | "percentage": false, 519 | "pointradius": 0.5, 520 | "points": false, 521 | "renderer": "flot", 522 | "seriesOverrides": [ 523 | { 524 | "alias": "Humidity", 525 | "yaxis": 2 526 | }, 527 | { 528 | "alias": "Temperature", 529 | "yaxis": 2 530 | } 531 | ], 532 | "spaceLength": 10, 533 | "stack": false, 534 | "steppedLine": false, 535 | "targets": [ 536 | { 537 | "alias": "CO2", 538 | "groupBy": [ 539 | { 540 | "params": [ 541 | "$__interval" 542 | ], 543 | "type": "time" 544 | }, 545 | { 546 | "params": [ 547 | "null" 548 | ], 549 | "type": "fill" 550 | } 551 | ], 552 | "hide": false, 553 | "measurement": "mes", 554 | "orderByTime": "ASC", 555 | "policy": "default", 556 | "refId": "A", 557 | "resultFormat": "time_series", 558 | "select": [ 559 | [ 560 | { 561 | "params": [ 562 | "co2" 563 | ], 564 | "type": "field" 565 | }, 566 | { 567 | "params": [], 568 | "type": "mean" 569 | } 570 | ] 571 | ], 572 | "tags": [] 573 | }, 574 | { 575 | "alias": "Humidity", 576 | "groupBy": [ 577 | { 578 | "params": [ 579 | "$__interval" 580 | ], 581 | "type": "time" 582 | }, 583 | { 584 | "params": [ 585 | "null" 586 | ], 587 | "type": "fill" 588 | } 589 | ], 590 | "measurement": "mes", 591 | "orderByTime": "ASC", 592 | "policy": "default", 593 | "refId": "C", 594 | "resultFormat": "time_series", 595 | "select": [ 596 | [ 597 | { 598 | "params": [ 599 | "humidity" 600 | ], 601 | "type": "field" 602 | }, 603 | { 604 | "params": [], 605 | "type": "mean" 606 | } 607 | ] 608 | ], 609 | "tags": [] 610 | } 611 | ], 612 | "thresholds": [ 613 | { 614 | "colorMode": "critical", 615 | "fill": true, 616 | "line": true, 617 | "op": "gt", 618 | "value": 1500 619 | } 620 | ], 621 | "timeFrom": null, 622 | "timeShift": null, 623 | "title": "CO2-Humidity", 624 | "tooltip": { 625 | "shared": true, 626 | "sort": 0, 627 | "value_type": "individual" 628 | }, 629 | "transparent": false, 630 | "type": "graph", 631 | "xaxis": { 632 | "buckets": null, 633 | "mode": "time", 634 | "name": null, 635 | "show": true, 636 | "values": [] 637 | }, 638 | "yaxes": [ 639 | { 640 | "format": "ppm", 641 | "label": "CO2", 642 | "logBase": 1, 643 | "max": null, 644 | "min": null, 645 | "show": true 646 | }, 647 | { 648 | "decimals": null, 649 | "format": "humidity", 650 | "label": "", 651 | "logBase": 1, 652 | "max": null, 653 | "min": null, 654 | "show": true 655 | } 656 | ], 657 | "yaxis": { 658 | "align": false, 659 | "alignLevel": 2 660 | } 661 | }, 662 | { 663 | "aliasColors": {}, 664 | "bars": false, 665 | "dashLength": 10, 666 | "dashes": false, 667 | "datasource": "${DS_INFLUX}", 668 | "fill": 1, 669 | "gridPos": { 670 | "h": 7, 671 | "w": 18, 672 | "x": 0, 673 | "y": 16 674 | }, 675 | "hideTimeOverride": false, 676 | "id": 7, 677 | "legend": { 678 | "avg": false, 679 | "current": true, 680 | "hideEmpty": false, 681 | "hideZero": false, 682 | "max": false, 683 | "min": false, 684 | "show": true, 685 | "total": false, 686 | "values": true 687 | }, 688 | "lines": true, 689 | "linewidth": 2, 690 | "links": [], 691 | "nullPointMode": "null", 692 | "percentage": false, 693 | "pointradius": 0.5, 694 | "points": false, 695 | "renderer": "flot", 696 | "seriesOverrides": [ 697 | { 698 | "alias": "Humidity", 699 | "yaxis": 2 700 | } 701 | ], 702 | "spaceLength": 10, 703 | "stack": false, 704 | "steppedLine": false, 705 | "targets": [ 706 | { 707 | "alias": "Temperature", 708 | "groupBy": [ 709 | { 710 | "params": [ 711 | "$__interval" 712 | ], 713 | "type": "time" 714 | }, 715 | { 716 | "params": [ 717 | "null" 718 | ], 719 | "type": "fill" 720 | } 721 | ], 722 | "measurement": "mes", 723 | "orderByTime": "ASC", 724 | "policy": "default", 725 | "refId": "C", 726 | "resultFormat": "time_series", 727 | "select": [ 728 | [ 729 | { 730 | "params": [ 731 | "temperature" 732 | ], 733 | "type": "field" 734 | }, 735 | { 736 | "params": [], 737 | "type": "mean" 738 | } 739 | ] 740 | ], 741 | "tags": [] 742 | }, 743 | { 744 | "alias": "Humidity", 745 | "groupBy": [ 746 | { 747 | "params": [ 748 | "$__interval" 749 | ], 750 | "type": "time" 751 | }, 752 | { 753 | "params": [ 754 | "null" 755 | ], 756 | "type": "fill" 757 | } 758 | ], 759 | "measurement": "mes", 760 | "orderByTime": "ASC", 761 | "policy": "default", 762 | "refId": "A", 763 | "resultFormat": "time_series", 764 | "select": [ 765 | [ 766 | { 767 | "params": [ 768 | "humidity" 769 | ], 770 | "type": "field" 771 | }, 772 | { 773 | "params": [], 774 | "type": "mean" 775 | } 776 | ] 777 | ], 778 | "tags": [] 779 | } 780 | ], 781 | "thresholds": [], 782 | "timeFrom": null, 783 | "timeShift": null, 784 | "title": "Temperature-Humidity", 785 | "tooltip": { 786 | "shared": true, 787 | "sort": 0, 788 | "value_type": "individual" 789 | }, 790 | "transparent": false, 791 | "type": "graph", 792 | "xaxis": { 793 | "buckets": null, 794 | "mode": "time", 795 | "name": null, 796 | "show": true, 797 | "values": [] 798 | }, 799 | "yaxes": [ 800 | { 801 | "format": "celsius", 802 | "label": "Temperature", 803 | "logBase": 1, 804 | "max": null, 805 | "min": null, 806 | "show": true 807 | }, 808 | { 809 | "decimals": null, 810 | "format": "humidity", 811 | "label": "", 812 | "logBase": 1, 813 | "max": null, 814 | "min": null, 815 | "show": true 816 | } 817 | ], 818 | "yaxis": { 819 | "align": false, 820 | "alignLevel": 2 821 | } 822 | } 823 | ], 824 | "refresh": "10s", 825 | "schemaVersion": 16, 826 | "style": "dark", 827 | "tags": [], 828 | "templating": { 829 | "list": [] 830 | }, 831 | "time": { 832 | "from": "now-6h", 833 | "to": "now" 834 | }, 835 | "timepicker": { 836 | "refresh_intervals": [ 837 | "10s", 838 | "30s", 839 | "1m", 840 | "5m" 841 | ], 842 | "time_options": [ 843 | "5m", 844 | "15m", 845 | "1h", 846 | "6h", 847 | "12h", 848 | "24h", 849 | "2d", 850 | "7d", 851 | "30d" 852 | ] 853 | }, 854 | "timezone": "", 855 | "title": "SW climate", 856 | "uid": "knRaieSik", 857 | "version": 29 858 | } -------------------------------------------------------------------------------- /ESP8266AZ7798/images/DSC_0065.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266AZ7798/images/DSC_0065.JPG -------------------------------------------------------------------------------- /ESP8266AZ7798/images/DSC_0067.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266AZ7798/images/DSC_0067.JPG -------------------------------------------------------------------------------- /ESP8266AZ7798/images/DSC_2057.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266AZ7798/images/DSC_2057.jpg -------------------------------------------------------------------------------- /ESP8266AZ7798/node-red.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "defeb2cd.9607a", 4 | "type": "mqtt in", 5 | "z": "cf46c3c9.0721b", 6 | "name": "LastConnectedDateTime", 7 | "topic": "CO2/LastConnectedDateTime", 8 | "qos": "0", 9 | "broker": "ebb24325.efd6e", 10 | "x": 170, 11 | "y": 160, 12 | "wires": [ 13 | [ 14 | "8d0a50d8.c29d2" 15 | ] 16 | ] 17 | }, 18 | { 19 | "id": "8d0a50d8.c29d2", 20 | "type": "ui_text", 21 | "z": "cf46c3c9.0721b", 22 | "group": "477dfe8d.9ccaa", 23 | "order": 5, 24 | "width": "0", 25 | "height": "0", 26 | "name": "", 27 | "label": "Last connect", 28 | "format": "{{msg.payload}}", 29 | "layout": "col-center", 30 | "x": 370, 31 | "y": 160, 32 | "wires": [] 33 | }, 34 | { 35 | "id": "e62791b.f06d77", 36 | "type": "mqtt in", 37 | "z": "cf46c3c9.0721b", 38 | "name": "Temp", 39 | "topic": "CO2/Temp", 40 | "qos": "0", 41 | "broker": "ebb24325.efd6e", 42 | "x": 110, 43 | "y": 340, 44 | "wires": [ 45 | [ 46 | "9e36eed0.1ab2a", 47 | "e23661e2.184a5", 48 | "d455c064.c61c4" 49 | ] 50 | ] 51 | }, 52 | { 53 | "id": "9e36eed0.1ab2a", 54 | "type": "ui_text", 55 | "z": "cf46c3c9.0721b", 56 | "group": "477dfe8d.9ccaa", 57 | "order": 9, 58 | "width": "2", 59 | "height": "1", 60 | "name": "", 61 | "label": "Temp", 62 | "format": "{{msg.payload}}", 63 | "layout": "col-center", 64 | "x": 350, 65 | "y": 340, 66 | "wires": [] 67 | }, 68 | { 69 | "id": "1eedb10b.d79bff", 70 | "type": "mqtt in", 71 | "z": "cf46c3c9.0721b", 72 | "name": "Humidity", 73 | "topic": "CO2/Humidity", 74 | "qos": "0", 75 | "broker": "ebb24325.efd6e", 76 | "x": 120, 77 | "y": 440, 78 | "wires": [ 79 | [ 80 | "1b851d81.e65272", 81 | "1be01ab.a9945e5", 82 | "d455c064.c61c4" 83 | ] 84 | ] 85 | }, 86 | { 87 | "id": "1b851d81.e65272", 88 | "type": "ui_text", 89 | "z": "cf46c3c9.0721b", 90 | "group": "477dfe8d.9ccaa", 91 | "order": 10, 92 | "width": "2", 93 | "height": "1", 94 | "name": "", 95 | "label": "Humidity", 96 | "format": "{{msg.payload}}", 97 | "layout": "col-center", 98 | "x": 360, 99 | "y": 440, 100 | "wires": [] 101 | }, 102 | { 103 | "id": "72a271d5.e9131", 104 | "type": "mqtt in", 105 | "z": "cf46c3c9.0721b", 106 | "name": "LastSeenDateTime", 107 | "topic": "CO2/LastSeenDateTime", 108 | "qos": "0", 109 | "broker": "ebb24325.efd6e", 110 | "x": 150, 111 | "y": 100, 112 | "wires": [ 113 | [ 114 | "cf70086e.79e208" 115 | ] 116 | ] 117 | }, 118 | { 119 | "id": "cf70086e.79e208", 120 | "type": "ui_text", 121 | "z": "cf46c3c9.0721b", 122 | "group": "477dfe8d.9ccaa", 123 | "order": 4, 124 | "width": "0", 125 | "height": "0", 126 | "name": "", 127 | "label": "Last seen", 128 | "format": "{{msg.payload}}", 129 | "layout": "col-center", 130 | "x": 360, 131 | "y": 100, 132 | "wires": [] 133 | }, 134 | { 135 | "id": "3c0baa32.149596", 136 | "type": "mqtt in", 137 | "z": "cf46c3c9.0721b", 138 | "name": "Uptime", 139 | "topic": "CO2/Uptime", 140 | "qos": "0", 141 | "broker": "ebb24325.efd6e", 142 | "x": 110, 143 | "y": 40, 144 | "wires": [ 145 | [ 146 | "fc877d01.9d97f" 147 | ] 148 | ] 149 | }, 150 | { 151 | "id": "fc877d01.9d97f", 152 | "type": "ui_text", 153 | "z": "cf46c3c9.0721b", 154 | "group": "477dfe8d.9ccaa", 155 | "order": 6, 156 | "width": "2", 157 | "height": "1", 158 | "name": "", 159 | "label": "Uptime", 160 | "format": "{{msg.payload}}", 161 | "layout": "col-center", 162 | "x": 360, 163 | "y": 40, 164 | "wires": [] 165 | }, 166 | { 167 | "id": "d141a8c8.9f2ce8", 168 | "type": "mqtt in", 169 | "z": "cf46c3c9.0721b", 170 | "name": "CO2", 171 | "topic": "CO2/CO2", 172 | "qos": "0", 173 | "broker": "ebb24325.efd6e", 174 | "x": 110, 175 | "y": 540, 176 | "wires": [ 177 | [ 178 | "6995d28f.208d0c", 179 | "d6a20dcb.3fd3b", 180 | "1c110754.2cdcf9", 181 | "d455c064.c61c4" 182 | ] 183 | ] 184 | }, 185 | { 186 | "id": "6995d28f.208d0c", 187 | "type": "ui_text", 188 | "z": "cf46c3c9.0721b", 189 | "group": "477dfe8d.9ccaa", 190 | "order": 11, 191 | "width": "2", 192 | "height": "1", 193 | "name": "", 194 | "label": "CO2", 195 | "format": " 1000)?'orange':'green'}}>{{msg.payload}}", 196 | "layout": "col-center", 197 | "x": 350, 198 | "y": 540, 199 | "wires": [] 200 | }, 201 | { 202 | "id": "25289fc3.93cb1", 203 | "type": "mqtt in", 204 | "z": "cf46c3c9.0721b", 205 | "name": "VCC", 206 | "topic": "CO2/VCC", 207 | "qos": "0", 208 | "broker": "ebb24325.efd6e", 209 | "x": 110, 210 | "y": 220, 211 | "wires": [ 212 | [ 213 | "63d4ee02.4f7be", 214 | "d455c064.c61c4" 215 | ] 216 | ] 217 | }, 218 | { 219 | "id": "63d4ee02.4f7be", 220 | "type": "ui_text", 221 | "z": "cf46c3c9.0721b", 222 | "group": "477dfe8d.9ccaa", 223 | "order": 7, 224 | "width": "2", 225 | "height": "1", 226 | "name": "", 227 | "label": "VCC", 228 | "format": "{{msg.payload}}", 229 | "layout": "col-center", 230 | "x": 350, 231 | "y": 220, 232 | "wires": [] 233 | }, 234 | { 235 | "id": "d6a20dcb.3fd3b", 236 | "type": "ui_chart", 237 | "z": "cf46c3c9.0721b", 238 | "name": "", 239 | "group": "8d2ce4c4.fa6338", 240 | "order": 3, 241 | "width": 0, 242 | "height": 0, 243 | "label": "CO2", 244 | "chartType": "line", 245 | "legend": "false", 246 | "xformat": "HH:mm:ss", 247 | "interpolate": "linear", 248 | "nodata": "", 249 | "dot": false, 250 | "ymin": "", 251 | "ymax": "", 252 | "removeOlder": "4", 253 | "removeOlderPoints": "", 254 | "removeOlderUnit": "3600", 255 | "cutout": 0, 256 | "useOneColor": false, 257 | "colors": [ 258 | "#1f77b4", 259 | "#aec7e8", 260 | "#ff7f0e", 261 | "#2ca02c", 262 | "#98df8a", 263 | "#d62728", 264 | "#ff9896", 265 | "#9467bd", 266 | "#c5b0d5" 267 | ], 268 | "useOldStyle": false, 269 | "x": 350, 270 | "y": 580, 271 | "wires": [ 272 | [], 273 | [] 274 | ] 275 | }, 276 | { 277 | "id": "1be01ab.a9945e5", 278 | "type": "ui_chart", 279 | "z": "cf46c3c9.0721b", 280 | "name": "", 281 | "group": "8d2ce4c4.fa6338", 282 | "order": 2, 283 | "width": 0, 284 | "height": 0, 285 | "label": "Humidity", 286 | "chartType": "line", 287 | "legend": "false", 288 | "xformat": "HH:mm:ss", 289 | "interpolate": "linear", 290 | "nodata": "", 291 | "dot": false, 292 | "ymin": "", 293 | "ymax": "", 294 | "removeOlder": "4", 295 | "removeOlderPoints": "", 296 | "removeOlderUnit": "3600", 297 | "cutout": 0, 298 | "useOneColor": false, 299 | "colors": [ 300 | "#1f77b4", 301 | "#aec7e8", 302 | "#ff7f0e", 303 | "#2ca02c", 304 | "#98df8a", 305 | "#d62728", 306 | "#ff9896", 307 | "#9467bd", 308 | "#c5b0d5" 309 | ], 310 | "useOldStyle": false, 311 | "x": 360, 312 | "y": 480, 313 | "wires": [ 314 | [], 315 | [] 316 | ] 317 | }, 318 | { 319 | "id": "e23661e2.184a5", 320 | "type": "ui_chart", 321 | "z": "cf46c3c9.0721b", 322 | "name": "", 323 | "group": "8d2ce4c4.fa6338", 324 | "order": 1, 325 | "width": 0, 326 | "height": 0, 327 | "label": "Temperature", 328 | "chartType": "line", 329 | "legend": "false", 330 | "xformat": "HH:mm:ss", 331 | "interpolate": "linear", 332 | "nodata": "", 333 | "dot": false, 334 | "ymin": "", 335 | "ymax": "", 336 | "removeOlder": "4", 337 | "removeOlderPoints": "", 338 | "removeOlderUnit": "3600", 339 | "cutout": 0, 340 | "useOneColor": false, 341 | "colors": [ 342 | "#1f77b4", 343 | "#aec7e8", 344 | "#ff7f0e", 345 | "#2ca02c", 346 | "#98df8a", 347 | "#d62728", 348 | "#ff9896", 349 | "#9467bd", 350 | "#c5b0d5" 351 | ], 352 | "useOldStyle": false, 353 | "x": 370, 354 | "y": 380, 355 | "wires": [ 356 | [], 357 | [] 358 | ] 359 | }, 360 | { 361 | "id": "1c110754.2cdcf9", 362 | "type": "ui_gauge", 363 | "z": "cf46c3c9.0721b", 364 | "name": "", 365 | "group": "477dfe8d.9ccaa", 366 | "order": 12, 367 | "width": 0, 368 | "height": 0, 369 | "gtype": "donut", 370 | "title": "CO2", 371 | "label": "units", 372 | "format": "{{value}}", 373 | "min": 0, 374 | "max": "4000", 375 | "colors": [ 376 | "#00b500", 377 | "#e6e600", 378 | "#ca3838" 379 | ], 380 | "seg1": "1200", 381 | "seg2": "3000", 382 | "x": 350, 383 | "y": 620, 384 | "wires": [] 385 | }, 386 | { 387 | "id": "a0a76e36.36a8", 388 | "type": "influxdb batch", 389 | "z": "cf46c3c9.0721b", 390 | "influxdb": "ff84a2bf.b8dd5", 391 | "precision": "s", 392 | "retentionPolicy": "", 393 | "name": "", 394 | "x": 880, 395 | "y": 660, 396 | "wires": [] 397 | }, 398 | { 399 | "id": "a1ce8f08.81dda", 400 | "type": "mqtt in", 401 | "z": "cf46c3c9.0721b", 402 | "name": "Connected", 403 | "topic": "CO2/Connected", 404 | "qos": "0", 405 | "broker": "ebb24325.efd6e", 406 | "x": 120, 407 | "y": 280, 408 | "wires": [ 409 | [ 410 | "5a826667.b98e88", 411 | "d455c064.c61c4" 412 | ] 413 | ] 414 | }, 415 | { 416 | "id": "5a826667.b98e88", 417 | "type": "ui_text", 418 | "z": "cf46c3c9.0721b", 419 | "group": "477dfe8d.9ccaa", 420 | "order": 8, 421 | "width": "2", 422 | "height": "1", 423 | "name": "", 424 | "label": "Connected", 425 | "format": "{{msg.payload}}", 426 | "layout": "col-center", 427 | "x": 370, 428 | "y": 280, 429 | "wires": [] 430 | }, 431 | { 432 | "id": "fe929f12.e3a4c", 433 | "type": "mqtt in", 434 | "z": "cf46c3c9.0721b", 435 | "name": "DeviceType", 436 | "topic": "CO2/DeviceType", 437 | "qos": "0", 438 | "broker": "ebb24325.efd6e", 439 | "x": 570, 440 | "y": 160, 441 | "wires": [ 442 | [ 443 | "f6072e29.c47ff" 444 | ] 445 | ] 446 | }, 447 | { 448 | "id": "f6072e29.c47ff", 449 | "type": "ui_text", 450 | "z": "cf46c3c9.0721b", 451 | "group": "477dfe8d.9ccaa", 452 | "order": 3, 453 | "width": "2", 454 | "height": "1", 455 | "name": "", 456 | "label": "DeviceType", 457 | "format": "{{msg.payload}}", 458 | "layout": "col-center", 459 | "x": 810, 460 | "y": 160, 461 | "wires": [] 462 | }, 463 | { 464 | "id": "f4ea4604.c92fa8", 465 | "type": "mqtt in", 466 | "z": "cf46c3c9.0721b", 467 | "name": "Version", 468 | "topic": "CO2/Version", 469 | "qos": "0", 470 | "broker": "ebb24325.efd6e", 471 | "x": 550, 472 | "y": 100, 473 | "wires": [ 474 | [ 475 | "6badfce7.70eea4" 476 | ] 477 | ] 478 | }, 479 | { 480 | "id": "6badfce7.70eea4", 481 | "type": "ui_text", 482 | "z": "cf46c3c9.0721b", 483 | "group": "477dfe8d.9ccaa", 484 | "order": 2, 485 | "width": "2", 486 | "height": "1", 487 | "name": "", 488 | "label": "Version", 489 | "format": "{{msg.payload}}", 490 | "layout": "col-center", 491 | "x": 800, 492 | "y": 100, 493 | "wires": [] 494 | }, 495 | { 496 | "id": "405d91f.eecf27", 497 | "type": "mqtt in", 498 | "z": "cf46c3c9.0721b", 499 | "name": "MAC", 500 | "topic": "CO2/MAC", 501 | "qos": "0", 502 | "broker": "ebb24325.efd6e", 503 | "x": 550, 504 | "y": 40, 505 | "wires": [ 506 | [ 507 | "280b4921.19e1d6" 508 | ] 509 | ] 510 | }, 511 | { 512 | "id": "280b4921.19e1d6", 513 | "type": "ui_text", 514 | "z": "cf46c3c9.0721b", 515 | "group": "477dfe8d.9ccaa", 516 | "order": 1, 517 | "width": "2", 518 | "height": "1", 519 | "name": "", 520 | "label": "MAC", 521 | "format": "{{msg.payload}}", 522 | "layout": "col-center", 523 | "x": 790, 524 | "y": 40, 525 | "wires": [] 526 | }, 527 | { 528 | "id": "d455c064.c61c4", 529 | "type": "function", 530 | "z": "cf46c3c9.0721b", 531 | "name": "insert script", 532 | "func": "const topics = [\n{t: 'str', topic: 'CO2/Connected', area: 'az', vname: 'connected'},\n{t: 'int', topic: 'CO2/VCC', area: 'az', vname: 'vcc'},\n{t: 'float', topic: 'CO2/Temp', area: 'az', vname: 'temperature'},\n{t: 'float', topic: 'CO2/Humidity', area: 'az', vname: 'humidity'},\n{t: 'int', topic: 'CO2/CO2', area: 'az', vname: 'co2'}\n]\n\nfor (var t in topics) {\n if (topics[t].topic == msg.topic) {\n var value = msg.payload;\n msg.payload = [{\n measurement: 'mes',\n tags: {'area': topics[t].area},\n fields: {}\n }];\n switch (topics[t].t) {\n case 'int': msg.payload[0].fields[topics[t].vname] = parseInt(value); break;\n case 'float': msg.payload[0].fields[topics[t].vname] = parseFloat(value); break;\n default: msg.payload[0].fields[topics[t].vname] = value;\n }\n return msg;\n }\n}\n\nreturn ", 533 | "outputs": 1, 534 | "noerr": 0, 535 | "x": 650, 536 | "y": 660, 537 | "wires": [ 538 | [ 539 | "a0a76e36.36a8", 540 | "1355607b.0a2d3" 541 | ] 542 | ] 543 | }, 544 | { 545 | "id": "1355607b.0a2d3", 546 | "type": "debug", 547 | "z": "cf46c3c9.0721b", 548 | "name": "", 549 | "active": false, 550 | "tosidebar": true, 551 | "console": false, 552 | "tostatus": false, 553 | "complete": "false", 554 | "x": 860, 555 | "y": 720, 556 | "wires": [] 557 | }, 558 | { 559 | "id": "ebb24325.efd6e", 560 | "type": "mqtt-broker", 561 | "z": "", 562 | "name": "local mqtt", 563 | "broker": "18.0.0.11", 564 | "port": "1883", 565 | "clientid": "node-red", 566 | "usetls": false, 567 | "compatmode": true, 568 | "keepalive": "60", 569 | "cleansession": true, 570 | "birthTopic": "", 571 | "birthQos": "0", 572 | "birthPayload": "", 573 | "closeTopic": "", 574 | "closeQos": "0", 575 | "closePayload": "", 576 | "willTopic": "", 577 | "willQos": "0", 578 | "willPayload": "" 579 | }, 580 | { 581 | "id": "477dfe8d.9ccaa", 582 | "type": "ui_group", 583 | "z": "", 584 | "name": "AZ7798", 585 | "tab": "b2c1d58b.5da018", 586 | "disp": true, 587 | "width": "6", 588 | "collapse": false 589 | }, 590 | { 591 | "id": "8d2ce4c4.fa6338", 592 | "type": "ui_group", 593 | "z": "", 594 | "name": "Graphs", 595 | "tab": "b2c1d58b.5da018", 596 | "order": 2, 597 | "disp": true, 598 | "width": "6", 599 | "collapse": false 600 | }, 601 | { 602 | "id": "ff84a2bf.b8dd5", 603 | "type": "influxdb", 604 | "z": "", 605 | "hostname": "18.0.0.11", 606 | "port": "8086", 607 | "protocol": "http", 608 | "database": "office", 609 | "name": "", 610 | "usetls": false, 611 | "tls": "" 612 | }, 613 | { 614 | "id": "b2c1d58b.5da018", 615 | "type": "ui_tab", 616 | "z": "", 617 | "name": "CO2 meter", 618 | "icon": "dashboard" 619 | } 620 | ] -------------------------------------------------------------------------------- /ESP8266AZ7798/upload.bat: -------------------------------------------------------------------------------- 1 | C:\Users\oleg\AppData\Local\Arduino15\packages\esp8266\tools\esptool\0.4.9/esptool.exe -vvv -cb 115200 -cp COM17 -bz 1M -ce 2 | pause 3 | C:\Users\oleg\AppData\Local\Arduino15\packages\esp8266\tools\esptool\0.4.9/esptool.exe -vv -cd ck -cb 921600 -cp COM17 -ca 0x00000 -cf C:\Users\oleg\AppData\Local\Temp\arduino_build_585678/ESP14MES.ino.bin 4 | pause 5 | -------------------------------------------------------------------------------- /ESP8266CO2PM25/ESP8266CO2PM25.config: -------------------------------------------------------------------------------- 1 | // Add predefined macros for your project here. For example: 2 | // #define THE_ANSWER 42 3 | -------------------------------------------------------------------------------- /ESP8266CO2PM25/ESP8266CO2PM25.creator: -------------------------------------------------------------------------------- 1 | [General] 2 | -------------------------------------------------------------------------------- /ESP8266CO2PM25/ESP8266CO2PM25.files: -------------------------------------------------------------------------------- 1 | ../../libraries/Adafruit_BME280_Library/Adafruit_BME280.cpp 2 | ../../libraries/Adafruit_BME280_Library/Adafruit_BME280.h 3 | ../../libraries/Adafruit_SI1145_Library/Adafruit_SI1145.cpp 4 | ../../libraries/Adafruit_SI1145_Library/Adafruit_SI1145.h 5 | ../../libraries/Adafruit_SI1145_Library/examples/si1145lux/si1145lux.ino 6 | ../../libraries/ClosedCube_HDC1080/src/ClosedCube_HDC1080.cpp 7 | ../../libraries/ClosedCube_HDC1080/src/ClosedCube_HDC1080.h 8 | ../../libraries/XLogger/xlogger.cpp 9 | ../../libraries/XLogger/xlogger.h 10 | ../../libraries/pms7003_esp/pms7003.cpp 11 | ../../libraries/pms7003_esp/pms7003.h 12 | ../../libraries/pms7003_esp/pms7003_esp.ino 13 | ../lib/BME280.cpp 14 | ../lib/BME280.h 15 | ../lib/HDC1080.cpp 16 | ../lib/HDC1080.h 17 | ../lib/emodbus.cpp 18 | ../lib/emodbus.h 19 | ../lib/modbuspoll.cpp 20 | ../lib/modbuspoll.h 21 | ../lib/pitimer.cpp 22 | ../lib/pitimer.h 23 | ../lib/pmsx003.cpp 24 | ../lib/pmsx003.h 25 | ../lib/si1145.cpp 26 | ../lib/si1145.h 27 | ../lib/xmqtt.cpp 28 | ../lib/xmqtt.h 29 | ../lib/xparam.cpp 30 | ../lib/xparam.h 31 | ESP8266CO2PM25.ino 32 | -------------------------------------------------------------------------------- /ESP8266CO2PM25/ESP8266CO2PM25.includes: -------------------------------------------------------------------------------- 1 | . 2 | ../lib 3 | ../../libraries/Adafruit_BME280_Library 4 | ../../libraries/ClosedCube_HDC1080/src 5 | ../../libraries/pms7003_esp 6 | ../../libraries/XLogger 7 | ../../libraries/Adafruit_SI1145_Library 8 | ../../libraries/Adafruit_SI1145_Library/examples/si1145lux 9 | -------------------------------------------------------------------------------- /ESP8266CO2PM25/ESP8266CO2PM25.ino: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Measurement unit 4 | * CO2, Temperature, Humidity sensors communication module. 5 | * Sends sensor measurements to MQTT 6 | * Senseair S8 (modbus) - CO2, 7 | * MH-Z19(B) - CO2, 8 | * Plantower PMS5003, PMS7003, PMSA003 - particle concentration sensors 9 | * HDC1080 - temperature + humidity, 10 | * BMP280 - pressure + temperature, 11 | * SI1145 - light sensor 12 | * 13 | * original repository here https://github.com/merlokk/SmartHome/tree/master/ESP8266CO2PM25 14 | * (c) Oleg Moiseenko 2017 15 | */ 16 | 17 | #include 18 | #include // https://github.com/esp8266/Arduino 19 | #include // logger https://github.com/merlokk/xlogger 20 | #include 21 | #include 22 | 23 | // my libraries 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include // timers 30 | #include 31 | #include 32 | #include "general.h" 33 | 34 | #define PROGRAM_VERSION "1.01" 35 | 36 | #define DEBUG // enable debugging 37 | #define DEBUG_SERIAL logger 38 | 39 | // device modbus address 40 | #define MODBUS_ADDRESS 0xfe 41 | #define CONNECTED_OBJ modbus.Connected 42 | 43 | #define MQTT_DEFAULT_TOPIC "MesUnit" 44 | 45 | // poll 46 | #define MILLIS_TO_POLL 10*1000 //max time to wait for poll input registers (regular poll) 47 | #define MILLIS_TO_POLL_HOLD_REG 10*60*1000 //max time to wait for poll all 15*60 48 | #define MILLIS_TO_MES 10*1000 //max time to measurements 49 | // timers 50 | #define TID_POLL 0x0001 // timer UID for regular poll 51 | #define TID_HOLD_REG 0x0002 // timer UID for poll all the registers 52 | #define TID_MES 0x0003 // timer UID for measurements 53 | 54 | // LEDs and pins 55 | #define PIN_PGM 0 // programming pin and jumper 56 | #define LED1 12 // led 1 57 | #define LED2 14 // led 2 58 | #define LEDON LOW 59 | #define LEDOFF HIGH 60 | #define SDAPIN 4 61 | #define CLKPIN 5 62 | 63 | // objects 64 | SoftwareSerial mSerial1(13, 15); // RX, TX (13,15) 65 | ModbusPoll modbus; 66 | piTimer ptimer; 67 | hdc1080 hdc; 68 | bme280 bme; 69 | si1145 si; 70 | pmsx003 pms; 71 | 72 | /////////////////////////////////////////////////////////////////////////// 73 | // i2c 74 | /////////////////////////////////////////////////////////////////////////// 75 | void i2cInit() { 76 | pinMode(SDAPIN, INPUT_PULLUP); 77 | pinMode(CLKPIN, INPUT_PULLUP); 78 | delay(200); 79 | bool isok = digitalRead(SDAPIN) && digitalRead(CLKPIN); 80 | if (!isok) { 81 | DEBUG_PRINTLN(F("I2C bus recovery...")); 82 | delay(500); 83 | //try i2c bus recovery at 100kHz = 5uS high, 5uS low 84 | pinMode(SDAPIN, OUTPUT_OPEN_DRAIN);//keeping SDA high during recovery 85 | pinMode(CLKPIN, OUTPUT_OPEN_DRAIN); 86 | digitalWrite(SDAPIN, HIGH); 87 | digitalWrite(CLKPIN, HIGH); 88 | 89 | for (int i = 0; i < 10; i++) { //9nth cycle acts as NACK 90 | digitalWrite(CLKPIN, HIGH); 91 | delayMicroseconds(5); 92 | digitalWrite(CLKPIN, LOW); 93 | delayMicroseconds(5); 94 | } 95 | 96 | //a STOP signal (SDA from low to high while CLK is high) 97 | digitalWrite(SDAPIN, LOW); 98 | delayMicroseconds(5); 99 | digitalWrite(CLKPIN, HIGH); 100 | delayMicroseconds(2); 101 | digitalWrite(SDAPIN, HIGH); 102 | delayMicroseconds(2); 103 | //bus status is now : FREE 104 | 105 | //return to power up mode 106 | pinMode(SDAPIN, INPUT_PULLUP); 107 | pinMode(CLKPIN, INPUT_PULLUP); 108 | delay(500); 109 | 110 | isok = digitalRead(SDAPIN) && digitalRead(CLKPIN); 111 | if (!digitalRead(SDAPIN)) 112 | DEBUG_PRINTLN(F("I2C bus recovery error: SDA still LOW.")); 113 | if (!digitalRead(CLKPIN)) 114 | DEBUG_PRINTLN(F("I2C bus recovery error: CLK still LOW.")); 115 | 116 | if (isok) 117 | DEBUG_PRINTLN(F("I2C bus recovery complete.")); 118 | } 119 | 120 | Wire.pins(SDAPIN, CLKPIN); 121 | Wire.begin(SDAPIN, CLKPIN); 122 | } 123 | 124 | /////////////////////////////////////////////////////////////////////////// 125 | // Setup() and loop() 126 | /////////////////////////////////////////////////////////////////////////// 127 | void setup() { 128 | Serial.setDebugOutput(false); 129 | Serial1.setDebugOutput(true); 130 | Serial.begin(9600); 131 | Serial1.begin(230400); // high speed logging port 132 | 133 | mSerial1.begin(9600); 134 | 135 | generalSetup(); 136 | 137 | //timer 138 | ptimer.Add(TID_POLL, MILLIS_TO_POLL); 139 | ptimer.Add(TID_HOLD_REG, MILLIS_TO_POLL_HOLD_REG); 140 | ptimer.Add(TID_MES, MILLIS_TO_MES); 141 | 142 | // LED init 143 | pinMode(LED1, OUTPUT); 144 | pinMode(LED2, OUTPUT); 145 | digitalWrite(LED1, LEDOFF); 146 | digitalWrite(LED2, LEDOFF); 147 | 148 | // eastron setup 149 | modbus.SetDeviceAddress(MODBUS_ADDRESS); 150 | modbus.SetLogger(&logger); 151 | modbus.SetSerial(&mSerial1); 152 | DEBUG_PRINT(F("DeviceType: ")); 153 | DEBUG_PRINTLN(params[F("device_type")]); 154 | modbus.ModbusSetup(params[F("device_type")].c_str()); 155 | 156 | String str; 157 | modbus.getStrModbusConfig(str); 158 | DEBUG_PRINTLN(F("Modbus config:")); 159 | DEBUG_PRINTLN(str); 160 | 161 | //i2c 162 | i2cInit(); 163 | 164 | // hdc1080 165 | hdc.begin(&logger); 166 | hdc.SetMQTT(&mqtt, SF("THConnected"), SF("Temperature"), SF("Humidity"), SF("Heater")); 167 | 168 | // BME280 i2c 169 | bme.begin(&logger); 170 | bme.SetMQTT(&mqtt, SF("TH2Connected"), SF("Temperature2"), SF("Humidity2"), SF("Pressure2")); 171 | 172 | // si1145 i2c 173 | si.begin(&logger); 174 | si.SetMQTT(&mqtt, SF("LightConnected"), SF("Visible"), SF("IR"), SF("UV")); 175 | 176 | // pmsX003 serial mode 177 | pms.begin(&logger, &Serial); 178 | pms.SetMQTT(&mqtt, SF("PMSConnected"), SF("PM1.0"), SF("PM2.5"), SF("PM10")); 179 | 180 | // set password in work mode 181 | if (params[F("device_passwd")].length() > 0) 182 | logger.setPassword(params[F("device_passwd")].c_str()); 183 | 184 | ticker.detach(); 185 | 186 | delay(2000); // start sensors 187 | 188 | digitalWrite(LED1, LEDOFF); 189 | } 190 | 191 | // the loop function runs over and over again forever 192 | void loop() { 193 | digitalWrite(LED2, LEDON); 194 | if (!generalLoop()) { 195 | digitalWrite(LED2, LEDOFF); 196 | return; 197 | } 198 | 199 | if (ptimer.isArmed(TID_POLL)) { 200 | // modbus poll function 201 | if (ptimer.isArmed(TID_HOLD_REG)) { 202 | if (modbus.getWordValue(POLL_INPUT_REGISTERS, 0x1e) == 0) { 203 | modbus.PollAddress(0x19); 204 | } 205 | ptimer.Reset(TID_HOLD_REG); 206 | } else { 207 | // it needs because time from time seanseair s8 returns timeout. 208 | for(int i = 0; i < 4; i++) { 209 | modbus.PollAddress(0x00); 210 | if(modbus.Connected) break; 211 | delay(150); 212 | } 213 | }; 214 | 215 | yield(); 216 | 217 | // publish some system vars 218 | mqtt.BeginPublish(); 219 | mqttPublishRegularState(); 220 | 221 | // publish vars from configuration 222 | if (modbus.mapConfigLen && modbus.mapConfig && modbus.Connected) { 223 | String str; 224 | for(int i = 0; i < modbus.mapConfigLen; i++) { 225 | modbus.getValue( 226 | str, 227 | modbus.mapConfig[i].command, 228 | modbus.mapConfig[i].modbusAddress, 229 | modbus.mapConfig[i].valueType); 230 | mqtt.PublishState(modbus.mapConfig[i].mqttTopicName, str); 231 | } 232 | }; 233 | mqtt.Commit(); 234 | 235 | ptimer.Reset(TID_POLL); 236 | } 237 | 238 | // HDC 1080 239 | hdc.handle(); 240 | 241 | // BME280 242 | bme.handle(); 243 | 244 | // si1145 245 | si.handle(); 246 | 247 | // pmsX003 248 | pms.handle(); 249 | 250 | digitalWrite(LED2, LEDOFF); 251 | delay(100); 252 | } 253 | 254 | -------------------------------------------------------------------------------- /ESP8266CO2PM25/doc/BST-BME280_DS001-11.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266CO2PM25/doc/BST-BME280_DS001-11.pdf -------------------------------------------------------------------------------- /ESP8266CO2PM25/doc/MH-Z19 CO2 Ver1.0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266CO2PM25/doc/MH-Z19 CO2 Ver1.0.pdf -------------------------------------------------------------------------------- /ESP8266CO2PM25/doc/PMS5003_LOGOELE.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266CO2PM25/doc/PMS5003_LOGOELE.pdf -------------------------------------------------------------------------------- /ESP8266CO2PM25/doc/PMS7003 series data manua_English_V2.5.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266CO2PM25/doc/PMS7003 series data manua_English_V2.5.pdf -------------------------------------------------------------------------------- /ESP8266CO2PM25/doc/PMSA003_cn.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266CO2PM25/doc/PMSA003_cn.pdf -------------------------------------------------------------------------------- /ESP8266CO2PM25/doc/PMSx003_Schematics.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266CO2PM25/doc/PMSx003_Schematics.jpg -------------------------------------------------------------------------------- /ESP8266CO2PM25/doc/PSP126.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266CO2PM25/doc/PSP126.pdf -------------------------------------------------------------------------------- /ESP8266CO2PM25/doc/Si1145-46-47.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266CO2PM25/doc/Si1145-46-47.pdf -------------------------------------------------------------------------------- /ESP8266CO2PM25/doc/TDE2067.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266CO2PM25/doc/TDE2067.pdf -------------------------------------------------------------------------------- /ESP8266CO2PM25/doc/links.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266CO2PM25/doc/links.txt -------------------------------------------------------------------------------- /ESP8266CO2PM25/doc/mh-z19b-co2-ver1_0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266CO2PM25/doc/mh-z19b-co2-ver1_0.pdf -------------------------------------------------------------------------------- /ESP8266CO2PM25/doc/plantower-pms-7003-sensor-data-sheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266CO2PM25/doc/plantower-pms-7003-sensor-data-sheet.pdf -------------------------------------------------------------------------------- /ESP8266CO2PM25/doc/plantower-pms5003-manual_v2-3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266CO2PM25/doc/plantower-pms5003-manual_v2-3.pdf -------------------------------------------------------------------------------- /ESP8266CO2PM25/doc/pms5003_pinout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266CO2PM25/doc/pms5003_pinout.png -------------------------------------------------------------------------------- /ESP8266CO2PM25/doc/pms7003_cn.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266CO2PM25/doc/pms7003_cn.pdf -------------------------------------------------------------------------------- /ESP8266CO2PM25/doc/pms7003pinout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266CO2PM25/doc/pms7003pinout.jpg -------------------------------------------------------------------------------- /ESP8266CO2PM25/doc/pms7003pins.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266CO2PM25/doc/pms7003pins.jpg -------------------------------------------------------------------------------- /ESP8266CO2PM25/upload.cmd: -------------------------------------------------------------------------------- 1 | python.exe C:\Users\oleg\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.4.1/tools/espota.py -i 192.168.55.12 -p 8266 -f C:\Users\oleg\AppData\Local\Temp\arduino_build_280899/ESP8266CO2PM25.ino.bin -------------------------------------------------------------------------------- /ESP8266DISABLECPU/ESP8266DISABLECPU.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern "C" { 4 | #include "user_interface.h" 5 | } 6 | 7 | //#define USE_DEEPSLEEP 8 | 9 | uint32_t counter; 10 | uint32_t salt = 12345; 11 | 12 | #define RX_PIN 3 // GPIO3 13 | #define TX_PIN 1 // GPIO1 14 | 15 | void setup() { 16 | Serial1.begin(74880); //74880 17 | // pinMode(RX_PIN, INPUT); 18 | // pinMode(TX_PIN, INPUT); 19 | while(!Serial1) { }; 20 | Serial1.println("Init..."); 21 | Serial1.println("ms from start=" + String(millis())); 22 | 23 | uint32_t check = 0; 24 | ESP.rtcUserMemoryRead(0, &check, sizeof(uint32_t)); 25 | if (check != salt) { 26 | ESP.rtcUserMemoryWrite(0, &salt, sizeof(uint32_t)); 27 | counter = 0; 28 | Serial1.println("Error RTC memory. Counter cleared."); 29 | } else { 30 | ESP.rtcUserMemoryRead(4, &counter, sizeof(uint32_t)); 31 | Serial1.println("RTC counter=" + String(counter)); 32 | } 33 | counter ++; 34 | ESP.rtcUserMemoryWrite(4, &counter, sizeof(uint32_t)); 35 | 36 | // deepSleep time is defined in microseconds. Multiply 37 | // seconds by 1e6 38 | // second param: WAKE_RF_DEFAULT, WAKE_RFCAL, WAKE_NO_RFCAL, WAKE_RF_DISABLED 39 | 40 | #ifdef USE_DEEPSLEEP 41 | Serial1.println("Go to 10s deepsleep..."); 42 | ESP.deepSleep(10 * 1000000, WAKE_RF_DEFAULT); // 10 sec, max ~71 minutes. 43 | // ESP.deepSleep(0); // forever 44 | delay(1000); 45 | #else 46 | Serial1.println("Go to 10s lightsleep..."); 47 | wifi_set_sleep_type(LIGHT_SLEEP_T); 48 | delay(10 * 1000); // 10 sec 49 | wifi_set_sleep_type(NONE_SLEEP_T); 50 | delay(200); 51 | ESP.reset(); 52 | delay(200); 53 | #endif 54 | Serial1.println("I cant be here!!!"); 55 | 56 | //ESP.rtcUserMemoryWrite(offset, &data, sizeof(data)) and ESP.rtcUserMemoryRead(offset, &data, sizeof(data)) 57 | //allow data to be stored in and retrieved from the RTC user memory of the chip respectively. Total size of 58 | //RTC user memory is 512 bytes, so offset + sizeof(data) shouldn't exceed 512. Data should be 4-byte aligned. 59 | //The stored data can be retained between deep sleep cycles. However, the data might be lost after power cycling the chip. 60 | 61 | //http://www.espressif.com/sites/default/files/9b-esp8266-low_power_solutions_en_0.pdf 62 | 63 | // http://homecircuits.eu/blog/esp8266-temperature-iot-logger/ 64 | // Required for LIGHT_SLEEP_T delay mode 65 | //extern "C" { 66 | //#include "user_interface.h" 67 | //} 68 | //wifi_set_sleep_type(LIGHT_SLEEP_T); 69 | //delay(60000*3-800); // loop every 3 minutes 70 | 71 | // deep sleep wo sleep) https://hackaday.io/project/12866-esp8266-power-latch 72 | 73 | // not so deep sleep (at the bottom) https://github.com/chaeplin/esp8266_and_arduino/blob/master/_48-door-alarm-deepsleep/_48-door-alarm-deepsleep.ino 74 | } 75 | 76 | void loop() { 77 | 78 | delay(1000); 79 | } 80 | -------------------------------------------------------------------------------- /ESP8266EASTRON/ESP8266EASTRON.config: -------------------------------------------------------------------------------- 1 | // Add predefined macros for your project here. For example: 2 | // #define THE_ANSWER 42 3 | -------------------------------------------------------------------------------- /ESP8266EASTRON/ESP8266EASTRON.creator: -------------------------------------------------------------------------------- 1 | [General] 2 | -------------------------------------------------------------------------------- /ESP8266EASTRON/ESP8266EASTRON.files: -------------------------------------------------------------------------------- 1 | ../../libraries/Adafruit_BME280_Library/Adafruit_BME280.cpp 2 | ../../libraries/Adafruit_BME280_Library/Adafruit_BME280.h 3 | ../../libraries/ClosedCube_HDC1080/src/ClosedCube_HDC1080.cpp 4 | ../../libraries/ClosedCube_HDC1080/src/ClosedCube_HDC1080.h 5 | ../../libraries/pms7003_esp/pms7003.cpp 6 | ../../libraries/pms7003_esp/pms7003.h 7 | ../lib/BME280.cpp 8 | ../lib/BME280.h 9 | ../lib/HDC1080.cpp 10 | ../lib/HDC1080.h 11 | ../lib/autotimezone.cpp 12 | ../lib/autotimezone.h 13 | ../lib/pmsx003.cpp 14 | ../lib/pmsx003.h 15 | general.ino 16 | general.h 17 | ../lib/az7798.cpp 18 | ../lib/az7798.h 19 | ../lib/eastron.h 20 | ../lib/modbuspoll.cpp 21 | ../lib/modbuspoll.h 22 | ../lib/emodbus.cpp 23 | ../lib/emodbus.h 24 | ../lib/etools.cpp 25 | ../lib/etools.h 26 | ../lib/pitimer.cpp 27 | ../lib/pitimer.h 28 | ../lib/xparam.cpp 29 | ../lib/xparam.h 30 | ../lib/xmqtt.cpp 31 | ../lib/xmqtt.h 32 | ESP8266EASTRON.ino 33 | -------------------------------------------------------------------------------- /ESP8266EASTRON/ESP8266EASTRON.includes: -------------------------------------------------------------------------------- 1 | . 2 | ..\..\libraries\pubsubclient-2.6\src 3 | ..\..\libraries\WiFiManager 4 | ..\..\libraries\XLogger 5 | ..\..\libraries\Time 6 | ..\..\libraries\NtpClient 7 | ..\..\libraries\NtpClient\src 8 | ..\..\libraries\ArduinoJson 9 | ..\..\libraries\ArduinoJson\src 10 | ..\..\libraries\ 11 | ..\lib 12 | ../../libraries/Adafruit_BME280_Library 13 | ../../libraries/ClosedCube_HDC1080/src 14 | ../../libraries/pms7003_esp 15 | -------------------------------------------------------------------------------- /ESP8266EASTRON/ESP8266EASTRON.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /* 12 | * Eastron energy counter bridge. 13 | * SDM 230, SDM 630 14 | * Eastron -> RS-485 -> esp-8266 -> WiFi -> MQTT 15 | * 16 | * original repository here https://github.com/merlokk/SmartHome/tree/master/ESP8266EASTRON 17 | * (c) Oleg Moiseenko 2017 18 | */ 19 | #include 20 | #include // https://github.com/esp8266/Arduino 21 | #include // logger https://github.com/merlokk/xlogger 22 | // my libraries 23 | #include 24 | #include // timers 25 | #include 26 | #include 27 | #include "general.h" 28 | 29 | #define PROGRAM_VERSION "1.01" 30 | 31 | #define DEBUG // enable debugging 32 | #define DEBUG_SERIAL logger 33 | 34 | // device modbus address 35 | #define MODBUS_ADDRESS 1 36 | #define CONNECTED_OBJ eas.Connected 37 | 38 | #define MQTT_DEFAULT_TOPIC "PowerMeter" 39 | 40 | // poll 41 | #define MILLIS_TO_POLL 1500 //max time to wait for poll input registers (regular poll) 42 | #define MILLIS_TO_POLL_HOLD_REG 15*60*1000 //max time to wait for poll all 43 | // timers 44 | #define TID_POLL 0x0001 // timer UID for regular poll 45 | #define TID_HOLD_REG 0x0002 // timer UID for poll all the registers 46 | 47 | // LEDs and pins 48 | #define PIN_PGM 0 // programming pin and jumper 49 | #define LED1 12 // led 1 50 | #define LED2 14 // led 2 51 | #define LEDON LOW 52 | #define LEDOFF HIGH 53 | 54 | // objects 55 | const int units = 4; 56 | ModbusPoll eas; 57 | ModbusPoll eastron[units]; 58 | piTimer ptimer; 59 | 60 | /////////////////////////////////////////////////////////////////////////// 61 | // Setup() and loop() 62 | /////////////////////////////////////////////////////////////////////////// 63 | void setup() { 64 | Serial.setDebugOutput(false); 65 | Serial1.setDebugOutput(true); 66 | Serial.begin(9600); //74880 67 | //Serial.swap(); // Move it to D7 and D8 instead 68 | Serial1.begin(230400); // high speed logging port 69 | 70 | generalSetup(); 71 | 72 | //Timers 73 | ptimer.Add(TID_POLL, MILLIS_TO_POLL); 74 | ptimer.Add(TID_HOLD_REG, MILLIS_TO_POLL_HOLD_REG); 75 | 76 | // LED init 77 | pinMode(LED1, OUTPUT); 78 | pinMode(LED2, OUTPUT); 79 | digitalWrite(LED1, LEDOFF); 80 | digitalWrite(LED2, LEDOFF); 81 | 82 | // eastron setup 83 | for (int i = 1; i < units+1; i++) { 84 | eastron[i].SetDeviceAddress(i); 85 | eastron[i].SetLogger(&logger); 86 | DEBUG_PRINT(F("DeviceType: ")); 87 | DEBUG_PRINTLN(params[F("device_type")]); 88 | eastron[i].ModbusSetup(params[F("device_type")].c_str()); 89 | 90 | 91 | } 92 | 93 | 94 | // String str; 95 | // eastron[.getStrModbusConfig(str); 96 | // DEBUG_PRINTLN(F("Modbus config:")); 97 | // DEBUG_PRINTLN(str); 98 | 99 | // set password in work mode 100 | if (params[F("device_passwd")].length() > 0) 101 | logger.setPassword(params[F("device_passwd")].c_str()); 102 | 103 | ticker.detach(); 104 | digitalWrite(LED1, LEDOFF); 105 | } 106 | 107 | void checkWifi() { 108 | if (WiFi.status() != WL_CONNECTED) { 109 | Serial.println("Wifi was disconnected, reconnecting"); 110 | 111 | WiFi.begin(); 112 | delay(500); 113 | } 114 | } 115 | 116 | // the loop function runs over and over again forever 117 | void loop() { 118 | digitalWrite(LED2, LEDON); 119 | if (!generalLoop()) { 120 | digitalWrite(LED2, LEDOFF); 121 | return; 122 | } 123 | 124 | 125 | 126 | if (ptimer.isArmed(TID_POLL)) { //Lets poll the meters now 127 | checkWifi(); 128 | // modbus poll function 129 | bool pol = false; 130 | if (ptimer.isArmed(TID_HOLD_REG)) { //Lets ooll all registers instead 131 | pol = true; 132 | ptimer.Reset(TID_HOLD_REG); 133 | } 134 | 135 | // publish some system vars 136 | mqtt.BeginPublish(); 137 | mqttPublishRegularState(); 138 | 139 | 140 | for (int i = 1; i // http updater https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h 2 | #include // Local DNS Server used for redirecting all requests to the configuration portal 3 | #include // https://github.com/merlokk/WiFiManager original:https://github.com/tzapu/WiFiManager 4 | #include // https://github.com/knolleary/pubsubclient/releases/tag/v2.6 5 | #include 6 | #include 7 | #include 8 | #include // https://github.com/PaulStoffregen/Time 9 | #include // https://github.com/gmag11/NtpClient 10 | #include 11 | 12 | // macros for debugging 13 | xLogger logger; 14 | #ifdef DEBUG 15 | // log level: info 16 | #define DEBUG_PRINT(...) logger.print(__VA_ARGS__) 17 | #define DEBUG_PRINTLN(...) logger.println(__VA_ARGS__) 18 | // log level: warning 19 | #define DEBUG_WPRINT(...) logger.print(llWarning, __VA_ARGS__) 20 | #define DEBUG_WPRINTLN(...) logger.println(llWarning, __VA_ARGS__) 21 | // log level: error 22 | #define DEBUG_EPRINT(...) logger.print(llError, __VA_ARGS__) 23 | #define DEBUG_EPRINTLN(...) logger.println(llError, __VA_ARGS__) 24 | #else 25 | #define DEBUG_PRINT(...) 26 | #define DEBUG_PRINTLN(...) 27 | #define DEBUG_WPRINT(...) 28 | #define DEBUG_WPRINTLN(...) 29 | #define DEBUG_EPRINT(...) 30 | #define DEBUG_EPRINTLN(...) 31 | #endif 32 | 33 | // MQTT ID and topics 34 | char HARDWARE_ID[7] = {0}; 35 | char MQTT_STATE_TOPIC[30] = {0}; 36 | const char* MQTT_ON_PAYLOAD = "ON"; 37 | const char* MQTT_OFF_PAYLOAD = "OFF"; 38 | 39 | // vars 40 | bool inProgrammingMode = false; 41 | 42 | // objects 43 | Ticker ticker; 44 | xParam params; 45 | xMQTT mqtt; 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /ESP8266EASTRON/general.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * General ESP8266 library. 4 | * 5 | * MQTT, NTP, ArduinoOTA, WifiNabager, ExecuteCommands, xLogger 6 | * 7 | * (c) Oleg Moiseenko 2017 8 | */ 9 | 10 | // vcc measurements 11 | ADC_MODE(ADC_VCC); // set ADC to meassure esp8266 VCC 12 | 13 | /////////////////////////////////////////////////////////////////////////// 14 | // MQTT 15 | /////////////////////////////////////////////////////////////////////////// 16 | 17 | /* 18 | Function called to init MQTT 19 | */ 20 | void initMQTT(const char * topicName) { 21 | mqtt.begin(HARDWARE_ID, topicName, ¶ms, &logger, false, false); // hardwareID, topicName, xParam, xLogger, postAsJson, retained 22 | mqtt.SetProgramVersion(PROGRAM_VERSION); 23 | mqtt.SetCmdCallback(CmdCallback); 24 | 25 | mqtt.Connect(); 26 | mqtt.PublishInitialState(); 27 | } 28 | 29 | void mqttPublishRegularState() { 30 | String s = ""; 31 | eTimeToStr(s, millis() / 1000); 32 | mqtt.PublishState(SF("Uptime"), s); 33 | mqtt.PublishState(SF("VCC"), String(ESP.getVcc())); 34 | RSSItoStr(s, WiFi.RSSI()); 35 | mqtt.PublishState(SF("RSSI"), s); 36 | 37 | if (timeStatus() == timeSet){ 38 | mqtt.PublishState(SF("LastSeenDateTime"), NTP.getTimeDateString()); 39 | mqtt.PublishState(SF("LastBootDateTime"), NTP.getTimeDateString(NTP.getFirstSync())); 40 | 41 | #ifdef CONNECTED_OBJ 42 | if (CONNECTED_OBJ) { 43 | mqtt.PublishState(SF("LastConnectedDateTime"), NTP.getTimeDateString()); 44 | } 45 | #endif 46 | } 47 | 48 | //#ifdef CONNECTED_OBJ 49 | // mqtt.PublishState(SF("Connected"), CONNECTED_OBJ ? MQTT_ON_PAYLOAD:MQTT_OFF_PAYLOAD); 50 | //#endif 51 | } 52 | 53 | /////////////////////////////////////////////////////////////////////////// 54 | // NTP 55 | /////////////////////////////////////////////////////////////////////////// 56 | boolean syncEventTriggered = false; // True if a time even has been triggered 57 | NTPSyncEvent_t ntpEvent; // Last triggered event 58 | 59 | void processSyncEvent(NTPSyncEvent_t ntpEvent) { 60 | if (ntpEvent) { 61 | DEBUG_EPRINT(F("NTP sync error: ")); 62 | switch (noResponse) { 63 | case noResponse: DEBUG_EPRINTLN(F("NTP server not reachable")); break; 64 | case invalidAddress: DEBUG_EPRINTLN(F("Invalid NTP server address")); break; 65 | default: DEBUG_EPRINTLN(F("")); 66 | } 67 | } else { 68 | DEBUG_PRINT(F("Got NTP time: ")); 69 | DEBUG_PRINTLN(NTP.getTimeDateString(NTP.getLastNTPSync())); 70 | } 71 | } 72 | 73 | /////////////////////////////////////////////////////////////////////////// 74 | // WiFiManager 75 | /////////////////////////////////////////////////////////////////////////// 76 | /* 77 | Function called to toggle the state of the LED 78 | */ 79 | void tick() { 80 | digitalWrite(LED1, !digitalRead(LED1)); 81 | } 82 | 83 | // flag for saving data 84 | bool shouldSaveConfig = false; 85 | 86 | // callback notifying us of the need to save config 87 | void saveConfigCallback () { 88 | shouldSaveConfig = true; 89 | } 90 | 91 | void configModeCallback (WiFiManager *myWiFiManager) { 92 | ticker.attach(0.2, tick); 93 | } 94 | 95 | void wifiSetup(bool withAutoConnect) { 96 | shouldSaveConfig = false; 97 | ticker.attach(0.1, tick); 98 | 99 | // load custom params 100 | params.LoadFromEEPROM(); 101 | 102 | WiFiManager wifiManager; 103 | //wifiManager.mainProgramVersion = PROGRAM_VERSION; 104 | 105 | WiFiManagerParameter custom_mqtt_text("
MQTT config:
"); 106 | wifiManager.addParameter(&custom_mqtt_text); 107 | WiFiManagerParameter custom_mqtt_user("mqtt-user", "MQTT User", params[F("mqtt_user")].c_str(), 20); 108 | wifiManager.addParameter(&custom_mqtt_user); 109 | WiFiManagerParameter custom_mqtt_password("mqtt-password", "MQTT Password", params[F("mqtt_passwd")].c_str(), 20, "type = \"password\""); 110 | wifiManager.addParameter(&custom_mqtt_password); 111 | WiFiManagerParameter custom_mqtt_server("mqtt-server", "MQTT Broker Address", params[F("mqtt_server")].c_str(), 20); 112 | wifiManager.addParameter(&custom_mqtt_server); 113 | WiFiManagerParameter custom_mqtt_port("mqtt-port", "MQTT Broker Port", params[F("mqtt_port")].c_str(), 20); 114 | wifiManager.addParameter(&custom_mqtt_port); 115 | WiFiManagerParameter custom_mqtt_path("mqtt-path", "MQTT Path", params[F("mqtt_path")].c_str(), 20); 116 | wifiManager.addParameter(&custom_mqtt_path); 117 | WiFiManagerParameter custom_device_type("device-type", "Device type", params[F("device_type")].c_str(), 20); 118 | wifiManager.addParameter(&custom_device_type); 119 | WiFiManagerParameter custom_mqtt_text1("
Development config:
"); 120 | wifiManager.addParameter(&custom_mqtt_text1); 121 | WiFiManagerParameter custom_passwd("device-paswd", "Device dev password", params[F("device_passwd")].c_str(), 20); 122 | wifiManager.addParameter(&custom_passwd); 123 | 124 | wifiManager.setAPCallback(configModeCallback); 125 | wifiManager.setConfigPortalTimeout(180); 126 | // set config save notify callback 127 | wifiManager.setSaveConfigCallback(saveConfigCallback); 128 | // resets after 129 | wifiManager.setBreakAfterConfig(true); 130 | 131 | bool needsRestart = false; 132 | if (withAutoConnect){ 133 | if (!wifiManager.autoConnect()) { 134 | needsRestart = true; 135 | } 136 | } else { 137 | String ssid = SF("ESP") + String(ESP.getChipId()); 138 | if (!wifiManager.startConfigPortal(ssid.c_str())) { 139 | needsRestart = true; 140 | } 141 | } 142 | if (shouldSaveConfig) { 143 | params.SetParam(F("mqtt_server"), custom_mqtt_server.getValue()); 144 | params.SetParam(F("mqtt_port"), custom_mqtt_port.getValue()); 145 | params.SetParam(F("mqtt_user"), custom_mqtt_user.getValue()); 146 | params.SetParam(F("mqtt_passwd"), custom_mqtt_password.getValue()); 147 | params.SetParam(F("mqtt_path"), custom_mqtt_path.getValue()); 148 | params.SetParam(F("device_type"), custom_device_type.getValue()); 149 | params.SetParam(F("device_passwd"), custom_passwd.getValue()); 150 | 151 | params.SaveToEEPROM(); 152 | } 153 | 154 | if (needsRestart) { 155 | restart(); 156 | } 157 | 158 | ticker.detach(); 159 | } 160 | 161 | /////////////////////////////////////////////////////////////////////////// 162 | // ESP 163 | /////////////////////////////////////////////////////////////////////////// 164 | /* 165 | Function called to restart the switch 166 | */ 167 | void restart() { 168 | DEBUG_PRINTLN(F("Restart...")); 169 | ESP.reset(); 170 | delay(1000); 171 | } 172 | 173 | /* 174 | Function called to reset the configuration of the switch 175 | Warning! It clears wifi connection parameters! 176 | */ 177 | void reset() { 178 | DEBUG_PRINTLN(F("Reset...")); 179 | WiFi.disconnect(); 180 | delay(1000); 181 | ESP.reset(); 182 | delay(1000); 183 | } 184 | 185 | void initPrintStartDebugInfo() { 186 | DEBUG_PRINTLN(F("")); 187 | DEBUG_PRINTLN(F("Starting...")); 188 | 189 | DEBUG_PRINT(F("ResetReason: ")); 190 | DEBUG_PRINTLN(ESP.getResetReason()); 191 | 192 | DEBUG_PRINT(F("Hardware ID/Hostname: ")); 193 | DEBUG_PRINTLN(HARDWARE_ID); 194 | 195 | DEBUG_PRINT(F("MAC address: ")); 196 | DEBUG_PRINTLN(WiFi.macAddress()); 197 | 198 | DEBUG_PRINT("Module VCC: "); 199 | DEBUG_PRINTLN(ESP.getVcc()); 200 | } 201 | 202 | /////////////////////////////////////////////////////////////////////////// 203 | // Update from WEB 204 | /////////////////////////////////////////////////////////////////////////// 205 | 206 | void UpdateFromWeb(const String &server) { 207 | t_httpUpdate_return ret = ESPhttpUpdate.update(server, 80, "/update/arduino.php", PROGRAM_VERSION); 208 | switch(ret) { 209 | case HTTP_UPDATE_FAILED: 210 | DEBUG_EPRINTLN(F("[update] Update failed.")); 211 | break; 212 | case HTTP_UPDATE_NO_UPDATES: 213 | DEBUG_PRINTLN(F("[update] Update no Update.")); 214 | break; 215 | case HTTP_UPDATE_OK: 216 | DEBUG_PRINTLN(F("[update] Update ok.")); // may not called we reboot the ESP 217 | break; 218 | } 219 | } 220 | 221 | /////////////////////////////////////////////////////////////////////////// 222 | // ArduinoOTA 223 | /////////////////////////////////////////////////////////////////////////// 224 | 225 | void setupArduinoOTA() { 226 | ArduinoOTA.onError([](ota_error_t error) { 227 | DEBUG_EPRINT(F("OTA error: ")); 228 | DEBUG_EPRINTLN(error); 229 | switch (error) { 230 | case OTA_AUTH_ERROR: DEBUG_EPRINTLN(F("OTA: Auth Failed")); break; 231 | case OTA_BEGIN_ERROR: DEBUG_EPRINTLN(F("OTA: Begin Failed")); break; 232 | case OTA_CONNECT_ERROR: DEBUG_EPRINTLN(F("OTA: Connect Failed")); break; 233 | case OTA_RECEIVE_ERROR: DEBUG_EPRINTLN(F("OTA: Receive Failed")); break; 234 | case OTA_END_ERROR: DEBUG_EPRINTLN(F("OTA: End Failed")); break; 235 | } 236 | 237 | ESP.restart(); 238 | }); 239 | 240 | ArduinoOTA.onStart([]() { 241 | DEBUG_PRINTLN(F("Start OTA")); 242 | }); 243 | ArduinoOTA.onEnd([]() { 244 | DEBUG_PRINTLN(F("End OTA")); 245 | }); 246 | 247 | // Port defaults to 8266 248 | // ArduinoOTA.setPort(8266); 249 | // No authentication by default 250 | if (params[F("device_passwd")].length() > 0) 251 | ArduinoOTA.setPassword(params[F("device_passwd")].c_str()); 252 | ArduinoOTA.setHostname(HARDWARE_ID); 253 | ArduinoOTA.begin(); 254 | } 255 | 256 | /////////////////////////////////////////////////////////////////////////// 257 | // execute commands 258 | /////////////////////////////////////////////////////////////////////////// 259 | const char* strCommandsDesc = 260 | "Command reboot reboots ESP.\r\n"\ 261 | "Command startwificfg puts ESP to configure mode. Show configuration AP.\r\n"\ 262 | "Command set writes parameter to ESP memory. \r\n"\ 263 | "parameters: mqtt_server, mqtt_port, mqtt_user, mqtt_passwd, mqtt_path, device_type.\r\n"\ 264 | "System commands: resetcfg, webupdate, webupdatec, showcfg. "; 265 | 266 | bool CmdCallback(String &cmd) { 267 | if (cmd == "reboot") { 268 | DEBUG_PRINTLN(F("COMMAND: reboot. Rebooting...")); 269 | restart(); 270 | delay(200); 271 | return true; 272 | } 273 | 274 | if (cmd == "resetcfg") { 275 | DEBUG_PRINTLN(F("COMMAND: Reset wifi config...")); 276 | reset(); 277 | delay(200); 278 | return true; 279 | } 280 | 281 | if (cmd == "startwificfg") { 282 | DEBUG_PRINTLN(F("COMMAND: start wifi config.")); 283 | wifiSetup(false); 284 | restart(); 285 | delay(200); 286 | return true; 287 | } 288 | 289 | if (cmd.length() >= 15 && cmd.startsWith("webupdate ")) { 290 | String srv = cmd.substring(10); 291 | DEBUG_PRINTLN(SF("COMMAND: web update. server=") + srv); 292 | UpdateFromWeb(srv); 293 | return true; 294 | } 295 | 296 | if (cmd.length() >= 16 && cmd.startsWith("webupdatec ")) { 297 | String srv = cmd.substring(10); 298 | DEBUG_PRINTLN(SF("COMMAND: web update. server=") + srv); 299 | params.LoadFromWeb(srv + "/update/arduinocfg.php"); 300 | return true; 301 | } 302 | 303 | if (cmd == "showcfg") { 304 | DEBUG_PRINTLN(F("COMMAND: show config.")); 305 | String s; 306 | params.GetParamsJsonStr(s); 307 | DEBUG_PRINTLN(s); 308 | return true; 309 | } 310 | 311 | // set . sample: set device_type 220 312 | if (cmd.length() > 7 && cmd.startsWith("set ")) { 313 | String name = cmd.substring(4); 314 | int indx = name.indexOf(" "); 315 | String value = name.substring(indx + 1); 316 | name = name.substring(0, indx); 317 | DEBUG_PRINTLN(SF("COMMAND: config <") + name + SF("> <") + value + SF(">")); 318 | 319 | if (params.ParamExists(name)) { 320 | params.LoadFromEEPROM(); 321 | params.SetParam(name, value); 322 | params.SaveToEEPROM(); 323 | } 324 | else { 325 | DEBUG_EPRINTLN(SF("Command error. Parameter <") + name + SF("> not found")); 326 | } 327 | return true; 328 | } 329 | 330 | return false; 331 | } 332 | 333 | 334 | /////////////////////////////////////////////////////////////////////////// 335 | // logger and xParam logic 336 | /////////////////////////////////////////////////////////////////////////// 337 | 338 | void initLogger() { 339 | logger.cmdCallback(CmdCallback, strCommandsDesc); 340 | logger.begin(HARDWARE_ID, &Serial1, true); 341 | logger.setProgramVersion(PROGRAM_VERSION); 342 | logger.setTimeFormat(ltNone); 343 | } 344 | 345 | void initXParam() { 346 | params.SetLogger(&logger); 347 | params.begin(); 348 | params.LoadFromEEPROM(); 349 | } 350 | 351 | 352 | /////////////////////////////////////////////////////////////////////////// 353 | // generalSetup() and generalloop() 354 | /////////////////////////////////////////////////////////////////////////// 355 | void generalSetup() { 356 | // start 357 | ticker.attach(0.1, tick); 358 | 359 | // client ID 360 | sprintf(HARDWARE_ID, "%06X", ESP.getChipId()); 361 | // start logger 362 | initLogger(); 363 | 364 | // for debug 365 | delay(200); 366 | initPrintStartDebugInfo(); 367 | 368 | WiFi.hostname("ESP_SDM630"); 369 | 370 | // 0 = programming mode 371 | pinMode(PIN_PGM, INPUT); 372 | inProgrammingMode = !digitalRead(PIN_PGM); 373 | if (inProgrammingMode) { 374 | DEBUG_WPRINTLN(F("Programming mode active!")); 375 | } 376 | 377 | // setup xparam lib 378 | initXParam(); 379 | 380 | // wifi setup with autoconnect 381 | wifiSetup(true); 382 | 383 | // pause for connecting 384 | // By default, ESP will attempt to reconnect to Wi-Fi network whenever it is disconnected. There is no need to handle this by separate code. ????? TODO: check 385 | if (WiFi.status() != WL_CONNECTED) { 386 | DEBUG_WPRINTLN(F("Wifi is not connected. Trying to reconnect...")); 387 | WiFi.begin(); 388 | delay(5000); 389 | } 390 | 391 | // NTP config 392 | NTP.onNTPSyncEvent([](NTPSyncEvent_t event) { 393 | ntpEvent = event; 394 | syncEventTriggered = true; 395 | }); 396 | NTP.begin("ua.pool.ntp.org", 0, false); 397 | NTP.setInterval(30 * 60); // twice a hour 398 | 399 | ticker.detach(); 400 | ticker.attach(0.1, tick); 401 | 402 | // configure MQTT 403 | initMQTT(MQTT_DEFAULT_TOPIC); 404 | 405 | // ArduinoOTA 406 | setupArduinoOTA(); 407 | } 408 | 409 | bool generalLoop() { 410 | 411 | // Programming pin activates setup 412 | if (!inProgrammingMode && (!digitalRead(PIN_PGM))) { 413 | DEBUG_WPRINTLN(F("Wifi setup activated")); 414 | 415 | wifiSetup(false); 416 | delay(1000); 417 | return false; 418 | } 419 | 420 | // check wifi connection 421 | if (WiFi.status() != WL_CONNECTED) { 422 | delay(100); 423 | } 424 | 425 | ArduinoOTA.handle(); 426 | logger.handle(); 427 | 428 | yield(); 429 | 430 | // NTP 431 | if (syncEventTriggered) { 432 | processSyncEvent(ntpEvent); 433 | syncEventTriggered = false; 434 | } 435 | 436 | yield(); 437 | 438 | // MQTT 439 | mqtt.handle(); 440 | if (!mqtt.Connected()){ 441 | return false; 442 | } 443 | 444 | return true; 445 | } 446 | 447 | -------------------------------------------------------------------------------- /ESP8266EASTRON/images/DSC_0089.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266EASTRON/images/DSC_0089.JPG -------------------------------------------------------------------------------- /ESP8266EASTRON/images/DSC_0091.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266EASTRON/images/DSC_0091.JPG -------------------------------------------------------------------------------- /ESP8266EASTRON/images/DSC_0111.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266EASTRON/images/DSC_0111.JPG -------------------------------------------------------------------------------- /ESP8266EASTRON/images/DSC_0113.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266EASTRON/images/DSC_0113.JPG -------------------------------------------------------------------------------- /ESP8266EASTRON/images/DSC_0116.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266EASTRON/images/DSC_0116.JPG -------------------------------------------------------------------------------- /ESP8266EASTRON/images/DSC_0119.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266EASTRON/images/DSC_0119.JPG -------------------------------------------------------------------------------- /ESP8266EASTRON/images/DSC_0120.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266EASTRON/images/DSC_0120.JPG -------------------------------------------------------------------------------- /ESP8266EASTRON/images/DSC_0121.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266EASTRON/images/DSC_0121.JPG -------------------------------------------------------------------------------- /ESP8266EASTRON/images/DSC_0123.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266EASTRON/images/DSC_0123.JPG -------------------------------------------------------------------------------- /ESP8266EASTRON/images/DSC_0126.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266EASTRON/images/DSC_0126.JPG -------------------------------------------------------------------------------- /ESP8266EASTRON/images/DSC_0130.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266EASTRON/images/DSC_0130.JPG -------------------------------------------------------------------------------- /ESP8266EASTRON/images/DSC_0167.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266EASTRON/images/DSC_0167.jpg -------------------------------------------------------------------------------- /ESP8266EASTRON/images/DSC_0171.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266EASTRON/images/DSC_0171.jpg -------------------------------------------------------------------------------- /ESP8266EASTRON/images/DSC_0175.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266EASTRON/images/DSC_0175.jpg -------------------------------------------------------------------------------- /ESP8266EASTRON/images/d3-MQTT-Topic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266EASTRON/images/d3-MQTT-Topic.png -------------------------------------------------------------------------------- /ESP8266EASTRON/images/d3-MQTT-Topic2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/ESP8266EASTRON/images/d3-MQTT-Topic2.png -------------------------------------------------------------------------------- /ESP8266EASTRON/upload.cmd: -------------------------------------------------------------------------------- 1 | python.exe C:\Users\oleg\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.3.0/tools/espota.py -i 192.168.55.10 -p 8266 --auth=123454321 -f C:\Users\oleg\AppData\Local\Temp\arduino_build_673477/ESP8266EASTRON.ino.bin -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Projects for smart home. ESP8266 + NanoPi NEO Air 5 | [![Build Status](https://travis-ci.org/merlokk/SmartHome.svg?branch=master)](https://travis-ci.org/merlokk/SmartHome) 6 | 7 | 8 | ## DEPRECATED. Tried to get it to compile but its just a pain. It need to be updated and rebuilt from scratch. Feel free to do that :) 9 | 10 | ## Why forked? 11 | Its forked because Merloks version does not work with updated libraries. Expect this to be updated in short to work. 12 | 13 | ### ESP8266AZ7788 14 | Connect Datalogger AZ-7788 to MQTT 15 | 16 | ### ESP8266EASTRON 17 | Connect Eastron energy meters (SDM220, SDM230, SDM630) to MQTT 18 | 19 | [wiki](https://github.com/merlokk/SmartHome/wiki/ESP8266-to-Eastron-energy-meters) 20 | 21 | release in the box: 22 | ![release](https://github.com/merlokk/SmartHome/blob/master/ESP8266EASTRON/images/DSC_0119.JPG) 23 | 24 | ### ESP8266DISABLECPU 25 | How do disable CPU and put it into deep sleep mode. It needs for debug ESP-14 26 | 27 | ### ESP14MES 28 | connect temperature/humidity/light/pressure to MQTT. 29 | 30 | ![ESP-14](https://raw.github.com/merlokk/SmartHome/master/ESP14MES/docs/img2.jpg "ESP-14") 31 | 32 | **HDC1080** - temp + humidity, **BMP280** - pressure + temp, **BH1750FVI** - light, **ESP-14** - cpu (ESP8266 + STM8003) 33 | 34 | board https://www.aliexpress.com/item/3-5V-Multi-HDC1080-Temperature-Humidity-Sensor-BMP280-Pressure-Control-Sensor-ESP8266-WIFI-BH1750FVI-Module/32742652977.html 35 | 36 | ESP-14 info https://esp8266.ru/forum/threads/esp-14-chto-ehto.531/ 37 | 38 | STM8 sensor reading part https://bitbucket.org/hrandib/esp-stm8-sensors 39 | 40 | ### ESP8266CO2PM25 41 | Air quality monitor. [Code here](https://github.com/merlokk/SmartHome/tree/master/ESP8266CO2PM25) 42 | 43 | It measures parameters: 44 | * Humidity 45 | * Temperature 46 | * Pressure 47 | * CO2 48 | * PM1.0/PM2.5/PM10 dust 49 | * Light: Visible / UV / UV index 50 | -------------------------------------------------------------------------------- /create_general_symlink.bat: -------------------------------------------------------------------------------- 1 | set drive=%~dp0 2 | set drivep=%drive% 3 | 4 | mklink %drivep%\ESP14MES\general.h %drivep%\ESP8266EASTRON\general.h 5 | mklink %drivep%\ESP14MES\general.ino %drivep%\ESP8266EASTRON\general.ino 6 | mklink %drivep%\ESP8266AZ7798\general.h %drivep%\ESP8266EASTRON\general.h 7 | mklink %drivep%\ESP8266AZ7798\general.ino %drivep%\ESP8266EASTRON\general.ino 8 | mklink %drivep%\ESP8266CO2PM25\general.h %drivep%\ESP8266EASTRON\general.h 9 | mklink %drivep%\ESP8266CO2PM25\general.ino %drivep%\ESP8266EASTRON\general.ino 10 | pause 11 | 12 | -------------------------------------------------------------------------------- /create_lib_symlink.bat: -------------------------------------------------------------------------------- 1 | set drive=%~dp0 2 | set drivep=%drive% 3 | 4 | mklink /D %drivep%\..\libraries\lib %drivep%\lib 5 | pause 6 | -------------------------------------------------------------------------------- /images/parts/ESP8266 Serial Wi-Fi ESP-01-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/ESP8266 Serial Wi-Fi ESP-01-1.jpg -------------------------------------------------------------------------------- /images/parts/ESP8266 Serial Wi-Fi ESP-01-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/ESP8266 Serial Wi-Fi ESP-01-2.jpg -------------------------------------------------------------------------------- /images/parts/ESP8266 Serial Wi-Fi ESP-01-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/ESP8266 Serial Wi-Fi ESP-01-3.jpg -------------------------------------------------------------------------------- /images/parts/ESP8266 Serial Wi-Fi ESP-01-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/ESP8266 Serial Wi-Fi ESP-01-4.jpg -------------------------------------------------------------------------------- /images/parts/ESP8266 Serial Wi-Fi ESP-01-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/ESP8266 Serial Wi-Fi ESP-01-5.jpg -------------------------------------------------------------------------------- /images/parts/ESP8266 Serial Wi-Fi ESP-01-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/ESP8266 Serial Wi-Fi ESP-01-6.jpg -------------------------------------------------------------------------------- /images/parts/ESP8266 Serial Wi-Fi ESP-01-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/ESP8266 Serial Wi-Fi ESP-01-7.jpg -------------------------------------------------------------------------------- /images/parts/ESP8266 Serial Wi-Fi ESP-01-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/ESP8266 Serial Wi-Fi ESP-01-8.jpg -------------------------------------------------------------------------------- /images/parts/Isolated-TTL-to-RS485-module-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/Isolated-TTL-to-RS485-module-1.jpg -------------------------------------------------------------------------------- /images/parts/Isolated-TTL-to-RS485-module-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/Isolated-TTL-to-RS485-module-2.jpg -------------------------------------------------------------------------------- /images/parts/Isolated-TTL-to-RS485-module-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/Isolated-TTL-to-RS485-module-3.jpg -------------------------------------------------------------------------------- /images/parts/Isolated-TTL-to-RS485-module-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/Isolated-TTL-to-RS485-module-4.jpg -------------------------------------------------------------------------------- /images/parts/Isolated-TTL-to-RS485-module-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/Isolated-TTL-to-RS485-module-5.jpg -------------------------------------------------------------------------------- /images/parts/Isolated-TTL-to-RS485-module-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/Isolated-TTL-to-RS485-module-6.jpg -------------------------------------------------------------------------------- /images/parts/Isolated-TTL-to-RS485-module-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/Isolated-TTL-to-RS485-module-7.jpg -------------------------------------------------------------------------------- /images/parts/TTL To RS485 Adapter-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/TTL To RS485 Adapter-1.jpg -------------------------------------------------------------------------------- /images/parts/TTL To RS485 Adapter-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/TTL To RS485 Adapter-2.jpg -------------------------------------------------------------------------------- /images/parts/TTL To RS485 Adapter-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/TTL To RS485 Adapter-3.jpg -------------------------------------------------------------------------------- /images/parts/TTL To RS485 Adapter-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/TTL To RS485 Adapter-4.jpg -------------------------------------------------------------------------------- /images/parts/TTL To RS485 Adapter-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/TTL To RS485 Adapter-5.jpg -------------------------------------------------------------------------------- /images/parts/TTL To RS485 Adapter-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/TTL To RS485 Adapter-6.jpg -------------------------------------------------------------------------------- /images/parts/WeMos D1 Mini Pro 16MB-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/WeMos D1 Mini Pro 16MB-1.jpg -------------------------------------------------------------------------------- /images/parts/WeMos D1 Mini Pro 16MB-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/WeMos D1 Mini Pro 16MB-2.jpg -------------------------------------------------------------------------------- /images/parts/WeMos D1 Mini Pro 16MB-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/WeMos D1 Mini Pro 16MB-3.jpg -------------------------------------------------------------------------------- /images/parts/WeMos D1 Mini Pro 16MB-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/parts/WeMos D1 Mini Pro 16MB-4.jpg -------------------------------------------------------------------------------- /images/sch_esp8266-AZ7798.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/sch_esp8266-AZ7798.png -------------------------------------------------------------------------------- /images/sch_esp8266-AZ7798_nodemcu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/sch_esp8266-AZ7798_nodemcu.png -------------------------------------------------------------------------------- /images/sch_esp8266-air-quality.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/SDM630toMQTT/4a6380e757676e07a918285244678a05ec3a69be/images/sch_esp8266-air-quality.png -------------------------------------------------------------------------------- /lib/BME280.cpp: -------------------------------------------------------------------------------- 1 | #include "bme280.h" 2 | 3 | bme280::bme280() { 4 | TextIDs = SF("n/a"); 5 | } 6 | 7 | uint8_t bme280::Reset() { 8 | uint8_t err = bme.reset(); 9 | if (err) return err; 10 | 11 | // bme.setResolution(HDC1080_RESOLUTION_11BIT, HDC1080_RESOLUTION_11BIT); 12 | return 0; 13 | } 14 | 15 | void bme280::SensorInit(){ 16 | aConnected = false; 17 | 18 | // default address bme280 - 0x77, SDO=GND=>0x76 19 | if (!bme.begin(0x76)) { 20 | DEBUG_PRINTLN(SF("BME280 sensor offline.")); 21 | return; 22 | }; 23 | 24 | bme.getLastError(); 25 | uint8_t bme280ID = bme.readManufacturerId(); 26 | uint8_t err = bme.getLastError(); 27 | if (!err && bme280ID != 0x00 && bme280ID != 0xFF) { 28 | // sensor online 29 | TextIDs = SF("BME280: manufacturerID=0x") + String(bme280ID, HEX); // 0x60 BME280 ID 30 | 31 | DEBUG_PRINTLN(TextIDs); 32 | 33 | 34 | bme.setSampling(Adafruit_BME280::MODE_FORCED, 35 | Adafruit_BME280::SAMPLING_X1, // temperature 36 | Adafruit_BME280::SAMPLING_X1, // pressure 37 | Adafruit_BME280::SAMPLING_X1, // humidity 38 | Adafruit_BME280::FILTER_OFF ); 39 | 40 | err = bme.getLastError(); 41 | if (err) { 42 | DEBUG_PRINTLN(SF("BME280 set sampling error: ") + String(err)); 43 | return; 44 | } 45 | 46 | 47 | aConnected = true; 48 | } else { 49 | TextIDs = SF("offline..."); 50 | DEBUG_PRINTLN(SF("BME280 sensor offline. error:") + String(err)); 51 | } 52 | } 53 | 54 | void bme280::begin(xLogger *_logger) { 55 | atimer.Add(TID_POLL, MILLIS_TO_POLL); 56 | 57 | SetLogger(_logger); 58 | 59 | SensorInit(); 60 | } 61 | 62 | void bme280::handle() { 63 | 64 | if (atimer.isArmed(TID_POLL)) { 65 | 66 | uint8_t err; 67 | bme.getLastError(); 68 | 69 | bme.takeForcedMeasurement(); 70 | err = bme.getLastError(); 71 | if (err) { 72 | DEBUG_PRINTLN(SF("BME280 cant take mes. error: ") + String(err)); 73 | return; 74 | } 75 | 76 | double Temp = bme.readTemperature(); 77 | double Hum = bme.readHumidity(); 78 | double Pre = bme.readPressure() / 100.0F; 79 | err = bme.getLastError(); 80 | 81 | if (!err) { 82 | aConnected = true; 83 | Temperature = Temp; 84 | Humidity = Hum; 85 | Pressure = Pre; 86 | DEBUG_PRINTLN(SF("T=") + String(Temp) + SF("C, RH=") + String(Hum) + "% P=" + String(Pre)); 87 | if (amqtt){ 88 | amqtt->PublishState(atopicT, String(Temp)); 89 | amqtt->PublishState(atopicH, String(Hum)); 90 | amqtt->PublishState(atopicP, String(Pre)); 91 | amqtt->PublishState(atopicOnline, SF("ON")); 92 | } 93 | } else { 94 | aConnected = false; 95 | if (amqtt){ 96 | amqtt->PublishState(atopicOnline, SF("OFF")); 97 | } 98 | DEBUG_PRINTLN("BME280 I2C error: " + String(err)); 99 | } 100 | 101 | atimer.Reset(TID_POLL); 102 | return; 103 | } 104 | 105 | } 106 | 107 | void bme280::SetLogger(xLogger *_logger) { 108 | logger = _logger; 109 | } 110 | 111 | void bme280::SetMQTT(xMQTT *_mqtt, String _topicOnline, String _topicT, String _topicH, String _topicP) { 112 | amqtt = _mqtt; 113 | 114 | atopicOnline = _topicOnline; 115 | atopicT = _topicT; 116 | atopicH = _topicH; 117 | atopicP = _topicP; 118 | } 119 | 120 | bool bme280::Connected() { 121 | return aConnected; 122 | } 123 | 124 | String bme280::GetTextIDs() const { 125 | return TextIDs; 126 | } 127 | 128 | float bme280::GetTemperature() const { 129 | return Temperature; 130 | } 131 | 132 | float bme280::GetHumidity() const { 133 | return Humidity; 134 | } 135 | 136 | float bme280::GetPressure() const { 137 | return Pressure; 138 | } 139 | 140 | -------------------------------------------------------------------------------- /lib/BME280.h: -------------------------------------------------------------------------------- 1 | /* 2 | * BME280 library. 3 | * Get temperature and humidity. 4 | * It works via I2C Wire object. It needs to init it first. ESP-8266 tested. 5 | * 6 | * (c) Oleg Moiseenko 2018 7 | */ 8 | 9 | #ifndef LIBBME280_H 10 | #define LIBBME280_H 11 | 12 | #include 13 | #include 14 | #include 15 | #include // logger https://github.com/merlokk/xlogger 16 | 17 | #include 18 | 19 | #include 20 | #include // original: https://github.com/adafruit/Adafruit_BME280_Library 21 | // with error handling fix: https://github.com/merlokk/Adafruit_BME280_Library 22 | 23 | #define BME_DEBUG 24 | 25 | // poll 26 | #define MILLIS_TO_POLL 10*1000 // max time to wait for poll 27 | 28 | // timers 29 | #define TID_POLL 0x0001 // timer UID for poll 30 | 31 | class bme280 { 32 | public: 33 | bme280(); 34 | 35 | void begin(xLogger *_logger); 36 | void handle(); 37 | void SetLogger(xLogger * _logger); 38 | void SetMQTT(xMQTT *_mqtt, String _topicOnline, String _topicT, String _topicH, String _topicP); 39 | uint8_t Reset(); 40 | 41 | bool Connected(); 42 | 43 | String GetTextIDs() const; 44 | float GetTemperature() const; 45 | float GetHumidity() const; 46 | float GetPressure() const; 47 | 48 | private: 49 | xLogger *logger = NULL; 50 | piTimer atimer; 51 | xMQTT *amqtt = NULL; 52 | 53 | Adafruit_BME280 bme; // I2C 54 | 55 | bool aConnected = false; 56 | // string ID 57 | String TextIDs; 58 | 59 | //mqtt topics 60 | String atopicOnline; 61 | String atopicT; 62 | String atopicH; 63 | String atopicP; 64 | 65 | // decoded measurements 66 | float Temperature; 67 | float Humidity; 68 | float Pressure; 69 | 70 | void SensorInit(); 71 | 72 | template 73 | void DEBUG_PRINTLN(Args... args); 74 | }; 75 | 76 | template 77 | void bme280::DEBUG_PRINTLN(Args... args) { 78 | #ifdef BME_DEBUG 79 | if (logger) { 80 | logger->println(args...); 81 | } 82 | #endif 83 | } 84 | 85 | #endif // LIBBME280_H 86 | -------------------------------------------------------------------------------- /lib/HDC1080.cpp: -------------------------------------------------------------------------------- 1 | #include "hdc1080.h" 2 | 3 | hdc1080::hdc1080() { 4 | TextIDs = SF("n/a"); 5 | } 6 | 7 | uint8_t hdc1080::Reset() { 8 | uint8_t err; 9 | hdc.GetLastError(); 10 | reg = hdc.readRegister(); 11 | err = hdc.GetLastError(); 12 | if (err) return err; 13 | 14 | reg.SoftwareReset = 1; 15 | 16 | hdc.writeRegister(reg); 17 | err = hdc.GetLastError(); 18 | if (err) return err; 19 | 20 | delay(20); 21 | 22 | reg = hdc.readRegister(); 23 | err = hdc.GetLastError(); 24 | if (err) return err; 25 | 26 | hdc.setResolution(HDC1080_RESOLUTION_11BIT, HDC1080_RESOLUTION_11BIT); 27 | return 0; 28 | } 29 | 30 | void hdc1080::SensorInit(){ 31 | // default address hdc1080 - 0x40 32 | hdc.begin(0x40); 33 | 34 | hdc.GetLastError(); 35 | uint16_t HDC1080MID = hdc.readManufacturerId(); 36 | uint8_t err = hdc.GetLastError(); 37 | if (!err && HDC1080MID != 0x0000 && HDC1080MID != 0xFFFF) { 38 | // sensor online 39 | char HDCSerial[13] = {0}; 40 | HDC1080_SerialNumber sernum = hdc.readSerialNumber(); 41 | sprintf(HDCSerial, "%02X-%04X-%04X", sernum.serialFirst, sernum.serialMid, sernum.serialLast); 42 | TextIDs = SF("HDC1080: manufacturerID=0x") + String(HDC1080MID, HEX) + // 0x5449 ID of Texas Instruments 43 | SF(" deviceID=0x") + String(hdc.readDeviceId(), HEX) + // 0x1050 ID of the device 44 | SF(" serial=") + String(HDCSerial); 45 | 46 | DEBUG_PRINTLN(TextIDs); 47 | 48 | // hdc1080.heatUp(10); // heating every start -- not cool. needs to have test.... 49 | hdc.setResolution(HDC1080_RESOLUTION_11BIT, HDC1080_RESOLUTION_11BIT); 50 | aConnected = true; 51 | } else { 52 | TextIDs = SF("offline..."); 53 | DEBUG_PRINTLN(SF("HDC1080 sensor offline. error:") + String(err)); 54 | aConnected = false; 55 | } 56 | } 57 | 58 | void hdc1080::begin(xLogger *_logger) { 59 | atimer.Add(TID_POLL, MILLIS_TO_POLL); 60 | 61 | SetLogger(_logger); 62 | 63 | SensorInit(); 64 | } 65 | 66 | void hdc1080::handle() { 67 | 68 | if (atimer.isArmed(TID_POLL)) { 69 | reg = hdc.readRegister(); 70 | DEBUG_PRINTLN(SF("Heater: ") + String(reg.Heater, BIN)); 71 | if (amqtt && atopicHeater.length() > 0){ 72 | amqtt->PublishState(atopicHeater, String(reg.Heater)); 73 | } 74 | 75 | /* if (reg.Heater) { 76 | DEBUG_PRINTLN("Try to clear heating state..."); 77 | reg.Heater = 0; 78 | hdc1080.writeRegister(reg); 79 | }*/ 80 | 81 | double Temp = hdc.readTemperature(); 82 | double Hum = hdc.readHumidity(); 83 | uint8_t err = hdc.GetLastError(); 84 | if (err == 100) { 85 | DEBUG_PRINTLN(SF("Try to reset...")); 86 | err = Reset(); 87 | if (err) { 88 | DEBUG_PRINTLN(SF("Reset error: ") + String(err)); 89 | } else { 90 | Temp = hdc.readTemperature(); 91 | Hum = hdc.readHumidity(); 92 | err = hdc.GetLastError(); 93 | } 94 | } 95 | if (!err) { 96 | Hum -= HUMIDITY_DELTA; 97 | aConnected = true; 98 | Temperature = Temp; 99 | Humidity = Hum; 100 | DEBUG_PRINTLN(SF("T=") + String(Temp) + SF("C, RH=") + String(Hum) + "%"); 101 | if (amqtt){ 102 | amqtt->PublishState(atopicT, String(Temp)); 103 | amqtt->PublishState(atopicH, String(Hum)); 104 | amqtt->PublishState(atopicOnline, SF("ON")); 105 | } 106 | } else { 107 | aConnected = false; 108 | if (amqtt){ 109 | amqtt->PublishState(atopicOnline, SF("OFF")); 110 | } 111 | DEBUG_PRINTLN("HDC1080 I2C error: " + String(err)); 112 | } 113 | 114 | atimer.Reset(TID_POLL); 115 | return; 116 | } 117 | 118 | } 119 | 120 | void hdc1080::SetLogger(xLogger *_logger) { 121 | logger = _logger; 122 | } 123 | 124 | void hdc1080::SetMQTT(xMQTT *_mqtt, String _topicOnline, String _topicT, String _topicH, String _topicHeater) { 125 | amqtt = _mqtt; 126 | 127 | atopicOnline = _topicOnline; 128 | atopicT = _topicT; 129 | atopicH = _topicH; 130 | atopicHeater = _topicHeater; 131 | } 132 | 133 | bool hdc1080::Connected() { 134 | return aConnected; 135 | } 136 | 137 | String hdc1080::GetTextIDs() const { 138 | return TextIDs; 139 | } 140 | 141 | float hdc1080::GetTemperature() const { 142 | return Temperature; 143 | } 144 | 145 | float hdc1080::GetHumidity() const { 146 | return Humidity; 147 | } 148 | 149 | -------------------------------------------------------------------------------- /lib/HDC1080.h: -------------------------------------------------------------------------------- 1 | /* 2 | * HDC1080 library. 3 | * Get temperature and humidity. 4 | * It works via I2C Wire object. It needs to init it first. ESP-8266 tested. 5 | * 6 | * (c) Oleg Moiseenko 2018 7 | */ 8 | 9 | #ifndef HDC1080_H 10 | #define HDC1080_H 11 | 12 | #include 13 | #include 14 | #include 15 | #include // logger https://github.com/merlokk/xlogger 16 | 17 | #include 18 | 19 | #include 20 | #include // original: https://github.com/closedcube/ClosedCube_HDC1080_Arduino 21 | // with error handling fix: https://github.com/merlokk/ClosedCube_HDC1080_Arduino 22 | 23 | #define HDC_DEBUG 24 | 25 | // humidity correction coefficient 26 | #define HUMIDITY_DELTA 5 27 | 28 | // poll 29 | #define MILLIS_TO_POLL 10*1000 // max time to wait for poll 30 | 31 | // timers 32 | #define TID_POLL 0x0001 // timer UID for poll 33 | 34 | class hdc1080 { 35 | public: 36 | hdc1080(); 37 | 38 | void begin(xLogger *_logger); 39 | void handle(); 40 | void SetLogger(xLogger * _logger); 41 | void SetMQTT(xMQTT *_mqtt, String _topicOnline, String _topicT, String _topicH, String _topicHeater); 42 | uint8_t Reset(); 43 | 44 | bool Connected(); 45 | 46 | String GetTextIDs() const; 47 | float GetTemperature() const; 48 | float GetHumidity() const; 49 | 50 | private: 51 | xLogger *logger = NULL; 52 | piTimer atimer; 53 | xMQTT *amqtt = NULL; 54 | 55 | ClosedCube_HDC1080 hdc; 56 | 57 | bool aConnected = false; 58 | // string ID 59 | String TextIDs; 60 | // readed registers 61 | HDC1080_Registers reg; 62 | //mqtt topics 63 | String atopicOnline; 64 | String atopicT; 65 | String atopicH; 66 | String atopicHeater; 67 | 68 | // decoded measurements 69 | float Temperature; 70 | float Humidity; 71 | 72 | void SensorInit(); 73 | 74 | template 75 | void DEBUG_PRINTLN(Args... args); 76 | }; 77 | 78 | template 79 | void hdc1080::DEBUG_PRINTLN(Args... args) { 80 | #ifdef HDC_DEBUG 81 | if (logger) { 82 | logger->println(args...); 83 | } 84 | #endif 85 | } 86 | 87 | #endif // HDC1080_H 88 | -------------------------------------------------------------------------------- /lib/autotimezone.cpp: -------------------------------------------------------------------------------- 1 | #include "autotimezone.h" 2 | 3 | AutoTimeZone::AutoTimeZone() { 4 | } 5 | 6 | void AutoTimeZone::begin(xLogger *_logger) { 7 | SetLogger(_logger); 8 | 9 | ttimer.Add(TID_ERROR_TIMEOUT, ERROR_TIMEOUT); 10 | state = tzWait; 11 | } 12 | 13 | // 2017-05-17T14:32:25Z 14 | String GetJSDate(time_t dt) { 15 | String s = IntToStr(year(dt), 4) + '-' + IntToStr(month(dt)) + '-' + IntToStr(day(dt)) + 'T' + 16 | IntToStr(hour(dt)) + ':' + IntToStr(minute(dt)) + ':' + IntToStr(second(dt)) + 'Z'; 17 | return s; 18 | } 19 | 20 | void AutoTimeZone::handle() { 21 | if (state == tzInit) 22 | return; 23 | 24 | switch (state) { 25 | case tzWait: 26 | if (ttimer.isArmed(TID_ERROR_TIMEOUT)) { 27 | state = tzStage1Send; 28 | } 29 | break; 30 | 31 | case tzStage1Send: { 32 | DEBUG_PRINTLN(SF("TZ:HTTP GET stage 1")); 33 | HTTPClient http; 34 | http.begin(SF("http://ip-api.com/json")); 35 | int httpCode = http.GET(); 36 | if(httpCode > 0) { 37 | DEBUG_PRINTLN(SF("TZ:HTTP GET ok: ") + String(httpCode)); 38 | 39 | // file found at server 40 | if(httpCode == HTTP_CODE_OK) { 41 | if (ProcessStage1(http.getString())) { 42 | state = tzWaitStage2; 43 | } else { 44 | ttimer.Reset(TID_ERROR_TIMEOUT); 45 | state = tzWait; 46 | } 47 | } 48 | } else { 49 | DEBUG_PRINTLN(llError, SF("TZ:HTTP GET error: ") + http.errorToString(httpCode)); 50 | } 51 | 52 | http.end(); 53 | break; 54 | } 55 | 56 | case tzWaitStage2: 57 | if (timeStatus() == timeSet && ttimer.isArmed(TID_ERROR_TIMEOUT)) { 58 | state = tzStage2Send; 59 | } 60 | 61 | break; 62 | 63 | case tzStage2Send:{ 64 | HTTPClient http; 65 | String ia = IanaTimezone; 66 | DEBUG_PRINTLN(SF("TZ:HTTP GET stage 2. iana:") + ia + SF(" now:") + GetJSDate(now())); 67 | ia.replace("/", "%2F"); 68 | http.begin(SF("http://api.teleport.org/api/timezones/iana:") + ia + SF("/offsets/?date=") + GetJSDate(now())); 69 | int httpCode = http.GET(); 70 | if(httpCode > 0) { 71 | DEBUG_PRINTLN(SF("TZ:HTTP GET ok: ") + String(httpCode)); 72 | 73 | // file found at server 74 | if(httpCode == HTTP_CODE_OK) { 75 | String str = http.getString(); 76 | String strOut = ""; 77 | 78 | // short json 79 | int bCnt = 0; 80 | int bFrom = -1; 81 | for (int i = 0; i < str.length(); i++) { 82 | if (str[i] == '{') 83 | bCnt ++; 84 | if (str[i] == '}') 85 | bCnt --; 86 | 87 | if (bCnt == 2 && bFrom < 0) { 88 | bFrom = i; 89 | } 90 | 91 | if (bCnt == 1 && bFrom >= 0) { 92 | strOut = str.substring(0, bFrom) + "[]" + str.substring(i + 1); 93 | break; 94 | } 95 | 96 | if (i == str.length() - 1) 97 | strOut = str; 98 | } 99 | 100 | // process json 101 | if (ProcessStage2(strOut)) { 102 | state = tzGotResponse; 103 | } else { 104 | ttimer.Reset(TID_ERROR_TIMEOUT); 105 | state = tzWaitStage2; 106 | } 107 | } 108 | } else { 109 | DEBUG_PRINTLN(llError, SF("TZ:HTTP GET error: ") + http.errorToString(httpCode)); 110 | } 111 | 112 | http.end(); 113 | break; 114 | } 115 | 116 | case tzGotResponse: { 117 | String s; 118 | GetStr(s); 119 | DEBUG_PRINTLN(SF("TZ: ok. ") + s); 120 | 121 | // all is OK 122 | GotData = true; 123 | 124 | state = tzSleep; 125 | break; 126 | } 127 | 128 | case tzSleep: 129 | break; 130 | 131 | default: 132 | break; 133 | } 134 | 135 | } 136 | 137 | String AutoTimeZone::getIanaTimezone() const { 138 | return IanaTimezone; 139 | } 140 | 141 | String AutoTimeZone::getTimezoneShortName() const { 142 | return TimezoneShortName; 143 | } 144 | 145 | int AutoTimeZone::getBaseOffset() const { 146 | return BaseOffset; 147 | } 148 | 149 | int AutoTimeZone::getDSTOffset() const { 150 | return DSTOffset; 151 | } 152 | 153 | int AutoTimeZone::getCurrentOffset() const { 154 | return CurrentOffset; 155 | } 156 | 157 | void AutoTimeZone::GetStr(String &s) { 158 | s = IanaTimezone + " [" + TimezoneShortName + "] offset:" + String(CurrentOffset) + 159 | " b/d: " + String(BaseOffset) + "/" + String(DSTOffset) + 160 | " IP:" + IP + " (" + CountryCode + ")" + Country + "-" + City + 161 | " c:" + lat + ":" + lon; 162 | } 163 | 164 | bool AutoTimeZone::ProcessStage1(const String &str) { 165 | StaticJsonBuffer jsonBuffer; 166 | JsonObject& root = jsonBuffer.parseObject(str); 167 | if (!root.success()) { 168 | DEBUG_PRINTLN(llError, SF("AZ: error loading json.")); 169 | return false; 170 | } 171 | 172 | IP = root.get("query"); 173 | 174 | if (root["status"] != "success") { 175 | DEBUG_PRINTLN(llError, SF("AZ: server returned error json: ") + root["message"].as()); 176 | return false; 177 | } 178 | 179 | lat = root["lat"].as(); 180 | lon = root["lon"].as(); 181 | Country = root["country"].as(); 182 | CountryCode = root["countryCode"].as(); 183 | City = root["city"].as(); 184 | IanaTimezone = root["timezone"].as(); 185 | 186 | return true; 187 | } 188 | 189 | bool AutoTimeZone::ProcessStage2(const String &str) { 190 | StaticJsonBuffer jsonBuffer; 191 | JsonObject& root = jsonBuffer.parseObject(str); 192 | if (!root.success()) { 193 | DEBUG_PRINTLN(llError, SF("AZ: error loading json.")); 194 | return false; 195 | } 196 | 197 | if (root["total_offset_min"] == "") { 198 | DEBUG_PRINTLN(llError, SF("AZ: server returned no data.")); 199 | return false; 200 | } 201 | 202 | TimezoneShortName = root["short_name"].as(); 203 | BaseOffset = root["base_offset_min"].as(); 204 | DSTOffset = root["dst_offset_min"].as(); 205 | CurrentOffset = root["total_offset_min"].as(); 206 | 207 | return true; 208 | } 209 | 210 | void AutoTimeZone::SetLogger(xLogger *value) 211 | { 212 | logger = value; 213 | } 214 | -------------------------------------------------------------------------------- /lib/autotimezone.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AZ7798 library. 3 | * Get measurements and set date time. 4 | * It works via HardwareSerial/SoftwareSerial. ESP-8266 tested. 5 | * 6 | * (c) Oleg Moiseenko 2017 7 | */ 8 | 9 | #ifndef AUTOTIMEZONE_H 10 | #define AUTOTIMEZONE_H 11 | 12 | #include 13 | #include 14 | #include // https://github.com/bblanchon/ArduinoJson 15 | #include // https://github.com/PaulStoffregen/Time 16 | #include // https://github.com/gmag11/NtpClient 17 | #include // logger https://github.com/merlokk/xlogger 18 | #include 19 | #include 20 | 21 | // stage 1 22 | // http://ip-api.com/json 23 | // http://freegeoip.net/json/ 24 | // stage 2 25 | // https://api.teleport.org/api/timezones/iana:Europe%2FKiev/offsets/?date=2017-05-17T09:25:41Z 26 | 27 | #define TZ_DEBUG 28 | #define JSON_OBJ_BUFFER_LEN 1024 29 | 30 | // timers 31 | #define ERROR_TIMEOUT 20000 32 | #define TID_ERROR_TIMEOUT 0x0001 // timer UID for error timeout 33 | 34 | enum TimeZoneState { 35 | tzInit = 0x00, 36 | tzWait = 0x01, 37 | tzStage1Send = 0x02, 38 | tzWaitStage2 = 0x03, 39 | tzStage2Send = 0x04, 40 | tzGotResponse = 0x05, 41 | tzSleep = 0x06 42 | }; 43 | 44 | class AutoTimeZone { 45 | public: 46 | AutoTimeZone(); 47 | 48 | bool GotData = false; 49 | 50 | void begin(xLogger *_logger); 51 | void handle(); 52 | void SetLogger(xLogger *value); 53 | 54 | String getIanaTimezone() const; 55 | String getTimezoneShortName() const; 56 | int getBaseOffset() const; 57 | int getDSTOffset() const; 58 | int getCurrentOffset() const; 59 | 60 | void GetStr(String &s); 61 | 62 | private: 63 | xLogger *logger = NULL; 64 | piTimer ttimer; 65 | 66 | TimeZoneState state = tzInit; 67 | 68 | String lat; 69 | String lon; 70 | String IP; 71 | String Country; 72 | String CountryCode; 73 | String City; 74 | String IanaTimezone; 75 | String TimezoneShortName; 76 | int BaseOffset; 77 | int DSTOffset; 78 | int CurrentOffset; 79 | bool ProcessStage1(const String &str); 80 | bool ProcessStage2(const String &str); 81 | 82 | template 83 | void DEBUG_PRINTLN(Args... args); 84 | }; 85 | 86 | template 87 | void AutoTimeZone::DEBUG_PRINTLN(Args... args) { 88 | #ifdef TZ_DEBUG 89 | if (logger) { 90 | logger->println(args...); 91 | } 92 | #endif 93 | } 94 | 95 | #endif // AUTOTIMEZONE_H 96 | -------------------------------------------------------------------------------- /lib/az7798.cpp: -------------------------------------------------------------------------------- 1 | #include "az7798.h" 2 | 3 | #ifndef AUTO_TIMEZONE 4 | //UA Ukraine 5 | TimeChangeRule myDST = {"EEST", Last, Sun, Mar, 3, 180}; //Daylight time = UTC + 3 hours 6 | TimeChangeRule mySTD = {"EET", Last, Sun, Oct, 4, 120}; //Standard time = UTC + 2 hours 7 | Timezone myTZ(myDST, mySTD); 8 | #endif 9 | 10 | az7798::az7798() { 11 | responseBuffer.reserve(22); 12 | } 13 | 14 | void az7798::begin(Stream *_serial, xLogger *_logger) { 15 | atimer.Add(TID_POLL, MILLIS_TO_POLL); 16 | atimer.Add(TID_SET_TIME, MILLIS_TO_SET_TIME); 17 | atimer.Add(TID_TIMEOUT, MILLIS_TIMEOUT); 18 | 19 | SetSerial(_serial); 20 | SetLogger(_logger); 21 | 22 | #ifdef AUTO_TIMEZONE 23 | atz.begin(logger); 24 | #endif 25 | state = asWait; 26 | } 27 | 28 | void az7798::handle() { 29 | if (state == asInit || !serial) 30 | return; 31 | 32 | #ifdef AUTO_TIMEZONE 33 | atz.handle(); 34 | #endif 35 | 36 | switch (state) { 37 | case asWait: 38 | if (!atimer.isArmed(TID_POLL)) { 39 | break; 40 | } 41 | 42 | if (processingCommand == acNone){ 43 | // get version 44 | if (Version.length() == 0) { 45 | SendCommand(acGetVersion); 46 | return; 47 | } 48 | 49 | // set time 50 | if (timeStatus() == timeSet && atimer.isArmed(TID_SET_TIME)) { 51 | SendCommand(acSetDateTime); 52 | atimer.Reset(TID_SET_TIME); 53 | return; 54 | } 55 | 56 | // get measurements 57 | SendCommand(acGetMeasurements); 58 | return; 59 | } else { 60 | processingCommand = acNone; 61 | } 62 | 63 | 64 | break; 65 | 66 | case asSentCommand: 67 | while (serial->available()) { 68 | char c = serial->read(); 69 | if (c == '\r' || c == '\0') { 70 | state = asGotResponse; 71 | break; 72 | } 73 | responseBuffer += c; 74 | } 75 | 76 | if (atimer.isArmed(TID_TIMEOUT)) { 77 | state = asTimeout; 78 | } 79 | break; 80 | 81 | case asGotResponse: 82 | ProcessCommand(processingCommand); 83 | 84 | atimer.Reset(TID_POLL); 85 | processingCommand = acNone; 86 | state = asWait; 87 | break; 88 | 89 | case asTimeout: 90 | DEBUG_PRINTLN(llError, SF("Receive command timeout. Buffer:") + responseBuffer); 91 | 92 | atimer.Reset(TID_POLL); 93 | processingCommand = acNone; 94 | state = asWait; 95 | break; 96 | 97 | default: 98 | break; 99 | } 100 | } 101 | 102 | void az7798::SetLogger(xLogger *_logger) { 103 | logger = _logger; 104 | } 105 | 106 | void az7798::SetSerial(Stream *_serial) { 107 | serial = _serial; 108 | } 109 | 110 | bool az7798::Connected() { 111 | return millis() - LastGetMeasurements < MILLIS_TO_POLL * 3; 112 | } 113 | 114 | String az7798::GetVersion() const { 115 | return Version; 116 | } 117 | 118 | String az7798::GetMeasurements() const { 119 | return Measurements; 120 | } 121 | 122 | float az7798::GetTemperature() const { 123 | return Temperature; 124 | } 125 | 126 | float az7798::GetHumidity() const { 127 | return Humidity; 128 | } 129 | 130 | int az7798::GetCO2() const { 131 | return CO2; 132 | } 133 | 134 | void az7798::SendCommand(AZProcessCommands cmd) { 135 | serial->flush(); 136 | 137 | switch (cmd) { 138 | case acNone: 139 | processingCommand = acNone; 140 | break; 141 | 142 | case acGetVersion: 143 | serial->print("I\r"); 144 | DEBUG_PRINTLN(SF("Sent command: info.")); 145 | processingCommand = acGetVersion; 146 | state = asSentCommand; 147 | break; 148 | 149 | case acGetDateTime: 150 | //can't ( 151 | break; 152 | 153 | case acSetDateTime: 154 | // number of seconds from 1 jan 2000. 155 | // ">" 156 | #ifdef AUTO_TIMEZONE 157 | if (timeStatus() == timeSet && atz.GotData) { 158 | time_t dt = now(); 159 | 160 | dt = dt + (atz.getCurrentOffset() * 60); // offset in minutes 161 | #else 162 | if (timeStatus() == timeSet) { 163 | time_t dt = now(); 164 | 165 | dt = myTZ.toLocal(dt); 166 | // dt = dt + (TIMEZONE * 60 * 60); 167 | #endif 168 | int32_t azdt = dt - TIMESTAMP_01_01_2000; 169 | 170 | serial->print("C " + String(azdt) + "\r"); 171 | DEBUG_PRINTLN(SF("Sent command: set datetime ") + String(azdt) + " dt:" + NTP.getTimeDateString(dt)); 172 | processingCommand = acSetDateTime; 173 | state = asSentCommand; 174 | } 175 | break; 176 | 177 | case acGetMeasurements: 178 | //": T20.4C:C1753ppm:H47.5%" 179 | serial->print(":\r"); 180 | DEBUG_PRINTLN(SF("Sent command: GetMeasurements.")); 181 | processingCommand = acGetMeasurements; 182 | state = asSentCommand; 183 | break; 184 | 185 | default: 186 | processingCommand = acNone; 187 | break; 188 | } 189 | 190 | responseBuffer = ""; 191 | while (serial->read() != -1); 192 | atimer.Reset(TID_POLL); 193 | atimer.Reset(TID_TIMEOUT); 194 | } 195 | 196 | void az7798::ProcessCommand(AZProcessCommands cmd) { 197 | switch (cmd) { 198 | case acNone: 199 | break; 200 | 201 | case acGetVersion: 202 | Version = responseBuffer; 203 | if (Version.startsWith("i ")) { 204 | Version = Version.substring(2); 205 | } else { 206 | DEBUG_PRINTLN(llWarning, SF("AZ strange version information...")); 207 | } 208 | DEBUG_PRINTLN(SF("AZ version: ") + Version); 209 | break; 210 | 211 | case acGetDateTime: 212 | break; 213 | 214 | case acSetDateTime: 215 | // number of seconds from 1 jan 2000. 216 | // ">" 217 | if (responseBuffer == ">") { 218 | DEBUG_PRINTLN(SF("AZ time set.")); 219 | } else { 220 | DEBUG_PRINTLN(llError, SF("AZ time set error:") + responseBuffer); 221 | } 222 | break; 223 | 224 | case acGetMeasurements: 225 | if (responseBuffer.startsWith(": ")) { 226 | Measurements = responseBuffer.substring(2); 227 | DEBUG_PRINTLN(SF("AZ mes: ") + Measurements); 228 | if (Measurements.length() >= 15) 229 | ExtractMeasurements(); 230 | LastGetMeasurements = millis(); 231 | } else { 232 | DEBUG_PRINTLN(llError, SF("AZ invalid mes data:") + String(responseBuffer)); 233 | } 234 | break; 235 | 236 | default: 237 | break; 238 | } 239 | 240 | processingCommand = acNone; 241 | 242 | responseBuffer = ""; 243 | while (serial->read() != -1); 244 | } 245 | 246 | void ExtractStr(const String &in, String &out, const String &sFrom, const String &sTo) { 247 | out = ""; 248 | 249 | int i1 = in.indexOf(sFrom); 250 | int i2 = in.indexOf(sTo); 251 | if (i1 < 0 || i2 < 0 || i1 >= i2) { 252 | return; 253 | } 254 | 255 | out = in.substring(i1 + sFrom.length(), i2); 256 | } 257 | 258 | void az7798::ExtractMeasurements() { 259 | Temperature = 0; 260 | Humidity = 0; 261 | CO2 = 0; 262 | 263 | String s; 264 | ExtractStr(Measurements, s, "T", "C:"); 265 | Temperature = s.toFloat(); 266 | ExtractStr(Measurements, s, ":C", "ppm"); 267 | CO2 = s.toInt(); 268 | ExtractStr(Measurements, s, "H", "%"); 269 | Humidity = s.toFloat(); 270 | } 271 | -------------------------------------------------------------------------------- /lib/az7798.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AZ7798 library. 3 | * Get measurements and set date time. 4 | * It works via HardwareSerial/SoftwareSerial. ESP-8266 tested. 5 | * 6 | * (c) Oleg Moiseenko 2017 7 | */ 8 | 9 | #ifndef AZ7798_H 10 | #define AZ7798_H 11 | 12 | #include 13 | #include // https://github.com/PaulStoffregen/Time 14 | #include // https://github.com/gmag11/NtpClient 15 | #include 16 | #include 17 | #include // logger https://github.com/merlokk/xlogger 18 | 19 | #define AZ_DEBUG 20 | 21 | // time zones 22 | #define AUTO_TIMEZONE 23 | #ifdef AUTO_TIMEZONE 24 | #include 25 | #else 26 | #include //https://github.com/JChristensen/Timezone 27 | #endif 28 | 29 | #define TIMESTAMP_01_01_2000 946684800 // start time of AZ time. 01/01/2000 @ 12:00am (UTC) 30 | 31 | // poll 32 | #define MILLIS_TO_POLL 15*1000 // max time to wait for poll 33 | #define MILLIS_TO_SET_TIME 12*60*60*1000 // settime interval 12 hours 34 | #define MILLIS_TIMEOUT 700 // AZ response timeout 35 | // timers 36 | #define TID_POLL 0x0001 // timer UID for poll 37 | #define TID_SET_TIME 0x0002 // timer UID for set time 38 | #define TID_TIMEOUT 0x0003 // timer UID for response timeout 39 | 40 | enum AZState { 41 | asInit = 0x00, 42 | asWait = 0x01, 43 | asSentCommand = 0x02, 44 | asGotResponse = 0x03, 45 | asTimeout = 0x04 46 | }; 47 | 48 | enum AZProcessCommands { 49 | acNone = 0x00, 50 | acGetVersion = 0x01, 51 | acGetDateTime = 0x02, 52 | acSetDateTime = 0x03, 53 | acGetMeasurements = 0x04 54 | }; 55 | 56 | class az7798 { 57 | public: 58 | az7798(); 59 | 60 | void begin(Stream *_serial = NULL, xLogger *_logger = NULL); 61 | void handle(); 62 | void SetLogger(xLogger * _logger); 63 | void SetSerial(Stream *_serial); 64 | 65 | //last connect to AZ 66 | int LastGetMeasurements = -MILLIS_TO_POLL * 4; 67 | bool Connected(); 68 | 69 | String GetVersion() const; 70 | String GetMeasurements() const; 71 | float GetTemperature() const; 72 | float GetHumidity() const; 73 | int GetCO2() const; 74 | 75 | private: 76 | xLogger *logger = NULL; 77 | Stream *serial = NULL; 78 | piTimer atimer; 79 | 80 | AZState state = asInit; 81 | AZProcessCommands processingCommand = acNone; 82 | String responseBuffer; 83 | 84 | // strings from AZ 85 | String Version; 86 | String DateTime; 87 | bool SetDateTime; 88 | String Measurements; 89 | // decoded measurements 90 | float Temperature; 91 | float Humidity; 92 | int CO2; 93 | 94 | void SendCommand(AZProcessCommands cmd); 95 | void ProcessCommand(AZProcessCommands cmd); 96 | void ExtractMeasurements(); 97 | 98 | #ifdef AUTO_TIMEZONE 99 | AutoTimeZone atz; 100 | #endif 101 | template 102 | void DEBUG_PRINTLN(Args... args); 103 | }; 104 | 105 | template 106 | void az7798::DEBUG_PRINTLN(Args... args) { 107 | #ifdef AZ_DEBUG 108 | if (logger) { 109 | logger->println(args...); 110 | } 111 | #endif 112 | } 113 | 114 | #endif // AZ7798_H 115 | -------------------------------------------------------------------------------- /lib/eastron.h: -------------------------------------------------------------------------------- 1 | #ifndef EASTRON_H 2 | #define EASTRON_H 3 | 4 | /////////////////////////////////////////////////////////////////////////// 5 | // EASTRON modbus addresses 6 | // original here https://github.com/beireken/SDM220t/blob/master/SDM.h 7 | /////////////////////////////////////////////////////////////////////////// 8 | 9 | //SDM 220 registers 10 | #define SDM220T_VOLTAGE 0x0000 //V 11 | #define SDM220T_CURRENT 0x0006 //A 12 | #define SDM220T_POWER 0x000C //W 13 | #define SDM220T_ACTIVE_APPARENT_POWER 0x0012 //VA 14 | #define SDM220T_REACTIVE_APPARENT_POWER 0x0018 //VAR 15 | #define SDM220T_POWER_FACTOR 0x001E // 16 | #define SDM220T_PHASE_ANGLE 0x0024 //DEGREE 17 | #define SDM220T_FREQUENCY 0x0046 //Hz 18 | #define SDM220T_IMPORT_ACTIVE_ENERGY 0x0048 //Wh 19 | #define SDM220T_EXPORT_ACTIVE_ENERGY 0x004A //Wh 20 | #define SDM220T_IMPORT_REACTIVE_ENERGY 0x004C //VARh 21 | #define SDM220T_EXPORT_REACTIVE_ENERGY 0x004E //VARh 22 | #define SDM220T_TOTAL_ACTIVE_ENERGY 0x0156 //Wh 23 | #define SDM220T_TOTAL_REACTIVE_ENERGY 0x0158 //VARh 24 | //SDM 630 registers 25 | #define SDM630_VOLTAGE1 0x0000 //V 26 | #define SDM630_VOLTAGE2 0x0002 //V 27 | #define SDM630_VOLTAGE3 0x0004 //V 28 | #define SDM630_CURRENT1 0x0006 //A 29 | #define SDM630_CURRENT2 0x0008 //A 30 | #define SDM630_CURRENT3 0x000A //A 31 | #define SDM630_CURRENT_TOTAL 0x0030 //A 32 | #define SDM630_POWER1 0x000C //W 33 | #define SDM630_POWER2 0x000E //W 34 | #define SDM630_POWER3 0x0010 //W 35 | #define SDM630_POWER_TOTAL 0x0034 //W 36 | #define SDM630_VOLT_AMPS1 0x0012 //VA 37 | #define SDM630_VOLT_AMPS2 0x0014 //VA 38 | #define SDM630_VOLT_AMPS3 0x0016 //VA 39 | #define SDM630_VOLT_AMPS_TOTAL 0x0038 //VA 40 | #define SDM630_VOLT_AMPS_REACTIVE1 0x0018 //VAr 41 | #define SDM630_VOLT_AMPS_REACTIVE2 0x001A //VAr 42 | #define SDM630_VOLT_AMPS_REACTIVE3 0x001C //VAr 43 | #define SDM630_VOLT_AMPS_REACTIVE_TOTAL 0x003C //VAr 44 | #define SDM630_POWER_FACTOR1 0x001E 45 | #define SDM630_POWER_FACTOR2 0x0020 46 | #define SDM630_POWER_FACTOR3 0x0022 47 | #define SDM630_POWER_FACTOR_TOTAL 0x003E 48 | #define SDM630_PHASE_ANGLE1 0x0024 //Degrees 49 | #define SDM630_PHASE_ANGLE2 0x0026 //Degrees 50 | #define SDM630_PHASE_ANGLE3 0x0028 //Degrees 51 | #define SDM630_PHASE_ANGLE_TOTAL 0x0042 //Degrees 52 | #define SDM630_VOLTAGE_AVERAGE 0x002A //V 53 | #define SDM630_CURRENT_AVERAGE 0x002E //A 54 | #define SDM630_FREQUENCY 0x0046 //HZ 55 | #define SDM630_IMPORT_ACTIVE_ENERGY 0x0048 //Wh 56 | #define SDM630_EXPORT_ACTIVE_ENERGY 0x004A //Wh 57 | #define SDM630_IMPORT_REACTIVE_ENERGY 0x004C //VARh 58 | #define SDM630_EXPORT_REACTIVE_ENERGY 0x004E //VARh 59 | #define SDM630_TOTAL_SYSTEM_POWER_DEMAND 0x0054 //W 60 | #define SDM630_MAXIMUM_TOTAL_SYSTEM_POWER 0x0056 //W 61 | 62 | 63 | #endif // EASTRON_H 64 | -------------------------------------------------------------------------------- /lib/emodbus.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | static uint16_t crc16_update(uint16_t crc, uint8_t a) { 7 | int i; 8 | 9 | crc ^= a; 10 | for (i = 0; i < 8; ++i) { 11 | if (crc & 1) 12 | crc = (crc >> 1) ^ 0xA001; 13 | else 14 | crc = (crc >> 1); 15 | } 16 | 17 | return crc; 18 | } 19 | 20 | void strModbusError(String &str, int error) { 21 | str = SF("[0x") + String(error, HEX) + SF("] "); 22 | switch (error) { 23 | case MBSuccess: str += SF("Success."); break; 24 | case MBIllegalFunction: str += SF("Illegal Function."); break; 25 | case MBIllegalDataAddress: str += SF("Illegal Data Address."); break; 26 | case MBIllegalDataValue: str += SF("Illegal Data Value."); break; 27 | case MBSlaveDeviceFailure: str += SF("Slave Device Failure."); break; 28 | case MBInvalidSlaveID: str += SF("Invalid Slave ID."); break; 29 | case MBInvalidFunction: str += SF("Invalid Func."); break; 30 | case MBResponseTimedOut: str += SF("Response Timeout."); break; 31 | case MBInvalidCRC: str += SF("Invalid CRC."); break; 32 | case MBInvalidPacketLength: str += SF("Invalid Packet Length."); break; 33 | default: str += SF("Unknown error."); 34 | } 35 | } 36 | 37 | ModbusMaster::ModbusMaster(void) { 38 | _idle = NULL; 39 | _preTransmission = NULL; 40 | _postTransmission = NULL; 41 | 42 | _serial = NULL; 43 | } 44 | 45 | void ModbusMaster::begin(Stream *serial, int SerialSpeed) 46 | { 47 | _serial = serial; 48 | 49 | #ifdef USE_SOFTWARE_SERIAL 50 | static_cast(serial)->begin(SerialSpeed); 51 | #else 52 | static_cast(serial)->begin(SerialSpeed); 53 | #endif 54 | } 55 | 56 | // callbacks 57 | void ModbusMaster::idle(void (*idle)()) 58 | { 59 | _idle = idle; 60 | } 61 | 62 | void ModbusMaster::preTransmission(void (*preTransmission)()) 63 | { 64 | _preTransmission = preTransmission; 65 | } 66 | 67 | void ModbusMaster::postTransmission(void (*postTransmission)()) 68 | { 69 | _postTransmission = postTransmission; 70 | } 71 | 72 | uint8_t ModbusMaster::ModbusMasterTransaction( 73 | uint8_t u8MBSlave, 74 | uint8_t u8MBFunction, uint16_t u16ReadAddress, uint16_t u16ReadQty, 75 | uint8_t *_u8ReceiveBuffer, 76 | uint16_t *u16TransmitBuffer, uint16_t u16WriteAddress, uint16_t u16WriteQty) 77 | { 78 | // Serial1.print("Modbus poll. F="); 79 | // Serial1.print(u8MBFunction); 80 | // Serial1.print(" Addr="); 81 | // Serial1.println(u16ReadAddress); 82 | 83 | uint8_t u8ModbusADU[256]; 84 | uint8_t u8ModbusADUSize = 0; 85 | uint8_t i, u8Qty; 86 | uint16_t u16CRC; 87 | uint32_t u32StartTime; 88 | uint8_t u8BytesLeft = 8; 89 | uint8_t u8MBStatus = MBSuccess; 90 | 91 | // assemble Modbus Request Application Data Unit 92 | u8ModbusADU[u8ModbusADUSize++] = u8MBSlave; 93 | u8ModbusADU[u8ModbusADUSize++] = u8MBFunction; 94 | 95 | switch (u8MBFunction) { 96 | case MBReadCoils: 97 | case MBReadDiscreteInputs: 98 | case MBReadInputRegisters: 99 | case MBReadHoldingRegisters: 100 | case MBReadWriteMultipleRegisters: 101 | u8ModbusADU[u8ModbusADUSize++] = highByte(u16ReadAddress); 102 | u8ModbusADU[u8ModbusADUSize++] = lowByte(u16ReadAddress); 103 | u8ModbusADU[u8ModbusADUSize++] = highByte(u16ReadQty); 104 | u8ModbusADU[u8ModbusADUSize++] = lowByte(u16ReadQty); 105 | break; 106 | } 107 | 108 | switch (u8MBFunction) { 109 | case MBWriteSingleCoil: 110 | case MBMaskWriteRegister: 111 | case MBWriteMultipleCoils: 112 | case MBWriteSingleRegister: 113 | case MBWriteMultipleRegisters: 114 | case MBReadWriteMultipleRegisters: 115 | u8ModbusADU[u8ModbusADUSize++] = highByte(u16WriteAddress); 116 | u8ModbusADU[u8ModbusADUSize++] = lowByte(u16WriteAddress); 117 | break; 118 | } 119 | 120 | switch (u8MBFunction) { 121 | case MBWriteSingleCoil: 122 | u8ModbusADU[u8ModbusADUSize++] = highByte(u16WriteQty); 123 | u8ModbusADU[u8ModbusADUSize++] = lowByte(u16WriteQty); 124 | break; 125 | 126 | case MBWriteSingleRegister: 127 | u8ModbusADU[u8ModbusADUSize++] = highByte(u16TransmitBuffer[0]); 128 | u8ModbusADU[u8ModbusADUSize++] = lowByte(u16TransmitBuffer[0]); 129 | break; 130 | 131 | case MBWriteMultipleCoils: 132 | u8ModbusADU[u8ModbusADUSize++] = highByte(u16WriteQty); 133 | u8ModbusADU[u8ModbusADUSize++] = lowByte(u16WriteQty); 134 | u8Qty = (u16WriteQty % 8) ? ((u16WriteQty >> 3) + 1) : (u16WriteQty >> 3); 135 | u8ModbusADU[u8ModbusADUSize++] = u8Qty; 136 | for (i = 0; i < u8Qty; i++) { 137 | switch (i % 2) { 138 | case 0: // i is even 139 | u8ModbusADU[u8ModbusADUSize++] = lowByte(u16TransmitBuffer[i >> 1]); 140 | break; 141 | 142 | case 1: // i is odd 143 | u8ModbusADU[u8ModbusADUSize++] = highByte(u16TransmitBuffer[i >> 1]); 144 | break; 145 | } 146 | } 147 | break; 148 | 149 | case MBWriteMultipleRegisters: 150 | case MBReadWriteMultipleRegisters: 151 | u8ModbusADU[u8ModbusADUSize++] = highByte(u16WriteQty); 152 | u8ModbusADU[u8ModbusADUSize++] = lowByte(u16WriteQty); 153 | u8ModbusADU[u8ModbusADUSize++] = lowByte(u16WriteQty << 1); 154 | 155 | for (i = 0; i < lowByte(u16WriteQty); i++) { 156 | u8ModbusADU[u8ModbusADUSize++] = highByte(u16TransmitBuffer[i]); 157 | u8ModbusADU[u8ModbusADUSize++] = lowByte(u16TransmitBuffer[i]); 158 | } 159 | break; 160 | 161 | case MBMaskWriteRegister: 162 | u8ModbusADU[u8ModbusADUSize++] = highByte(u16TransmitBuffer[0]); 163 | u8ModbusADU[u8ModbusADUSize++] = lowByte(u16TransmitBuffer[0]); 164 | u8ModbusADU[u8ModbusADUSize++] = highByte(u16TransmitBuffer[1]); 165 | u8ModbusADU[u8ModbusADUSize++] = lowByte(u16TransmitBuffer[1]); 166 | break; 167 | } 168 | 169 | // append CRC 170 | u16CRC = 0xFFFF; 171 | for (i = 0; i < u8ModbusADUSize; i++) { 172 | u16CRC = crc16_update(u16CRC, u8ModbusADU[i]); 173 | } 174 | u8ModbusADU[u8ModbusADUSize++] = lowByte(u16CRC); 175 | u8ModbusADU[u8ModbusADUSize++] = highByte(u16CRC); 176 | u8ModbusADU[u8ModbusADUSize] = 0; 177 | 178 | // flush receive buffer before transmitting request 179 | while (_serial->read() != -1); 180 | 181 | // transmit request 182 | if (_preTransmission) { 183 | _preTransmission(); 184 | } 185 | for (i = 0; i < u8ModbusADUSize; i++) { 186 | _serial->write(u8ModbusADU[i]); 187 | } 188 | 189 | u8ModbusADUSize = 0; 190 | _serial->flush(); // flush transmit buffer 191 | if (_postTransmission) { 192 | _postTransmission(); 193 | } 194 | 195 | // loop until we run out of time or bytes, or an error occurs 196 | u32StartTime = millis(); 197 | while (u8BytesLeft && !u8MBStatus) { 198 | if (_serial->available()) { 199 | u8ModbusADU[u8ModbusADUSize++] = _serial->read(); 200 | u8BytesLeft--; 201 | } else { 202 | if (_idle) { 203 | _idle(); 204 | } 205 | } 206 | 207 | // evaluate slave ID, function code once enough bytes have been read 208 | if (u8ModbusADUSize == 5) { 209 | // verify response is for correct Modbus slave 210 | if (u8ModbusADU[0] != u8MBSlave) { 211 | u8MBStatus = MBInvalidSlaveID; 212 | break; 213 | } 214 | 215 | // verify response is for correct Modbus function code (mask exception bit 7) 216 | if ((u8ModbusADU[1] & 0x7F) != u8MBFunction) { 217 | u8MBStatus = MBInvalidFunction; 218 | break; 219 | } 220 | 221 | // check whether Modbus exception occurred; return Modbus Exception Code 222 | if (bitRead(u8ModbusADU[1], 7)) { 223 | u8MBStatus = u8ModbusADU[2]; 224 | break; 225 | } 226 | 227 | // evaluate returned Modbus function code 228 | switch (u8ModbusADU[1]) { 229 | case MBReadCoils: 230 | case MBReadDiscreteInputs: 231 | case MBReadInputRegisters: 232 | case MBReadHoldingRegisters: 233 | case MBReadWriteMultipleRegisters: 234 | u8BytesLeft = u8ModbusADU[2]; 235 | break; 236 | 237 | case MBWriteSingleCoil: 238 | case MBWriteMultipleCoils: 239 | case MBWriteSingleRegister: 240 | case MBWriteMultipleRegisters: 241 | u8BytesLeft = 3; 242 | break; 243 | 244 | case MBMaskWriteRegister: 245 | u8BytesLeft = 5; 246 | break; 247 | } 248 | } 249 | if ((millis() - u32StartTime) > ku16MBResponseTimeout) { 250 | u8MBStatus = MBResponseTimedOut; 251 | } 252 | } 253 | 254 | // verify response is large enough to inspect further 255 | if (!u8MBStatus && u8ModbusADUSize >= 5) { 256 | // check packet length 257 | if (u8ModbusADU[2] != u8ModbusADUSize -3 -2) { 258 | u8MBStatus = MBInvalidPacketLength; 259 | } 260 | 261 | // calculate CRC 262 | u16CRC = 0xFFFF; 263 | for (i = 0; i < (u8ModbusADUSize - 2); i++) { 264 | u16CRC = crc16_update(u16CRC, u8ModbusADU[i]); 265 | } 266 | 267 | // verify CRC 268 | if (!u8MBStatus && (lowByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 2] || 269 | highByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 1])) { 270 | u8MBStatus = MBInvalidCRC; 271 | } 272 | } 273 | 274 | if (u8MBStatus == MBSuccess) { 275 | memcpy(_u8ReceiveBuffer, &u8ModbusADU[3], u8ModbusADU[2]); 276 | } 277 | 278 | /* 279 | // disassemble ADU into words 280 | if (!u8MBStatus) 281 | { 282 | // evaluate returned Modbus function code 283 | switch(u8ModbusADU[1]) 284 | { 285 | case ku8MBReadCoils: 286 | case ku8MBReadDiscreteInputs: 287 | // load bytes into word; response bytes are ordered L, H, L, H, ... 288 | for (i = 0; i < (u8ModbusADU[2] >> 1); i++) 289 | { 290 | if (i < ku8MaxBufferSize) 291 | { 292 | _u16ResponseBuffer[i] = word(u8ModbusADU[2 * i + 4], u8ModbusADU[2 * i + 3]); 293 | } 294 | 295 | _u8ResponseBufferLength = i; 296 | } 297 | 298 | // in the event of an odd number of bytes, load last byte into zero-padded word 299 | if (u8ModbusADU[2] % 2) 300 | { 301 | if (i < ku8MaxBufferSize) 302 | { 303 | _u16ResponseBuffer[i] = word(0, u8ModbusADU[2 * i + 3]); 304 | } 305 | 306 | _u8ResponseBufferLength = i + 1; 307 | } 308 | break; 309 | 310 | case ku8MBReadInputRegisters: 311 | case ku8MBReadHoldingRegisters: 312 | case ku8MBReadWriteMultipleRegisters: 313 | // load bytes into word; response bytes are ordered H, L, H, L, ... 314 | for (i = 0; i < (u8ModbusADU[2] >> 1); i++) 315 | { 316 | if (i < ku8MaxBufferSize) 317 | { 318 | _u16ResponseBuffer[i] = word(u8ModbusADU[2 * i + 3], u8ModbusADU[2 * i + 4]); 319 | } 320 | 321 | _u8ResponseBufferLength = i; 322 | } 323 | break; 324 | } 325 | }*/ 326 | 327 | return u8MBStatus; 328 | } 329 | -------------------------------------------------------------------------------- /lib/emodbus.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Modbus RTU library. 3 | * It works via HardwareSerial. ESP-8266 tested. 4 | * 5 | * (c) Oleg Moiseenko 2017 6 | */ 7 | 8 | #ifndef __EMODBUS_H__ 9 | #define __EMODBUS_H__ 10 | 11 | #include 12 | #define htons(x) ( ((x)<< 8 & 0xFF00) | \ 13 | ((x)>> 8 & 0x00FF) ) 14 | 15 | #define USE_SOFTWARE_SERIAL 16 | #ifdef USE_SOFTWARE_SERIAL 17 | #include 18 | #endif 19 | 20 | enum ModbusError { 21 | MBSuccess = 0x00, 22 | MBIllegalFunction = 0x01, 23 | MBIllegalDataAddress = 0x02, 24 | MBIllegalDataValue = 0x03, 25 | MBSlaveDeviceFailure = 0x04, 26 | MBInvalidSlaveID = 0xE0, 27 | MBInvalidFunction = 0xE1, 28 | MBResponseTimedOut = 0xE2, 29 | MBInvalidCRC = 0xE3, 30 | MBInvalidPacketLength = 0xE4 31 | }; 32 | void strModbusError(String &str, int error); 33 | 34 | enum ModbusFunctions { 35 | // Modbus function codes for bit access 36 | MBReadCoils = 0x01, // Modbus function 0x01 Read Coils 37 | MBReadDiscreteInputs = 0x02, // Modbus function 0x02 Read Discrete Inputs 38 | MBWriteSingleCoil = 0x05, // Modbus function 0x05 Write Single Coil 39 | MBWriteMultipleCoils = 0x0F, // Modbus function 0x0F Write Multiple Coils 40 | 41 | // Modbus function codes for 16 bit access 42 | MBReadHoldingRegisters = 0x03, // Modbus function 0x03 Read Holding Registers 43 | MBReadInputRegisters = 0x04, // Modbus function 0x04 Read Input Registers 44 | MBWriteSingleRegister = 0x06, // Modbus function 0x06 Write Single Register 45 | MBWriteMultipleRegisters = 0x10, // Modbus function 0x10 Write Multiple Registers 46 | MBMaskWriteRegister = 0x16, // Modbus function 0x16 Mask Write Register 47 | MBReadWriteMultipleRegisters = 0x17 // Modbus function 0x17 Read Write Multiple Registers 48 | }; 49 | 50 | typedef void (*mbCallback)(); 51 | 52 | class ModbusMaster { 53 | public: 54 | ModbusMaster(); 55 | 56 | //init 57 | void begin(Stream *serial, int SerialSpeed); 58 | //callbacks 59 | void idle(mbCallback); 60 | void preTransmission(mbCallback); 61 | void postTransmission(mbCallback); 62 | 63 | Stream *_serial; // reference to serial port object 64 | uint16_t ku16MBResponseTimeout = 2000; // Modbus timeout [milliseconds] 65 | 66 | // master function that conducts Modbus transactions 67 | uint8_t ModbusMasterTransaction( 68 | uint8_t u8MBSlave, 69 | uint8_t u8MBFunction, uint16_t u16ReadAddress, uint16_t u16ReadQty, 70 | uint8_t *u8ReceiveBuffer, 71 | uint16_t *u16TransmitBuffer = NULL, uint16_t u16WriteAddress = 0, uint16_t u16WriteQty = 0); 72 | private: 73 | // idle callback function; gets called during idle time between TX and RX 74 | mbCallback _idle; 75 | // preTransmission callback function; gets called before writing a Modbus message 76 | mbCallback _preTransmission; 77 | // postTransmission callback function; gets called after a Modbus message has been sent 78 | mbCallback _postTransmission; 79 | }; 80 | 81 | 82 | #endif // ifndef __EMODBUS_H__ 83 | -------------------------------------------------------------------------------- /lib/etools.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void BufferToString(String & str, const char* buf, int len) { 5 | str.reserve(len + 1); // set internal String buffer length 6 | // bug in lib! strcpy instead of strncpy!!!! WString.cpp#L176 7 | for (int i = 0; i < len; i++) { 8 | str.concat(buf[i]); 9 | } 10 | } 11 | 12 | String IntToStr(int i, int digits) { 13 | String s = String(i); 14 | if (s.length() < digits) { 15 | for(int k = 0; k < digits - s.length(); k++) 16 | s = '0' + s; 17 | } 18 | return s; 19 | } 20 | 21 | // https://www.speedguide.net/faq/how-does-rssi-dbm-relate-to-signal-quality-percent-439 22 | // rssi -100 = 0%; -50=100% 23 | int RSSItoQuality(const int RSSI) { 24 | int quality = 2 * (RSSI + 100); 25 | 26 | if (quality < 0) { 27 | quality = 0; 28 | } 29 | if (quality > 100) { 30 | quality = 100; 31 | } 32 | return quality; 33 | } 34 | 35 | void RSSItoStr(String &str, const int RSSI) { 36 | str = String(RSSI) + " (" + RSSItoQuality(RSSI) + "%)"; 37 | } 38 | -------------------------------------------------------------------------------- /lib/etools.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Tools 3 | * 4 | * (c) Oleg Moiseenko 2017 5 | */ 6 | 7 | #ifndef __ETOOLS_H__ 8 | #define __ETOOLS_H__ 9 | 10 | #include 11 | 12 | // put strings to flash 13 | #define SF(x) String(F(x)) 14 | #define STR_RN SF("\r\n") 15 | 16 | //align 17 | #define STORE_ATTR __attribute__((aligned(4))) 18 | 19 | 20 | // IEEE 754 Float. web check here https://www.h-schmidt.net/FloatConverter/IEEE754.html 21 | // wiki https://en.wikipedia.org/wiki/IEEE_floating_point 22 | union dataFloat { 23 | float f; 24 | uint8_t arr[4]; 25 | uint32_t i; 26 | }; 27 | 28 | union dataDouble 29 | { 30 | double d; 31 | uint8_t b[8]; 32 | uint64_t i64; 33 | }; 34 | 35 | // printing date time utils 36 | #define SECS_PER_MIN (60UL) 37 | #define SECS_PER_HOUR (3600UL) 38 | #define SECS_PER_DAY (SECS_PER_HOUR * 24L) 39 | 40 | #define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN) 41 | #define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN) 42 | #define numberOfHours(_time_) (( _time_% SECS_PER_DAY) / SECS_PER_HOUR) 43 | #define elapsedDays(_time_) ( _time_ / SECS_PER_DAY) 44 | 45 | // String 46 | void BufferToString(String & str, const char* buf, int len); 47 | String IntToStr(int i, int digits = 2); 48 | 49 | // tools 50 | int RSSItoQuality(const int RSSI); 51 | void RSSItoStr(String &str, const int RSSI); 52 | 53 | #endif // ifndef __ETOOLS_H__ 54 | 55 | -------------------------------------------------------------------------------- /lib/modbuspoll.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Eastron bridge. 3 | * Universal eastron reader. 4 | * 5 | * (c) Oleg Moiseenko 2017 6 | */ 7 | 8 | #ifndef __MODBUSPOLL_H__ 9 | #define __MODBUSPOLL_H__ 10 | 11 | #include 12 | #include 13 | #include 14 | #include // logger https://github.com/merlokk/xlogger 15 | 16 | #define MODBUSPOLL_DEBUG 17 | 18 | #define SERIAL_BAUD 9600 // baudrate 19 | #define MODBUS_POLL_TIMEOUT 700 // max time to wait for response from SDM 20 | 21 | // Poll commands 22 | #define POLL_ALL 0 23 | #define POLL_HOLDING_REGISTERS MBReadHoldingRegisters 24 | #define POLL_INPUT_REGISTERS MBReadInputRegisters 25 | 26 | // registers configuration 27 | #define MDB_WORD 1 28 | #define MDB_INT 2 29 | #define MDB_INT64 3 30 | #define MDB_FLOAT 4 31 | #define MDB_FLOAT8 5 32 | #define MDB_2BYTE_HEX 6 33 | #define MDB_4BYTE_HEX 7 34 | #define MDB_INT64_HEX 8 35 | #define MDB_8BYTE_HEX 9 36 | #define MDB_16BYTE_HEX 10 37 | struct mqttMapConfigS { 38 | const char * mqttTopicName; 39 | byte command; 40 | word modbusAddress; 41 | byte valueType; 42 | }; 43 | 44 | #define MAX_MODBUS_DIAP 20 45 | typedef struct { 46 | byte Command = 0; 47 | word StartDiap = 0; 48 | word LengthDiap = 0; 49 | uint8_t* Address = NULL; 50 | } ModbusDiap; 51 | 52 | class ModbusPoll { 53 | private: 54 | ModbusDiap modbusArray[MAX_MODBUS_DIAP]; 55 | ModbusMaster modbusNode; 56 | xLogger * logger = NULL; 57 | Stream * mSerial = &Serial; 58 | uint8_t deviceAddress = 1; 59 | uint32_t sleepBetweenPolls = 0; 60 | public: 61 | uint8_t* getValueAddress(byte Command, word ModbusAddress); 62 | 63 | bool Connected = false; 64 | const mqttMapConfigS *mapConfig; 65 | int mapConfigLen; 66 | 67 | ModbusPoll(uint8_t _deviceAddress = 1); 68 | void SetLogger(xLogger * _logger); 69 | void SetSerial(Stream * _serial); 70 | void SetDeviceAddress(uint8_t _deviceAddress); 71 | void SetSleepBetweenPolls(uint32_t _sleep); 72 | int AddModbusDiap(byte Command, word StartDiap, word LengthDiap); 73 | int getModbusDiapLength(); 74 | void getStrModbusConfig(String &str); 75 | void ModbusSetup(const char *deviceType); 76 | void Connect(); 77 | void Poll(byte Command); 78 | void PollAddress(word ModbusAddress); 79 | 80 | uint16_t getWordValue(byte Command, word ModbusAddress); 81 | void setWordValue(uint16_t value, byte Command, word ModbusAddress); 82 | uint32_t getIntValue(byte Command, word ModbusAddress); 83 | uint64_t getInt64Value(byte Command, word ModbusAddress); 84 | float getFloatValue(byte Command, word ModbusAddress); 85 | void getMemoryHex(String &str, byte Command, word ModbusAddress, int len); 86 | void getValue(String &str, byte Command, word ModbusAddress, byte valueType); 87 | 88 | template 89 | void DEBUG_PRINTLN(Args... args); 90 | }; 91 | 92 | #endif // ifndef __MODBUSPOLL_H__ 93 | 94 | 95 | -------------------------------------------------------------------------------- /lib/pitimer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | piTimer::piTimer(){ 4 | 5 | } 6 | 7 | bool piTimer::Add(int timerUID, int interval, bool fromNow){ 8 | for (int i = 0; i < PI_MAX_TIMERS; i++) { 9 | if(!timers[i].timerUID){ 10 | timers[i].timerUID = timerUID; 11 | timers[i].interval = interval; 12 | if (fromNow) 13 | timers[i].lastTimeReset = millis(); 14 | else 15 | timers[i].lastTimeReset = - interval; // after reset -> millis() == 0!!! 16 | return true; 17 | } 18 | } 19 | 20 | return false; 21 | } 22 | 23 | bool piTimer::Delete(int timerUID) { 24 | int id = getTimerNum(timerUID); 25 | if (id < 0) return false; 26 | 27 | timers[id].timerUID = 0; 28 | return true; 29 | } 30 | 31 | bool piTimer::Reset(int timerUID) { 32 | int id = getTimerNum(timerUID); 33 | if (id < 0) return false; 34 | 35 | timers[id].lastTimeReset = millis(); 36 | return true; 37 | } 38 | 39 | bool piTimer::isArmed(int timerUID) { 40 | int id = getTimerNum(timerUID); 41 | if (id < 0) return false; 42 | 43 | return (timers[id].lastTimeReset + timers[id].interval < millis()); 44 | } 45 | 46 | int piTimer::getTimerNum(int timerUID) { 47 | for (int i = 0; i < PI_MAX_TIMERS; i++) { 48 | if(timers[i].timerUID == timerUID){ 49 | return i; 50 | } 51 | } 52 | 53 | return -1; 54 | } 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /lib/pitimer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Timer 3 | * 4 | * (c) Oleg Moiseenko 2017 5 | */ 6 | 7 | #ifndef __PITIMER_H__ 8 | #define __PITIMER_H__ 9 | 10 | #include 11 | 12 | #define PI_MAX_TIMERS 20 13 | 14 | struct timer { 15 | int timerUID = 0; 16 | int interval = 0; 17 | int lastTimeReset = 0; 18 | }; 19 | 20 | class piTimer { 21 | public: 22 | piTimer(); 23 | 24 | bool Add(int timerUID, int interval, bool fromNow = false); 25 | bool Delete(int timerUID); 26 | bool Reset(int timerUID); 27 | bool isArmed(int timerUID); 28 | private: 29 | timer timers[PI_MAX_TIMERS]; 30 | int getTimerNum(int timerUID); 31 | }; 32 | 33 | #endif // ifndef __PITIMER_H__ 34 | 35 | -------------------------------------------------------------------------------- /lib/pmsx003.cpp: -------------------------------------------------------------------------------- 1 | #include "pmsx003.h" 2 | 3 | struct tPMSVer { 4 | uint8_t version; 5 | char *name; 6 | }; 7 | 8 | const tPMSVer PMSVer[] = { 9 | {0x80, "PMS5003"}, 10 | {0x91, "PMS7003"}, 11 | {0x97, "PMSA003"} 12 | }; 13 | 14 | pmsx003::pmsx003() { 15 | state = pqInit; 16 | lastChangeState = 0; 17 | lastDataArriived = 0; 18 | TextIDs = SF("n/a"); 19 | } 20 | 21 | void pmsx003::SetLogger(xLogger *_logger) { 22 | logger = _logger; 23 | } 24 | 25 | void pmsx003::SetMQTT(xMQTT *_mqtt, String _topicOnline, String _topicPM1_0, String _topicPM2_5, String _topicPM10) { 26 | amqtt = _mqtt; 27 | 28 | atopicOnline = _topicOnline; 29 | atopicPM1_0 = _topicPM1_0; 30 | atopicPM2_5 = _topicPM2_5; 31 | atopicPM10 = _topicPM10; 32 | } 33 | 34 | char *pmsx003::GetVersionName(uint8_t ver) { 35 | for(int i = 0; i < sizeof(PMSVer)/sizeof(tPMSVer); i++) { 36 | if (PMSVer[i].version == ver) 37 | return PMSVer[i].name; 38 | } 39 | return "n/a"; 40 | } 41 | 42 | void pmsx003::PrintMeasurement(bool detailed) { 43 | DEBUG_PRINTLN(SF("PMS ver: 0x") + String(pms_meas.version, 16) + 44 | SF(" err: ") + String(pms_meas.errorCode) + 45 | SF(" PM1.0: ") + String(pms_meas.concPM1_0_amb) + 46 | SF(" PM2.5: ") + String(pms_meas.concPM2_5_amb) + 47 | SF(" PM10: ") + String(pms_meas.concPM10_0_amb)); 48 | if (detailed) { 49 | DEBUG_PRINTLN(SF(" PM1.0: ") + String(pms_meas.concPM1_0_CF1) + 50 | SF(" PM2.5: ") + String(pms_meas.concPM2_5_CF1) + 51 | SF(" PM10: ") + String(pms_meas.concPM10_0_CF1)); 52 | DEBUG_PRINTLN(SF(" raw0.3: ") + String(pms_meas.rawGt0_3um) + 53 | SF(" raw0.5: ") + String(pms_meas.rawGt0_5um) + 54 | SF(" raw1.0: ") + String(pms_meas.rawGt1_0um) + 55 | SF(" raw2.5: ") + String(pms_meas.rawGt2_5um) + 56 | SF(" raw5.0: ") + String(pms_meas.rawGt5_0um) + 57 | SF(" raw10.0: ") + String(pms_meas.rawGt10_0um)); 58 | } 59 | } 60 | 61 | void pmsx003::begin(xLogger *_logger, Stream *_serial) { 62 | atimer.Add(TID_POLL, MILLIS_TO_POLL); 63 | 64 | SetLogger(_logger); 65 | aserial = _serial; 66 | 67 | SensorInit(); 68 | 69 | return; 70 | } 71 | 72 | void pmsx003::SensorInit() { 73 | //pinMode(PIN_RST, INPUT_PULLUP); 74 | //pinMode(PIN_SET, INPUT_PULLUP); 75 | 76 | #ifdef PMS_USE_SOFTWARE_SERIAL 77 | static_cast(aserial)->begin(9600); 78 | #else 79 | static_cast(aserial)->begin(9600); 80 | #endif 81 | 82 | PmsInit(); 83 | while (aserial->available()) aserial->read(); 84 | DEBUG_PRINTLN("PMS sensor is starting..."); 85 | aConnected = false; 86 | 87 | return; 88 | } 89 | 90 | bool pmsx003::ReadPMSPacket() { 91 | // read data from serial 92 | while (aserial->available()) { 93 | uint8_t c = aserial->read(); 94 | if (PmsProcess(c)) { 95 | return true; 96 | } 97 | } 98 | return false; 99 | } 100 | 101 | bool pmsx003::Connected() { 102 | return aConnected; 103 | } 104 | 105 | uint8_t pmsx003::getErrorCode() const { 106 | return errorCode; 107 | } 108 | 109 | // when wakeup pms resets and first command can come in 3s. not earlier! 110 | void pmsx003::SetSleepWakeupMode(bool wakeup) { 111 | uint8_t txbuf[8]; 112 | int txlen = PmsCreateCmd(txbuf, sizeof(txbuf), PMS_CMD_ON_STANDBY, wakeup); 113 | aserial->write(txbuf, txlen); 114 | 115 | return; 116 | } 117 | 118 | bool pmsx003::SetAutoSendMode(bool activeMode) { 119 | while (aserial->available()) aserial->read(); 120 | 121 | uint8_t txbuf[8]; 122 | int txlen = PmsCreateCmd(txbuf, sizeof(txbuf), PMS_CMD_AUTO_MANUAL, activeMode); 123 | aserial->write(txbuf, txlen); 124 | 125 | delay(30); 126 | 127 | if (ReadPMSPacket()) { 128 | uint16_t res = PmsParse16(); 129 | if (res != PMS_CMD_AUTO_MANUAL >> 8 + activeMode) { 130 | DEBUG_PRINTLN(SF("PMS set mode OK. res: 0x") + String(res, HEX)); 131 | } else { 132 | DEBUG_PRINTLN(SF("PMS set mode error: wrong response from pms (crc ok): 0x") + String(res, HEX)); 133 | } 134 | } else { 135 | DEBUG_PRINTLN(SF("PMS set mode error: no or wrong response from pms.")); 136 | return false; 137 | } 138 | 139 | return true; 140 | } 141 | 142 | void pmsx003::ManualMeasurement() { 143 | uint8_t txbuf[8]; 144 | int txlen = PmsCreateCmd(txbuf, sizeof(txbuf), PMS_CMD_TRIG_MANUAL, 0); 145 | aserial->write(txbuf, txlen); 146 | 147 | return; 148 | } 149 | 150 | void pmsx003::handle() { 151 | 152 | switch(state) { 153 | // init 154 | case pqStarting: 155 | case pqInit: 156 | if (lastChangeState + 6000 < millis()) { 157 | 158 | // PMS wakeup and reset 159 | DEBUG_PRINTLN(SF("PMS wakeup and reset...")); 160 | SetSleepWakeupMode(true); 161 | lastChangeState = millis(); 162 | aConnected = false; 163 | 164 | if (amqtt){ 165 | amqtt->PublishState(atopicOnline, SF("OFF")); 166 | } 167 | 168 | // clear serial 169 | while (aserial->available()) aserial->read(); 170 | state = pqInit; 171 | 172 | return; 173 | } 174 | break; 175 | 176 | case pqInvalidData: 177 | case pqData: 178 | // work 179 | if (atimer.isArmed(TID_POLL)) { 180 | DEBUG_PRINTLN(SF("PMS get measurement...")); 181 | ManualMeasurement(); // needs 40 ms to reply 182 | delay(50); 183 | 184 | atimer.Reset(TID_POLL); 185 | } 186 | 187 | // timeout 188 | if (lastDataArriived + 15000 < millis()) { 189 | if(aConnected) 190 | amqtt->PublishState(atopicOnline, SF("OFF")); 191 | 192 | aConnected = false; 193 | } 194 | 195 | break; 196 | 197 | case pqSleep: 198 | break; 199 | 200 | default: 201 | break; 202 | } 203 | 204 | // read data 205 | while (ReadPMSPacket()) { 206 | PmsParse(&pms_meas); 207 | 208 | errorCode = pms_meas.errorCode; 209 | PrintMeasurement(true); 210 | lastDataArriived = millis(); 211 | 212 | switch(state) { 213 | case pqStarting: 214 | break; 215 | case pqInit: 216 | version = pms_meas.version; 217 | 218 | // configure PMS send mode 219 | delay(20); 220 | if (SetAutoSendMode(false)) { 221 | TextIDs = SF("Plantower PMS sensor is online. Version: 0x") + String(version, HEX) + 222 | SF(" name: ") + String(GetVersionName(version)); 223 | } else { 224 | TextIDs = SF("Plantower PMS sensor is offline."); 225 | DEBUG_PRINTLN(TextIDs); 226 | 227 | while (aserial->available()) aserial->read(); 228 | return; 229 | } 230 | DEBUG_PRINTLN(TextIDs); 231 | delay(100); 232 | 233 | state = pqInvalidData; 234 | lastChangeState = millis(); 235 | atimer.Reset(TID_POLL); 236 | 237 | while (aserial->available()) aserial->read(); 238 | return; 239 | 240 | break; 241 | 242 | case pqInvalidData: 243 | // invalid data timeout 244 | if (lastChangeState + 30000 < millis()) { 245 | state = pqData; 246 | lastChangeState = millis(); 247 | } else { 248 | DEBUG_PRINTLN(SF("PMS get valid data timeout (ms): ") + String(millis() - lastChangeState)); 249 | break; 250 | } 251 | case pqData: 252 | aConnected = true; 253 | // mqtt 254 | if (amqtt){ 255 | amqtt->PublishState(atopicPM1_0, String(pms_meas.concPM1_0_amb)); 256 | amqtt->PublishState(atopicPM2_5, String(pms_meas.concPM2_5_amb)); 257 | amqtt->PublishState(atopicPM10, String(pms_meas.concPM10_0_amb)); 258 | amqtt->PublishState(atopicOnline, SF("ON")); 259 | 260 | // publish raw states 261 | amqtt->PublishState("raw0.3", String(pms_meas.rawGt0_3um)); 262 | amqtt->PublishState("raw0.5", String(pms_meas.rawGt0_5um)); 263 | amqtt->PublishState("raw1.0", String(pms_meas.rawGt1_0um)); 264 | amqtt->PublishState("raw2.5", String(pms_meas.rawGt2_5um)); 265 | amqtt->PublishState("raw5.0", String(pms_meas.rawGt5_0um)); 266 | amqtt->PublishState("raw10.0", String(pms_meas.rawGt10_0um)); 267 | } 268 | break; 269 | default: 270 | DEBUG_PRINTLN(SF("PMS got valid data in invalid state.")); 271 | break; 272 | } // switch 273 | 274 | } // read data 275 | 276 | } 277 | 278 | -------------------------------------------------------------------------------- /lib/pmsx003.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Plantower pms5003, pms7003, pmsA003 dust sensors library. 3 | * Get PM1.0, PM2.5, PM10 concentration, unit: μg/m³. 4 | * It works via SoftwareSerial or HardwareSerial object. It needs to init it first. ESP-8266 tested. 5 | * 6 | * (c) Oleg Moiseenko 2018 7 | */ 8 | 9 | #ifndef PMSX003_H 10 | #define PMSX003_H 11 | 12 | #include 13 | #include 14 | #include 15 | #include // logger https://github.com/merlokk/xlogger 16 | 17 | #include 18 | 19 | #include // library here: https://github.com/bertrik/pms7003/tree/master/pms7003_esp 20 | // my version here: https://github.com/merlokk/pms7003 21 | 22 | #define PMS_DEBUG 23 | //#define PMS_USE_SOFTWARE_SERIAL 24 | 25 | #ifdef PMS_USE_SOFTWARE_SERIAL 26 | #include 27 | #endif 28 | 29 | // poll 30 | #define MILLIS_TO_POLL 10*1000 // max time to wait for poll 31 | 32 | // timers 33 | #define TID_POLL 0x0001 // timer UID for poll 34 | 35 | enum PMSQueryState { 36 | pqStarting = 0x00, 37 | pqInit = 0x01, 38 | pqInvalidData = 0x02, 39 | pqData = 0x03, 40 | pqSleep = 0x04, 41 | pqError = 0x05 42 | }; 43 | 44 | class pmsx003 { 45 | public: 46 | pmsx003(); 47 | 48 | void begin(xLogger *_logger, Stream *_serial); 49 | void handle(); 50 | void SetLogger(xLogger * _logger); 51 | void SetMQTT(xMQTT *_mqtt, String _topicOnline, String _topicPM1_0, String _topicPM2_5, String _topicPM10); 52 | uint8_t Reset(); 53 | 54 | bool Connected(); 55 | 56 | uint8_t getErrorCode() const; 57 | uint16_t GetPM1_0() const; 58 | uint16_t GetPM2_5() const; 59 | uint16_t GetPM10() const; 60 | pms_meas_t *GetMeasurements(); 61 | 62 | private: 63 | Stream *aserial = NULL; 64 | xLogger *logger = NULL; 65 | piTimer atimer; 66 | xMQTT *amqtt = NULL; 67 | PMSQueryState state = pqStarting; 68 | uint32_t lastChangeState = 0; 69 | uint32_t lastDataArriived = 0; 70 | 71 | bool aConnected = false; 72 | 73 | uint8_t version = 0; 74 | uint8_t errorCode = 0; 75 | // string ID 76 | String TextIDs; 77 | 78 | //mqtt topics 79 | String atopicOnline; 80 | String atopicPM1_0; 81 | String atopicPM2_5; 82 | String atopicPM10; 83 | 84 | // decoded measurements 85 | pms_meas_t pms_meas; 86 | 87 | void SetSleepWakeupMode(bool wakeup); 88 | bool SetAutoSendMode(bool activeMode); 89 | void ManualMeasurement(); 90 | void SensorInit(); 91 | 92 | bool ReadPMSPacket(); 93 | char *GetVersionName(uint8_t ver); 94 | void PrintMeasurement(bool detailed); 95 | 96 | template 97 | void DEBUG_PRINTLN(Args... args); 98 | }; 99 | 100 | template 101 | void pmsx003::DEBUG_PRINTLN(Args... args) { 102 | #ifdef PMS_DEBUG 103 | if (logger) { 104 | logger->println(args...); 105 | } 106 | #endif 107 | } 108 | 109 | #endif // PMSX003_H 110 | -------------------------------------------------------------------------------- /lib/si1145.cpp: -------------------------------------------------------------------------------- 1 | #include "si1145.h" 2 | 3 | si1145::si1145() { 4 | TextIDs = SF("n/a"); 5 | } 6 | 7 | uint8_t si1145::Reset() { 8 | si.getLastError(); 9 | uint8_t err = si.reset(); 10 | 11 | return err; 12 | } 13 | 14 | void si1145::SensorInit(){ 15 | aConnected = false; 16 | 17 | if (!si.begin(false)) { 18 | DEBUG_PRINTLN(SF("Si1145 sensor init error. last error:") + String(si.getLastError())); 19 | return; 20 | }; 21 | 22 | si.getLastError(); 23 | uint8_t siID = si.readPartId(); 24 | SI114X_CAL_S *siCalibParam = si.getCalibrationParameters(); 25 | si.setMeassureChannels(SI1145_PARAM_CHLIST_ENUV | SI1145_PARAM_CHLIST_ENALSIR | SI1145_PARAM_CHLIST_ENALSVIS | 26 | SI1145_PARAM_CHLIST_ENPS2 | SI1145_PARAM_CHLIST_ENPS3); 27 | uint8_t err = si.getLastError(); 28 | if (!err && siID != 0x00 && siID != 0xFF) { 29 | // sensor online 30 | TextIDs = SF("Si1145: partID=0x") + String(siID, HEX) + // ID: 0x45 Si1145, 0x46 Si1146, 0x47 Si1147 31 | SF(" revID=0x") + String(si.readRevId(), HEX) + // 0x00 32 | SF(" seqID=0x") + String(si.readSeqId(), HEX); // 0x08 33 | 34 | DEBUG_PRINTLN(TextIDs); 35 | 36 | if (siCalibParam) { 37 | DEBUG_PRINTLN(SF("Calibration parameters:") + 38 | SF(" adcrange=") + String(FX20_TO_FLT(siCalibParam->adcrange_ratio)) + 39 | SF(" vispd=") + String(FX20_TO_FLT(siCalibParam->vispd_correction)) + 40 | SF(" irpd=") + String(FX20_TO_FLT(siCalibParam->irpd_correction)) + 41 | SF(" irsize=") + String(FX20_TO_FLT(siCalibParam->irsize_ratio)) + 42 | SF(" ledi=") + String(FX20_TO_FLT(siCalibParam->ledi_ratio)) ); 43 | if (siCalibParam->ucoef_p) 44 | DEBUG_PRINTLN( 45 | SF("ucoef_p[0]=") + String(siCalibParam->ucoef_p[0], 16) + 46 | SF(" [1]=") + String(siCalibParam->ucoef_p[1], 16) + 47 | SF(" [2]=") + String(siCalibParam->ucoef_p[2], 16) + 48 | SF(" [3]=") + String(siCalibParam->ucoef_p[3], 16) ); 49 | } else { 50 | DEBUG_PRINTLN(SF("Calibration parameters is empty! Used default calibration values.")); 51 | } 52 | 53 | aConnected = true; 54 | } else { 55 | TextIDs = SF("offline..."); 56 | DEBUG_PRINTLN(SF("Si1145 sensor offline. error:") + String(err) + SF(" id:0x") + String(siID, HEX)); 57 | } 58 | } 59 | 60 | void si1145::begin(xLogger *_logger) { 61 | atimer.Add(TID_POLL, MILLIS_TO_POLL); 62 | 63 | SetLogger(_logger); 64 | 65 | SensorInit(); 66 | } 67 | 68 | bool si1145::ExecMeasurementCycle(uint16_t *gainVis, uint16_t *gainIR, double *uv) { 69 | *gainVis = 0x8000; 70 | *gainIR = 0x8000; 71 | *uv = 0; 72 | si.getLastError(); 73 | 74 | 75 | si.setVisibleGain(*gainVis); 76 | si.setIRGain(*gainIR); 77 | uint8_t merr = si.takeForcedMeasurement(); 78 | if (merr || si.getLastError()) { 79 | DEBUG_PRINTLN(SF("SI1145 error get data for gain calculation. err:") + String(merr, HEX)); 80 | return false; 81 | } else { 82 | int gvis = si.readVisible() - si.getADCOffset(); 83 | int gir = si.readIR() - si.getADCOffset(); 84 | *gainVis = si.calcOptimalGainFromSignal(gvis); 85 | *gainIR = si.calcOptimalGainFromSignal(gir); 86 | DEBUG_PRINTLN(SF("vis=") + String(gvis) + SF(" ir=") + String(gir) + 87 | SF(" gain Vis=0x") + String(*gainVis, HEX) + SF(" Ir=0x") + String(*gainIR, HEX)); 88 | } 89 | 90 | // from datasheet: 91 | // To enable UV reading, set the EN_UV bit in CHLIST, and configure UCOEF [0:3] to the default values of 0x7B, 92 | // 0x6B, 0x01, and 0x00. Also, set the VIS_RANGE and IR_RANGE bits 93 | *uv = si.readUV() / 100.; // the index is multiplied by 100 94 | 95 | uint8_t err; 96 | si.getLastError(); 97 | 98 | // cycle while not error 99 | bool repeat = true; 100 | while (repeat) { 101 | repeat = false; 102 | si.setVisibleGain(*gainVis); 103 | si.setIRGain(*gainIR); 104 | uint8_t meserr = si.takeForcedMeasurement(); 105 | if(meserr) 106 | DEBUG_PRINTLN(SF("SI1145 take measurement error:0x") + String(meserr, HEX)); 107 | 108 | err = si.getLastError(); 109 | if(err) { 110 | DEBUG_PRINTLN(SF("SI1145 error get data for gain calculation. err:") + String(err, HEX)); 111 | return false; 112 | } 113 | 114 | switch(meserr) { 115 | case 0x80: // Invalid command 116 | case 0x88: // PS1 overflow 117 | case 0x89: // PS2 overflow 118 | case 0x8A: // PS3 overflow 119 | break; 120 | case 0x8C: // VIS overflow 121 | *gainVis = si.decGain(*gainVis); 122 | if (*gainVis == 0xFFFF) { 123 | *gainVis = 0x8000; 124 | } else { 125 | repeat = true; 126 | DEBUG_PRINTLN(SF("SI1145 Vis measurement repeat. new gain:0x") + String(*gainVis)); 127 | } 128 | break; 129 | case 0x8D: // IR overflow 130 | *gainIR = si.decGain(*gainIR); 131 | if (*gainIR == 0xFFFF) { 132 | *gainIR = 0x8000; 133 | } else { 134 | repeat = true; 135 | DEBUG_PRINTLN(SF("SI1145 IR measurement repeat. new gain:0x") + String(*gainIR)); 136 | } 137 | break; 138 | case 0x8E: // AUX overflow 139 | break; 140 | default: 141 | break; 142 | } 143 | } 144 | 145 | return true; 146 | } 147 | 148 | void si1145::handle() { 149 | 150 | if (atimer.isArmed(TID_POLL)) { 151 | uint16_t gainVis = 0x8000; 152 | uint16_t gainIR = 0x8000; 153 | double uv = 0; // calc UV in ExecMeasurementCycle() according to the datasheet 154 | 155 | // if we can do measurement (dont have i2c error) 156 | if (ExecMeasurementCycle(&gainVis, &gainIR, &uv)) { 157 | double visible = si.readVisible(); 158 | double ir = si.readIR(); 159 | double ref = si.readPS2(); 160 | double temp = si.readPS3(); 161 | uint8_t err = si.getLastError(); 162 | 163 | if (!err) { 164 | aConnected = true; 165 | aVisible = visible; 166 | aIR = ir; 167 | aUV = uv; 168 | DEBUG_PRINTLN(SF("SI1145 Visible=") + String(visible) + SF(" IR=") + String(ir) + SF(" UVindx=") + String(uv) + 169 | SF(" Temp=") + String(temp) + SF(" ref=") + String(ref)); 170 | ir = ir - si.getADCOffset(); 171 | if (ir < 0) ir = 0; 172 | 173 | visible = visible - si.getADCOffset(); 174 | if (visible < 0) visible = 0; 175 | 176 | float lux = (5.41f * visible) / si.calcGain(gainVis) + (-0.08f * ir) / si.calcGain(gainIR); 177 | if (lux < 0) 178 | lux = 0.0; 179 | float luxir = ir / (si.calcGain(gainIR) * 2.44f); 180 | if (lux < 0) 181 | lux = 0.0; 182 | 183 | DEBUG_PRINTLN(SF("Real Visible=") + String(visible / si.calcGain(gainVis)) + SF(" IR=") + String(ir / si.calcGain(gainIR)) + 184 | SF(" lux=") + String(lux) + SF(" luxir=") + String(luxir)); 185 | 186 | if (amqtt){ 187 | amqtt->PublishState(atopicVisible, String(lux)); 188 | amqtt->PublishState(atopicIR, String(luxir)); 189 | amqtt->PublishState(atopicUV, String(uv)); 190 | amqtt->PublishState(atopicOnline, SF("ON")); 191 | } 192 | } else { 193 | aConnected = false; 194 | if (amqtt){ 195 | amqtt->PublishState(atopicOnline, SF("OFF")); 196 | } 197 | DEBUG_PRINTLN("Si1145 I2C error: " + String(err)); 198 | } 199 | } else { 200 | aConnected = false; 201 | if (amqtt){ 202 | amqtt->PublishState(atopicOnline, SF("OFF")); 203 | } 204 | DEBUG_PRINTLN("Si1145 I2C error."); 205 | } 206 | 207 | atimer.Reset(TID_POLL); 208 | return; 209 | } 210 | 211 | } 212 | 213 | void si1145::SetLogger(xLogger *_logger) { 214 | logger = _logger; 215 | } 216 | 217 | void si1145::SetMQTT(xMQTT *_mqtt, String _topicOnline, String _topicVisible, String _topicIR, String _topicUV) { 218 | amqtt = _mqtt; 219 | 220 | atopicOnline = _topicOnline; 221 | atopicVisible = _topicVisible; 222 | atopicIR = _topicIR; 223 | atopicUV = _topicUV; 224 | } 225 | 226 | bool si1145::Connected() { 227 | return aConnected; 228 | } 229 | 230 | String si1145::GetTextIDs() const { 231 | return TextIDs; 232 | } 233 | 234 | float si1145::GetVisible() const { 235 | return aVisible; 236 | } 237 | 238 | float si1145::GetIR() const { 239 | return aIR; 240 | } 241 | 242 | float si1145::GetUV() const { 243 | return aUV; 244 | } 245 | 246 | -------------------------------------------------------------------------------- /lib/si1145.h: -------------------------------------------------------------------------------- 1 | /* 2 | * BME280 library. 3 | * Get temperature and humidity. 4 | * It works via I2C Wire object. It needs to init it first. ESP-8266 tested. 5 | * 6 | * (c) Oleg Moiseenko 2018 7 | */ 8 | 9 | #ifndef LIBSI1145_H 10 | #define LIBSI1145_H 11 | 12 | /* Ideas: 13 | https://github.com/HGrabas/SI1145/blob/master/SI1145.cpp 14 | https://github.com/ArticCynda/OpenObservatory/blob/master/firmware-v3.2/si1145/SI114X.cpp 15 | https://github.com/torvalds/linux/blob/master/drivers/iio/light/si1145.c 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include // logger https://github.com/merlokk/xlogger 22 | 23 | #include 24 | 25 | #include 26 | #include // original: https://github.com/adafruit/Adafruit_SI1145_Library 27 | // with error handling fix: https://github.com/merlokk/Adafruit_SI1145_Library 28 | 29 | #define SI_DEBUG 30 | 31 | // poll 32 | #define MILLIS_TO_POLL 10*1000 // max time to wait for poll 33 | 34 | // timers 35 | #define TID_POLL 0x0001 // timer UID for poll 36 | 37 | class si1145 { 38 | public: 39 | si1145(); 40 | 41 | void begin(xLogger *_logger); 42 | void handle(); 43 | void SetLogger(xLogger * _logger); 44 | void SetMQTT(xMQTT *_mqtt, String _topicOnline, String _topicVisible, String _topicIR, String _topicUV); 45 | uint8_t Reset(); 46 | 47 | bool Connected(); 48 | 49 | String GetTextIDs() const; 50 | float GetVisible() const; 51 | float GetIR() const; 52 | float GetUV() const; 53 | 54 | uint16_t calcOptimalGainFromSignal(int signal); 55 | private: 56 | xLogger *logger = NULL; 57 | piTimer atimer; 58 | xMQTT *amqtt = NULL; 59 | 60 | Adafruit_SI1145 si; // I2C 61 | 62 | bool aConnected = false; 63 | // string ID 64 | String TextIDs; 65 | 66 | //mqtt topics 67 | String atopicOnline; 68 | String atopicVisible; 69 | String atopicIR; 70 | String atopicUV; 71 | 72 | // decoded measurements 73 | float aVisible; 74 | float aIR; 75 | float aUV; 76 | 77 | void SensorInit(); 78 | bool ExecMeasurementCycle(uint16_t *gainVis, uint16_t *gainIR, double *uv); 79 | 80 | template 81 | void DEBUG_PRINTLN(Args... args); 82 | }; 83 | 84 | template 85 | void si1145::DEBUG_PRINTLN(Args... args) { 86 | #ifdef SI_DEBUG 87 | if (logger) { 88 | logger->println(args...); 89 | } 90 | #endif 91 | } 92 | 93 | #endif // LIBSI1145_H 94 | -------------------------------------------------------------------------------- /lib/xmqtt.cpp: -------------------------------------------------------------------------------- 1 | #include "xmqtt.h" 2 | 3 | xMQTT::xMQTT() { 4 | mqttClient.setClient(wifiClient); 5 | 6 | using namespace std::placeholders; 7 | mqttClient.setCallback(std::bind(&xMQTT::mqttCallback, this, _1, _2, _3)); 8 | } 9 | 10 | void xMQTT::begin(const char *_hwID, const char *_topicName, xParam *_params, xLogger *_logger, bool _postAsJson, bool retained) { 11 | // here is the right order!!! 12 | SetHardwareID(String(_hwID)); 13 | SetLogger(_logger); 14 | SetParamStorage(_params); 15 | SetTopicName(_topicName); 16 | SetPostAsJson(_postAsJson); 17 | } 18 | 19 | void xMQTT::SetParamStorage(xParam *_params) { 20 | params = _params; 21 | } 22 | 23 | void xMQTT::SetLogger(xLogger * _logger) { 24 | logger = _logger; 25 | } 26 | 27 | void xMQTT::SetPostAsJson(bool _postAsJson) { 28 | postAsJson = _postAsJson; 29 | } 30 | 31 | void xMQTT::SetTopicName(const char *defaultTopicName) { 32 | if (!params) 33 | return; 34 | 35 | String mqttPath = (*params)[F("mqtt_path")]; 36 | if (mqttPath.length() == 0) { 37 | mqttTopic = hardwareID + "/" + defaultTopicName + "/"; 38 | } else { 39 | if (mqttPath[mqttPath.length() - 1] == '/') { 40 | mqttTopic = mqttPath; 41 | } else { 42 | mqttTopic = mqttPath + "/"; 43 | } 44 | } 45 | DEBUG_PRINTLN(SF("MQTT topic: ") + mqttTopic); 46 | } 47 | 48 | void xMQTT::SetDefaultRetained(bool _retained) { 49 | retained = _retained; 50 | } 51 | 52 | void xMQTT::SetProgramVersion(char *_programVersion){ 53 | programVersion = _programVersion; 54 | } 55 | 56 | void xMQTT::SetHardwareID(const String &value){ 57 | hardwareID = value; 58 | } 59 | 60 | void xMQTT::SetCmdCallback(cmdCallback callback) { 61 | _cmdCallback = callback; 62 | } 63 | 64 | bool xMQTT::execCmdCallback(String &cmd) { 65 | if (_cmdCallback) { 66 | return _cmdCallback(cmd); 67 | } 68 | return false; 69 | } 70 | 71 | void xMQTT::mqttCallback(char* topic, uint8_t* payload, unsigned int length) { 72 | String sPayload; 73 | BufferToString(sPayload, (char*)payload, length); 74 | 75 | DEBUG_PRINTLN(SF("MQTT message arrived [") + topic + SF("] ") + sPayload); 76 | 77 | // commands 78 | // execute logger commands 79 | if (logger && logger->ExecCommand(sPayload)) { 80 | return; 81 | } 82 | // execute callback commands 83 | execCmdCallback(sPayload); 84 | } 85 | 86 | 87 | bool xMQTT::Connect() { 88 | if (mqttClient.connected()) { 89 | return true; 90 | } 91 | if (!params) { 92 | return false; 93 | } 94 | 95 | auto &lparams = *params; 96 | uint8_t i = 0; 97 | String srv = lparams[F("mqtt_server")]; // extend visiblity of the "mqtt_server" parameter 98 | mqttClient.setServer(srv.c_str(), lparams[F("mqtt_port")].toInt()); 99 | while (!mqttClient.connected()) { 100 | if (mqttClient.connect(hardwareID.c_str(), lparams[F("mqtt_user")].c_str(), lparams[F("mqtt_passwd")].c_str())) { // because connect doing here 101 | DEBUG_PRINTLN(F("The client is successfully connected to the MQTT broker")); 102 | 103 | // subscribe to control topic 104 | String vtopic = mqttTopic + SF("control"); 105 | mqttClient.subscribe(vtopic.c_str()); 106 | resetErrorCnt = 0; 107 | } else { 108 | DEBUG_PRINTLN(llError, SF("The connection to the MQTT broker failed. User: ") + lparams[F("mqtt_user")] + 109 | SF(" Passwd: ") + lparams[F("mqtt_passwd")] + SF(" Broker: ") + srv + SF(":") + lparams[F("mqtt_port")]); 110 | 111 | delay(1000); 112 | 113 | if (i >= 2) { 114 | break; 115 | } 116 | i++; 117 | 118 | if (resetErrorCnt >= 50) { 119 | DEBUG_PRINTLN(F("ESP reset...")); 120 | ESP.reset(); 121 | delay(1000); 122 | } 123 | resetErrorCnt++; 124 | } 125 | } 126 | } 127 | 128 | bool xMQTT::Disconnect() { 129 | if (!mqttClient.connected()) { 130 | return true; 131 | } 132 | mqttClient.disconnect(); 133 | } 134 | 135 | bool xMQTT::Reconnect() { 136 | if (Disconnect()) 137 | return Connect(); 138 | return false; 139 | } 140 | 141 | bool xMQTT::Connected(){ 142 | return mqttClient.connected(); 143 | } 144 | 145 | void xMQTT::mqttInternalPublish(const String &topic, const String &payload) { 146 | if (!mqttClient.connected()) { 147 | return; 148 | } 149 | 150 | String vtopic = mqttTopic + topic; 151 | if (mqttClient.publish(vtopic.c_str(), payload.c_str(), retained)) { 152 | DEBUG_PRINTLN(SF("MQTT publish ok. Topic: ") + vtopic + SF(" Payload: ") + payload); 153 | } else { 154 | DEBUG_PRINTLN(llError, F("MQTT message publish failed")); 155 | } 156 | } 157 | 158 | bool xMQTT::jsonInternalPublish(const String &topic, const String &payload) { 159 | StaticJsonBuffer jsonBuffer; 160 | JsonObject *root = &jsonBuffer.parseObject(jsonStrBuffer); 161 | if (!root->success()){ 162 | DEBUG_PRINTLN(llError, "xMQTT: json load error."); 163 | root = &jsonBuffer.createObject(); 164 | }; 165 | if (!root->success()) { 166 | DEBUG_PRINTLN(llError, F("xMQTT: json final check load error.")); 167 | return false; 168 | } 169 | 170 | if (topic.indexOf("/") < 0) { 171 | root->set(topic, payload); 172 | } else { 173 | JsonObject *jobj = root; 174 | String str = topic; 175 | String sNode = ""; 176 | 177 | if (str.indexOf("/") == 0) 178 | str = str.substring(1); 179 | 180 | while (str.indexOf("/") >= 0) { 181 | int indx = str.indexOf("/"); 182 | sNode = str.substring(0, indx); 183 | str = str.substring(indx + 1); 184 | 185 | JsonObject *jNode = &(*jobj)[sNode].asObject(); 186 | if (!jNode->success()) 187 | jNode = &jobj->createNestedObject(sNode); 188 | 189 | jobj = jNode; 190 | } 191 | 192 | jobj->set(str, payload); 193 | 194 | } 195 | 196 | if (root->measureLength() > MAX_JSON_LENGTH) { 197 | DEBUG_PRINTLN(llError, "xMQTT: JSON too big. Can't save to memory."); 198 | return false; 199 | } 200 | 201 | 202 | jsonStrBuffer = ""; 203 | root->printTo(jsonStrBuffer); 204 | return root->success(); 205 | } 206 | 207 | void xMQTT::BeginPublish() { 208 | if (!postAsJson) 209 | return; 210 | 211 | jsonStrBuffer.reserve(BUF_RESERVE_LEN); 212 | jsonStrBuffer = "{}"; 213 | } 214 | 215 | void xMQTT::PublishState(const char *topic, const char *payload) { 216 | PublishState(String(topic), String(payload)); 217 | } 218 | 219 | void xMQTT::PublishState(const char *topic, const String &payload) { 220 | PublishState(String(topic), payload); 221 | } 222 | 223 | void xMQTT::PublishState(const String &topic, const String &payload) { 224 | if (postAsJson) { 225 | jsonInternalPublish(topic, payload); 226 | } else { 227 | mqttInternalPublish(topic, payload); 228 | } 229 | } 230 | 231 | void xMQTT::Commit(const String &topicAdd) { 232 | if (!postAsJson) 233 | return; 234 | 235 | mqttInternalPublish(topicAdd, jsonStrBuffer); 236 | } 237 | 238 | void xMQTT::Commit(const char *topicAdd) { 239 | Commit(String(topicAdd)); 240 | } 241 | 242 | void xMQTT::Commit() { 243 | Commit(""); 244 | } 245 | 246 | void xMQTT::PublishInitialState() { 247 | BeginPublish(); 248 | PublishState(SF("MAC"), WiFi.macAddress()); 249 | PublishState(SF("HardwareId"), hardwareID); 250 | PublishState(SF("Version"), programVersion); 251 | PublishState(SF("DeviceType"), (*params)[F("device_type")]); 252 | Commit(SF("System")); 253 | } 254 | 255 | void xMQTT::handle() { 256 | if (!mqttClient.connected()) { 257 | Connect(); 258 | } 259 | 260 | mqttClient.loop(); 261 | } 262 | 263 | -------------------------------------------------------------------------------- /lib/xmqtt.h: -------------------------------------------------------------------------------- 1 | #ifndef XMQTT_H 2 | #define XMQTT_H 3 | 4 | #include 5 | #include // https://github.com/knolleary/pubsubclient/releases/tag/v2.6 6 | #include // https://github.com/bblanchon/ArduinoJson 7 | #include 8 | #include 9 | #include // logger https://github.com/merlokk/xlogger 10 | 11 | #define MQTT_DEBUG 12 | #define JSON_OBJ_BUFFER_LEN 500 // json buffer length 13 | #define BUF_RESERVE_LEN 256 // reserve for json string 14 | #define MAX_JSON_LENGTH 1500 // maximum json length in bytes 15 | 16 | typedef bool (*cmdCallback)(String &cmd); 17 | 18 | class xMQTT { 19 | public: 20 | xMQTT(); 21 | 22 | void begin(const char *_hwID, const char *_topicName, xParam *_params, xLogger *_logger = NULL, bool _postAsJson = false, bool retained = false); 23 | void SetParamStorage(xParam *_params); 24 | void SetLogger(xLogger *_logger); 25 | void SetPostAsJson(bool _postAsJson); 26 | void SetTopicName(const char * topicName); 27 | void SetDefaultRetained(bool _retained); 28 | void SetProgramVersion(char * _programVersion); 29 | void SetHardwareID(const String &value); 30 | 31 | void SetCmdCallback(cmdCallback callback); 32 | 33 | bool Connect(); 34 | bool Disconnect(); 35 | bool Reconnect(); 36 | bool Connected(); 37 | 38 | void BeginPublish(); // works for json push 39 | void PublishState(const char *topic, const char *payload); 40 | void PublishState(const char *topic, const String &payload); 41 | void PublishState(const String &topic, const String &payload); 42 | void Commit(); // works for json push 43 | void Commit(const String &topicAdd); 44 | void Commit(const char *topicAdd); 45 | 46 | void PublishInitialState(); 47 | 48 | void handle(); 49 | 50 | private: 51 | xParam *params = NULL; 52 | xLogger *logger = NULL; 53 | bool postAsJson = false; 54 | bool retained = false; 55 | int resetErrorCnt = 0; // connect error counter 56 | String jsonStrBuffer = ""; 57 | 58 | String mqttTopic; 59 | String hardwareID; 60 | const char * programVersion = NULL; 61 | 62 | WiFiClient wifiClient; 63 | PubSubClient mqttClient; 64 | 65 | void mqttInternalPublish(const String &topic, const String &payload); 66 | bool jsonInternalPublish(const String &topic, const String &payload); 67 | 68 | cmdCallback _cmdCallback; 69 | bool execCmdCallback(String &cmd); 70 | void mqttCallback(char* topic, uint8_t *payload, unsigned int length); 71 | 72 | template 73 | void DEBUG_PRINTLN(Args... args); 74 | }; 75 | 76 | template 77 | void xMQTT::DEBUG_PRINTLN(Args... args) { 78 | #ifdef MQTT_DEBUG 79 | if (logger) { 80 | logger->println(args...); 81 | } 82 | #endif 83 | } 84 | 85 | #endif // XMQTT_H 86 | -------------------------------------------------------------------------------- /lib/xparam.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | xParam::xParam(){ 4 | 5 | } 6 | 7 | void xParam::begin(){ 8 | ClearAll(); 9 | } 10 | 11 | void xParam::ClearAll() { 12 | memset(&jsonMem[0], 0x00, JSON_MEM_BUFFER_LEN); 13 | jsonMem[0] = '{'; 14 | jsonMem[1] = '}'; 15 | } 16 | 17 | bool xParam::LoadFromEEPROM() { 18 | ClearAll(); 19 | 20 | EEPROM.begin(JSON_MEM_BUFFER_LEN); 21 | EEPROM.get(0, jsonMem); 22 | EEPROM.end(); 23 | 24 | if (crc8(&jsonMem[0], JSON_MEM_BUFFER_LEN - 2) != jsonMem[JSON_MEM_BUFFER_LEN - 1]) { 25 | ClearAll(); 26 | DEBUG_PRINTLN(llError, F("xParam: Invalid settings in EEPROM, settings was cleared.")); 27 | 28 | return false; 29 | } 30 | 31 | return true; 32 | } 33 | 34 | bool xParam::SaveToEEPROM() { 35 | jsonMem[JSON_MEM_BUFFER_LEN - 1] = crc8(&jsonMem[0], JSON_MEM_BUFFER_LEN - 2); 36 | 37 | EEPROM.begin(JSON_MEM_BUFFER_LEN); 38 | EEPROM.put(0, jsonMem); 39 | EEPROM.end(); 40 | 41 | return true; 42 | } 43 | 44 | bool xParam::LoadFromRTC() { 45 | ClearAll(); 46 | ESP.rtcUserMemoryRead(0, (uint32_t*)&jsonMem[0], JSON_MEM_BUFFER_LEN); 47 | 48 | if (crc8(&jsonMem[0], JSON_MEM_BUFFER_LEN - 2) != jsonMem[JSON_MEM_BUFFER_LEN - 1]) { 49 | ClearAll(); 50 | DEBUG_PRINTLN(llError, F("xParam: Invalid settings in RTC, settings was cleared.")); 51 | 52 | return false; 53 | } 54 | 55 | return true; 56 | } 57 | 58 | bool xParam::SaveToRTC() { 59 | jsonMem[JSON_MEM_BUFFER_LEN - 1] = crc8(&jsonMem[0], JSON_MEM_BUFFER_LEN - 2); 60 | 61 | ESP.rtcUserMemoryWrite(0, (uint32_t*)&jsonMem[0], JSON_MEM_BUFFER_LEN); 62 | } 63 | 64 | bool xParam::LoadFromWeb(const String &url) { 65 | DEBUG_PRINTLN(SF("xParam: load from web: ") + url); 66 | HTTPClient http; 67 | http.begin(url); 68 | int httpCode = http.GET(); 69 | if(httpCode > 0) { 70 | DEBUG_PRINTLN(SF("xParam: GET ok: ") + String(httpCode)); 71 | 72 | // file found at server 73 | if (httpCode == HTTP_CODE_OK) { 74 | String s = http.getString(); 75 | int n = min((int)s.length(), JSON_MEM_BUFFER_LEN - 1); 76 | s.toCharArray(&jsonMem[0], n); 77 | jsonMem[n] = 0x00; 78 | DEBUG_PRINTLN(SF("xParam: loaded ok. length: ") + String(n)); 79 | } else { 80 | return false; 81 | } 82 | } else { 83 | DEBUG_PRINTLN(llError, SF("xParam: GET error: ") + http.errorToString(httpCode)); 84 | return false; 85 | } 86 | 87 | http.end(); 88 | 89 | return true; 90 | } 91 | 92 | void xParam::GetParamsJsonStr(String &str) { 93 | str = String((const char*)jsonMem); 94 | } 95 | 96 | void xParam::SetLogger(xLogger *_logger) { 97 | logger = _logger; 98 | } 99 | 100 | // non class functions 101 | char crc8(const char *data, char len) { 102 | char crc = 0x00; 103 | while (len--) { 104 | char extract = *data++; 105 | for (byte tempI = 8; tempI; tempI--) { 106 | char sum = (crc ^ extract) & 0x01; 107 | crc >>= 1; 108 | if (sum) { 109 | crc ^= 0x8C; 110 | } 111 | extract >>= 1; 112 | } 113 | } 114 | return crc; 115 | } 116 | 117 | -------------------------------------------------------------------------------- /lib/xparam.h: -------------------------------------------------------------------------------- 1 | #ifndef __XPARAM_H 2 | #define __XPARAM_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include // https://github.com/bblanchon/ArduinoJson 8 | #include 9 | #include 10 | 11 | #define DEBUG 12 | 13 | #define JSON_OBJ_BUFFER_LEN 500 14 | #define JSON_MEM_BUFFER_LEN 512 15 | 16 | class xParam 17 | { 18 | public: 19 | xParam(); 20 | 21 | void begin(); 22 | 23 | template 24 | bool SetParam(const TString ¶mName, const T ¶mValue); 25 | template 26 | bool GetParam(const TString ¶mName, T ¶m); 27 | template 28 | bool GetParamDef(const TString ¶mName, T ¶m, String &defaultParamValue); 29 | template 30 | String GetParamStr(const TString ¶mName); 31 | template 32 | bool ParamExists(const TString ¶mName); 33 | 34 | template 35 | String operator[](const TString ¶mName) { 36 | return GetParamStr(paramName); 37 | } 38 | 39 | void ClearAll(); 40 | bool LoadFromEEPROM(); 41 | bool SaveToEEPROM(); 42 | bool LoadFromRTC(); 43 | bool SaveToRTC(); 44 | bool LoadFromWeb(const String &url); 45 | 46 | void GetParamsJsonStr(String &str); 47 | 48 | void SetLogger(xLogger * _logger); 49 | private: 50 | xLogger* logger = NULL; 51 | // memory buffer 4-byte aligned 52 | char jsonMem[JSON_MEM_BUFFER_LEN] STORE_ATTR = {0}; 53 | 54 | template 55 | void DEBUG_PRINTLN(Args... args); 56 | }; 57 | 58 | // not in class 59 | 60 | char crc8(const char *data, char len); 61 | 62 | // templates 63 | 64 | template 65 | bool xParam::SetParam(const TString ¶mName, const T& paramValue) { 66 | StaticJsonBuffer jsonBuffer; 67 | JsonObject *root = &jsonBuffer.parseObject((const char *)jsonMem); 68 | if (!root->success()){ 69 | DEBUG_PRINTLN(llError, "SetParam: json load error."); 70 | root = &jsonBuffer.createObject(); 71 | }; 72 | if (!root->success()) { 73 | DEBUG_PRINTLN(llError, "SetParam: json final check load error."); 74 | return false; 75 | } 76 | 77 | root->set(paramName, paramValue); 78 | 79 | if (root->measureLength() > JSON_MEM_BUFFER_LEN - 1) { 80 | DEBUG_PRINTLN(llError, "SetParam: JSON too big. Can't save it to memory."); 81 | return false; 82 | } 83 | 84 | int n = root->printTo(&jsonMem[0], JSON_MEM_BUFFER_LEN - 1); 85 | jsonMem[n] = 0x00; 86 | return root->success(); 87 | } 88 | 89 | template 90 | bool xParam::GetParam(const TString ¶mName, T ¶m) { 91 | StaticJsonBuffer jsonBuffer; 92 | JsonObject& root = jsonBuffer.parseObject((const char *)jsonMem); 93 | if (!root.success() || !root.containsKey(paramName) ) {//|| !(root[paramName].is())) 94 | DEBUG_PRINTLN(llError, "GetParam: error loading json."); 95 | return false; 96 | } 97 | 98 | param = root.get(paramName); 99 | 100 | return true; 101 | } 102 | 103 | template 104 | bool xParam::GetParamDef(const TString ¶mName, T ¶m, String &defaultParamValue) { 105 | if (!GetParam(paramName, param)) { 106 | param = defaultParamValue; 107 | } 108 | 109 | return true; 110 | } 111 | 112 | template 113 | String xParam::GetParamStr(const TString ¶mName) { 114 | String s = ""; 115 | if (GetParam(paramName, s)) 116 | return s; 117 | else 118 | return ""; 119 | } 120 | 121 | template 122 | bool xParam::ParamExists(const TString ¶mName) { 123 | StaticJsonBuffer jsonBuffer; 124 | JsonObject& root = jsonBuffer.parseObject((const char *)jsonMem); 125 | 126 | return (root.success() && root.containsKey(paramName)); 127 | } 128 | 129 | 130 | template 131 | void xParam::DEBUG_PRINTLN(Args... args) { 132 | #ifdef DEBUG 133 | if (logger) { 134 | logger->println(args...); 135 | } 136 | #endif 137 | } 138 | 139 | #endif // __XPARAM_H 140 | --------------------------------------------------------------------------------