├── .gitignore ├── LICENSE ├── README.md ├── examples └── GarageDoorOpener │ ├── GarageDoor-Opener.h │ ├── GarageDoorOpener.ino │ └── README.md ├── keywords.txt ├── library.properties ├── screenshots ├── .DS_Store ├── mqtt-screen.png ├── status-screen.png ├── system-screen.png └── wifi-screen.png └── src ├── ArduinoJson.h ├── CoogleEEPROM.cpp ├── CoogleEEPROM.h ├── CoogleIOT.cpp ├── CoogleIOT.h ├── CoogleIOTConfig.h ├── CoogleIOTWebserver.cpp ├── CoogleIOTWebserver.h ├── DNSServer ├── DNSServer.cpp └── DNSServer.h ├── EEPROM_map.h ├── LUrlParser ├── LUrlParser.cpp └── LUrlParser.h ├── WiFiClientPrint.h └── webpages ├── 404.h ├── home.h ├── home.html ├── jquery-3.2.1.min.h ├── jquery-3.2.1.slim.min.h ├── mini_css_default.h ├── restarting.h └── restarting.html /.gitignore: -------------------------------------------------------------------------------- 1 | .cproject 2 | .project 3 | .settings 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 John Coggeshall 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CoogleIOT 2 | 3 | A ESP8266 Library for easy IOT device development. 4 | 5 | The CoogleIOT library was created to make building IOT devices on the ESP8266 microcontroller easier by providing a 6 | solid encapsulated framework for most of the common things you want to do on an IOT device, including: 7 | 8 | - Captive Portal for configuration of the device - allowing you to configure the AP name, the Wifi Client, and the built in MQTT Client. Just connect to the AP and configure (mobile friendly). 9 | - Built in persistent logging mechanisms using SPIFFS filesystem (also available for viewing from the web interface) 10 | - Built in MQTT client (provided by PubSubClient) 11 | - Built in UI libraries for the device (Mini.css for style, jquery 3.x for Javascript) that can be served from the AP 12 | using the `/css` or `/jquery` URLs 13 | - Built in NTP client for access to local date / time on device 14 | - Built in DNS Server during configuration for captive portal support when connected to the device as an AP directly 15 | - Built in Security-minded tools like HTML Escaping and other filters to prevent malicious inputs 16 | - Built in OTA firmware update support. Can both upload a new firmware from the UI or pull a new one down from a server 17 | - Built in Timer allows you to create very clean timings for measurements, et.c (i.e. read sensor every x minutes) 18 | 19 | ## Screenshots 20 | 21 | WiFi Configuration 22 | 23 | ![Wifi Configuration](https://raw.github.com/coogle/CoogleIOT/master/screenshots/wifi-screen.png) 24 | 25 | MQTT Client Configuration 26 | 27 | ![MQTT Configuration](https://raw.github.com/coogle/CoogleIOT/master/screenshots/mqtt-screen.png) 28 | 29 | System Configuration 30 | 31 | ![System Configuration](https://raw.github.com/coogle/CoogleIOT/master/screenshots/system-screen.png) 32 | 33 | Status Page 34 | 35 | ![Status Page](https://raw.github.com/coogle/CoogleIOT/master/screenshots/status-screen.png) 36 | 37 | ## Example 38 | 39 | CoogleIOT is designed to hang out in the background so your sketches are focused on the things you actually want to 40 | work on without having to worry about things like WiFi or MQTT clients. Here is an example of using it to control a 41 | Garage Door w/open and close sensors: 42 | 43 | ``` 44 | /* GarageDoor-Opener.h */ 45 | #ifndef GARAGEDOOR_OPENER_H 46 | #define GARAGEDOOR_OPENER_H 47 | 48 | #define SERIAL_BAUD 115200 49 | 50 | #define OPEN_SENSOR_PIN 2 // The pin that detects when the door is closed 51 | #define CLOSE_SENSOR_PIN 5 // The pin that detects when the door is open 52 | #define OPEN_SWTICH_PIN 14 // The pin that activates the open / close door action 53 | #define LIGHT_SWITCH_PIN 4 // The pin that turns the light on / off 54 | 55 | #define GARAGE_DOOR_STATUS_TOPIC "/status/garage-door" 56 | #define GARAGE_DOOR_ACTION_TOPIC_DOOR "/garage-door/door" 57 | #define GARAGE_DOOR_ACTION_TOPIC_LIGHT "/garage-door/light" 58 | #define GARAGE_DOOR_MQTT_CLIENT_ID "garage-door" 59 | 60 | #include 61 | 62 | typedef enum { 63 | GD_OPEN, 64 | GD_CLOSED, 65 | GD_OPENING, 66 | GD_CLOSING, 67 | GD_UNKNOWN 68 | } GarageDoorState; 69 | 70 | #endif 71 | 72 | ``` 73 | 74 | 75 | ``` 76 | #include 77 | #include "GarageDoor-Opener.h" 78 | 79 | CoogleIOT *iot; 80 | PubSubClient *mqtt; 81 | 82 | GarageDoorState _currentState = GD_UNKNOWN; 83 | 84 | String getDoorStateAsString(GarageDoorState state) 85 | { 86 | switch(state) { 87 | case GD_OPEN: 88 | return String("open"); 89 | case GD_CLOSED: 90 | return String("closed"); 91 | case GD_OPENING: 92 | return String("opening"); 93 | case GD_CLOSING: 94 | return String("closing"); 95 | case GD_UNKNOWN: 96 | return String("unknown"); 97 | } 98 | 99 | iot->warn("Garage Door State Value Unknown!"); 100 | 101 | return String("unknown"); 102 | } 103 | 104 | GarageDoorState getGarageDoorState() 105 | { 106 | bool isClosed, isOpen; 107 | GarageDoorState retval = GD_UNKNOWN; 108 | 109 | isOpen = digitalRead(OPEN_SENSOR_PIN) == LOW; 110 | isClosed = digitalRead(CLOSE_SENSOR_PIN) == LOW; 111 | 112 | 113 | if(isOpen && isClosed) { 114 | iot->error("Can't be both open and closed at the same time! Sensor failure!"); 115 | 116 | retval = GD_UNKNOWN; 117 | return retval; 118 | } 119 | 120 | if(!isOpen && isClosed) { 121 | retval = GD_CLOSED; 122 | return retval; 123 | } 124 | 125 | if(isOpen && !isClosed) { 126 | retval = GD_OPEN; 127 | return retval; 128 | } 129 | 130 | if(!isOpen && !isClosed) { 131 | 132 | if((_currentState == GD_OPEN) || (_currentState == GD_CLOSING)) { 133 | retval = GD_CLOSING; 134 | return retval; 135 | } 136 | 137 | if((_currentState == GD_CLOSED) || (_currentState == GD_OPENING)) { 138 | retval = GD_OPENING; 139 | return retval; 140 | } 141 | } 142 | 143 | retval = GD_UNKNOWN; 144 | return retval; 145 | } 146 | 147 | void triggerDoor() 148 | { 149 | iot->info("Triggering Garage Door Open"); 150 | digitalWrite(OPEN_SWTICH_PIN, LOW); 151 | delay(200); 152 | digitalWrite(OPEN_SWTICH_PIN, HIGH); 153 | } 154 | 155 | void triggerLight() 156 | { 157 | iot->info("Triggering Garage Door Light"); 158 | digitalWrite(LIGHT_SWITCH_PIN, LOW); 159 | delay(200); 160 | digitalWrite(LIGHT_SWITCH_PIN, HIGH); 161 | 162 | } 163 | 164 | void setup() 165 | { 166 | iot = new CoogleIOT(LED_BUILTIN); 167 | 168 | iot->enableSerial(SERIAL_BAUD) 169 | .setMQTTClientId(GARAGE_DOOR_MQTT_CLIENT_ID) 170 | .initialize(); 171 | 172 | pinMode(OPEN_SWTICH_PIN, OUTPUT); 173 | pinMode(LIGHT_SWITCH_PIN, OUTPUT); 174 | pinMode(OPEN_SENSOR_PIN, INPUT_PULLUP); 175 | pinMode(CLOSE_SENSOR_PIN, INPUT_PULLUP); 176 | 177 | digitalWrite(OPEN_SWTICH_PIN, HIGH); 178 | digitalWrite(LIGHT_SWITCH_PIN, HIGH); 179 | 180 | if(iot->mqttActive()) { 181 | mqtt = iot->getMQTTClient(); 182 | 183 | mqtt->setCallback(mqttCallbackHandler); 184 | 185 | iot->logPrintf(INFO, "Subscribed to Door-Open Topic: %s", GARAGE_DOOR_ACTION_TOPIC_DOOR); 186 | iot->logPrintf(INFO, "Subscribed to Light-Activate Topic: %s", GARAGE_DOOR_ACTION_TOPIC_LIGHT); 187 | 188 | mqtt->subscribe(GARAGE_DOOR_ACTION_TOPIC_DOOR); 189 | mqtt->subscribe(GARAGE_DOOR_ACTION_TOPIC_LIGHT); 190 | 191 | mqtt->publish(GARAGE_DOOR_STATUS_TOPIC, getDoorStateAsString(_currentState).c_str(), true); 192 | 193 | iot->info("Garage Door Opener Initialized"); 194 | 195 | } else { 196 | iot->error("MQTT Not initialized, Garage Door Opener Inactive"); 197 | } 198 | } 199 | 200 | void loop() 201 | { 202 | GarageDoorState liveState; 203 | 204 | iot->loop(); 205 | 206 | if(iot->mqttActive()) { 207 | liveState = getGarageDoorState(); 208 | 209 | if(liveState != _currentState) { 210 | mqtt->publish(GARAGE_DOOR_STATUS_TOPIC, getDoorStateAsString(liveState).c_str(), true); 211 | _currentState = liveState; 212 | } 213 | } 214 | 215 | } 216 | 217 | void mqttCallbackHandler(char *topic, byte *payload, unsigned int length) 218 | { 219 | String action; 220 | char *payloadStr; 221 | 222 | if(strcmp(topic, GARAGE_DOOR_ACTION_TOPIC_DOOR) == 0) { 223 | 224 | iot->info("Handling Garage Door Action Request"); 225 | iot->flashStatus(200, 1); 226 | triggerDoor(); 227 | 228 | } else if(strcmp(topic, GARAGE_DOOR_ACTION_TOPIC_LIGHT) == 0) { 229 | 230 | iot->info("Handing Garage Door Light Request"); 231 | iot->flashStatus(200, 2); 232 | triggerLight(); 233 | 234 | } 235 | } 236 | 237 | ``` 238 | 239 | There are other projects that use this library which serve as great examples of it's use as well. You should probably check out these: 240 | 241 | [Coogle Switch](https://github.com/ThisSmartHouse/coogle-switch) - A CoogleIOT-powered ESP8266 sketch for creating smart switches that operate over MQTT (controlling a relay module of configured sized). Just set up which pins your relay operates on and it takes care of all the MQTT topics, etc. you need for it to work. 242 | 243 | ## Where's my Device? 244 | 245 | When MQTT is enabled, CoogleIOT automatically sends a periodic heartbeat message to `/coogleiot/devices/` containing a JSON payload with useful information: 246 | 247 | ``` 248 | { 249 | "timestamp" : "2017-10-27 05:27:13", 250 | "ip" : "192.168.1.130", 251 | "coogleiot_version" : "1.2.1", 252 | "client_id" : "bbq-temp-probe" 253 | } 254 | ``` 255 | 256 | If running multiple CoogleIOT devices this can be very useful to keep track of them all by just subscribing to the `/coogleiot/devices/#` wildcard channel which will capture all the heartbeat transmissions. 257 | 258 | ## MQTT Client Notes 259 | 260 | Presently, due to [This Issue](https://github.com/knolleary/pubsubclient/issues/110) in the MQTT client used by CoogleIOT it is important that you compile your sketches using the `MQTT_MAX_PACKET_SIZE` flag set to a reasonable value (we recommend 512). Without this flag, larger MQTT packets (i.e. long topic names) will not be sent properly. 261 | 262 | Please consult your build envrionment's documentation on how to set this compile-time variable. (hint: `-DMQTT_MAX_PACKET_SIZE 512` works) 263 | 264 | ## API 265 | 266 | CoogleIOT is an evolving code base, so this API may change before this document is updated to reflect that. The best source is the source. When possible CoogleIOT uses a fluent interface, allowing you to chain method calls together: 267 | 268 | ``` 269 | // Chaining method calls together 270 | iot->enableSerial(115200) 271 | ->initialize(); 272 | ``` 273 | 274 | `void CoogleIOT::CoogleIOT(status_led_pin = NULL)` 275 | The library constructor. You may provide an optional pin to use for a status LED which will be used to indicate different 276 | states the device can be in (i.e. WiFi initializing, etc.) 277 | 278 | `bool CoogleIOT::initialize()` 279 | Must be called in `setup()` of your sketch to initialize the library and it's components 280 | 281 | `void CoogleIOT::loop()` 282 | Must be called in `loop()` of your sketch 283 | 284 | `CoogleIOT& CoogleIOT::enableSerial(int baud = 115200)` 285 | Enables Serial output from the IOT library. Will initialize the `Serial` object for you at the baud rate specified if not 286 | already initialized. 287 | 288 | `PubSubClient* CoogleIOT::getMQTTClient()` 289 | Return a pointer to the built in PubSubClient to use in your sketch 290 | 291 | `bool CoogleIOT::serialEnabled()` 292 | Returns true if Serial is enabled 293 | 294 | `CoogleIOT& CoogleIOT::flashStatus(speed_in_ms, repeat = 5)` 295 | Flashes the defined pin / LED at a speed, repeating as defined (5 times by default) 296 | 297 | `CoogleIOT& CoogleIOT::flashSOS()` 298 | Flashes the status pin / LED in an SOS pattern (useful to indicate an error) 299 | 300 | `CoogleIOT& CoogleIOT::resetEEProm()` 301 | Resets the EEPROM memory used by CoogleIOT to NULL, effectively "factory resetting" the device 302 | 303 | `void CoogleIOT::restartDevice()` 304 | Restarts the device. Due to [This Bug](https://github.com/esp8266/Arduino/issues/1722), you must physically press the restart button on the ESP8266 after flashing via Serial. If you fail to do that, this command will hang the device. 305 | 306 | `String CoogleIOT::filterAscii()` 307 | Filters the provided string of anything that is not a printable ASCII character 308 | 309 | `bool CoogleIOT::verifyFlashConfiguration()` 310 | Verifies the Flash configuration for the device (what the device supports, vs. what the device is set as in your sketch) is 311 | correct. 312 | 313 | `CoogleIOT& CoogleIOT::syncNTPTime(int offsetSeconds, int daylightOffsetSeconds)` 314 | Synchronizes and sets the local device date / time based on NTP servers. Must have a working WiFi connection to use this 315 | method. The first parameter is the number of seconds local time is offset from UTC time (i.e. -5 hrs in seconds is America/New York). The second parameter is the number of seconds to offset based on daylight savings. 316 | 317 | `String CoogleIOT::getWiFiStatus()` 318 | Returns a string representing the current state of the WiFi Client 319 | 320 | `bool CoogleIOT::mqttActive()` 321 | Returns true/false indicating if the MQTT client is active and ready to use or not 322 | 323 | `bool CoogleIOT::dnsActive()` 324 | Returns true/false if the integrated captive portal DNS is enabled or not 325 | 326 | `bool CoogleIOT::ntpActive()` 327 | Returns true/false if the NTP client is online and synchronizing with NTP time servers 328 | 329 | `bool CoogleIOT::firmwareClientActive()` 330 | Returns true/false if the periodic firmware client (that will download a new firmware from a web server) is active or not. If active, the Firmware client will check every 30 minutes for a new firmware at the configured URL 331 | 332 | `bool CoogleIOT::apStatus()` 333 | Returns true/false if the AP of the device is active or not. 334 | 335 | `CoogleIOT& CoogleIOT::registerTimer(int interval, callback)` 336 | Create a callback timer that will call `callback` (void function with no params) every `interval` milliseconds. You can turn off the timer 337 | by passing 0 as the interval. Useful for taking a sensor reading every X seconds, etc. 338 | 339 | `void CoogleIOT::checkForFirmwareUpdate()` 340 | Performs a check against the specified Firmware Server endpoint for a new version of this device's firmware. If a new version exists it performs the upgrade. 341 | 342 | The following getters/setters are pretty self explainatory. Each getter will return a `String` object of the value from EEPROM (or another primiative data type), with a matching setter: 343 | 344 | `String CoogleIOT::getRemoteAPName()` 345 | `CoogleIOT& CoogleIOT::setRemoteAPName(String)` 346 | `String CoogleIOT::getRemoteAPPassword()` 347 | `CoogleIOT& CoogleIOT::setRemoteAPPassword(String)` 348 | `String CoogleIOT::getMQTTHostname()` 349 | `CoogleIOT& CoogleIOT::setMQTTHostname(String)` 350 | `String CoogleIOT::getMQTTUsername()` 351 | `CoogleIOT& CoogleIOT::setMQTTUsername(String)` 352 | `String CoogleIOT::getMQTTPassword()` 353 | `CoogleIOT& CoogleIOT::setMQTTPassword(String)` 354 | `String CoogleIOT::getMQTTClientId()` 355 | `CoogleIOT& CoogleIOT::setMQTTClientId()` 356 | `int CoogleIOT::getMQTTPort()` 357 | `CoogleIOT& CoogleIOT::setMQTTPort(int)` 358 | `String CoogleIOT::getAPName()` 359 | `CoogleIOT& CoogleIOT::setAPName(String)` 360 | `String CoogleIOT::getAPPassword()` 361 | `CoogleIOT& CoogleIOT::setAPPassword(String)` 362 | `String CoogleIOT::getFirmwareUpdateUrl()` 363 | `CoogleIOT& CoogleIOT::setFirmwareUpdateUrl(String)` 364 | 365 | ## CoogleIOT Firmware Configuration 366 | 367 | The Firmware default values and settings are defined in the `CoogleIOTConfig.h` file and can be overriden by providing new `#define` statements 368 | 369 | `#define COOGLEIOT_STATUS_INIT 500` 370 | Defines the status LED flash speed in MS for initial initialization 371 | 372 | `#define COOGLEIOT_STATUS_WIFI_INIT 250` 373 | Defines the status LED flash speed for WiFi initialization 374 | 375 | `#define COOGLEIOT_STATUS_MQTT_INIT 100` 376 | Defines the status LED flash speed for MQTT initialization 377 | 378 | `#define COOGLEIOT_AP "COOGLEIOT_` 379 | Defines the prepended string used for the default AP name. The remainder of the AP name will be a randomly generated number (i.e. COOGLEIOT_234934) 380 | 381 | `#define COOGLEIOT_AP_DEFAULT_PASSWORD "coogleiot"` 382 | The default AP password 383 | 384 | `#define COOGLEIOT_DEFAULT_MQTT_CLIENT_ID "coogleIoT"` 385 | The default MQTT client ID 386 | 387 | `#define COOGLEIOT_DEFAULT_MQTT_PORT 1883` 388 | The default MQTT Port 389 | 390 | `#define COOGLEIOT_TIMEZONE_OFFSET ((3600 * 5) * -1)` 391 | The default NTP Timezone offset (America/New York) 392 | 393 | `#define COOGLEIOT_DAYLIGHT_OFFSET 0` 394 | The default NTP Daylight Offset 395 | 396 | ` 397 | #define COOGLEIOT_NTP_SERVER_1 "pool.ntp.org" 398 | #define COOGLEIOT_NTP_SERVER_2 "time.nist.gov" 399 | #define COOGLEIOT_NTP_SERVER_3 "time.google.com" 400 | ` 401 | The three default NTP servers to attempt to synchronize with 402 | 403 | `#define COOGLEIOT_FIRMWARE_UPDATE_CHECK_MS 54000000 // 15 Minutes in Milliseconds` 404 | The frequency that we will check for a new Firmware Update if the server is configured. Defaults to 15 minutes. 405 | 406 | `#define COOGLEIOT_DNS_PORT 53` 407 | The default DNS port 408 | 409 | `#define COOGLE_EEPROM_EEPROM_SIZE 1024` 410 | The amount of EEPROM memory allocated to CoogleIOT, 1kb default. 411 | 412 | *IMPORTANT NOTE:* 413 | Do _NOT_ reduce this value below it's default value unless you really know what you are doing, otherwise you will break the firmware. 414 | 415 | `#define COOGLEIOT_WEBSERVER_PORT 80` 416 | The default Webserver port for the configuration system 417 | 418 | `#define COOGLEIOT_DEBUG` 419 | If defined, it will enable debugging mode for CoogleIOT which will dump lots of debugging data to the Serial port (if enabled) 420 | -------------------------------------------------------------------------------- /examples/GarageDoorOpener/GarageDoor-Opener.h: -------------------------------------------------------------------------------- 1 | #ifndef GARAGEDOOR_OPENER_H 2 | #define GARAGEDOOR_OPENER_H 3 | 4 | #define SERIAL_BAUD 115200 5 | 6 | #define OPEN_SENSOR_PIN 2 // The pin that detects when the door is closed 7 | #define CLOSE_SENSOR_PIN 5 // The pin that detects when the door is open 8 | #define OPEN_SWTICH_PIN 14 // The pin that activates the open / close door action 9 | #define LIGHT_SWITCH_PIN 4 // The pin that turns the light on / off 10 | 11 | #define GARAGE_DOOR_STATUS_TOPIC "/status/garage-door" 12 | #define GARAGE_DOOR_ACTION_TOPIC_DOOR "/garage-door/door" 13 | #define GARAGE_DOOR_ACTION_TOPIC_LIGHT "/garage-door/light" 14 | #define GARAGE_DOOR_MQTT_CLIENT_ID "garage-door" 15 | 16 | #include 17 | 18 | typedef enum { 19 | GD_OPEN, 20 | GD_CLOSED, 21 | GD_OPENING, 22 | GD_CLOSING, 23 | GD_UNKNOWN 24 | } GarageDoorState; 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /examples/GarageDoorOpener/GarageDoorOpener.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "GarageDoor-Opener.h" 3 | 4 | CoogleIOT *iot; 5 | PubSubClient *mqtt; 6 | 7 | GarageDoorState _currentState = GD_UNKNOWN; 8 | 9 | String getDoorStateAsString(GarageDoorState state) 10 | { 11 | switch(state) { 12 | case GD_OPEN: 13 | return String("open"); 14 | case GD_CLOSED: 15 | return String("closed"); 16 | case GD_OPENING: 17 | return String("opening"); 18 | case GD_CLOSING: 19 | return String("closing"); 20 | case GD_UNKNOWN: 21 | return String("unknown"); 22 | } 23 | 24 | iot->warn("Garage Door State Value Unknown!"); 25 | 26 | return String("unknown"); 27 | } 28 | 29 | GarageDoorState getGarageDoorState() 30 | { 31 | bool isClosed, isOpen; 32 | GarageDoorState retval = GD_UNKNOWN; 33 | 34 | isOpen = digitalRead(OPEN_SENSOR_PIN) == LOW; 35 | isClosed = digitalRead(CLOSE_SENSOR_PIN) == LOW; 36 | 37 | 38 | if(isOpen && isClosed) { 39 | iot->error("Can't be both open and closed at the same time! Sensor failure!"); 40 | 41 | retval = GD_UNKNOWN; 42 | return retval; 43 | } 44 | 45 | if(!isOpen && isClosed) { 46 | retval = GD_CLOSED; 47 | return retval; 48 | } 49 | 50 | if(isOpen && !isClosed) { 51 | retval = GD_OPEN; 52 | return retval; 53 | } 54 | 55 | if(!isOpen && !isClosed) { 56 | 57 | if((_currentState == GD_OPEN) || (_currentState == GD_CLOSING)) { 58 | retval = GD_CLOSING; 59 | return retval; 60 | } 61 | 62 | if((_currentState == GD_CLOSED) || (_currentState == GD_OPENING)) { 63 | retval = GD_OPENING; 64 | return retval; 65 | } 66 | } 67 | 68 | retval = GD_UNKNOWN; 69 | return retval; 70 | } 71 | 72 | void triggerDoor() 73 | { 74 | iot->info("Triggering Garage Door Open"); 75 | digitalWrite(OPEN_SWTICH_PIN, LOW); 76 | delay(200); 77 | digitalWrite(OPEN_SWTICH_PIN, HIGH); 78 | } 79 | 80 | void triggerLight() 81 | { 82 | iot->info("Triggering Garage Door Light"); 83 | digitalWrite(LIGHT_SWITCH_PIN, LOW); 84 | delay(200); 85 | digitalWrite(LIGHT_SWITCH_PIN, HIGH); 86 | 87 | } 88 | 89 | void setup() 90 | { 91 | iot = new CoogleIOT(LED_BUILTIN); 92 | 93 | iot->enableSerial(SERIAL_BAUD) 94 | .setMQTTClientId(GARAGE_DOOR_MQTT_CLIENT_ID) 95 | .initialize(); 96 | 97 | pinMode(OPEN_SWTICH_PIN, OUTPUT); 98 | pinMode(LIGHT_SWITCH_PIN, OUTPUT); 99 | pinMode(OPEN_SENSOR_PIN, INPUT_PULLUP); 100 | pinMode(CLOSE_SENSOR_PIN, INPUT_PULLUP); 101 | 102 | digitalWrite(OPEN_SWTICH_PIN, HIGH); 103 | digitalWrite(LIGHT_SWITCH_PIN, HIGH); 104 | 105 | if(iot->mqttActive()) { 106 | mqtt = iot->getMQTTClient(); 107 | 108 | mqtt->setCallback(mqttCallbackHandler); 109 | 110 | iot->logPrintf(INFO, "Subscribed to Door-Open Topic: %s", GARAGE_DOOR_ACTION_TOPIC_DOOR); 111 | iot->logPrintf(INFO, "Subscribed to Light-Activate Topic: %s", GARAGE_DOOR_ACTION_TOPIC_LIGHT); 112 | 113 | mqtt->subscribe(GARAGE_DOOR_ACTION_TOPIC_DOOR); 114 | mqtt->subscribe(GARAGE_DOOR_ACTION_TOPIC_LIGHT); 115 | 116 | mqtt->publish(GARAGE_DOOR_STATUS_TOPIC, getDoorStateAsString(_currentState).c_str(), true); 117 | 118 | iot->info("Garage Door Opener Initialized"); 119 | 120 | } else { 121 | iot->error("MQTT Not initialized, Garage Door Opener Inactive"); 122 | } 123 | } 124 | 125 | void loop() 126 | { 127 | GarageDoorState liveState; 128 | 129 | iot->loop(); 130 | 131 | if(iot->mqttActive()) { 132 | liveState = getGarageDoorState(); 133 | 134 | if(liveState != _currentState) { 135 | mqtt->publish(GARAGE_DOOR_STATUS_TOPIC, getDoorStateAsString(liveState).c_str(), true); 136 | _currentState = liveState; 137 | } 138 | } 139 | 140 | } 141 | 142 | void mqttCallbackHandler(char *topic, byte *payload, unsigned int length) 143 | { 144 | String action; 145 | char *payloadStr; 146 | 147 | if(strcmp(topic, GARAGE_DOOR_ACTION_TOPIC_DOOR) == 0) { 148 | 149 | iot->info("Handling Garage Door Action Request"); 150 | iot->flashStatus(200, 1); 151 | triggerDoor(); 152 | 153 | } else if(strcmp(topic, GARAGE_DOOR_ACTION_TOPIC_LIGHT) == 0) { 154 | 155 | iot->info("Handing Garage Door Light Request"); 156 | iot->flashStatus(200, 2); 157 | triggerLight(); 158 | 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /examples/GarageDoorOpener/README.md: -------------------------------------------------------------------------------- 1 | This is just one of many different tools I've built. Most of these are just dependent on the CoogleIOT framework for basics 2 | and implement other specific functions. As I build other things I will post links to those repositories here: 3 | 4 | https://github.com/ThisSmartHouse/coogle-switch - Smart Switch / Relay Control 5 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | CoogleIOT KEYWORD1 2 | enableSerial KEYWORD2 3 | getMQTTClient KEYWORD2 4 | serialEnabled KEYWORD2 5 | flashStatus KEYWORD2 6 | flashSOS KEYWORD2 7 | resetEEPROM KEYWORD2 8 | restartDevice KEYWORD2 9 | getRemoteAPName KEYWORD2 10 | getRemoteAPPassword KEYWORD2 11 | getMQTTHostname KEYWORD2 12 | getMQTTUsername KEYWORD2 13 | getMQTTPassword KEYWORD2 14 | getMQTTClientId KEYWORD2 15 | getAPName KEYWORD2 16 | getAPPassword KEYWORD2 17 | filterAscii KEYWORD2 18 | getMQTTPort KEYWORD2 19 | getFirmwareUpdateUrl KEYWORD2 20 | getWiFiStatus KEYWORD2 21 | verifyFlashConfiguration KEYWORD2 22 | setMQTTPort KEYWORD2 23 | setMQTTHostname KEYWORD2 24 | setMQTTPassword KEYWORD2 25 | setRemoteAPName KEYWORD2 26 | setRemoteAPPassword KEYWORD2 27 | setMQTTClientId KEYWORD2 28 | setAPName KEYWORD2 29 | setAPPassword KEYWORD2 30 | setFirmwareUpdateUrl KEYWORD2 31 | syncNTPTime KEYWORD2 32 | mqttActive KEYWORD2 33 | dnsActive KEYWORD2 34 | ntpActive KEYWORD2 35 | firmwareClientActive KEYWORD2 36 | apStatus KEYWORD2 37 | checkForFirmwareUpdate KEYWORD2 38 | 39 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=CoogleIOT 2 | version=1.3.1 3 | author=John Coggeshall 4 | maintainer=John Coggeshall 5 | sentence=An IOT library for ESP8266 to provide WiFi Configuration, MQTT Client, OTA updates and more. 6 | paragraph=Also includes NTP Support, A captive portal for Configuration and improved EEPROM support. 7 | category=Device Control 8 | url=http://www.thissmarthouse.net/ 9 | architectures=esp8266 10 | includes=CoogleIOT.h 11 | -------------------------------------------------------------------------------- /screenshots/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThisSmartHouse/CoogleIOT/261c9b469c1c5886c10b430c145f73f97905e58d/screenshots/.DS_Store -------------------------------------------------------------------------------- /screenshots/mqtt-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThisSmartHouse/CoogleIOT/261c9b469c1c5886c10b430c145f73f97905e58d/screenshots/mqtt-screen.png -------------------------------------------------------------------------------- /screenshots/status-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThisSmartHouse/CoogleIOT/261c9b469c1c5886c10b430c145f73f97905e58d/screenshots/status-screen.png -------------------------------------------------------------------------------- /screenshots/system-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThisSmartHouse/CoogleIOT/261c9b469c1c5886c10b430c145f73f97905e58d/screenshots/system-screen.png -------------------------------------------------------------------------------- /screenshots/wifi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThisSmartHouse/CoogleIOT/261c9b469c1c5886c10b430c145f73f97905e58d/screenshots/wifi-screen.png -------------------------------------------------------------------------------- /src/CoogleEEPROM.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | CoogleIOT for ESP8266 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 2017-2018 John Coggeshall | 6 | +----------------------------------------------------------------------+ 7 | | Licensed under the Apache License, Version 2.0 (the "License"); | 8 | | you may not use this file except in compliance with the License. You | 9 | | may obtain a copy of the License at: | 10 | | | 11 | | http://www.apache.org/licenses/LICENSE-2.0 | 12 | | | 13 | | Unless required by applicable law or agreed to in writing, software | 14 | | distributed under the License is distributed on an "AS IS" BASIS, | 15 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | 16 | | implied. See the License for the specific language governing | 17 | | permissions and limitations under the License. | 18 | +----------------------------------------------------------------------+ 19 | | Authors: John Coggeshall | 20 | +----------------------------------------------------------------------+ 21 | */ 22 | 23 | #include "CoogleEEPROM.h" 24 | 25 | void CoogleEEProm::initialize(size_t size) 26 | { 27 | 28 | #ifdef COOGLEEEPROM_DEBUG 29 | Serial.print("Initializing EEPROM to "); 30 | Serial.print(size); 31 | Serial.println(" bytes"); 32 | #endif 33 | 34 | EEPROM.begin(size); 35 | } 36 | 37 | void CoogleEEProm::initialize() 38 | { 39 | EEPROM.begin(COOGLE_EEPROM_EEPROM_SIZE); 40 | } 41 | 42 | void CoogleEEProm::reset() 43 | { 44 | return fill(0, COOGLE_EEPROM_EEPROM_SIZE, 0x0); 45 | } 46 | 47 | void CoogleEEProm::fill(int startAddress, int endAddress, byte b) 48 | { 49 | for(int i = startAddress; i <= endAddress; i++) { 50 | EEPROM.write(i, b); 51 | } 52 | 53 | EEPROM.commit(); 54 | } 55 | 56 | bool CoogleEEProm::setApp(const byte *magic) 57 | { 58 | return writeBytes(0, magic, 4); 59 | } 60 | 61 | bool CoogleEEProm::isApp(const byte *magic) 62 | { 63 | byte bytes[4]; 64 | 65 | readBytes(0, bytes, 4); 66 | 67 | for(int i = 0; i < 4; i++) { 68 | if(bytes[i] != magic[i]) { 69 | 70 | #ifdef COOGLEEEPROM_DEBUG 71 | Serial.println("[COOGLE-EEPROM] Failed to locate magic bytes to identify memory"); 72 | #endif 73 | return false; 74 | } 75 | } 76 | 77 | return true; 78 | } 79 | 80 | void CoogleEEProm::dump(int bytesPerRow = 16) 81 | { 82 | int curAddr, curRow; 83 | byte b; 84 | 85 | char buf[10]; 86 | 87 | if(!Serial) { 88 | return; 89 | } 90 | 91 | curRow = 0; 92 | 93 | for(int i = 0; i <= COOGLE_EEPROM_EEPROM_SIZE; i++) { 94 | 95 | if(curRow == 0) { 96 | sprintf(buf, "%03X", i); 97 | Serial.print(buf); 98 | } 99 | 100 | b = EEPROM.read(i); 101 | 102 | sprintf(buf, "%02X", b); 103 | 104 | curRow++; 105 | 106 | if(curRow == bytesPerRow) { 107 | curRow = 0; 108 | Serial.println(buf); 109 | } else { 110 | Serial.print(buf); 111 | } 112 | } 113 | } 114 | 115 | bool CoogleEEProm::validAddress(int address) 116 | { 117 | if(address <= COOGLE_EEPROM_EEPROM_SIZE) { 118 | return true; 119 | } 120 | 121 | #ifdef COOGLEEEPROM_DEBUG 122 | Serial.printf("[COOGLE-EEPROM] Invalid Address: %d of %d\n", address, COOGLE_EEPROM_EEPROM_SIZE); 123 | #endif 124 | 125 | return (address <= COOGLE_EEPROM_EEPROM_SIZE); 126 | } 127 | 128 | bool CoogleEEProm::writeBytes(int startAddress, const byte *array, int length) 129 | { 130 | if(!validAddress(startAddress) || !validAddress(startAddress + length)) { 131 | return false; 132 | } 133 | 134 | #ifdef COOGLEEEPROM_DEBUG 135 | Serial.print("Writing Bytes: "); 136 | #endif 137 | 138 | for(int i = 0; i < length; i++) { 139 | 140 | #ifdef COOGLEEEPROM_DEBUG 141 | Serial.print((char)array[i]); 142 | #endif 143 | 144 | EEPROM.write(startAddress + i, array[i]); 145 | } 146 | 147 | #ifdef COOGLEEEPROM_DEBUG 148 | Serial.println(); 149 | #endif 150 | 151 | EEPROM.commit(); 152 | 153 | #ifdef COOGLEEEPROM_DEBUG 154 | Serial.print("[COOGLE-EEPROM] Wrote "); 155 | Serial.print(length); 156 | Serial.print(" bytes to address "); 157 | Serial.println(startAddress); 158 | #endif 159 | 160 | return true; 161 | } 162 | 163 | bool CoogleEEProm::readBytes(int startAddress, byte array[], int length) 164 | { 165 | if(!validAddress(startAddress) || !validAddress(startAddress + length)) { 166 | return false; 167 | } 168 | 169 | for(int i = 0; i < length; i++) { 170 | array[i] = EEPROM.read(startAddress + i); 171 | } 172 | 173 | return true; 174 | } 175 | 176 | bool CoogleEEProm::writeInt(int address, int value) 177 | { 178 | byte *ptr; 179 | 180 | ptr = (byte *)&value; 181 | 182 | return writeBytes(address, ptr, sizeof(value)); 183 | } 184 | 185 | bool CoogleEEProm::readInt(int address, int *value) 186 | { 187 | return readBytes(address, (byte *)value, sizeof(int)); 188 | } 189 | 190 | bool CoogleEEProm::writeString(int address, String str) 191 | { 192 | char *data; 193 | bool retval; 194 | 195 | data = (char *)malloc(str.length() + 1); 196 | str.toCharArray(data, str.length() + 1); 197 | 198 | retval = writeString(address, data); 199 | 200 | #ifdef COOGLEEEPROM_DEBUG 201 | Serial.print("[COOGLE-EEPROM] Wrote String: "); 202 | Serial.println(data); 203 | #endif 204 | 205 | free(data); 206 | 207 | return retval; 208 | 209 | } 210 | bool CoogleEEProm::writeString(int address, const char *string) 211 | { 212 | int length; 213 | 214 | length = strlen(string) + 1; 215 | 216 | return writeBytes(address, (const byte *)string, length); 217 | } 218 | 219 | bool CoogleEEProm::readString(int startAddress, char *buffer, int bufSize) 220 | { 221 | int bufIdx; 222 | 223 | #ifdef COOGLEEEPROM_DEBUG 224 | Serial.print("Reading into Buffer that is "); 225 | Serial.print(bufSize); 226 | Serial.print(" byte(s) from "); 227 | Serial.println(startAddress); 228 | 229 | #endif 230 | 231 | if(!validAddress(startAddress)) { 232 | 233 | #ifdef COOGLEEPROM_DEBUG 234 | Serial.println("Failed to read from address, invalid address!"); 235 | #endif 236 | 237 | return false; 238 | } 239 | 240 | if(bufSize == 0) { 241 | #ifdef COOGLEEEPROM_DEBUG 242 | Serial.println("Read buffer size was zero, returning false"); 243 | #endif 244 | return false; 245 | } 246 | 247 | if(bufSize == 1) { 248 | 249 | #ifdef COOGLEEEPROM_DEBUG 250 | Serial.println("Buffer Size was 1, returning null"); 251 | #endif 252 | 253 | buffer[0] = '\0'; 254 | return true; 255 | } 256 | 257 | bufIdx = 0; 258 | 259 | #ifdef COOGLEEEPROM_DEBUG 260 | Serial.print("[COOGLE-EEPROM] Read Chars: "); 261 | #endif 262 | 263 | do { 264 | 265 | buffer[bufIdx] = EEPROM.read(startAddress + bufIdx); 266 | 267 | #ifdef COOGLEEEPROM_DEBUG 268 | Serial.print(buffer[bufIdx]); 269 | #endif 270 | 271 | bufIdx++; 272 | 273 | } while( 274 | (buffer[bufIdx - 1] != 0x00) && // Null hit 275 | (bufIdx < bufSize) && // Out of space 276 | ((startAddress + bufIdx) <= COOGLE_EEPROM_EEPROM_SIZE) // End of EEPROM 277 | ); 278 | 279 | #ifdef COOGLEEEPROM_DEBUG 280 | Serial.println(); 281 | 282 | if(buffer[bufIdx - 1] == 0x00) { 283 | Serial.println("Read stopped due to NULL"); 284 | } else if(bufIdx >= bufSize) { 285 | Serial.println("Read stopped due to hitting buffer limit"); 286 | } else if((startAddress + bufIdx) > COOGLE_EEPROM_EEPROM_SIZE) { 287 | Serial.println("Read stopped due to hitting max EEPROM size limit"); 288 | } 289 | #endif 290 | 291 | if((buffer[bufIdx - 1] != 0x00) && (bufIdx >= 1)) { 292 | buffer[bufIdx - 1] = '\0'; 293 | } 294 | 295 | #ifdef COOGLEEEPROM_DEBUG 296 | Serial.print("Read String: "); 297 | Serial.println(buffer); 298 | #endif 299 | 300 | return true; 301 | 302 | } 303 | -------------------------------------------------------------------------------- /src/CoogleEEPROM.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | CoogleIOT for ESP8266 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 2017-2018 John Coggeshall | 6 | +----------------------------------------------------------------------+ 7 | | Licensed under the Apache License, Version 2.0 (the "License"); | 8 | | you may not use this file except in compliance with the License. You | 9 | | may obtain a copy of the License at: | 10 | | | 11 | | http://www.apache.org/licenses/LICENSE-2.0 | 12 | | | 13 | | Unless required by applicable law or agreed to in writing, software | 14 | | distributed under the License is distributed on an "AS IS" BASIS, | 15 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | 16 | | implied. See the License for the specific language governing | 17 | | permissions and limitations under the License. | 18 | +----------------------------------------------------------------------+ 19 | | Authors: John Coggeshall | 20 | +----------------------------------------------------------------------+ 21 | */ 22 | 23 | #ifndef COOGLE_EEPROM_H 24 | #define COOGLE_EEPROM_H 25 | 26 | #include 27 | #include "Arduino.h" 28 | #include "CoogleIOTConfig.h" 29 | 30 | class CoogleEEProm 31 | { 32 | public: 33 | void initialize(size_t); 34 | void initialize(); 35 | void reset(); 36 | void fill(int, int, byte); 37 | void dump(int); 38 | bool validAddress(int); 39 | bool writeBytes(int, const byte *, int); 40 | bool readBytes(int, byte[], int); 41 | bool writeInt(int, int); 42 | bool readInt(int, int *); 43 | bool writeString(int address, String str); 44 | bool writeString(int, const char *); 45 | bool readString(int, char *, int); 46 | bool isApp(const byte *); 47 | bool setApp(const byte *); 48 | }; 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /src/CoogleIOT.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | CoogleIOT for ESP8266 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 2017-2018 John Coggeshall | 6 | +----------------------------------------------------------------------+ 7 | | Licensed under the Apache License, Version 2.0 (the "License"); | 8 | | you may not use this file except in compliance with the License. You | 9 | | may obtain a copy of the License at: | 10 | | | 11 | | http://www.apache.org/licenses/LICENSE-2.0 | 12 | | | 13 | | Unless required by applicable law or agreed to in writing, software | 14 | | distributed under the License is distributed on an "AS IS" BASIS, | 15 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | 16 | | implied. See the License for the specific language governing | 17 | | permissions and limitations under the License. | 18 | +----------------------------------------------------------------------+ 19 | | Authors: John Coggeshall | 20 | +----------------------------------------------------------------------+ 21 | */ 22 | 23 | #include "CoogleIOT.h" 24 | #include "CoogleIOTConfig.h" 25 | 26 | CoogleIOT* __coogle_iot_self; 27 | 28 | extern "C" void __coogle_iot_firmware_timer_callback(void *pArg) 29 | { 30 | __coogle_iot_self->firmwareUpdateTick = true; 31 | } 32 | 33 | extern "C" void __coogle_iot_heartbeat_timer_callback(void *pArg) 34 | { 35 | __coogle_iot_self->heartbeatTick = true; 36 | } 37 | 38 | extern "C" void __coogle_iot_sketch_timer_callback(void *pArg) 39 | { 40 | __coogle_iot_self->sketchTimerTick = true; 41 | } 42 | 43 | CoogleIOT::CoogleIOT(int statusPin) 44 | { 45 | _statusPin = statusPin; 46 | _serial = false; 47 | } 48 | 49 | CoogleIOT::CoogleIOT() 50 | { 51 | CoogleIOT(-1); 52 | } 53 | 54 | CoogleIOT::~CoogleIOT() 55 | { 56 | delete mqttClient; 57 | delete webServer; 58 | 59 | if(logFile) { 60 | logFile.close(); 61 | } 62 | 63 | SPIFFS.end(); 64 | Serial.end(); 65 | 66 | if(_firmwareClientActive) { 67 | os_timer_disarm(&firmwareUpdateTimer); 68 | } 69 | 70 | if(sketchTimerInterval > 0) { 71 | os_timer_disarm(&sketchTimer); 72 | } 73 | 74 | os_timer_disarm(&heartbeatTimer); 75 | } 76 | 77 | CoogleIOT& CoogleIOT::registerTimer(int interval, sketchtimer_cb_t callback) 78 | { 79 | if(interval <= 0) { 80 | if(sketchTimerInterval > 0) { 81 | os_timer_disarm(&sketchTimer); 82 | } 83 | 84 | sketchTimerCallback = NULL; 85 | sketchTimerInterval = 0; 86 | 87 | return *this; 88 | } 89 | 90 | sketchTimerCallback = callback; 91 | sketchTimerInterval = interval; 92 | 93 | os_timer_setfn(&sketchTimer, __coogle_iot_sketch_timer_callback, NULL); 94 | os_timer_arm(&sketchTimer, sketchTimerInterval, true); 95 | 96 | return *this; 97 | } 98 | 99 | String CoogleIOT::getTimestampAsString() 100 | { 101 | String timestamp; 102 | struct tm* p_tm; 103 | 104 | if(now) { 105 | p_tm = localtime(&now); 106 | 107 | timestamp = timestamp + 108 | (p_tm->tm_year + 1900) + "-" + 109 | (p_tm->tm_mon < 10 ? "0" : "") + p_tm->tm_mon + "-" + 110 | (p_tm->tm_mday < 10 ? "0" : "") + p_tm->tm_mday + " " + 111 | (p_tm->tm_hour < 10 ? "0" : "") + p_tm->tm_hour + ":" + 112 | (p_tm->tm_min < 10 ? "0" : "") + p_tm->tm_min + ":" + 113 | (p_tm->tm_sec < 10 ? "0" : "") + p_tm->tm_sec; 114 | } else { 115 | timestamp = F("UKWN"); 116 | } 117 | 118 | return timestamp; 119 | } 120 | 121 | String CoogleIOT::buildLogMsg(String msg, CoogleIOT_LogSeverity severity) 122 | { 123 | String retval; 124 | String timestamp; 125 | 126 | timestamp = getTimestampAsString(); 127 | 128 | switch(severity) { 129 | case DEBUG: 130 | retval = "[DEBUG " + timestamp + "] " + msg; 131 | break; 132 | case INFO: 133 | retval = "[INFO " + timestamp + "] " + msg; 134 | break; 135 | case WARNING: 136 | retval = "[WARNING " + timestamp + "] " + msg; 137 | break; 138 | case ERROR: 139 | retval = "[ERROR " + timestamp + "] " + msg; 140 | break; 141 | case CRITICAL: 142 | retval = "[CRTICAL " + timestamp + "] " + msg; 143 | break; 144 | default: 145 | retval = "[UNKNOWN " + timestamp + "] " + msg; 146 | break; 147 | } 148 | 149 | return retval; 150 | } 151 | 152 | CoogleIOT& CoogleIOT::logPrintf(CoogleIOT_LogSeverity severity, const char *format, ...) 153 | { 154 | va_list arg; 155 | va_start(arg, format); 156 | char temp[64]; 157 | char* buffer = temp; 158 | size_t len = vsnprintf(temp, sizeof(temp), format, arg); 159 | va_end(arg); 160 | 161 | String logMsg; 162 | 163 | if (len > sizeof(temp) - 1) { 164 | buffer = new char[len + 1]; 165 | if (!buffer) { 166 | return *this; 167 | } 168 | va_start(arg, format); 169 | vsnprintf(buffer, len + 1, format, arg); 170 | va_end(arg); 171 | } 172 | 173 | logMsg = String(buffer); 174 | log(logMsg, severity); 175 | 176 | if (buffer != temp) { 177 | delete[] buffer; 178 | } 179 | 180 | return *this; 181 | } 182 | 183 | CoogleIOT& CoogleIOT::debug(String msg) 184 | { 185 | return log(msg, DEBUG); 186 | } 187 | 188 | CoogleIOT& CoogleIOT::info(String msg) 189 | { 190 | return log(msg, INFO); 191 | } 192 | 193 | CoogleIOT& CoogleIOT::warn(String msg) 194 | { 195 | return log(msg, WARNING); 196 | } 197 | 198 | CoogleIOT& CoogleIOT::error(String msg) 199 | { 200 | return log(msg, ERROR); 201 | } 202 | 203 | CoogleIOT& CoogleIOT::critical(String msg) 204 | { 205 | return log(msg, CRITICAL); 206 | } 207 | 208 | File& CoogleIOT::getLogFile() 209 | { 210 | return logFile; 211 | } 212 | 213 | String CoogleIOT::getLogs() 214 | { 215 | return CoogleIOT::getLogs(false); 216 | } 217 | 218 | String CoogleIOT::getLogs(bool asHTML) 219 | { 220 | String retval; 221 | 222 | if(!logFile || !logFile.size()) { 223 | return retval; 224 | } 225 | 226 | logFile.seek(0, SeekSet); 227 | 228 | while(logFile.available()) { 229 | retval += (char)logFile.read(); 230 | } 231 | 232 | logFile.seek(0, SeekEnd); 233 | 234 | return retval; 235 | } 236 | 237 | CoogleIOT& CoogleIOT::log(String msg, CoogleIOT_LogSeverity severity) 238 | { 239 | String logMsg = buildLogMsg(msg, severity); 240 | 241 | if(_serial) { 242 | Serial.println(logMsg); 243 | } 244 | 245 | if(!logFile) { 246 | return *this; 247 | } 248 | 249 | if((logFile.size() + msg.length()) > COOGLEIOT_LOGFILE_MAXSIZE) { 250 | 251 | logFile.close(); 252 | SPIFFS.remove(COOGLEIOT_SPIFFS_LOGFILE); 253 | logFile = SPIFFS.open(COOGLEIOT_SPIFFS_LOGFILE, "a+"); 254 | 255 | if(!logFile) { 256 | if(_serial) { 257 | Serial.println("ERROR Could not open SPIFFS log file!"); 258 | } 259 | return *this; 260 | } 261 | } 262 | 263 | logFile.println(logMsg); 264 | 265 | return *this; 266 | } 267 | 268 | bool CoogleIOT::serialEnabled() 269 | { 270 | return _serial; 271 | } 272 | 273 | void CoogleIOT::loop() 274 | { 275 | struct tm* p_tm; 276 | String remoteAPName; 277 | String mqttClientId; 278 | 279 | char topic[150]; 280 | char json[150]; 281 | 282 | 283 | if(sketchTimerTick) { 284 | sketchTimerTick = false; 285 | sketchTimerCallback(); 286 | } 287 | 288 | if(heartbeatTick) { 289 | heartbeatTick = false; 290 | flashStatus(100, 1); 291 | 292 | if((wifiFailuresCount > COOGLEIOT_MAX_WIFI_ATTEMPTS) && (WiFi.status() != WL_CONNECTED)) { 293 | info("Failed too many times to establish a WiFi connection. Restarting Device."); 294 | restartDevice(); 295 | return; 296 | } 297 | 298 | if((mqttFailuresCount > COOGLEIOT_MAX_MQTT_ATTEMPTS) && !mqttClient->connected()) { 299 | info("Failed too many times to establish a MQTT connection. Restarting Device."); 300 | restartDevice(); 301 | return; 302 | } 303 | 304 | if(mqttClientActive) { 305 | 306 | mqttClientId = getMQTTClientId(); 307 | 308 | snprintf(json, 150, "{ \"timestamp\" : \"%s\", \"ip\" : \"%s\", \"coogleiot_version\" : \"%s\", \"client_id\" : \"%s\" }", 309 | getTimestampAsString().c_str(), 310 | WiFi.localIP().toString().c_str(), 311 | COOGLEIOT_VERSION, 312 | mqttClientId.c_str()); 313 | 314 | snprintf(topic, 150, COOGLEIOT_DEVICE_TOPIC "/%s", mqttClientId.c_str()); 315 | 316 | if(!mqttClient->publish(topic, json, true)) { 317 | error("Failed to publish to heartbeat topic!"); 318 | } 319 | } 320 | 321 | } 322 | 323 | if(WiFi.status() != WL_CONNECTED) { 324 | 325 | remoteAPName = getRemoteAPName(); 326 | 327 | if(remoteAPName.length() > 0) { 328 | info("Not connected to WiFi. Attempting reconnection."); 329 | if(!connectToSSID()) { 330 | wifiFailuresCount++; 331 | logPrintf(INFO, "Attempt %d failed. Will attempt %d times before restarting.", wifiFailuresCount, COOGLEIOT_MAX_WIFI_ATTEMPTS); 332 | return; 333 | } 334 | } 335 | 336 | } else { 337 | 338 | wifiFailuresCount = 0; 339 | 340 | if(!mqttClient->connected()) { 341 | yield(); 342 | if(!connectToMQTT()) { 343 | mqttFailuresCount++; 344 | } 345 | } 346 | 347 | if(mqttClientActive) { 348 | mqttFailuresCount = 0; 349 | yield(); 350 | mqttClient->loop(); 351 | } 352 | 353 | if(ntpClientActive) { 354 | now = time(nullptr); 355 | 356 | if(now) { 357 | p_tm = localtime(&now); 358 | 359 | if( (p_tm->tm_hour == 12) && 360 | (p_tm->tm_min == 0) && 361 | (p_tm->tm_sec == 6)) { 362 | yield(); 363 | syncNTPTime(COOGLEIOT_TIMEZONE_OFFSET, COOGLEIOT_DAYLIGHT_OFFSET); 364 | } 365 | } 366 | } 367 | 368 | if(firmwareUpdateTick) { 369 | firmwareUpdateTick = false; 370 | 371 | checkForFirmwareUpdate(); 372 | 373 | if(_serial) { 374 | switch(firmwareUpdateStatus) { 375 | case HTTP_UPDATE_FAILED: 376 | warn("Warning! Failed to update firmware with specified URL"); 377 | break; 378 | case HTTP_UPDATE_NO_UPDATES: 379 | info("Firmware update check completed - at current version"); 380 | break; 381 | case HTTP_UPDATE_OK: 382 | info("Firmware Updated!"); 383 | break; 384 | default: 385 | warn("Warning! No updated performed. Perhaps an invalid URL?"); 386 | break; 387 | } 388 | } 389 | } 390 | } 391 | 392 | yield(); 393 | webServer->loop(); 394 | 395 | yield(); 396 | #ifndef ARDUINO_ESP8266_ESP01 397 | if(dnsServerActive) { 398 | dnsServer.processNextRequest(); 399 | } 400 | #endif 401 | 402 | } 403 | 404 | CoogleIOT& CoogleIOT::flashSOS() 405 | { 406 | for(int i = 0; i < 3; i++) { 407 | flashStatus(200, 3); 408 | delay(1000); 409 | flashStatus(500, 3); 410 | delay(1000); 411 | flashStatus(200, 3); 412 | delay(5000); 413 | } 414 | 415 | return *this; 416 | } 417 | 418 | bool CoogleIOT::mqttActive() 419 | { 420 | return mqttClientActive; 421 | } 422 | 423 | #ifndef ARDUINO_ESP8266_ESP01 424 | bool CoogleIOT::dnsActive() 425 | { 426 | return dnsServerActive; 427 | } 428 | #else 429 | bool CoogleIOT::dnsActive() 430 | { 431 | return false; 432 | } 433 | #endif 434 | 435 | bool CoogleIOT::ntpActive() 436 | { 437 | return ntpClientActive; 438 | } 439 | 440 | bool CoogleIOT::firmwareClientActive() 441 | { 442 | return _firmwareClientActive; 443 | } 444 | 445 | bool CoogleIOT::apStatus() 446 | { 447 | return _apStatus; 448 | } 449 | 450 | String CoogleIOT::getWiFiStatus() 451 | { 452 | String retval; 453 | 454 | switch(WiFi.status()) { 455 | case WL_CONNECTED: 456 | retval = "Connected"; 457 | break; 458 | case WL_NO_SSID_AVAIL: 459 | retval = "No SSID Available"; 460 | break; 461 | case WL_CONNECT_FAILED: 462 | retval = "Failed to Connect"; 463 | break; 464 | case WL_IDLE_STATUS: 465 | retval = "Idle"; 466 | break; 467 | case WL_DISCONNECTED: 468 | default: 469 | retval = "Disconnected"; 470 | break; 471 | } 472 | 473 | return retval; 474 | } 475 | 476 | CoogleIOT& CoogleIOT::syncNTPTime(int offsetSeconds, int daylightOffsetSec) 477 | { 478 | if(!WiFi.status() == WL_CONNECTED) { 479 | warn("Cannot synchronize time with NTP Servers - No WiFi Connection"); 480 | return *this; 481 | } 482 | 483 | if(_serial) { 484 | info("Synchronizing time on device with NTP Servers"); 485 | } 486 | 487 | configTime(offsetSeconds, daylightOffsetSec, COOGLEIOT_NTP_SERVER_1, COOGLEIOT_NTP_SERVER_2, COOGLEIOT_NTP_SERVER_3); 488 | 489 | for(int i = 0; (i < 10) && !time(nullptr); i++) { 490 | delay(1000); 491 | } 492 | 493 | if(!(now = time(nullptr))) { 494 | warn("Failed to synchronize with time server!"); 495 | } else { 496 | info("Time successfully synchronized with NTP server"); 497 | ntpClientActive = true; 498 | } 499 | 500 | return *this; 501 | } 502 | 503 | CoogleIOT& CoogleIOT::flashStatus(int speed) 504 | { 505 | flashStatus(speed, 5); 506 | return *this; 507 | } 508 | 509 | CoogleIOT& CoogleIOT::flashStatus(int speed, int repeat) 510 | { 511 | if(_statusPin > -1) { 512 | for(int i = 0; i < repeat; i++) { 513 | digitalWrite(_statusPin, LOW); 514 | delay(speed); 515 | digitalWrite(_statusPin, HIGH); 516 | delay(speed); 517 | } 518 | 519 | digitalWrite(_statusPin, HIGH); 520 | } 521 | 522 | return *this; 523 | } 524 | 525 | bool CoogleIOT::initialize() 526 | { 527 | String firmwareUrl; 528 | String localAPName; 529 | 530 | if(_statusPin > -1) { 531 | pinMode(_statusPin, OUTPUT); 532 | flashStatus(COOGLEIOT_STATUS_INIT); 533 | } 534 | 535 | info("Coogle IOT v" COOGLEIOT_VERSION " initializing.."); 536 | 537 | verifyFlashConfiguration(); 538 | 539 | randomSeed(micros()); 540 | 541 | eeprom.initialize(COOGLE_EEPROM_EEPROM_SIZE); 542 | 543 | SPIFFS.begin(); 544 | 545 | if(!eeprom.isApp((const byte *)COOGLEIOT_MAGIC_BYTES)) { 546 | 547 | info("EEPROM not initialized for platform, erasing.."); 548 | 549 | eeprom.reset(); 550 | eeprom.setApp((const byte *)COOGLEIOT_MAGIC_BYTES); 551 | SPIFFS.format(); 552 | } 553 | 554 | logFile = SPIFFS.open(COOGLEIOT_SPIFFS_LOGFILE, "a+"); 555 | 556 | if(!logFile) { 557 | error("Could not open SPIFFS log file!"); 558 | } else { 559 | info("Log file successfully opened"); 560 | } 561 | 562 | WiFi.disconnect(); 563 | WiFi.setAutoConnect(false); 564 | WiFi.setAutoReconnect(true); 565 | WiFi.mode(WIFI_AP_STA); 566 | 567 | localAPName = getAPName(); 568 | 569 | if(localAPName.length() > 0) { 570 | WiFi.hostname(localAPName.c_str()); 571 | } 572 | 573 | if(!connectToSSID()) { 574 | error("Failed to connect to remote AP"); 575 | } else { 576 | 577 | syncNTPTime(COOGLEIOT_TIMEZONE_OFFSET, COOGLEIOT_DAYLIGHT_OFFSET); 578 | 579 | if(!initializeMQTT()) { 580 | error("Failed to connect to MQTT Server"); 581 | } 582 | 583 | } 584 | 585 | // Used for callbacks into C, where we can't use std::bind to bind an object method 586 | __coogle_iot_self = this; 587 | 588 | enableConfigurationMode(); 589 | 590 | firmwareUrl = getFirmwareUpdateUrl(); 591 | 592 | if(firmwareUrl.length() > 0) { 593 | os_timer_setfn(&firmwareUpdateTimer, __coogle_iot_firmware_timer_callback, NULL); 594 | os_timer_arm(&firmwareUpdateTimer, COOGLEIOT_FIRMWARE_UPDATE_CHECK_MS, true); 595 | 596 | info("Automatic Firmware Update Enabled"); 597 | 598 | _firmwareClientActive = true; 599 | } 600 | 601 | os_timer_setfn(&heartbeatTimer, __coogle_iot_heartbeat_timer_callback, NULL); 602 | os_timer_arm(&heartbeatTimer, COOGLEIOT_HEARTBEAT_MS, true); 603 | 604 | return true; 605 | } 606 | 607 | void CoogleIOT::restartDevice() 608 | { 609 | _restarting = true; 610 | ESP.restart(); 611 | } 612 | 613 | CoogleIOT& CoogleIOT::resetEEProm() 614 | { 615 | eeprom.reset(); 616 | return *this; 617 | } 618 | 619 | bool CoogleIOT::verifyFlashConfiguration() 620 | { 621 | uint32_t realSize = ESP.getFlashChipRealSize(); 622 | uint32_t ideSize = ESP.getFlashChipSize(); 623 | FlashMode_t ideMode = ESP.getFlashChipMode(); 624 | 625 | debug("Introspecting on-board Flash Memory:"); 626 | logPrintf(DEBUG, "Flash ID: %08X", ESP.getFlashChipId()); 627 | logPrintf(DEBUG, "Flash real size: %u", realSize); 628 | logPrintf(DEBUG, "Flash IDE Size: %u", ideSize); 629 | logPrintf(DEBUG, "Flash IDE Speed: %u", ESP.getFlashChipSpeed()); 630 | logPrintf(DEBUG, "Flash IDE Mode: %u", (ideMode == FM_QIO ? "QIO" : ideMode == FM_QOUT ? "QOUT" : ideMode == FM_DIO ? "DIO" : ideMode == FM_DOUT ? "DOUT" : "UNKNOWN")); 631 | 632 | if(ideSize != realSize) { 633 | warn("Flashed size is not equal to size available on chip!"); 634 | } else { 635 | debug("Flash Chip Configuration Verified: OK"); 636 | } 637 | 638 | } 639 | 640 | void CoogleIOT::enableConfigurationMode() 641 | { 642 | info("Enabling Configuration Mode"); 643 | 644 | initializeLocalAP(); 645 | 646 | webServer = new CoogleIOTWebserver(*this); 647 | 648 | if(!webServer->initialize()) { 649 | error("Failed to initialize configuration web server"); 650 | flashSOS(); 651 | } 652 | } 653 | 654 | 655 | void CoogleIOT::initializeLocalAP() 656 | { 657 | String localAPName, localAPPassword; 658 | 659 | IPAddress apLocalIP(192,168,0,1); 660 | IPAddress apSubnetMask(255,255,255,0); 661 | IPAddress apGateway(192,168,0,1); 662 | 663 | localAPName = getAPName(); 664 | localAPPassword = getAPPassword(); 665 | 666 | if(localAPPassword.length() == 0) { 667 | info("No AP Password found in memory"); 668 | info("Setting to default password: " COOGLEIOT_AP_DEFAULT_PASSWORD); 669 | 670 | localAPPassword = COOGLEIOT_AP_DEFAULT_PASSWORD; 671 | setAPPassword(localAPPassword); 672 | 673 | } 674 | 675 | if(localAPName.length() == 0) { 676 | info("No AP Name found in memory. Auto-generating AP name."); 677 | 678 | localAPName = COOGLEIOT_AP; 679 | localAPName.concat((int)random(100000, 999999)); 680 | 681 | info("Setting AP Name To: " ); 682 | info(localAPName); 683 | 684 | setAPName(localAPName); 685 | } 686 | 687 | info("Intiailzing Access Point"); 688 | 689 | WiFi.softAPConfig(apLocalIP, apGateway, apSubnetMask); 690 | WiFi.softAP(localAPName.c_str(), localAPPassword.c_str()); 691 | 692 | info("Local IP Address: "); 693 | info(WiFi.softAPIP().toString()); 694 | 695 | #ifndef ARDUINO_ESP8266_ESP01 696 | if(WiFi.status() != WL_CONNECTED) { 697 | 698 | info("Initializing DNS Server"); 699 | 700 | dnsServer.start(COOGLEIOT_DNS_PORT, "*", WiFi.softAPIP()); 701 | dnsServerActive = true; 702 | 703 | } else { 704 | 705 | info("Disabled DNS Server while connected to WiFI"); 706 | dnsServerActive = false; 707 | 708 | } 709 | #endif 710 | 711 | _apStatus = true; 712 | 713 | } 714 | 715 | String CoogleIOT::getFirmwareUpdateUrl() 716 | { 717 | char firmwareUrl[COOGLEIOT_FIRMWARE_UPDATE_URL_MAXLEN]; 718 | 719 | if(!eeprom.readString(COOGLEIOT_FIRMWARE_UPDATE_URL_ADDR, firmwareUrl, COOGLEIOT_FIRMWARE_UPDATE_URL_MAXLEN)) { 720 | error("Failed to read Firmware Update URL from EEPROM"); 721 | } 722 | 723 | String retval(firmwareUrl); 724 | return filterAscii(retval); 725 | } 726 | 727 | String CoogleIOT::getMQTTHostname() 728 | { 729 | char mqttHost[COOGLEIOT_MQTT_HOST_MAXLEN]; 730 | 731 | if(!eeprom.readString(COOGLEIOT_MQTT_HOST_ADDR, mqttHost, COOGLEIOT_MQTT_HOST_MAXLEN)) { 732 | error("Failed to read MQTT Server Hostname from EEPROM"); 733 | } 734 | 735 | String retval(mqttHost); 736 | return filterAscii(retval); 737 | } 738 | 739 | String CoogleIOT::getMQTTClientId() 740 | { 741 | char mqtt[COOGLEIOT_MQTT_CLIENT_ID_MAXLEN]; 742 | 743 | if(!eeprom.readString(COOGLEIOT_MQTT_CLIENT_ID_ADDR, mqtt, COOGLEIOT_MQTT_CLIENT_ID_MAXLEN)) { 744 | error("Failed to read MQTT Client ID from EEPROM"); 745 | } 746 | 747 | String retval(mqtt); 748 | return filterAscii(retval); 749 | } 750 | 751 | String CoogleIOT::getMQTTUsername() 752 | { 753 | char mqtt[COOGLEIOT_MQTT_USER_MAXLEN]; 754 | 755 | if(!eeprom.readString(COOGLEIOT_MQTT_USER_ADDR, mqtt, COOGLEIOT_MQTT_USER_MAXLEN)) { 756 | error("Failed to read MQTT Username from EEPROM"); 757 | } 758 | 759 | String retval(mqtt); 760 | return filterAscii(retval); 761 | } 762 | 763 | String CoogleIOT::getMQTTPassword() 764 | { 765 | char mqtt[COOGLEIOT_MQTT_USER_PASSWORD_MAXLEN]; 766 | 767 | if(!eeprom.readString(COOGLEIOT_MQTT_USER_PASSWORD_ADDR, mqtt, COOGLEIOT_MQTT_USER_PASSWORD_MAXLEN)) { 768 | error("Failed to read MQTT Password from EEPROM"); 769 | } 770 | 771 | String retval(mqtt); 772 | return filterAscii(retval); 773 | } 774 | 775 | String CoogleIOT::getMQTTLWTTopic() 776 | { 777 | char mqtt[COOGLEIOT_MQTT_LWT_TOPIC_MAXLEN]; 778 | 779 | if(!eeprom.readString(COOGLEIOT_MQTT_LWT_TOPIC_ADDR, mqtt, COOGLEIOT_MQTT_LWT_TOPIC_MAXLEN)) { 780 | error("Failed to read MQTT LWT Topic from EEPROM"); 781 | } 782 | 783 | String retval(mqtt); 784 | return filterAscii(retval); 785 | } 786 | 787 | String CoogleIOT::getMQTTLWTMessage() 788 | { 789 | char mqtt[COOGLEIOT_MQTT_LWT_MESSAGE_MAXLEN]; 790 | 791 | if(!eeprom.readString(COOGLEIOT_MQTT_LWT_MESSAGE_ADDR, mqtt, COOGLEIOT_MQTT_LWT_MESSAGE_MAXLEN)) { 792 | error("Failed to read MQTT LWT Message from EEPROM"); 793 | } 794 | 795 | String retval(mqtt); 796 | return filterAscii(retval); 797 | } 798 | 799 | int CoogleIOT::getMQTTPort() 800 | { 801 | int mqtt; 802 | 803 | if(!eeprom.readInt(COOGLEIOT_MQTT_PORT_ADDR, &mqtt)) { 804 | error("Failed to read MQTT Port from EEPROM"); 805 | } 806 | 807 | return mqtt; 808 | } 809 | 810 | CoogleIOT& CoogleIOT::setMQTTPort(int port) 811 | { 812 | if(!eeprom.writeInt(COOGLEIOT_MQTT_PORT_ADDR, port)) { 813 | error("Failed to write MQTT Port to EEPROM"); 814 | } 815 | 816 | return *this; 817 | } 818 | 819 | CoogleIOT& CoogleIOT::setFirmwareUpdateUrl(String s) 820 | { 821 | if(s.length() > COOGLEIOT_FIRMWARE_UPDATE_URL_MAXLEN) { 822 | warn("Attempted to write beyond max length for Firmware Update URL"); 823 | return *this; 824 | } 825 | 826 | if(!eeprom.writeString(COOGLEIOT_FIRMWARE_UPDATE_URL_ADDR, s)) { 827 | error("Failed to write Firmware Update URL to EEPROM"); 828 | } 829 | 830 | return *this; 831 | } 832 | 833 | CoogleIOT& CoogleIOT::setMQTTClientId(String s) 834 | { 835 | if(s.length() > COOGLEIOT_MQTT_CLIENT_ID_MAXLEN) { 836 | warn("Attempted to write beyond max length for MQTT Client ID"); 837 | return *this; 838 | } 839 | 840 | if(!eeprom.writeString(COOGLEIOT_MQTT_CLIENT_ID_ADDR, s)) { 841 | error("Failed to write MQTT Client ID to EEPROM"); 842 | } 843 | 844 | return *this; 845 | } 846 | 847 | CoogleIOT& CoogleIOT::setMQTTHostname(String s) 848 | { 849 | if(s.length() > COOGLEIOT_MQTT_HOST_MAXLEN) { 850 | warn("Attempted to write beyond max length for MQTT Hostname"); 851 | return *this; 852 | } 853 | 854 | if(!eeprom.writeString(COOGLEIOT_MQTT_HOST_ADDR, s)) { 855 | error("Failed to write MQTT Hostname to EEPROM"); 856 | } 857 | 858 | return *this; 859 | } 860 | 861 | CoogleIOT& CoogleIOT::setMQTTUsername(String s) 862 | { 863 | if(s.length() > COOGLEIOT_MQTT_USER_MAXLEN) { 864 | warn("Attempted to write beyond max length for MQTT Username"); 865 | return *this; 866 | } 867 | 868 | if(!eeprom.writeString(COOGLEIOT_MQTT_USER_ADDR, s)) { 869 | error("Failed to write MQTT Username to EEPROM"); 870 | } 871 | 872 | return *this; 873 | } 874 | 875 | CoogleIOT& CoogleIOT::setMQTTPassword(String s) 876 | { 877 | if(s.length() > COOGLEIOT_MQTT_USER_PASSWORD_MAXLEN) { 878 | warn("Attempted to write beyond max length for MQTT Password"); 879 | return *this; 880 | } 881 | 882 | if(!eeprom.writeString(COOGLEIOT_MQTT_USER_PASSWORD_ADDR, s)) { 883 | error("Failed to write MQTT Password to EEPROM"); 884 | } 885 | 886 | return *this; 887 | } 888 | 889 | CoogleIOT& CoogleIOT::setMQTTLWTTopic(String s) 890 | { 891 | if(s.length() > COOGLEIOT_MQTT_LWT_TOPIC_MAXLEN) { 892 | warn("Attempted to write beyond max length for MQTT Last Will Topic"); 893 | return *this; 894 | } 895 | 896 | if(!eeprom.writeString(COOGLEIOT_MQTT_LWT_TOPIC_ADDR, s)) { 897 | error("Failed to write MQTT Last Will Topic to EEPROM"); 898 | } 899 | Serial.print("Setting MQTT TOPIC: "); 900 | Serial.println(s); 901 | return *this; 902 | } 903 | 904 | CoogleIOT& CoogleIOT::setMQTTLWTMessage(String s) 905 | { 906 | if(s.length() > COOGLEIOT_MQTT_LWT_MESSAGE_MAXLEN) { 907 | warn("Attempted to write beyond max length for MQTT Last Will Message"); 908 | return *this; 909 | } 910 | 911 | if(!eeprom.writeString(COOGLEIOT_MQTT_LWT_MESSAGE_ADDR, s)) { 912 | error("Failed to write MQTT Last Will Message to EEPROM"); 913 | } 914 | 915 | return *this; 916 | } 917 | 918 | CoogleIOT& CoogleIOT::setRemoteAPName(String s) 919 | { 920 | if(s.length() > COOGLEIOT_REMOTE_AP_NAME_MAXLEN) { 921 | warn("Attempted to write beyond max length for Remote AP name"); 922 | return *this; 923 | } 924 | 925 | if(!eeprom.writeString(COOGLEIOT_REMOTE_AP_NAME_ADDR, s)) { 926 | error("Failed to write Remote AP name to EEPROM"); 927 | } 928 | 929 | return *this; 930 | } 931 | 932 | CoogleIOT& CoogleIOT::setRemoteAPPassword(String s) 933 | { 934 | if(s.length() > COOGLEIOT_REMOTE_AP_PASSWORD_MAXLEN) { 935 | warn("Attempted to write beyond max length for Remote AP Password"); 936 | return *this; 937 | } 938 | 939 | if(!eeprom.writeString(COOGLEIOT_REMOTE_AP_PASSWORD_ADDR, s)) { 940 | error("Failed to write Remote AP Password to EEPROM"); 941 | } 942 | 943 | return *this; 944 | } 945 | 946 | CoogleIOT& CoogleIOT::setAPName(String s) 947 | { 948 | if(s.length() > COOGLEIOT_AP_NAME_MAXLEN) { 949 | warn("Attempted to write beyond max length for AP Name"); 950 | return *this; 951 | } 952 | 953 | if(!eeprom.writeString(COOGLEIOT_AP_NAME_ADDR, s)) { 954 | error("Failed to write AP Name to EEPROM"); 955 | } 956 | 957 | return *this; 958 | } 959 | 960 | void CoogleIOT::checkForFirmwareUpdate() 961 | { 962 | String firmwareUrl; 963 | LUrlParser::clParseURL URL; 964 | int port; 965 | 966 | firmwareUrl = getFirmwareUpdateUrl(); 967 | 968 | if(firmwareUrl.length() == 0) { 969 | return; 970 | } 971 | 972 | info("Checking for Firmware Updates"); 973 | 974 | os_intr_lock(); 975 | 976 | URL = LUrlParser::clParseURL::ParseURL(firmwareUrl.c_str()); 977 | 978 | if(!URL.IsValid()) { 979 | os_intr_unlock(); 980 | return; 981 | } 982 | 983 | if(!URL.GetPort(&port)) { 984 | port = 80; 985 | } 986 | 987 | firmwareUpdateStatus = ESPhttpUpdate.update(URL.m_Host.c_str(), port, URL.m_Path.c_str(), COOGLEIOT_VERSION); 988 | 989 | os_intr_unlock(); 990 | } 991 | 992 | CoogleIOT& CoogleIOT::setAPPassword(String s) 993 | { 994 | if(s.length() > COOGLEIOT_AP_PASSWORD_MAXLEN) { 995 | warn("Attempted to write beyond max length for AP Password"); 996 | return *this; 997 | } 998 | 999 | if(!eeprom.writeString(COOGLEIOT_AP_PASSWORD_ADDR, s)) { 1000 | error("Failed to write AP Password to EEPROM"); 1001 | } 1002 | 1003 | return *this; 1004 | } 1005 | 1006 | bool CoogleIOT::initializeMQTT() 1007 | { 1008 | String mqttHostname, mqttClientId, mqttUsername, mqttPassword; 1009 | int mqttPort; 1010 | 1011 | flashStatus(COOGLEIOT_STATUS_MQTT_INIT); 1012 | 1013 | mqttHostname = getMQTTHostname(); 1014 | 1015 | if(mqttHostname.length() == 0) { 1016 | info("No MQTT Hostname specified. Cannot Initialize MQTT"); 1017 | mqttClientActive = false; 1018 | return false; 1019 | } 1020 | 1021 | mqttClientId = getMQTTClientId(); 1022 | 1023 | if(mqttClientId.length() == 0) { 1024 | info("Setting to default MQTT Client ID: " COOGLEIOT_DEFAULT_MQTT_CLIENT_ID); 1025 | 1026 | mqttClientId = COOGLEIOT_DEFAULT_MQTT_CLIENT_ID; 1027 | 1028 | setMQTTClientId(mqttClientId); 1029 | } 1030 | 1031 | mqttPort = getMQTTPort(); 1032 | 1033 | if(mqttPort == 0) { 1034 | info("Setting to default MQTT Port"); 1035 | setMQTTPort(COOGLEIOT_DEFAULT_MQTT_PORT); 1036 | } 1037 | 1038 | mqttClient = new PubSubClient(espClient); 1039 | mqttClient->setServer(mqttHostname.c_str(), mqttPort); 1040 | 1041 | return connectToMQTT(); 1042 | } 1043 | 1044 | PubSubClient* CoogleIOT::getMQTTClient() 1045 | { 1046 | return mqttClient; 1047 | } 1048 | 1049 | bool CoogleIOT::connectToMQTT() 1050 | { 1051 | bool connectResult; 1052 | String mqttHostname, mqttUsername, mqttPassword, mqttClientId, mqttLWTTopic, mqttLWTMessage; 1053 | int mqttPort; 1054 | 1055 | if(mqttClient->connected()) { 1056 | mqttClientActive = true; 1057 | return true; 1058 | } 1059 | 1060 | if(WiFi.status() != WL_CONNECTED) { 1061 | info("Cannot connect to MQTT because there is no WiFi Connection"); 1062 | mqttClientActive = false; 1063 | return false; 1064 | } 1065 | 1066 | mqttHostname = getMQTTHostname(); 1067 | mqttUsername = getMQTTUsername(); 1068 | mqttPassword = getMQTTPassword(); 1069 | mqttPort = getMQTTPort(); 1070 | mqttClientId = getMQTTClientId(); 1071 | mqttLWTTopic = getMQTTLWTTopic(); 1072 | mqttLWTMessage = getMQTTLWTMessage(); 1073 | 1074 | if(mqttHostname.length() == 0) { 1075 | mqttClientActive = false; 1076 | return false; 1077 | } 1078 | 1079 | info("Attempting to Connect to MQTT Server"); 1080 | 1081 | mqttClient->setServer(mqttHostname.c_str(), mqttPort); 1082 | 1083 | logPrintf(DEBUG, "Host: %s : %d", mqttHostname.c_str(), mqttPort); 1084 | 1085 | if(mqttUsername.length() == 0) { 1086 | if(mqttLWTTopic.length() == 0) { 1087 | connectResult = mqttClient->connect(mqttClientId.c_str()); 1088 | } else { 1089 | connectResult = mqttClient->connect(mqttClientId.c_str(), mqttLWTTopic.c_str(), 0, true, mqttLWTMessage.c_str()); 1090 | } 1091 | } else { 1092 | if(mqttLWTTopic.length() == 0) { 1093 | connectResult = mqttClient->connect(mqttClientId.c_str(), mqttUsername.c_str(), mqttPassword.c_str()); 1094 | } else { 1095 | connectResult = mqttClient->connect(mqttClientId.c_str(), mqttUsername.c_str(), mqttPassword.c_str(), mqttLWTTopic.c_str(), 0, true, mqttLWTMessage.c_str()); 1096 | } 1097 | } 1098 | 1099 | if(!mqttClient->connected()) { 1100 | 1101 | switch(mqttClient->state()) { 1102 | 1103 | case MQTT_CONNECTION_TIMEOUT: 1104 | error("MQTT Failure: Connection Timeout (server didn't respond within keepalive time)"); 1105 | break; 1106 | case MQTT_CONNECTION_LOST: 1107 | error("MQTT Failure: Connection Lost (the network connection was broken)"); 1108 | break; 1109 | case MQTT_CONNECT_FAILED: 1110 | error("MQTT Failure: Connection Failed (the network connection failed)"); 1111 | break; 1112 | case MQTT_DISCONNECTED: 1113 | error("MQTT Failure: Disconnected (the client is disconnected)"); 1114 | break; 1115 | case MQTT_CONNECTED: 1116 | error("MQTT reported as not connected, but state says it is!"); 1117 | break; 1118 | case MQTT_CONNECT_BAD_PROTOCOL: 1119 | error("MQTT Failure: Bad Protocol (the server doesn't support the requested version of MQTT)"); 1120 | break; 1121 | case MQTT_CONNECT_BAD_CLIENT_ID: 1122 | error("MQTT Failure: Bad Client ID (the server rejected the client identifier)"); 1123 | break; 1124 | case MQTT_CONNECT_UNAVAILABLE: 1125 | error("MQTT Failure: Unavailable (the server was unable to accept the connection)"); 1126 | break; 1127 | case MQTT_CONNECT_BAD_CREDENTIALS: 1128 | error("MQTT Failure: Bad Credentials (the username/password were rejected)"); 1129 | break; 1130 | case MQTT_CONNECT_UNAUTHORIZED: 1131 | error("MQTT Failure: Unauthorized (the client was not authorized to connect)"); 1132 | break; 1133 | default: 1134 | error("MQTT Failure: Unknown Error"); 1135 | break; 1136 | } 1137 | 1138 | error("Failed to connect to MQTT Server!"); 1139 | mqttClientActive = false; 1140 | return false; 1141 | } 1142 | 1143 | info("MQTT Client Initialized"); 1144 | 1145 | mqttClientActive = true; 1146 | 1147 | return true; 1148 | } 1149 | 1150 | String CoogleIOT::getAPName() 1151 | { 1152 | char APName[COOGLEIOT_AP_NAME_MAXLEN]; 1153 | 1154 | if(!eeprom.readString(COOGLEIOT_AP_NAME_ADDR, APName, COOGLEIOT_AP_NAME_MAXLEN)) { 1155 | error("Failed to read AP name from EEPROM"); 1156 | } 1157 | 1158 | String retval(APName); 1159 | return filterAscii(retval); 1160 | } 1161 | 1162 | String CoogleIOT::getAPPassword() 1163 | { 1164 | char password[COOGLEIOT_AP_PASSWORD_MAXLEN]; 1165 | 1166 | if(!eeprom.readString(COOGLEIOT_AP_PASSWORD_ADDR, password, COOGLEIOT_AP_PASSWORD_MAXLEN)) { 1167 | error("Failed to read AP Password from EEPROM"); 1168 | } 1169 | 1170 | String retval(password); 1171 | return filterAscii(retval); 1172 | } 1173 | 1174 | String CoogleIOT::filterAscii(String s) 1175 | { 1176 | String retval; 1177 | 1178 | for(int i = 0; i < s.length(); i++) { 1179 | 1180 | if(isascii(s.charAt(i))) { 1181 | retval += s.charAt(i); 1182 | } 1183 | } 1184 | 1185 | return retval; 1186 | } 1187 | 1188 | String CoogleIOT::getRemoteAPName() 1189 | { 1190 | char remoteAPName[COOGLEIOT_AP_NAME_MAXLEN]; 1191 | 1192 | if(!eeprom.readString(COOGLEIOT_REMOTE_AP_NAME_ADDR, remoteAPName, COOGLEIOT_REMOTE_AP_NAME_MAXLEN)) { 1193 | error("Failed to read Remote AP Name from EEPROM"); 1194 | remoteAPName[0] = 0; 1195 | } 1196 | 1197 | String retval(remoteAPName); 1198 | 1199 | return filterAscii(retval); 1200 | } 1201 | 1202 | String CoogleIOT::getRemoteAPPassword() 1203 | { 1204 | char remoteAPPassword[COOGLEIOT_REMOTE_AP_PASSWORD_MAXLEN]; 1205 | 1206 | if(!eeprom.readString(COOGLEIOT_REMOTE_AP_PASSWORD_ADDR, remoteAPPassword, COOGLEIOT_REMOTE_AP_PASSWORD_MAXLEN)) { 1207 | error("Failed to read remote AP Password from EEPROM"); 1208 | } 1209 | 1210 | String retval(remoteAPPassword); 1211 | return filterAscii(retval); 1212 | } 1213 | 1214 | bool CoogleIOT::connectToSSID() 1215 | { 1216 | String remoteAPName; 1217 | String remoteAPPassword; 1218 | String localAPName; 1219 | 1220 | flashStatus(COOGLEIOT_STATUS_WIFI_INIT); 1221 | 1222 | remoteAPName = getRemoteAPName(); 1223 | remoteAPPassword = getRemoteAPPassword(); 1224 | 1225 | if(remoteAPName.length() == 0) { 1226 | info("Cannot connect WiFi client, no remote AP specified"); 1227 | return false; 1228 | } 1229 | 1230 | info("Connecting to remote AP"); 1231 | 1232 | if(remoteAPPassword.length() == 0) { 1233 | warn("No Remote AP Password Specified!"); 1234 | 1235 | WiFi.begin(remoteAPName.c_str(), NULL, 0, NULL, true); 1236 | 1237 | } else { 1238 | 1239 | WiFi.begin(remoteAPName.c_str(), remoteAPPassword.c_str(), 0, NULL, true); 1240 | 1241 | } 1242 | 1243 | for(int i = 0; (i < 50) && (WiFi.status() != WL_CONNECTED) && (WiFi.status() != WL_CONNECT_FAILED); i++) { 1244 | delay(500); 1245 | } 1246 | 1247 | if(WiFi.status() != WL_CONNECTED) { 1248 | error("Could not connect to Access Point!"); 1249 | flashSOS(); 1250 | 1251 | return false; 1252 | } 1253 | 1254 | info("Connected to Remote Access Point!"); 1255 | info("Our IP Address is:"); 1256 | info(WiFi.localIP().toString()); 1257 | 1258 | return true; 1259 | } 1260 | 1261 | CoogleIOT& CoogleIOT::enableSerial() 1262 | { 1263 | return enableSerial(115200); 1264 | } 1265 | 1266 | CoogleIOT& CoogleIOT::enableSerial(int baud) 1267 | { 1268 | if(!Serial) { 1269 | 1270 | Serial.begin(baud); 1271 | 1272 | while(!Serial) { 1273 | yield(); 1274 | } 1275 | 1276 | } 1277 | 1278 | _serial = true; 1279 | return *this; 1280 | } 1281 | 1282 | CoogleIOT& CoogleIOT::enableSerial(int baud, SerialConfig config) 1283 | { 1284 | if(!Serial) { 1285 | 1286 | Serial.begin(baud, config); 1287 | 1288 | while(!Serial) { 1289 | yield(); 1290 | } 1291 | 1292 | } 1293 | 1294 | _serial = true; 1295 | return *this; 1296 | } 1297 | 1298 | CoogleIOT& CoogleIOT::enableSerial(int baud, SerialConfig config, SerialMode mode) 1299 | { 1300 | if(!Serial) { 1301 | 1302 | Serial.begin(baud, config, mode); 1303 | 1304 | while(!Serial) { 1305 | yield(); 1306 | } 1307 | 1308 | } 1309 | 1310 | _serial = true; 1311 | return *this; 1312 | } 1313 | -------------------------------------------------------------------------------- /src/CoogleIOT.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | CoogleIOT for ESP8266 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 2017-2018 John Coggeshall | 6 | +----------------------------------------------------------------------+ 7 | | Licensed under the Apache License, Version 2.0 (the "License"); | 8 | | you may not use this file except in compliance with the License. You | 9 | | may obtain a copy of the License at: | 10 | | | 11 | | http://www.apache.org/licenses/LICENSE-2.0 | 12 | | | 13 | | Unless required by applicable law or agreed to in writing, software | 14 | | distributed under the License is distributed on an "AS IS" BASIS, | 15 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | 16 | | implied. See the License for the specific language governing | 17 | | permissions and limitations under the License. | 18 | +----------------------------------------------------------------------+ 19 | | Authors: John Coggeshall | 20 | +----------------------------------------------------------------------+ 21 | */ 22 | 23 | #ifndef COOGLEIOT_H 24 | #define COOGLEIOT_H 25 | 26 | #include "EEPROM_map.h" 27 | #include "CoogleIOTConfig.h" 28 | #include 29 | 30 | #include "Arduino.h" 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #ifndef ARDUINO_ESP8266_ESP01 42 | #include "DNSServer/DNSServer.h" 43 | #endif 44 | 45 | #include "LUrlParser/LUrlParser.h" 46 | 47 | #include "CoogleEEPROM.h" 48 | #include "CoogleIOTWebserver.h" 49 | 50 | #include 51 | 52 | typedef enum { 53 | DEBUG, 54 | INFO, 55 | WARNING, 56 | ERROR, 57 | CRITICAL 58 | } CoogleIOT_LogSeverity; 59 | 60 | typedef void (*sketchtimer_cb_t)(); 61 | 62 | extern "C" void __coogle_iot_firmware_timer_callback(void *); 63 | extern "C" void __coogle_iot_heartbeat_timer_callback(void *); 64 | extern "C" void __coogle_iot_sketch_timer_callback(void *); 65 | 66 | class CoogleIOTWebserver; 67 | 68 | class CoogleIOT 69 | { 70 | public: 71 | bool firmwareUpdateTick = false; 72 | bool heartbeatTick = false; 73 | bool sketchTimerTick = false; 74 | bool _restarting = false; 75 | 76 | CoogleIOT(int); 77 | CoogleIOT(); 78 | ~CoogleIOT(); 79 | void loop(); 80 | bool initialize(); 81 | CoogleIOT& enableSerial(int, SerialConfig, SerialMode); 82 | CoogleIOT& enableSerial(int, SerialConfig); 83 | CoogleIOT& enableSerial(int); 84 | CoogleIOT& enableSerial(); 85 | PubSubClient* getMQTTClient(); 86 | bool serialEnabled(); 87 | CoogleIOT& flashStatus(int); 88 | CoogleIOT& flashStatus(int, int); 89 | CoogleIOT& flashSOS(); 90 | CoogleIOT& resetEEProm(); 91 | void restartDevice(); 92 | 93 | String getRemoteAPName(); 94 | String getRemoteAPPassword(); 95 | String getMQTTHostname(); 96 | String getMQTTUsername(); 97 | String getMQTTPassword(); 98 | String getMQTTClientId(); 99 | String getMQTTLWTTopic(); 100 | String getMQTTLWTMessage(); 101 | String getAPName(); 102 | String getAPPassword(); 103 | 104 | String filterAscii(String); 105 | int getMQTTPort(); 106 | String getFirmwareUpdateUrl(); 107 | String getWiFiStatus(); 108 | String getTimestampAsString(); 109 | 110 | bool verifyFlashConfiguration(); 111 | 112 | CoogleIOT& setMQTTPort(int); 113 | CoogleIOT& setMQTTHostname(String); 114 | CoogleIOT& setMQTTUsername(String); 115 | CoogleIOT& setMQTTPassword(String); 116 | CoogleIOT& setMQTTLWTTopic(String); 117 | CoogleIOT& setMQTTLWTMessage(String); 118 | CoogleIOT& setRemoteAPName(String); 119 | CoogleIOT& setRemoteAPPassword(String); 120 | CoogleIOT& setMQTTClientId(String); 121 | CoogleIOT& setAPName(String); 122 | CoogleIOT& setAPPassword(String); 123 | CoogleIOT& setFirmwareUpdateUrl(String); 124 | CoogleIOT& syncNTPTime(int, int); 125 | 126 | CoogleIOT& warn(String); 127 | CoogleIOT& error(String); 128 | CoogleIOT& critical(String); 129 | CoogleIOT& log(String, CoogleIOT_LogSeverity); 130 | CoogleIOT& logPrintf(CoogleIOT_LogSeverity, const char *format, ...); 131 | CoogleIOT& debug(String); 132 | CoogleIOT& info(String); 133 | 134 | CoogleIOT& registerTimer(int, sketchtimer_cb_t); 135 | 136 | String buildLogMsg(String, CoogleIOT_LogSeverity); 137 | String getLogs(bool); 138 | String getLogs(); 139 | File& getLogFile(); 140 | 141 | bool mqttActive(); 142 | bool dnsActive(); 143 | bool ntpActive(); 144 | bool firmwareClientActive(); 145 | bool apStatus(); 146 | 147 | void checkForFirmwareUpdate(); 148 | 149 | private: 150 | 151 | bool _serial; 152 | int _statusPin; 153 | 154 | HTTPUpdateResult firmwareUpdateStatus; 155 | time_t now; 156 | 157 | #ifndef ARDUINO_ESP8266_ESP01 158 | DNSServer dnsServer; 159 | #endif 160 | 161 | WiFiClient espClient; 162 | PubSubClient *mqttClient; 163 | CoogleEEProm eeprom; 164 | CoogleIOTWebserver *webServer; 165 | File logFile; 166 | 167 | os_timer_t firmwareUpdateTimer; 168 | os_timer_t heartbeatTimer; 169 | os_timer_t sketchTimer; 170 | 171 | int sketchTimerInterval = 0; 172 | sketchtimer_cb_t sketchTimerCallback; 173 | 174 | int wifiFailuresCount; 175 | int mqttFailuresCount; 176 | 177 | bool mqttClientActive = false; 178 | bool dnsServerActive = false; 179 | bool ntpClientActive = false; 180 | bool _firmwareClientActive = false; 181 | bool _apStatus = false; 182 | 183 | void initializeLocalAP(); 184 | void enableConfigurationMode(); 185 | bool connectToSSID(); 186 | bool initializeMQTT(); 187 | bool connectToMQTT(); 188 | }; 189 | 190 | #endif 191 | -------------------------------------------------------------------------------- /src/CoogleIOTConfig.h: -------------------------------------------------------------------------------- 1 | #ifndef COOGLEIOT_CONFIG_H 2 | #define COOGLEIOT_CONFIG_H 3 | 4 | //#define COOGLEIOT_DEBUG 5 | 6 | #define COOGLEIOT_VERSION "1.3.1" 7 | 8 | #ifndef COOGLEIOT_SPIFFS_LOGFILE 9 | #define COOGLEIOT_SPIFFS_LOGFILE "/coogleiot-log.txt" 10 | #endif 11 | 12 | #ifndef COOGLEIOT_LOGFILE_MAXSIZE 13 | #define COOGLEIOT_LOGFILE_MAXSIZE 32768 // 32k 14 | #endif 15 | 16 | #ifndef COOGLEIOT_STATUS_INIT 17 | #define COOGLEIOT_STATUS_INIT 500 18 | #endif 19 | 20 | #ifndef COOGLEIOT_STATUS_WIFI_INIT 21 | #define COOGLEIOT_STATUS_WIFI_INIT 250 22 | #endif 23 | 24 | #ifndef COOGLEIOT_STATUS_MQTT_INIT 25 | #define COOGLEIOT_STATUS_MQTT_INIT 100 26 | #endif 27 | 28 | #define COOGLEIOT_MAGIC_BYTES "ciot" 29 | 30 | #ifndef COOGLEIOT_AP 31 | #define COOGLEIOT_AP "COOGLEIOT_" 32 | #endif 33 | 34 | #ifndef COOGLEIOT_AP_DEFAULT_PASSWORD 35 | #define COOGLEIOT_AP_DEFAULT_PASSWORD "coogleiot" 36 | #endif 37 | 38 | #ifndef COOGLEIOT_DEFAULT_MQTT_CLIENT_ID 39 | #define COOGLEIOT_DEFAULT_MQTT_CLIENT_ID "coogleIoT" 40 | #endif 41 | 42 | #ifndef COOGLEIOT_DEFAULT_MQTT_PORT 43 | #define COOGLEIOT_DEFAULT_MQTT_PORT 1883 44 | #endif 45 | 46 | #ifndef COOGLEIOT_TIMEZONE_OFFSET 47 | #define COOGLEIOT_TIMEZONE_OFFSET ((3600 * 5) * -1) // Default Timezone is -5 UTC (America/New York) 48 | #endif 49 | 50 | #ifndef COOGLEIOT_DAYLIGHT_OFFSET 51 | #define COOGLEIOT_DAYLIGHT_OFFSET 0 // seconds 52 | #endif 53 | 54 | #ifndef COOGLEIOT_NTP_SERVER_1 55 | #define COOGLEIOT_NTP_SERVER_1 "pool.ntp.org" 56 | #endif 57 | 58 | #ifndef COOGLEIOT_NTP_SERVER_2 59 | #define COOGLEIOT_NTP_SERVER_2 "time.nist.gov" 60 | #endif 61 | 62 | #ifndef COOGLEIOT_NTP_SERVER_3 63 | #define COOGLEIOT_NTP_SERVER_3 "time.google.com" 64 | #endif 65 | 66 | #ifndef COOGLEIOT_HEARTBEAT_MS 67 | #define COOGLEIOT_HEARTBEAT_MS 30000 68 | #endif 69 | 70 | #ifndef COOGLEIOT_FIRMWARE_UPDATE_CHECK_MS 71 | #define COOGLEIOT_FIRMWARE_UPDATE_CHECK_MS 54000000 // 15 Minutes in Milliseconds 72 | #endif 73 | 74 | #ifndef COOGLEIOT_DNS_PORT 75 | #define COOGLEIOT_DNS_PORT 53 76 | #endif 77 | 78 | #ifndef COOGLEIOT_EEPROM_EEPROM_SIZE 79 | #define COOGLE_EEPROM_EEPROM_SIZE 1024 80 | #endif 81 | 82 | #ifndef COOGLEIOT_WEBSERVER_PORT 83 | #define COOGLEIOT_WEBSERVER_PORT 80 84 | #endif 85 | 86 | #ifndef COOGLEIOT_MAX_WIFI_ATTEMPTS 87 | #define COOGLEIOT_MAX_WIFI_ATTEMPTS 10 88 | #endif 89 | 90 | #ifndef COOGLEIOT_MAX_MQTT_ATTEMPTS 91 | #define COOGLEIOT_MAX_MQTT_ATTEMPTS 10 92 | #endif 93 | 94 | #ifndef COOGLEIOT_DEVICE_TOPIC 95 | #define COOGLEIOT_DEVICE_TOPIC "/coogleiot/devices" 96 | #endif 97 | 98 | #ifdef COOGLEIOT_DEBUG 99 | #define COOGLEEEPROM_DEBUG 100 | #endif 101 | 102 | #endif 103 | -------------------------------------------------------------------------------- /src/CoogleIOTWebserver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | CoogleIOT for ESP8266 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 2017-2018 John Coggeshall | 6 | +----------------------------------------------------------------------+ 7 | | Licensed under the Apache License, Version 2.0 (the "License"); | 8 | | you may not use this file except in compliance with the License. You | 9 | | may obtain a copy of the License at: | 10 | | | 11 | | http://www.apache.org/licenses/LICENSE-2.0 | 12 | | | 13 | | Unless required by applicable law or agreed to in writing, software | 14 | | distributed under the License is distributed on an "AS IS" BASIS, | 15 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | 16 | | implied. See the License for the specific language governing | 17 | | permissions and limitations under the License. | 18 | +----------------------------------------------------------------------+ 19 | | Authors: John Coggeshall | 20 | +----------------------------------------------------------------------+ 21 | */ 22 | 23 | #include 24 | #include 25 | 26 | #ifdef COOGLEIOT_DEBUG 27 | 28 | #define MDNS_DEBUG_RX 29 | #define MDNS_DEBUG_TX 30 | #define MDNS_DEBUG_ERR 31 | 32 | #endif 33 | 34 | CoogleIOTWebserver::CoogleIOTWebserver(CoogleIOT &_iot) 35 | { 36 | setIOT(_iot); 37 | 38 | this->serverPort = 80; 39 | 40 | iot->info("Creating Configuration Web Server"); 41 | 42 | setWebserver(new ESP8266WebServer(this->serverPort)); 43 | } 44 | 45 | CoogleIOTWebserver::CoogleIOTWebserver(CoogleIOT& _iot, int port) 46 | { 47 | setIOT(_iot); 48 | 49 | this->serverPort = port; 50 | 51 | iot->info("Creating Configuration Web Server"); 52 | setWebserver(new ESP8266WebServer(this->serverPort)); 53 | } 54 | 55 | CoogleIOTWebserver::~CoogleIOTWebserver() 56 | { 57 | delete webServer; 58 | } 59 | 60 | CoogleIOTWebserver& CoogleIOTWebserver::initializePages() 61 | { 62 | webServer->on("/", std::bind(&CoogleIOTWebserver::handleRoot, this)); 63 | webServer->on("/css", std::bind(&CoogleIOTWebserver::handleCSS, this)); 64 | webServer->on("/reset", std::bind(&CoogleIOTWebserver::handleReset, this)); 65 | webServer->on("/restart", std::bind(&CoogleIOTWebserver::handleRestart, this)); 66 | webServer->on("/jquery", std::bind(&CoogleIOTWebserver::handleJS, this)); 67 | webServer->on("/logs", std::bind(&CoogleIOTWebserver::handleLogs, this)); 68 | 69 | webServer->on("/api/status", std::bind(&CoogleIOTWebserver::handleApiStatus, this)); 70 | webServer->on("/api/reset", std::bind(&CoogleIOTWebserver::handleApiReset, this)); 71 | webServer->on("/api/restart", std::bind(&CoogleIOTWebserver::handleApiRestart, this)); 72 | webServer->on("/api/save", std::bind(&CoogleIOTWebserver::handleSubmit, this)); 73 | 74 | webServer->on("/firmware-upload", 75 | HTTP_POST, 76 | std::bind(&CoogleIOTWebserver::handleFirmwareUploadResponse, this), 77 | std::bind(&CoogleIOTWebserver::handleFirmwareUpload, this) 78 | ); 79 | 80 | webServer->onNotFound(std::bind(&CoogleIOTWebserver::handle404, this)); 81 | 82 | return *this; 83 | } 84 | 85 | CoogleIOTWebserver& CoogleIOTWebserver::setServerPort(int port) 86 | { 87 | this->serverPort = port; 88 | return *this; 89 | } 90 | 91 | CoogleIOTWebserver& CoogleIOTWebserver::setWebserver(ESP8266WebServer* server) 92 | { 93 | this->webServer = server; 94 | return *this; 95 | } 96 | 97 | CoogleIOTWebserver& CoogleIOTWebserver::setIOT(CoogleIOT& _iot) 98 | { 99 | this->iot = &_iot; 100 | return *this; 101 | } 102 | 103 | bool CoogleIOTWebserver::initialize() 104 | { 105 | iot->info("Initializing Webserver"); 106 | 107 | initializePages(); 108 | webServer->begin(); 109 | 110 | iot->info("Webserver Initiailized!"); 111 | 112 | return true; 113 | } 114 | 115 | void CoogleIOTWebserver::loop() 116 | { 117 | webServer->handleClient(); 118 | } 119 | 120 | String CoogleIOTWebserver::htmlEncode(char *input) 121 | { 122 | String i = String(input); 123 | return htmlEncode(i); 124 | } 125 | 126 | String CoogleIOTWebserver::htmlEncode(String& input) 127 | { 128 | char t; 129 | String retval, escape; 130 | 131 | for(int i = 0; i < input.length(); i++) { 132 | t = input.charAt(i); 133 | switch(t) { 134 | case '&': 135 | escape = "&"; 136 | break; 137 | 138 | case '<': 139 | escape = "<"; 140 | break; 141 | 142 | case '>': 143 | escape = ">"; 144 | break; 145 | 146 | case '"': 147 | escape = """; 148 | break; 149 | 150 | case '\'': 151 | escape = "'"; 152 | break; 153 | 154 | default: 155 | escape = t; 156 | break; 157 | } 158 | 159 | retval = retval + escape; 160 | } 161 | 162 | return retval; 163 | } 164 | void CoogleIOTWebserver::handleRoot() 165 | { 166 | String page(FPSTR(WEBPAGE_Home)); 167 | String ap_name, ap_password, ap_remote_name, ap_remote_password, 168 | mqtt_host, mqtt_username, mqtt_password, mqtt_client_id, 169 | mqtt_lwt_topic, mqtt_lwt_message, firmware_url, mqtt_port, 170 | local_ip, mac_address, wifi_status, logs; 171 | 172 | ap_name = iot->getAPName(); 173 | ap_password = iot->getAPPassword(); 174 | ap_remote_name = iot->getRemoteAPName(); 175 | ap_remote_password = iot->getRemoteAPPassword(); 176 | mqtt_host = iot->getMQTTHostname(); 177 | mqtt_username = iot->getMQTTUsername(); 178 | mqtt_password = iot->getMQTTPassword(); 179 | mqtt_client_id = iot->getMQTTClientId(); 180 | mqtt_lwt_topic = iot->getMQTTLWTTopic(); 181 | mqtt_lwt_message = iot->getMQTTLWTMessage(); 182 | firmware_url = iot->getFirmwareUpdateUrl(); 183 | mqtt_port = String(iot->getMQTTPort()); 184 | local_ip = WiFi.localIP().toString(); 185 | mac_address = WiFi.macAddress(); 186 | wifi_status = iot->getWiFiStatus(); 187 | 188 | page.replace(F("{{ap_name}}"), htmlEncode(ap_name)); 189 | page.replace(F("{{ap_password}}"), htmlEncode(ap_password)); 190 | page.replace(F("{{remote_ap_name}}"), htmlEncode(ap_remote_name)); 191 | page.replace(F("{{remote_ap_password}}"), htmlEncode(ap_remote_password)); 192 | page.replace(F("{{mqtt_host}}"), htmlEncode(mqtt_host)); 193 | page.replace(F("{{mqtt_username}}"), htmlEncode(mqtt_username)); 194 | page.replace(F("{{mqtt_password}}"), htmlEncode(mqtt_password)); 195 | page.replace(F("{{mqtt_client_id}}"), htmlEncode(mqtt_client_id)); 196 | page.replace(F("{{mqtt_lwt_topic}}"), htmlEncode(mqtt_lwt_topic)); 197 | page.replace(F("{{mqtt_lwt_message}}"), htmlEncode(mqtt_lwt_message)); 198 | page.replace(F("{{firmware_url}}"), htmlEncode(firmware_url)); 199 | page.replace(F("{{mqtt_port}}"), htmlEncode(mqtt_port)); 200 | page.replace(F("{{coogleiot_version}}"), htmlEncode(COOGLEIOT_VERSION)); 201 | page.replace(F("{{coogleiot_buildtime}}"), htmlEncode(__DATE__ " " __TIME__)); 202 | page.replace(F("{{coogleiot_ap_ssid}}"), htmlEncode(ap_name)); 203 | page.replace(F("{{wifi_ip_address}}"), htmlEncode(local_ip)); 204 | page.replace(F("{{mac_address}}"), htmlEncode(mac_address)); 205 | page.replace(F("{{wifi_status}}"), htmlEncode(wifi_status)); 206 | page.replace(F("{{mqtt_status}}"), iot->mqttActive() ? "Active" : "Not Connected"); 207 | page.replace(F("{{ntp_status}}"), iot->ntpActive() ? "Active" : "Not Connected"); 208 | page.replace(F("{{dns_status}}"), iot->dnsActive() ? "Active" : "Disabled"); 209 | page.replace(F("{{firmware_update_status}}"), iot->firmwareClientActive() ? "Active" : "Disabled"); 210 | page.replace(F("{{coogleiot_ap_status}}"), iot->apStatus() ? "Active" : "Disabled"); 211 | 212 | webServer->send(200, "text/html", page.c_str()); 213 | } 214 | 215 | void CoogleIOTWebserver::handleJS() 216 | { 217 | webServer->sendHeader("Content-Encoding", "gzip", true); 218 | webServer->send_P(200, "application/javascript", jquery_3_2_1_min_js_gz, jquery_3_2_1_min_js_gz_len); 219 | } 220 | 221 | void CoogleIOTWebserver::handleCSS() 222 | { 223 | webServer->send_P(200, "text/css", WEBPAGE_CSS, mini_default_min_css_len); 224 | } 225 | 226 | void CoogleIOTWebserver::handle404() 227 | { 228 | webServer->send_P(404, "text/html", WEBPAGE_NOTFOUND); 229 | } 230 | 231 | void CoogleIOTWebserver::handleLogs() 232 | { 233 | File logFile; 234 | 235 | logFile = iot->getLogFile(); 236 | 237 | logFile.seek(0, SeekSet); 238 | webServer->streamFile(logFile, "text/html"); 239 | logFile.seek(0, SeekEnd); 240 | } 241 | 242 | void CoogleIOTWebserver::handleFirmwareUploadResponse() 243 | { 244 | if(_manualFirmwareUpdateSuccess) { 245 | webServer->send_P(200, "text/html", WEBPAGE_Restart); 246 | return; 247 | } 248 | 249 | webServer->send(200, "text/html", "There was an error uploading the firmware"); 250 | 251 | } 252 | 253 | void CoogleIOTWebserver::handleFirmwareUpload() 254 | { 255 | HTTPUpload& upload = webServer->upload(); 256 | uint32_t maxSketchSpace; 257 | 258 | switch(upload.status) { 259 | case UPLOAD_FILE_START: 260 | WiFiUDP::stopAll(); 261 | 262 | iot->info("Receiving Firmware Upload..."); 263 | 264 | maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; 265 | 266 | if(!Update.begin(maxSketchSpace)) { 267 | iot->error("Failed to begin Firmware Upload!"); 268 | 269 | if(iot->serialEnabled()) { 270 | Update.printError(Serial); 271 | } 272 | 273 | _manualFirmwareUpdateSuccess = false; 274 | } 275 | 276 | break; 277 | case UPLOAD_FILE_WRITE: 278 | 279 | if(Update.write(upload.buf, upload.currentSize) != upload.currentSize) { 280 | 281 | iot->error("Failed to write Firmware Upload!"); 282 | 283 | if(iot->serialEnabled()) { 284 | Update.printError(Serial); 285 | } 286 | 287 | _manualFirmwareUpdateSuccess = false; 288 | } 289 | break; 290 | 291 | case UPLOAD_FILE_END: 292 | 293 | if(Update.end(true)) { 294 | 295 | iot->info("Firmware updated!"); 296 | 297 | _manualFirmwareUpdateSuccess = true; 298 | 299 | } else { 300 | iot->error("Failed to update Firmware!"); 301 | 302 | if(iot->serialEnabled()) { 303 | Update.printError(Serial); 304 | } 305 | 306 | _manualFirmwareUpdateSuccess = false; 307 | } 308 | 309 | break; 310 | 311 | case UPLOAD_FILE_ABORTED: 312 | Update.end(); 313 | 314 | iot->info("Firmware upload aborted!"); 315 | 316 | _manualFirmwareUpdateSuccess = false; 317 | 318 | break; 319 | } 320 | 321 | yield(); 322 | } 323 | 324 | void CoogleIOTWebserver::handleSubmit() 325 | { 326 | StaticJsonBuffer<200> jsonBuffer; 327 | WiFiClientPrint<> p(webServer->client()); 328 | 329 | JsonObject& retval = jsonBuffer.createObject(); 330 | JsonArray& errors = retval.createNestedArray("errors"); 331 | 332 | String ap_name, ap_password, remote_ap_name, remote_ap_password, 333 | mqtt_host, mqtt_port, mqtt_username, mqtt_password, mqtt_client_id, 334 | mqtt_lwt_topic, mqtt_lwt_message,firmware_url; 335 | 336 | bool success = true; 337 | 338 | ap_name = webServer->arg("ap_name"); 339 | ap_password = webServer->arg("ap_password"); 340 | remote_ap_name = webServer->arg("remote_ap_name"); 341 | remote_ap_password = webServer->arg("remote_ap_password"); 342 | mqtt_host = webServer->arg("mqtt_host"); 343 | mqtt_port = webServer->arg("mqtt_port"); 344 | mqtt_username = webServer->arg("mqtt_username"); 345 | mqtt_password = webServer->arg("mqtt_password"); 346 | mqtt_client_id = webServer->arg("mqtt_client_id"); 347 | mqtt_lwt_topic = webServer->arg("mqtt_lwt_topic"); 348 | mqtt_lwt_message = webServer->arg("mqtt_lwt_message"); 349 | firmware_url = webServer->arg("firmware_url"); 350 | 351 | if(ap_name.length() > 0) { 352 | if(ap_name.length() < COOGLEIOT_AP_NAME_MAXLEN) { 353 | iot->setAPName(ap_name); 354 | } else { 355 | errors.add("AP Name was too long"); 356 | success = false; 357 | } 358 | } 359 | 360 | if(ap_password.length() < COOGLEIOT_AP_PASSWORD_MAXLEN) { 361 | iot->setAPPassword(ap_password); 362 | } else { 363 | errors.add("AP Password is too long!"); 364 | success = false; 365 | } 366 | 367 | if(remote_ap_name.length() > 0) { 368 | if(remote_ap_name.length() < COOGLEIOT_REMOTE_AP_NAME_MAXLEN) { 369 | iot->setRemoteAPName(remote_ap_name); 370 | } else { 371 | errors.add("Remote AP Name is too long!"); 372 | success = false; 373 | } 374 | } 375 | 376 | if(remote_ap_password.length() < COOGLEIOT_REMOTE_AP_PASSWORD_MAXLEN) { 377 | iot->setRemoteAPPassword(remote_ap_password); 378 | } else { 379 | errors.add("Remote AP Password was too long!"); 380 | success = false; 381 | } 382 | 383 | if(mqtt_host.length() > 0) { 384 | if(mqtt_host.length() < COOGLEIOT_MQTT_HOST_MAXLEN) { 385 | iot->setMQTTHostname(mqtt_host); 386 | } else { 387 | errors.add("The MQTT Hostname was too long!"); 388 | success = false; 389 | } 390 | } 391 | 392 | if(mqtt_port.length() > 0) { 393 | if(mqtt_port.toInt() > 0) { 394 | iot->setMQTTPort(mqtt_port.toInt()); 395 | } else { 396 | errors.add("The MQTT Port was Invalid"); 397 | success = false; 398 | } 399 | } 400 | 401 | if(mqtt_username.length() > 0) { 402 | if(mqtt_username.length() < COOGLEIOT_MQTT_USER_MAXLEN) { 403 | iot->setMQTTUsername(mqtt_username); 404 | } else { 405 | errors.add("The MQTT Username was too long"); 406 | success = false; 407 | } 408 | } 409 | 410 | if(mqtt_password.length() > 0) { 411 | if(mqtt_password.length() < COOGLEIOT_MQTT_USER_PASSWORD_MAXLEN) { 412 | iot->setMQTTPassword(mqtt_password); 413 | } else { 414 | errors.add("The MQTT Password was too long"); 415 | success = false; 416 | } 417 | } 418 | 419 | if(mqtt_client_id.length() > 0) { 420 | if(mqtt_client_id.length() < COOGLEIOT_MQTT_CLIENT_ID_MAXLEN) { 421 | iot->setMQTTClientId(mqtt_client_id); 422 | } else { 423 | errors.add("The MQTT Client ID was too long"); 424 | success = false; 425 | } 426 | } 427 | 428 | if(mqtt_lwt_topic.length() > 0) { 429 | if(mqtt_lwt_topic.length() < COOGLEIOT_MQTT_LWT_TOPIC_MAXLEN) { 430 | iot->setMQTTLWTTopic(mqtt_lwt_topic); 431 | } else { 432 | errors.add("The MQTT LWT topic was too long"); 433 | success = false; 434 | } 435 | } 436 | 437 | if(mqtt_lwt_message.length() > 0) { 438 | if(mqtt_lwt_message.length() < COOGLEIOT_MQTT_LWT_MESSAGE_MAXLEN) { 439 | iot->setMQTTLWTMessage(mqtt_lwt_message); 440 | } else { 441 | errors.add("The MQTT LWT message was too long"); 442 | success = false; 443 | } 444 | } 445 | 446 | if(firmware_url.length() > 0) { 447 | if(firmware_url.length() < COOGLEIOT_FIRMWARE_UPDATE_URL_MAXLEN) { 448 | iot->setFirmwareUpdateUrl(firmware_url); 449 | } else { 450 | errors.add("The Firmware Update URL was too long"); 451 | success = false; 452 | } 453 | } 454 | 455 | retval["status"] = success; 456 | 457 | webServer->setContentLength(retval.measureLength()); 458 | webServer->send(200, "application/json", ""); 459 | 460 | retval.printTo(p); 461 | p.stop(); 462 | 463 | } 464 | 465 | void CoogleIOTWebserver::handleReset() 466 | { 467 | webServer->send_P(200, "text/html", WEBPAGE_Restart); 468 | iot->resetEEProm(); 469 | } 470 | 471 | void CoogleIOTWebserver::handleRestart() 472 | { 473 | webServer->send_P(200, "text/html", WEBPAGE_Restart); 474 | } 475 | 476 | void CoogleIOTWebserver::handleApiReset() 477 | { 478 | iot->resetEEProm(); 479 | } 480 | 481 | void CoogleIOTWebserver::handleApiRestart() 482 | { 483 | iot->restartDevice(); 484 | } 485 | 486 | void CoogleIOTWebserver::handleApiStatus() 487 | { 488 | StaticJsonBuffer<200> jsonBuffer; 489 | WiFiClientPrint<> p(webServer->client()); 490 | 491 | JsonObject& retval = jsonBuffer.createObject(); 492 | 493 | retval["status"] = !iot->_restarting; 494 | 495 | webServer->setContentLength(retval.measureLength()); 496 | webServer->send(200, "application/json", ""); 497 | retval.printTo(p); 498 | p.stop(); 499 | 500 | } 501 | -------------------------------------------------------------------------------- /src/CoogleIOTWebserver.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | CoogleIOT for ESP8266 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 2017-2018 John Coggeshall | 6 | +----------------------------------------------------------------------+ 7 | | Licensed under the Apache License, Version 2.0 (the "License"); | 8 | | you may not use this file except in compliance with the License. You | 9 | | may obtain a copy of the License at: | 10 | | | 11 | | http://www.apache.org/licenses/LICENSE-2.0 | 12 | | | 13 | | Unless required by applicable law or agreed to in writing, software | 14 | | distributed under the License is distributed on an "AS IS" BASIS, | 15 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | 16 | | implied. See the License for the specific language governing | 17 | | permissions and limitations under the License. | 18 | +----------------------------------------------------------------------+ 19 | | Authors: John Coggeshall | 20 | +----------------------------------------------------------------------+ 21 | */ 22 | 23 | #ifndef COOGLEIOT_WEBSERVER_H 24 | #define COOGLEIOT_WEBSERVER_H 25 | 26 | #include 27 | #include 28 | #include "ArduinoJson.h" 29 | #include "WiFiClientPrint.h" 30 | #include "CoogleIOT.h" 31 | #include "DNSServer/DNSServer.h" 32 | #include "CoogleIOTConfig.h" 33 | 34 | #include "webpages/home.h" 35 | #include "webpages/mini_css_default.h" 36 | #include "webpages/jquery-3.2.1.min.h" 37 | #include "webpages/404.h" 38 | #include "webpages/restarting.h" 39 | 40 | class CoogleIOT; 41 | 42 | class CoogleIOTWebserver 43 | { 44 | public: 45 | CoogleIOTWebserver(CoogleIOT& _iot); 46 | CoogleIOTWebserver(CoogleIOT& _iot, int port); 47 | ~CoogleIOTWebserver(); 48 | CoogleIOTWebserver& setIOT(CoogleIOT& _iot); 49 | CoogleIOTWebserver& setWebserver(ESP8266WebServer* server); 50 | CoogleIOTWebserver& setServerPort(int port); 51 | 52 | String htmlEncode(String&); 53 | String htmlEncode(char *); 54 | 55 | bool initialize(); 56 | void handleRoot(); 57 | void handle404(); 58 | void handleCSS(); 59 | void handleJS(); 60 | void handleSubmit(); 61 | void handleReset(); 62 | void handleRestart(); 63 | void handleFirmwareUpload(); 64 | void handleFirmwareUploadResponse(); 65 | void handleLogs(); 66 | 67 | void handleApiStatus(); 68 | void handleApiReset(); 69 | void handleApiRestart(); 70 | 71 | void loop(); 72 | protected: 73 | CoogleIOTWebserver& initializePages(); 74 | private: 75 | ESP8266WebServer* webServer; 76 | CoogleIOT* iot; 77 | bool _manualFirmwareUpdateSuccess = false; 78 | int serverPort = 80; 79 | }; 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /src/DNSServer/DNSServer.cpp: -------------------------------------------------------------------------------- 1 | #include "DNSServer.h" 2 | #include 3 | #include 4 | 5 | DNSServer::DNSServer() 6 | { 7 | _ttl = htonl(60); 8 | _errorReplyCode = DNSReplyCode::NonExistentDomain; 9 | } 10 | 11 | bool DNSServer::start(const uint16_t &port, const String &domainName, 12 | const IPAddress &resolvedIP) 13 | { 14 | _port = port; 15 | _buffer = NULL; 16 | _domainName = domainName; 17 | _resolvedIP[0] = resolvedIP[0]; 18 | _resolvedIP[1] = resolvedIP[1]; 19 | _resolvedIP[2] = resolvedIP[2]; 20 | _resolvedIP[3] = resolvedIP[3]; 21 | downcaseAndRemoveWwwPrefix(_domainName); 22 | return _udp.begin(_port) == 1; 23 | } 24 | 25 | void DNSServer::setErrorReplyCode(const DNSReplyCode &replyCode) 26 | { 27 | _errorReplyCode = replyCode; 28 | } 29 | 30 | void DNSServer::setTTL(const uint32_t &ttl) 31 | { 32 | _ttl = htonl(ttl); 33 | } 34 | 35 | void DNSServer::stop() 36 | { 37 | _udp.stop(); 38 | free(_buffer); 39 | _buffer = NULL; 40 | } 41 | 42 | void DNSServer::downcaseAndRemoveWwwPrefix(String &domainName) 43 | { 44 | domainName.toLowerCase(); 45 | domainName.replace("www.", ""); 46 | } 47 | 48 | void DNSServer::processNextRequest() 49 | { 50 | _currentPacketSize = _udp.parsePacket(); 51 | if (_currentPacketSize) 52 | { 53 | if (_buffer != NULL) free(_buffer); 54 | _buffer = (unsigned char*)malloc(_currentPacketSize * sizeof(char)); 55 | if (_buffer == NULL) return; 56 | _udp.read(_buffer, _currentPacketSize); 57 | _dnsHeader = (DNSHeader*) _buffer; 58 | 59 | if (_dnsHeader->QR == DNS_QR_QUERY && 60 | _dnsHeader->OPCode == DNS_OPCODE_QUERY && 61 | requestIncludesOnlyOneQuestion() && 62 | (_domainName == "*" || getDomainNameWithoutWwwPrefix() == _domainName) 63 | ) 64 | { 65 | replyWithIP(); 66 | } 67 | else if (_dnsHeader->QR == DNS_QR_QUERY) 68 | { 69 | replyWithCustomCode(); 70 | } 71 | 72 | free(_buffer); 73 | _buffer = NULL; 74 | } 75 | } 76 | 77 | bool DNSServer::requestIncludesOnlyOneQuestion() 78 | { 79 | return ntohs(_dnsHeader->QDCount) == 1 && 80 | _dnsHeader->ANCount == 0 && 81 | _dnsHeader->NSCount == 0 && 82 | _dnsHeader->ARCount == 0; 83 | } 84 | 85 | String DNSServer::getDomainNameWithoutWwwPrefix() 86 | { 87 | String parsedDomainName = ""; 88 | if (_buffer == NULL) return parsedDomainName; 89 | unsigned char *start = _buffer + 12; 90 | if (*start == 0) 91 | { 92 | return parsedDomainName; 93 | } 94 | int pos = 0; 95 | while(true) 96 | { 97 | unsigned char labelLength = *(start + pos); 98 | for(int i = 0; i < labelLength; i++) 99 | { 100 | pos++; 101 | parsedDomainName += (char)*(start + pos); 102 | } 103 | pos++; 104 | if (*(start + pos) == 0) 105 | { 106 | downcaseAndRemoveWwwPrefix(parsedDomainName); 107 | return parsedDomainName; 108 | } 109 | else 110 | { 111 | parsedDomainName += "."; 112 | } 113 | } 114 | } 115 | 116 | void DNSServer::replyWithIP() 117 | { 118 | if (_buffer == NULL) return; 119 | _dnsHeader->QR = DNS_QR_RESPONSE; 120 | _dnsHeader->ANCount = _dnsHeader->QDCount; 121 | _dnsHeader->QDCount = _dnsHeader->QDCount; 122 | //_dnsHeader->RA = 1; 123 | 124 | _udp.beginPacket(_udp.remoteIP(), _udp.remotePort()); 125 | _udp.write(_buffer, _currentPacketSize); 126 | 127 | _udp.write((uint8_t)192); // answer name is a pointer 128 | _udp.write((uint8_t)12); // pointer to offset at 0x00c 129 | 130 | _udp.write((uint8_t)0); // 0x0001 answer is type A query (host address) 131 | _udp.write((uint8_t)1); 132 | 133 | _udp.write((uint8_t)0); //0x0001 answer is class IN (internet address) 134 | _udp.write((uint8_t)1); 135 | 136 | _udp.write((unsigned char*)&_ttl, 4); 137 | 138 | // Length of RData is 4 bytes (because, in this case, RData is IPv4) 139 | _udp.write((uint8_t)0); 140 | _udp.write((uint8_t)4); 141 | _udp.write(_resolvedIP, sizeof(_resolvedIP)); 142 | _udp.endPacket(); 143 | 144 | #ifdef DNSSERVER_DEBUG 145 | DEBUG_OUTPUT.print("DNS responds: "); 146 | DEBUG_OUTPUT.print(_resolvedIP[0]); 147 | DEBUG_OUTPUT.print("."); 148 | DEBUG_OUTPUT.print(_resolvedIP[1]); 149 | DEBUG_OUTPUT.print("."); 150 | DEBUG_OUTPUT.print(_resolvedIP[2]); 151 | DEBUG_OUTPUT.print("."); 152 | DEBUG_OUTPUT.print(_resolvedIP[3]); 153 | DEBUG_OUTPUT.print(" for "); 154 | DEBUG_OUTPUT.println(getDomainNameWithoutWwwPrefix()); 155 | #endif 156 | } 157 | 158 | void DNSServer::replyWithCustomCode() 159 | { 160 | if (_buffer == NULL) return; 161 | _dnsHeader->QR = DNS_QR_RESPONSE; 162 | _dnsHeader->RCode = (unsigned char)_errorReplyCode; 163 | _dnsHeader->QDCount = 0; 164 | 165 | _udp.beginPacket(_udp.remoteIP(), _udp.remotePort()); 166 | _udp.write(_buffer, sizeof(DNSHeader)); 167 | _udp.endPacket(); 168 | } -------------------------------------------------------------------------------- /src/DNSServer/DNSServer.h: -------------------------------------------------------------------------------- 1 | #ifndef DNSServer_h 2 | #define DNSServer_h 3 | #include 4 | 5 | #define DNS_QR_QUERY 0 6 | #define DNS_QR_RESPONSE 1 7 | #define DNS_OPCODE_QUERY 0 8 | 9 | #ifdef COOGLEIOT_DEBUG 10 | #define DNSSERVER_DEBUG 11 | #endif 12 | 13 | enum class DNSReplyCode 14 | { 15 | NoError = 0, 16 | FormError = 1, 17 | ServerFailure = 2, 18 | NonExistentDomain = 3, 19 | NotImplemented = 4, 20 | Refused = 5, 21 | YXDomain = 6, 22 | YXRRSet = 7, 23 | NXRRSet = 8 24 | }; 25 | 26 | struct DNSHeader 27 | { 28 | uint16_t ID; // identification number 29 | unsigned char RD : 1; // recursion desired 30 | unsigned char TC : 1; // truncated message 31 | unsigned char AA : 1; // authoritive answer 32 | unsigned char OPCode : 4; // message_type 33 | unsigned char QR : 1; // query/response flag 34 | unsigned char RCode : 4; // response code 35 | unsigned char Z : 3; // its z! reserved 36 | unsigned char RA : 1; // recursion available 37 | uint16_t QDCount; // number of question entries 38 | uint16_t ANCount; // number of answer entries 39 | uint16_t NSCount; // number of authority entries 40 | uint16_t ARCount; // number of resource entries 41 | }; 42 | 43 | class DNSServer 44 | { 45 | public: 46 | DNSServer(); 47 | void processNextRequest(); 48 | void setErrorReplyCode(const DNSReplyCode &replyCode); 49 | void setTTL(const uint32_t &ttl); 50 | 51 | // Returns true if successful, false if there are no sockets available 52 | bool start(const uint16_t &port, 53 | const String &domainName, 54 | const IPAddress &resolvedIP); 55 | // stops the DNS server 56 | void stop(); 57 | 58 | private: 59 | WiFiUDP _udp; 60 | uint16_t _port; 61 | String _domainName; 62 | unsigned char _resolvedIP[4]; 63 | int _currentPacketSize; 64 | unsigned char* _buffer; 65 | DNSHeader* _dnsHeader; 66 | uint32_t _ttl; 67 | DNSReplyCode _errorReplyCode; 68 | 69 | void downcaseAndRemoveWwwPrefix(String &domainName); 70 | String getDomainNameWithoutWwwPrefix(); 71 | bool requestIncludesOnlyOneQuestion(); 72 | void replyWithIP(); 73 | void replyWithCustomCode(); 74 | }; 75 | #endif -------------------------------------------------------------------------------- /src/EEPROM_map.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | CoogleIOT for ESP8266 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 2017-2018 John Coggeshall | 6 | +----------------------------------------------------------------------+ 7 | | Licensed under the Apache License, Version 2.0 (the "License"); | 8 | | you may not use this file except in compliance with the License. You | 9 | | may obtain a copy of the License at: | 10 | | | 11 | | http://www.apache.org/licenses/LICENSE-2.0 | 12 | | | 13 | | Unless required by applicable law or agreed to in writing, software | 14 | | distributed under the License is distributed on an "AS IS" BASIS, | 15 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | 16 | | implied. See the License for the specific language governing | 17 | | permissions and limitations under the License. | 18 | +----------------------------------------------------------------------+ 19 | | Authors: John Coggeshall | 20 | +----------------------------------------------------------------------+ 21 | */ 22 | 23 | #ifndef COOGLEIOT_EEPROM_MAP 24 | #define COOGLEIOT_EEPROM_MAP 25 | 26 | #define COOGLEIOT_AP_PASSWORD_ADDR 5 // 5-21 27 | #define COOGLEIOT_AP_PASSWORD_MAXLEN 16 28 | 29 | #define COOGLEIOT_AP_NAME_ADDR 22 30 | #define COOGLEIOT_AP_NAME_MAXLEN 25 // 22-47 31 | 32 | #define COOGLEIOT_REMOTE_AP_PASSWORD_ADDR 48 33 | #define COOGLEIOT_REMOTE_AP_PASSWORD_MAXLEN 64 // 48-112 34 | 35 | #define COOGLEIOT_MQTT_HOST_ADDR 113 36 | #define COOGLEIOT_MQTT_HOST_MAXLEN 64 // 113 - 177 37 | 38 | #define COOGLEIOT_MQTT_USER_ADDR 178 39 | #define COOGLEIOT_MQTT_USER_MAXLEN 16 // 178 - 194 40 | 41 | #define COOGLEIOT_MQTT_USER_PASSWORD_ADDR 195 42 | #define COOGLEIOT_MQTT_USER_PASSWORD_MAXLEN 24 // 195 - 219 43 | 44 | #define COOGLEIOT_MQTT_CLIENT_ID_ADDR 220 45 | #define COOGLEIOT_MQTT_CLIENT_ID_MAXLEN 32 // 220 - 252 46 | 47 | #define COOGLEIOT_MQTT_PORT_ADDR 253 48 | #define COOGLEIOT_MQTT_PORT_MAXLEN 2 // 253 - 255 (int) 49 | 50 | #define COOGLEIOT_FIRMWARE_UPDATE_URL_ADDR 282 51 | #define COOGLEIOT_FIRMWARE_UPDATE_URL_MAXLEN 255 // 282 - 537 52 | 53 | #define COOGLEIOT_REMOTE_AP_NAME_ADDR 538 54 | #define COOGLEIOT_REMOTE_AP_NAME_MAXLEN 25 // 538- 563 55 | 56 | #define COOGLEIOT_MQTT_LWT_TOPIC_ADDR 564 57 | #define COOGLEIOT_MQTT_LWT_TOPIC_MAXLEN 128 // 564 - 692 58 | 59 | #define COOGLEIOT_MQTT_LWT_MESSAGE_ADDR 693 60 | #define COOGLEIOT_MQTT_LWT_MESSAGE_MAXLEN 128 // 693 - 821 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /src/LUrlParser/LUrlParser.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Lightweight URL & URI parser (RFC 1738, RFC 3986) 3 | * https://github.com/corporateshark/LUrlParser 4 | * 5 | * The MIT License (MIT) 6 | * 7 | * Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com) 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in all 17 | * copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | * SOFTWARE. 26 | */ 27 | 28 | #include "LUrlParser.h" 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | // check if the scheme name is valid 35 | static bool IsSchemeValid( const std::string& SchemeName ) 36 | { 37 | for ( auto c : SchemeName ) 38 | { 39 | if ( !isalpha( c ) && c != '+' && c != '-' && c != '.' ) return false; 40 | } 41 | 42 | return true; 43 | } 44 | 45 | bool LUrlParser::clParseURL::GetPort( int* OutPort ) const 46 | { 47 | if ( !IsValid() ) { return false; } 48 | 49 | int Port = atoi( m_Port.c_str() ); 50 | 51 | if ( Port <= 0 || Port > 65535 ) { return false; } 52 | 53 | if ( OutPort ) { *OutPort = Port; } 54 | 55 | return true; 56 | } 57 | 58 | // based on RFC 1738 and RFC 3986 59 | LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL( const std::string& URL ) 60 | { 61 | LUrlParser::clParseURL Result; 62 | 63 | const char* CurrentString = URL.c_str(); 64 | 65 | /* 66 | * : 67 | * := [a-z\+\-\.]+ 68 | * For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names 69 | */ 70 | 71 | // try to read scheme 72 | { 73 | const char* LocalString = strchr( CurrentString, ':' ); 74 | 75 | if ( !LocalString ) 76 | { 77 | return clParseURL( LUrlParserError_NoUrlCharacter ); 78 | } 79 | 80 | // save the scheme name 81 | Result.m_Scheme = std::string( CurrentString, LocalString - CurrentString ); 82 | 83 | if ( !IsSchemeValid( Result.m_Scheme ) ) 84 | { 85 | return clParseURL( LUrlParserError_InvalidSchemeName ); 86 | } 87 | 88 | // scheme should be lowercase 89 | std::transform( Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower ); 90 | 91 | // skip ':' 92 | CurrentString = LocalString+1; 93 | } 94 | 95 | /* 96 | * //:@:/ 97 | * any ":", "@" and "/" must be normalized 98 | */ 99 | 100 | // skip "//" 101 | if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash ); 102 | if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash ); 103 | 104 | // check if the user name and password are specified 105 | bool bHasUserName = false; 106 | 107 | const char* LocalString = CurrentString; 108 | 109 | while ( *LocalString ) 110 | { 111 | if ( *LocalString == '@' ) 112 | { 113 | // user name and password are specified 114 | bHasUserName = true; 115 | break; 116 | } 117 | else if ( *LocalString == '/' ) 118 | { 119 | // end of : specification 120 | bHasUserName = false; 121 | break; 122 | } 123 | 124 | LocalString++; 125 | } 126 | 127 | // user name and password 128 | LocalString = CurrentString; 129 | 130 | if ( bHasUserName ) 131 | { 132 | // read user name 133 | while ( *LocalString && *LocalString != ':' && *LocalString != '@' ) LocalString++; 134 | 135 | Result.m_UserName = std::string( CurrentString, LocalString - CurrentString ); 136 | 137 | // proceed with the current pointer 138 | CurrentString = LocalString; 139 | 140 | if ( *CurrentString == ':' ) 141 | { 142 | // skip ':' 143 | CurrentString++; 144 | 145 | // read password 146 | LocalString = CurrentString; 147 | 148 | while ( *LocalString && *LocalString != '@' ) LocalString++; 149 | 150 | Result.m_Password = std::string( CurrentString, LocalString - CurrentString ); 151 | 152 | CurrentString = LocalString; 153 | } 154 | 155 | // skip '@' 156 | if ( *CurrentString != '@' ) 157 | { 158 | return clParseURL( LUrlParserError_NoAtSign ); 159 | } 160 | 161 | CurrentString++; 162 | } 163 | 164 | bool bHasBracket = ( *CurrentString == '[' ); 165 | 166 | // go ahead, read the host name 167 | LocalString = CurrentString; 168 | 169 | while ( *LocalString ) 170 | { 171 | if ( bHasBracket && *LocalString == ']' ) 172 | { 173 | // end of IPv6 address 174 | LocalString++; 175 | break; 176 | } 177 | else if ( !bHasBracket && ( *LocalString == ':' || *LocalString == '/' ) ) 178 | { 179 | // port number is specified 180 | break; 181 | } 182 | 183 | LocalString++; 184 | } 185 | 186 | Result.m_Host = std::string( CurrentString, LocalString - CurrentString ); 187 | 188 | CurrentString = LocalString; 189 | 190 | // is port number specified? 191 | if ( *CurrentString == ':' ) 192 | { 193 | CurrentString++; 194 | 195 | // read port number 196 | LocalString = CurrentString; 197 | 198 | while ( *LocalString && *LocalString != '/' ) LocalString++; 199 | 200 | Result.m_Port = std::string( CurrentString, LocalString - CurrentString ); 201 | 202 | CurrentString = LocalString; 203 | } 204 | 205 | // end of string 206 | if ( !*CurrentString ) 207 | { 208 | return clParseURL( LUrlParserError_UnexpectedEndOfLine ); 209 | } 210 | 211 | // skip '/' 212 | if ( *CurrentString != '/' ) 213 | { 214 | return clParseURL( LUrlParserError_NoSlash ); 215 | } 216 | 217 | CurrentString++; 218 | 219 | // parse the path 220 | LocalString = CurrentString; 221 | 222 | while ( *LocalString && *LocalString != '#' && *LocalString != '?' ) LocalString++; 223 | 224 | Result.m_Path = std::string( CurrentString, LocalString - CurrentString ); 225 | 226 | CurrentString = LocalString; 227 | 228 | // check for query 229 | if ( *CurrentString == '?' ) 230 | { 231 | // skip '?' 232 | CurrentString++; 233 | 234 | // read query 235 | LocalString = CurrentString; 236 | 237 | while ( *LocalString && *LocalString != '#' ) LocalString++; 238 | 239 | Result.m_Query = std::string( CurrentString, LocalString - CurrentString ); 240 | 241 | CurrentString = LocalString; 242 | } 243 | 244 | // check for fragment 245 | if ( *CurrentString == '#' ) 246 | { 247 | // skip '#' 248 | CurrentString++; 249 | 250 | // read fragment 251 | LocalString = CurrentString; 252 | 253 | while ( *LocalString ) LocalString++; 254 | 255 | Result.m_Fragment = std::string( CurrentString, LocalString - CurrentString ); 256 | 257 | CurrentString = LocalString; 258 | } 259 | 260 | Result.m_ErrorCode = LUrlParserError_Ok; 261 | 262 | return Result; 263 | } -------------------------------------------------------------------------------- /src/LUrlParser/LUrlParser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Lightweight URL & URI parser (RFC 1738, RFC 3986) 3 | * https://github.com/corporateshark/LUrlParser 4 | * 5 | * The MIT License (MIT) 6 | * 7 | * Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com) 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in all 17 | * copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | * SOFTWARE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #include 31 | 32 | namespace LUrlParser 33 | { 34 | enum LUrlParserError 35 | { 36 | LUrlParserError_Ok = 0, 37 | LUrlParserError_Uninitialized = 1, 38 | LUrlParserError_NoUrlCharacter = 2, 39 | LUrlParserError_InvalidSchemeName = 3, 40 | LUrlParserError_NoDoubleSlash = 4, 41 | LUrlParserError_NoAtSign = 5, 42 | LUrlParserError_UnexpectedEndOfLine = 6, 43 | LUrlParserError_NoSlash = 7, 44 | }; 45 | 46 | class clParseURL 47 | { 48 | public: 49 | LUrlParserError m_ErrorCode; 50 | std::string m_Scheme; 51 | std::string m_Host; 52 | std::string m_Port; 53 | std::string m_Path; 54 | std::string m_Query; 55 | std::string m_Fragment; 56 | std::string m_UserName; 57 | std::string m_Password; 58 | 59 | clParseURL() 60 | : m_ErrorCode( LUrlParserError_Uninitialized ) 61 | {} 62 | 63 | /// return 'true' if the parsing was successful 64 | bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; } 65 | 66 | /// helper to convert the port number to int, return 'true' if the port is valid (within the 0..65535 range) 67 | bool GetPort( int* OutPort ) const; 68 | 69 | /// parse the URL 70 | static clParseURL ParseURL( const std::string& URL ); 71 | 72 | private: 73 | explicit clParseURL( LUrlParserError ErrorCode ) 74 | : m_ErrorCode( ErrorCode ) 75 | {} 76 | }; 77 | 78 | } // namespace LUrlParser -------------------------------------------------------------------------------- /src/WiFiClientPrint.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | template 7 | 8 | class WiFiClientPrint : public Print 9 | { 10 | public: 11 | WiFiClientPrint(WiFiClient client) 12 | : _client(client), 13 | _length(0) 14 | { 15 | } 16 | 17 | ~WiFiClientPrint() 18 | { 19 | #ifdef DEBUG_ESP_PORT 20 | // Note: This is manual expansion of assertion macro 21 | if (_length != 0) { 22 | DEBUG_ESP_PORT.printf("\nassertion failed at " __FILE__ ":%d: " "_length == 0" "\n", __LINE__); 23 | // Note: abort() causes stack dump and restart of the ESP 24 | abort(); 25 | } 26 | #endif 27 | } 28 | 29 | virtual size_t write(uint8_t c) override 30 | { 31 | _buffer[_length++] = c; 32 | if (_length == BUFFER_SIZE) { 33 | flush(); 34 | } 35 | } 36 | 37 | void flush() 38 | { 39 | if (_length != 0) { 40 | _client.write((const uint8_t*)_buffer, _length); 41 | _length = 0; 42 | } 43 | } 44 | 45 | void stop() 46 | { 47 | flush(); 48 | _client.stop(); 49 | } 50 | 51 | private: 52 | WiFiClient _client; 53 | uint8_t _buffer[BUFFER_SIZE]; 54 | size_t _length; 55 | }; -------------------------------------------------------------------------------- /src/webpages/404.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | CoogleIOT for ESP8266 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 2017-2018 John Coggeshall | 6 | +----------------------------------------------------------------------+ 7 | | Licensed under the Apache License, Version 2.0 (the "License"); | 8 | | you may not use this file except in compliance with the License. You | 9 | | may obtain a copy of the License at: | 10 | | | 11 | | http://www.apache.org/licenses/LICENSE-2.0 | 12 | | | 13 | | Unless required by applicable law or agreed to in writing, software | 14 | | distributed under the License is distributed on an "AS IS" BASIS, | 15 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | 16 | | implied. See the License for the specific language governing | 17 | | permissions and limitations under the License. | 18 | +----------------------------------------------------------------------+ 19 | | Authors: John Coggeshall | 20 | +----------------------------------------------------------------------+ 21 | */ 22 | 23 | #ifndef COOGLEIOT_WEBPAGES_404_H_ 24 | #define COOGLEIOT_WEBPAGES_404_H_ 25 | 26 | const char WEBPAGE_NOTFOUND[] PROGMEM = R"=====( 27 | 28 | 29 | CoogleIOT 30 | 31 | 32 | 33 | 34 |

