├── LICENSE ├── README.md ├── ota-server ├── pom.xml ├── resources │ └── log4j2.xml └── src │ └── main │ └── java │ ├── OTAHandler.java │ └── OTAServer.java ├── sensor-case ├── ESP8266_Temp_Sensor_Assembled.png ├── ESP8266_Temp_Sensor_Case_Parts.png ├── ESP8266_Temp_Sensor_Open_Case.png ├── sensor_case.f3d ├── sensor_case.step └── sensor_case.stl └── sensor ├── ESP8266_Temp_Sensor.ino ├── ESP8266_Temp_Sensor_Wiring_Diagram.fzz └── ESP8266_Temp_Sensor_Wiring_Diagram.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Chris Fraschetti 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP8266 Power efficient temperature sensor 2 | 3 | ![Assembled sensor](/sensor-case/ESP8266_Temp_Sensor_Assembled.png?raw=true) 4 | 5 | ## Sensor design and features 6 | - Utilizes DHT22 temperature + humidity sensor 7 | - Changing to a different sensor should only require changes to a few lines of code 8 | - OTA (Over The Air) firmware updates 9 | - No need to physically connect to device to flash new firmware 10 | - Utilizes InfluxDB time series DB for data logging 11 | - Utilizes the board's deep sleep (low power) mode to minimize power consumption 12 | - For maximum efficient/lowest power consumption, the board needs to be powered directly and not via the USB connector (which utilizes the on-board voltage regulator) 13 | - Per device configuration for temperature and humidity adjustments 14 | - Adjustments can be made globally (it's all code) but for my purposes I used the ESP8266 WiFI adapter MAC address 15 | - It's reccomended to calibrate (adjust the software offsets) relative to known accurate sensors 16 | 17 | ## BOM (Bill of Materials) 18 | ### Sensor 19 | - 1x ESP8266 20 | - The provided case leverages NodeMCU v3 w/o attached headers 21 | - 1x 1N5817 Schottky diode 22 | - 1x 10kΩ resistor 23 | 24 | ### Case 25 | - 4x M2x4 self tapping screws 26 | - 2x M2x8 self tapping screws 27 | - 1x M2 nylon washer (optional) 28 | - 1x 3-wire Micro JST connector (optional to simplify disassembly/reassembly) 29 | 30 | ## Wiring diagram 31 | ![Wiring diagram](/sensor/ESP8266_Temp_Sensor_Wiring_Diagram.png?raw=true) 32 | 33 | ## Sensor OTA (Over The Air) update server 34 | 35 | ## Sensor case 36 | - stl, step, and f3d models provided 37 | - Flame retartent filament recommended 38 | - I personally use eSun ePC (Flame retardant level: UL94:V2) for electronics projects 39 | - Remember to scale the 3D model to compensate for filament shrinkage 40 | - This case relies on the ESP8266 NodeMCU v3 dimensions and a headerless (no pre-soldered pin headers) board. While headerless boards are more difficult to find, their use does allow for a much shorter, uniform, and convenient case height 41 | - Board dimensions vary wildly between ESP8266 version and manufacturer. I highly reccomend comparing the dimensions of your board and screw hole sizes/locations to the 3D model to avoid wasting unnecessary time and filament 42 | - Assembly 43 | - Use the 4x M2x4 screws to attach the ESP8266 to the case 44 | - Use the 2x M2x8 screws to attach the sensor lid to the case 45 | - If the sensor does not sit flush, use the nylon washer (any thin washer would do but I used a nylon washer as I had one available) to fill the empty space between the sensor and the lid 46 | - As long as the tolerances of your 3D print are within reason, the sliding lid should attach without force but should also not slide out on its own 47 | 48 | ![Sensor case parts](/sensor-case/ESP8266_Temp_Sensor_Case_Parts.png?raw=true) 49 | ![Open sensor case](/sensor-case/ESP8266_Temp_Sensor_Open_Case.png?raw=true) 50 | 51 | 52 | ## Getting started 53 | 54 | 1. Install the ES8266 boards 55 | * Instructions [here](https://github.com/esp8266/Arduino#installing-with-boards-manager) 56 | 2. Select the ESP8266 board 57 | * Tools --> Boards --> *NodeMCU 1.0 (ESP-12E Module)* 58 | 3. Install the Adafruit DHT sensor library 59 | * Instructions [here](https://learn.adafruit.com/dht/using-a-dhtxx-sensor) 60 | * Don't forget to install both the *DHT sensor library* **and** the *Adafruit Unified Sensor* libraries 61 | 4. Locate the *writeNewDeviceSettings* function and make updates as necessary 62 | 63 | These settings should be common across all your sensors. 64 | ```cpp 65 | char* otaUrl = "http://otaserverhostname:otaserverport/ota/tempsensor.bin"; //Blank if not using OTA updates 66 | short dhtPin = 5; //ESP8266 D1 67 | short dhtPowerPin = 4; //ESP8266 D2 (Not required if you use a power pin directly) 68 | char* wifiSSID = "yourwifissid"; 69 | char* wifiPwd = "yourwifipwd"; 70 | char* influxDBProtocol = "http"; 71 | char* influxDBHost = "yourinfluxdbhost"; 72 | int influxDBPort = 8086; 73 | char* influxDBDB = "sensordata"; 74 | char* influxDBMeasurement = "temperature"; 75 | short sendInterval = 60; //seconds 76 | ``` 77 | 78 | Each sensor must also have an entry to specify the device's ID and temperature/humidity adjustments. 79 | To locate each sensor's MAC address, upload the sketch and observe the serial output. 80 | ```WiFi MAC Address: AA:AA:AA:AA:AA:AA``` 81 | 82 | ```cpp 83 | if (mac_address == "AA:AA:AA:AA:AA:AA") { 84 | sensorTempAdjustment = 0.0; 85 | sensorHumdidityAdjustment = 0.0; 86 | deviceId = "SensorA"; 87 | } else if (mac_address == "BB:BB:BB:BB:BB:BB") { 88 | sensorTempAdjustment = 0.0; 89 | sensorHumdidityAdjustment = 0.0; 90 | deviceId = "SensorB"; 91 | } 92 | ``` 93 | -------------------------------------------------------------------------------- /ota-server/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | org.example 6 | ota-server 7 | 0.1-SNAPSHOT 8 | jar 9 | ota-server 10 | 11 | 1.8 12 | 1.8 13 | 14 | 15 | 16 | 17 | resources 18 | 19 | 20 | 21 | 22 | 23 | org.eclipse.jetty 24 | jetty-server 25 | 9.4.18.v20190429 26 | 27 | 28 | org.apache.commons 29 | commons-lang3 30 | 3.8.1 31 | 32 | 33 | commons-io 34 | commons-io 35 | 2.6 36 | 37 | 38 | org.apache.logging.log4j 39 | log4j-api 40 | 2.11.1 41 | 42 | 43 | org.apache.logging.log4j 44 | log4j-core 45 | 2.11.1 46 | 47 | 48 | -------------------------------------------------------------------------------- /ota-server/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ota-server/src/main/java/OTAHandler.java: -------------------------------------------------------------------------------- 1 | import java.io.File; 2 | import java.io.FileInputStream; 3 | import java.io.FilenameFilter; 4 | import java.io.IOException; 5 | import java.io.OutputStream; 6 | import java.util.Arrays; 7 | import java.util.Comparator; 8 | 9 | import javax.servlet.ServletException; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | 13 | import org.apache.commons.io.IOUtils; 14 | import org.apache.commons.lang3.StringUtils; 15 | import org.apache.logging.log4j.Level; 16 | import org.apache.logging.log4j.LogManager; 17 | import org.apache.logging.log4j.Logger; 18 | import org.eclipse.jetty.server.Request; 19 | 20 | public class OTAHandler { 21 | private static final Logger LOGGER = LogManager.getLogger(OTAHandler.class.getName()); 22 | 23 | public static void handle(String[] pathParts, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 24 | throws IOException, ServletException { 25 | 26 | //TODO add graceful shutdown method 27 | 28 | //pathParts[0] = OTA 29 | //pathParts[1] = requested file 30 | 31 | String httpFieldsStr = baseRequest.getHttpFields().toString().trim(); 32 | httpFieldsStr = " " + StringUtils.join(StringUtils.split(httpFieldsStr, "\r\n"), "\n "); 33 | 34 | LOGGER.log(Level.INFO, "Request headers:\n" + httpFieldsStr); 35 | 36 | if(pathParts.length < 2) 37 | return; 38 | 39 | if(!StringUtils.endsWithIgnoreCase(pathParts[1], ".bin")) 40 | return; 41 | 42 | String otaFile = pathParts[1].substring(0, StringUtils.lastIndexOfIgnoreCase(pathParts[1], ".bin")); 43 | if(StringUtils.isBlank(otaFile)) 44 | return; 45 | 46 | File otaDir = new File("c:\\temp\\ota\\"); 47 | File[] otaFiles = otaDir.listFiles(new FilenameFilter() { 48 | 49 | @Override 50 | public boolean accept(File dir, String name) { 51 | return StringUtils.startsWithIgnoreCase(name, otaFile) 52 | && StringUtils.endsWithIgnoreCase(name, ".bin") 53 | && getBinVersion(name) != null; 54 | } 55 | }); 56 | 57 | if(otaFiles.length == 0) 58 | return; 59 | 60 | //Reverse sort the list 61 | Arrays.sort(otaFiles, new Comparator() { 62 | public int compare(File f1, File f2) { 63 | Integer f1_ver = getBinVersion(f1.getName()); 64 | Integer f2_ver = getBinVersion(f2.getName()); 65 | 66 | return f2_ver.compareTo(f1_ver); 67 | } 68 | }); 69 | 70 | File latestBinFile = otaFiles[0]; 71 | String latestBinName = latestBinFile.getName(); 72 | 73 | //We should never see a null here as we've already called this function 74 | //twice on this string 75 | Integer latestBinVer = getBinVersion(latestBinName); 76 | 77 | String espBinVersionStr = request.getHeader("x-ESP8266-version"); 78 | if(StringUtils.isBlank(espBinVersionStr)) 79 | espBinVersionStr = "-1"; 80 | 81 | int espBinVer = Integer.parseInt(espBinVersionStr); 82 | 83 | if(latestBinVer > espBinVer) { 84 | LOGGER.log(Level.INFO, "Returning new firmware: " + latestBinName); 85 | writeFileToResponse(latestBinFile.getAbsolutePath(), baseRequest, response); 86 | } else { 87 | LOGGER.log(Level.INFO, "Device is already at latest firmware version"); 88 | response.setContentType("text/html;charset=utf-8"); 89 | response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); 90 | baseRequest.setHandled(true); 91 | response.getWriter().println("

