diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b5991b5..0a2501e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,6 +26,7 @@ jobs: - name: Build & Package NRF Firmware run: | + export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA" pio run --environment oeplnrf mkdir firmware_output cp .pio/build/oeplnrf/firmware.hex firmware_output/NRF52840.hex @@ -35,6 +36,7 @@ jobs: - name: Build & Package esp32-s3-N16R8 Firmware run: | + export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA" pioenv=oeplesp32-s3-N16R8 pio run --environment ${pioenv} mkdir ${pioenv} @@ -50,6 +52,7 @@ jobs: - name: Build & Package esp32-s3-N8R8 Firmware run: | + export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA" pioenv=oeplesp32-s3-N8R8 pio run --environment ${pioenv} mkdir ${pioenv} @@ -65,6 +68,7 @@ jobs: - name: Build & Package esp32-s3-N32R8 Firmware run: | + export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA" pioenv=oeplesp32-s3-N32R8 pio run --environment ${pioenv} mkdir ${pioenv} @@ -80,6 +84,7 @@ jobs: - name: Build & Package esp32-c6-N4 Firmware run: | + export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA" pioenv=esp32-c6-N4 pio run --environment ${pioenv} mkdir ${pioenv} @@ -95,6 +100,7 @@ jobs: - name: Build & Package esp32-c3-N4 Firmware run: | + export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA" pioenv=esp32-c3-N4 pio run --environment ${pioenv} mkdir ${pioenv} diff --git a/platformio.ini b/platformio.ini index fdf7af7..b273ff3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -6,7 +6,8 @@ lib_deps = [env:oeplnrf] #build_unflags = -DUSE_LFXO #build_flags = -DNRF52840_XXAA -DUSE_LFRC -DNRF52_S140 -DNRF52 -DNRF52840 -build_flags = -DTARGET_NRF +build_flags = + -DTARGET_NRF #lib_compat_mode = soft platform = https://github.com/maxgerhardt/platform-nordicnrf52 #platform = nordicnrf52 @@ -23,6 +24,7 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/ framework = arduino build_flags = -DTARGET_ESP32 + -DTARGET_LARGE_MEMORY -DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 @@ -44,6 +46,7 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/ framework = arduino build_flags = -DTARGET_ESP32 + -DTARGET_LARGE_MEMORY -DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 @@ -65,6 +68,7 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/ framework = arduino build_flags = -DTARGET_ESP32 + -DTARGET_LARGE_MEMORY -DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 diff --git a/src/main.cpp b/src/main.cpp index 99b70de..56351fa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -100,6 +100,22 @@ MyBLECharacteristicCallbacks* charCallbacks = nullptr; void setup() { Serial.begin(115200); + delay(100); // Give serial time to initialize + writeSerial("=== FIRMWARE INFO ==="); + writeSerial("Firmware Version: " + String(getFirmwareMajor()) + "." + String(getFirmwareMinor())); + // SHA_STRING is already a string literal from the macro, so we can use it directly + const char* shaCStr = SHA_STRING; + String shaStr = String(shaCStr); + // Remove surrounding quotes if present (from stringification when SHA is empty string "") + if (shaStr.length() >= 2 && shaStr.charAt(0) == '"' && shaStr.charAt(shaStr.length() - 1) == '"') { + shaStr = shaStr.substring(1, shaStr.length() - 1); + } + if (shaStr.length() > 0 && shaStr != "\"\"" && shaStr != "") { + writeSerial("Git SHA: " + shaStr); + } else { + writeSerial("Git SHA: (not set)"); + } + writeSerial("====================="); writeSerial("Starting setup..."); writeSerial("Initializing full config..."); full_config_init(); @@ -209,6 +225,24 @@ void initio(){ initSensors(); } +uint8_t getFirmwareMajor(){ + String version = String(BUILD_VERSION); + int dotIndex = version.indexOf('.'); + if (dotIndex > 0) { + return version.substring(0, dotIndex).toInt(); + } + return 0; +} + +uint8_t getFirmwareMinor(){ + String version = String(BUILD_VERSION); + int dotIndex = version.indexOf('.'); + if (dotIndex > 0 && dotIndex < version.length() - 1) { + return version.substring(dotIndex + 1).toInt(); + } + return 0; +} + void initDataBuses(){ writeSerial("=== Initializing Data Buses ==="); if(globalConfig.data_bus_count == 0){ @@ -917,6 +951,21 @@ void initDisplay(){ epd.print("Name: OEPL"+ chipId); epd.setCursor(100, 300); epd.print("Epaper driver by Larry Bank https://github.com/bitbank2"); + epd.setCursor(100, 400); + const char* shaCStr = SHA_STRING; + String shaStr = String(shaCStr); + // Remove surrounding quotes if present (from stringification when SHA is empty string "") + if (shaStr.length() >= 2 && shaStr.charAt(0) == '"' && shaStr.charAt(shaStr.length() - 1) == '"') { + shaStr = shaStr.substring(1, shaStr.length() - 1); + } + if (shaStr.length() == 0 || shaStr == "\"\"" || shaStr == "") { + shaStr = "(not set)"; + } + epd.print("Firmware: " + String(getFirmwareMajor()) + "." + String(getFirmwareMinor()) + " SHA: " + shaStr); + if(globalConfig.displays[0].color_scheme == 0){ + writeSerial("2 color test"); + epd.fillRect(100,320,10,10,BBEP_BLACK); + } if(globalConfig.displays[0].color_scheme == 4){ writeSerial("6 color test"); epd.fillRect(100,320,10,10,BBEP_RED); @@ -1080,6 +1129,10 @@ void imageDataWritten(void* conn_hdl, void* chr, uint8_t* data, uint16_t len){ delay(100); reboot(); break; + case 0x0043: // Firmware Version command + writeSerial("=== FIRMWARE VERSION COMMAND (0x0043) ==="); + handleFirmwareVersion(); + break; default: writeSerial("ERROR: Unknown command: 0x" + String(command, HEX)); writeSerial("Expected: 0x0011 (read config), 0x0064 (image info), 0x0065 (block data), or 0x0003 (finalize)"); @@ -1951,13 +2004,11 @@ bool decompressImageDataChunked(){ uint8_t invertedFirstPlane = ~firstPlaneByte; uint16_t planeY = thirdPlaneIndex / (imgWidth / 8); uint16_t planeX = (thirdPlaneIndex % (imgWidth / 8)) * 8; - for (int bit = 7; bit >= 0; bit--) { if (planeY < ((imgHeight < displayWidth) ? imgHeight : displayWidth) && planeX + (7-bit) < ((imgWidth < displayHeight) ? imgWidth : displayHeight)) { bool plane1 = (invertedFirstPlane >> bit) & 0x01; // B/W base bool plane2 = (secondPlaneByte >> bit) & 0x01; // R/Y bool plane3 = (byte >> bit) & 0x01; // G/B - // Decode 6 colors: BLACK=000, WHITE=100, RED=110, YELLOW=010, GREEN=101, BLUE=001 if (!plane1 && !plane2 && !plane3) { epd.drawPixel(displayWidth - planeY, planeX + (7-bit), BBEP_BLACK); @@ -1988,13 +2039,10 @@ bool decompressImageDataChunked(){ writeSerial("ERROR: uzlib data error"); break; } - } while (res == TINF_OK && totalDecompressed < originalLength); writeSerial("Chunked decompression complete: " + String(totalDecompressed) + " bytes"); writeSerial("Expected: " + String(originalLength) + " bytes"); writeSerial("Pixel bytes processed: " + String(pixelBytesProcessed)); - - // Cleanup allocated buffers if (firstPlaneBuffer) { free(firstPlaneBuffer); writeSerial("Freed first plane buffer"); @@ -2252,6 +2300,34 @@ void handleReadConfig(){ writeSerial("handleReadConfig function completed successfully"); } +void handleFirmwareVersion(){ + writeSerial("Building Firmware Version response..."); + uint8_t major = getFirmwareMajor(); + uint8_t minor = getFirmwareMinor(); + const char* shaCStr = SHA_STRING; + String shaStr = String(shaCStr); + // Remove surrounding quotes if present (from stringification when SHA is empty string "") + if (shaStr.length() >= 2 && shaStr.charAt(0) == '"' && shaStr.charAt(shaStr.length() - 1) == '"') { + shaStr = shaStr.substring(1, shaStr.length() - 1); + } + writeSerial("Firmware version: " + String(major) + "." + String(minor)); + writeSerial("SHA: " + shaStr); + uint8_t shaLen = shaStr.length(); + if (shaLen > 40) shaLen = 40; + uint8_t response[2 + 1 + 1 + 1 + 40]; + uint16_t offset = 0; + response[offset++] = 0x00; + response[offset++] = 0x43; + response[offset++] = major; + response[offset++] = minor; + response[offset++] = shaLen; + for (uint8_t i = 0; i < shaLen && i < 40; i++) { + response[offset++] = shaStr.charAt(i); + } + sendResponse(response, offset); + writeSerial("Firmware version response sent"); +} + void handleWriteConfig(uint8_t* data, uint16_t len){ if (len == 0) { writeSerial("ERROR: No config data received"); @@ -2354,6 +2430,11 @@ void handleWriteConfigChunk(uint8_t* data, uint16_t len){ bool loadGlobalConfig(){ writeSerial("Loading global configuration..."); memset(&globalConfig, 0, sizeof(globalConfig)); + // Reset WiFi configuration flag + wifiConfigured = false; + wifiSsid[0] = '\0'; + wifiPassword[0] = '\0'; + wifiEncryptionType = 0; static uint8_t configData[MAX_CONFIG_SIZE]; static uint32_t configLen = MAX_CONFIG_SIZE; if (!loadConfig(configData, &configLen)) { @@ -2496,6 +2577,58 @@ bool loadGlobalConfig(){ return false; } break; + case 0x26: // wifi_config + { + const uint16_t WIFI_CONFIG_SIZE = 162; // 32 (SSID) + 32 (password) + 1 (encryption) + 95 (reserved) + if (offset + WIFI_CONFIG_SIZE <= configLen) { + // Parse SSID (32 bytes, null-terminated) + memcpy(wifiSsid, &configData[offset], 32); + wifiSsid[32] = '\0'; // Ensure null termination + // Find actual string length (up to first null byte) + uint8_t ssidLen = 0; + while (ssidLen < 32 && wifiSsid[ssidLen] != '\0') ssidLen++; + offset += 32; + + // Parse password (32 bytes, null-terminated) + memcpy(wifiPassword, &configData[offset], 32); + wifiPassword[32] = '\0'; // Ensure null termination + // Find actual string length (up to first null byte) + uint8_t passwordLen = 0; + while (passwordLen < 32 && wifiPassword[passwordLen] != '\0') passwordLen++; + offset += 32; + + // Parse encryption type (1 byte) + wifiEncryptionType = configData[offset++]; + + // Skip reserved bytes (95 bytes) + offset += 95; + + // Mark WiFi as configured + wifiConfigured = true; + + // Print WiFi config to console + writeSerial("=== WiFi Configuration Loaded ==="); + writeSerial("SSID: \"" + String(wifiSsid) + "\""); + writeSerial("Password: " + String(passwordLen > 0 ? "***" : "(empty)")); + String encTypeStr = "Unknown"; + switch(wifiEncryptionType) { + case 0x00: encTypeStr = "None (Open)"; break; + case 0x01: encTypeStr = "WEP"; break; + case 0x02: encTypeStr = "WPA"; break; + case 0x03: encTypeStr = "WPA2"; break; + case 0x04: encTypeStr = "WPA3"; break; + } + writeSerial("Encryption Type: 0x" + String(wifiEncryptionType, HEX) + " (" + encTypeStr + ")"); + writeSerial("SSID length: " + String(ssidLen) + " bytes"); + writeSerial("Password length: " + String(passwordLen) + " bytes"); + writeSerial("WiFi configured: true"); + } else { + writeSerial("ERROR: Not enough data for wifi_config"); + globalConfig.loaded = false; + return false; + } + } + break; default: writeSerial("WARNING: Unknown packet ID 0x" + String(packetId, HEX) + ", skipping"); // Skip unknown packet - we can't determine its size, so we'll stop parsing diff --git a/src/main.h b/src/main.h index 0fe68b5..c6c4a56 100644 --- a/src/main.h +++ b/src/main.h @@ -3,6 +3,18 @@ #include "uzlib.h" #include +// Firmware version - parsed from BUILD_VERSION at compile time +#ifndef BUILD_VERSION +#define BUILD_VERSION "0.0" +#endif +#ifndef SHA +#define SHA "" +#endif +// Helper macros to properly stringify SHA (handles both quoted and unquoted defines) +#define STRINGIFY(x) #x +#define XSTRINGIFY(x) STRINGIFY(x) +#define SHA_STRING XSTRINGIFY(SHA) + #ifdef TARGET_NRF #include #include @@ -82,6 +94,12 @@ uint8_t dictionaryBuffer[MAX_DICT_SIZE]; uint8_t headerBuffer[6]; uint8_t bleResponseBuffer[94]; +// WiFi configuration (from packet 0x26) +char wifiSsid[33] = {0}; // 32 bytes + null terminator +char wifiPassword[33] = {0}; // 32 bytes + null terminator +uint8_t wifiEncryptionType = 0; // 0x00=none, 0x01=WEP, 0x02=WPA, 0x03=WPA2, 0x04=WPA3 +bool wifiConfigured = false; // True if WiFi config packet (0x26) was received and parsed + bool waitforrefresh(int timeout); void pwrmgm(bool onoff); void writeSerial(String message, bool newLine = true); @@ -126,6 +144,7 @@ uint32_t calculateConfigCRC(uint8_t* data, uint32_t len); void handleReadConfig(); void handleWriteConfig(uint8_t* data, uint16_t len); void handleWriteConfigChunk(uint8_t* data, uint16_t len); +void handleFirmwareVersion(); void printConfigSummary(); void reboot(); bool loadGlobalConfig(); @@ -145,6 +164,8 @@ struct BinaryInputs* getBinaryInput(uint8_t index); uint8_t getBinaryInputCount(); void printConfigSummary(); int mapEpd(int id); +uint8_t getFirmwareMajor(); +uint8_t getFirmwareMinor(); typedef struct { bool active;