commit 642100181d156cf829865d2254a193ddc82f7819 Author: Julian Metzler Date: Fri Feb 9 23:28:40 2018 +0100 Initial release diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8dc0329 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.tmp +Thumbs.db diff --git a/WiFi_Shield.ino b/WiFi_Shield.ino new file mode 100644 index 0000000..62b4deb --- /dev/null +++ b/WiFi_Shield.ino @@ -0,0 +1,573 @@ +/* + ESP8266 WiFi Shield for xatLabs IBIS converter + (C) 2017-2018 Julian Metzler +*/ + +/* + UPLOAD SETTINGS + + Board: Generic ESP8266 Module + Flash Mode: DIO + Flash Size: 4M + SPIFFS Size: 1M + Debug port: Disabled + Debug Level: None + Reset Method: ck + Flash Freq: 40 MHz + CPU Freq: 80 MHz + Upload Speed: 115200 +*/ + +#define ARDUINO_OTA_ENABLEDXXX +#define INIT_EEPROMXXX + +#include +//#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef ARDUINO_OTA_ENABLED +#include +#endif + +#include "ibis.h" + +/* + TYPEDEFS +*/ + +enum UpdateStatus { + US_NO_UPDATE, + US_AVAILABLE, + US_FAILED +}; + +/* + CONSTANTS +*/ + +#define PIN_STATUS 10 +#define PIN_CONFIG 0 + +#define WIFI_TIMEOUT 10000 + +/* + GLOBAL VARIABLES +*/ + +unsigned long HW_GROUP = 1; // Changes with hardware changes that require software changes +unsigned long FW_VERSION = 1802080001; // Changes with each release; must always increase +unsigned long SP_VERSION = 0; // Loaded from SPIFFS; changed with each SPIFFS build; must always increase (uses timestamp as version) + +// HTTPS update settings +//String UPDATE_HOST = "static.mezgrman.de"; +//int UPDATE_PORT = 443; + +// HTTP update settings +String UPDATE_URL_BASE = "http://static.mezgrman.de/firmware/wifi_shield/"; + +// Start time of the last WiFi connection attempt +unsigned long wifiTimer = 0; +bool wifiTimedOut = false; + +// Variables for Station WiFi +String STA_SSID; +String STA_PASS; +bool STA_SETUP = false; + +// Variables for Access Point WiFi +String AP_SSID = "xatLabs WiFi Shield"; +String AP_PASS = "xatlabs_wifi"; +bool AP_ACTIVE = false; + +// Variables for keeping track of the config button +volatile bool btnState = 0; // Current button state +volatile bool btnPressed = 0; // Flag to check if the last button press has already been processed +volatile unsigned long btnTimer = 0; // Start time of last button press (only while pressed) +volatile unsigned long btnDur = 0; // Duration of the last button press (only while released) + +ESP8266WebServer server(80); +WiFiServer IBISServer(5001); +WiFiClient client; + +/* + CONFIG FILE HANDLING +*/ + +void reset_EEPROM() { + for (byte i = 0; i < 96; i++) { + EEPROM.write(i, 0x00); + } + EEPROM.commit(); +} + +bool loadConfig() { + File configFile = SPIFFS.open("/config.json", "r"); + if (!configFile) { + return false; + } + + size_t size = configFile.size(); + if (size > 1024) { + return false; + } + + // Allocate a buffer to store contents of the file. + std::unique_ptr buf(new char[size]); + + // We don't use String here because ArduinoJson library requires the input + // buffer to be mutable. If you don't use ArduinoJson, you may as well + // use configFile.readString instead. + configFile.readBytes(buf.get(), size); + + StaticJsonBuffer<200> jsonBuffer; + JsonObject& json = jsonBuffer.parseObject(buf.get()); + + if (!json.success()) { + return false; + } + + /*STA_SSID = json["WiFiSSID"].as(); + STA_PASS = json["WiFiPassword"].as(); + STA_SETUP = json["WiFiSetup"];*/ + SP_VERSION = json["SPVersion"]; + + char curChar; + bool ssidSetup = false; + bool passSetup = false; + STA_SSID = ""; + for (byte i = 0; i < 32; i++) { + curChar = EEPROM.read(i); + if (curChar == 0x00) { + break; + } + STA_SSID += curChar; + ssidSetup = true; + } + STA_PASS = ""; + for (byte i = 0; i < 64; i++) { + curChar = EEPROM.read(i + 32); + if (curChar == 0x00) { + break; + } + STA_PASS += curChar; + passSetup = true; + } + STA_SETUP = (ssidSetup && passSetup); + + return true; +} + +bool saveConfig() { + StaticJsonBuffer<200> jsonBuffer; + JsonObject& json = jsonBuffer.createObject(); + /*json["WiFiSSID"] = STA_SSID; + json["WiFiPassword"] = STA_PASS; + json["WiFiSetup"] = STA_SETUP;*/ + json["SPVersion"] = SP_VERSION; + + File configFile = SPIFFS.open("/config.json", "w"); + if (!configFile) { + return false; + } + json.printTo(configFile); + + reset_EEPROM(); + for (byte i = 0; i < STA_SSID.length(); i++) { + EEPROM.write(i, STA_SSID.charAt(i)); + } + for (byte i = 0; i < STA_PASS.length(); i++) { + EEPROM.write(i + 32, STA_PASS.charAt(i)); + } + EEPROM.commit(); + + return true; +} + +/* + HELPER FUNCTIONS +*/ + +void setLED(bool state) { + digitalWrite(PIN_STATUS, state); +} + +void blinkLEDSingle(unsigned int duration) { + setLED(1); + delay(duration); + setLED(0); +} + +void blinkLEDLoop(unsigned int duration) { + blinkLEDSingle(duration); + delay(duration); +} + +/* + WEB SERVER +*/ + +void handleNotFound() { + String message = "File Not Found\n\n"; + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + for (uint8_t i = 0; i < server.args(); i++) { + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; + } + server.send(404, "text/plain", message); +} + +String formatPageBase(String content) { + String page; + page += ""; + page += ""; + page += ""; + page += ""; + page += ""; + page += ""; + page += "WiFi Shield"; + page += ""; + page += ""; + page += content; + page += ""; + page += ""; + return page; +} + +void handleRoot() { + String c; + c += "