Unchanged

"); 92 | } 93 | } 94 | 95 | private static Integer getBinVersion(String filename) { 96 | try { 97 | String binVerStr = filename.substring( 98 | StringUtils.lastIndexOf(filename, "_") + 1, 99 | StringUtils.lastIndexOfIgnoreCase(filename, ".bin")); 100 | 101 | return Integer.parseInt(binVerStr); 102 | } catch (Exception ex) { 103 | LOGGER.log(Level.ERROR, "Failed to parse bin version from: " + filename); 104 | return null; 105 | } 106 | } 107 | 108 | @SuppressWarnings("deprecation") 109 | private static void writeFileToResponse(String filePath, Request baseRequest, HttpServletResponse response) throws IOException { 110 | FileInputStream fis = null; 111 | OutputStream out = null; 112 | 113 | try { 114 | File fileInfo = new File(filePath); 115 | 116 | fis = new FileInputStream(filePath); 117 | response.setContentType("application/octet-stream"); 118 | response.setContentLengthLong(fileInfo.length()); 119 | response.setStatus(HttpServletResponse.SC_OK); 120 | 121 | out = response.getOutputStream(); 122 | IOUtils.copy(fis, out); // this is using apache-commons, 123 | // make sure you provide required JARs 124 | 125 | baseRequest.setHandled(true); 126 | } finally { 127 | IOUtils.closeQuietly(out); // this is using apache-commons, 128 | IOUtils.closeQuietly(fis); // make sure you provide required JARs 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /ota-server/src/main/java/OTAServer.java: -------------------------------------------------------------------------------- 1 | import java.io.IOException; 2 | 3 | import javax.servlet.ServletException; 4 | import javax.servlet.http.HttpServletRequest; 5 | import javax.servlet.http.HttpServletResponse; 6 | 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.apache.logging.log4j.Level; 9 | import org.apache.logging.log4j.LogManager; 10 | import org.apache.logging.log4j.Logger; 11 | import org.eclipse.jetty.server.Request; 12 | import org.eclipse.jetty.server.Server; 13 | import org.eclipse.jetty.server.handler.AbstractHandler; 14 | 15 | public class OTAServer extends AbstractHandler { 16 | private static final Logger LOGGER = LogManager.getLogger(OTAServer.class.getName()); 17 | 18 | public static void main(String[] args) throws Exception { 19 | LOGGER.log(Level.INFO, "Starting monitoring web app..."); 20 | 21 | Server server = new Server(8080); 22 | server.setHandler(new OTAServer()); 23 | 24 | server.start(); 25 | server.join(); 26 | } 27 | 28 | //Example filename - tempsensor_#.bin (where # is the new firmware version. ex: tempsensor_2.bin) 29 | 30 | public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 31 | throws IOException, ServletException { 32 | 33 | String hostname = request.getParameter("deviceid"); 34 | if(StringUtils.isBlank(hostname)) 35 | hostname = "Unknown"; 36 | 37 | LOGGER.log(Level.INFO, "DeviceID: " + hostname); 38 | LOGGER.log(Level.INFO, "Request: " + target); 39 | LOGGER.log(Level.INFO, "URL Args: " + request.getQueryString()); 40 | 41 | //quick hack 42 | if(target.endsWith("tempsensor_invalid.bin")) 43 | target = "/ota/tempsensor.bin"; 44 | 45 | String[] pathParts = StringUtils.split(target, "/"); 46 | if(pathParts.length > 0) { 47 | if(StringUtils.startsWithIgnoreCase(pathParts[0], "OTA")) { 48 | OTAHandler.handle(pathParts, baseRequest, request, response); 49 | } 50 | } 51 | 52 | if(!baseRequest.isHandled()) { 53 | response.setContentType("text/html;charset=utf-8"); 54 | response.setStatus(HttpServletResponse.SC_NOT_FOUND); 55 | baseRequest.setHandled(true); 56 | response.getWriter().println("

