/////////////////////////////////////////////////////////////////////////// // Copyright (C) Wizardry and Steamworks 2024 - License: GNU MIT // // Please see: http://www.gnu.org/licenses/gpl.html for legal details, // // rights of fair usage, the disclaimer and warranty conditions. // /////////////////////////////////////////////////////////////////////////// // As it is very much needed sometimes to manipulate GPIO pins, and then // // by combining the wifipreboot environment, the following template for // // the Arduino can be used to manipulate GPIO pins on the low level via // // a common MQTT broker that the template is meant to connect to. // // // // The full documentation can be found on: // // * https://grimore.org/arduino/gpio_tool // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // configurable parameters // /////////////////////////////////////////////////////////////////////////// // comment out to enable debugging #define DEBUG // set the master password for OTA updates and access to the soft AP #define PREBOOT_MASTER_PASSWORD "" // the name and length of the cookie to use for authentication #define PREBOOT_COOKIE_NAME "ArduinoPrebootCookie" #define PREBOOT_COOKIE_MAX_LENGTH 256 // timeout to establish STA connection in milliseconds #define WIFI_RETRY_TIMEOUT 10000 // retries as multiples of WIFI_RETRY_TIMEOUT milliseconds #define WIFI_CONNECT_TRIES 30 // the time between blinking a single digit #define BLINK_DIT_LENGTH 250 // the time between blinking the whole number #define BLINK_DAH_LENGTH 2500 // GPIO application // The MQTT broker to connect to. #define MQTT_HOST "" // The MQTT broker username. #define MQTT_USERNAME "" // The MQTT broker password. #define MQTT_PASSWORD "" // The MQTT broker port. #define MQTT_PORT 1883 // The MQTT topic #define MQTT_TOPIC "" // Maximal size of an MQTT payload #define MQTT_PAYLOAD_MAX_LENGTH 256 /////////////////////////////////////////////////////////////////////////// // includes // /////////////////////////////////////////////////////////////////////////// #include #if defined(ARDUINO_ARCH_ESP32) #include #include #elif defined(ESP8266) #include #include #include #endif #include #include #include // Arduino OTA #include #include #include // GPIO application #include // Platform specific defines. #if defined(ARDUINO_ARCH_ESP8266) #define GET_CHIP_ID() (ESP.getChipId()) #elif defined(ARDUINO_ARCH_ESP32) #define GET_CHIP_ID() ((uint16_t)(ESP.getEfuseMac() >> 32)) #endif #define HOSTNAME() String("esp-" + String(GET_CHIP_ID(), HEX)) #define CONFIGURATION_FILE_NAME "/config.json" #define CONFIGURATION_MAX_LENGTH 1024 /////////////////////////////////////////////////////////////////////////// // function definitions // /////////////////////////////////////////////////////////////////////////// byte* getHardwareAddress(void); char* getHardwareAddress(char colon); String computeTemporarySsid(void); void arduinoOtaTickCallback(void); void blinkDigitsDahTickCallback(void); void blinkDigitsDitTickCallback(void); void blinkDigitsBlinkTickCallback(void); void clientWifiTickCallback(void); void serverWifiTickCallback(void); void handleServerWifi(void); void handleClientWifi(void); void setConfiguration(const char* configurationFile, DynamicJsonDocument configuration, int bufferSize); DynamicJsonDocument getConfiguration(const char* configurationFile, int bufferSize); void handleRootHttpRequest(void); void handleSetupHttpRequest(void); void handleRootHttpGet(void); void handleSetupHttpGet(void); void handleRootHttpPost(void); void handleSetupHttpPost(void); void handleHttpNotFound(void); bool fsWriteFile(fs::FS &fs, const char *path, const char *payload); bool fsReadFile(fs::FS &fs, const char *path, char *payload, size_t maxLength); void rebootTickCallback(void); // GPIO application void gpioDebounceTickCallback(void); void mqttTickCallback(void); String getMqttTopic(void); String getMqttId(void); void mqttGpioSet(DynamicJsonDocument doc); DynamicJsonDocument mqttGpioGet(const DynamicJsonDocument doc); void mqttGpioDebounce(DynamicJsonDocument doc); DynamicJsonDocument mqttGpioMeasure(const DynamicJsonDocument doc); constexpr unsigned int mqttGpioActionHash(const char *s, int off = 0); char *mqttSerialize(const JsonDocument doc, size_t maxLength); /////////////////////////////////////////////////////////////////////////// // variable declarations // /////////////////////////////////////////////////////////////////////////// #if defined(ARDUINO_ARCH_ESP8266) ESP8266WebServer server(80); #elif defined(ARDUINO_ARCH_ESP32) WebServer server(80); #endif TickTwo arduinoOtaTick(arduinoOtaTickCallback, 1000); TickTwo rebootTick(rebootTickCallback, 1000); TickTwo clientWifiTick(clientWifiTickCallback, 25); TickTwo serverWifiTick(serverWifiTickCallback, 250); TickTwo blinkDigitsDahTick(blinkDigitsDahTickCallback, BLINK_DAH_LENGTH); TickTwo blinkDigitsDitTick(blinkDigitsDitTickCallback, BLINK_DIT_LENGTH); TickTwo blinkDigitsBlinkTick(blinkDigitsBlinkTickCallback, 25); char* authenticationCookie = NULL; bool otaStarted; bool networkConnected; int connectionTries; bool rebootPending; int temporarySsidLength; int temporarySsidIndex; int* temporarySsidNumbers; int blinkLedState; // GPIO application WiFiClient espClient; PubSubClient mqttClient(espClient); // GPIO application TickTwo gpioDebounceTick(gpioDebounceTickCallback, 25, 1); TickTwo mqttTick(mqttTickCallback, 250); // GPIO application int gpioDebouncePin; int gpioDebounceSleep; String gpioDebounceMode; /////////////////////////////////////////////////////////////////////////// // HTML templates // /////////////////////////////////////////////////////////////////////////// const char* HTML_BOOT_TEMPLATE = R"html( ESP Setup