Opps! 404 - Not found!

35 |

Back to Configuration page

36 | 37 | 38 | )====="; 39 | 40 | 41 | #endif /* COOGLEIOT_WEBPAGES_404_H_ */ 42 | -------------------------------------------------------------------------------- /src/webpages/home.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | CoogleIOT for ESP8266 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 2017-2018 John Coggeshall | 6 | +----------------------------------------------------------------------+ 7 | | Licensed under the Apache License, Version 2.0 (the "License"); | 8 | | you may not use this file except in compliance with the License. You | 9 | | may obtain a copy of the License at: | 10 | | | 11 | | http://www.apache.org/licenses/LICENSE-2.0 | 12 | | | 13 | | Unless required by applicable law or agreed to in writing, software | 14 | | distributed under the License is distributed on an "AS IS" BASIS, | 15 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | 16 | | implied. See the License for the specific language governing | 17 | | permissions and limitations under the License. | 18 | +----------------------------------------------------------------------+ 19 | | Authors: John Coggeshall | 20 | +----------------------------------------------------------------------+ 21 | */ 22 | 23 | #ifndef COOGLEIOT_WEBPAGES_HOME_H_ 24 | #define COOGLEIOT_WEBPAGES_HOME_H_ 25 | 26 | const char WEBPAGE_Home[] PROGMEM = R"=====( 27 | 28 | 29 | CoogleIOT Firmware 30 | 31 | 32 | 33 | 34 |

CoogleIOT Device Setup

35 |
36 | 37 | 38 |
39 |
40 | Device Wireless Setup 41 |

Settings for the device WiFi (as AP)

42 |
43 | 44 | 45 |
46 |
47 | 48 | 49 |
50 |
51 |
52 | Client WiFi Setup 53 |

Settings for WiFi (as Client)

54 |
55 | 56 | 57 |
58 |
59 | 60 | 61 |
62 |
63 |
64 | 65 | 66 |
67 |
68 | MQTT Client Configuration 69 |
70 | 71 | 72 |
73 |
74 | 75 | 76 |
77 |
78 | 79 | 80 |
81 |
82 | 83 | 84 |
85 |
86 | 87 | 88 |
89 |
90 | 91 | 92 |
93 |
94 | 95 | 96 |
97 |
98 |
99 | 100 | 101 |
102 |

System Commands

103 | 104 | 105 |
106 | Firmware Updates 107 |

Device will check for updates every 30 minutes at this URL. See:

108 | http://esp8266.github.io/Arduino/versions/2.0.0/doc/ota_updates/ota_updates.html#http-server

109 | For details on the server-side implementation.

110 |
111 | 112 | 113 |
114 |

Alternatively, you can directly upload a new .bin firmware file below:

115 |
116 |
117 | 118 | 119 | 120 |
121 |
122 |
123 |
124 | 125 | 126 |
127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 |
CoogleIOT Status
CoogleIOT VersionBuild Date/TimeCoogleIOT AP StatusCoogleIOT AP SSIDWiFi StatusWiFi SSIDLAN IP AddressMQTT StatusNTP StatusDNS StatusFirmware Updates
{{coogleiot_version}}{{coogleiot_buildtime}}{{coogleiot_ap_status}}{{coogleiot_ap_ssid}}{{wifi_status}}{{remote_ap_name}}{{wifi_ip_address}}{{mqtt_status}}{{ntp_status}}{{dns_status}}{{firmware_update_status}}
160 |
161 | 162 | 163 |
164 |
165 |
166 |

167 |       
168 |
169 | 170 | 171 | 226 | 227 | 228 | )====="; 229 | 230 | 231 | #endif /* COOGLEIOT_WEBPAGES_HOME_H_ */ 232 | -------------------------------------------------------------------------------- /src/webpages/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | CoogleIOT Firmware 4 | 5 | 6 | 7 | 8 |