Invalid request

"); 57 | } 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /sensor-case/ESP8266_Temp_Sensor_Assembled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraschetti/ESP8266-TempSensor/567134ab2afc950b2203639224dc47f85307e49c/sensor-case/ESP8266_Temp_Sensor_Assembled.png -------------------------------------------------------------------------------- /sensor-case/ESP8266_Temp_Sensor_Case_Parts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraschetti/ESP8266-TempSensor/567134ab2afc950b2203639224dc47f85307e49c/sensor-case/ESP8266_Temp_Sensor_Case_Parts.png -------------------------------------------------------------------------------- /sensor-case/ESP8266_Temp_Sensor_Open_Case.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraschetti/ESP8266-TempSensor/567134ab2afc950b2203639224dc47f85307e49c/sensor-case/ESP8266_Temp_Sensor_Open_Case.png -------------------------------------------------------------------------------- /sensor-case/sensor_case.f3d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraschetti/ESP8266-TempSensor/567134ab2afc950b2203639224dc47f85307e49c/sensor-case/sensor_case.f3d -------------------------------------------------------------------------------- /sensor-case/sensor_case.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraschetti/ESP8266-TempSensor/567134ab2afc950b2203639224dc47f85307e49c/sensor-case/sensor_case.stl -------------------------------------------------------------------------------- /sensor/ESP8266_Temp_Sensor.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "DHT.h" 7 | 8 | //Release/Binary version (increase this with each new release) 9 | #define FIRMWARE_VERSION 1 10 | 11 | // DHT 22 sensor 12 | #define DHTTYPE DHT22 13 | 14 | #define MICROS 1000000 15 | 16 | //Thread on deep sleep - GPIO16 + RESET diode - https://www.esp8266.com/viewtopic.php?f=160&t=13625&start=12 17 | //ESP8266 D0 = 16 // 1N5817 Schottky diode (cathode to D0/16, anode to reset pin/pad) 18 | //ESP8266 D1 = 5 // Data pin for DHT22 sensor (can be changed in the config below) 19 | //ESP8266 D2 = 4 // Power pin for the DHT22 sensor (optional - can be changed in the config below) 20 | //ESP8266 D3 = 0 21 | //ESP8266 D4 = 2 22 | //ESP8266 D5 = 14 23 | //ESP8266 D6 = 12 24 | //ESP8266 D7 = 13 25 | //ESP8266 D8 = 15 26 | //ESP8266 D9 = 3 27 | //ESP8266 D10 = 1 28 | //DHT22 1 = Power pin. Either tied directly to the 5v rail or a software controlled pin D2/4 (see above) 29 | //DHT22 2 = Ground pin. Also use a 10k resistor to the 5v rail 30 | //DHT22 3 = Not connected 31 | //DHT22 4 = Data pin. Connected to D1/5 (see above) 32 | 33 | //InfluxDB 34 | #define INFLUXDB_URL "$PROTOCOL$://$HOST$:$PORT$:/write?db=$DB$" 35 | //https://docs.influxdata.com/influxdb/v1.6/write_protocols/line_protocol_reference/ 36 | String TEMP_DATA_TEMPLATE = "$measurement$,deviceid=$deviceid$ temperature=$temperature$,humidity=$humidity$,firmware=$firmware$,wifi_rssi=$wifi_rssi$"; 37 | 38 | // DHT sensor 39 | float sensorPrimeTime = 2500; //Time required for sensor to be ready after power on 40 | 41 | //WiFi 42 | long wifiConnectStart; 43 | 44 | //EEPROM storage struct for settings 45 | struct { 46 | //Sanity valid for a valid struct (checking for a single integer isn't sufficient) 47 | char validation_string[6]; 48 | 49 | short firmware_version; 50 | 51 | //OTA - Auto update URL 52 | char ota_url[128]; 53 | 54 | //Sensor settings 55 | short dht_pin; 56 | short dht_power_pin; 57 | float sensor_temp_adjustment; 58 | float sensor_humidity_adjustment; 59 | 60 | //WiFi settings 61 | char wifi_ssid[64]; 62 | char wifi_pwd[64]; 63 | 64 | //InfluxDB settings 65 | char influxdb_protocol[6]; 66 | char influxdb_host[64]; 67 | int influxdb_port; 68 | char influxdb_db[32]; 69 | char influxdb_measurement[32]; 70 | char device_id[64]; 71 | short send_interval; //seconds 72 | } 73 | localSettings; 74 | 75 | void setup() { 76 | Serial.begin(115200); 77 | 78 | EEPROM.begin(512); 79 | 80 | long iterationStart = millis(); 81 | 82 | Serial.println(); 83 | Serial.println(); 84 | Serial.println("-=ESP8266=-"); 85 | Serial.println("Firmware version #: " + String(FIRMWARE_VERSION)); 86 | Serial.println("Sketch date: " __DATE__ ", time: " __TIME__); 87 | Serial.printf("SDK version: %s\n", ESP.getSdkVersion()); 88 | Serial.printf("Core Version: %s\n", ESP.getCoreVersion().c_str()); 89 | Serial.printf("Boot Version: %u\n", ESP.getBootVersion()); 90 | Serial.printf("Boot Mode: %u\n", ESP.getBootMode()); 91 | Serial.printf("CPU Frequency: %u MHz\n", ESP.getCpuFreqMHz()); 92 | Serial.printf("WiFi MAC Address: %s\n", WiFi.macAddress().c_str()); 93 | Serial.printf("Reset reason: %s\n", ESP.getResetReason().c_str()); 94 | 95 | Serial.println("-=Temperature Logger=-"); 96 | bool readSettingsSuccess; 97 | readSettings(readSettingsSuccess); 98 | if (readSettingsSuccess) 99 | Serial.println("Successfully read and validated settings"); 100 | else { 101 | Serial.println("Failed to read settings. Going into longest allowed deep sleep."); 102 | ESP.deepSleep(4294967295 * MICROS); 103 | return; 104 | } 105 | 106 | Serial.println("-=Settings=-"); 107 | Serial.printf("Sensor data pin: %d\n", localSettings.dht_pin); 108 | Serial.printf("Sensor power pin: %d\n", localSettings.dht_power_pin); 109 | Serial.printf("OTA Update URL: %s\n", localSettings.ota_url); 110 | Serial.println("InfluxDB Protocol: " + String(localSettings.influxdb_protocol)); 111 | Serial.println("InfluxDB Host: " + String(localSettings.influxdb_host)); 112 | Serial.printf("InfluxDB Port: %d\n", localSettings.influxdb_port); 113 | Serial.println("InfluxDB DB: " + String(localSettings.influxdb_db)); 114 | Serial.println("InfluxDB Measurement: " + String(localSettings.influxdb_measurement)); 115 | Serial.println("DeviceID: " + String(localSettings.device_id)); 116 | Serial.printf("Temperature adjustment: : %.2f\n", localSettings.sensor_temp_adjustment); 117 | Serial.printf("Humidity adjustment: : %.2f\n", localSettings.sensor_humidity_adjustment); 118 | 119 | //Init sensor power pin 120 | if (localSettings.dht_power_pin >= 0) { 121 | Serial.println("Energizing sensor power pin..."); 122 | pinMode(localSettings.dht_power_pin, OUTPUT); 123 | digitalWrite(localSettings.dht_power_pin, HIGH); 124 | } 125 | 126 | DHT dht(localSettings.dht_pin, DHTTYPE); 127 | dht.begin(); 128 | long sensorPrimeStart = millis(); 129 | 130 | //Start the WiFi before priming the sensor to make good use of our time 131 | startWiFi(); 132 | 133 | long remainingPrimeTime = sensorPrimeTime - (millis() - sensorPrimeStart); 134 | Serial.println("Priming sensor..."); 135 | if (remainingPrimeTime > 0) { 136 | delay(remainingPrimeTime); 137 | } 138 | Serial.println("Sensor primed"); 139 | 140 | connectToWiFi(1); 141 | 142 | if (WiFi.status() == WL_CONNECTED) { 143 | sendData(dht); 144 | } 145 | 146 | //Deenergize sensor power pin 147 | if (localSettings.dht_power_pin >= 0) { 148 | Serial.println("Denergizing sensor power pin..."); 149 | digitalWrite(localSettings.dht_power_pin, LOW); 150 | } 151 | 152 | if (WiFi.status() == WL_CONNECTED) 153 | attemptFirmwareUpdate(); 154 | 155 | stopWiFi(); 156 | 157 | unsigned long iterationElapsed = millis() - iterationStart; 158 | Serial.printf("Iteration elapsed: %lums\n", iterationElapsed); 159 | 160 | long sleepMicros = (localSettings.send_interval * MICROS) - micros(); 161 | if (sleepMicros <= 0) { 162 | Serial.println("No sleep needed. Restarting"); 163 | ESP.restart(); 164 | } else { 165 | int sleepSeconds = sleepMicros / MICROS; 166 | Serial.printf("Entering deep sleep (%dsecs)...\n", sleepSeconds); 167 | ESP.deepSleep(sleepMicros); 168 | } 169 | } 170 | 171 | void loop() { 172 | } 173 | 174 | void readSettings(bool &success) { 175 | Serial.println("Reading settings from EEPROM..."); 176 | 177 | for (unsigned int t = 0; t < sizeof(localSettings); t++) 178 | *((char*)&localSettings + t) = EEPROM.read(t); 179 | 180 | boolean needNewSettings = true; 181 | 182 | if (String(localSettings.validation_string) != "Valid") { 183 | Serial.println("Validation of exisiting settings failed. Validation header missing. New settings needed."); 184 | needNewSettings = true; 185 | } 186 | 187 | if (!needNewSettings && localSettings.firmware_version != FIRMWARE_VERSION) { 188 | Serial.println("Validaton of existing settings failed. Config firmware version does not match current firmware version. New settings needed"); 189 | needNewSettings = true; 190 | } 191 | 192 | if (needNewSettings) { 193 | Serial.println("New settings needed. Attempting to load new settings."); 194 | bool writeSettingsSuccess; 195 | writeNewDeviceSettings(writeSettingsSuccess); 196 | if (!writeSettingsSuccess) { 197 | success = false; 198 | return; 199 | } 200 | } 201 | 202 | //Validate WiFi settings 203 | String ssid = String(localSettings.wifi_ssid); 204 | String pwd = String(localSettings.wifi_pwd); 205 | if (ssid.length() == 0 || pwd.length() == 0) { 206 | Serial.println("Failed to validate WiFi settings"); 207 | success = false; 208 | return; 209 | } 210 | 211 | //Validate InfluxDB settings 212 | String protocol = String(localSettings.influxdb_protocol); 213 | String host = String(localSettings.influxdb_host); 214 | String db = String(localSettings.influxdb_db); 215 | String measurement = String(localSettings.influxdb_measurement); 216 | String deviceid = String(localSettings.device_id); 217 | if (protocol.length() == 0 || host.length() == 0 || db.length() == 0 218 | || measurement.length() == 0 || deviceid.length() == 0) { 219 | Serial.println("Failed to validate InfluxDB settings"); 220 | success = false; 221 | return; 222 | } 223 | 224 | success = true; 225 | } 226 | 227 | //Validate existing EEPROM settings are up to date and commit new settings if needed 228 | void writeNewDeviceSettings(bool &success) { 229 | Serial.println("Attempting to write new device settings..."); 230 | 231 | char* otaUrl = "http://otaserverhostname:otaserverport/ota/tempsensor.bin"; //Blank if not using OTA updates 232 | short dhtPin = 5; //ESP8266 D1 233 | short dhtPowerPin = 4; //ESP8266 D2 (Not required if you use a power pin directly) 234 | char* wifiSSID = "yourwifissid"; 235 | char* wifiPwd = "yourwifipwd"; 236 | char* influxDBProtocol = "http"; 237 | char* influxDBHost = "yourinfluxdbhost"; 238 | int influxDBPort = 8086; 239 | char* influxDBDB = "sensordata"; 240 | char* influxDBMeasurement = "temperature"; 241 | short sendInterval = 60; //seconds 242 | 243 | String mac_address = WiFi.macAddress().c_str(); 244 | 245 | float sensorTempAdjustment; 246 | float sensorHumdidityAdjustment; 247 | String deviceId; 248 | 249 | if (mac_address == "AA:AA:AA:AA:AA:AA") { 250 | sensorTempAdjustment = 0.0; 251 | sensorHumdidityAdjustment = 0.0; 252 | deviceId = "SensorA"; 253 | } else if (mac_address == "BB:BB:BB:BB:BB:BB") { 254 | sensorTempAdjustment = 0.0; 255 | sensorHumdidityAdjustment = 0.0; 256 | deviceId = "SensorB"; 257 | } else if (mac_address == "CC:CC:CC:CC:CC:CC") { 258 | sensorTempAdjustment = 0.0; 259 | sensorHumdidityAdjustment = 0.0; 260 | deviceId = "SensorC"; 261 | } else { 262 | Serial.printf("No configuration data found MAC address: %s\n", WiFi.macAddress().c_str()); 263 | success = false; 264 | return; 265 | } 266 | 267 | //TODO bind config version to firmware version so they're updated together 268 | strcpy(localSettings.validation_string, "Valid"); //DO NOT CHANGE THIS 269 | localSettings.firmware_version = FIRMWARE_VERSION; 270 | 271 | //OTA URL 272 | strcpy(localSettings.ota_url, otaUrl); 273 | 274 | //Sensor settings 275 | localSettings.dht_pin = dhtPin; 276 | localSettings.dht_power_pin = dhtPowerPin; 277 | localSettings.sensor_temp_adjustment = sensorTempAdjustment; 278 | localSettings.sensor_humidity_adjustment = sensorHumdidityAdjustment; 279 | //WiFi settings 280 | strcpy(localSettings.wifi_ssid, wifiSSID); 281 | strcpy(localSettings.wifi_pwd, wifiPwd); 282 | //InfluxDB settings 283 | strcpy(localSettings.influxdb_protocol, influxDBProtocol); 284 | strcpy(localSettings.influxdb_host, influxDBHost); 285 | localSettings.influxdb_port = influxDBPort; 286 | strcpy(localSettings.influxdb_db, influxDBDB); 287 | strcpy(localSettings.influxdb_measurement, influxDBMeasurement); 288 | strcpy(localSettings.device_id, deviceId.c_str()); 289 | localSettings.send_interval = sendInterval; 290 | 291 | Serial.println("Writing settings to EEPROM..."); 292 | 293 | for (unsigned int t = 0; t < sizeof(localSettings); t++) 294 | EEPROM.write(t, *((char*)&localSettings + t)); 295 | 296 | EEPROM.commit(); 297 | 298 | success = true; 299 | } 300 | 301 | int maxWiFiConnectAttemps = 2; 302 | void connectToWiFi(int attemptNumber) { 303 | Serial.println("Waiting for WiFi connection..."); 304 | 305 | int maxWaitMs = 15 * 1000; 306 | unsigned long waitAbortMs = millis() + maxWaitMs; 307 | while (WiFi.status() != WL_CONNECTED) { 308 | if (millis() >= waitAbortMs) { 309 | break; 310 | } 311 | delay(250); 312 | } 313 | 314 | if (WiFi.status() == WL_CONNECTED) { 315 | IPAddress localIP = WiFi.localIP(); 316 | Serial.printf("Connected to WiFi - IP: %d.%d.%d.%d\n", localIP[0], localIP[1], localIP[2], localIP[3]); 317 | Serial.printf("WiFi connection time %lums\n", millis() - wifiConnectStart); 318 | Serial.printf("WiFi signal stregnth: %d dBm\n", WiFi.RSSI()); 319 | Serial.printf("WiFi channel: %d\n", WiFi.channel()); 320 | } else if (attemptNumber >= maxWiFiConnectAttemps) { 321 | Serial.println("Final attempt to connect to WiFi failed."); 322 | } else { 323 | Serial.println("Failed to connect to WiFi. Retrying."); 324 | stopWiFi(); 325 | startWiFi(); 326 | connectToWiFi(attemptNumber + 1); 327 | } 328 | } 329 | 330 | void startWiFi() { 331 | Serial.println("Waking WiFi..."); 332 | WiFi.forceSleepWake(); 333 | delay(1); 334 | 335 | Serial.println("Connecting to WiFi network..."); 336 | 337 | WiFi.hostname("Sensor - " + String(localSettings.device_id)); 338 | WiFi.mode(WIFI_STA); 339 | WiFi.persistent(false); //Eliminate flash writes 340 | WiFi.setAutoConnect(false); 341 | WiFi.setAutoReconnect(false); 342 | WiFi.begin(localSettings.wifi_ssid, localSettings.wifi_pwd); 343 | wifiConnectStart = millis(); 344 | 345 | //DO NOT USE WiFi.waitForConnectResult() as current implementations use large timeouts 346 | } 347 | 348 | // https://github.com/esp8266/Arduino/issues/644 349 | void stopWiFi() { 350 | Serial.println("Putting WiFi to sleep..."); 351 | 352 | WiFi.disconnect(); 353 | delay(1000); //See: https://github.com/esp8266/Arduino/issues/4082 354 | WiFi.mode(WIFI_OFF); 355 | 356 | WiFi.forceSleepBegin(); 357 | delay(1); 358 | } 359 | 360 | void sendData(DHT &dht) { 361 | Serial.println("-=Send Data Start=-"); 362 | 363 | String deviceIdFormatted = localSettings.device_id; 364 | formatLineProtocol(deviceIdFormatted); 365 | 366 | String temp_influx_url = INFLUXDB_URL; 367 | temp_influx_url.replace("$PROTOCOL$", localSettings.influxdb_protocol); 368 | temp_influx_url.replace("$HOST$", localSettings.influxdb_host); 369 | temp_influx_url.replace("$PORT$", String(localSettings.influxdb_port)); 370 | temp_influx_url.replace("$DB$", localSettings.influxdb_db); 371 | 372 | float temp_data[2]; 373 | readTemp(dht, temp_data); 374 | float humidity_data = temp_data[0]; 375 | float temperature_data = temp_data[1]; 376 | 377 | if (isnan(humidity_data) 378 | || humidity_data <= 0 379 | || isnan(temperature_data) 380 | || temperature_data <= 0) { 381 | Serial.println("-=Send Data End=-"); 382 | return; 383 | } 384 | 385 | String temp_data_str = TEMP_DATA_TEMPLATE; 386 | temp_data_str.replace("$measurement$", localSettings.influxdb_measurement); 387 | temp_data_str.replace("$deviceid$", deviceIdFormatted); 388 | temp_data_str.replace("$temperature$", String(temperature_data, 2)); 389 | temp_data_str.replace("$humidity$", String(humidity_data, 2)); 390 | temp_data_str.replace("$firmware$", String(FIRMWARE_VERSION)); 391 | temp_data_str.replace("$wifi_rssi$", String(WiFi.RSSI())); 392 | 393 | Serial.println("InfluxDB URL: " + temp_influx_url); 394 | Serial.println("InfluxDB Record: " + temp_data_str); 395 | 396 | HTTPClient http; 397 | http.begin(temp_influx_url); 398 | 399 | int statusCode = http.POST(temp_data_str); 400 | String rspBody = http.getString(); 401 | 402 | Serial.printf("Response Status Code: %d (Success = 204)\n", statusCode); 403 | 404 | if (rspBody != NULL) { 405 | rspBody.trim(); 406 | 407 | if (rspBody.length() > 0) { 408 | Serial.println("Response Body: " + rspBody); 409 | } 410 | } 411 | 412 | http.end(); 413 | Serial.println("-=Send Data End=-"); 414 | } 415 | 416 | void readTemp(DHT &dht, float temp_data[]) { 417 | // Grab the current state of the sensor, 418 | // allowing for a couple of read errors (it happens from time to time) 419 | long startRead = millis(); 420 | Serial.println("Reading temperature..."); 421 | for (int i = 0; i < 10; i++) { 422 | float temperature_data = dht.readTemperature(true); 423 | float humidity_data = dht.readHumidity(); 424 | 425 | 426 | if (!isnan(temperature_data) && !isnan(humidity_data)) { 427 | Serial.printf("Sensor read elapsed: %lums\n", millis() - startRead); 428 | Serial.printf("Orig Temperature: %.2f\n", temperature_data); 429 | Serial.printf("Orig Humidity: %.2f\n", humidity_data); 430 | 431 | temperature_data += localSettings.sensor_temp_adjustment; 432 | humidity_data += localSettings.sensor_humidity_adjustment; 433 | 434 | Serial.printf("Adjusted Temperature: %.2f\n", temperature_data); 435 | Serial.printf("Adjusted Humidity: %.2f\n", humidity_data); 436 | 437 | temp_data[0] = humidity_data; 438 | temp_data[1] = temperature_data; 439 | 440 | return; 441 | } 442 | delay(2000); 443 | } 444 | 445 | Serial.println("Failed to read temperature & humidity data."); 446 | 447 | temp_data[0] = 0; 448 | temp_data[1] = 0; 449 | } 450 | 451 | void formatLineProtocol(String &input) { 452 | input.replace(",", "\\,"); 453 | input.replace("=", "\\="); 454 | input.replace(" ", "\\ "); 455 | input.replace("\"", "\\\""); 456 | } 457 | 458 | void attemptFirmwareUpdate() { 459 | Serial.println("Checking for firmware update..."); 460 | 461 | String OTAUrl = String(localSettings.ota_url); 462 | if (OTAUrl == NULL || OTAUrl.length() == 0) { 463 | Serial.println("No configured OTA update URL. Skipping update check."); 464 | return; 465 | } 466 | 467 | OTAUrl = OTAUrl + "?deviceid=" + urlencode(String(localSettings.device_id)) 468 | + "&firmware_version=" + String(FIRMWARE_VERSION) 469 | + "&temp_adjustment=" + String(localSettings.sensor_temp_adjustment, 2) 470 | + "&humidity_adjustment=" + String(localSettings.sensor_humidity_adjustment, 2) 471 | + "&wifi_rssi=" + String(WiFi.RSSI()); 472 | 473 | 474 | //TODO call handleUpdate directly to pass in a HTTPClient with manual timeouts? 475 | t_httpUpdate_return ret = ESPhttpUpdate.update(OTAUrl, String(FIRMWARE_VERSION)); 476 | Serial.println("Update check complete."); 477 | 478 | switch (ret) { 479 | case HTTP_UPDATE_FAILED: 480 | Serial.printf("Auto-update - Failed - Error(%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); 481 | break; 482 | 483 | case HTTP_UPDATE_NO_UPDATES: 484 | Serial.println("Auto-update - No update required"); 485 | break; 486 | 487 | case HTTP_UPDATE_OK: 488 | //This path will apparently never actually be called (auto reset is part of the update) 489 | Serial.println("Auto-update - Success"); 490 | break; 491 | } 492 | } 493 | 494 | //Borrowed from https://github.com/zenmanenergy/ESP8266-Arduino-Examples/blob/master/helloWorld_urlencoded/urlencode.ino 495 | //I needed something quick to get a job done but haven't fully vetted this logic 496 | String urlencode(String str) 497 | { 498 | String encodedString = ""; 499 | char c; 500 | char code0; 501 | char code1; 502 | for (unsigned int i = 0; i < str.length(); i++) { 503 | c = str.charAt(i); 504 | if (c == ' ') { 505 | encodedString += '+'; 506 | } else if (isalnum(c)) { 507 | encodedString += c; 508 | } else { 509 | code1 = (c & 0xf) + '0'; 510 | if ((c & 0xf) > 9) { 511 | code1 = (c & 0xf) - 10 + 'A'; 512 | } 513 | c = (c >> 4) & 0xf; 514 | code0 = c + '0'; 515 | if (c > 9) { 516 | code0 = c - 10 + 'A'; 517 | } 518 | encodedString += '%'; 519 | encodedString += code0; 520 | encodedString += code1; 521 | } 522 | yield(); 523 | } 524 | return encodedString; 525 | } 526 | -------------------------------------------------------------------------------- /sensor/ESP8266_Temp_Sensor_Wiring_Diagram.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraschetti/ESP8266-TempSensor/567134ab2afc950b2203639224dc47f85307e49c/sensor/ESP8266_Temp_Sensor_Wiring_Diagram.fzz -------------------------------------------------------------------------------- /sensor/ESP8266_Temp_Sensor_Wiring_Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fraschetti/ESP8266-TempSensor/567134ab2afc950b2203639224dc47f85307e49c/sensor/ESP8266_Temp_Sensor_Wiring_Diagram.png --------------------------------------------------------------------------------