Advertisement
metimmee

temp_humidity_sleeper.ino

Apr 28th, 2019
312
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 18.01 KB | None | 0 0
  1. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  2. //    ESP 8266 Temperature and Humidity Station - V0.3
  3. //
  4. //    This sketch does the following:
  5. //
  6. //                                    1.  Reads temperature and humidity from DHT sensors every 10 seconds.
  7. //                                    2.  Checks for updates and applies them.
  8. //                                    3.  Stores the temp, humidity, id of device, batt charge and firmware id in InfluxDB database.
  9. //                                    4.  Goes to sleep for a period dependent on the battery charge.
  10. //
  11. //                              TODO:
  12. //                                    1.  SPIFFS settings for update bands.
  13. //                                    2.  SPIFFS setting for DHT device in use.
  14. //                                    3.  Metadata for firmware, checksums, version info
  15. //                                    4.  Clean up redundant code and improve structure, check best practices.
  16. //                                    5.  SPIFFS data for url of firmware.
  17. //                                    6.  SPIFFS for all wifi hotspots that the device can connect to.
  18. //                                    7.  No hard coded wifi credentials, log in and set from AP mode.
  19. //                                    8.  Message via email or tweet that the battery charge is low.
  20. //                                    9.  Replace DHT sensor with an I2C device.
  21. //                                   10.  Improve structure...reduce globals...look for reusability opportunities.
  22. //
  23. //    Notes on external sources:
  24. //
  25. //          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.  
  26. //          Please take the time to visit their pages.  
  27. //  
  28. //    Sources:
  29. //          Erik H. Bakke – Excellent guide on self-updating OTA - https://www.bakke.online/index.php/2017/06/02/self-updating-ota-firmware-for-esp8266/
  30. //          Volmer Mihai-Cristian - AAC - https://elf.cs.pub.ro/wsn/wiki/proiecte/influxdb
  31. //          Rui Santos / Sara Santos - Random Nerd Tutorials - https://randomnerdtutorials.com/esp8266-dht11dht22-temperature-and-humidity-web-server-with-arduino-ide/
  32. //          GitHub hwwong - https://github.com/hwwong/ESP_influxdb
  33. //          lady ada / Tony DiCola - https://learn.adafruit.com/dht
  34. //
  35. //          "Based on" = Where I have edited to suit my application.
  36. //          "Source" = If external, mostly a copy and paste, or very minor edit from the original source.
  37. //
  38. //
  39. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  40.  
  41. // Including the ESP8266 WiFi library
  42. #include <ESP8266WiFi.h> //
  43.  
  44. #include <ESP8266WiFiMulti.h>
  45.  
  46. ESP8266WiFiMulti wifiMulti;
  47.  
  48. #include <WiFiUdp.h>
  49.  
  50. #include "DHT.h"          //https://learn.adafruit.com/dht
  51.  
  52. #include "ESPinfluxdb.h"  //https://github.com/hwwong/ESP_influxdb
  53.  
  54. #include "TimeLib.h"//https://github.com/PaulStoffregen/Time
  55.  
  56. //for the sleep ota
  57. #include <ESP8266HTTPClient.h>
  58.  
  59. #include <ESP8266httpUpdate.h>
  60.  
  61. const int FW_VERSION = 1003; //V0.3
  62. const char * fwUrlBase = "http://192.168.1.???:8000/firmware/esp8266/temp_humidity_sleeper/";//replace ??? with your ip address
  63. HTTPClient httpClient;
  64.  
  65. ADC_MODE(0);
  66.  
  67. // Uncomment one of the lines below for whatever DHT sensor type you're using
  68. //#define DHTTYPE DHT11   // DHT 11
  69. //#define DHTTYPE DHT21   // DHT 21 (AM2301)
  70. #define DHTTYPE DHT22 // DHT 22  (AM2302), AM2321
  71.  
  72. WiFiClient client;
  73.  
  74. //influxdb details
  75. const char * INFLUXDB_HOST = "192.168.1.???";//replace ??? with your database IP address
  76. const uint16_t INFLUXDB_PORT = 8086;
  77. const char * DATABASE = "logger";
  78. const char * DB_USER = "yourinfluxdbuser";
  79. const char * DB_PASSWORD = "yourinfluxdbpassword";
  80. Influxdb influxdb(INFLUXDB_HOST, INFLUXDB_PORT);
  81.  
  82. //Sleep time constants
  83. const int64_t NOMINAL_DELAY = 600e6; //600 seconds, 10 minutes
  84. const int64_t MAX_DELAY = ESP.deepSleepMax(); //12600e6;
  85. const unsigned int BAND_A = 500; //500 is approximately 3V - Your values will depend on the voltage divider you use, experiment.
  86. const unsigned int BAND_B = 545; //545 is approximately 3.3V - Your values will depend on the voltage divider you use, experiment.
  87. const unsigned int BAND_C = 570; //570 is approximately 3.5V - Your values will depend on the voltage divider you use, experiment.
  88. const unsigned int BAND_D = 590; //590 is approximately 3.6V - Your values will depend on the voltage divider you use, experiment.
  89.  
  90. const unsigned int BAND_AB_DELAY_MULTIPLIER = 9; //The battery voltage is down to between 3V & 3.3V
  91. const unsigned int BAND_BC_DELAY_MULTIPLIER = 6; //The battery voltage is down to between 3.3V & 3.5V
  92. const unsigned int BAND_CD_DELAY_MULTIPLIER = 3; //The battery voltage is down to between 3.5V & 3.6V
  93.  
  94. // DHT Sensor
  95. const int DHTPin = D1;
  96. // Initialize DHT sensor.
  97. DHT dht(DHTPin, DHTTYPE);
  98.  
  99. //LED
  100. #define LED D0
  101.  
  102. uint32_t chipid = ESP.getChipId();
  103. static String ChipIDStr = String(chipid, HEX);
  104. char * HexChipID = (char * ) malloc(9);
  105.  
  106. float tc, tf, h = 0;
  107. float old_tc, old_tf, old_h = 0;
  108.  
  109. //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  110. //  Setup - Sets up the project objects and variables and executes session processes before going to sleep.
  111. //          Loop() is never reached.
  112. //
  113. //  Source - Based on various including my own, details included at the opening comment.
  114. //
  115. //  1.  Initialises serial port
  116. //  2.  Initialises the DHT device
  117. //  3.  Connects to Wifi
  118. //  4.  Checks for firmware updates
  119. //  5.  Opens the InfluxDB database
  120. //  6.  Measures battery voltage
  121. //  7.  Read DHT device
  122. //  8.  Send data to database
  123. //  9.  Go to sleep for a time appropriate for the battery charge.
  124. //
  125. //  todo: Improve hard loops when connecting to wifi and database, if time out, drop out and sleep until next
  126. //        time.
  127. //        Improve firmware update process - security, resolution of correct file, checksums, metadata etc.          
  128. //        
  129. //
  130. //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  131. void setup() {
  132.   Serial.begin(115200);
  133.   Serial.setTimeout(2000);
  134.  
  135.   // Wait for serial to initialise.
  136.   while (!Serial) {
  137.     yield();
  138.   }
  139.  
  140.   //initialise the dht device
  141.   dht.begin();
  142.  
  143.   WiFi.mode(WIFI_STA);
  144.   wifiMulti.addAP("YOUR_SSID_ONE", "yourpassword1");//comment out any that are not needed
  145.   wifiMulti.addAP("YOUR_SSID_TWO", "yourpassword2");
  146.   wifiMulti.addAP("YOUR_SSID_THREE", "yourpassword3");
  147.   wifiMulti.addAP("YOUR_SSID_FOUR", "yourpassword4");
  148.  
  149.   //This needs looking at again.  It works, but it could be better
  150.   Serial.println("Connecting Wifi...");
  151.   if (wifiMulti.run() == WL_CONNECTED) {
  152.     Serial.println("");
  153.     Serial.println("WiFi connected");
  154.     Serial.println("IP address: ");
  155.     Serial.println(WiFi.localIP());
  156.   }
  157.   //This needs looking at again.  It works, but it could be better
  158.   while (wifiMulti.run() != WL_CONNECTED) {
  159.     Serial.println("WiFi not connected!");
  160.     delay(1000);
  161.   }
  162.  
  163.   // print the SSID of the network you're attached to:
  164.   Serial.print("SSID: ");
  165.   Serial.println(WiFi.SSID());
  166.   long rssi = WiFi.RSSI();
  167.   Serial.print("signal strength (RSSI):");
  168.   Serial.print(rssi);
  169.   Serial.println(" dBm");
  170.  
  171.   //Self OTA update check
  172.   Serial.printf("Checking for updates, current firmware version is %d\n", FW_VERSION);
  173.   checkForUpdates();
  174.  
  175.   //TODO need to ensure we can pop out of this loop if the DB fails
  176.   while (influxdb.opendb(DATABASE, DB_USER, DB_PASSWORD) != DB_SUCCESS) {
  177.     Serial.println("Open influxdb database failed");
  178.     delay(10000);
  179.  
  180.   }
  181.  
  182.   Serial.printf(" ESP8266 Chip id = %08X\n", ESP.getChipId());
  183.   sprintf(HexChipID, "%08X", ESP.getChipId());
  184.  
  185.   //get the power reading from the battery
  186.   int power = getReadings(250);
  187.  
  188.   //if the sensor is ready and the timer event has fired, then take store the latest measurement
  189.   //keeping hold of the current values.  This allows us to only update the database when values
  190.   //have changed.
  191.   if (GetMeasurement(tc, tf, h, true) == true) {
  192.  
  193.     digitalWrite(LED, HIGH); //so we know the unit is still alive
  194.     Serial.println("Sending new data to database");
  195.     SendDataToDatabase(client, tc, h, power, FW_VERSION, WiFi.SSID(), WiFi.RSSI());
  196.     digitalWrite(LED, LOW);
  197.  
  198.   } else {
  199.     Serial.println("We didnt get a measurement, going to sleep to try again next time");
  200.  
  201.   }
  202.  
  203.   // 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
  204.   Serial.print("Going into deep sleep mode for ");
  205.   Serial.print(resolveDelayTime(power) / 1e6);
  206.   Serial.println(" seconds");
  207.  
  208.   ESP.deepSleep(resolveDelayTime(power));
  209. }
  210.  
  211. //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  212. //  getReadings - Reads the ADC port a number of times before returning the average measurement
  213. //
  214. //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  215.  
  216. int getReadings(int numOfCycles) {
  217.   int reading = 0;
  218.   for (int n = 0; n < numOfCycles; n++) {
  219.     yield();
  220.     reading += analogRead(A0);
  221.   }
  222.  
  223.   return (int) reading / numOfCycles;
  224.  
  225. }
  226. //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  227.  
  228. //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  229. //  GetMeasurement - Gets a measurement from the DH sensor
  230. //
  231. // Based on:  https://randomnerdtutorials.com/esp8266-dht11dht22-temperature-and-humidity-web-server-with-arduino-ide/
  232. //
  233. //  1.  When the sensor is ready read the sensor
  234. //  2.  If the sensor output is valid, compute the values and return a flag that denotes success.
  235. //  3.  If the wrapAroundCounter reaches the threshold, exit the function and return False denoting failure
  236. //      to read sensor.
  237. //
  238. //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  239. bool GetMeasurement(float & temp_C, float & temp_F, float & humidity, bool SerialDebugOutput) {
  240.   //status of the sensor read. false returned = fail
  241.  
  242.   bool status = false;
  243.   int wrapAroundCounter = 0;
  244.  
  245.   while (!status) {
  246.     if (wrapAroundCounter > 2000) {//todo, make const or metadata in SPIFFS
  247.       Serial.println("Failed to read sensor in time, exiting");
  248.       return false;
  249.     } //the sensor is not reading
  250.  
  251.     // Sensor readings may also be up to 2 seconds 'old'
  252.     float h = dht.readHumidity();
  253.     // Read temperature as Celsius (the default)
  254.     float t = dht.readTemperature();
  255.     // Read temperature as Fahrenheit (isFahrenheit = true)
  256.     float f = dht.readTemperature(true);
  257.     // Check if any reads failed and exit early (to try again).
  258.     if ((isnan(h) || isnan(t) || isnan(f)) && SerialDebugOutput) {
  259.       Serial.println("Failed to read from DHT sensor!");
  260.       wrapAroundCounter++;
  261.       yield();
  262.     } else {
  263.       // Computes temperature values in Celsius + Fahrenheit and Humidity
  264.       temp_C = dht.computeHeatIndex(t, h, false);
  265.       temp_F = dht.computeHeatIndex(f, h);
  266.       humidity = h;
  267.       status = true;
  268.     }
  269.  
  270.   } //while status  
  271.  
  272.   return status;
  273. }
  274. //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  275.  
  276. //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  277. //  SendDataToDatabase - Sends data to existing InfluxDB database
  278. //
  279. //  Based on:  https://elf.cs.pub.ro/wsn/wiki/proiecte/influxdb
  280. //
  281. //  1.  Connect to the pre-existing InfluxDB
  282. //  2.  Output the UID of the ESP8266, tempC, tempF, humidity and firmware version to the database.
  283. //
  284. //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  285. void SendDataToDatabase(WiFiClient client, int t, int h, int power, int firmware, String ConnectedSSID, long ConnectedRSSI) {
  286.  
  287.   if (!client.connect(INFLUXDB_HOST, INFLUXDB_PORT)) {
  288.     Serial.println("Cant connect to InfluxDB");
  289.   } else {
  290.     //Create data object with measurment name=analog_read
  291.     dbMeasurement row("temp_humidity");
  292.     row.addTag("UID_HEX", String(HexChipID));
  293.     row.addTag("ssid", ConnectedSSID); //id of the connected wifi station
  294.     row.addField("temp_C", t); //
  295.     row.addField("humidity", h); //
  296.     row.addField("power", power);
  297.     row.addField("rssi", ConnectedRSSI); //strength of the connected wifi station
  298.     row.addField("firmware_version", firmware);
  299.  
  300.     Serial.println(influxdb.write(row) == DB_SUCCESS ? "Object write success" : "Writing failed");
  301.     // Empty field object.
  302.     row.empty();
  303.   }
  304.  
  305. }
  306. //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  307. // checkForUpdates() -  Checks file server for later firmware version.  If there is a newer version available
  308. //                      this will be downloaded into flash memory and ESP8266 will be rebooted with new
  309. //                      firmware.  
  310. //                      The URL is checked for a file that corresponds to the last 3 bytes of the MAC.  The
  311. //                      .version file starts with the integer of the version number, this is compared with the
  312. //                      const FW_VERSION.  If the file-server version is larger than the FW_VERSION then
  313. //                      the .bin file is downloaded.
  314. //
  315. //                      If the .version file, or the web server isnt reachable, the function will exit.  
  316. //                      Updates are checked every time the program wakes up.
  317. //
  318. // Source : Erik H. Bakke – Excellent guide on self-updating OTA -
  319. //          https://www.bakke.online/index.php/2017/06/02/self-updating-ota-firmware-for-esp8266/
  320. //
  321. // Todo : Think about better ways of doing this that would suit large scale deployments
  322. //        Think about the source file containing more metadata
  323. //        Think about rolling metadata into bin and unpacking during update so the firmware and .version file
  324. //        are tied into a single file.
  325. //        Deploy over https or depoy more securely.
  326. //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  327. void checkForUpdates() {
  328.   String mac = String(ESP.getChipId(), HEX);
  329.   String fwURL = String(fwUrlBase) + mac + "/" + mac;
  330.   String fwVersionURL = fwURL + ".version";
  331.  
  332.   Serial.println("Checking for firmware updates.");
  333.   Serial.print("MAC address: ");
  334.   Serial.println(mac);
  335.   Serial.print("Firmware version URL: ");
  336.   Serial.println(fwVersionURL);
  337.  
  338.   HTTPClient httpClient;
  339.   httpClient.begin(fwVersionURL);
  340.   int httpCode = httpClient.GET();
  341.   if (httpCode == 200) {
  342.     String newFWVersion = httpClient.getString();
  343.  
  344.     Serial.print("Current firmware version: ");
  345.     Serial.println(FW_VERSION);
  346.     Serial.print("Available firmware version: ");
  347.     Serial.println(newFWVersion);
  348.  
  349.     int newVersion = newFWVersion.toInt();
  350.  
  351.     if (newVersion > FW_VERSION) {
  352.       Serial.println("Preparing to update");
  353.  
  354.       String fwImageURL = fwURL;
  355.       fwImageURL.concat(".bin");
  356.       t_httpUpdate_return ret = ESPhttpUpdate.update(fwImageURL);
  357.  
  358.       switch (ret) {
  359.       case HTTP_UPDATE_FAILED:
  360.         Serial.printf("HTTP_UPDATE_FAILD Error (%d): %s", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
  361.         break;
  362.  
  363.       case HTTP_UPDATE_NO_UPDATES:
  364.         Serial.println("HTTP_UPDATE_NO_UPDATES");
  365.         break;
  366.       }
  367.     } else {
  368.       Serial.println("Already on latest version");
  369.     }
  370.   } else {
  371.     Serial.print("Firmware version check failed, got HTTP response code ");
  372.     Serial.println(httpCode);
  373.   }
  374.   httpClient.end();
  375. }
  376.  
  377. //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  378. // resolveDelayTime(int power) -  Takes a given power reading and returns the sleep time.  
  379. //
  380. //                                As the battery depletes, the updates will be less frequent. The delay will
  381. //                                be a maximum value of MAX_DELAY which is approx 3.3 hrs.  This value changes
  382. //                                (apparently) hence why it is generated.  Failure to do this will brick the
  383. //                                unit as it will never wake from sleep.
  384. //
  385. //                                There is a check to prevent calculated delay being greater than MAX_DELAY
  386. //
  387. //                                There is a commented out "resolvedDelay = NOMINAL_DELAY;" which is there
  388. //                                to prevent zero battery voltage (when connected via USB) returning MAX_DELAY
  389. //                                for debug purposes.
  390. //
  391. // todo:  Should would be better if the bands are stored in a SPIFFS file rather than hard coded into source.
  392. //        
  393. //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  394. int64_t
  395. resolveDelayTime(int power) {
  396.   int64_t resolvedDelay = 0;
  397.  
  398.   if (power < BAND_A) {
  399.     resolvedDelay = MAX_DELAY;
  400.   } else if (power >= BAND_A && power < BAND_B) {
  401.     resolvedDelay = NOMINAL_DELAY * BAND_AB_DELAY_MULTIPLIER;
  402.   } else if (power >= BAND_B && power < BAND_C) {
  403.     resolvedDelay = NOMINAL_DELAY * BAND_BC_DELAY_MULTIPLIER;
  404.   } else if (power >= BAND_C && power < BAND_D) {
  405.     resolvedDelay = NOMINAL_DELAY * BAND_CD_DELAY_MULTIPLIER;
  406.   } else {
  407.     resolvedDelay = NOMINAL_DELAY;
  408.   }
  409.  
  410.   //catch whether the delay is more than max delay, prevents permanent sleep.
  411.   if (resolvedDelay > MAX_DELAY) {
  412.     resolvedDelay = MAX_DELAY;
  413.   }
  414.  
  415.   //resolvedDelay = NOMINAL_DELAY;///////////////??DEBUG, without battery connected to ADC, update normally
  416.  
  417.   return resolvedDelay;
  418. }
  419.  
  420. void loop() {} //never reaches loop
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement