diff --git a/.github/workflows/arduino-report.yml b/.github/workflows/arduino-report.yml deleted file mode 100644 index a51cf64..0000000 --- a/.github/workflows/arduino-report.yml +++ /dev/null @@ -1,12 +0,0 @@ -on: - push: - branches: - - main - pull_request: - schedule: - - cron: '*/5 * * * *' -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: arduino/report-size-deltas@v1 diff --git a/.github/workflows/arduino.yml b/.github/workflows/arduino.yml deleted file mode 100644 index 383b19c..0000000 --- a/.github/workflows/arduino.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: arduino -on: - push: - branches: - - main - pull_request: - -jobs: - build-for-esp32: - runs-on: ubuntu-latest - strategy: - matrix: - fqbn: - - esp32:esp32:esp32 - steps: - - uses: actions/checkout@v4 - - uses: arduino/compile-sketches@v1 - with: - enable-deltas-report: true - github-token: ${{ secrets.GITHUB_TOKEN }} - fqbn: ${{ matrix.fqbn }} - platforms: | - - name: esp32:esp32 - source-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json - sketch-paths: | - - bitcoinSwitch - libraries: | - - name: WebSockets - - name: ArduinoJson - - name: TFT_eSPI - cli-compile-flags: | - - --warnings="none" - - - uses: actions/upload-artifact@v4 - with: - name: sketches-reports - path: sketches-reports - - - report: - needs: build-for-esp32 - if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - steps: - # This step is needed to get the size data produced by the compile jobs - - name: Download sketches reports artifact - uses: actions/download-artifact@v4 - with: - name: sketches-reports - path: sketches-reports - - - uses: arduino/report-size-deltas@v1 - with: - sketches-reports-source: sketches-reports diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 07b6d92..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: release - -on: - push: - tags: - - "v[0-9]+.[0-9]+.[0-9]+" - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: write - pages: write - id-token: write - -jobs: - release: - runs-on: ubuntu-latest - steps: - - - uses: actions/checkout@v4 - with: - ref: main - - - name: Install Arduino CLI - uses: arduino/setup-arduino-cli@v1 - - - name: build, release and upload on github - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - tag: ${{ github.ref_name }} - run: | - sh install.sh - devices=$(jq '.devices[]' versions.json -r) - uploads="" - for device in $devices; do - sh build.sh $device - mv build/bitcoinSwitch.ino.bin ./bitcoinSwitch-$device.ino.bin - uploads="$uploads ./bitcoinSwitch-$device.ino.bin" - mv build/bitcoinSwitch.ino.partitions.bin ./bitcoinSwitch-$device.ino.partitions.bin - uploads="$uploads ./bitcoinSwitch-$device.ino.partitions.bin" - mv build/bitcoinSwitch.ino.bootloader.bin ./bitcoinSwitch-$device.ino.bootloader.bin - uploads="$uploads ./bitcoinSwitch-$device.ino.bootloader.bin" - done - gh release create "$tag" --generate-notes $uploads diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml deleted file mode 100644 index d1e94e9..0000000 --- a/.github/workflows/static.yml +++ /dev/null @@ -1,54 +0,0 @@ -# Simple workflow for deploying static content to GitHub Pages -name: Deploy static content to Pages - -on: - # Runs on pushes targeting the default branch - push: - branches: ["main"] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - - # Allows you to run this workflow from other workflows - workflow_call: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Use Node.js - uses: actions/setup-node@v3 - with: - node-version: '22.x' - - name: build webinstaller - run: | - sh build-webinstaller.sh - cd hardware-installer - npm install - npm run build - - name: Setup Pages - uses: actions/configure-pages@v3 - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: "hardware-installer/dist" - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 0cbe9cd..fca82df 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ installer/firmware dist build firmware.zip +bitcoinSwitch/config-*.h +bitcoinSwitch/bitcoinSwitch.ino.cpp +.github \ No newline at end of file diff --git a/bitcoinSwitch/100_config.cpp b/bitcoinSwitch/100_config.cpp new file mode 100644 index 0000000..a36d0e0 --- /dev/null +++ b/bitcoinSwitch/100_config.cpp @@ -0,0 +1,134 @@ +#include "100_config.h" + +String config_ssid; +String config_password; +String config_device_string; + +#ifdef HARDCODED +void setupConfig() +{ + Serial.println("Setting hardcoded values..."); + config_ssid = CONFIG_SSID; + Serial.println("SSID: " + config_ssid); + config_password = CONFIG_PASSWORD; + Serial.println("SSID password: " + config_password); + config_device_string = CONFIG_DEVICE_STRING; + Serial.println("Device string: " + config_device_string); + + showWelcomeScreen(); +} +#else +void setupConfig() +{ + // first give the installer a chance to delete configuration file + executeConfigBoot(); + + showWelcomeScreen(); + + if (!readConfig()) + { + // file does not exist, so we will enter endless config mode + Serial.println("Config file does not exist."); + executeSerialConfigForever(); + } +} + +bool readConfig() +{ + Preferences preferences; + preferences.begin(CONFIG_NAME, true); + + if (!preferences.isKey("ssid")) + { + config_ssid = CONFIG_SSID; + config_password = CONFIG_PASSWORD; + config_device_string = CONFIG_DEVICE_STRING; + + preferences.end(); + return false; + } + + config_ssid = preferences.getString("ssid", CONFIG_SSID); + config_password = preferences.getString("password", CONFIG_PASSWORD); + config_device_string = preferences.getString("device_string", CONFIG_DEVICE_STRING); + + preferences.end(); + return true; +} + +void executeConfigBoot() +{ + Serial.println("Entering boot mode. Waiting for " + String(BOOTUP_TIMEOUT) + " seconds."); + clearTFT(); + printTFT("BOOT MODE", 21, 21); + + int counter = (BOOTUP_TIMEOUT + 1) * 10; + while (counter-- > 0) + { +#ifdef TOUCH_PIN + int val = touchRead(TOUCH_PIN); + Serial.println("Touch read value: " + String(val)); + if (val < 60) + { + Serial.println("Touch detected"); + executePortalConfig(); + return; + } +#endif + +#ifdef BT1_PIN + pinMode(BT1_PIN, INPUT_PULLUP); + if (digitalRead(BT1_PIN) == LOW) + { + Serial.println("Button pressed"); + executePortalConfig(); + return; + } +#endif + + if (Serial.available() == 0) + { + delay(100); + continue; + } + Serial.println(); + // if we get serial data in the first seconds, we will enter config mode + counter = 0; + executeSerialConfigForever(); + return; + } + + Serial.println("Exiting boot mode."); +} + +void clearConfig() +{ + Preferences preferences; + preferences.begin(CONFIG_NAME, false); + preferences.clear(); + preferences.end(); + + readConfig(); +} + +void saveConfig() +{ + Preferences preferences; + preferences.begin(CONFIG_NAME, false); + + preferences.putString("ssid", config_ssid); + preferences.putString("password", config_password); + preferences.putString("device_string", config_device_string); + + preferences.end(); +} +#endif + +void showWelcomeScreen() +{ + Serial.print("Welcome to BitcoinSwitch!"); + Serial.println(" (" + String(VERSION) + ")"); + clearTFT(); + printTFT("BitcoinSwitch", 21, 21); + printTFT(String(VERSION), 21, 42); +} \ No newline at end of file diff --git a/bitcoinSwitch/100_config.h b/bitcoinSwitch/100_config.h new file mode 100644 index 0000000..cbb35b3 --- /dev/null +++ b/bitcoinSwitch/100_config.h @@ -0,0 +1,37 @@ +#pragma once + +#define VERSION "v1.0.1" + +#define BOOTUP_TIMEOUT 2 // seconds +#define CONFIG_NAME "config" + +// uncomment if you dont want to use the configuration file +// #define HARDCODED + +// device specific configuration / defaults +#define CONFIG_SSID "mywifi" +#define CONFIG_PASSWORD "mypw" +#define CONFIG_DEVICE_STRING "" + +#include + +#include "300_tft.h" +#ifndef HARDCODED +#include +#include "101_serial_config.h" +#include "102_portal_config.h" +#endif + +extern String config_ssid; +extern String config_password; +extern String config_device_string; + +void showWelcomeScreen(); +void setupConfig(); + +#ifndef HARDCODED +bool readConfig(); +void executeConfigBoot(); +void clearConfig(); +void saveConfig(); +#endif \ No newline at end of file diff --git a/bitcoinSwitch/100_config.ino b/bitcoinSwitch/100_config.ino deleted file mode 100644 index 77b9196..0000000 --- a/bitcoinSwitch/100_config.ino +++ /dev/null @@ -1,186 +0,0 @@ -#define VERSION "v1.0.1" - -#define BOOTUP_TIMEOUT 2 // seconds -#define CONFIG_FILE "/elements.json" - -// uncomment if you dont want to use the configuration file -// #define HARDCODED - -// device specific configuration / defaults -#define CONFIG_SSID "mywifi" -#define CONFIG_PASSWORD "mypw" -#define CONFIG_DEVICE_STRING "" -#define CONFIG_THRESHOLD_INKEY "" // Invoice/read key of LNbits wallet -#define CONFIG_THRESHOLD_AMOUNT "" // In sats -#define CONFIG_THRESHOLD_PIN "" // GPIO pin -#define CONFIG_THRESHOLD_TIME "" // Time to turn pin on - -#ifdef HARDCODED -void setupConfig(){ - Serial.println("Setting hardcoded values..."); - config_ssid = CONFIG_SSID; - Serial.println("SSID: " + config_ssid); - config_password = CONFIG_PASSWORD; - Serial.println("SSID password: " + config_password); - config_device_string = CONFIG_DEVICE_STRING; - Serial.println("Device string: " + config_device_string); - config_threshold_inkey = CONFIG_THRESHOLD_INKEY; - Serial.println("Threshold inkey: " + config_threshold_inkey); - String threshold_amount = String(CONFIG_THRESHOLD_AMOUNT); - if (threshold_amount == "") { - config_threshold_amount = 0; - } else { - config_threshold_amount = threshold_amount.toInt(); - } - Serial.println("Threshold amount: " + String(config_threshold_amount)); - String led_pin = String(CONFIG_THRESHOLD_PIN); - config_threshold_pin = led_pin.toInt(); - Serial.println("Threshold pin: " + String(config_threshold_pin)); - String led_time = String(CONFIG_THRESHOLD_TIME); - config_threshold_time = led_time.toInt(); - Serial.println("Threshold time: " + String(config_threshold_time)); -} -#else -#include -#include - -void setupConfig(){ - SPIFFS.begin(true); - // first give the installer a chance to delete configuration file - executeConfigBoot(); - String fileContent = readConfig(); - // file does not exist, so we will enter endless config mode - if (fileContent == "") { - Serial.println("Config file does not exist."); - executeConfigForever(); - } - JsonDocument doc; - DeserializationError error = deserializeJson(doc, fileContent); - if(error){ - Serial.print("deserializeJson() failed: "); - Serial.println(error.c_str()); - } - - config_ssid = getJsonValue(doc, "config_ssid", CONFIG_SSID); - config_password = getJsonValue(doc, "config_password", CONFIG_PASSWORD); - config_device_string = getJsonValue(doc, "config_device_string", CONFIG_DEVICE_STRING); - config_threshold_inkey = getJsonValue(doc, "config_threshold_inkey", CONFIG_THRESHOLD_INKEY); - String threshold_amount = getJsonValue(doc, "config_threshold_amount", CONFIG_THRESHOLD_AMOUNT); - if (threshold_amount == "") { - config_threshold_amount = 0; - } else { - config_threshold_amount = threshold_amount.toInt(); - } - String led_pin = getJsonValue(doc, "config_threshold_pin", CONFIG_THRESHOLD_PIN); - config_threshold_pin = led_pin.toInt(); - Serial.println("Threshold pin: " + String(config_threshold_pin)); - String led_time = getJsonValue(doc, "config_threshold_time", CONFIG_THRESHOLD_TIME); - config_threshold_time = led_time.toInt(); - Serial.println("Threshold time: " + String(config_threshold_time)); -} - -String readConfig() { - File paramFile = SPIFFS.open(CONFIG_FILE, FILE_READ); - if (!paramFile) { - return ""; - } - String fileContent = paramFile.readString(); - if (fileContent == "") { - return ""; - } - paramFile.close(); - return fileContent; -} - -String getJsonValue(JsonDocument &doc, const char* name, String defaultValue) -{ - String value = defaultValue; - for (JsonObject elem : doc.as()) { - if (strcmp(elem["name"], name) == 0) { - value = elem["value"].as(); - Serial.println(String(name) + ": " + value); - return value; - } - } - Serial.println(String(name) + " (using default): " + value); - return defaultValue; -} - -void executeConfigBoot() { - Serial.println("Entering boot mode. Waiting for " + String(BOOTUP_TIMEOUT) + " seconds."); - clearTFT(); - printTFT("BOOT MODE", 21, 21); - int counter = BOOTUP_TIMEOUT + 1; - while (counter-- > 0) { - if (Serial.available() == 0) { - delay(1000); - continue; - } - Serial.println(); - // if we get serial data in the first 5 seconds, we will enter config mode - counter = 0; - executeConfigForever(); - } - Serial.println("Exiting boot mode."); - Serial.print("Welcome to BitcoinSwitch!"); - Serial.println(" (" + String(VERSION) + ")"); - clearTFT(); - printTFT("BitcoinSwitch", 21, 21); - printTFT(String(VERSION), 21, 42); -} - -void executeConfigForever() { - Serial.println("Entering config mode. until we receive /config-done."); - clearTFT(); - printTFT("CONFIG", 21, 21); - bool done = false; - while (true) { - done = executeConfig(); - if (done) { - Serial.println("Exiting config mode."); - return; - } - } -} - -bool executeConfig() { - if (Serial.available() == 0) return false; - String data = Serial.readStringUntil('\n'); - Serial.println("received serial data: " + data); - if (data == "/config-done") { - delay(1000); - return true; - } - if (data == "/file-remove") { - SPIFFS.remove(CONFIG_FILE); - } - if (data.startsWith("/file-append")) { - File file = SPIFFS.open(CONFIG_FILE, FILE_APPEND); - if (!file) { - file = SPIFFS.open(CONFIG_FILE, FILE_WRITE); - } - if (!file) { - Serial.println("Failed to open file for writing."); - } - if (file) { - int pos = data.indexOf(" "); - String jsondata = data.substring(pos + 1); - file.println(jsondata); - file.close(); - } - } - if (data.startsWith("/file-read")) { - File file = SPIFFS.open(CONFIG_FILE, "r"); - if (file) { - while (file.available()) { - String line = file.readStringUntil('\n'); - Serial.println("/file-send " + line); - } - file.close(); - Serial.println("/file-done"); - } - return false; - } - return false; -} -#endif diff --git a/bitcoinSwitch/101_serial_config.cpp b/bitcoinSwitch/101_serial_config.cpp new file mode 100644 index 0000000..b067132 --- /dev/null +++ b/bitcoinSwitch/101_serial_config.cpp @@ -0,0 +1,122 @@ +#include "101_serial_config.h" + +#ifndef HARDCODED +String fileContent = ""; + +void executeSerialConfigForever() +{ + Serial.println("Entering config mode. until we receive /config-done."); + clearTFT(); + printTFT("SERIAL CONFIG", 21, 21); + bool done = false; + while (true) + { + done = executeSerialConfig(); + if (done) + { + Serial.println("Exiting config mode."); + return; + } + } +} + +bool executeSerialConfig() +{ + if (Serial.available() == 0) + return false; + String data = Serial.readStringUntil('\n'); + Serial.println("received serial data: " + data); + if (data == "/config-done") + { + if (fileContent.length() > 0) + saveConfigFromFile(); + + delay(1000); + return true; + } + if (data == "/file-remove") + { + clearConfig(); + fileContent = ""; + } + if (data.startsWith("/file-append")) + { + int pos = data.indexOf(" "); + String jsondata = data.substring(pos + 1); + fileContent += jsondata; + } + if (data.startsWith("/file-read")) + { + fileContent = readConfigToFile(); + + int start = 0; + while (start < fileContent.length()) + { + int end = fileContent.indexOf('\n', start); + if (end == -1) + end = fileContent.length(); + String line = fileContent.substring(start, end); + Serial.println("/file-send " + line); + start = end + 1; + } + + fileContent = ""; + Serial.println("/file-done"); + return false; + } + return false; +} + +String readConfigToFile() +{ + readConfig(); + + String content = ""; + + JsonDocument doc; + JsonArray arr = doc.to(); + + JsonObject ssidObj = arr.add(); + ssidObj["name"] = "config_ssid"; + ssidObj["value"] = config_ssid; + + JsonObject pwObj = arr.add(); + pwObj["name"] = "config_password"; + pwObj["value"] = config_password; + + JsonObject devStrObj = arr.add(); + devStrObj["name"] = "config_device_string"; + devStrObj["value"] = config_device_string; + + serializeJsonPretty(doc, content); + content += "\n"; + return content; +} + +void saveConfigFromFile() +{ + JsonDocument doc; + DeserializationError error = deserializeJson(doc, fileContent); + if (error) + { + Serial.print("deserializeJson() failed: "); + Serial.println(error.c_str()); + } + + for (JsonObject elem : doc.as()) + { + String key = elem["name"].as(); + + if (key == "config_ssid") + config_ssid = elem["value"].as(); + else if (key == "config_password") + config_password = elem["value"].as(); + else if (key == "config_device_string") + config_device_string = elem["value"].as(); + else + Serial.println("Unknown config key: " + key); + } + + saveConfig(); +} +#endif \ No newline at end of file diff --git a/bitcoinSwitch/101_serial_config.h b/bitcoinSwitch/101_serial_config.h new file mode 100644 index 0000000..4e5afed --- /dev/null +++ b/bitcoinSwitch/101_serial_config.h @@ -0,0 +1,14 @@ +#pragma once + +#ifndef HARDCODED +#include +#include +#include "100_config.h" +#include "300_tft.h" + +void executeSerialConfigForever(); +bool executeSerialConfig(); +String readConfigToFile(); +void saveConfigFromFile(); +void executePortalConfig(); +#endif \ No newline at end of file diff --git a/bitcoinSwitch/102_portal_config.cpp b/bitcoinSwitch/102_portal_config.cpp new file mode 100644 index 0000000..762053a --- /dev/null +++ b/bitcoinSwitch/102_portal_config.cpp @@ -0,0 +1,51 @@ +#include "102_portal_config.h" + +#ifndef HARDCODED +#define AP_PASSWORD "111222333" + +void executePortalConfig() +{ + String apName = "BitcoinSwitch-" + String(WIFI_getChipId()); + + Serial.println("Entering portal config mode. Connect to AP: " + apName); + clearTFT(); + printTFT("PORTAL CONFIG", 21, 21); + printTFT("Connect to AP", 21, 51); + printTFT(apName, 21, 81); + + WiFiManager wm; + wm.setTitle("Bitcoin Switch"); + wm.setBreakAfterConfig(true); + wm.setParamsPage(true); + wm.setShowInfoUpdate(true); + wm.setDarkMode(true); + wm.setConfigPortalTimeout(0); + wm.setConnectTimeout(15); + + readConfig(); + WiFiManagerParameter device_string_field("config_device_string", "Device string", config_device_string.c_str(), 200, "placeholder=\"wss://\""); + wm.addParameter(&device_string_field); + wm.setSaveConfigCallback([&wm]() + { saveWiFi(wm); }); + wm.setSaveParamsCallback([&device_string_field]() + { saveParams(device_string_field); }); + + Serial.println("Starting config portal..."); + wm.startConfigPortal(apName.c_str(), AP_PASSWORD); +} + +void saveWiFi(WiFiManager &wm) +{ + Serial.println("Save WiFi settings"); + config_ssid = wm.getWiFiSSID(); + config_password = wm.getWiFiPass(); + saveConfig(); +} + +void saveParams(WiFiManagerParameter device_string_field) +{ + Serial.println("Save device string"); + config_device_string = String(device_string_field.getValue()); + saveConfig(); +} +#endif \ No newline at end of file diff --git a/bitcoinSwitch/102_portal_config.h b/bitcoinSwitch/102_portal_config.h new file mode 100644 index 0000000..7447609 --- /dev/null +++ b/bitcoinSwitch/102_portal_config.h @@ -0,0 +1,13 @@ +#pragma once + +#ifndef HARDCODED +#include +#include +#include +#include "100_config.h" +#include "300_tft.h" + +void executePortalConfig(); +void saveWiFi(WiFiManager &wm); +void saveParams(WiFiManagerParameter device_string_field); +#endif \ No newline at end of file diff --git a/bitcoinSwitch/200_wifi.cpp b/bitcoinSwitch/200_wifi.cpp new file mode 100644 index 0000000..7df2879 --- /dev/null +++ b/bitcoinSwitch/200_wifi.cpp @@ -0,0 +1,33 @@ +#include "200_wifi.h" + +void setupWifi() +{ + WiFi.begin(config_ssid.c_str(), config_password.c_str()); + Serial.print("Connecting to WiFi."); + while (WiFi.status() != WL_CONNECTED) + { + Serial.print("."); + delay(500); +#ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); +#endif + } +#if defined(LED_BUILTIN) && defined(LED_ON) + digitalWrite(LED_BUILTIN, !LED_ON); +#endif + + Serial.println(); + Serial.println("WiFi connection etablished!"); + printHome(true, false, false); +} + +void loopWifi() +{ + while (WiFi.status() != WL_CONNECTED) + { + Serial.println("WiFi disconnected!"); + printHome(false, false, false); + delay(500); + setupWifi(); + } +} diff --git a/bitcoinSwitch/200_wifi.h b/bitcoinSwitch/200_wifi.h new file mode 100644 index 0000000..75fe0df --- /dev/null +++ b/bitcoinSwitch/200_wifi.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +#include "100_config.h" +#include "300_tft.h" + +void setupWifi(); +void loopWifi(); \ No newline at end of file diff --git a/bitcoinSwitch/200_wifi.ino b/bitcoinSwitch/200_wifi.ino deleted file mode 100644 index 4d63b71..0000000 --- a/bitcoinSwitch/200_wifi.ino +++ /dev/null @@ -1,26 +0,0 @@ -#include - -void setupWifi() { - WiFi.begin(config_ssid.c_str(), config_password.c_str()); - Serial.print("Connecting to WiFi."); - while (WiFi.status() != WL_CONNECTED) { - Serial.print("."); - delay(500); - digitalWrite(2, HIGH); - Serial.print("."); - delay(500); - digitalWrite(2, LOW); - } - Serial.println(); - Serial.println("WiFi connection etablished!"); - printHome(true, false, false); -} - -void loopWifi() { - while (WiFi.status() != WL_CONNECTED) { - Serial.println("WiFi disconnected!"); - printHome(false, false, false); - delay(500); - setupWifi(); - } -} diff --git a/bitcoinSwitch/201_web_socket.cpp b/bitcoinSwitch/201_web_socket.cpp new file mode 100644 index 0000000..8a7bf43 --- /dev/null +++ b/bitcoinSwitch/201_web_socket.cpp @@ -0,0 +1,126 @@ +#include "201_web_socket.h" + +WebSocketsClient webSocket; +bool ping_toggle = false; + +void setupWebSocket() +{ + if (config_device_string == "") + { + Serial.println("No device string configured!"); + printTFT("No device string!", 21, 95); + return; + } + + if (!config_device_string.startsWith("wss://")) + { + Serial.println("Device string does not start with wss://"); + printTFT("no wss://!", 21, 95); + return; + } + + String cleaned_device_string = config_device_string.substring(6); // Remove wss:// + String host = cleaned_device_string.substring(0, cleaned_device_string.indexOf('/')); + String apiPath = cleaned_device_string.substring(cleaned_device_string.indexOf('/')); + Serial.println("Websocket host: " + host); + Serial.println("Websocket API Path: " + apiPath); + + // Use in normal mode + Serial.println("Using NORMAL mode"); + Serial.println("Connecting to websocket: " + host + apiPath); + webSocket.beginSSL(host, 443, apiPath); + + webSocket.onEvent(webSocketEvent); + webSocket.setReconnectInterval(1000); +} + +void loopWebSocket() +{ + webSocket.loop(); +} + +void webSocketEvent(WStype_t type, uint8_t *payload, size_t length) +{ + switch (type) + { + case WStype_ERROR: + Serial.printf("[WebSocket] Error: %s\n", payload); + printHome(true, false, false); + break; + case WStype_DISCONNECTED: + Serial.println("[WebSocket] Disconnected!\n"); + printHome(true, false, false); + break; + case WStype_CONNECTED: + Serial.printf("[WebSocket] Connected to url: %s\n", payload); + // send message to server when Connected + webSocket.sendTXT("Connected"); + printHome(true, true, false); + break; + case WStype_TEXT: + executePayment(payload); + break; + case WStype_BIN: + Serial.printf("[WebSocket] Received binary data: %s\n", payload); + break; + case WStype_FRAGMENT_TEXT_START: + break; + case WStype_FRAGMENT_BIN_START: + break; + case WStype_FRAGMENT: + break; + case WStype_FRAGMENT_FIN: + break; + case WStype_PING: + Serial.printf("[WebSocket] Ping!\n"); + ping_toggle = !ping_toggle; + printHome(true, true, ping_toggle); + // pong will be sent automatically + break; + case WStype_PONG: + // is not used + Serial.printf("[WebSocket] Pong!\n"); + printHome(true, true, true); + break; + } +} + +void executePayment(uint8_t *payload) +{ + printTFT("Payment received!", 21, 15); + flashTFT(); + + String parts[3]; // pin, time, comment + // format: {pin-time-comment} where comment is optional + String payloadStr = String((char *)payload); + int numParts = splitString(payloadStr, '-', parts, 3); + + int pin = parts[0].toInt(); + printTFT("Pin: " + String(pin), 21, 35); + + int time = parts[1].toInt(); + printTFT("Time: " + String(time), 21, 55); + + String comment = ""; + if (numParts == 3) + { + comment = parts[2]; + Serial.println("[WebSocket] received comment: " + comment); + printTFT("Comment: " + comment, 21, 75); + } + Serial.println("[WebSocket] received pin: " + String(pin) + ", duration: " + String(time)); + + // the magic happens here + pinMode(pin, OUTPUT); + digitalWrite(pin, HIGH); +#if defined(LED_BUILTIN) && defined(LED_ON) + digitalWrite(LED_BUILTIN, LED_ON); +#endif + delay(time); + digitalWrite(pin, LOW); +#if defined(LED_BUILTIN) && defined(LED_ON) + digitalWrite(LED_BUILTIN, !LED_ON); +#endif + + printHome(true, true, false); +} \ No newline at end of file diff --git a/bitcoinSwitch/201_web_socket.h b/bitcoinSwitch/201_web_socket.h new file mode 100644 index 0000000..f0c9eba --- /dev/null +++ b/bitcoinSwitch/201_web_socket.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +#include "100_config.h" +#include "300_tft.h" +#include "400_split_string.h" + +void setupWebSocket(); +void loopWebSocket(); +void webSocketEvent(WStype_t type, uint8_t *payload, size_t length); +void executePayment(uint8_t *payload); \ No newline at end of file diff --git a/bitcoinSwitch/300_tft.ino b/bitcoinSwitch/300_tft.cpp similarity index 80% rename from bitcoinSwitch/300_tft.ino rename to bitcoinSwitch/300_tft.cpp index 69d5e68..cc6697e 100644 --- a/bitcoinSwitch/300_tft.ino +++ b/bitcoinSwitch/300_tft.cpp @@ -1,7 +1,8 @@ +#include "300_tft.h" #ifdef TFT -#include TFT_eSPI tft = TFT_eSPI(TFT_WIDTH, TFT_HEIGHT); -void setupTFT() { +void setupTFT() +{ tft.init(); Serial.println("TFT: " + String(TFT_WIDTH) + "x" + String(TFT_HEIGHT)); Serial.println("TFT pin MISO: " + String(TFT_MISO)); @@ -15,54 +16,60 @@ void setupTFT() { tft.invertDisplay(true); tft.fillScreen(TFT_BLACK); } -void printTFT(String message, int x, int y) { +void printTFT(String message, int x, int y) +{ tft.setTextSize(2); tft.setTextColor(TFT_WHITE); tft.setCursor(x, y); tft.println(message); } -void printHome(bool wifi, bool ws, bool ping) { - if (ping) { +void printHome(bool wifi, bool ws, bool ping) +{ + if (ping) tft.fillScreen(TFT_BLUE); - } else { + else tft.fillScreen(TFT_BLACK); - } tft.setTextSize(2); tft.setTextColor(TFT_YELLOW); tft.setCursor(21, 21); tft.println("BitcoinSwitch"); tft.setTextSize(2); tft.setTextColor(TFT_WHITE); - if (wifi) { + if (wifi) + { tft.setCursor(21, 69); tft.println("WiFi connected!"); - } else { + } + else + { tft.setCursor(21, 69); tft.setTextColor(TFT_RED); tft.println("No WiFi!"); } tft.setCursor(21, 95); - if (ws) { - if (ping) { + if (ws) + { + if (ping) + { tft.setTextColor(TFT_WHITE); tft.println("WS ping!"); - } else { + } + else + { tft.setTextColor(TFT_GREEN); tft.println("WS connected!"); } } - else { + else + { tft.setTextColor(TFT_RED); tft.println("No WS!"); } } -void clearTFT() { - tft.fillScreen(TFT_BLACK); -} -void flashTFT() { - tft.fillScreen(TFT_GREEN); -} +void clearTFT() { tft.fillScreen(TFT_BLACK); } +void flashTFT() { tft.fillScreen(TFT_GREEN); } #else +void setupTFT() {} void printTFT(String message, int x, int y) {} void printHome(bool wifi, bool ws, bool ping) {} void clearTFT() {} diff --git a/bitcoinSwitch/300_tft.h b/bitcoinSwitch/300_tft.h new file mode 100644 index 0000000..59ccb16 --- /dev/null +++ b/bitcoinSwitch/300_tft.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#ifdef TFT +#include +#endif + +void setupTFT(); +void printTFT(String message, int x, int y); +void printHome(bool wifi, bool ws, bool ping); +void clearTFT(); +void flashTFT(); \ No newline at end of file diff --git a/bitcoinSwitch/101_split_string.ino b/bitcoinSwitch/400_split_string.cpp similarity index 79% rename from bitcoinSwitch/101_split_string.ino rename to bitcoinSwitch/400_split_string.cpp index ec16d0a..085d71b 100644 --- a/bitcoinSwitch/101_split_string.ino +++ b/bitcoinSwitch/400_split_string.cpp @@ -1,13 +1,18 @@ -int splitString(String input, char delimiter, String parts[], int maxParts) { +#include "400_split_string.h" + +int splitString(String input, char delimiter, String parts[], int maxParts) +{ int partIndex = 0; int startIndex = 0; int delimIndex; - while ((delimIndex = input.indexOf(delimiter, startIndex)) != -1 && partIndex < maxParts) { + while ((delimIndex = input.indexOf(delimiter, startIndex)) != -1 && partIndex < maxParts) + { parts[partIndex++] = input.substring(startIndex, delimIndex); startIndex = delimIndex + 1; } // Add the last part - if (partIndex < maxParts) { + if (partIndex < maxParts) + { parts[partIndex++] = input.substring(startIndex); } return partIndex; // number of parts found diff --git a/bitcoinSwitch/400_split_string.h b/bitcoinSwitch/400_split_string.h new file mode 100644 index 0000000..b626cdf --- /dev/null +++ b/bitcoinSwitch/400_split_string.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +int splitString(String input, char delimiter, String parts[], int maxParts); \ No newline at end of file diff --git a/bitcoinSwitch/bitcoinSwitch.ino b/bitcoinSwitch/bitcoinSwitch.ino index d48778b..34d304a 100644 --- a/bitcoinSwitch/bitcoinSwitch.ino +++ b/bitcoinSwitch/bitcoinSwitch.ino @@ -1,168 +1,30 @@ -#include -#include - -String config_ssid; -String config_password; -String config_device_string; -String config_threshold_inkey; -int config_threshold_amount; -int config_threshold_pin; -int config_threshold_time; - -String apiUrl = "/api/v1/ws/"; - -WebSocketsClient webSocket; - -void setup() { - Serial.begin(115200); - // Serial.setDebugOutput(true); - #ifdef TFT - setupTFT(); - printHome(false, false, false); - #endif - setupConfig(); - setupWifi(); - - pinMode(2, OUTPUT); // To blink on board LED - - if (config_device_string == "") { - Serial.println("No device string configured!"); - printTFT("No device string!", 21, 95); - return; - } - - if (!config_device_string.startsWith("wss://")) { - Serial.println("Device string does not start with wss://"); - printTFT("no wss://!", 21, 95); - return; - } - - String cleaned_device_string = config_device_string.substring(6); // Remove wss:// - String host = cleaned_device_string.substring(0, cleaned_device_string.indexOf('/')); - String apiPath = cleaned_device_string.substring(cleaned_device_string.indexOf('/')); - Serial.println("Websocket host: " + host); - Serial.println("Websocket API Path: " + apiPath); - - if (config_threshold_amount != 0) { // Use in threshold mode - Serial.println("Using THRESHOLD mode"); - Serial.println("Connecting to websocket: " + host + apiUrl + config_threshold_inkey); - webSocket.beginSSL(host, 443, apiUrl + config_threshold_inkey); - } else { // Use in normal mode - Serial.println("Using NORMAL mode"); - Serial.println("Connecting to websocket: " + host + apiPath); - webSocket.beginSSL(host, 443, apiPath); - } - webSocket.onEvent(webSocketEvent); - webSocket.setReconnectInterval(1000); +#include "100_config.h" +#include "200_wifi.h" +#include "201_web_socket.h" +#include "300_tft.h" + +void setup() +{ + Serial.begin(115200); +// Serial.setDebugOutput(true); +#ifdef TFT + setupTFT(); + printHome(false, false, false); +#endif + +#if defined(LED_BUILTIN) && defined(LED_ON) + pinMode(LED_BUILTIN, OUTPUT); // To blink on board LED + digitalWrite(LED_BUILTIN, !LED_ON); +#endif + + setupConfig(); + setupWifi(); + setupWebSocket(); } -void loop() { - loopWifi(); - webSocket.loop(); -} - -void executePayment(uint8_t *payload) { - printTFT("Payment received!", 21, 15); - flashTFT(); - - String parts[3]; // pin, time, comment - // format: {pin-time-comment} where comment is optional - String payloadStr = String((char *)payload); - int numParts = splitString(payloadStr, '-', parts, 3); - - int pin = parts[0].toInt(); - printTFT("Pin: " + String(pin), 21, 35); - - int time = parts[1].toInt(); - printTFT("Time: " + String(time), 21, 55); - - String comment = ""; - if (numParts == 3) { - comment = parts[2]; - Serial.println("[WebSocket] received comment: " + comment); - printTFT("Comment: " + comment, 21, 75); - } - Serial.println("[WebSocket] received pin: " + String(pin) + ", duration: " + String(time)); - - if (config_threshold_amount != 0) { - // If in threshold mode we check the "balance" pushed by the - // websocket and use the pin/time preset - // executeThreshold(); - return; // Threshold mode not implemented yet - } - - // the magic happens here - pinMode(pin, OUTPUT); - digitalWrite(pin, HIGH); - delay(time); - digitalWrite(pin, LOW); - - printHome(true, true, false); - -} - -// long thresholdSum = 0; -// void executeThreshold() { -// StaticJsonDocument<1900> doc; -// DeserializationError error = deserializeJson(doc, payloadStr); -// if (error) { -// Serial.print("deserializeJson() failed: "); -// Serial.println(error.c_str()); -// return; -// } -// thresholdSum = thresholdSum + doc["payment"]["amount"].toInt(); -// Serial.println("thresholdSum: " + String(thresholdSum)); -// if (thresholdSum >= (config_threshold_amount * 1000)) { -// pinMode(config_threshold_pin, OUTPUT); -// digitalWrite(config_threshold_pin, HIGH); -// delay(config_threshold_time); -// digitalWrite(config_threshold_pin, LOW); -// thresholdSum = 0; -// } -// } - -//////////////////WEBSOCKET/////////////////// -bool ping_toggle = false; -void webSocketEvent(WStype_t type, uint8_t *payload, size_t length) { - switch (type) { - case WStype_ERROR: - Serial.printf("[WebSocket] Error: %s\n", payload); - printHome(true, false, false); - break; - case WStype_DISCONNECTED: - Serial.println("[WebSocket] Disconnected!\n"); - printHome(true, false, false); - break; - case WStype_CONNECTED: - Serial.printf("[WebSocket] Connected to url: %s\n", payload); - // send message to server when Connected - webSocket.sendTXT("Connected"); - printHome(true, true, false); - break; - case WStype_TEXT: - executePayment(payload); - break; - case WStype_BIN: - Serial.printf("[WebSocket] Received binary data: %s\n", payload); - break; - case WStype_FRAGMENT_TEXT_START: - break; - case WStype_FRAGMENT_BIN_START: - break; - case WStype_FRAGMENT: - break; - case WStype_FRAGMENT_FIN: - break; - case WStype_PING: - Serial.printf("[WebSocket] Ping!\n"); - ping_toggle = !ping_toggle; - printHome(true, true, ping_toggle); - // pong will be sent automatically - break; - case WStype_PONG: - // is not used - Serial.printf("[WebSocket] Pong!\n"); - printHome(true, true, true); - break; - } -} +void loop() +{ + loopWifi(); + loopWebSocket(); + delay(10); // to allow background processes +} \ No newline at end of file diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..1aacaef --- /dev/null +++ b/platformio.ini @@ -0,0 +1,67 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +src_dir = bitcoinSwitch +;default_envs = esp-wrover-kit + +[env] +platform = espressif32 +framework = arduino +monitor_speed = 115200 +board_build.partitions = min_spiffs.csv +lib_deps = + WebSockets@^2.6.1 + ArduinoJson@^7.3.1 + tzapu/WiFiManager@^2.0.17 + +[env:esp32dev] +board = esp32dev +build_flags = + -D TOUCH_PIN=T9 ; GPIO32 + -D LED_BUILTIN=22 + -D LED_ON=LOW + +[env:lilygo-t-display] +board = lilygo-t-display +lib_deps = + ${env.lib_deps} + TFT_eSPI +build_flags = + -D TOUCH_PIN=T5 ; GPIO12 + -D BT1_PIN=35 + -D TFT + -D USER_SETUP_LOADED=1 + -include $PROJECT_LIBDEPS_DIR/$PIOENV/TFT_eSPI/User_Setups/Setup25_TTGO_T_Display.h + -D TOUCH_CS=-1 + +[env:esp-wrover-kit] +board = esp-wrover-kit +build_flags = + -D LED_BUILTIN=2 + -D LED_ON=LOW + +[env:esp32-s3] +platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip +board = esp32-s3-devkitc-1 + +[env:esp32c3_super_mini] +platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip +board = nologo_esp32c3_super_mini +build_flags = + -D ARDUINO_USB_CDC_ON_BOOT=1 + -D ARDUINO_USB_MODE=1 +; -D LED_BUILTIN=8 + -D LED_ON=LOW + -D BT1_PIN=9 + +[env:esp32-s2] +platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip +board = esp32-s2-saola-1 \ No newline at end of file