├── Home Assistant ├── automations.yaml └── configuration.yaml ├── LICENSE ├── README.md ├── WiFi-Doorbell-DB100 ├── WiFi-Doorbell-DB100.ino └── src │ ├── ConfigPortal.h │ ├── WiFiManager.cpp │ └── WiFiManager.h └── images ├── ConfigureDevice.png ├── DB100-Render.png ├── HA-config-automate.png ├── Settings.png ├── action-fields.PNG ├── action-service.PNG ├── button-press.png ├── choose-service.PNG ├── copy-ifttt-key.png ├── event-name.PNG ├── ifttt-screenshot-crop.png ├── j2-pins.JPG ├── mqtt-action.png ├── mqtt-trigger.png ├── remove-cover.png ├── wiring-diagram-2.png └── wiring-diagram.png /Home Assistant/automations.yaml: -------------------------------------------------------------------------------- 1 | alias: Doorbell 2 | trigger: 3 | - platform: mqtt 4 | topic: doorbellsensor/status 5 | condition: [] 6 | action: 7 | - data: 8 | data: 9 | attachment: 10 | content-type: jpeg 11 | entity_id: camera.front 12 | push: 13 | category: camera 14 | message: Doorbell Pressed 15 | service: notify.ios_matts_iphone 16 | -------------------------------------------------------------------------------- /Home Assistant/configuration.yaml: -------------------------------------------------------------------------------- 1 | sensor: 2 | - platform: mqtt 3 | state_topic: "doorbellsensor/status/battery" 4 | value_template: '{{value_json.batt}}' 5 | unit_of_measurement: "Volts" 6 | name: "WiFi Doorbell" 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Firefly Electronix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WiFi Doorbell Sensor 2 | This is the source code for the WiFi Doorbell Sensor from Firefly Electronix. Visit our website for more information. https://www.fireflyelectronix.com/product/wifidoorbell 3 | 4 | #### Software Roadmap 5 | - [x] Support for OTA (Over-the-air) Updates - v1.2 Release 6 | - [ ] Support for TLS/SSL Secure Client (Http port 443 and MQTT port 8883) 7 | 8 | ### Updating the firmware - OTA (Available in release v1.2 or newer) 9 | 10 | 1. Download the latest .bin file from our releases https://github.com/fireflyelectronix/wifidoorbell/releases 11 | 1. Press and hold SW1 within 10 seconds after installing the battery 12 | 1. Connect to the WiFi Network - Firefly-xxxxxx 13 | 1. Open your browser and navigate to 192.168.244.1 (Updating does not work throgh Apple's Captive Portal that pops up automatically) 14 | 1. Click on Update 15 | 1. Click on Choose File and Select the .bin file that was downloaded in step 1 16 | 1. Click on Update - It will automatically restart after update is completed. Settings will be kept after the update and not erased. 17 | 18 | 19 | ### Customizing and building your own firmware with Arduino 20 | 21 | 1. Download and install Arduino IDE (tested with version 1.8.8) 22 | 23 | 1. Add ESP8266 to File -> Preferences -> Additional Boards Manager URLs: http://arduino.esp8266.com/stable/package_esp8266com_index.json 24 | 25 | 1. Install ESP8266 Board Tools -> Board: -> Boards Manager... -> Search "ESP8266" -> click Install 26 | 27 | 1. Install Libraries Tools -> Manage Libraries -> Search -> Select Version -> click Install 28 | 1. Install ArduinoJson Library (tested with version 5.13.4) 29 | 1. Install PubSubClient Library (tested with version 2.7.0) 30 | 31 | ### Programming the WiFi Doorbell Sensor - USB to Serial 32 | 33 | 1. Tools that you will need. 34 | 1. Node MCU Py Flasher - Download it here https://github.com/marcelstoer/nodemcu-pyflasher/releases 35 | 1. Download the latest .bin file from our releases. https://github.com/fireflyelectronix/wifidoorbell/releases 36 | 1. FTDI programming tool - One that can switch to 3.3v logic and uses the FT232RL chip. https://www.amazon.com/gp/product/B07G2D34ZW 37 | 38 | 1. Conenct the FTDI to the J2 Header on the WiFi Doorbell Sensor. 39 | 40 | | FTDI Pin | WiFi Doorbell Pin | 41 | | -------- | ----------------- | 42 | | GND | GND | 43 | | RX | TX | 44 | | TX | RX | 45 | 46 | 1. Program the WiFi Doorbell Sensor 47 | 1. Press and hold SW1 prior to installing the battery. This puts the ESP8266 module into UART Download mode. 48 | 1. Open the Node MCU Py Flasher tool. Change Erase Flash to - yes. All other settings keep on default 49 | 1. Select the COM port that your FTDI programmer is plugged into. 50 | 1. Select the .bin file that you downloaded from our relases. 51 | 1. Click on Flash NodeMCU 52 | 53 | The 4 pin header J2 is used for progamming. 54 | 55 | 1. Pin 1 - VCC 56 | 1. Pin 2 - GND 57 | 1. Pin 3 - TX 58 | 1. Pin 4 - RX 59 | -------------------------------------------------------------------------------- /WiFi-Doorbell-DB100/WiFi-Doorbell-DB100.ino: -------------------------------------------------------------------------------- 1 | #include //SPIFFS File System to store files 2 | #include //https://github.com/esp8266/Arduino 3 | #include //library for HTTP client 4 | #include //MQTT library 5 | #include "src/ConfigPortal.h" //all the config portal code moved here to keep the main sketch clean. Based on WiFiManager 6 | 7 | ADC_MODE (ADC_VCC); //set the ADC mode of the ESP8266 to read the voltage level 8 | 9 | //varialbles for switch 1 (s1) 10 | int state_s1 = 0; 11 | int prev_state_s1 = 0; 12 | int pin_s1 = 0; 13 | int val_s1 = 0; 14 | unsigned long t_s1 = 0; 15 | unsigned long t_0_s1 = 0; 16 | #define bounce_delay_s1 10 17 | #define hold_delay_s1 2000 //Hold the button for 2 seconds to enter config portal 18 | 19 | unsigned long client_timer = 0; 20 | const long client_interval = 2000; //only run the http and mqtt clients every 2 seconds 21 | 22 | unsigned long loop_timer = 0; 23 | const long loop_interval = 8000; //run loop for 8 seconds then go to sleep 24 | 25 | bool ifttt_sent = false; 26 | bool mqtt_sent = false; 27 | 28 | WiFiClient wifiClient; 29 | HTTPClient http; 30 | PubSubClient client(wifiClient); 31 | 32 | 33 | void sendMQTT() { 34 | 35 | if (!client.connected()) { 36 | client.setServer(mqtt_server, atoi(mqtt_port)); //setup the mqtt target server and port 37 | } 38 | 39 | float vcc = ((float)ESP.getVcc())/1024; 40 | StaticJsonBuffer<100> jsonBuffer; 41 | JsonObject& JSONvoltage = jsonBuffer.createObject(); 42 | JSONvoltage["batt"] = vcc; 43 | JSONvoltage.printTo(Serial); 44 | char JSONmessageBuffer[100]; 45 | JSONvoltage.prettyPrintTo(JSONmessageBuffer, sizeof(JSONmessageBuffer)); 46 | 47 | char batt_topic[100]; 48 | const char *battery = "/battery"; 49 | strcpy(batt_topic, mqtt_topic); 50 | strcat(batt_topic, battery); 51 | Serial.println(batt_topic); 52 | 53 | Serial.print("MQTT: Attempting MQTT connection..."); 54 | // Create the clientID using the ESP8266 Chip ID 55 | String clientId = "FireFlyClient-"; 56 | clientId += String(random(0xffff), HEX); 57 | // Attempt to connect 58 | if (client.connect(clientId.c_str(), mqtt_username, mqtt_password)) { 59 | Serial.println("MQTT: Connected"); 60 | // Once connected, publish an announcement... 61 | client.publish(batt_topic, JSONmessageBuffer, true); 62 | Serial.println("MQTT: Battery Level Sent"); 63 | client.publish(mqtt_topic, "ON"); 64 | Serial.println("MQTT: Status Sent"); 65 | mqtt_sent = true; 66 | } else { 67 | Serial.println("MQTT: Connection failed, rc="); 68 | Serial.print(client.state()); 69 | } 70 | } 71 | 72 | void sendIFTTT() { 73 | 74 | float vcc = ((float)ESP.getVcc())/1024; 75 | StaticJsonBuffer<100> jsonBuffer; 76 | JsonObject& JSONvoltage = jsonBuffer.createObject(); 77 | JSONvoltage["value1"] = vcc; 78 | JSONvoltage.printTo(Serial); 79 | char JSONmessageBuffer[100]; 80 | JSONvoltage.prettyPrintTo(JSONmessageBuffer, sizeof(JSONmessageBuffer)); 81 | 82 | http.begin(wifiClient, "http://maker.ifttt.com/trigger/doorbell/with/key/" + String(ifttt_key)); 83 | http.addHeader("Content-Type", "application/json"); 84 | int httpCode = http.POST(JSONmessageBuffer); 85 | String payload = http.getString(); 86 | 87 | Serial.println(httpCode); 88 | Serial.println(payload); 89 | 90 | http.end(); 91 | ifttt_sent = true; //todo - need logic to get the http response before setting this flag. 92 | Serial.println("IFTTT: Trigger Sent"); 93 | 94 | } 95 | 96 | void setup() { 97 | // put your setup code here, to run once: 98 | Serial.begin(115200); 99 | pinMode(pin_s1, INPUT_PULLUP); 100 | pinMode(4, OUTPUT); 101 | digitalWrite(4, LOW); 102 | loadConfigFile(); 103 | } 104 | 105 | void loop() { 106 | 107 | loop_timer = millis(); 108 | 109 | while(millis() - loop_timer < loop_interval) { 110 | 111 | if (millis() - client_timer >= client_interval) { //Only run this periodically based on client_interval 112 | client_timer = millis(); //reset the clock starting point 113 | if (WiFi.status() == WL_CONNECTED) { //Only run the following once we are connected to wifi 114 | if (strlen(mqtt_topic) != 0) { //check if we have a value stored for mqtt setting 115 | if (mqtt_sent == false) { //if we haven't published to mqtt, then publish to mqtt server 116 | sendMQTT(); 117 | } 118 | } 119 | 120 | if (strlen(ifttt_key) != 0) { //check to see if there a value for the http address 121 | if (ifttt_sent == false) { //if we haven't sent an i, then send one 122 | sendIFTTT(); 123 | } 124 | } 125 | 126 | } 127 | } 128 | 129 | //client.loop(); may not be needed since we do not need to subcribe to incomming messages. 130 | 131 | s1buttonState(); //check the state of the button 132 | 133 | if (state_s1 == 5) { 134 | //nothing here yet for short press. maybe for reset 135 | } 136 | 137 | if (state_s1 == 6) {//if there is a long press, open config portal 138 | configPortal(); 139 | loop_timer = millis(); //reset the loop timer once we return from the config portal 140 | } 141 | yield(); 142 | } 143 | 144 | Serial.println("Going to sleep"); 145 | ESP.deepSleep(0); 146 | delay(100); //the module likes to have a delay after sending deepsleep. 147 | 148 | } 149 | 150 | void s1buttonState(){ 151 | val_s1 = digitalRead(pin_s1); 152 | prev_state_s1 = state_s1; 153 | 154 | switch (state_s1) { 155 | case 0: //Reset the state 156 | state_s1 = 1; 157 | break; 158 | 159 | case 1: //waiting to see if the switch goes LOW 160 | if (val_s1 == LOW) {state_s1 = 2;} 161 | break; 162 | 163 | case 2: //the switch has sensed a LOW. Start the clock 164 | t_0_s1 = millis(); 165 | state_s1 = 3; 166 | break; 167 | 168 | case 3: //debounce the switch 169 | t_s1 = millis(); 170 | if (t_s1 - t_0_s1 > bounce_delay_s1) {state_s1 = 4;} 171 | if (val_s1 == HIGH) {state_s1 = 0;} 172 | break; 173 | 174 | case 4: //check how the button is pressed short vs long press 175 | t_s1 = millis(); 176 | if (val_s1 == HIGH) {state_s1 = 5;} 177 | if (t_s1 - t_0_s1 > hold_delay_s1) {state_s1 = 6;} 178 | break; 179 | 180 | case 5: //short press state then reset the state to 0 181 | state_s1 = 0; 182 | break; 183 | 184 | case 6: //long press state then go to wait state 185 | state_s1 = 7; 186 | break; 187 | 188 | case 7: //wait for switch to be released and reset state 189 | if (val_s1 == HIGH) {state_s1 = 0;} 190 | break; 191 | } 192 | 193 | } 194 | -------------------------------------------------------------------------------- /WiFi-Doorbell-DB100/src/ConfigPortal.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "WiFiManager.h" //https://github.com/tzapu/WiFiManager 4 | #include //https://github.com/bblanchon/ArduinoJson 5 | 6 | //define your default values here, if there are different values in config.json, they are overwritten. 7 | char mqtt_server[100]; 8 | char mqtt_port[6] = "1883"; //default mqtt port 9 | char mqtt_username[20]; 10 | char mqtt_password[20]; 11 | char mqtt_topic[100] = "doorbellsensor/status"; //default mqtt topic 12 | char ifttt_key[50]; 13 | 14 | //flag for saving data 15 | bool shouldSaveConfig = false; 16 | 17 | //callback notifying us of the need to save config 18 | void saveConfigCallback () { 19 | Serial.println("Should save config"); 20 | shouldSaveConfig = true; 21 | } 22 | 23 | void loadConfigFile() { 24 | 25 | //read configuration from FS config.json file 26 | Serial.println("mounting FS..."); 27 | 28 | if (SPIFFS.begin()) { 29 | Serial.println("mounted file system"); 30 | if (SPIFFS.exists("/config.json")) { 31 | //file exists, reading and loading 32 | Serial.println("reading config file"); 33 | File configFile = SPIFFS.open("/config.json", "r"); 34 | if (configFile) { 35 | Serial.println("opened config file"); 36 | size_t size = configFile.size(); 37 | // Allocate a buffer to store contents of the file. 38 | std::unique_ptr buf(new char[size]); 39 | 40 | configFile.readBytes(buf.get(), size); 41 | DynamicJsonBuffer jsonBuffer; 42 | JsonObject& json = jsonBuffer.parseObject(buf.get()); 43 | json.printTo(Serial); 44 | if (json.success()) { 45 | Serial.println("\nparsed json"); 46 | 47 | strcpy(mqtt_server, json["mqtt_server"]); 48 | strcpy(mqtt_port, json["mqtt_port"]); 49 | strcpy(mqtt_username, json["mqtt_username"]); 50 | strcpy(mqtt_password, json["mqtt_password"]); 51 | strcpy(mqtt_topic, json["mqtt_topic"]); 52 | strcpy(ifttt_key, json["ifttt_key"]); 53 | 54 | } else { 55 | Serial.println("failed to load json config"); 56 | } 57 | configFile.close(); 58 | } 59 | } 60 | } else { 61 | Serial.println("failed to mount FS"); 62 | } 63 | } 64 | //end read 65 | 66 | //on demand configPortal 67 | void configPortal (){ 68 | 69 | // The extra parameters to be configured (can be either global or just in the setup) 70 | // After connecting, parameter.getValue() will get you the configured value 71 | // id/name placeholder/prompt default length 72 | WiFiManagerParameter custom_mqtt_server("server", "mqtt server", mqtt_server, 100); 73 | WiFiManagerParameter custom_mqtt_port("port", "mqtt port", mqtt_port, 6); 74 | WiFiManagerParameter custom_mqtt_username("username", "mqtt username", mqtt_username, 20); 75 | WiFiManagerParameter custom_mqtt_password("password", "mqtt password", mqtt_password, 20); 76 | WiFiManagerParameter custom_mqtt_topic("topic", "mqtt topic", mqtt_topic, 100); 77 | WiFiManagerParameter custom_ifttt_key("ifttt", "ifttt key", ifttt_key, 50); 78 | 79 | //WiFiManager 80 | //Local intialization. Once its business is done, there is no need to keep it around 81 | WiFiManager wifiManager; 82 | 83 | //set config save notify callback 84 | wifiManager.setSaveConfigCallback(saveConfigCallback); 85 | 86 | //set static ip 87 | //wifiManager.setSTAStaticIPConfig(IPAddress(10,0,1,99), IPAddress(10,0,1,1), IPAddress(255,255,255,0)); 88 | 89 | //add all your parameters here 90 | wifiManager.addParameter(&custom_mqtt_server); 91 | wifiManager.addParameter(&custom_mqtt_port); 92 | wifiManager.addParameter(&custom_mqtt_username); 93 | wifiManager.addParameter(&custom_mqtt_password); 94 | wifiManager.addParameter(&custom_mqtt_topic); 95 | wifiManager.addParameter(&custom_ifttt_key); 96 | 97 | //reset settings - for testing 98 | //wifiManager.resetSettings(); 99 | 100 | //set minimu quality of signal so it ignores AP's under that quality 101 | //defaults to 8% 102 | //wifiManager.setMinimumSignalQuality(); 103 | 104 | //sets timeout until configuration portal gets turned off 105 | //useful to make it all retry or go to sleep 106 | //in seconds 107 | //wifiManager.setTimeout(180); 108 | 109 | //fetches ssid and pass and tries to connect 110 | //if it does not connect it starts an access point with the specified name 111 | //here "AutoConnectAP" 112 | //and goes into a blocking loop awaiting configuration 113 | if (!wifiManager.startConfigPortal()) { 114 | Serial.println("failed to connect and hit timeout"); 115 | delay(3000); 116 | //reset and try again, or maybe put it to deep sleep 117 | ESP.reset(); 118 | delay(5000); 119 | } 120 | 121 | //if you get here you have connected to the WiFi 122 | Serial.println("connected...yeey :)"); 123 | 124 | //read updated parameters 125 | strcpy(mqtt_server, custom_mqtt_server.getValue()); 126 | strcpy(mqtt_port, custom_mqtt_port.getValue()); 127 | strcpy(mqtt_username, custom_mqtt_username.getValue()); 128 | strcpy(mqtt_password, custom_mqtt_password.getValue()); 129 | strcpy(mqtt_topic, custom_mqtt_topic.getValue()); 130 | strcpy(ifttt_key, custom_ifttt_key.getValue()); 131 | 132 | //save the custom parameters to FS 133 | if (shouldSaveConfig) { 134 | Serial.println("saving config"); 135 | DynamicJsonBuffer jsonBuffer; 136 | JsonObject& json = jsonBuffer.createObject(); 137 | json["mqtt_server"] = mqtt_server; 138 | json["mqtt_port"] = mqtt_port; 139 | json["mqtt_username"] = mqtt_username; 140 | json["mqtt_password"] = mqtt_password; 141 | json["mqtt_topic"] = mqtt_topic; 142 | json["ifttt_key"] = ifttt_key; 143 | 144 | File configFile = SPIFFS.open("/config.json", "w"); 145 | if (!configFile) { 146 | Serial.println("failed to open config file for writing"); 147 | } 148 | 149 | json.printTo(Serial); 150 | json.printTo(configFile); 151 | configFile.close(); 152 | //end save 153 | } 154 | 155 | Serial.println("local ip"); 156 | Serial.println(WiFi.localIP()); 157 | 158 | } 159 | -------------------------------------------------------------------------------- /WiFi-Doorbell-DB100/src/WiFiManager.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************** 2 | WiFiManager is a library for the ESP8266/Arduino platform 3 | (https://github.com/esp8266/Arduino) to enable easy 4 | configuration and reconfiguration of WiFi credentials using a Captive Portal 5 | inspired by: 6 | http://www.esp8266.com/viewtopic.php?f=29&t=2520 7 | https://github.com/chriscook8/esp-arduino-apboot 8 | https://github.com/esp8266/Arduino/tree/master/libraries/DNSServer/examples/CaptivePortalAdvanced 9 | Built by AlexT https://github.com/tzapu 10 | Licensed under MIT license 11 | **************************************************************/ 12 | 13 | #include "WiFiManager.h" 14 | 15 | const char sw_ver[10] = "1.3"; //software version 16 | 17 | WiFiManagerParameter::WiFiManagerParameter(const char *custom) { 18 | _id = NULL; 19 | _placeholder = NULL; 20 | _length = 0; 21 | _value = NULL; 22 | 23 | _customHTML = custom; 24 | } 25 | 26 | WiFiManagerParameter::WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length) { 27 | init(id, placeholder, defaultValue, length, ""); 28 | } 29 | 30 | WiFiManagerParameter::WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom) { 31 | init(id, placeholder, defaultValue, length, custom); 32 | } 33 | 34 | void WiFiManagerParameter::init(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom) { 35 | _id = id; 36 | _placeholder = placeholder; 37 | _length = length; 38 | _value = new char[length + 1]; 39 | for (int i = 0; i < length + 1; i++) { 40 | _value[i] = 0; 41 | } 42 | if (defaultValue != NULL) { 43 | strncpy(_value, defaultValue, length); 44 | } 45 | 46 | _customHTML = custom; 47 | } 48 | 49 | WiFiManagerParameter::~WiFiManagerParameter() { 50 | if (_value != NULL) { 51 | delete[] _value; 52 | } 53 | } 54 | 55 | const char* WiFiManagerParameter::getValue() { 56 | return _value; 57 | } 58 | const char* WiFiManagerParameter::getID() { 59 | return _id; 60 | } 61 | const char* WiFiManagerParameter::getPlaceholder() { 62 | return _placeholder; 63 | } 64 | int WiFiManagerParameter::getValueLength() { 65 | return _length; 66 | } 67 | const char* WiFiManagerParameter::getCustomHTML() { 68 | return _customHTML; 69 | } 70 | 71 | 72 | WiFiManager::WiFiManager() { 73 | _max_params = WIFI_MANAGER_MAX_PARAMS; 74 | _params = (WiFiManagerParameter**)malloc(_max_params * sizeof(WiFiManagerParameter*)); 75 | } 76 | 77 | WiFiManager::~WiFiManager() 78 | { 79 | if (_params != NULL) 80 | { 81 | DEBUG_WM(F("freeing allocated params!")); 82 | free(_params); 83 | } 84 | } 85 | 86 | bool WiFiManager::addParameter(WiFiManagerParameter *p) { 87 | if(_paramsCount + 1 > _max_params) 88 | { 89 | // rezise the params array 90 | _max_params += WIFI_MANAGER_MAX_PARAMS; 91 | DEBUG_WM(F("Increasing _max_params to:")); 92 | DEBUG_WM(_max_params); 93 | WiFiManagerParameter** new_params = (WiFiManagerParameter**)realloc(_params, _max_params * sizeof(WiFiManagerParameter*)); 94 | if (new_params != NULL) { 95 | _params = new_params; 96 | } else { 97 | DEBUG_WM(F("ERROR: failed to realloc params, size not increased!")); 98 | return false; 99 | } 100 | } 101 | 102 | _params[_paramsCount] = p; 103 | _paramsCount++; 104 | DEBUG_WM(F("Adding parameter")); 105 | DEBUG_WM(p->getID()); 106 | return true; 107 | } 108 | 109 | void WiFiManager::setupConfigPortal() { 110 | dnsServer.reset(new DNSServer()); 111 | server.reset(new ESP8266WebServer(80)); 112 | 113 | DEBUG_WM(F("")); 114 | _configPortalStart = millis(); 115 | 116 | DEBUG_WM(F("Configuring access point... ")); 117 | DEBUG_WM(_apName); 118 | if (_apPassword != NULL) { 119 | if (strlen(_apPassword) < 8 || strlen(_apPassword) > 63) { 120 | // fail passphrase to short or long! 121 | DEBUG_WM(F("Invalid AccessPoint password. Ignoring")); 122 | _apPassword = NULL; 123 | } 124 | DEBUG_WM(_apPassword); 125 | } 126 | 127 | //optional soft ip config 128 | if (_ap_static_ip) { 129 | DEBUG_WM(F("Custom AP IP/GW/Subnet")); 130 | WiFi.softAPConfig(_ap_static_ip, _ap_static_gw, _ap_static_sn); 131 | } 132 | 133 | if (_apPassword != NULL) { 134 | WiFi.softAP(_apName, _apPassword);//password option 135 | } else { 136 | WiFi.softAP(_apName); 137 | } 138 | 139 | delay(500); // Without delay I've seen the IP address blank 140 | DEBUG_WM(F("AP IP address: ")); 141 | DEBUG_WM(WiFi.softAPIP()); 142 | 143 | /* Setup the DNS server redirecting all the domains to the apIP */ 144 | dnsServer->setErrorReplyCode(DNSReplyCode::NoError); 145 | dnsServer->start(DNS_PORT, "*", WiFi.softAPIP()); 146 | 147 | /* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */ 148 | server->on(String(F("/")), std::bind(&WiFiManager::handleRoot, this)); 149 | server->on(String(F("/wifi")), std::bind(&WiFiManager::handleWifi, this, true)); 150 | server->on(String(F("/0wifi")), std::bind(&WiFiManager::handleWifi, this, false)); 151 | server->on(String(F("/wifisave")), std::bind(&WiFiManager::handleWifiSave, this)); 152 | server->on(String(F("/i")), std::bind(&WiFiManager::handleInfo, this)); 153 | server->on(String(F("/r")), std::bind(&WiFiManager::handleReset, this)); 154 | //server->on("/generate_204", std::bind(&WiFiManager::handle204, this)); //Android/Chrome OS captive portal check. 155 | server->on(String(F("/fwlink")), std::bind(&WiFiManager::handleRoot, this)); //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler. 156 | server->on(String(F("/update")), std::bind(&WiFiManager::handleUpdate, this)); 157 | server->on(String(F("/u")), HTTP_POST, std::bind(&WiFiManager::handleUpdateDone, this), std::bind(&WiFiManager::handleUpdating, this)); 158 | server->onNotFound (std::bind(&WiFiManager::handleNotFound, this)); 159 | server->begin(); // Web server start 160 | DEBUG_WM(F("HTTP server started")); 161 | 162 | } 163 | 164 | boolean WiFiManager::autoConnect() { 165 | String ssid = "Firefly-" + String(ESP.getChipId()); 166 | return autoConnect(ssid.c_str(), NULL); 167 | } 168 | 169 | boolean WiFiManager::autoConnect(char const *apName, char const *apPassword) { 170 | DEBUG_WM(F("")); 171 | DEBUG_WM(F("AutoConnect")); 172 | 173 | // read eeprom for ssid and pass 174 | //String ssid = getSSID(); 175 | //String pass = getPassword(); 176 | 177 | // attempt to connect; should it fail, fall back to AP 178 | WiFi.mode(WIFI_STA); 179 | 180 | if (connectWifi("", "") == WL_CONNECTED) { 181 | DEBUG_WM(F("IP Address:")); 182 | DEBUG_WM(WiFi.localIP()); 183 | //connected 184 | return true; 185 | } 186 | 187 | return startConfigPortal(apName, apPassword); 188 | } 189 | 190 | boolean WiFiManager::configPortalHasTimeout(){ 191 | if(_configPortalTimeout == 0 || wifi_softap_get_station_num() > 0){ 192 | _configPortalStart = millis(); // kludge, bump configportal start time to skew timeouts 193 | return false; 194 | } 195 | return (millis() > _configPortalStart + _configPortalTimeout); 196 | } 197 | 198 | boolean WiFiManager::startConfigPortal() { 199 | String ssid = "Firefly-" + String(ESP.getChipId()); 200 | return startConfigPortal(ssid.c_str(), NULL); 201 | } 202 | 203 | boolean WiFiManager::startConfigPortal(char const *apName, char const *apPassword) { 204 | 205 | if(!WiFi.isConnected()){ 206 | WiFi.persistent(false); 207 | // disconnect sta, start ap 208 | WiFi.disconnect(); // this alone is not enough to stop the autoconnecter 209 | WiFi.mode(WIFI_AP); 210 | WiFi.persistent(true); 211 | } 212 | else { 213 | //setup AP 214 | WiFi.mode(WIFI_AP_STA); 215 | DEBUG_WM(F("SET AP STA")); 216 | } 217 | 218 | 219 | _apName = apName; 220 | _apPassword = apPassword; 221 | 222 | //notify we entered AP mode 223 | if ( _apcallback != NULL) { 224 | _apcallback(this); 225 | } 226 | 227 | connect = false; 228 | setupConfigPortal(); 229 | 230 | unsigned long previousTime = 0; 231 | const long interval = 1000; 232 | int ledState = LOW; 233 | 234 | while(1){ 235 | 236 | unsigned long currentTime = millis(); 237 | if (currentTime - previousTime >= interval) { 238 | previousTime = currentTime; 239 | if (ledState == LOW) { 240 | ledState = HIGH; 241 | } else { 242 | ledState = LOW; 243 | } 244 | digitalWrite(4, ledState); 245 | } 246 | 247 | // check if timeout 248 | if(configPortalHasTimeout()) break; 249 | 250 | //DNS 251 | dnsServer->processNextRequest(); 252 | //HTTP 253 | server->handleClient(); 254 | 255 | 256 | if (connect) { 257 | connect = false; 258 | delay(2000); 259 | DEBUG_WM(F("Connecting to new AP")); 260 | 261 | // using user-provided _ssid, _pass in place of system-stored ssid and pass 262 | if (connectWifi(_ssid, _pass) != WL_CONNECTED) { 263 | DEBUG_WM(F("Failed to connect.")); 264 | } else { 265 | //connected 266 | WiFi.mode(WIFI_STA); 267 | //notify that configuration has changed and any optional parameters should be saved 268 | if ( _savecallback != NULL) { 269 | //todo: check if any custom parameters actually exist, and check if they really changed maybe 270 | _savecallback(); 271 | } 272 | digitalWrite(4, LOW); 273 | break; 274 | } 275 | 276 | if (_shouldBreakAfterConfig) { 277 | //flag set to exit after config after trying to connect 278 | //notify that configuration has changed and any optional parameters should be saved 279 | if ( _savecallback != NULL) { 280 | //todo: check if any custom parameters actually exist, and check if they really changed maybe 281 | _savecallback(); 282 | } 283 | digitalWrite(4, LOW); 284 | break; 285 | } 286 | } 287 | yield(); 288 | } 289 | 290 | server.reset(); 291 | dnsServer.reset(); 292 | 293 | digitalWrite(4, LOW); 294 | 295 | return WiFi.status() == WL_CONNECTED; 296 | } 297 | 298 | 299 | int WiFiManager::connectWifi(String ssid, String pass) { 300 | DEBUG_WM(F("Connecting as wifi client...")); 301 | 302 | // check if we've got static_ip settings, if we do, use those. 303 | if (_sta_static_ip) { 304 | DEBUG_WM(F("Custom STA IP/GW/Subnet")); 305 | WiFi.config(_sta_static_ip, _sta_static_gw, _sta_static_sn); 306 | DEBUG_WM(WiFi.localIP()); 307 | } 308 | //fix for auto connect racing issue 309 | if (WiFi.status() == WL_CONNECTED) { 310 | DEBUG_WM(F("Already connected. Bailing out.")); 311 | return WL_CONNECTED; 312 | } 313 | //check if we have ssid and pass and force those, if not, try with last saved values 314 | if (ssid != "") { 315 | WiFi.begin(ssid.c_str(), pass.c_str()); 316 | } else { 317 | if (WiFi.SSID()) { 318 | DEBUG_WM(F("Using last saved values, should be faster")); 319 | //trying to fix connection in progress hanging 320 | ETS_UART_INTR_DISABLE(); 321 | wifi_station_disconnect(); 322 | ETS_UART_INTR_ENABLE(); 323 | 324 | WiFi.begin(); 325 | } else { 326 | DEBUG_WM(F("No saved credentials")); 327 | } 328 | } 329 | 330 | int connRes = waitForConnectResult(); 331 | DEBUG_WM ("Connection result: "); 332 | DEBUG_WM ( connRes ); 333 | //not connected, WPS enabled, no pass - first attempt 334 | #ifdef NO_EXTRA_4K_HEAP 335 | if (_tryWPS && connRes != WL_CONNECTED && pass == "") { 336 | startWPS(); 337 | //should be connected at the end of WPS 338 | connRes = waitForConnectResult(); 339 | } 340 | #endif 341 | return connRes; 342 | } 343 | 344 | uint8_t WiFiManager::waitForConnectResult() { 345 | if (_connectTimeout == 0) { 346 | return WiFi.waitForConnectResult(); 347 | } else { 348 | DEBUG_WM (F("Waiting for connection result with time out")); 349 | unsigned long start = millis(); 350 | boolean keepConnecting = true; 351 | uint8_t status; 352 | while (keepConnecting) { 353 | status = WiFi.status(); 354 | if (millis() > start + _connectTimeout) { 355 | keepConnecting = false; 356 | DEBUG_WM (F("Connection timed out")); 357 | } 358 | if (status == WL_CONNECTED || status == WL_CONNECT_FAILED) { 359 | keepConnecting = false; 360 | } 361 | delay(100); 362 | } 363 | return status; 364 | } 365 | } 366 | 367 | void WiFiManager::startWPS() { 368 | DEBUG_WM(F("START WPS")); 369 | WiFi.beginWPSConfig(); 370 | DEBUG_WM(F("END WPS")); 371 | } 372 | /* 373 | String WiFiManager::getSSID() { 374 | if (_ssid == "") { 375 | DEBUG_WM(F("Reading SSID")); 376 | _ssid = WiFi.SSID(); 377 | DEBUG_WM(F("SSID: ")); 378 | DEBUG_WM(_ssid); 379 | } 380 | return _ssid; 381 | } 382 | 383 | String WiFiManager::getPassword() { 384 | if (_pass == "") { 385 | DEBUG_WM(F("Reading Password")); 386 | _pass = WiFi.psk(); 387 | DEBUG_WM("Password: " + _pass); 388 | //DEBUG_WM(_pass); 389 | } 390 | return _pass; 391 | } 392 | */ 393 | String WiFiManager::getConfigPortalSSID() { 394 | return _apName; 395 | } 396 | 397 | void WiFiManager::resetSettings() { 398 | DEBUG_WM(F("settings invalidated")); 399 | DEBUG_WM(F("THIS MAY CAUSE AP NOT TO START UP PROPERLY. YOU NEED TO COMMENT IT OUT AFTER ERASING THE DATA.")); 400 | WiFi.disconnect(true); 401 | //delay(200); 402 | } 403 | void WiFiManager::setTimeout(unsigned long seconds) { 404 | setConfigPortalTimeout(seconds); 405 | } 406 | 407 | void WiFiManager::setConfigPortalTimeout(unsigned long seconds) { 408 | _configPortalTimeout = seconds * 1000; 409 | } 410 | 411 | void WiFiManager::setConnectTimeout(unsigned long seconds) { 412 | _connectTimeout = seconds * 1000; 413 | } 414 | 415 | void WiFiManager::setDebugOutput(boolean debug) { 416 | _debug = debug; 417 | } 418 | 419 | void WiFiManager::setAPStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn) { 420 | _ap_static_ip = ip; 421 | _ap_static_gw = gw; 422 | _ap_static_sn = sn; 423 | } 424 | /* 425 | void WiFiManager::setSTAStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn) { 426 | _sta_static_ip = ip; 427 | _sta_static_gw = gw; 428 | _sta_static_sn = sn; 429 | } 430 | */ 431 | void WiFiManager::setMinimumSignalQuality(int quality) { 432 | _minimumQuality = quality; 433 | } 434 | 435 | void WiFiManager::setBreakAfterConfig(boolean shouldBreak) { 436 | _shouldBreakAfterConfig = shouldBreak; 437 | } 438 | 439 | /** Handle root or redirect to captive portal */ 440 | void WiFiManager::handleRoot() { 441 | DEBUG_WM(F("Handle root")); 442 | if (captivePortal()) { // If caprive portal redirect instead of displaying the page. 443 | return; 444 | } 445 | 446 | String page = FPSTR(HTTP_HEAD); 447 | page.replace("{v}", "Configure"); 448 | page += FPSTR(HTTP_SCRIPT); 449 | page += FPSTR(HTTP_STYLE); 450 | page += _customHeadElement; 451 | page += FPSTR(HTTP_HEAD_END); 452 | //page += String(F("

