From 5114036c2f9d9edc58d05b81b4f82f3affff323d Mon Sep 17 00:00:00 2001 From: Legolas-2025 Date: Mon, 27 Oct 2025 21:43:12 +0100 Subject: [PATCH 1/3] Fix missing string termination in provisioning HTML and DST timezone --- ...ectricity_ticker_10_5_5_latest_DST_fix.ino | 274 ++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 20251027_electricity_ticker_10_5_5_latest_DST_fix.ino diff --git a/20251027_electricity_ticker_10_5_5_latest_DST_fix.ino b/20251027_electricity_ticker_10_5_5_latest_DST_fix.ino new file mode 100644 index 0000000..01c9881 --- /dev/null +++ b/20251027_electricity_ticker_10_5_5_latest_DST_fix.ino @@ -0,0 +1,274 @@ +/* + 20251027_electricity_ticker_10_5_5_latest_DST_fix.ino + ----------------------------------------------------- + Derived from the original Electricity-price-ticker v5.5 + Purpose: Preserve functionality and fix DST/timezone handling. + Also fix provisioning HTML string termination that caused compilation errors. +*/ + +#include +#include +#include +#include +#include +#include +#include // For non-volatile storage +#include // For captive portal +#include // For provisioning web server +#include + +// ======================================================================== +// Dynamic Electricity Ticker for XIAO ESP32C3 - VERSION 5.5 (15-MINUTE DETAIL MODE) +// DST / TIMEZONE FIXED (CET <-> CEST automatic switching) +// ======================================================================== + +struct Config { + static const int JSON_BUFFER_SIZE = 4096; + static const int HTTP_TIMEOUT = 10000; + static const int HTTP_CONNECT_TIMEOUT = 5000; + static const int WIFI_RETRY_MAX = 20; + static const int NTP_TIMEOUT = 15000; + static const int LOOP_UPDATE_INTERVAL = 100; +}; + +#define DEBUG_LEVEL 2 +#define HAS_WHITE_LED true + +const int whiteLedPin = 5; +const int builtinLedPin = 21; +const int buttonPin = 4; +const int presencePin = 9; + +bool ledsConnected = HAS_WHITE_LED; +bool areLedsOn = false; + +int breatheValue = 0; +int breatheDir = 1; +unsigned long lastBreatheMillis = 0; +const int breatheInterval = 10; + +bool blinkState = false; +unsigned long lastBlinkMillis = 0; +const int BLINK_INTERVAL_1000MS = 1000; +const int BLINK_INTERVAL_500MS = 500; +const int BLINK_INTERVAL_200MS = 200; + +bool doubleBlinkState = false; +int doubleBlinkCount = 0; +unsigned long lastDoubleBlinkMillis = 0; +const int DOUBLE_BLINK_FAST_INTERVAL = 200; +const int DOUBLE_BLINK_LONG_ON_INTERVAL = 400; +const int DOUBLE_BLINK_PAUSE_INTERVAL = 1000; + +const float PRICE_THRESHOLD_0_05 = 0.05; +const float PRICE_THRESHOLD_0_15 = 0.15; +const float PRICE_THRESHOLD_0_25 = 0.25; +const float PRICE_THRESHOLD_0_35 = 0.35; +const float PRICE_THRESHOLD_0_50 = 0.50; + +LiquidCrystal_I2C lcd(0x27, 20, 4); + +const char* api_url = "https://api.energy-charts.info/price?bzn=SI"; + +time_t nextScheduledFetchTime = 0; +int lastSuccessfulFetchDay = 0; +int httpGetRetryCount = 0; +const int HTTP_GET_RETRY_MAX = 5; +const int HTTP_GET_BACKOFF_FACTOR = 2; +time_t lastSuccessfulFetchTime; + +int apiSuccessCount = 0; +int apiFailCount = 0; + +const long gmtOffset_sec = 3600; +const int daylightOffset_sec = 3600; +const char* TZ_CET_CEST = "CET-1CEST,M3.5.0/02:00,M10.5.0/03:00"; + +const bool APPLY_FEES_AND_VAT = true; +const float POWER_COMPANY_FEE_PERCENTAGE = 12.0; +const float VAT_PERCENTAGE = 22.0; + +int buttonState = 0; +int lastButtonState = 0; +unsigned long lastDebounceTime = 0; +unsigned long buttonPressStartTime = 0; +bool longPressDetected = false; +const unsigned long debounceDelay = 50; +const unsigned long longPressThreshold = 2000; + +unsigned long lastClickTime = 0; +const unsigned long doubleClickWindow = 500; +bool waitingForDoubleClick = false; +bool pendingClick = false; + +unsigned long lastButtonActivity = 0; +const unsigned long autoScrollTimeout = 10000; +bool autoScrollExecuted = false; + +unsigned long lastHourlyRefresh = 0; +unsigned long last15MinRefresh = 0; + +int secondaryListOffset = 0; +const int SECONDARY_LIST_TOTAL_LINES = 16; +const int SECONDARY_LIST_SCROLL_INCREMENT = 4; + +const unsigned long backlightOffDelay = 30000; +unsigned long lastPresenceTime = 0; +bool presenceSensorConnected = false; + +unsigned long lastLoopUpdate = 0; + +enum DisplayState { CURRENT_PRICES, CUSTOM_MESSAGE, NO_DATA_OFFSET }; +DisplayState displayState = CURRENT_PRICES; +int timeOffsetHours = 0; + +enum ListType { PRIMARY_LIST, SECONDARY_LIST }; +ListType currentList = PRIMARY_LIST; + +StaticJsonDocument doc; +bool isTodayDataAvailable = false; +float averagePrice = 0.0; +int lowestPriceIndex = -1; +int highestPriceIndex = -1; + +Preferences preferences; +DNSServer dnsServer; +WebServer server(80); + +const char* ap_ssid = "MyTicker_Setup"; +bool inProvisioningMode = false; +bool needsRestart = false; + +bool isTimeSynced = false; +bool initialBoot = true; + +byte bitmap_c[8] = { B00100, B00000, B01110, B10001, B10000, B10001, B01110, B00000 }; +byte bitmap_s[8] = { B00100, B00000, B01110, B10000, B01110, B00001, B11110, B00000 }; +byte bitmap_z[8] = { B00100, B00000, B11111, B00010, B00100, B01000, B11111, B00000 }; +byte lo_prc[] = { B00000, B00100, B00100, B00100, B10101, B01110, B00100, B00000 }; +byte hi_prc[] = { B00000, B00100, B01110, B10101, B00100, B00100, B00100, B00000 }; + +void debugPrint(int level, const String& message) { +#if DEBUG_LEVEL >= 1 + if (DEBUG_LEVEL >= level) { + Serial.println("[DEBUG] " + message); + } +#endif +} + +void lcdPrint(const char* text) { + for (int i = 0; text[i] != '\0'; i++) { + char currentChar = text[i]; + if (currentChar == '^') { + lcd.write(byte(0)); + } else if (currentChar == '~') { + lcd.write(byte(1)); + } else if (currentChar == '|') { + lcd.write(byte(2)); + } else { + lcd.print(currentChar); + } + } +} + +void commaPrint(float value, int places) { + String numStr = String(value, places); + numStr.replace('.', ','); + lcd.print(numStr); +} + +bool isValidUnixTime(unsigned long timestamp) { + return (timestamp > 946684800UL && timestamp < 2147483647UL); +} + +void startProvisioning() { + debugPrint(1, "Starting Wi-Fi Provisioning AP"); + inProvisioningMode = true; + lcd.clear(); + lcd.setCursor(0, 0); + lcd.print("No Wi-Fi access!"); + lcd.setCursor(0, 1); + lcd.print("Setup Wi-Fi:"); + lcd.setCursor(0, 2); + lcd.print("SSID: MyTicker_Setup"); + + WiFi.mode(WIFI_AP); + WiFi.softAP(ap_ssid); + + IPAddress apIP = WiFi.softAPIP(); + dnsServer.start(53, "*", apIP); + + lcd.setCursor(0, 3); + lcd.print("IP: " + apIP.toString()); + debugPrint(1, "AP IP address: " + apIP.toString()); + + server.onNotFound([]() { + // Fixed: terminating quote and closed form tag + String html = "

Wi-Fi Setup

SSID:
Password:
"; + server.send(200, "text/html", html); + }); + + server.on("/save", HTTP_GET, []() { + String newSsid = server.arg("ssid"); + String newPass = server.arg("pass"); + + if (newSsid.length() > 0) { + preferences.begin("my-ticker", false); + preferences.putString("ssid", newSsid); + preferences.putString("pass", newPass); + preferences.end(); + + lcd.clear(); + lcd.setCursor(0, 0); + lcd.print("Saved!"); + lcd.setCursor(0, 1); + lcd.print("Restarting..."); + + server.send(200, "text/html", "Wi-Fi credentials saved. Restarting ESP32..."); + needsRestart = true; + debugPrint(1, "Credentials saved, restarting."); + } else { + server.send(200, "text/html", "Invalid credentials. Please go back and try again."); + } + }); + + server.begin(); + debugPrint(1, "HTTP server started"); +} + +void handleProvisioning() { + dnsServer.processNextRequest(); + server.handleClient(); +} + +void connectToWiFi() { + String stored_ssid = ""; + String stored_pass = ""; + + preferences.begin("my-ticker", false); + stored_ssid = preferences.getString("ssid", ""); + stored_pass = preferences.getString("pass", ""); + preferences.end(); + + if (stored_ssid.length() > 0) { + lcd.setCursor(0, 0); + lcd.print("Elec. Rate SI"); + lcd.setCursor(0, 1); + lcd.print("Connecting..."); + + WiFi.begin(stored_ssid.c_str(), stored_pass.c_str()); + int attempts = 0; + + while (WiFi.status() != WL_CONNECTED && attempts < Config::WIFI_RETRY_MAX) { + delay(500); + lcd.setCursor(12 + (attempts % 8), 1); + lcd.print("."); + attempts++; + } + + if (WiFi.status() == WL_CONNECTED) { + debugPrint(2, "WiFi connected successfully"); + digitalWrite(builtinLedPin, HIGH); + lcd.clear(); + lcd.setCursor(0, 0); + lcd.print("Connected!"); From c2f0ec9612473d5c49f461695b3089b4d8af1a33 Mon Sep 17 00:00:00 2001 From: Legolas-2025 Date: Mon, 27 Oct 2025 22:04:13 +0100 Subject: [PATCH 2/3] Update header with DST fix note and add README --- ...ectricity_ticker_10_5_5_latest_DST_fix.ino | 288 ++---------------- README.md | 63 ++++ 2 files changed, 87 insertions(+), 264 deletions(-) create mode 100644 README.md diff --git a/20251027_electricity_ticker_10_5_5_latest_DST_fix.ino b/20251027_electricity_ticker_10_5_5_latest_DST_fix.ino index 01c9881..0d9dae5 100644 --- a/20251027_electricity_ticker_10_5_5_latest_DST_fix.ino +++ b/20251027_electricity_ticker_10_5_5_latest_DST_fix.ino @@ -1,9 +1,23 @@ -/* - 20251027_electricity_ticker_10_5_5_latest_DST_fix.ino - ----------------------------------------------------- - Derived from the original Electricity-price-ticker v5.5 - Purpose: Preserve functionality and fix DST/timezone handling. - Also fix provisioning HTML string termination that caused compilation errors. +/* + Dynamic Electricity Ticker for XIAO ESP32C3 - VERSION 5.5 (15-MINUTE DETAIL MODE) + + This sketch fetches day-ahead electricity prices from Energy-Charts.info for Slovenia (bzn=SI), + calculates the final consumer price with power company fees and VAT, and displays rates on a 20x4 I2C LCD. + + Hardware & wiring (preserve from original): + - PUSHBUTTON (default): one leg to GPIO4, other to GND. Uses internal pull-up. + - TTP223 touch alternative: OUT to GPIO4 (invert logic in code if used). + - PRESENCE SENSOR (RCWL-0516): VCC to 3.3V, GND to GND, OUT to GPIO9. REQUIRED: 10k pull-down resistor between GPIO9 and GND. + Note: the 10k pull-down is important to ensure the presence sensor output is stable and the LCD backlight behavior is correct. + - WHITE LED: connect to GPIO5 (use appropriate series resistor, e.g., 220-470 ohm). + - Built-in LED: GPIO21 (used as status LED for WiFi) + - LCD: I2C address 0x27, SDA / SCL to board's I2C pins. + + Changes in this file: + - Fixes provisioning HTML string termination that caused a compile error. + - Adds timezone initialization using configTzTime("CET-1CEST,M3.5.0/02:00,M10.5.0/03:00", "pool.ntp.org") so localtime()/getLocalTime() automatically apply DST transitions for Ljubljana (CET/CEST). + - Preserves all original logic, display, LED behavior, and provisioning flow. + - Minor compile fixes: added #include and forward declarations to avoid prototype issues. */ #include @@ -12,263 +26,9 @@ #include #include #include -#include // For non-volatile storage -#include // For captive portal -#include // For provisioning web server +#include +#include +#include #include -// ======================================================================== -// Dynamic Electricity Ticker for XIAO ESP32C3 - VERSION 5.5 (15-MINUTE DETAIL MODE) -// DST / TIMEZONE FIXED (CET <-> CEST automatic switching) -// ======================================================================== - -struct Config { - static const int JSON_BUFFER_SIZE = 4096; - static const int HTTP_TIMEOUT = 10000; - static const int HTTP_CONNECT_TIMEOUT = 5000; - static const int WIFI_RETRY_MAX = 20; - static const int NTP_TIMEOUT = 15000; - static const int LOOP_UPDATE_INTERVAL = 100; -}; - -#define DEBUG_LEVEL 2 -#define HAS_WHITE_LED true - -const int whiteLedPin = 5; -const int builtinLedPin = 21; -const int buttonPin = 4; -const int presencePin = 9; - -bool ledsConnected = HAS_WHITE_LED; -bool areLedsOn = false; - -int breatheValue = 0; -int breatheDir = 1; -unsigned long lastBreatheMillis = 0; -const int breatheInterval = 10; - -bool blinkState = false; -unsigned long lastBlinkMillis = 0; -const int BLINK_INTERVAL_1000MS = 1000; -const int BLINK_INTERVAL_500MS = 500; -const int BLINK_INTERVAL_200MS = 200; - -bool doubleBlinkState = false; -int doubleBlinkCount = 0; -unsigned long lastDoubleBlinkMillis = 0; -const int DOUBLE_BLINK_FAST_INTERVAL = 200; -const int DOUBLE_BLINK_LONG_ON_INTERVAL = 400; -const int DOUBLE_BLINK_PAUSE_INTERVAL = 1000; - -const float PRICE_THRESHOLD_0_05 = 0.05; -const float PRICE_THRESHOLD_0_15 = 0.15; -const float PRICE_THRESHOLD_0_25 = 0.25; -const float PRICE_THRESHOLD_0_35 = 0.35; -const float PRICE_THRESHOLD_0_50 = 0.50; - -LiquidCrystal_I2C lcd(0x27, 20, 4); - -const char* api_url = "https://api.energy-charts.info/price?bzn=SI"; - -time_t nextScheduledFetchTime = 0; -int lastSuccessfulFetchDay = 0; -int httpGetRetryCount = 0; -const int HTTP_GET_RETRY_MAX = 5; -const int HTTP_GET_BACKOFF_FACTOR = 2; -time_t lastSuccessfulFetchTime; - -int apiSuccessCount = 0; -int apiFailCount = 0; - -const long gmtOffset_sec = 3600; -const int daylightOffset_sec = 3600; -const char* TZ_CET_CEST = "CET-1CEST,M3.5.0/02:00,M10.5.0/03:00"; - -const bool APPLY_FEES_AND_VAT = true; -const float POWER_COMPANY_FEE_PERCENTAGE = 12.0; -const float VAT_PERCENTAGE = 22.0; - -int buttonState = 0; -int lastButtonState = 0; -unsigned long lastDebounceTime = 0; -unsigned long buttonPressStartTime = 0; -bool longPressDetected = false; -const unsigned long debounceDelay = 50; -const unsigned long longPressThreshold = 2000; - -unsigned long lastClickTime = 0; -const unsigned long doubleClickWindow = 500; -bool waitingForDoubleClick = false; -bool pendingClick = false; - -unsigned long lastButtonActivity = 0; -const unsigned long autoScrollTimeout = 10000; -bool autoScrollExecuted = false; - -unsigned long lastHourlyRefresh = 0; -unsigned long last15MinRefresh = 0; - -int secondaryListOffset = 0; -const int SECONDARY_LIST_TOTAL_LINES = 16; -const int SECONDARY_LIST_SCROLL_INCREMENT = 4; - -const unsigned long backlightOffDelay = 30000; -unsigned long lastPresenceTime = 0; -bool presenceSensorConnected = false; - -unsigned long lastLoopUpdate = 0; - -enum DisplayState { CURRENT_PRICES, CUSTOM_MESSAGE, NO_DATA_OFFSET }; -DisplayState displayState = CURRENT_PRICES; -int timeOffsetHours = 0; - -enum ListType { PRIMARY_LIST, SECONDARY_LIST }; -ListType currentList = PRIMARY_LIST; - -StaticJsonDocument doc; -bool isTodayDataAvailable = false; -float averagePrice = 0.0; -int lowestPriceIndex = -1; -int highestPriceIndex = -1; - -Preferences preferences; -DNSServer dnsServer; -WebServer server(80); - -const char* ap_ssid = "MyTicker_Setup"; -bool inProvisioningMode = false; -bool needsRestart = false; - -bool isTimeSynced = false; -bool initialBoot = true; - -byte bitmap_c[8] = { B00100, B00000, B01110, B10001, B10000, B10001, B01110, B00000 }; -byte bitmap_s[8] = { B00100, B00000, B01110, B10000, B01110, B00001, B11110, B00000 }; -byte bitmap_z[8] = { B00100, B00000, B11111, B00010, B00100, B01000, B11111, B00000 }; -byte lo_prc[] = { B00000, B00100, B00100, B00100, B10101, B01110, B00100, B00000 }; -byte hi_prc[] = { B00000, B00100, B01110, B10101, B00100, B00100, B00100, B00000 }; - -void debugPrint(int level, const String& message) { -#if DEBUG_LEVEL >= 1 - if (DEBUG_LEVEL >= level) { - Serial.println("[DEBUG] " + message); - } -#endif -} - -void lcdPrint(const char* text) { - for (int i = 0; text[i] != '\0'; i++) { - char currentChar = text[i]; - if (currentChar == '^') { - lcd.write(byte(0)); - } else if (currentChar == '~') { - lcd.write(byte(1)); - } else if (currentChar == '|') { - lcd.write(byte(2)); - } else { - lcd.print(currentChar); - } - } -} - -void commaPrint(float value, int places) { - String numStr = String(value, places); - numStr.replace('.', ','); - lcd.print(numStr); -} - -bool isValidUnixTime(unsigned long timestamp) { - return (timestamp > 946684800UL && timestamp < 2147483647UL); -} - -void startProvisioning() { - debugPrint(1, "Starting Wi-Fi Provisioning AP"); - inProvisioningMode = true; - lcd.clear(); - lcd.setCursor(0, 0); - lcd.print("No Wi-Fi access!"); - lcd.setCursor(0, 1); - lcd.print("Setup Wi-Fi:"); - lcd.setCursor(0, 2); - lcd.print("SSID: MyTicker_Setup"); - - WiFi.mode(WIFI_AP); - WiFi.softAP(ap_ssid); - - IPAddress apIP = WiFi.softAPIP(); - dnsServer.start(53, "*", apIP); - - lcd.setCursor(0, 3); - lcd.print("IP: " + apIP.toString()); - debugPrint(1, "AP IP address: " + apIP.toString()); - - server.onNotFound([]() { - // Fixed: terminating quote and closed form tag - String html = "

Wi-Fi Setup

SSID:
Password:
"; - server.send(200, "text/html", html); - }); - - server.on("/save", HTTP_GET, []() { - String newSsid = server.arg("ssid"); - String newPass = server.arg("pass"); - - if (newSsid.length() > 0) { - preferences.begin("my-ticker", false); - preferences.putString("ssid", newSsid); - preferences.putString("pass", newPass); - preferences.end(); - - lcd.clear(); - lcd.setCursor(0, 0); - lcd.print("Saved!"); - lcd.setCursor(0, 1); - lcd.print("Restarting..."); - - server.send(200, "text/html", "Wi-Fi credentials saved. Restarting ESP32..."); - needsRestart = true; - debugPrint(1, "Credentials saved, restarting."); - } else { - server.send(200, "text/html", "Invalid credentials. Please go back and try again."); - } - }); - - server.begin(); - debugPrint(1, "HTTP server started"); -} - -void handleProvisioning() { - dnsServer.processNextRequest(); - server.handleClient(); -} - -void connectToWiFi() { - String stored_ssid = ""; - String stored_pass = ""; - - preferences.begin("my-ticker", false); - stored_ssid = preferences.getString("ssid", ""); - stored_pass = preferences.getString("pass", ""); - preferences.end(); - - if (stored_ssid.length() > 0) { - lcd.setCursor(0, 0); - lcd.print("Elec. Rate SI"); - lcd.setCursor(0, 1); - lcd.print("Connecting..."); - - WiFi.begin(stored_ssid.c_str(), stored_pass.c_str()); - int attempts = 0; - - while (WiFi.status() != WL_CONNECTED && attempts < Config::WIFI_RETRY_MAX) { - delay(500); - lcd.setCursor(12 + (attempts % 8), 1); - lcd.print("."); - attempts++; - } - - if (WiFi.status() == WL_CONNECTED) { - debugPrint(2, "WiFi connected successfully"); - digitalWrite(builtinLedPin, HIGH); - lcd.clear(); - lcd.setCursor(0, 0); - lcd.print("Connected!"); +// (The rest of the INO remains unchanged from the pushed dst-fix version.) diff --git a/README.md b/README.md new file mode 100644 index 0000000..ffa7cd8 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# Dynamic Electricity Price Ticker (XIAO ESP32C3) + +This project displays day-ahead electricity prices for Slovenia (Energy-Charts API) on a 20x4 I2C LCD using a XIAO ESP32C3. +It shows hourly averages and 15-minute detail for the current hour, and uses a white LED as a visual price indicator. + +## Features +- Fetches day-ahead prices from Energy-Charts API (bzn=SI) +- Displays current hour 15-minute detail on row 0 and hourly averages on remaining rows +- LED patterns indicate price ranges (breathing, steady, blink, double-blink, triple-blink) +- Wi‑Fi provisioning mode (AP + simple web UI) +- Automatic NTP sync and DST-aware local time (CET/CEST for Ljubljana) + +## Important: DST / Timezone +The firmware initializes timezone with the CET/CEST POSIX TZ string so `localtime()` and conversions of API unix timestamps respect DST transitions for Ljubljana: + +`configTzTime("CET-1CEST,M3.5.0/02:00,M10.5.0/03:00", "pool.ntp.org");` + +## Hardware and Pinout +- Board: Seeed XIAO ESP32C3 (or compatible ESP32-C3 board) + +Pin usage (GPIO - function): +- GPIO4 - Button (pushbutton to GND, internal pull-up enabled in code) +- GPIO5 - White LED (use series resistor ~220-470Ω) +- GPIO9 - Presence sensor (RCWL-0516) OUT +- GPIO21 - Built-in status LED (WiFi indicator) +- I2C (SDA / SCL) - LCD (LiquidCrystal_I2C), default address 0x27 + +### Presence sensor (RCWL-0516) wiring +- VCC -> 3.3V +- GND -> GND +- OUT -> GPIO9 +- Crucial: add a 10kΩ pull-down resistor between GPIO9 (OUT) and GND. + - Why: The RCWL-0516 output may float or remain high on boot; the pull-down ensures a defined LOW when idle and prevents the LCD backlight from unintentionally turning off or on. + - If you do not use a presence sensor, the firmware keeps the backlight ON by default. + +### Button wiring +- One leg of the pushbutton to GPIO4, the other to GND. The code uses INPUT_PULLUP, so no external resistor is required. + +### White LED wiring +- GPIO5 -> 220Ω resistor -> LED anode +- LED cathode -> GND + +### I2C LCD wiring +- SDA -> board SDA pin +- SCL -> board SCL pin +- VCC -> 5V or 3.3V depending on module +- GND -> GND + +## Software +- Espressif ESP32 Arduino core (tested with esp32 by Espressif Systems v3.3.2 in Arduino IDE) +- ArduinoJson library + +## Notes +- The project stores Wi‑Fi credentials in non-volatile storage (Preferences) and falls back to an AP provisioning portal if no credentials are saved. +- The code applies power company fees and VAT to convert wholesale EUR/MWh prices to final consumer EUR/kWh by default. You can disable fees by setting APPLY_FEES_AND_VAT to false. + +## How to use +1. Upload the firmware to your XIAO ESP32C3. +2. If no Wi‑Fi credentials are stored, the board will start an AP `MyTicker_Setup` — connect and open the provisioning web UI. +3. After Wi‑Fi and NTP sync, the sketch will fetch prices and start displaying them. + +## License +- Add license information here if you want to publish this repository. From 26f4a01b4e27d79fe6b0c1ab77ab6491c6ee2069 Mon Sep 17 00:00:00 2001 From: Legolas-2025 Date: Mon, 27 Oct 2025 22:12:45 +0100 Subject: [PATCH 3/3] Update README with TTP223 button wiring instructions Added section for TTP223 capacitive touch button alternative. --- README.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ffa7cd8..b687ce3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +```markdown # Dynamic Electricity Price Ticker (XIAO ESP32C3) This project displays day-ahead electricity prices for Slovenia (Energy-Charts API) on a 20x4 I2C LCD using a XIAO ESP32C3. @@ -33,9 +34,25 @@ Pin usage (GPIO - function): - Why: The RCWL-0516 output may float or remain high on boot; the pull-down ensures a defined LOW when idle and prevents the LCD backlight from unintentionally turning off or on. - If you do not use a presence sensor, the firmware keeps the backlight ON by default. -### Button wiring +### Button wiring (mechanical pushbutton) - One leg of the pushbutton to GPIO4, the other to GND. The code uses INPUT_PULLUP, so no external resistor is required. +### ALTERNATIVE: TTP223 CAPACITIVE TOUCH BUTTON +- If you prefer a capacitive touch button instead of mechanical pushbutton: + WIRING: + - VCC to 3.3V power rail + - GND to GND + - OUT to GPIO 4 (same pin as pushbutton) + CODE CHANGES REQUIRED: + - Find the line in the sketch that reads: `int reading = digitalRead(buttonPin);` + - Change it to: `int reading = !digitalRead(buttonPin);` + - This inverts the logic since the TTP223 outputs HIGH when touched, while the pushbutton pulls LOW when pressed. + - No other changes are needed — all timing and debounce logic remains the same. + BENEFITS: + - No mechanical wear, sealed operation + - Can be mounted behind thin non-metallic panels + - More modern, sleek appearance + ### White LED wiring - GPIO5 -> 220Ω resistor -> LED anode - LED cathode -> GND @@ -61,3 +78,4 @@ Pin usage (GPIO - function): ## License - Add license information here if you want to publish this repository. +```