CoogleIOT Device Setup

9 |
10 | 11 | 12 |
13 |
14 | Device Wireless Setup 15 |

Settings for the device WiFi (as AP)

16 |
17 | 18 | 19 |
20 |
21 | 22 | 23 |
24 |
25 |
26 | Client WiFi Setup 27 |

Settings for WiFi (as Client)

28 |
29 | 30 | 31 |
32 |
33 | 34 | 35 |
36 |
37 |
38 | 39 | 40 |
41 |
42 | MQTT Client Configuration 43 |
44 | 45 | 46 |
47 |
48 | 49 | 50 |
51 |
52 | 53 | 54 |
55 |
56 | 57 | 58 |
59 |
60 | 61 | 62 |
63 |
64 | 65 | 66 |
67 |
68 | 69 | 70 |
71 |
72 |
73 | 74 | 75 |
76 |

System Commands

77 | 78 | 79 |
80 | Firmware Updates 81 |

Device will check for updates every 30 minutes at this URL. See:

82 | http://esp8266.github.io/Arduino/versions/2.0.0/doc/ota_updates/ota_updates.html#http-server

83 | For details on the server-side implementation.

84 |
85 | 86 | 87 |
88 |

Alternatively, you can directly upload a new .bin firmware file below:

89 |
90 |
91 | 92 | 93 | 94 |
95 |
96 |
97 |
98 | 99 | 100 |
101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 |
CoogleIOT Status
CoogleIOT VersionCoogleIOT AP StatusCoogleIOT AP SSIDWiFi StatusWiFi SSIDLAN IP AddressMQTT StatusNTP StatusFirmware Updates
{{coogleiot_version}}{{coogleiot_ap_status}}{{coogleiot_ap_ssid}}{{wifi_status}}{{remote_ap_name}}{{wifi_ip_address}}{{mqtt_status}}{{ntp_status}}{{firmware_update_status}}
130 |
131 | 132 | 133 |
134 |
135 |
136 |
137 |
138 |
139 | 140 | 141 | 196 | 197 | 198 | -------------------------------------------------------------------------------- /src/webpages/restarting.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | CoogleIOT for ESP8266 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 2017-2018 John Coggeshall | 6 | +----------------------------------------------------------------------+ 7 | | Licensed under the Apache License, Version 2.0 (the "License"); | 8 | | you may not use this file except in compliance with the License. You | 9 | | may obtain a copy of the License at: | 10 | | | 11 | | http://www.apache.org/licenses/LICENSE-2.0 | 12 | | | 13 | | Unless required by applicable law or agreed to in writing, software | 14 | | distributed under the License is distributed on an "AS IS" BASIS, | 15 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | 16 | | implied. See the License for the specific language governing | 17 | | permissions and limitations under the License. | 18 | +----------------------------------------------------------------------+ 19 | | Authors: John Coggeshall | 20 | +----------------------------------------------------------------------+ 21 | */ 22 | 23 | #ifndef COOGLEIOT_WEBPAGES_RESTART_H_ 24 | #define COOGLEIOT_WEBPAGES_RESTART_H_ 25 | 26 | const char WEBPAGE_Restart[] PROGMEM = R"=====( 27 | 28 | 29 | CoogleIOT Firmware Restarting 30 | 31 | 32 | 33 | 34 |
35 |
36 |
37 |

Restarting CoogleIOT

38 |

Please wait, this CoogleIOT device is restarting.

39 |

You will likely lose your connection to the AP.

40 |
41 |

42 | 43 |

44 |
45 |
46 |
47 | 48 | 68 | 69 | 70 | )====="; 71 | 72 | 73 | #endif /* COOGLEIOT_WEBPAGES_HOME_H_ */ 74 | -------------------------------------------------------------------------------- /src/webpages/restarting.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | CoogleIOT Firmware Restarting 4 | 5 | 6 | 7 | 8 |
9 |
10 |
11 |

Restarting CoogleIOT

12 |

Please wait, this CoogleIOT device is restarting.

13 |

You will likely lose your connection to the AP.

14 |
15 |

16 | 17 |

18 |
19 |
20 |
21 | 22 | 42 | 43 | 44 | --------------------------------------------------------------------------------