")); 453 | //page += _apName; 454 | //page += String(F("

")); 455 | page += F("

WiFi Doorbell Sensor

"); 456 | page += FPSTR(HTTP_PORTAL_OPTIONS); 457 | page += FPSTR(HTTP_END); 458 | 459 | server->sendHeader("Content-Length", String(page.length())); 460 | server->send(200, "text/html", page); 461 | 462 | } 463 | 464 | /** Wifi config page handler */ 465 | void WiFiManager::handleWifi(boolean scan) { 466 | 467 | String page = FPSTR(HTTP_HEAD); 468 | page.replace("{v}", "Settings"); 469 | page += FPSTR(HTTP_SCRIPT); 470 | page += FPSTR(HTTP_STYLE); 471 | page += _customHeadElement; 472 | page += F("

Choose a Network...

"); 473 | page += FPSTR(HTTP_HEAD_END); 474 | 475 | if (scan) { 476 | int n = WiFi.scanNetworks(); 477 | DEBUG_WM(F("Scan done")); 478 | if (n == 0) { 479 | DEBUG_WM(F("No networks found")); 480 | page += F("No networks found. Refresh to scan again."); 481 | } else { 482 | 483 | //sort networks 484 | int indices[n]; 485 | for (int i = 0; i < n; i++) { 486 | indices[i] = i; 487 | } 488 | 489 | // RSSI SORT 490 | 491 | // old sort 492 | for (int i = 0; i < n; i++) { 493 | for (int j = i + 1; j < n; j++) { 494 | if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) { 495 | std::swap(indices[i], indices[j]); 496 | } 497 | } 498 | } 499 | 500 | /*std::sort(indices, indices + n, [](const int & a, const int & b) -> bool 501 | { 502 | return WiFi.RSSI(a) > WiFi.RSSI(b); 503 | });*/ 504 | 505 | // remove duplicates ( must be RSSI sorted ) 506 | if (_removeDuplicateAPs) { 507 | String cssid; 508 | for (int i = 0; i < n; i++) { 509 | if (indices[i] == -1) continue; 510 | cssid = WiFi.SSID(indices[i]); 511 | for (int j = i + 1; j < n; j++) { 512 | if (cssid == WiFi.SSID(indices[j])) { 513 | DEBUG_WM("DUP AP: " + WiFi.SSID(indices[j])); 514 | indices[j] = -1; // set dup aps to index -1 515 | } 516 | } 517 | } 518 | } 519 | 520 | //display networks in page 521 | for (int i = 0; i < n; i++) { 522 | if (indices[i] == -1) continue; // skip dups 523 | DEBUG_WM(WiFi.SSID(indices[i])); 524 | DEBUG_WM(WiFi.RSSI(indices[i])); 525 | int quality = getRSSIasQuality(WiFi.RSSI(indices[i])); 526 | 527 | if (_minimumQuality == -1 || _minimumQuality < quality) { 528 | String item = FPSTR(HTTP_ITEM); 529 | String rssiQ; 530 | rssiQ += quality; 531 | item.replace("{v}", WiFi.SSID(indices[i])); 532 | item.replace("{r}", rssiQ); 533 | if (WiFi.encryptionType(indices[i]) != ENC_TYPE_NONE) { 534 | item.replace("{i}", "l"); 535 | } else { 536 | item.replace("{i}", ""); 537 | } 538 | //DEBUG_WM(item); 539 | page += "
    "; 540 | page += item; 541 | page += "
