Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // ESP 8266 Temperature and Humidity Station - V0.3
- //
- // This sketch does the following:
- //
- // 1. Reads temperature and humidity from DHT sensors every 10 seconds.
- // 2. Checks for updates and applies them.
- // 3. Stores the temp, humidity, id of device, batt charge and firmware id in InfluxDB database.
- // 4. Goes to sleep for a period dependent on the battery charge.
- //
- // TODO:
- // 1. SPIFFS settings for update bands.
- // 2. SPIFFS setting for DHT device in use.
- // 3. Metadata for firmware, checksums, version info
- // 4. Clean up redundant code and improve structure, check best practices.
- // 5. SPIFFS data for url of firmware.
- // 6. SPIFFS for all wifi hotspots that the device can connect to.
- // 7. No hard coded wifi credentials, log in and set from AP mode.
- // 8. Message via email or tweet that the battery charge is low.
- // 9. Replace DHT sensor with an I2C device.
- // 10. Improve structure...reduce globals...look for reusability opportunities.
- //
- // Notes on external sources:
- //
- // Thanks go to those that have provided the examples that were the basis for this project. Where I have remembered the source of the source code, I have linked to it.
- // Please take the time to visit their pages.
- //
- // Sources:
- // Erik H. Bakke – Excellent guide on self-updating OTA - https://www.bakke.online/index.php/2017/06/02/self-updating-ota-firmware-for-esp8266/
- // Volmer Mihai-Cristian - AAC - https://elf.cs.pub.ro/wsn/wiki/proiecte/influxdb
- // Rui Santos / Sara Santos - Random Nerd Tutorials - https://randomnerdtutorials.com/esp8266-dht11dht22-temperature-and-humidity-web-server-with-arduino-ide/
- // GitHub hwwong - https://github.com/hwwong/ESP_influxdb
- // lady ada / Tony DiCola - https://learn.adafruit.com/dht
- //
- // "Based on" = Where I have edited to suit my application.
- // "Source" = If external, mostly a copy and paste, or very minor edit from the original source.
- //
- //
- /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // Including the ESP8266 WiFi library
- #include <ESP8266WiFi.h> //
- #include <ESP8266WiFiMulti.h>
- ESP8266WiFiMulti wifiMulti;
- #include <WiFiUdp.h>
- #include "DHT.h" //https://learn.adafruit.com/dht
- #include "ESPinfluxdb.h" //https://github.com/hwwong/ESP_influxdb
- #include "TimeLib.h"//https://github.com/PaulStoffregen/Time
- //for the sleep ota
- #include <ESP8266HTTPClient.h>
- #include <ESP8266httpUpdate.h>
- const int FW_VERSION = 1003; //V0.3
- const char * fwUrlBase = "http://192.168.1.???:8000/firmware/esp8266/temp_humidity_sleeper/";//replace ??? with your ip address
- HTTPClient httpClient;
- ADC_MODE(0);
- // Uncomment one of the lines below for whatever DHT sensor type you're using
- //#define DHTTYPE DHT11 // DHT 11
- //#define DHTTYPE DHT21 // DHT 21 (AM2301)
- #define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321
- WiFiClient client;
- //influxdb details
- const char * INFLUXDB_HOST = "192.168.1.???";//replace ??? with your database IP address
- const uint16_t INFLUXDB_PORT = 8086;
- const char * DATABASE = "logger";
- const char * DB_USER = "yourinfluxdbuser";
- const char * DB_PASSWORD = "yourinfluxdbpassword";
- Influxdb influxdb(INFLUXDB_HOST, INFLUXDB_PORT);
- //Sleep time constants
- const int64_t NOMINAL_DELAY = 600e6; //600 seconds, 10 minutes
- const int64_t MAX_DELAY = ESP.deepSleepMax(); //12600e6;
- const unsigned int BAND_A = 500; //500 is approximately 3V - Your values will depend on the voltage divider you use, experiment.
- const unsigned int BAND_B = 545; //545 is approximately 3.3V - Your values will depend on the voltage divider you use, experiment.
- const unsigned int BAND_C = 570; //570 is approximately 3.5V - Your values will depend on the voltage divider you use, experiment.
- const unsigned int BAND_D = 590; //590 is approximately 3.6V - Your values will depend on the voltage divider you use, experiment.
- const unsigned int BAND_AB_DELAY_MULTIPLIER = 9; //The battery voltage is down to between 3V & 3.3V
- const unsigned int BAND_BC_DELAY_MULTIPLIER = 6; //The battery voltage is down to between 3.3V & 3.5V
- const unsigned int BAND_CD_DELAY_MULTIPLIER = 3; //The battery voltage is down to between 3.5V & 3.6V
- // DHT Sensor
- const int DHTPin = D1;
- // Initialize DHT sensor.
- DHT dht(DHTPin, DHTTYPE);
- //LED
- #define LED D0
- uint32_t chipid = ESP.getChipId();
- static String ChipIDStr = String(chipid, HEX);
- char * HexChipID = (char * ) malloc(9);
- float tc, tf, h = 0;
- float old_tc, old_tf, old_h = 0;
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // Setup - Sets up the project objects and variables and executes session processes before going to sleep.
- // Loop() is never reached.
- //
- // Source - Based on various including my own, details included at the opening comment.
- //
- // 1. Initialises serial port
- // 2. Initialises the DHT device
- // 3. Connects to Wifi
- // 4. Checks for firmware updates
- // 5. Opens the InfluxDB database
- // 6. Measures battery voltage
- // 7. Read DHT device
- // 8. Send data to database
- // 9. Go to sleep for a time appropriate for the battery charge.
- //
- // todo: Improve hard loops when connecting to wifi and database, if time out, drop out and sleep until next
- // time.
- // Improve firmware update process - security, resolution of correct file, checksums, metadata etc.
- //
- //
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////
- void setup() {
- Serial.begin(115200);
- Serial.setTimeout(2000);
- // Wait for serial to initialise.
- while (!Serial) {
- yield();
- }
- //initialise the dht device
- dht.begin();
- WiFi.mode(WIFI_STA);
- wifiMulti.addAP("YOUR_SSID_ONE", "yourpassword1");//comment out any that are not needed
- wifiMulti.addAP("YOUR_SSID_TWO", "yourpassword2");
- wifiMulti.addAP("YOUR_SSID_THREE", "yourpassword3");
- wifiMulti.addAP("YOUR_SSID_FOUR", "yourpassword4");
- //This needs looking at again. It works, but it could be better
- Serial.println("Connecting Wifi...");
- if (wifiMulti.run() == WL_CONNECTED) {
- Serial.println("");
- Serial.println("WiFi connected");
- Serial.println("IP address: ");
- Serial.println(WiFi.localIP());
- }
- //This needs looking at again. It works, but it could be better
- while (wifiMulti.run() != WL_CONNECTED) {
- Serial.println("WiFi not connected!");
- delay(1000);
- }
- // print the SSID of the network you're attached to:
- Serial.print("SSID: ");
- Serial.println(WiFi.SSID());
- long rssi = WiFi.RSSI();
- Serial.print("signal strength (RSSI):");
- Serial.print(rssi);
- Serial.println(" dBm");
- //Self OTA update check
- Serial.printf("Checking for updates, current firmware version is %d\n", FW_VERSION);
- checkForUpdates();
- //TODO need to ensure we can pop out of this loop if the DB fails
- while (influxdb.opendb(DATABASE, DB_USER, DB_PASSWORD) != DB_SUCCESS) {
- Serial.println("Open influxdb database failed");
- delay(10000);
- }
- Serial.printf(" ESP8266 Chip id = %08X\n", ESP.getChipId());
- sprintf(HexChipID, "%08X", ESP.getChipId());
- //get the power reading from the battery
- int power = getReadings(250);
- //if the sensor is ready and the timer event has fired, then take store the latest measurement
- //keeping hold of the current values. This allows us to only update the database when values
- //have changed.
- if (GetMeasurement(tc, tf, h, true) == true) {
- digitalWrite(LED, HIGH); //so we know the unit is still alive
- Serial.println("Sending new data to database");
- SendDataToDatabase(client, tc, h, power, FW_VERSION, WiFi.SSID(), WiFi.RSSI());
- digitalWrite(LED, LOW);
- } else {
- Serial.println("We didnt get a measurement, going to sleep to try again next time");
- }
- // Deep sleep mode for resolveDelayTime seconds, the ESP8266 wakes up by itself when GPIO 16 (D0 in NodeMCU board) is connected to the RESET pin
- Serial.print("Going into deep sleep mode for ");
- Serial.print(resolveDelayTime(power) / 1e6);
- Serial.println(" seconds");
- ESP.deepSleep(resolveDelayTime(power));
- }
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // getReadings - Reads the ADC port a number of times before returning the average measurement
- //
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////
- int getReadings(int numOfCycles) {
- int reading = 0;
- for (int n = 0; n < numOfCycles; n++) {
- yield();
- reading += analogRead(A0);
- }
- return (int) reading / numOfCycles;
- }
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // GetMeasurement - Gets a measurement from the DH sensor
- //
- // Based on: https://randomnerdtutorials.com/esp8266-dht11dht22-temperature-and-humidity-web-server-with-arduino-ide/
- //
- // 1. When the sensor is ready read the sensor
- // 2. If the sensor output is valid, compute the values and return a flag that denotes success.
- // 3. If the wrapAroundCounter reaches the threshold, exit the function and return False denoting failure
- // to read sensor.
- //
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////
- bool GetMeasurement(float & temp_C, float & temp_F, float & humidity, bool SerialDebugOutput) {
- //status of the sensor read. false returned = fail
- bool status = false;
- int wrapAroundCounter = 0;
- while (!status) {
- if (wrapAroundCounter > 2000) {//todo, make const or metadata in SPIFFS
- Serial.println("Failed to read sensor in time, exiting");
- return false;
- } //the sensor is not reading
- // Sensor readings may also be up to 2 seconds 'old'
- float h = dht.readHumidity();
- // Read temperature as Celsius (the default)
- float t = dht.readTemperature();
- // Read temperature as Fahrenheit (isFahrenheit = true)
- float f = dht.readTemperature(true);
- // Check if any reads failed and exit early (to try again).
- if ((isnan(h) || isnan(t) || isnan(f)) && SerialDebugOutput) {
- Serial.println("Failed to read from DHT sensor!");
- wrapAroundCounter++;
- yield();
- } else {
- // Computes temperature values in Celsius + Fahrenheit and Humidity
- temp_C = dht.computeHeatIndex(t, h, false);
- temp_F = dht.computeHeatIndex(f, h);
- humidity = h;
- status = true;
- }
- } //while status
- return status;
- }
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // SendDataToDatabase - Sends data to existing InfluxDB database
- //
- // Based on: https://elf.cs.pub.ro/wsn/wiki/proiecte/influxdb
- //
- // 1. Connect to the pre-existing InfluxDB
- // 2. Output the UID of the ESP8266, tempC, tempF, humidity and firmware version to the database.
- //
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////
- void SendDataToDatabase(WiFiClient client, int t, int h, int power, int firmware, String ConnectedSSID, long ConnectedRSSI) {
- if (!client.connect(INFLUXDB_HOST, INFLUXDB_PORT)) {
- Serial.println("Cant connect to InfluxDB");
- } else {
- //Create data object with measurment name=analog_read
- dbMeasurement row("temp_humidity");
- row.addTag("UID_HEX", String(HexChipID));
- row.addTag("ssid", ConnectedSSID); //id of the connected wifi station
- row.addField("temp_C", t); //
- row.addField("humidity", h); //
- row.addField("power", power);
- row.addField("rssi", ConnectedRSSI); //strength of the connected wifi station
- row.addField("firmware_version", firmware);
- Serial.println(influxdb.write(row) == DB_SUCCESS ? "Object write success" : "Writing failed");
- // Empty field object.
- row.empty();
- }
- }
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // checkForUpdates() - Checks file server for later firmware version. If there is a newer version available
- // this will be downloaded into flash memory and ESP8266 will be rebooted with new
- // firmware.
- // The URL is checked for a file that corresponds to the last 3 bytes of the MAC. The
- // .version file starts with the integer of the version number, this is compared with the
- // const FW_VERSION. If the file-server version is larger than the FW_VERSION then
- // the .bin file is downloaded.
- //
- // If the .version file, or the web server isnt reachable, the function will exit.
- // Updates are checked every time the program wakes up.
- //
- // Source : Erik H. Bakke – Excellent guide on self-updating OTA -
- // https://www.bakke.online/index.php/2017/06/02/self-updating-ota-firmware-for-esp8266/
- //
- // Todo : Think about better ways of doing this that would suit large scale deployments
- // Think about the source file containing more metadata
- // Think about rolling metadata into bin and unpacking during update so the firmware and .version file
- // are tied into a single file.
- // Deploy over https or depoy more securely.
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////
- void checkForUpdates() {
- String mac = String(ESP.getChipId(), HEX);
- String fwURL = String(fwUrlBase) + mac + "/" + mac;
- String fwVersionURL = fwURL + ".version";
- Serial.println("Checking for firmware updates.");
- Serial.print("MAC address: ");
- Serial.println(mac);
- Serial.print("Firmware version URL: ");
- Serial.println(fwVersionURL);
- HTTPClient httpClient;
- httpClient.begin(fwVersionURL);
- int httpCode = httpClient.GET();
- if (httpCode == 200) {
- String newFWVersion = httpClient.getString();
- Serial.print("Current firmware version: ");
- Serial.println(FW_VERSION);
- Serial.print("Available firmware version: ");
- Serial.println(newFWVersion);
- int newVersion = newFWVersion.toInt();
- if (newVersion > FW_VERSION) {
- Serial.println("Preparing to update");
- String fwImageURL = fwURL;
- fwImageURL.concat(".bin");
- t_httpUpdate_return ret = ESPhttpUpdate.update(fwImageURL);
- switch (ret) {
- case HTTP_UPDATE_FAILED:
- Serial.printf("HTTP_UPDATE_FAILD Error (%d): %s", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
- break;
- case HTTP_UPDATE_NO_UPDATES:
- Serial.println("HTTP_UPDATE_NO_UPDATES");
- break;
- }
- } else {
- Serial.println("Already on latest version");
- }
- } else {
- Serial.print("Firmware version check failed, got HTTP response code ");
- Serial.println(httpCode);
- }
- httpClient.end();
- }
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // resolveDelayTime(int power) - Takes a given power reading and returns the sleep time.
- //
- // As the battery depletes, the updates will be less frequent. The delay will
- // be a maximum value of MAX_DELAY which is approx 3.3 hrs. This value changes
- // (apparently) hence why it is generated. Failure to do this will brick the
- // unit as it will never wake from sleep.
- //
- // There is a check to prevent calculated delay being greater than MAX_DELAY
- //
- // There is a commented out "resolvedDelay = NOMINAL_DELAY;" which is there
- // to prevent zero battery voltage (when connected via USB) returning MAX_DELAY
- // for debug purposes.
- //
- // todo: Should would be better if the bands are stored in a SPIFFS file rather than hard coded into source.
- //
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////
- int64_t
- resolveDelayTime(int power) {
- int64_t resolvedDelay = 0;
- if (power < BAND_A) {
- resolvedDelay = MAX_DELAY;
- } else if (power >= BAND_A && power < BAND_B) {
- resolvedDelay = NOMINAL_DELAY * BAND_AB_DELAY_MULTIPLIER;
- } else if (power >= BAND_B && power < BAND_C) {
- resolvedDelay = NOMINAL_DELAY * BAND_BC_DELAY_MULTIPLIER;
- } else if (power >= BAND_C && power < BAND_D) {
- resolvedDelay = NOMINAL_DELAY * BAND_CD_DELAY_MULTIPLIER;
- } else {
- resolvedDelay = NOMINAL_DELAY;
- }
- //catch whether the delay is more than max delay, prevents permanent sleep.
- if (resolvedDelay > MAX_DELAY) {
- resolvedDelay = MAX_DELAY;
- }
- //resolvedDelay = NOMINAL_DELAY;///////////////??DEBUG, without battery connected to ADC, update normally
- return resolvedDelay;
- }
- void loop() {} //never reaches loop
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement