├── 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 | 
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 | 
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 | 
49 | 
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
--------------------------------------------------------------------------------