"; 542 | delay(0); 543 | } else { 544 | DEBUG_WM(F("Skipping due to quality")); 545 | } 546 | 547 | } 548 | page += "
"; 549 | } 550 | } 551 | 552 | page += FPSTR(HTTP_FORM_START); 553 | char parLength[5]; 554 | // add the extra parameters to the form 555 | for (int i = 0; i < _paramsCount; i++) { 556 | if (_params[i] == NULL) { 557 | break; 558 | } 559 | 560 | String pitem = FPSTR(HTTP_FORM_PARAM); 561 | if (_params[i]->getID() != NULL) { 562 | pitem.replace("{i}", _params[i]->getID()); 563 | pitem.replace("{n}", _params[i]->getID()); 564 | pitem.replace("{p}", _params[i]->getPlaceholder()); 565 | snprintf(parLength, 5, "%d", _params[i]->getValueLength()); 566 | pitem.replace("{l}", parLength); 567 | pitem.replace("{v}", _params[i]->getValue()); 568 | pitem.replace("{c}", _params[i]->getCustomHTML()); 569 | } else { 570 | pitem = _params[i]->getCustomHTML(); 571 | } 572 | 573 | page += pitem; 574 | } 575 | if (_params[0] != NULL) { 576 | page += "
"; 577 | } 578 | /* 579 | if (_sta_static_ip) { 580 | 581 | String item = FPSTR(HTTP_FORM_PARAM); 582 | item.replace("{i}", "ip"); 583 | item.replace("{n}", "ip"); 584 | item.replace("{p}", "Static IP"); 585 | item.replace("{l}", "15"); 586 | item.replace("{v}", _sta_static_ip.toString()); 587 | 588 | page += item; 589 | 590 | item = FPSTR(HTTP_FORM_PARAM); 591 | item.replace("{i}", "gw"); 592 | item.replace("{n}", "gw"); 593 | item.replace("{p}", "Static Gateway"); 594 | item.replace("{l}", "15"); 595 | item.replace("{v}", _sta_static_gw.toString()); 596 | 597 | page += item; 598 | 599 | item = FPSTR(HTTP_FORM_PARAM); 600 | item.replace("{i}", "sn"); 601 | item.replace("{n}", "sn"); 602 | item.replace("{p}", "Subnet"); 603 | item.replace("{l}", "15"); 604 | item.replace("{v}", _sta_static_sn.toString()); 605 | 606 | page += item; 607 | 608 | page += "
"; 609 | }*/ 610 | 611 | page += FPSTR(HTTP_FORM_END); 612 | 613 | page += FPSTR(HTTP_END); 614 | 615 | server->sendHeader("Content-Length", String(page.length())); 616 | server->send(200, "text/html", page); 617 | 618 | 619 | DEBUG_WM(F("Sent config page")); 620 | } 621 | 622 | /** Handle the WLAN save form and redirect to WLAN config page again */ 623 | void WiFiManager::handleWifiSave() { 624 | DEBUG_WM(F("WiFi save")); 625 | 626 | //SAVE/connect here 627 | _ssid = server->arg("s").c_str(); 628 | _pass = server->arg("p").c_str(); 629 | 630 | //parameters 631 | for (int i = 0; i < _paramsCount; i++) { 632 | if (_params[i] == NULL) { 633 | break; 634 | } 635 | //read parameter 636 | String value = server->arg(_params[i]->getID()).c_str(); 637 | //store it in array 638 | value.toCharArray(_params[i]->_value, _params[i]->_length + 1); 639 | DEBUG_WM(F("Parameter")); 640 | DEBUG_WM(_params[i]->getID()); 641 | DEBUG_WM(value); 642 | } 643 | 644 | if (server->arg("ip") != "") { 645 | DEBUG_WM(F("static ip")); 646 | DEBUG_WM(server->arg("ip")); 647 | //_sta_static_ip.fromString(server->arg("ip")); 648 | String ip = server->arg("ip"); 649 | optionalIPFromString(&_sta_static_ip, ip.c_str()); 650 | } 651 | if (server->arg("gw") != "") { 652 | DEBUG_WM(F("static gateway")); 653 | DEBUG_WM(server->arg("gw")); 654 | String gw = server->arg("gw"); 655 | optionalIPFromString(&_sta_static_gw, gw.c_str()); 656 | } 657 | if (server->arg("sn") != "") { 658 | DEBUG_WM(F("static netmask")); 659 | DEBUG_WM(server->arg("sn")); 660 | String sn = server->arg("sn"); 661 | optionalIPFromString(&_sta_static_sn, sn.c_str()); 662 | } 663 | 664 | String page = FPSTR(HTTP_HEAD); 665 | page.replace("{v}", "Credentials Saved"); 666 | page += FPSTR(HTTP_SCRIPT); 667 | page += FPSTR(HTTP_STYLE); 668 | page += _customHeadElement; 669 | page += FPSTR(HTTP_HEAD_END); 670 | page += FPSTR(HTTP_SAVED); 671 | page += FPSTR(HTTP_END); 672 | 673 | server->sendHeader("Content-Length", String(page.length())); 674 | server->send(200, "text/html", page); 675 | 676 | DEBUG_WM(F("Sent wifi save page")); 677 | 678 | connect = true; //signal ready to connect/reset 679 | } 680 | 681 | /** Handle the info page */ 682 | void WiFiManager::handleInfo() { 683 | DEBUG_WM(F("Info")); 684 | 685 | String page = FPSTR(HTTP_HEAD); 686 | page.replace("{v}", "About"); 687 | page += FPSTR(HTTP_SCRIPT); 688 | page += FPSTR(HTTP_STYLE); 689 | page += _customHeadElement; 690 | page += FPSTR(HTTP_HEAD_END); 691 | page += F("
"); 692 | page += F("
AP IP
"); 693 | page += WiFi.softAPIP().toString(); 694 | page += F("
"); 695 | page += F("
AP MAC
"); 696 | page += WiFi.softAPmacAddress(); 697 | page += F("
"); 698 | page += F("
Station MAC
"); 699 | page += WiFi.macAddress(); 700 | page += F("
"); 701 | page += F("
Chip ID
"); 702 | page += ESP.getChipId(); 703 | page += F("
"); 704 | page += F("
Flash Chip ID
"); 705 | page += ESP.getFlashChipId(); 706 | page += F("
"); 707 | page += F("
Software Version
"); 708 | page += sw_ver; 709 | page += F("
"); 710 | page += F("
"); 711 | page += FPSTR(HTTP_END); 712 | 713 | server->sendHeader("Content-Length", String(page.length())); 714 | server->send(200, "text/html", page); 715 | 716 | DEBUG_WM(F("Sent info page")); 717 | } 718 | 719 | /** Handle the reset page */ 720 | void WiFiManager::handleReset() { 721 | DEBUG_WM(F("Reset")); 722 | 723 | String page = FPSTR(HTTP_HEAD); 724 | page.replace("{v}", "Info"); 725 | page += FPSTR(HTTP_SCRIPT); 726 | page += FPSTR(HTTP_STYLE); 727 | page += _customHeadElement; 728 | page += FPSTR(HTTP_HEAD_END); 729 | page += F("Module will reset in a few seconds."); 730 | page += FPSTR(HTTP_END); 731 | 732 | server->sendHeader("Content-Length", String(page.length())); 733 | server->send(200, "text/html", page); 734 | 735 | //Erose WiFi Credentials 736 | WiFi.disconnect(true); 737 | 738 | DEBUG_WM(F("Sent reset page")); 739 | delay(3000); 740 | ESP.reset(); 741 | delay(2000); 742 | } 743 | 744 | // Called when /update is requested 745 | void WiFiManager::handleUpdate() { 746 | DEBUG_WM(F("<- Handle update")); 747 | if (captivePortal()) return; // If captive portal redirect instead of displaying the page 748 | 749 | //str.replace(FPSTR(T_v), configPortalActive ? _apName : WiFi.localIP().toString()); // use ip if ap is not active for heading 750 | //page += str; 751 | String page = FPSTR(HTTP_HEAD); 752 | page.replace("{v}", "Update"); 753 | 754 | page += FPSTR(HTTP_STYLE); 755 | page += FPSTR(HTTP_UPDATE); 756 | page += FPSTR(HTTP_END); 757 | 758 | server->sendHeader("Content-Length", String(page.length())); 759 | server->send(200, "text/html", page); 760 | 761 | } 762 | 763 | // upload via /u POST 764 | void WiFiManager::handleUpdating(){ 765 | // @todo 766 | // cannot upload files in captive portal, file select is not allowed, show message with link or hide 767 | // cannot upload if softreset after upload, maybe check for hard reset at least for dev, ERROR[11]: Invalid bootstrapping state, reset ESP8266 before updating 768 | // add upload status to webpage somehow 769 | // abort upload if error detected ? 770 | // [x] supress cp timeout on upload, so it doesnt keep uploading? 771 | // add progress handler for debugging 772 | // combine route handlers into one callback and use argument or post checking instead of mutiple functions maybe, if POST process else server upload page? 773 | // [x] add upload checking, do we need too check file? 774 | // convert output to debugger if not moving to example 775 | if (captivePortal()) return; // If captive portal redirect instead of displaying the page 776 | bool error = false; 777 | unsigned long _configPortalTimeoutSAV = _configPortalTimeout; // store cp timeout 778 | _configPortalTimeout = 0; // disable timeout 779 | 780 | // handler for the file upload, get's the sketch bytes, and writes 781 | // them through the Update object 782 | HTTPUpload& upload = server->upload(); 783 | 784 | // UPLOAD START 785 | if (upload.status == UPLOAD_FILE_START) { 786 | if(_debug) Serial.setDebugOutput(true); 787 | 788 | #ifdef ESP8266 789 | WiFiUDP::stopAll(); 790 | uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; 791 | #elif defined(ESP32) 792 | // Think we do not need to stop WiFIUDP because we haven't started a listener 793 | uint32_t maxSketchSpace = (ESP.getFlashChipSize() - 0x1000) & 0xFFFFF000; 794 | #endif 795 | 796 | Serial.printf("Update: %s\r\n", upload.filename.c_str()); 797 | 798 | if (!Update.begin(maxSketchSpace)) { // start with max available size 799 | Update.printError(Serial); // size error 800 | error = true; 801 | } 802 | } 803 | // UPLOAD WRITE 804 | else if (upload.status == UPLOAD_FILE_WRITE) { 805 | Serial.print("."); 806 | if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { 807 | Update.printError(Serial); // write failure 808 | error = true; 809 | } 810 | } 811 | // UPLOAD FILE END 812 | else if (upload.status == UPLOAD_FILE_END) { 813 | if (Update.end(true)) { // true to set the size to the current progress 814 | Serial.printf("Updated: %u bytes\r\nRebooting...\r\n", upload.totalSize); 815 | } 816 | else { 817 | Update.printError(Serial); 818 | error = true; 819 | } 820 | } 821 | // UPLOAD ABORT 822 | else if (upload.status == UPLOAD_FILE_ABORTED) { 823 | Update.end(); 824 | DEBUG_WM(F("[OTA] Update was aborted")); 825 | error = true; 826 | } 827 | if(error) _configPortalTimeout = _configPortalTimeoutSAV; 828 | delay(0); 829 | } 830 | 831 | // upload and ota done, show status 832 | void WiFiManager::handleUpdateDone() { 833 | DEBUG_WM(F("<- Handle update done")); 834 | if (captivePortal()) return; // If captive portal redirect instead of displaying the page 835 | 836 | //str.replace(FPSTR(T_v), configPortalActive ? _apName : WiFi.localIP().toString()); // use ip if ap is not active for heading 837 | //page += str; 838 | String page = FPSTR(HTTP_HEAD); 839 | page.replace("{v}", "Update Done"); 840 | 841 | if (Update.hasError()) { 842 | page += FPSTR(HTTP_UPDATE_FAIL); 843 | DEBUG_WM(F("[OTA] update failed")); 844 | } 845 | else { 846 | page += FPSTR(HTTP_UPDATE_OK); 847 | DEBUG_WM(F("[OTA] update ok")); 848 | } 849 | page += FPSTR(HTTP_END); 850 | 851 | server->sendHeader("Content-Length", String(page.length())); 852 | server->send(200, "text/html", page); 853 | 854 | delay(1000); // send page 855 | if (!Update.hasError()) { 856 | ESP.restart(); 857 | } 858 | } 859 | 860 | void WiFiManager::handleNotFound() { 861 | if (captivePortal()) { // If captive portal redirect instead of displaying the error page. 862 | return; 863 | } 864 | String message = "File Not Found\n\n"; 865 | message += "URI: "; 866 | message += server->uri(); 867 | message += "\nMethod: "; 868 | message += ( server->method() == HTTP_GET ) ? "GET" : "POST"; 869 | message += "\nArguments: "; 870 | message += server->args(); 871 | message += "\n"; 872 | 873 | for ( uint8_t i = 0; i < server->args(); i++ ) { 874 | message += " " + server->argName ( i ) + ": " + server->arg ( i ) + "\n"; 875 | } 876 | server->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); 877 | server->sendHeader("Pragma", "no-cache"); 878 | server->sendHeader("Expires", "-1"); 879 | server->sendHeader("Content-Length", String(message.length())); 880 | server->send ( 404, "text/plain", message ); 881 | } 882 | 883 | 884 | /** Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */ 885 | boolean WiFiManager::captivePortal() { 886 | if (!isIp(server->hostHeader()) ) { 887 | DEBUG_WM(F("Request redirected to captive portal")); 888 | server->sendHeader("Location", String("http://") + toStringIp(server->client().localIP()), true); 889 | server->send (302, "text/plain", "302: Found"); // Empty content inhibits Content-length header so we have to close the socket ourselves. 890 | //server->client().stop(); // Stop is needed because we sent no content length 891 | return true; 892 | } 893 | return false; 894 | } 895 | 896 | //start up config portal callback 897 | void WiFiManager::setAPCallback( void (*func)(WiFiManager* myWiFiManager) ) { 898 | _apcallback = func; 899 | } 900 | 901 | //start up save config callback 902 | void WiFiManager::setSaveConfigCallback( void (*func)(void) ) { 903 | _savecallback = func; 904 | } 905 | 906 | //sets a custom element to add to head, like a new style tag 907 | void WiFiManager::setCustomHeadElement(const char* element) { 908 | _customHeadElement = element; 909 | } 910 | 911 | //if this is true, remove duplicated Access Points - defaut true 912 | void WiFiManager::setRemoveDuplicateAPs(boolean removeDuplicates) { 913 | _removeDuplicateAPs = removeDuplicates; 914 | } 915 | 916 | 917 | 918 | template 919 | void WiFiManager::DEBUG_WM(Generic text) { 920 | if (_debug) { 921 | Serial.print("*WM: "); 922 | Serial.println(text); 923 | } 924 | } 925 | 926 | int WiFiManager::getRSSIasQuality(int RSSI) { 927 | int quality = 0; 928 | 929 | if (RSSI <= -100) { 930 | quality = 0; 931 | } else if (RSSI >= -50) { 932 | quality = 100; 933 | } else { 934 | quality = 2 * (RSSI + 100); 935 | } 936 | return quality; 937 | } 938 | 939 | /** Is this an IP? */ 940 | boolean WiFiManager::isIp(String str) { 941 | for (size_t i = 0; i < str.length(); i++) { 942 | int c = str.charAt(i); 943 | if (c != '.' && (c < '0' || c > '9')) { 944 | return false; 945 | } 946 | } 947 | return true; 948 | } 949 | 950 | /** IP to String? */ 951 | String WiFiManager::toStringIp(IPAddress ip) { 952 | String res = ""; 953 | for (int i = 0; i < 3; i++) { 954 | res += String((ip >> (8 * i)) & 0xFF) + "."; 955 | } 956 | res += String(((ip >> 8 * 3)) & 0xFF); 957 | return res; 958 | } 959 | -------------------------------------------------------------------------------- /WiFi-Doorbell-DB100/src/WiFiManager.h: -------------------------------------------------------------------------------- 1 | /************************************************************** 2 | WiFiManager is a library for the ESP8266/Arduino platform 3 | (https://github.com/esp8266/Arduino) to enable easy 4 | configuration and reconfiguration of WiFi credentials using a Captive Portal 5 | inspired by: 6 | http://www.esp8266.com/viewtopic.php?f=29&t=2520 7 | https://github.com/chriscook8/esp-arduino-apboot 8 | https://github.com/esp8266/Arduino/tree/master/libraries/DNSServer/examples/CaptivePortalAdvanced 9 | Built by AlexT https://github.com/tzapu 10 | Licensed under MIT license 11 | **************************************************************/ 12 | 13 | #ifndef WiFiManager_h 14 | #define WiFiManager_h 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | extern "C" { 22 | #include "user_interface.h" 23 | } 24 | 25 | const char HTTP_HEAD[] PROGMEM = "{v}"; 26 | const char HTTP_STYLE[] PROGMEM = ""; 27 | const char HTTP_SCRIPT[] PROGMEM = ""; 28 | const char HTTP_HEAD_END[] PROGMEM = ""; 29 | const char HTTP_PORTAL_OPTIONS[] PROGMEM = ""; 30 | const char HTTP_ITEM[] PROGMEM = "
  • {v}
  • "; 31 | const char HTTP_FORM_START[] PROGMEM = "



    Settings

    "; 32 | const char HTTP_FORM_PARAM[] PROGMEM = ""; 33 | const char HTTP_FORM_END[] PROGMEM = ""; 34 | const char HTTP_SAVED[] PROGMEM = "
    Settings Saved
    Trying to connect to network.
    If it fails reconnect to AP to try again
    "; 35 | const char HTTP_UPDATE[] PROGMEM = "
    "; 36 | const char HTTP_UPDATE_FAIL[] PROGMEM = "
    Update Failed!
    Reboot device and try again
    "; 37 | const char HTTP_UPDATE_OK[] PROGMEM = "
    Update OK! Device Rebooting now...
    "; 38 | const char HTTP_END[] PROGMEM = ""; 39 | 40 | #ifndef WIFI_MANAGER_MAX_PARAMS 41 | #define WIFI_MANAGER_MAX_PARAMS 10 42 | #endif 43 | 44 | class WiFiManagerParameter { 45 | public: 46 | /** 47 | Create custom parameters that can be added to the WiFiManager setup web page 48 | @id is used for HTTP queries and must not contain spaces nor other special characters 49 | */ 50 | WiFiManagerParameter(const char *custom); 51 | WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length); 52 | WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom); 53 | ~WiFiManagerParameter(); 54 | 55 | const char *getID(); 56 | const char *getValue(); 57 | const char *getPlaceholder(); 58 | int getValueLength(); 59 | const char *getCustomHTML(); 60 | private: 61 | const char *_id; 62 | const char *_placeholder; 63 | char *_value; 64 | int _length; 65 | const char *_customHTML; 66 | 67 | void init(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom); 68 | 69 | friend class WiFiManager; 70 | }; 71 | 72 | 73 | class WiFiManager 74 | { 75 | public: 76 | WiFiManager(); 77 | ~WiFiManager(); 78 | 79 | boolean autoConnect(); 80 | boolean autoConnect(char const *apName, char const *apPassword = NULL); 81 | 82 | //if you want to always start the config portal, without trying to connect first 83 | boolean startConfigPortal(); 84 | boolean startConfigPortal(char const *apName, char const *apPassword = NULL); 85 | 86 | // get the AP name of the config portal, so it can be used in the callback 87 | String getConfigPortalSSID(); 88 | 89 | void resetSettings(); 90 | 91 | //sets timeout before webserver loop ends and exits even if there has been no setup. 92 | //useful for devices that failed to connect at some point and got stuck in a webserver loop 93 | //in seconds setConfigPortalTimeout is a new name for setTimeout 94 | void setConfigPortalTimeout(unsigned long seconds); 95 | void setTimeout(unsigned long seconds); 96 | 97 | //sets timeout for which to attempt connecting, useful if you get a lot of failed connects 98 | void setConnectTimeout(unsigned long seconds); 99 | 100 | 101 | void setDebugOutput(boolean debug); 102 | //defaults to not showing anything under 8% signal quality if called 103 | void setMinimumSignalQuality(int quality = 8); 104 | //sets a custom ip /gateway /subnet configuration 105 | void setAPStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn); 106 | //sets config for a static IP 107 | 108 | void setSTAStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn); 109 | 110 | //called when AP mode and config portal is started 111 | void setAPCallback( void (*func)(WiFiManager*) ); 112 | //called when settings have been changed and connection was successful 113 | void setSaveConfigCallback( void (*func)(void) ); 114 | //adds a custom parameter, returns false on failure 115 | bool addParameter(WiFiManagerParameter *p); 116 | //if this is set, it will exit after config, even if connection is unsuccessful. 117 | void setBreakAfterConfig(boolean shouldBreak); 118 | //if this is set, try WPS setup when starting (this will delay config portal for up to 2 mins) 119 | //TODO 120 | //if this is set, customise style 121 | void setCustomHeadElement(const char* element); 122 | //if this is true, remove duplicated Access Points - defaut true 123 | void setRemoveDuplicateAPs(boolean removeDuplicates); 124 | 125 | private: 126 | std::unique_ptr dnsServer; 127 | std::unique_ptr server; 128 | 129 | //const int WM_DONE = 0; 130 | //const int WM_WAIT = 10; 131 | 132 | //const String HTTP_HEAD = "{v}"; 133 | 134 | void setupConfigPortal(); 135 | void startWPS(); 136 | 137 | const char* _apName = "no-net"; 138 | const char* _apPassword = NULL; 139 | String _ssid = ""; 140 | String _pass = ""; 141 | unsigned long _configPortalTimeout = 0; 142 | unsigned long _connectTimeout = 0; 143 | unsigned long _configPortalStart = 0; 144 | 145 | IPAddress _ap_static_ip; 146 | IPAddress _ap_static_gw; 147 | IPAddress _ap_static_sn; 148 | IPAddress _sta_static_ip; 149 | IPAddress _sta_static_gw; 150 | IPAddress _sta_static_sn; 151 | 152 | int _paramsCount = 0; 153 | int _minimumQuality = -1; 154 | boolean _removeDuplicateAPs = true; 155 | boolean _shouldBreakAfterConfig = false; 156 | boolean _tryWPS = false; 157 | 158 | const char* _customHeadElement = ""; 159 | 160 | //String getEEPROMString(int start, int len); 161 | //void setEEPROMString(int start, int len, String string); 162 | 163 | int status = WL_IDLE_STATUS; 164 | int connectWifi(String ssid, String pass); 165 | uint8_t waitForConnectResult(); 166 | 167 | void handleRoot(); 168 | void handleWifi(boolean scan); 169 | void handleWifiSave(); 170 | void handleInfo(); 171 | void handleReset(); 172 | void handleNotFound(); 173 | void handle204(); 174 | boolean captivePortal(); 175 | boolean configPortalHasTimeout(); 176 | 177 | //OTA Update 178 | void handleUpdate(); 179 | void handleUpdating(); 180 | void handleUpdateDone(); 181 | 182 | // DNS server 183 | const byte DNS_PORT = 53; 184 | 185 | //helpers 186 | int getRSSIasQuality(int RSSI); 187 | boolean isIp(String str); 188 | String toStringIp(IPAddress ip); 189 | 190 | boolean connect; 191 | boolean _debug = true; 192 | 193 | void (*_apcallback)(WiFiManager*) = NULL; 194 | void (*_savecallback)(void) = NULL; 195 | 196 | int _max_params; 197 | WiFiManagerParameter** _params; 198 | 199 | template 200 | void DEBUG_WM(Generic text); 201 | 202 | template 203 | auto optionalIPFromString(T *obj, const char *s) -> decltype( obj->fromString(s) ) { 204 | return obj->fromString(s); 205 | } 206 | auto optionalIPFromString(...) -> bool { 207 | DEBUG_WM("NO fromString METHOD ON IPAddress, you need ESP8266 core 2.1.0 or newer for Custom IP configuration to work."); 208 | return false; 209 | } 210 | }; 211 | 212 | #endif 213 | -------------------------------------------------------------------------------- /images/ConfigureDevice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fireflyelectronix/firefly_doorbell_sensor/6461aabd481c3a047f1b53993a7bd50023cd4552/images/ConfigureDevice.png -------------------------------------------------------------------------------- /images/DB100-Render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fireflyelectronix/firefly_doorbell_sensor/6461aabd481c3a047f1b53993a7bd50023cd4552/images/DB100-Render.png -------------------------------------------------------------------------------- /images/HA-config-automate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fireflyelectronix/firefly_doorbell_sensor/6461aabd481c3a047f1b53993a7bd50023cd4552/images/HA-config-automate.png -------------------------------------------------------------------------------- /images/Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fireflyelectronix/firefly_doorbell_sensor/6461aabd481c3a047f1b53993a7bd50023cd4552/images/Settings.png -------------------------------------------------------------------------------- /images/action-fields.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fireflyelectronix/firefly_doorbell_sensor/6461aabd481c3a047f1b53993a7bd50023cd4552/images/action-fields.PNG -------------------------------------------------------------------------------- /images/action-service.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fireflyelectronix/firefly_doorbell_sensor/6461aabd481c3a047f1b53993a7bd50023cd4552/images/action-service.PNG -------------------------------------------------------------------------------- /images/button-press.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fireflyelectronix/firefly_doorbell_sensor/6461aabd481c3a047f1b53993a7bd50023cd4552/images/button-press.png -------------------------------------------------------------------------------- /images/choose-service.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fireflyelectronix/firefly_doorbell_sensor/6461aabd481c3a047f1b53993a7bd50023cd4552/images/choose-service.PNG -------------------------------------------------------------------------------- /images/copy-ifttt-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fireflyelectronix/firefly_doorbell_sensor/6461aabd481c3a047f1b53993a7bd50023cd4552/images/copy-ifttt-key.png -------------------------------------------------------------------------------- /images/event-name.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fireflyelectronix/firefly_doorbell_sensor/6461aabd481c3a047f1b53993a7bd50023cd4552/images/event-name.PNG -------------------------------------------------------------------------------- /images/ifttt-screenshot-crop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fireflyelectronix/firefly_doorbell_sensor/6461aabd481c3a047f1b53993a7bd50023cd4552/images/ifttt-screenshot-crop.png -------------------------------------------------------------------------------- /images/j2-pins.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fireflyelectronix/firefly_doorbell_sensor/6461aabd481c3a047f1b53993a7bd50023cd4552/images/j2-pins.JPG -------------------------------------------------------------------------------- /images/mqtt-action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fireflyelectronix/firefly_doorbell_sensor/6461aabd481c3a047f1b53993a7bd50023cd4552/images/mqtt-action.png -------------------------------------------------------------------------------- /images/mqtt-trigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fireflyelectronix/firefly_doorbell_sensor/6461aabd481c3a047f1b53993a7bd50023cd4552/images/mqtt-trigger.png -------------------------------------------------------------------------------- /images/remove-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fireflyelectronix/firefly_doorbell_sensor/6461aabd481c3a047f1b53993a7bd50023cd4552/images/remove-cover.png -------------------------------------------------------------------------------- /images/wiring-diagram-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fireflyelectronix/firefly_doorbell_sensor/6461aabd481c3a047f1b53993a7bd50023cd4552/images/wiring-diagram-2.png -------------------------------------------------------------------------------- /images/wiring-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fireflyelectronix/firefly_doorbell_sensor/6461aabd481c3a047f1b53993a7bd50023cd4552/images/wiring-diagram.png --------------------------------------------------------------------------------