ESP Setup


AP: %AP%
MAC: %MAC%




)html"; const char* HTML_AUTH_TEMPLATE = R"html( Preboot Access

Preboot Access


)html"; /////////////////////////////////////////////////////////////////////////// // begin Arduino // /////////////////////////////////////////////////////////////////////////// void setup() { #ifdef DEBUG Serial.begin(115200); // wait for serial while (!Serial) { delay(100); } Serial.println(); #endif #if defined(ARDUINO_ARCH_ESP8266) if (!LittleFS.begin()) { #ifdef DEBUG Serial.println("LittleFS mount failed, formatting and rebooting..."); #endif LittleFS.format(); delay(1000); ESP.restart(); #elif defined(ARDUINO_ARCH_ESP32) if (!LittleFS.begin(true)) { #endif Serial.println("LittleFS mount failed..."); return; } #ifdef DEBUG Serial.printf("Checking if WiFi server must be started...\n"); #endif // check if Ssid is set and start soft AP or STA mode DynamicJsonDocument configuration = getConfiguration(CONFIGURATION_FILE_NAME, CONFIGURATION_MAX_LENGTH); if(configuration.isNull() || !configuration.containsKey("Ssid")) { #ifdef DEBUG Serial.printf("No stored STA Ssid found, proceeding to soft AP...\n"); #endif // start soft AP rebootTick.start(); serverWifiTick.start(); return; } #ifdef DEBUG Serial.printf("No stored STA Ssid found, proceeding to soft AP...\n"); #endif clientWifiTick.start(); // setup OTA ArduinoOTA.setHostname(configuration["name"].as()); // allow flashing with the master password ArduinoOTA.setPassword(PREBOOT_MASTER_PASSWORD); ArduinoOTA.onStart([]() { String type; if (ArduinoOTA.getCommand() == U_FLASH) { type = "sketch"; } else { // U_FS type = "filesystem"; } // NOTE: if updating FS this would be the place to unmount FS using FS.end() Serial.println("Start updating " + type); }); ArduinoOTA.onEnd([]() { Serial.println("\nEnd"); }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }); ArduinoOTA.onError([](ota_error_t error) { Serial.printf("Error[%u]: ", error); if (error == OTA_AUTH_ERROR) { Serial.println("Auth Failed"); } else if (error == OTA_BEGIN_ERROR) { Serial.println("Begin Failed"); } else if (error == OTA_CONNECT_ERROR) { Serial.println("Connect Failed"); } else if (error == OTA_RECEIVE_ERROR) { Serial.println("Receive Failed"); } else if (error == OTA_END_ERROR) { Serial.println("End Failed"); } }); // start timers / threads arduinoOtaTick.start(); rebootTick.start(); // GPIO application mqttTick.start(); } void loop() { arduinoOtaTick.update(); rebootTick.update(); clientWifiTick.update(); serverWifiTick.update(); blinkDigitsDitTick.update(); blinkDigitsDahTick.update(); blinkDigitsBlinkTick.update(); // GPIO application gpioDebounceTick.update(); mqttTick.update(); } /////////////////////////////////////////////////////////////////////////// // end Arduino // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // GPIO-MQTT // /////////////////////////////////////////////////////////////////////////// constexpr unsigned int mqttGpioActionHash(const char *s, int off) { return !s[off] ? 5381 : (mqttGpioActionHash(s, off+1)*33) ^ s[off]; } void gpioDebounceTickCallback(void) { gpioDebounceTick.pause(); #ifdef DEBUG Serial.printf("Pin %d debounce.\n", gpioDebouncePin); #endif switch(mqttGpioActionHash(gpioDebounceMode.c_str())) { case mqttGpioActionHash("LTH"): digitalWrite(gpioDebouncePin, HIGH); break; default: digitalWrite(gpioDebouncePin, LOW); break; } } String getMqttTopic(void) { String mqttTopic = String(MQTT_TOPIC); if(mqttTopic == NULL || mqttTopic.length() == 0) { DynamicJsonDocument configuration = getConfiguration(CONFIGURATION_FILE_NAME, CONFIGURATION_MAX_LENGTH); if(configuration.containsKey("name")) { mqttTopic = configuration["name"].as(); } } return mqttTopic; } String getMqttId(void) { String mqttId = String(HOSTNAME()); DynamicJsonDocument configuration = getConfiguration(CONFIGURATION_FILE_NAME, CONFIGURATION_MAX_LENGTH); if(configuration.containsKey("name")) { mqttId = configuration["name"].as(); } return mqttId; } void mqttGpioSet(DynamicJsonDocument doc) { String state = doc["state"].as(); const int pin = doc["pin"].as(); #ifdef DEBUG Serial.printf("Setting pin %d to state %s...\n", pin, state); #endif pinMode(pin, OUTPUT); if (state == "on") { digitalWrite(pin, HIGH); int pinStatus = digitalRead(pin); #ifdef DEBUG Serial.printf("Pin %d state is now %d.\n", pin, pinStatus); #endif return; } digitalWrite(pin, LOW); int pinStatus = digitalRead(pin); #ifdef DEBUG Serial.printf("Pin %s state is now %d.\n", pinStatus); #endif } void mqttGpioDebounce(DynamicJsonDocument doc) { gpioDebounceSleep = doc["sleep"].as(); gpioDebouncePin = doc["pin"].as(); if(doc.containsKey("mode")) { gpioDebounceMode = String(doc["mode"].as()); gpioDebounceMode.trim(); gpioDebounceMode.toUpperCase(); } #ifdef DEBUG Serial.printf("Pin %d bounce.\n", gpioDebouncePin); #endif pinMode(gpioDebouncePin, OUTPUT); switch(mqttGpioActionHash(gpioDebounceMode.c_str())) { case mqttGpioActionHash("LTH"): digitalWrite(gpioDebouncePin, LOW); break; default: digitalWrite(gpioDebouncePin, HIGH); break; } #ifdef DEBUG Serial.printf("Debouncing pin %d with sleep %d and mode %s.\n", gpioDebouncePin, gpioDebounceSleep, gpioDebounceMode.c_str()); #endif gpioDebounceTick.interval(gpioDebounceSleep); gpioDebounceTick.resume(); } DynamicJsonDocument mqttGpioGet(const DynamicJsonDocument doc) { const int pin = doc["pin"].as(); #ifdef DEBUG Serial.printf("Getting pin: %d state.\n", pin); #endif // Set up digital read pins. pinMode(3, FUNCTION_3); int pinStatus = digitalRead(pin); #ifdef DEBUG Serial.printf("Pin %d state is %d.\n", pin, pinStatus); #endif // Announce the action. DynamicJsonDocument msg(MQTT_PAYLOAD_MAX_LENGTH); msg["pin"] = pin; switch (pinStatus) { case 1: msg["state"] = "on"; break; case 0: msg["state"] = "off"; break; default: msg["state"] = "unknown"; break; } return msg; } DynamicJsonDocument mqttGpioMeasure(const DynamicJsonDocument doc) { const int pin = doc["pin"].as(); #ifdef DEBUG Serial.printf("Getting analog pin %d state...\n", pin); #endif int analogValue = analogRead(pin); #ifdef DEBUG Serial.printf("Value of analog pin %d is %d.\n", pin, analogValue); #endif // Announce the analog value. DynamicJsonDocument msg(MQTT_PAYLOAD_MAX_LENGTH); msg["pin"] = pin; msg["value"] = analogValue; return msg; } char *mqttSerialize(const JsonDocument doc, size_t maxLength) { char* buff = (char*) malloc(maxLength * sizeof(char)); serializeJson(doc, buff, maxLength); return buff; } void mqttCallback(char *topic, byte *payload, unsigned int length) { String msgTopic = String(topic); // do not listen on topics not subscribed to or on empty topics if(msgTopic.length() == 0 || !msgTopic.equals(getMqttTopic())) { return; } // payload is not null terminated and casting will not work char* msgPayload = (char*) malloc((length +1) * sizeof(char)); snprintf(msgPayload, length + 1, "%s", payload); #ifdef DEBUG Serial.printf("Message received on topic %s with payload %s...\n", topic, msgPayload); #endif // Parse the payload sent to the MQTT topic as a JSON document. DynamicJsonDocument doc(MQTT_PAYLOAD_MAX_LENGTH); #ifdef DEBUG Serial.println("Deserializing message..."); #endif DeserializationError error = deserializeJson(doc, msgPayload); if (error) { #ifdef DEBUG Serial.println("Failed to parse MQTT payload as JSON: " + String(error.c_str())); #endif free(msgPayload); return; } free(msgPayload); // Do not process messages without an action key. if (!doc.containsKey("action")) { return; } String action = String(doc["action"].as()); // normalize action action.trim(); action.toUpperCase(); if(action.length() == 0) { #ifdef DEBUG Serial.println("Empty action provided."); #endif return; } DynamicJsonDocument msg(MQTT_PAYLOAD_MAX_LENGTH); msg["execute"] = doc; switch(mqttGpioActionHash(action.c_str())) { case mqttGpioActionHash("SET"): mqttGpioSet(doc); break; case mqttGpioActionHash("GET"): msg = mqttGpioGet(doc); break; case mqttGpioActionHash("DEBOUNCE"): mqttGpioDebounce(doc); break; case mqttGpioActionHash("MEASURE"): msg = mqttGpioMeasure(doc); break; } msgPayload = mqttSerialize(msg, MQTT_PAYLOAD_MAX_LENGTH); mqttClient.publish(getMqttTopic().c_str(), msgPayload); free(msgPayload); } bool mqttConnect() { #ifdef DEBUG Serial.println("Attempting to connect to MQTT broker: " + String(MQTT_HOST)); #endif mqttClient.setServer(MQTT_HOST, MQTT_PORT); DynamicJsonDocument msg(MQTT_PAYLOAD_MAX_LENGTH); if (mqttClient.connect(getMqttId().c_str(), MQTT_USERNAME, MQTT_PASSWORD)) { #ifdef DEBUG Serial.println("Established connection with MQTT broker using client ID: " + getMqttId()); #endif mqttClient.setCallback(mqttCallback); msg["action"] = "connected"; char *payload = mqttSerialize(msg, MQTT_PAYLOAD_MAX_LENGTH); mqttClient.publish(getMqttTopic().c_str(), payload); free(payload); #ifdef DEBUG Serial.println("Attempting to subscribe to MQTT topic: " + getMqttTopic()); #endif if (!mqttClient.subscribe(getMqttTopic().c_str())) { #ifdef DEBUG Serial.println("Failed to subscribe to MQTT topic: " + getMqttTopic()); #endif return false; } #ifdef DEBUG Serial.println("Subscribed to MQTT topic: " + getMqttTopic()); #endif msg["action"] = "subscribed"; payload = mqttSerialize(msg, MQTT_PAYLOAD_MAX_LENGTH); mqttClient.publish(getMqttTopic().c_str(), payload); free(payload); return true; } #ifdef DEBUG Serial.println("Connection to MQTT broker failed with MQTT client state: " + String(mqttClient.state())); #endif return false; } /////////////////////////////////////////////////////////////////////////// // Arduino loop // /////////////////////////////////////////////////////////////////////////// void mqttTickCallback(void) { if(!networkConnected) { return; } // Process MQTT client loop. if (mqttClient.connected()) { mqttClient.loop(); return; } if (!mqttConnect()) { #ifdef DEBUG Serial.printf("Unable to connect to MQTT\n"); #endif } } /////////////////////////////////////////////////////////////////////////// // OTA updates // /////////////////////////////////////////////////////////////////////////// void arduinoOtaTickCallback(void) { ArduinoOTA.handle(); if(!networkConnected) { return; } if(!otaStarted) { ArduinoOTA.begin(); otaStarted = true; } } /////////////////////////////////////////////////////////////////////////// // system-wide reboot // /////////////////////////////////////////////////////////////////////////// void rebootTickCallback(void) { // check if a reboot has been scheduled. if(!rebootPending) { return; } #ifdef DEBUG Serial.printf("Reboot pending, restarting in 1s...\n"); #endif ESP.restart(); } /////////////////////////////////////////////////////////////////////////// // HTTP route handling // /////////////////////////////////////////////////////////////////////////// void handleRootHttpPost(void) { String password; for(int i = 0; i < server.args(); ++i) { if(server.argName(i) == "password") { password = server.arg(i); continue; } } if(!password.equals(PREBOOT_MASTER_PASSWORD)) { server.sendHeader("Location", "/"); server.sendHeader("Cache-Control", "no-cache"); server.send(302); return; } #ifdef DEBUG Serial.println("Authentication succeeded, setting cookie and redirecting."); #endif // clear old authentication cookie if(authenticationCookie != NULL) { free(authenticationCookie); authenticationCookie = NULL; } authenticationCookie = randomStringHex(8); char* buff = (char*) malloc(PREBOOT_COOKIE_MAX_LENGTH * sizeof(char)); snprintf(buff, PREBOOT_COOKIE_MAX_LENGTH, "%s=%s; Max-Age=600; SameSite=Strict", PREBOOT_COOKIE_NAME, authenticationCookie); #ifdef DEBUG Serial.printf("Preboot cookie set to: %s\n", buff); #endif server.sendHeader("Set-Cookie", buff); server.sendHeader("Location", "/setup"); server.sendHeader("Cache-Control", "no-cache"); server.send(302); free(buff); } void handleSetupHttpPost(void) { String espName, staSsid, password; for(int i = 0; i < server.args(); ++i) { if(server.argName(i) == "name") { espName = server.arg(i); continue; } if(server.argName(i) == "Ssid") { staSsid = server.arg(i); continue; } if(server.argName(i) == "password") { password = server.arg(i); continue; } } if(espName == NULL || staSsid == NULL || password == NULL) { server.sendHeader("Location", "/"); server.sendHeader("Cache-Control", "no-cache"); server.send(302); return; } #ifdef DEBUG Serial.printf("Ssid %s and password %s received from web application.\n", staSsid, password); #endif DynamicJsonDocument configuration(CONFIGURATION_MAX_LENGTH); configuration["name"] = espName; configuration["Ssid"] = staSsid; configuration["password"] = password; setConfiguration(CONFIGURATION_FILE_NAME, configuration, CONFIGURATION_MAX_LENGTH); server.send(200, "text/plain", "Parameters applied. Scheduling reboot..."); #ifdef DEBUG Serial.printf("Configuration applied...\n"); #endif rebootPending = true; } void handleRootHttpGet(void) { // send login form #ifdef DEBUG Serial.printf("Sending authentication webpage.\n"); #endif String processTemplate = String(HTML_AUTH_TEMPLATE); server.send(200, "text/html", processTemplate); } void handleSetupHttpGet(void) { DynamicJsonDocument configuration = getConfiguration(CONFIGURATION_FILE_NAME, CONFIGURATION_MAX_LENGTH); String espName = HOSTNAME(); if(configuration.containsKey("name")) { espName = configuration["name"].as(); } // send default boot webpage #ifdef DEBUG Serial.printf("Sending configuration form webpage.\n"); #endif String processTemplate = String(HTML_BOOT_TEMPLATE); processTemplate.replace("%AP%", computeTemporarySsid()); processTemplate.replace("%MAC%", getHardwareAddress(':')); processTemplate.replace("%NAME%", espName); server.send(200, "text/html", processTemplate); } void handleRootHttpRequest(void) { switch(server.method()) { case HTTP_GET: handleRootHttpGet(); break; case HTTP_POST: handleRootHttpPost(); break; } } void handleSetupHttpRequest(void) { #ifdef DEBUG Serial.println("HTTP setup request received."); #endif if(!server.hasHeader("Cookie")) { #ifdef DEBUG Serial.println("No cookie header found."); #endif server.sendHeader("Location", "/"); server.sendHeader("Cache-Control", "no-cache"); server.send(302); return; } String cookie = server.header("Cookie"); if(authenticationCookie == NULL || cookie.indexOf(authenticationCookie) == -1) { #ifdef DEBUG Serial.println("Authentication failed."); #endif server.sendHeader("Location", "/"); server.sendHeader("Cache-Control", "no-cache"); server.send(302); return; } switch(server.method()) { case HTTP_GET: #ifdef DEBUG Serial.printf("HTTP GET request received for setup.\n"); #endif handleSetupHttpGet(); break; case HTTP_POST: #ifdef DEBUG Serial.printf("HTTP POST request received for setup.\n"); #endif handleSetupHttpPost(); break; } } void handleHttpNotFound(void) { server.sendHeader("Location", "/"); server.send(302); } /////////////////////////////////////////////////////////////////////////// // LittleFS file operations // /////////////////////////////////////////////////////////////////////////// bool fsWriteFile(fs::FS &fs, const char *path, const char *payload) { #if defined(ARDUINO_ARCH_ESP8266) File file = fs.open(path, "w"); #elif defined(ARDUINO_ARCH_ESP32) File file = fs.open(path, FILE_WRITE); #endif if (!file) { #ifdef DEBUG Serial.println("Failed to open file for writing."); #endif return false; } bool success = file.println(payload); file.close(); return success; } bool fsReadFile(fs::FS &fs, const char *path, char *payload, size_t maxLength) { #if defined(ARDUINO_ARCH_ESP8266) File file = fs.open(path, "r"); #elif defined(ARDUINO_ARCH_ESP32) File file = fs.open(path); #endif if (!file || file.isDirectory()) { #ifdef DEBUG Serial.println("Failed to open file for reading."); #endif return false; } int i = 0; while(file.available() && i < maxLength) { payload[i] = file.read(); ++i; } file.close(); payload[i] = '\0'; return true; } /////////////////////////////////////////////////////////////////////////// // set the current configuration // /////////////////////////////////////////////////////////////////////////// void setConfiguration(const char* configurationFile, DynamicJsonDocument configuration, int bufferSize) { char payload[bufferSize]; serializeJson(configuration, payload, bufferSize); if(!fsWriteFile(LittleFS, configurationFile, payload)) { #ifdef DEBUG Serial.printf("Unable to store configuration.\n"); #endif } } /////////////////////////////////////////////////////////////////////////// // get the current configuration // /////////////////////////////////////////////////////////////////////////// DynamicJsonDocument getConfiguration(const char* configurationFile, int bufferSize) { DynamicJsonDocument configuration(bufferSize); #ifdef DEBUG Serial.printf("Attempting to read configuration...\n"); #endif char* payload = (char *) malloc(bufferSize * sizeof(char)); if (fsReadFile(LittleFS, configurationFile, payload, bufferSize)) { #ifdef DEBUG Serial.printf("Found a valid configuration payload...\n"); #endif DeserializationError error = deserializeJson(configuration, payload); if(error) { #ifdef DEBUG Serial.printf("Deserialization of configuration failed.\n"); #endif } } #ifdef DEBUG Serial.printf("Configuration read complete.\n"); #endif free(payload); return configuration; } /////////////////////////////////////////////////////////////////////////// // generate random string // /////////////////////////////////////////////////////////////////////////// char* randomStringHex(int length) { const char alphabet[] = "0123456789abcdef"; char* payload = (char*) malloc(length * sizeof(char)); int i; for (i=0; i WIFI_CONNECT_TRIES) { // zap the Ssid in order to start softAP if(configuration.containsKey("Ssid")) { configuration.remove("Ssid"); } if(configuration.containsKey("password")) { configuration.remove("password"); } setConfiguration(CONFIGURATION_FILE_NAME, configuration, CONFIGURATION_MAX_LENGTH); #ifdef DEBUG Serial.printf("Restarting in 1 second...\n"); #endif rebootPending = true; return; } #ifdef DEBUG Serial.printf("Attempting to establish WiFi STA connecton [%d/%d]\n", (WIFI_CONNECT_TRIES - connectionTries) + 1, WIFI_CONNECT_TRIES); #endif #if defined(ARDUINO_ARCH_ESP8266) WiFi.hostname(configuration["name"].as()); #elif defined(ARDUINO_ARCH_ESP32) WiFi.setHostname(configuration["name"].as()); #endif String Ssid = configuration["Ssid"].as(); String password = configuration["password"].as(); #ifdef DEBUG Serial.printf("Trying connection to %s with %s...\n", Ssid, password); #endif WiFi.begin(Ssid, password); } /////////////////////////////////////////////////////////////////////////// // blink the temporary Ssid // /////////////////////////////////////////////////////////////////////////// void blinkDigitsDahTickCallback(void) { // wait for the dits to complete if(blinkDigitsDitTick.state() != STOPPED) { return; } if(temporarySsidIndex >= temporarySsidLength) { blinkDigitsDahTick.stop(); blinkDigitsDitTick.stop(); blinkDigitsBlinkTick.stop(); free(temporarySsidNumbers); #ifdef DEBUG Serial.println(); Serial.println("Dah-dit blink sequence completed."); #endif return; } #ifdef DEBUG Serial.printf("Starting to blink %d times: ", temporarySsidNumbers[temporarySsidIndex]); #endif pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW); blinkDigitsDitTick.start(); } void blinkDigitsDitTickCallback(void) { #ifdef DEBUG Serial.printf("Dit: %d/%d\n", blinkDigitsDitTick.counter(), temporarySsidNumbers[temporarySsidIndex]); #endif if(blinkDigitsDitTick.counter() > temporarySsidNumbers[temporarySsidIndex]) { blinkDigitsDitTick.stop(); ++temporarySsidIndex; #ifdef DEBUG Serial.println("Dits completed..."); #endif return; } blinkDigitsDitTick.pause(); blinkDigitsBlinkTick.start(); } void blinkDigitsBlinkTickCallback(void) { if(blinkDigitsBlinkTick.counter() > 2) { blinkDigitsBlinkTick.stop(); blinkDigitsDitTick.resume(); return; } blinkLedState = !blinkLedState; digitalWrite(LED_BUILTIN, blinkLedState); }