├── LICENSE ├── README.md └── mastercode.ino /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mastercode 2 | The ESP8266 sketch template I use for my IoT stuff. This framework implements all those things which I want in essentially every Feather HUZZAH-based device I build, to save on reimplementation and increase consistency. 3 | 4 | ## Supports 5 | 6 | * TASK SCHEDULER 7 | 8 | Task scheduling is done using Anatoli Arkhipenko's cooperative multitasking library, found at: 9 | 10 | https://github.com/arkhipenko/TaskScheduler 11 | 12 | Since all non-template functionality added to this sketch is intended to be implemented using Tasks, I highly recommend careful study of the documentation at https://github.com/arkhipenko/TaskScheduler/blob/master/extras/TaskScheduler.pdf before implementing sketches based on this template. 13 | 14 | * OLED FEATHERWING SUPPORT 15 | 16 | If enabled (OLED_ENABLED setting), supports the display of battery status (if enabled: VBAT_ENABLED setting) and of WiFi network connectivity, RSSI, and IP address using the Adafruit OLED FeatherWing ( https://www.adafruit.com/products/2900 ). 17 | 18 | * WIFI/MQTT SERVER CONNECTIVITY 19 | 20 | Maintains (reconnecting on failure) connectivity to a specified wireless LAN and MQTT server. 21 | 22 | * WATCHDOG 23 | 24 | Periodically sends a status message (by default, every sixty seconds) to an MQTT topic, providing remote monitoring and assurance that the Feather and its attached device are still operating. 25 | 26 | * BATTERY MONITORING 27 | 28 | If enabled (VBAT_ENABLED setting), monitors the current battery voltage/charge status of the LiPo battery attached to the Feather. If OLED support is enabled, this is displayed locally; in any case, battery charge information is reported to the MQTT server on a configurable topic at two-minute intervals. 29 | 30 | The use of battery monitoring assumes that the Vbat pin of the Feather HUZZAH is connected to the analog input pin via a 1M/200K voltage divider. 31 | 32 | * STATUS BLINKING AND DISPLAY 33 | 34 | The red LED on the Feather (connected to pin 0) is blinked at 1s intervals to indicate locally that the Feather is up and running. If application code indicates failure, this is switched to continuous-on, to distinguish it both from the up state and from power failure. 35 | 36 | * SLEEP MODE 37 | 38 | The template enables the Feather to be placed in deep sleep for a set amount of time, automatically waking again when the period is over, to allow power conservation. If the period of deep sleep is likely to exceed the watchdog firing period (defined as half or more of the watchdog interval), an automatic "yawn" notification is sent to the MQTT server over the watchdog topic. 39 | 40 | * OTA UPDATE 41 | 42 | The template permits password-protected over-the-air updates from the Arduino IDE, to allow simple updating of in-place devices. 43 | 44 | ## Required libraries 45 | 46 | To compile this, you'll need to install the following libraries from the Arduino IDE: 47 | 48 | * Adafruit GFX Library 49 | * Adafruit MQTT Library 50 | * Adafruit SSD1306 51 | * Adafruit_FeatherOLED (from Github, as described here: https://learn.adafruit.com/adafruit-oled-featherwing/featheroled-library#featheroled-library ) 52 | * ArduinoJson 53 | * ArduinoOTA 54 | * TaskScheduler 55 | 56 | ## Configuration 57 | 58 | See the _USER SETTINGS SECTION_ in the code, documented in comments. 59 | 60 | ## API 61 | 62 | ### Must-implement 63 | 64 | The standard Arduino entrypoints (`setup()` and `loop()`) are taken over by the template code. It is the application developer's responsibility, therefore, to fit their application code into the three callback functions defined by the template (at the bottom of the code file, these are defined empty). These are: 65 | 66 | *void application_setup ()* 67 | 68 | This is the primary entry point of your application, called by the template's initialization code. Note that an equivalent to `loop()` does not exist in the template; thus, apart from other setup, it is the responsibility of `application_setup ()` to define and enable a number of tasks (see the Task Scheduler library documentation mentioned above, and the example below) to carry out the functions of the application once `application_setup ()` exits. 69 | 70 | *void application_fail ()* 71 | 72 | This function is a callback from `fail ()` (see below) invoked on fatal errors to shut down application activities (disabling tasks, etc.) before shutting down system activities and otherwise handling failure. 73 | 74 | *void application_otasafe ()* 75 | 76 | This function is invoked immediately before an over-the-air update is installed. It should do whatever is necessary to ensure that any devices controlled by this Feather are in a safe and shut-down state, such that time taken for, or errors during, the update do not pose any risk of harm. Be aware that the OTA update will proceed _immediately_ upon return from `application_otasafe()`, and so this function cannot rely on any outside tasks; no other code will execute between this function and the reset following the update. 77 | 78 | ### Functions and References 79 | 80 | These are the functions intended to be called by the user of the template; i.e., those which _do not_ begin with an underscore. Those which do begin with an underscore are internal functions. 81 | 82 | #### Failure 83 | 84 | *void fail (char * reason)* 85 | 86 | Handles fatal errors. After invoking the callback to shut down application functionality, it displays the given reason on the OLED display (if enabled), shuts down system activities (except for basic WiFi and OTA update support, to permit repair), and sets the status/red LED to continuously on, to locally indicate the failure. 87 | 88 | #### MQTT 89 | 90 | The MQTT client, an instance of the client from the Adafruit MQTT Library ( https://github.com/adafruit/Adafruit_MQTT_Library ), is available at `mqttClient`; application code pubs and subs should make use of this instance. 91 | 92 | #### OLED (only available if OLED_ENABLED) 93 | 94 | *void oledClear ()* 95 | 96 | Clears the middle 128x15 pixels message area on the OLED display. 97 | 98 | *void oledPrintln (char * line)* 99 | 100 | Prints a string to one line of the message area of the OLED display. 101 | 102 | *void oledDisplay ()* 103 | 104 | Causes the OLED display message area to update. 105 | 106 | A typical usage of these helper functions to display a message would be a call to `oledClear` followed by two calls to `oledPrintln` and a single call to `oledDisplay`, thus: 107 | 108 | ``` 109 | oledClear (); 110 | oledPrintln ("first line"); 111 | oledPrintln ("second line"); 112 | oledDisplay (); 113 | ``` 114 | 115 | If it is desirable to just clear the message area, `oledClear` can be called alone. 116 | 117 | *Adafruit_FeatherOLED_WiFi getOled ()* 118 | 119 | Returns the `Adafruit_FeatherOLED_WiFi` object used by the template to access the OLED display. This class is that from the FeatherOLED library, which see ( https://learn.adafruit.com/adafruit-oled-featherwing/featheroled-library#featheroled-library ) for details, available to be used when more complex operations are required than the simple helper functions above permit. Note that using this directly, it is easy to interfere with the use of the status areas above and below that the template code uses; _caveat_ developer! 120 | 121 | ### Sleep 122 | 123 | *void deepSleep (int msec)* 124 | 125 | Go into deep sleep mode for the specified number of milliseconds. If this length of time exceeds half the configured WATCHDOG_INTERVAL, an impending-sleep notification ("yawn") will be sent on the watchdog MQTT topic. If the OLED display is enabled, a notification message ("yawn...") is displayed to indicate that the Feather is asleep locally, since status blinking ceases with the red LED off while in deep sleep. 126 | 127 | On wakeup, the Feather initializes again. Application setup code can determine whether it was awoken from sleep rather than another type of reset by checking the... 128 | 129 | *bool woken* 130 | 131 | ...variable, which is true on wake-from-sleep, false otherwise. 132 | 133 | ### Task Scheduler 134 | 135 | The default task scheduler instance, to which application tasks should be added, is available as: 136 | 137 | *Scheduler taskManager* 138 | 139 | Application tasks, declared thus (for example): 140 | 141 | ``` 142 | Task tLoop (0, TASK_FOREVER, &loop, NULL, false, NULL, NULL); 143 | 144 | void loop () 145 | { 146 | ... 147 | } 148 | ``` 149 | 150 | Can therefore be added (typically in `application_setup ()`) thus: 151 | 152 | ``` 153 | taskManager.addTask(tLoop); 154 | tLoop.enable(); 155 | ``` 156 | -------------------------------------------------------------------------------- /mastercode.ino: -------------------------------------------------------------------------------- 1 | // ************************************************************************** 2 | // * MASTERCODE - STANDARD TEMPLATE FOR IOT APPS * 3 | // * * 4 | // * INCLUDES: * 5 | // * * 6 | // * TASK SCHEDULER * 7 | // * OLED FEATHERWING SUPPORT * 8 | // * WIFI/MQTT SERVER CONNECTIVITY * 9 | // * WATCHDOG * 10 | // * BATTERY MONITORING * 11 | // * STATUS BLINKING AND DISPLAY * 12 | // * SLEEP MODE * 13 | // * OTA UPDATE * 14 | // * * 15 | // ************************************************************************** 16 | 17 | // TASK SCHEDULER PARAMETERS ************************************************ 18 | 19 | // Task scheduling is done using Anatoli Arkhipenko's cooperative multitasking 20 | // library, found at: 21 | // 22 | // https://github.com/arkhipenko/TaskScheduler 23 | // 24 | // Since all non-template functionality added to this sketch is intended to be 25 | // implemented using Tasks, I highly recommend careful study of the documentation 26 | // at https://github.com/arkhipenko/TaskScheduler/blob/master/extras/TaskScheduler.pdf 27 | // before implementing sketches based on this template. 28 | 29 | // Go into idle sleep mode (until next 1 ms timer tick) when task chain empty. 30 | #define _TASK_SLEEP_ON_IDLE_RUN 31 | 32 | // Support Status Requests for tasks. 33 | #define _TASK_STATUS_REQUEST 34 | 35 | // Support task IDs and control points. 36 | #define _TASK_WDT_IDS 37 | 38 | // Support local task storage. 39 | #define _TASK_LTS_POINTER 40 | 41 | #include 42 | 43 | // ************************************************************************** 44 | // * USER SETTINGS SECTION - CONFIGURE AS DESIRED * 45 | // ************************************************************************** 46 | 47 | // Device name (also functions as hostname for WiFi purposes). 48 | #define DEV_NAME "feather" 49 | 50 | // Debugging mode enabled; causes libraries to output debug information to 51 | // the serial monitor. 52 | #define DEBUG_ENABLED 1 53 | 54 | // Enables support for the OLED FeatherWing ( https://www.adafruit.com/products/2900 ). 55 | #define OLED_ENABLED 1 56 | 57 | // This sketch template does not, by default, specifically support the three 58 | // optional buttons on the OLED FeatherWing. Button C (on pin 2) is available 59 | // for use as standard. Button B (on pin 16) functions as an "early-wake" 60 | // button if the deep sleep functionality is used and therefore pin 16 is 61 | // connected to RST; if this functionality is not used and this connection 62 | // is not made, button B can be used as standard. 63 | 64 | // Making use of button A (on pin 0) required removing the status blink 65 | // functionality entirely from this template, since it makes use of pin 0 66 | // as an output. 67 | 68 | // Wireless network SSID and passphrase. 69 | #define WLAN_SSID "YOUR-SSID-HERE" 70 | #define WLAN_PASS "YOUR-PASSPHRASE-HERE" 71 | 72 | // MQTT server details. 73 | #define MQTT_SERVER "YOUR.MQTT.SERVER.HOSTNAME" 74 | #define MQTT_SERVERPORT 1883 75 | #define MQTT_CLIENTID DEV_NAME 76 | #define MQTT_USERNAME "" 77 | #define MQTT_PASSWORD "" 78 | 79 | // Watchdog timer configuration (MQTT topic and interval (s). 80 | #define WATCHDOG_FEED "watchdog/devices" 81 | #define WATCHDOG_INTERVAL 60 82 | 83 | // Battery monitoring enabled. 84 | #define VBAT_ENABLED 1 85 | 86 | // Low battery reports posted to this MQTT topic. 87 | #define BATTERY_FEED "status/battery" 88 | 89 | // Battery monitoring assumes that the VBat pin of the Feather HUZZAH is 90 | // connected to the analog input pin via a 1 M/200K voltage divider, as per 91 | // the implementation comment below. 92 | 93 | // Over-the-Air update settings. 94 | #define OTA_PORT 8266 95 | #define OTA_PASSWORD "YOUR-OTA-PASSWORD-HERE" 96 | 97 | // ************************************************************************** 98 | // * STANDARD TEMPLATE CODE - DO NOT EDIT * 99 | // ************************************************************************** 100 | 101 | // INCLUDES ***************************************************************** 102 | 103 | // OLED support. 104 | #if OLED_ENABLED == 1 105 | #include 106 | #include 107 | #include 108 | #include 109 | #include 110 | #endif 111 | 112 | // WiFi support. 113 | #include 114 | 115 | // Expose Espressif SDK functionality 116 | extern "C" 117 | { 118 | #include "user_interface.h" 119 | } 120 | 121 | // MQTT support. 122 | #include 123 | #include 124 | #include 125 | 126 | // OTA update support. 127 | #include 128 | 129 | // SYSTEM DEFINITIONS ******************************************************* 130 | 131 | // Board pins. 132 | // 133 | // Available for GPIO: 4, 5, 12, 13, 14, 15 (see caveats) 134 | // 135 | // If I2C is used, pins 4 and 5 are reserved. 136 | // 137 | // If SPI is used, pins 12, 13, and 14 are reserved. 138 | // 139 | // Pin 15 MUST NOT be pulled HIGH on startup. 140 | // 141 | // 16 is used by the deep sleep functionality and/or optional button B; 0 by 142 | // the status blink functionality and/or optional button A; 2 may be used for 143 | // GPIO if neither the blue LED nor button C is used. 144 | 145 | #define BOARD_RED_LED_PIN 0 146 | #define BOARD_BLUE_LED_PIN 2 147 | #define BOARD_WAKE_PIN 16 148 | 149 | #define LED_OFF HIGH 150 | #define LED_ON LOW 151 | 152 | // TASK DECLARATIONS ******************************************************** 153 | 154 | void _wifiMonitor (); 155 | bool _wifiStartup (); 156 | 157 | // Task for the WiFi monitoring function. 158 | Task _tWifi (1UL * TASK_SECOND, TASK_FOREVER, &_wifiMonitor, NULL, false, &_wifiStartup, NULL); 159 | 160 | void _mqttMonitor (); 161 | bool _mqttStartup (); 162 | void _mqttShutdown (); 163 | 164 | // Task for the MQTT monitoring/keepalive function. 165 | Task _tMqtt (30UL * TASK_SECOND, TASK_FOREVER, &_mqttMonitor, NULL, false, &_mqttStartup, &_mqttShutdown); 166 | 167 | void _bark (); 168 | 169 | // Task for the Watchdog Timer function. 170 | Task _tWatchdog (WATCHDOG_INTERVAL * TASK_SECOND, TASK_FOREVER, &_bark, NULL, false, NULL, NULL); 171 | 172 | #if VBAT_ENABLED == 1 173 | void _batteryUpdate () ; 174 | 175 | // Task for the battery update function. 176 | Task _tBattery (5UL * TASK_SECOND, TASK_FOREVER, &_batteryUpdate, NULL, false, NULL, NULL); 177 | #endif 178 | 179 | void _blinkCallback (); 180 | bool _blinkEnable (); 181 | void _blinkDisable (); 182 | 183 | // Task for the status blink function. 184 | Task _tBlink(1UL * TASK_SECOND, TASK_FOREVER, &_blinkCallback, NULL, false, &_blinkEnable, &_blinkDisable); 185 | 186 | void _handleOTA (); 187 | bool _setupOTA (); 188 | 189 | // Task for the OTA update function. 190 | Task _tOTA(0, TASK_FOREVER, &_handleOTA, NULL, false, &_setupOTA, NULL); 191 | 192 | // OLED SUPPORT ************************************************************* 193 | 194 | #if OLED_ENABLED == 1 195 | 196 | // OLED FeatherWing object 197 | Adafruit_FeatherOLED_WiFi _oled = Adafruit_FeatherOLED_WiFi(); 198 | 199 | // Perform initial setup of the OLED display. 200 | void _oled_initialize() 201 | { 202 | // Set up the OLED display. 203 | _oled.init(); 204 | _oled.clearDisplay(); 205 | 206 | #if VBAT_ENABLED == 1 207 | // Enable battery display; and 208 | // Get a VBAT reading before refreshing (if available) 209 | _oled.setBatteryVisible(true); 210 | _batteryUpdate(); 211 | #else 212 | // Disable battery display. 213 | _oled.setBatteryVisible(false); 214 | #endif 215 | 216 | // Enable the WiFi displays. 217 | _oled.setConnectedVisible (true); 218 | _oled.setRSSIVisible (true); 219 | _oled.setRSSIIcon (true); 220 | _oled.setRSSIAsPercentage (true); 221 | _oled.setIPAddressVisible (true); 222 | 223 | // Refresh the screen. 224 | _oled.refreshIcons(); 225 | _oled.clearMsgArea(); 226 | _oled.println (DEV_NAME); 227 | _oled.println ("booting..."); 228 | _oled.display(); 229 | } 230 | 231 | // Returns the `Adafruit_FeatherOLED_WiFi` object used by the template to access 232 | // the OLED display. This class is that from the FeatherOLED library, which see 233 | // ( https://learn.adafruit.com/adafruit-oled-featherwing/featheroled-library#featheroled-library ) 234 | // for details, available to be used when more complex operations are required 235 | // than the simple helper functions below permit. Note that using this directly, 236 | // it is easy to interfere with the use of the status areas above and below 237 | // that the template code uses; _caveat_ developer! 238 | Adafruit_FeatherOLED_WiFi getOled () 239 | { 240 | return _oled; 241 | } 242 | 243 | // Clears the middle 128x15 pixels message area on the OLED display. 244 | void oledClear () 245 | { 246 | _oled.clearMsgArea(); 247 | } 248 | 249 | // Prints a string to one line of the message area of the OLED display. 250 | void oledPrintln (char * line) 251 | { 252 | _oled.println (line); 253 | } 254 | 255 | // Causes the OLED display message area to update. 256 | void oledDisplay () 257 | { 258 | _oled.display (); 259 | } 260 | 261 | #endif 262 | 263 | // WIFI SUPPORT ************************************************************* 264 | 265 | // Initialize the WiFi connection. 266 | bool _wifiStartup () 267 | { 268 | #if OLED_ENABLED == 1 269 | _oled.setConnected(false); 270 | _oled.refreshIcons(); 271 | _oled.clearMsgArea(); 272 | _oled.println ("Connecting to WLAN..."); 273 | _oled.println (WLAN_SSID); 274 | _oled.display(); 275 | #endif 276 | 277 | Serial.print ("Connecting to WLAN:"); Serial.println(WLAN_SSID); 278 | 279 | // WiFi to station mode only. 280 | WiFi.mode (WIFI_STA); 281 | 282 | // Set hostname to device name. 283 | wifi_station_set_hostname (DEV_NAME); 284 | 285 | // Configure no auto-connect on power on (since we do this manually here); but 286 | // auto-reconnect if connection fails. 287 | WiFi.setAutoConnect (false); 288 | WiFi.setAutoReconnect (true); 289 | 290 | // Connect to WiFi network. 291 | WiFi.begin (WLAN_SSID, WLAN_PASS); 292 | 293 | return true; 294 | } 295 | 296 | // Saved status from last monitor pass. 297 | wl_status_t _lastKnownStatus = WL_IDLE_STATUS; 298 | 299 | // Monitor the WiFi connection and display status. 300 | void _wifiMonitor () 301 | { 302 | wl_status_t currentStatus = WiFi.status(); 303 | 304 | if (currentStatus == WL_CONNECTED) 305 | { 306 | // We are connected. 307 | if (_lastKnownStatus != WL_CONNECTED) 308 | { 309 | // Newly connected. 310 | 311 | #if OLED_ENABLED == 1 312 | // Clear display (of connecting message or reconnecting message). 313 | _oled.clearMsgArea (); 314 | 315 | // Set as connected; set IP address. 316 | _oled.setConnected (true); 317 | _oled.setIPAddress (WiFi.localIP()); 318 | #endif 319 | 320 | Serial.println ("Connected."); 321 | Serial.print ("IP address: "); Serial.println (WiFi.localIP()); 322 | 323 | // Enable MQTT task, since WiFi now available. 324 | _tMqtt.enable(); 325 | } 326 | 327 | // Fallthrough, or continue to be connected. 328 | 329 | #if OLED_ENABLED == 1 330 | // Update RSSI. 331 | _oled.setRSSI (WiFi.RSSI()); 332 | #endif 333 | 334 | } 335 | else if (currentStatus != WL_IDLE_STATUS && currentStatus != WL_DISCONNECTED) 336 | { 337 | // Not connected. 338 | char * message; 339 | 340 | switch (currentStatus) 341 | { 342 | case WL_NO_SSID_AVAIL: 343 | message = "Could not find network: "; 344 | break; 345 | 346 | case WL_CONNECT_FAILED: 347 | message = "Incorrect passphrase for: "; 348 | break; 349 | 350 | default: 351 | message = "Unknown WiFi error on: "; 352 | break; 353 | } 354 | 355 | #if OLED_ENABLED == 1 356 | _oled.clearMsgArea (); 357 | _oled.println (message); 358 | _oled.println (WLAN_SSID); 359 | _oled.display (); 360 | #endif 361 | 362 | Serial.print (message); Serial.println (WLAN_SSID); 363 | 364 | // Disable MQTT task until reconnected. 365 | _tMqtt.disable (); 366 | 367 | // Reconnecting is done automatically. 368 | } 369 | 370 | _lastKnownStatus = currentStatus; 371 | 372 | #if OLED_ENABLED == 1 373 | // Refresh icons. 374 | _oled.refreshIcons (); 375 | #endif 376 | } 377 | 378 | // MQTT SUPPORT ************************************************************* 379 | 380 | // WiFi client for use by MQTT 381 | WiFiClient _mqttWiFiClient; 382 | 383 | // MQTT client class; this instance is available to application code. 384 | Adafruit_MQTT_Client mqttClient (&_mqttWiFiClient, MQTT_SERVER, MQTT_SERVERPORT, MQTT_CLIENTID, MQTT_USERNAME, MQTT_PASSWORD); 385 | 386 | // Helper function for connecting. 387 | bool _mqttConnect () 388 | { 389 | int8_t ret = mqttClient.connect(); 390 | 391 | if (ret != 0) 392 | { 393 | // Connection failed. 394 | mqttClient.disconnect (); 395 | 396 | char * error; 397 | 398 | switch (ret) 399 | { 400 | case 1: 401 | error = "Wrong protocol"; 402 | break; 403 | 404 | case 2: 405 | error = "ID rejected"; 406 | break; 407 | 408 | case 3: 409 | error = "Server unavailable"; 410 | break; 411 | 412 | case 4: 413 | error = "Bad username/password"; 414 | break; 415 | 416 | case 5: 417 | error = "Not authenticated"; 418 | break; 419 | 420 | case 6: 421 | error = "Failed to subscribe"; 422 | break; 423 | 424 | default: 425 | error = "Unknown"; 426 | break; 427 | } 428 | 429 | #if OLED_ENABLED == 1 430 | _oled.clearMsgArea (); 431 | _oled.println ("MQTT error:"); 432 | _oled.println (error); 433 | _oled.display (); 434 | #endif 435 | 436 | Serial.print ("MQTT error: "); Serial.println(error); 437 | 438 | return false; 439 | } 440 | 441 | return true; 442 | } 443 | 444 | // Initial connection to the MQTT server. 445 | bool _mqttStartup () 446 | { 447 | #if OLED_ENABLED == 1 448 | _oled.clearMsgArea (); 449 | _oled.println ("Connecting to MQTT"); 450 | _oled.println (MQTT_SERVER); 451 | _oled.display (); 452 | #endif 453 | 454 | Serial.print ("Connecting to MQTT: "); Serial.println(MQTT_SERVER); 455 | 456 | // Try to connect to the MQTT server. 457 | if (_mqttConnect ()) 458 | { 459 | // Connected successfully. Clear connecting message. 460 | 461 | #if OLED_ENABLED == 1 462 | _oled.clearMsgArea(); 463 | #endif 464 | 465 | Serial.println ("MQTT connected"); 466 | 467 | // Start the watchdog task. 468 | _tWatchdog.enable(); 469 | } 470 | 471 | // If we don't connect successfully, then the error is already displayed; 472 | // and we let _mqttMonitor handle it in 30s as if it were a reconnection. 473 | } 474 | 475 | // Monitor the status of the MQTT server and reconnect if necessary. 476 | void _mqttMonitor () 477 | { 478 | // Ping the MQTT server to be surte we remain connected (keepalive). 479 | if (! mqttClient.ping()) 480 | { 481 | // Ping failed. So... 482 | if (! mqttClient.connected ()) 483 | { 484 | // Not connected. Disable watchdog task until reconnected. 485 | _tWatchdog.disable(); 486 | 487 | // Confirm disconnect. 488 | mqttClient.disconnect (); 489 | 490 | #if OLED_ENABLED == 1 491 | _oled.clearMsgArea (); 492 | _oled.println ("MQTT disconnected"); 493 | _oled.println ("Reconnecting"); 494 | _oled.display (); 495 | #endif 496 | 497 | Serial.println ("MQTT disconnected; reconnecting."); 498 | 499 | // Attempt reconnect 500 | if (_mqttConnect()) 501 | { 502 | // Reconnected successfully. Clear connecting message. 503 | 504 | #if OLED_ENABLED == 1 505 | _oled.clearMsgArea(); 506 | #endif 507 | 508 | Serial.println ("MQTT reconnected"); 509 | 510 | // Start the watchdog task. 511 | _tWatchdog.enable(); 512 | } 513 | 514 | // Otherwise, we pick it up again next time through. 515 | } 516 | } 517 | } 518 | 519 | // Shut down the connection to the MQTT server. 520 | void _mqttShutdown () 521 | { 522 | mqttClient.disconnect (); 523 | } 524 | 525 | // WATCHDOG SUPPORT ********************************************************* 526 | 527 | // MQTT topic for watchdog timer reports. 528 | Adafruit_MQTT_Publish _watchdog = Adafruit_MQTT_Publish (&mqttClient, WATCHDOG_FEED); 529 | 530 | // Send a watchdog timer message ("bark"). 531 | void _bark () 532 | { 533 | // Format as JSON 534 | // Allocate memory pool for the object tree. 535 | StaticJsonBuffer<128> jsonBuffer; 536 | 537 | // Create the root of the object tree. 538 | JsonObject& root = jsonBuffer.createObject(); 539 | 540 | // Add values to the object. 541 | root["device"] = DEV_NAME ; 542 | root["notify"] = "bark" ; 543 | root["uptime"] = millis(); 544 | 545 | char buffer[128]; 546 | root.printTo (buffer, sizeof(buffer)); 547 | 548 | if (!_watchdog.publish(buffer)) 549 | { 550 | Serial.println ("bark failed"); 551 | } 552 | } 553 | 554 | // BATTERY MONITORING ******************************************************* 555 | 556 | #if VBAT_ENABLED == 1 557 | 558 | // The analog input pin. 559 | #define VBAT_PIN A0 560 | 561 | // MQTT topic for battery level reports. 562 | Adafruit_MQTT_Publish _battlevel = Adafruit_MQTT_Publish (&mqttClient, BATTERY_FEED); 563 | 564 | // Update battery status, including OLED display if enabled and sending 565 | // status MQTT message at appropriate interval. 566 | void _batteryUpdate () 567 | { 568 | float mvPerLev = 5.426357F; 569 | 570 | // read the battery level from the ESP8266 analog in pin. 571 | // analog read level is 10 bit 0-1023 (0V-1V). 572 | // our 1M & 220K voltage divider takes the max 573 | // lipo value of 4.2V and drops it to 0.758V max. 574 | // this means our min analog read value should be 580 (3.14V) 575 | // and the max analog read value should be 774 (4.2V). 576 | int level = analogRead(A0); 577 | 578 | // convert battery level to voltage in mV 579 | float voltage = ((float)level * mvPerLev); 580 | 581 | #if OLED_ENABLED == 1 582 | // set display 583 | _oled.setBattery (voltage / 1000); 584 | _oled.refreshIcons (); 585 | #endif 586 | 587 | // send battery level to MQTT server, but only once per two minutes. 588 | if (((_tBattery.getRunCounter() % 24) == 0) && (!_tBattery.isFirstIteration())) 589 | { 590 | // Convert battery level to percentage. 591 | int percent = (level - 574) / 2; 592 | 593 | // Format as JSON 594 | // Allocate memory pool for the object tree. 595 | StaticJsonBuffer<128> jsonBuffer; 596 | 597 | // Create the root of the object tree. 598 | JsonObject& root = jsonBuffer.createObject(); 599 | 600 | // Add values to the object. 601 | root["device"] = DEV_NAME ; 602 | root["level"] = percent; 603 | 604 | char buffer[128]; 605 | root.printTo (buffer, sizeof(buffer)); 606 | 607 | if (!_battlevel.publish(buffer)) 608 | { 609 | Serial.println ("level report failed"); 610 | } 611 | } 612 | } 613 | 614 | #endif 615 | 616 | // STATUS BLINKING ********************************************************** 617 | 618 | // Enable method for the status blink task; this configures the RED LED 619 | // pin for output. 620 | bool _blinkEnable() 621 | { 622 | pinMode (BOARD_RED_LED_PIN, OUTPUT); 623 | digitalWrite (BOARD_RED_LED_PIN, LED_OFF); 624 | 625 | return true; 626 | } 627 | 628 | // Callback method for the status blink task; this toggles the RED LED 629 | // pin, causing this LED to flash periodically to indicate operational status. 630 | void _blinkCallback() 631 | { 632 | int state = digitalRead(BOARD_RED_LED_PIN); 633 | digitalWrite(BOARD_RED_LED_PIN, !state); 634 | } 635 | 636 | // Disable method for the status blink task; this ensures that the RED LED 637 | // is turned off when the task is disabled. 638 | void _blinkDisable() 639 | { 640 | digitalWrite(BOARD_RED_LED_PIN, LED_OFF); 641 | } 642 | 643 | // SLEEP MODE SUPPORT ******************************************************* 644 | 645 | // Send an MQTT notification of impending sleep ("yawn") on the watchdog channel. 646 | void _longSleepNotify (int msec) 647 | { 648 | // Format as JSON 649 | // Allocate memory pool for the object tree. 650 | StaticJsonBuffer<128> jsonBuffer; 651 | 652 | // Create the root of the object tree. 653 | JsonObject& root = jsonBuffer.createObject(); 654 | 655 | // Add values to the object. 656 | root["device"] = DEV_NAME ; 657 | root["notify"] = "yawn"; 658 | root["msec"] = msec; 659 | 660 | char buffer[128]; 661 | root.printTo (buffer, sizeof(buffer)); 662 | 663 | if (!_watchdog.publish(buffer)) 664 | { 665 | Serial.println ("yawn failed"); 666 | } 667 | } 668 | 669 | // Go into deep sleep mode for the specified number of milliseconds. If this length of 670 | // time exceeds half the configured WATCHDOG_INTERVAL, an impending-sleep notification 671 | // ("yawn") will be sent on the watchdog MQTT topic. If the OLED display is enabled, 672 | // a notification message ("yawn...") is displayed to indicate that the Feather is 673 | // asleep locally, since status blinking ceases with the red LED off while in deep sleep. 674 | void deepSleep (int msec) 675 | { 676 | if (msec > (WATCHDOG_INTERVAL * 1000) / 2) 677 | { 678 | // Long sleep (send notification) 679 | _longSleepNotify (msec); 680 | 681 | #if OLED_ENABLED == 1 682 | _oled.clearMsgArea (); 683 | _oled.println ("yawn..."); 684 | _oled.display (); 685 | #endif 686 | 687 | Serial.println ("yawn..."); 688 | } 689 | 690 | Serial.println ("Sleeping."); 691 | 692 | ESP.deepSleep (msec * 1000); 693 | } 694 | 695 | // OTA UPDATE SUPPORT ******************************************************* 696 | 697 | // Set up over-the-air update support. 698 | bool _setupOTA () 699 | { 700 | // Set up OTA parameters. 701 | ArduinoOTA.setHostname(DEV_NAME); 702 | ArduinoOTA.setPort(OTA_PORT); 703 | ArduinoOTA.setPassword((const char *)OTA_PASSWORD); 704 | 705 | // OTA update reporting functions. 706 | ArduinoOTA.onStart([]() 707 | { 708 | #if OLED_ENABLED == 1 709 | _oled.clearMsgArea (); 710 | _oled.println ("OTA Update:"); 711 | _oled.println ("starting"); 712 | _oled.display (); 713 | #endif 714 | 715 | Serial.println ("OTA Update: starting."); 716 | 717 | application_otasafe (); 718 | }); 719 | 720 | ArduinoOTA.onEnd([]() 721 | { 722 | #if OLED_ENABLED == 1 723 | _oled.clearMsgArea (); 724 | _oled.println ("OTA Update:"); 725 | _oled.println ("done"); 726 | _oled.display (); 727 | #endif 728 | 729 | Serial.println ("OTA Update: done."); 730 | 731 | ESP.restart(); 732 | }); 733 | 734 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) 735 | { 736 | unsigned int percent = (progress / (total / 100)); 737 | 738 | #if OLED_ENABLED == 1 739 | _oled.clearMsgArea (); 740 | _oled.println ("OTA Update:"); 741 | _oled.print ("progress (%): "); _oled.println(percent); 742 | _oled.display (); 743 | #endif 744 | 745 | Serial.print ("OTA Update: progress (%): "); Serial.println (percent); 746 | }); 747 | 748 | ArduinoOTA.onError([](ota_error_t error) 749 | { 750 | char * errmsg ; 751 | 752 | switch (error) 753 | { 754 | case OTA_AUTH_ERROR: 755 | errmsg = "auth failed"; 756 | break; 757 | 758 | case OTA_BEGIN_ERROR: 759 | errmsg = "begin failed"; 760 | break; 761 | 762 | case OTA_CONNECT_ERROR: 763 | errmsg = "connect failed"; 764 | break; 765 | 766 | case OTA_RECEIVE_ERROR: 767 | errmsg = "receive error"; 768 | break; 769 | 770 | case OTA_END_ERROR: 771 | errmsg = "end failed"; 772 | break; 773 | 774 | default: 775 | errmsg = "unknown error"; 776 | break; 777 | } 778 | 779 | #if OLED_ENABLED == 1 780 | _oled.clearMsgArea (); 781 | _oled.println ("OTA Error:"); 782 | _oled.println (errmsg); 783 | _oled.display (); 784 | #endif 785 | 786 | Serial.print ("OTA Error:"); Serial.println (errmsg); 787 | }); 788 | 789 | ArduinoOTA.begin(); 790 | 791 | return true; 792 | } 793 | 794 | // Handle OTA requests. 795 | void _handleOTA () 796 | { 797 | ArduinoOTA.handle(); 798 | } 799 | 800 | // FAIL ********************************************************************* 801 | 802 | void fail (char * reason) 803 | { 804 | // Display reason. 805 | #if OLED_ENABLED == 1 806 | _oled.clearMsgArea (); 807 | _oled.println ("Fail:"); 808 | _oled.println (reason); 809 | _oled.display (); 810 | #endif 811 | 812 | Serial.print ("Fail:"); Serial.println (reason); 813 | 814 | // Stop application activities. 815 | application_fail (); 816 | 817 | // Stop system activites - except OTA update and WiFi, which we leave running 818 | // to allow repair of the fail. 819 | _tBlink.disable (); 820 | 821 | #if VBAT_ENABLED == 1 822 | _tBattery.disable (); 823 | #endif 824 | 825 | _tWatchdog.disable (); 826 | _tMqtt.disable (); 827 | 828 | // Turn the status LED to continuous on to indicate the failure. 829 | digitalWrite(BOARD_RED_LED_PIN, LED_ON); 830 | } 831 | 832 | // TASK SCHEDULER *********************************************************** 833 | 834 | Scheduler taskManager; 835 | 836 | // ENTRYPOINTS ************************************************************** 837 | 838 | bool woken = false; 839 | 840 | // Called once when the board is reset (i.e., initializes or awakens from 841 | // [deep] sleep). 842 | void setup() 843 | { 844 | // Set serial monitor baud rate. 845 | Serial.begin(57600); 846 | 847 | // Where did we come from? 848 | const rst_info * resetInfo = system_get_rst_info(); 849 | woken = resetInfo->reason == 5; 850 | 851 | #if DEBUG_ENABLED == 1 852 | // Enable debug output. 853 | Serial.setDebugOutput(true); 854 | #endif 855 | 856 | #if OLED_ENABLED == 1 857 | // Initialize the display. 858 | _oled_initialize(); 859 | #endif 860 | 861 | Serial.println (DEV_NAME); 862 | Serial.println ("booting..."); 863 | 864 | // Initialize the task manager. 865 | taskManager.init(); 866 | 867 | // Add the system tasks to the task manager and enable them. 868 | 869 | taskManager.addTask(_tWifi); 870 | _tWifi.enable(); 871 | 872 | taskManager.addTask (_tMqtt); 873 | // MQTT task is not enabled by setup; it's enabled by the WiFi task once it inits successfully and connects. 874 | 875 | taskManager.addTask (_tWatchdog); 876 | // Watchdog task is not enabled by setup; it's enabled by the MQTT task once it inits successfully and connects. 877 | 878 | #if VBAT_ENABLED == 1 879 | taskManager.addTask(_tBattery); 880 | _tBattery.enable(); 881 | #endif 882 | 883 | taskManager.addTask(_tBlink); 884 | _tBlink.enable(); 885 | 886 | taskManager.addTask(_tOTA); 887 | _tOTA.enable(); 888 | 889 | // Call application setup code. 890 | application_setup(); 891 | } 892 | 893 | // Loops endlessly after setup() finishes and for so long as the board is 894 | // powered and awake. 895 | void loop() 896 | { 897 | taskManager.execute(); 898 | } 899 | 900 | // ************************************************************************** 901 | // * APPLICATION CODE - INSERT USER CODE BELOW * 902 | // ************************************************************************** 903 | 904 | // Initially set up application tasks (i.e., add them and enable them in the 905 | // task manager) and perform other initialization functions. 906 | void application_setup() 907 | { 908 | // insert code here 909 | } 910 | 911 | // This function is a callback from `fail ()` (see below) invoked on fatal errors 912 | // to shut down application activities (disabling tasks, etc.) before shutting 913 | // down system activities and otherwise handling failure. 914 | void application_fail () 915 | { 916 | // insert code here 917 | } 918 | 919 | // This function is invoked immediately before an over-the-air update is installed. 920 | // It should do whatever is necessary to ensure that any devices controlled by this 921 | // Feather are in a safe and shut-down state, such that time taken for, or errors 922 | // during, the update do not pose any risk of harm. Be aware that the OTA update will 923 | // proceed _immediately_ upon return from `application_otasafe()`, and so this 924 | // function cannot rely on any outside tasks; no other code will execute between 925 | // this function and the reset following the update. 926 | void application_otasafe() 927 | { 928 | // insert code here 929 | } 930 | 931 | --------------------------------------------------------------------------------