WiFi Shield

"; + c += "
Current SSID: "; + if (AP_ACTIVE) { + c += AP_SSID; + } else if (STA_SETUP) { + c += STA_SSID; + } else { + c += "None"; + } + c += "
"; + c += "
"; + c += ""; + c += ""; + c += ""; + c += ""; + c += "
SSID
Password
"; + c += "
"; + c += "Check for firmware update"; + server.send(200, "text/html", formatPageBase(c)); +} + +void handle_wifi_setup() { + STA_SSID = server.arg("ssid"); + STA_PASS = server.arg("password"); + STA_SETUP = true; + saveConfig(); + server.sendHeader("Location", "/", true); + server.send(303, "text/plain", ""); + ESP.restart(); +} + +void handle_check_update() { + String c; + UpdateStatus fwStatus = checkForFWUpdate(); + UpdateStatus spStatus = checkForSPUpdate(); + c += "

Update Status

"; + c += ""; + c += ""; + if (fwStatus == US_AVAILABLE) { + c += ""; + c += ""; + } else if (fwStatus == US_FAILED) { + c += ""; + } else if (fwStatus == US_NO_UPDATE) { + c += ""; + } + c += ""; + c += ""; + c += ""; + if (spStatus == US_AVAILABLE) { + c += ""; + c += ""; + } else if (spStatus == US_FAILED) { + c += ""; + } else if (spStatus == US_NO_UPDATE) { + c += ""; + } + c += "
Firmware version:"; + c += "WS-"; + c += HW_GROUP; + c += "-"; + c += FW_VERSION; + c += "Update available
Update check failedNo update available
Filesystem version:"; + c += "WSF-"; + c += HW_GROUP; + c += "-"; + c += SP_VERSION; + c += "Update available
Update check failedNo update available
"; + server.send(200, "text/html", formatPageBase(c)); +} + +void handle_update_fw() { + server.sendHeader("Location", "/check-update", true); + server.send(303, "text/plain", ""); + if (checkForFWUpdate() == US_AVAILABLE) { + doFWUpdate(); + } +} + +void handle_update_sp() { + server.sendHeader("Location", "/check-update", true); + server.send(303, "text/plain", ""); + if (checkForSPUpdate() == US_AVAILABLE) { + doSPUpdate(); + } +} + +/* + INTERRUPT ROUTINES +*/ + +void ISR_config() { + // Read the config button state to determine if it has been pressed or released + btnState = !digitalRead(PIN_CONFIG); + // Calculate the last press duration + if (btnState) { + btnTimer = millis(); + btnDur = 0; + } else { + btnDur = millis() - btnTimer; + btnTimer = 0; + // Discard presses <= 50ms + if (btnDur > 50) { + btnPressed = 1; + } else { + btnDur = 0; + } + } +} + +/* + MAIN PROGRAM ROUTINES +*/ + +void doWiFiConfigViaWPS() { + WiFi.disconnect(); + WiFi.mode(WIFI_STA); + WiFi.beginWPSConfig(); + while (WiFi.status() != WL_CONNECTED) { + blinkLEDLoop(250); + } + STA_SSID = WiFi.SSID(); + STA_PASS = WiFi.psk(); + STA_SETUP = true; + saveConfig(); + for (int i = 0; i < 3; i++) { + blinkLEDLoop(125); + } +} + +void doWiFiConfigViaAP() { + WiFi.disconnect(); + WiFi.mode(WIFI_AP); + WiFi.softAP(AP_SSID.c_str(), AP_PASS.c_str()); + AP_ACTIVE = true; + setLED(1); +} + +UpdateStatus checkForFWUpdate() { + /*WiFiClientSecure httpsClient; + if (!httpsClient.connect(UPDATE_HOST.c_str(), UPDATE_PORT)) { + //Serial.print("connection failed"); + } + httpsClient.println("GET /firmware/wifi_shield/1/firmware.version HTTP/1.1"); + httpsClient.println("Host: " + UPDATE_HOST); + httpsClient.println("Connection: close"); + httpsClient.println(); + delay(1000); + while (httpsClient.available()) { + String line = httpsClient.readStringUntil('\n'); + Serial.print(line); + } + return 2;*/ + String url = UPDATE_URL_BASE + HW_GROUP + "/firmware.version"; + HTTPClient httpClient; + httpClient.begin(url); + int httpCode = httpClient.GET(); + if (httpCode == 200) { + String newFWVersion = httpClient.getString(); + unsigned long newVersion = newFWVersion.toInt(); + if (newVersion > FW_VERSION ) { + return US_AVAILABLE; + } + } else { + return US_FAILED; + } + return US_NO_UPDATE; +} + +void doFWUpdate() { + // Set both LEDs on during update + setLED(1); + pinMode(2, OUTPUT); + digitalWrite(2, LOW); + String url = UPDATE_URL_BASE + HW_GROUP + "/firmware.bin"; + t_httpUpdate_return ret = ESPhttpUpdate.update(url); + +} + +UpdateStatus checkForSPUpdate() { + String url = UPDATE_URL_BASE + HW_GROUP + "/spiffs.version"; + HTTPClient httpClient; + httpClient.begin(url); + int httpCode = httpClient.GET(); + if (httpCode == 200) { + String newSPVersion = httpClient.getString(); + unsigned long newVersion = newSPVersion.toInt(); + if (newVersion > SP_VERSION ) { + return US_AVAILABLE; + } + } else { + return US_FAILED; + } + return US_NO_UPDATE; +} + +void doSPUpdate() { + // Set both LEDs on during update + setLED(1); + pinMode(2, OUTPUT); + digitalWrite(2, LOW); + String url = UPDATE_URL_BASE + HW_GROUP + "/spiffs.bin"; + t_httpUpdate_return ret = ESPhttpUpdate.updateSpiffs(url); +} + +void setup() { + pinMode(PIN_STATUS, OUTPUT); + pinMode(PIN_CONFIG, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(PIN_CONFIG), ISR_config, CHANGE); + +#ifdef ARDUINO_OTA_ENABLED + ArduinoOTA.setHostname("WiFi-Shield"); + ArduinoOTA.begin(); +#endif + + EEPROM.begin(96); // 96 bytes - 32 for SSID, 64 for PSK + +#ifdef INIT_EEPROM + reset_EEPROM(); +#endif + + SPIFFS.begin(); + + loadConfig(); + + if (STA_SETUP) { + // WiFi connection has been set up + WiFi.mode(WIFI_STA); + WiFi.begin(STA_SSID.c_str(), STA_PASS.c_str()); + wifiTimer = millis(); + while (WiFi.status() != WL_CONNECTED) { + if ((millis() - wifiTimer) > WIFI_TIMEOUT) { + wifiTimedOut = true; + break; + } + blinkLEDLoop(250); + } + if (wifiTimedOut) { + // Connection timed out, fall back to AP mode + doWiFiConfigViaAP(); + } + } else { + // No WiFi connection has yet been set up, enter AP mode + doWiFiConfigViaAP(); + } + + IBISServer.begin(); + + server.onNotFound(handleNotFound); + server.on("/", handleRoot); + server.on("/wifi-setup", handle_wifi_setup); + server.on("/check-update", handle_check_update); + server.on("/update-fw", handle_update_fw); + server.on("/update-sp", handle_update_sp); + server.serveStatic("/main.css", SPIFFS, "/main.css"); + server.serveStatic("/favicon.ico", SPIFFS, "/favicon.ico"); + server.begin(); + + IBIS_init(); + + for (int i = 0; i < 3; i++) { + blinkLEDLoop(125); + } + + if (AP_ACTIVE) { + setLED(1); + } else { + if (checkForFWUpdate()) { + doFWUpdate(); + } + if (checkForSPUpdate()) { + doSPUpdate(); + } + } +} + +void loop() { +#ifdef ARDUINO_OTA_ENABLED + ArduinoOTA.handle(); +#endif + server.handleClient(); + + WiFiClient newClient = IBISServer.available(); + if (newClient) client = newClient; + if (client) { + while (client.available()) { + setLED(1); + Serial.write(client.read()); + setLED(0); + } + } + + // Handle LED blinking while button is being pressed (visual timing help) + if (btnState) { + unsigned long dur = (millis() - btnTimer) % 1000; + setLED(dur > 500); + } + + // Check if the button has been pressed and for how long + if (btnPressed) { + btnPressed = 0; + setLED(0); + int selectedOption = btnDur / 1000; + switch (selectedOption) { + case 0: { + // Short press + break; + } + case 1: { + // WPS mode + doWiFiConfigViaWPS(); + break; + } + case 2: { + // AP mode + doWiFiConfigViaAP(); + break; + } + default: { + break; + } + } + } +} + diff --git a/data/config.json b/data/config.json new file mode 100644 index 0000000..05955c8 --- /dev/null +++ b/data/config.json @@ -0,0 +1 @@ +{"SPVersion": 1802082058} \ No newline at end of file diff --git a/data/favicon.ico b/data/favicon.ico new file mode 100644 index 0000000..045b6b0 Binary files /dev/null and b/data/favicon.ico differ diff --git a/data/main.css b/data/main.css new file mode 100644 index 0000000..998bdfa --- /dev/null +++ b/data/main.css @@ -0,0 +1,8 @@ +body { + background-color: #ccc; + font-family: sans-serif; +} + +form { + margin: 0; +} \ No newline at end of file diff --git a/firmware.bin b/firmware.bin new file mode 100644 index 0000000..979f70e Binary files /dev/null and b/firmware.bin differ diff --git a/firmware.version b/firmware.version new file mode 100644 index 0000000..f0bd251 --- /dev/null +++ b/firmware.version @@ -0,0 +1 @@ +1802080001 \ No newline at end of file diff --git a/ibis.h b/ibis.h new file mode 100644 index 0000000..fec5e95 --- /dev/null +++ b/ibis.h @@ -0,0 +1,37 @@ +void IBIS_init() { + Serial.begin(1200, SERIAL_7E2); +} + +void IBIS_processSpecialCharacters(String* telegram) { + telegram->replace("ä", "{"); + telegram->replace("ö", "|"); + telegram->replace("ü", "}"); + telegram->replace("ß", "~"); + telegram->replace("Ä", "["); + telegram->replace("Ö", "\\"); + telegram->replace("Ü", "]"); +} + +String IBIS_wrapTelegram(String telegram) { + telegram += '\x0d'; + unsigned char checksum = 0x7F; + for (int i = 0; i < telegram.length(); i++) { + checksum ^= (unsigned char)telegram[i]; + } + // Get ready for a retarded fucking Arduino workaround + telegram += " "; + telegram.setCharAt(telegram.length() - 1, checksum); // seriously fuck that + return telegram; +} + +void IBIS_sendTelegram(String telegram) { + IBIS_processSpecialCharacters(&telegram); + telegram = IBIS_wrapTelegram(telegram); + Serial.print(telegram); +} + +void IBIS_DS009(String text) { + String telegram = "v" + text; + IBIS_sendTelegram(telegram); +} + diff --git a/mkspiffs.exe b/mkspiffs.exe new file mode 100644 index 0000000..9d589c2 Binary files /dev/null and b/mkspiffs.exe differ diff --git a/prepare_firmware.py b/prepare_firmware.py new file mode 100644 index 0000000..b23b945 --- /dev/null +++ b/prepare_firmware.py @@ -0,0 +1,25 @@ +""" +Prepare the firmware image for publishing + +- Read Firmware version from code +- Generate firmware.version file +- Rename Firmware image +""" + +import os +import re +import time + +with open("WiFi_Shield.ino", 'r') as f: + code = f.read() + +match = re.search(r"FW_VERSION = (\d+);", code) +fw_version = match.group(1) + +with open("firmware.version", 'w') as f: + f.write(fw_version) + +if os.path.exists("WiFi_Shield.ino.generic.bin"): + if os.path.exists("firmware.bin"): + os.remove("firmware.bin") + os.rename("WiFi_Shield.ino.generic.bin", "firmware.bin") \ No newline at end of file diff --git a/prepare_spiffs.py b/prepare_spiffs.py new file mode 100644 index 0000000..436b00f --- /dev/null +++ b/prepare_spiffs.py @@ -0,0 +1,24 @@ +""" +Prepare the SPIFFS image for publishing + +- Set version number in config.json +- Generate spiffs.version file +- Build SPIFFS image +""" + +import json +import os +import time + +with open("data/config.json", 'r') as f: + data = json.load(f) + +data['SPVersion'] = int(time.strftime("%y%m%d%H%M")) + +with open("data/config.json", 'w') as f: + json.dump(data, f) + +with open("spiffs.version", 'w') as f: + f.write(str(data['SPVersion'])) + +os.system("mkspiffs -c data -p 256 -b 8192 -s 1028096 spiffs.bin") \ No newline at end of file diff --git a/spiffs.bin b/spiffs.bin new file mode 100644 index 0000000..24b16e6 Binary files /dev/null and b/spiffs.bin differ diff --git a/spiffs.version b/spiffs.version new file mode 100644 index 0000000..bd0876a --- /dev/null +++ b/spiffs.version @@ -0,0 +1 @@ +1802082058 \ No newline at end of file