├── .gitignore ├── .travis.yml ├── LICENCE.md ├── README.md ├── img └── devices.jpg ├── lib └── readme.txt ├── platformio.ini └── src ├── BatteryMonitor.cpp ├── BatteryMonitor.h ├── DataReporter.cpp ├── DataReporter.h ├── MeasurementProvider.cpp ├── MeasurementProvider.h ├── RoomMonitorState.h ├── config.h └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | src/keys.h 2 | .pioenvs 3 | .piolibdeps 4 | .vscode/.browse.c_cpp.db* 5 | .vscode/c_cpp_properties.json 6 | .vscode/launch.json 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < http://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < http://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < http://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choice one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to by used as a library with examples 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | # MIT 2 | 3 | Copyright (c) 2019 Josef Adamcik 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RoomMonitor 2 | 3 | Software/firmware for DYI room conditions monitor. 4 | 5 | - ESP8266 wifi connection & brain 6 | - SHT30 or SHT21 - temperature and humidity sensor 7 | - BMP280 - atmospheric pressure sensor (optional) 8 | - BH1750 - light intensity sensor. 9 | 10 | All sesnors communicate via I2C bus. Data are sent to a MQTT broker. 11 | 12 | ![2 prototypes and the actual PCB version](img/devices.jpg) 13 | 14 | PlatformIO is used to build this project but it should be easy to convert it back to a project for Arduino IDE. 15 | 16 | More information can be found in this series of blog posts: 17 | 18 | - [ESP8266 based room-conditions monitor, Part 1](https://josef-adamcik.cz/electronics/esp8266-based-room-conditions-monitor-part-1.html) 19 | - [ESP8266 based room-conditions monitor, part 2: PCB](https://josef-adamcik.cz/electronics/esp8266-based-room-conditions-monitor-part-2.html) 20 | 21 | There is also another projects for a remote MQTT display for measured data: 22 | 23 | - [Github project RoomMonitorClient](https://github.com/josefadamcik/RoomMonitorClient) 24 | - blog post [Freeform ESP8266 OLED MQTT client](https://josef-adamcik.cz/electronics/freeform-esp8266-based-mqtt-oled-client.html) 25 | 26 | 27 | ## Flashing the device 28 | 29 | ## Config 30 | 31 | - change the name of the room in config.h 32 | - setup IP and port for MQTT server in main.cpp 33 | 34 | ``` 35 | const char mqttServer[] = "192.168.178.31"; 36 | const int mqttServerport = 1883; 37 | ``` 38 | 39 | - setup IP for the device and network details in main.cpp 40 | 41 | ``` 42 | IPAddress ip(192, 168, 178, 51); 43 | IPAddress gateway(192, 168, 178, 1); 44 | IPAddress subnet(255, 255, 255, 0); 45 | ``` 46 | 47 | 48 | ### First flash 49 | 50 | - use the programming breakout J1 on the board 51 | - check the layout in the schematic 52 | - hold PRG button and pres RST to get into programming mode 53 | 54 | ### OTA 55 | 56 | You need to know IP of the device. 57 | 58 | Setup in `platformio.ini` as this: 59 | 60 | ``` 61 | platform = espressif8266 62 | board = esp12e 63 | framework = arduino 64 | upload_protocol = espota 65 | upload_port = 192.168.178.51 66 | ``` 67 | 68 | - Restart the device and press shortly PRG after restart. The device will switch into "waiting for OTA" mode. 69 | - Run upload through platformio. 70 | 71 | -------------------------------------------------------------------------------- /img/devices.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefadamcik/RoomMonitor/a816528a5fd1a0cae69797149b759ded913ab3de/img/devices.jpg -------------------------------------------------------------------------------- /lib/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for the project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link to executable file. 4 | 5 | The source code of each library should be placed in separate directory, like 6 | "lib/private_lib/[here are source files]". 7 | 8 | For example, see how can be organized `Foo` and `Bar` libraries: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) http://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- readme.txt --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | Then in `src/main.c` you should use: 31 | 32 | #include 33 | #include 34 | 35 | // rest H/C/CPP code 36 | 37 | PlatformIO will find your libraries automatically, configure preprocessor's 38 | include paths and build them. 39 | 40 | More information about PlatformIO Library Dependency Finder 41 | - http://docs.platformio.org/page/librarymanager/ldf.html 42 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; http://docs.platformio.org/page/projectconf.html 10 | 11 | ; [env:nodemcuv2] 12 | ; platform = espressif8266 13 | ; board = nodemcuv2 14 | ; framework = arduino 15 | ; monitor_speed = 115200 16 | ; lib_deps = 17 | ; Adafruit MQTT Library 18 | ; LiquidCrystal_I2C 19 | ; Adafruit Unified Sensor 20 | ; Adafruit BMP280 Library 21 | ; BH1750 22 | 23 | [env:d1_mini] 24 | platform = espressif8266 25 | board = esp12e 26 | framework = arduino 27 | ; upload_resetmethod = ck 28 | monitor_speed = 57600 29 | upload_protocol = espota 30 | upload_port = 192.168.178.50 ; ota, livingroom 31 | ; upload_port = 192.168.178.51 ; ota, bedroom 32 | lib_deps = 33 | PubSubClient 34 | LiquidCrystal_I2C 35 | Adafruit Unified Sensor 36 | Adafruit BMP280 Library 37 | BH1750 -------------------------------------------------------------------------------- /src/BatteryMonitor.cpp: -------------------------------------------------------------------------------- 1 | #include "BatteryMonitor.h" 2 | 3 | bool BatteryMonitor::checkBattery(float voltage) { 4 | int voltageInt = roundf(voltage * 100.0f); 5 | if (triggered) { 6 | if (voltageInt >= resetThreshold) { 7 | triggered = false; 8 | } 9 | } else { 10 | if (voltageInt <= triggerThreshold) { 11 | triggered = true; 12 | return true; 13 | } 14 | } 15 | return false; 16 | } 17 | 18 | void BatteryMonitor::setState(bool oldState) { 19 | triggered = oldState; 20 | } 21 | -------------------------------------------------------------------------------- /src/BatteryMonitor.h: -------------------------------------------------------------------------------- 1 | #ifndef BATTERY_MONITOR_h 2 | #define BATTERY_MONITOR_h 3 | 4 | #include 5 | #include "Arduino.h" 6 | #include "RoomMonitorState.h" 7 | 8 | 9 | class BatteryMonitor { 10 | public: 11 | /** 12 | * @param trigger -> voltage in volts * 100 for trigger threshold 13 | * @param reset -> voltage in volts * 100 for reset threshold (must be higher than trigger) 14 | */ 15 | BatteryMonitor(int trigger, int reset) 16 | : triggered(false), triggerThreshold(trigger), resetThreshold(reset) {}; 17 | /** 18 | * Constructor for saved state. 19 | */ 20 | BatteryMonitor(int trigger, int reset, bool oldState) 21 | : triggered(oldState), 22 | triggerThreshold(trigger), 23 | resetThreshold(reset) 24 | {}; 25 | /** 26 | * Checks the battery and lets the caller know, if low battery notification 27 | * should be raised 28 | * @param voltage - measured voltage 29 | * @return - true when notificatin should be raised 30 | */ 31 | bool checkBattery(float voltage); 32 | void setState(bool triggered); 33 | bool triggered; 34 | private: 35 | const int triggerThreshold; 36 | const int resetThreshold; 37 | 38 | 39 | }; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/DataReporter.cpp: -------------------------------------------------------------------------------- 1 | #include "DataReporter.h" 2 | 3 | void DataReporter::begin(const RoomMonitorState& state) { 4 | WiFi.forceSleepWake(); 5 | delay(1); 6 | WiFi.persistent(false); 7 | WiFi.mode(WIFI_STA); 8 | WiFi.config(wifiSetup.ip, wifiSetup.gateway, wifiSetup.subnet, wifiSetup.gateway, wifiSetup.gateway); 9 | WIFIConect(state); 10 | 11 | pubSubClient.setClient(client); 12 | pubSubClient.setServer(serverSetup.mqttServer, serverSetup.mqttServerPort); 13 | } 14 | 15 | RoomMonitorState DataReporter::getState(bool baterryWarningTriggered) { 16 | RoomMonitorState newState; 17 | newState.triggered = baterryWarningTriggered; 18 | if (WiFi.status() == WL_CONNECTED) { 19 | newState.channel = WiFi.channel(); 20 | memcpy(newState.bssid, WiFi.BSSID(), 6); 21 | newState.validWifi = true; 22 | } else { 23 | newState.validWifi = false; 24 | } 25 | return newState; 26 | } 27 | 28 | void DataReporter::closeConnections() { 29 | MQTTDisconnect(); 30 | } 31 | 32 | void DataReporter::doReport(const MeasurementsData& measurementData) { 33 | MQTTConect(); 34 | Serial.print(F("Sending measurements: ")); 35 | measurementData.printToSerial(); 36 | Serial.print(F("... ")); 37 | char tmpStr[8]; // Buffer big enough for 7-character float 38 | dtostrf(measurementData.temperature, 4, 2, tmpStr); 39 | bool succ = pubSubClient.publish(feedsSetup.tempfeed, tmpStr, true); 40 | dtostrf(measurementData.humidity, 4, 2, tmpStr); 41 | succ = succ && pubSubClient.publish(feedsSetup.humfeed, tmpStr, true); 42 | dtostrf(measurementData.voltage, 4, 2, tmpStr); 43 | succ = succ && pubSubClient.publish(feedsSetup.vccfeed, tmpStr, true); 44 | dtostrf(measurementData.voltageRaw, 1, 0, tmpStr); 45 | succ = succ && pubSubClient.publish(feedsSetup.vccrawfeed, tmpStr, true); 46 | dtostrf(measurementData.lightLevel, 1, 0, tmpStr); 47 | succ = succ && pubSubClient.publish(feedsSetup.photovfeed, tmpStr, true); 48 | dtostrf(measurementData.pressure, 1, 0, tmpStr); 49 | succ = succ && pubSubClient.publish(feedsSetup.pressurefeed, tmpStr, true); 50 | 51 | bool triggerVccWarning = batteryMonitor->checkBattery(measurementData.voltage); 52 | 53 | if (triggerVccWarning) { 54 | Serial.println(F("Triggered battery monitor")); 55 | dtostrf(measurementData.voltage, 6, 2, tmpStr); 56 | succ = succ && pubSubClient.publish(feedsSetup.vccwarning, tmpStr, true); 57 | } 58 | 59 | if (succ) { 60 | Serial.println(F(" OK!")); 61 | } else { 62 | Serial.println(F(" Failed")); 63 | } 64 | } 65 | 66 | void DataReporter::MQTTConect() { 67 | int wifiStatus = WiFi.status(); 68 | if (wifiStatus != WL_CONNECTED){ 69 | Serial.println(); 70 | Serial.print(F("WiFi not connected, status: ")); 71 | Serial.print(wifiStatus); 72 | Serial.println(); 73 | return; 74 | } 75 | 76 | // Stop if already connected, but double check with ping 77 | if (pubSubClient.connected()) { 78 | return; 79 | } 80 | Serial.print(F("Connecting to MQTT... ")); 81 | 82 | int8_t ret = 0; 83 | uint8_t retries = 3; 84 | while (!pubSubClient.connect("RoomMonitor." ROOM_NAME, serverSetup.mqttLogin, serverSetup.mqttPass)) { // connect will return 0 for connected 85 | ret = pubSubClient.state(); 86 | Serial.println(F("Retr MQTT connection in 1 second...")); 87 | Serial.println(ret); 88 | pubSubClient.disconnect(); 89 | delay(250); 90 | retries--; 91 | if (retries == 0) { 92 | return; //lets go to sleep we'll see the next time.. 93 | } 94 | } 95 | 96 | if (pubSubClient.connected()) { 97 | Serial.println(F(" MQTT Connected!")); 98 | } else { 99 | Serial.print(F(" MQTT still NOT onnected! ")); 100 | Serial.println(ret); 101 | } 102 | } 103 | 104 | void DataReporter::MQTTDisconnect() { 105 | if (pubSubClient.connected()) { 106 | pubSubClient.disconnect(); 107 | } 108 | int wifiStatus = WiFi.status(); 109 | if(wifiStatus == WL_CONNECTED) { 110 | WiFi.disconnect(true); 111 | delay(1); 112 | Serial.println("Wifi disconnected..."); 113 | } 114 | } 115 | 116 | void DataReporter::WIFIConect(const RoomMonitorState& state) { 117 | bool quickSetup = false; 118 | if (state.valid && state.validWifi) { 119 | quickSetup = true; 120 | WiFi.begin(wifiSetup.ssid, wifiSetup.key, state.channel, state.bssid, true); 121 | } else { 122 | WiFi.begin(wifiSetup.ssid, wifiSetup.key); 123 | } 124 | int retries = 0; 125 | int wifiStatus = WiFi.status(); 126 | while (wifiStatus != WL_CONNECTED) { 127 | retries++; 128 | if (retries == 100 && quickSetup) { 129 | Serial.println(F("QC Failed")); 130 | // Quick connect is not working, reset WiFi and try regular 131 | // connection 132 | WiFi.disconnect(); 133 | delay(10); 134 | WiFi.forceSleepBegin(); 135 | delay(10); 136 | WiFi.forceSleepWake(); 137 | delay(10); 138 | WiFi.begin(wifiSetup.ssid, wifiSetup.key); 139 | } 140 | if (retries == 600) { 141 | Serial.println(F("Give up")); 142 | // Giving up after 30 seconds and going back to sleep 143 | WiFi.disconnect(true); 144 | delay(1); 145 | WiFi.mode(WIFI_OFF); 146 | return; 147 | } 148 | if (retries % 10 == 0) { 149 | Serial.print(F(".")); 150 | } 151 | delay(50); 152 | wifiStatus = WiFi.status(); 153 | } 154 | Serial.println(WiFi.status()); 155 | Serial.println(F("Setup done")); 156 | } 157 | 158 | -------------------------------------------------------------------------------- /src/DataReporter.h: -------------------------------------------------------------------------------- 1 | #ifndef DATA_REPORTER_h 2 | #define DATA_REPORTER_h 3 | 4 | #include "keys.h" //this file is not versioned and should contain only ssid and password 5 | #include 6 | #include 7 | #include "debug.h" 8 | #include "BatteryMonitor.h" 9 | #include "MeasurementProvider.h" 10 | #include 11 | 12 | const int powerLowerThanWarningThresholds[] = {490, 480, 470}; //round(vcc * 10) 13 | const byte powerLowerThanWarningThresholdCount = 3; 14 | // const int powerLowerThanWarningThresholds[] = {520, 510, 500}; //round(vcc * 10) 15 | const bool vccReportingOn = false; 16 | 17 | class WifiSetup { 18 | public: 19 | WifiSetup( 20 | const char wifiSsid[], 21 | const char wifiKey[], 22 | IPAddress ip, 23 | IPAddress gateway, 24 | IPAddress subnet 25 | ) : ssid(wifiSsid), key(wifiKey), ip(ip), gateway(gateway), subnet(subnet) {}; 26 | const char* ssid; 27 | const char* key; 28 | IPAddress ip; 29 | IPAddress gateway; 30 | IPAddress subnet; 31 | }; 32 | 33 | class ServerSetup { 34 | public: 35 | ServerSetup(const char server[], int port, const char login[], const char pass[]) 36 | : mqttServer(server), mqttServerPort(port), mqttLogin(login), mqttPass(pass) {}; 37 | const char* mqttServer; 38 | const int mqttServerPort; 39 | const char* mqttLogin; 40 | const char* mqttPass; 41 | }; 42 | 43 | class FeedsSetup { 44 | public: 45 | FeedsSetup( 46 | const char tempfeedkey[], 47 | const char humfeedkey[], 48 | const char vccfeedkey[], 49 | const char vssrawfeedkey[], 50 | const char vccwarningfeedkey[], 51 | const char photovfeedkey[], 52 | const char pressurefeedkey[] 53 | ) 54 | : tempfeed(tempfeedkey), 55 | humfeed(humfeedkey), 56 | vccfeed(vccfeedkey), 57 | vccrawfeed(vssrawfeedkey), 58 | vccwarning(vccwarningfeedkey), 59 | photovfeed(photovfeedkey), 60 | pressurefeed(pressurefeedkey) 61 | {} 62 | const char* tempfeed; 63 | const char* humfeed; 64 | const char* vccfeed; 65 | const char* vccrawfeed; 66 | const char* vccwarning; 67 | const char* photovfeed; 68 | const char* pressurefeed; 69 | }; 70 | 71 | class DataReporter { 72 | public: 73 | DataReporter( 74 | const WifiSetup& wifi, 75 | const ServerSetup& server, 76 | const FeedsSetup& feed, 77 | BatteryMonitor* battery 78 | ) : wifiSetup(wifi), 79 | serverSetup(server), 80 | feedsSetup(feed), 81 | batteryMonitor(battery) 82 | {}; 83 | /** Call in setup() */ 84 | void begin(const RoomMonitorState& state); 85 | void doReport(const MeasurementsData& data); 86 | void closeConnections(); 87 | RoomMonitorState getState(bool baterryWarningTriggered); 88 | private: 89 | const WifiSetup wifiSetup; 90 | const ServerSetup serverSetup; 91 | const FeedsSetup feedsSetup; 92 | WiFiClient client; 93 | PubSubClient pubSubClient; 94 | BatteryMonitor* batteryMonitor; 95 | void MQTTConect(); 96 | void MQTTDisconnect(); 97 | void WIFIConect(const RoomMonitorState& state); 98 | }; 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /src/MeasurementProvider.cpp: -------------------------------------------------------------------------------- 1 | #include "MeasurementProvider.h" 2 | 3 | #define SHT21_TRIGGER_TEMP_MEASURE_NOHOLD 0xF3 4 | #define SHT21_TRIGGER_HUMD_MEASURE_NOHOLD 0xF5 5 | #define SHT21_TRIGGER_TEMP_MEASURE_HOLD 0xE3 6 | #define SHT21_TRIGGER_HUMD_MEASURE_HOLD 0xE5 7 | 8 | MeasurementProvider::MeasurementProvider( 9 | uint8_t tempSensAddr, 10 | uint8_t lightSensAddr, 11 | float analogVCCToRealCoeficient 12 | ): tempSensAddress(tempSensAddr), 13 | lightSensor(lightSensAddr), 14 | analogVCCToRealCoeficient(analogVCCToRealCoeficient) 15 | { } 16 | 17 | bool MeasurementProvider::begin() { 18 | bool lsStatus = lightSensor.begin(BH1750::ONE_TIME_HIGH_RES_MODE); //goes to sleep after measurement 19 | #ifdef USE_BMP280 20 | bool bmpStatus = bmp.begin(0x76); 21 | return lsStatus && bmpStatus; 22 | #else 23 | return lsStatus; 24 | #endif 25 | } 26 | 27 | const MeasurementsData& MeasurementProvider::getCurrentMeasurements(){ 28 | return data; 29 | } 30 | 31 | bool MeasurementProvider::doMeasurements() { 32 | //measure battery voltage 33 | data.voltageRaw = analogRead(A0); 34 | data.voltage = analogToVoltage(data.voltageRaw); 35 | 36 | #ifdef USE_SHT30 37 | //SHT-30 measure temp and humidity 38 | byte tempMeasureRes; 39 | int retryMeasurement = 3; 40 | do { 41 | retryMeasurement--; 42 | tempMeasureRes = measureTempSTH30(); 43 | if (tempMeasureRes != 0) { 44 | Serial.println(F("Unable to measure temperature")); 45 | Serial.println(tempMeasureRes); 46 | delay(100); 47 | } 48 | } while (tempMeasureRes != 0 && retryMeasurement > 0); 49 | 50 | if (tempMeasureRes != 0) { 51 | return false; 52 | } 53 | #endif 54 | 55 | #ifdef USE_SHT21 56 | //SHT21 57 | data.temperature = 0; 58 | data.humidity = 0; 59 | measureTempSTH21(); 60 | #endif 61 | 62 | #ifdef USE_BMP280 63 | // measur pressure 64 | data.bmpTemp = bmp.readTemperature(); 65 | data.pressure = bmp.readPressure(); 66 | #else 67 | data.bmpTemp = 0; 68 | data.pressure = 0; 69 | #endif 70 | 71 | // measure lipht 72 | data.lightLevel = lightSensor.readLightLevel(); 73 | return true; 74 | } 75 | 76 | #ifdef USE_SHT30 77 | byte MeasurementProvider::measureTempSTH30() { 78 | unsigned int data[6]; 79 | Wire.beginTransmission(tempSensAddress); 80 | // measurement command -> one shot measurement, clock stretching, high repeatability 81 | Wire.write(0x2C); 82 | Wire.write(0x06); 83 | uint8_t wireResult = Wire.endTransmission(); 84 | if (wireResult != 0) { 85 | return wireResult; 86 | } 87 | delay(500); 88 | // Request 6 bytes of data 89 | Wire.requestFrom(tempSensAddress, 6); 90 | // cTemp msb, cTemp lsb, cTemp crc, humidity msb, humidity lsb, humidity crc 91 | for (int i=0;i<6;i++) { 92 | data[i]=Wire.read(); 93 | }; 94 | delay(50); 95 | 96 | if (Wire.available() != 0) { 97 | return 20; 98 | } 99 | // Convert the data 100 | this->data.temperature = ((((data[0] * 256.0) + data[1]) * 175) / 65535.0) - 45; 101 | this->data.humidity = ((((data[3] * 256.0) + data[4]) * 100) / 65535.0); 102 | 103 | return 0; 104 | } 105 | #endif 106 | 107 | #ifdef USE_SHT21 108 | uint8_t MeasurementProvider::measureTempSTH21() { 109 | // Convert the data 110 | this->data.humidity = (-6.0 + 125.0 / 65536.0 * readFloatSHT21(SHT21_TRIGGER_HUMD_MEASURE_HOLD)); 111 | this->data.temperature = (-46.85 + 175.72 / 65536.0 * readFloatSHT21(SHT21_TRIGGER_TEMP_MEASURE_HOLD)); 112 | return 0; 113 | } 114 | 115 | 116 | float MeasurementProvider::readFloatSHT21(uint8_t command) 117 | { 118 | uint16_t result; 119 | 120 | Wire.beginTransmission(tempSensAddress); 121 | Wire.write(command); 122 | Wire.endTransmission(); 123 | delay(100); 124 | 125 | Wire.requestFrom(tempSensAddress, 3); 126 | while(Wire.available() < 3) { 127 | delay(10); 128 | } 129 | 130 | // return result 131 | result = ((Wire.read()) << 8); 132 | result += Wire.read(); 133 | result &= ~0x0003; // clear two low bits (status bits) 134 | return (float)result; 135 | } 136 | #endif 137 | 138 | float MeasurementProvider::analogToVoltage(int analog) { 139 | return analog * analogVCCToRealCoeficient; 140 | } 141 | 142 | void MeasurementsData::printToSerial() const { 143 | Serial.print(F("temp: ")); 144 | Serial.print(temperature); 145 | Serial.print(F(" (")); 146 | Serial.print(bmpTemp); 147 | Serial.print(F(") ")); 148 | Serial.print(F(", hum: ")); 149 | Serial.print(humidity); 150 | Serial.print(F(", press: ")); 151 | Serial.print(pressure); 152 | Serial.print(F(", vcc: ")); 153 | Serial.print(voltage); 154 | Serial.print(F(", vcc raw: ")); 155 | Serial.print(voltageRaw); 156 | Serial.print(F(", light: ")); 157 | Serial.print(lightLevel); 158 | } 159 | -------------------------------------------------------------------------------- /src/MeasurementProvider.h: -------------------------------------------------------------------------------- 1 | #ifndef MEASUREMENTS_PROVIDER_h 2 | #define MEASUREMENTS_PROVIDER_h 3 | 4 | #include 5 | #include "config.h" 6 | #include 7 | #ifdef USE_BMP280 8 | #include 9 | #endif 10 | #include 11 | #include "RoomMonitorState.h" 12 | 13 | class MeasurementsData { 14 | public: 15 | float temperature = 0.0; 16 | float humidity = 0.0; 17 | float voltage = 0.0; 18 | int voltageRaw = 0; 19 | unsigned char reportIn = 0; 20 | float pressure = 0.0; 21 | float bmpTemp = 0.0; 22 | int lightLevel = 0; 23 | void printToSerial() const; 24 | private: 25 | }; 26 | 27 | class MeasurementProvider { 28 | public: 29 | MeasurementProvider(uint8_t tempSensAddr, uint8_t lightSensAddr, float analogVCCToRealCoeficient); 30 | const MeasurementsData& getCurrentMeasurements(); 31 | /** @return true for success. */ 32 | bool begin(); 33 | /** @return true for success. */ 34 | bool doMeasurements(); 35 | RoomMonitorState getWifiSetup(); 36 | private : 37 | const uint8_t tempSensAddress; 38 | BH1750 lightSensor; 39 | const float analogVCCToRealCoeficient; 40 | #ifdef USE_BMP280 41 | Adafruit_BMP280 bmp; 42 | #endif 43 | MeasurementsData data; 44 | #ifdef USE_SHT30 45 | uint8_t measureTempSTH30(); 46 | #endif 47 | #ifdef USE_SHT21 48 | uint8_t measureTempSTH21(); 49 | float readFloatSHT21(uint8_t command); 50 | #endif 51 | float analogToVoltage(int analog); 52 | }; 53 | 54 | #endif 55 | 56 | -------------------------------------------------------------------------------- /src/RoomMonitorState.h: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | 3 | #ifndef _ROOM_MONITOR_STATE_ 4 | #define _ROOM_MONITOR_STATE_ 5 | 6 | struct RoomMonitorState { 7 | public: 8 | bool valid = false; //1 byte 9 | bool triggered = false; // 1 byte 10 | bool validWifi = false; //1 byte 11 | uint8_t channel; // 1 byte, 4 in total 12 | uint8_t bssid[6]; // 6 bytes, 10 in total 13 | /** 14 | * We will set this to special know value before we store it into 15 | * persistent memory, before deep sleep. So after wakep we can 16 | * distinguish random junk in memory from our stored value. 17 | */ 18 | uint32_t magic; // 4bytes -> 14 in total 19 | uint16_t padding; // s2 byte, 16 in total 20 | }; 21 | 22 | #endif -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | //Optional sensor for pressure 2 | // #define USE_BMP280 3 | //Use one of those 2 4 | #define USE_SHT21 5 | // #define USE_SHT30 6 | 7 | 8 | // #define ROOM_NAME "workroom" 9 | #define ROOM_NAME "livingroom" 10 | 11 | #define DEBUG true 12 | #define Serial if(DEBUG)Serial -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "keys.h" //this file is not versioned and should contain only ssid and password 2 | #include 3 | #include "config.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "BatteryMonitor.h" 11 | #include "MeasurementProvider.h" 12 | #include "DataReporter.h" 13 | #include 14 | #include 15 | 16 | #ifdef USE_SHT21 17 | const byte tempSensAddr = 0x40; 18 | #endif 19 | #ifdef USE_SHT30 20 | const byte tempSensAddr = 0x45; 21 | #endif 22 | const byte ligthSensAddr = 0x23; 23 | //1min 24 | // const unsigned long sleepForUs = 60 * 1000000; //1m 25 | //5min 26 | const unsigned long sleepForUs = 5 * 60 * 1000000; //5m 27 | const char mqttServer[] = "192.168.178.76"; 28 | const int mqttServerport = 1883; 29 | const char mqttLogin[] = "mqtt"; 30 | const char mqttPass[] = MQTTPASS; 31 | const char ssid[] = MYSSID; //put #define MYSSID "xyz" in keys.h 32 | const char password[] = MYPASS; //put #define MYPASS "blf" in keys.h 33 | const char tempfeed[] = "home/" ROOM_NAME "/temperature"; 34 | const char humfeed[] = "home/" ROOM_NAME "/humidity"; 35 | const char vccfeed[] = "home/" ROOM_NAME "/vcc"; 36 | const char vccrawfeed[] = "home/" ROOM_NAME "/vcc-raw"; 37 | const char vccwarning[] = "home/" ROOM_NAME "/vcc-warning"; 38 | const char photovfeed[] = "home/" ROOM_NAME "/light"; 39 | const char pressurefeed[] = "home/" ROOM_NAME "/pressure"; 40 | const char msgWifiConnecting[] PROGMEM = "WIFI connecting to: "; 41 | const float analogVccToRealCoeficient = 0.0047705311; // Second Prototype: 0.004904651; D1 Mini: 0.00611 42 | bool measurementReady = false; 43 | 44 | // IPAddress ip(192, 168, 178, 51); //bedroom 45 | IPAddress ip(192, 168, 178, 50); //living-room 46 | IPAddress gateway(192, 168, 178, 1); 47 | IPAddress subnet(255, 255, 255, 0); 48 | 49 | const uint32_t deepSleepStateMagic = 0x8af2ba12; 50 | 51 | BatteryMonitor batteryMonitor(/* trigger low voltage */ 320, /* reset low voltage */ 360); 52 | 53 | RoomMonitorState oldState; 54 | 55 | DataReporter reporter( 56 | WifiSetup( ssid, password, ip, gateway, subnet ), 57 | ServerSetup( mqttServer, mqttServerport, mqttLogin, mqttPass), 58 | FeedsSetup( 59 | tempfeed, 60 | humfeed, 61 | vccfeed, 62 | vccrawfeed, 63 | vccwarning, 64 | photovfeed, 65 | pressurefeed 66 | ), 67 | &batteryMonitor 68 | ); 69 | 70 | MeasurementProvider measurement(tempSensAddr, ligthSensAddr, analogVccToRealCoeficient); 71 | 72 | void otaInitialize() { 73 | // Initialise OTA in case there is a software upgrade 74 | ArduinoOTA.setHostname(ROOM_NAME ".roommonitor.local"); 75 | ArduinoOTA.onStart([]() { Serial.println("Start"); }); 76 | ArduinoOTA.onEnd([]() { Serial.println("\nEnd"); }); 77 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 78 | Serial.printf("Progress: %u%%\r", (progress / (total / 100))); 79 | }); 80 | ArduinoOTA.onError([](ota_error_t error) { 81 | Serial.printf("Error[%u]: ", error); 82 | if (error == OTA_AUTH_ERROR) { 83 | Serial.println("Auth Failed"); 84 | } else if (error == OTA_BEGIN_ERROR) { 85 | Serial.println("Begin Failed"); 86 | } else if (error == OTA_CONNECT_ERROR) { 87 | Serial.println("Connect Failed"); 88 | } else if (error == OTA_RECEIVE_ERROR) { 89 | Serial.println("Receive Failed"); 90 | } else if (error == OTA_END_ERROR) { 91 | Serial.println("End Failed"); 92 | } 93 | }); 94 | 95 | ArduinoOTA.begin(); 96 | } 97 | 98 | void storeState() { 99 | RoomMonitorState newState = reporter.getState(batteryMonitor.triggered); 100 | Serial.print(F("RoomMonitorState state: ")); 101 | Serial.println(newState.triggered); 102 | if (newState.validWifi) { 103 | Serial.println(F("Valid Wifi setup")); 104 | Serial.print(F("Channel: ")); 105 | Serial.println(newState.channel); 106 | Serial.print(F("BSSID: ")); 107 | for (byte i = 0; i < 6; i++) { 108 | Serial.print(newState.bssid[i]); 109 | Serial.print(F(":")); 110 | } 111 | Serial.println(); 112 | } else { 113 | Serial.println(F("No valid wifi setup.")); 114 | } 115 | 116 | // writ state to rtc memory 117 | newState.magic = deepSleepStateMagic; 118 | ESP.rtcUserMemoryWrite(0, reinterpret_cast(&newState), 119 | sizeof(newState)); 120 | delay(2); 121 | } 122 | 123 | /** 124 | * Everything happens in setup, we have nothing in loop because we are sleeping all the time. 125 | */ 126 | void setup() { 127 | //switch radio off to save energy 128 | WiFi.mode(WIFI_OFF); 129 | WiFi.forceSleepBegin(); 130 | delay(100); 131 | 132 | Serial.begin(57600); 133 | Serial.println(); 134 | Serial.println(F("Starting...")); 135 | 136 | pinMode(D3, INPUT_PULLUP); 137 | 138 | //restor state from rtc memory 139 | ESP.rtcUserMemoryRead(0, reinterpret_cast(&oldState), sizeof(oldState)); 140 | 141 | if (oldState.magic != deepSleepStateMagic) { //FIRST RUN 142 | Serial.println(F("First run!")); 143 | oldState.valid = false; 144 | } else { //state restored 145 | oldState.valid = true; 146 | Serial.print(F("Recoverd old state... battery: ")); 147 | Serial.println(oldState.triggered); 148 | batteryMonitor.setState(oldState.triggered); 149 | } 150 | 151 | // wait a few sec for OTA button press, when the button is pressed we will be waiting for OTA update. 152 | bool waiforOTA = false; 153 | Serial.println("Waiting for any OTA updates"); 154 | int keeptrying = 5; 155 | while (keeptrying-- > 0 && !waiforOTA) { 156 | if (digitalRead(D3) == LOW) { 157 | waiforOTA = true; 158 | Serial.println(); 159 | Serial.println("Will wait for OTA"); 160 | break; 161 | } 162 | Serial.print("."); 163 | delay(500); 164 | } 165 | 166 | if (waiforOTA) { 167 | reporter.begin(oldState); 168 | otaInitialize(); 169 | while(1) { 170 | Serial.print("."); 171 | ArduinoOTA.handle(); 172 | delay(500); 173 | } 174 | } 175 | 176 | Serial.println("No OTA update"); 177 | //no ota update (there will be restart after OTA or manual restart if ota was not performed) 178 | Wire.begin(/*sda*/4, /*scl*/5); //GPIO4, GPIO5 179 | Wire.setTimeout(500); 180 | measurementReady = measurement.begin(); 181 | if (!measurementReady) { 182 | Serial.println("Unable to initialize measurement"); 183 | } else { 184 | // do measurements 185 | Serial.println("Do measurements"); 186 | bool measureRes = measurement.doMeasurements(); 187 | const MeasurementsData measurementData = measurement.getCurrentMeasurements(); 188 | if (!measureRes) { 189 | Serial.println(F("Unable to measure")); 190 | } else { 191 | Serial.println("Send measurements"); 192 | // start wifi and mqtt 193 | reporter.begin(oldState); 194 | // report 195 | reporter.doReport(measurementData); 196 | //store state 197 | storeState(); 198 | delay(100); 199 | // finish things up 200 | reporter.closeConnections(); 201 | } 202 | } 203 | Serial.print(F("Go to sleep...")); 204 | Serial.println(sleepForUs); 205 | Serial.flush(); 206 | //sleep deeply 207 | ESP.deepSleepInstant(sleepForUs, WAKE_RF_DISABLED); 208 | } 209 | 210 | void loop() { 211 | // NOP 212 | } 213 | 214 | 215 | 216 | 217 | 218 | --------------------------------------------------------------------------------