diff --git a/boards/btclock_rev_b.json b/boards/btclock_rev_b.json new file mode 100644 index 0000000..446576e --- /dev/null +++ b/boards/btclock_rev_b.json @@ -0,0 +1,60 @@ +{ + "build": { + "arduino":{ + "ldscript": "esp32s3_out.ld", + "partitions": "default_8MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_BTCLOCK_REV_B", + "-DARDUINO_ESP32S3_DEV", + "-DIS_BTCLOCK_REV_B", + "-DARDUINO_USB_MODE=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1", + "-DARDUINO_USB_CDC_ON_BOOT=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "espidf": { + "sdkconfig_path": "boards" + }, + "hwids": [ + [ + "0x303A", + "0x1001" + ] + ], + "mcu": "esp32s3", + "variant": "esp32s3" + }, + "connectivity": [ + "bluetooth", + "wifi" + ], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": [ + "esp-builtin" + ], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "BTClock (rev. B)", + "upload": { + "flash_size": "8MB", + "maximum_ram_size": 327680, + "maximum_size": 8388608, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 460800 + }, + "url": "http://github.com/btclock", + "vendor": "BTClock" +} \ No newline at end of file diff --git a/partition_8mb.csv b/partition_8mb.csv new file mode 100644 index 0000000..4ca1357 --- /dev/null +++ b/partition_8mb.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 36K, 20K, +otadata, data, ota, 56K, 8K, +app0, app, ota_0, 64K, 1700K, +app1, app, ota_1, , 1700K, +spiffs, data, spiffs, , 400K, +coredump, data, coredump,, 64K, diff --git a/platformio.ini b/platformio.ini index dffad22..9ec55c0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] data_dir = data/build_gz -default_envs = lolin_s3_mini_213epd, lolin_s3_mini_29epd +default_envs = lolin_s3_mini_213epd, lolin_s3_mini_29epd, btclock_rev_b_213epd [env] @@ -57,6 +57,28 @@ build_flags = build_unflags = ${btclock_base.build_unflags} + +[env:btclock_rev_b] +extends = btclock_base +board = btclock_rev_b +board_build.partitions = partition.csv +build_flags = + ${btclock_base.build_flags} + -D MCP_INT_PIN=8 + -D NEOPIXEL_PIN=15 + -D NEOPIXEL_COUNT=4 + -D NUM_SCREENS=7 + -D I2C_SDA_PIN=35 + -D I2C_SCK_PIN=36 + -D HAS_FRONTLIGHT + -D PCA_OE_PIN=45 + -D PCA_I2C_ADDR=0x42 +lib_deps = + ${btclock_base.lib_deps} + robtillaart/PCA9685@^0.7.1 +build_unflags = + ${btclock_base.build_unflags} + [env:lolin_s3_mini_213epd] extends = env:lolin_s3_mini test_framework = unity @@ -65,6 +87,15 @@ build_flags = -D USE_QR -D VERSION_EPD_2_13 + +[env:btclock_rev_b_213epd] +extends = env:btclock_rev_b +test_framework = unity +build_flags = + ${env:btclock_rev_b.build_flags} + -D USE_QR + -D VERSION_EPD_2_13 + [env:lolin_s3_mini_29epd] extends = env:lolin_s3_mini test_framework = unity diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 19b8132..eb420dc 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -7,6 +7,11 @@ Adafruit_MCP23X17 mcp1; #ifdef IS_BTCLOCK_S3 Adafruit_MCP23X17 mcp2; #endif + +#ifdef HAS_FRONTLIGHT +PCA9685 flArray(PCA_I2C_ADDR); +#endif + std::vector screenNameMap(SCREEN_COUNT); std::mutex mcpMutex; uint lastTimeSync; @@ -352,6 +357,10 @@ void setupHardware() // ; } #endif + +#ifdef HAS_FRONTLIGHT + setupFrontlight(); +#endif } void improvGetAvailableWifiNetworks() @@ -658,4 +667,21 @@ String getMyHostname() uint getLastTimeSync() { return lastTimeSync; -} \ No newline at end of file +} + +#ifdef HAS_FRONTLIGHT +void setupFrontlight() { + flArray.begin(); + flArray.setFrequency(1000); + flArray.setOutputEnablePin(PCA_OE_PIN); + + if (!preferences.isKey("flMaxBrightness")) { + preferences.putUInt("flMaxBrightness", 4095); + } + // Initialize all LEDs to off + // for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) { + // flArray.setPWM(ledPin, 0, 0); // Turn off LED + // } + flArray.allOFF(); +} +#endif \ No newline at end of file diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 4f5a83c..e5a59f1 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -20,6 +20,9 @@ #include "lib/screen_handler.hpp" #include "lib/shared.hpp" #include "lib/webserver.hpp" +#ifdef HAS_FRONTLIGHT +#include "PCA9685.h" +#endif #define NTP_SERVER "pool.ntp.org" #define DEFAULT_MEMPOOL_INSTANCE "mempool.space" @@ -42,6 +45,11 @@ void tryImprovSetup(); void setupTimers(); void finishSetup(); void setupMcp(); +#ifdef HAS_FRONTLIGHT +void setupFrontlight(); +extern PCA9685 flArray; +#endif + String getMyHostname(); std::vector getScreenNameMap(); diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 0f4a229..9fef2eb 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -1,6 +1,65 @@ #include "epd.hpp" -#ifndef IS_BTCLOCK_S3 +#ifdef IS_BTCLOCK_REV_B +Native_Pin EPD_CS[NUM_SCREENS] = { + Native_Pin(2), + Native_Pin(4), + Native_Pin(6), + Native_Pin(10), + Native_Pin(38), + Native_Pin(21), + Native_Pin(17), +}; +Native_Pin EPD_BUSY[NUM_SCREENS] = { + Native_Pin(3), + Native_Pin(5), + Native_Pin(7), + Native_Pin(9), + Native_Pin(37), + Native_Pin(18), + Native_Pin(16), +}; +MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { + MCP23X17_Pin(mcp1, 8), + MCP23X17_Pin(mcp1, 9), + MCP23X17_Pin(mcp1, 10), + MCP23X17_Pin(mcp1, 11), + MCP23X17_Pin(mcp1, 12), + MCP23X17_Pin(mcp1, 13), + MCP23X17_Pin(mcp1, 14), +}; + +Native_Pin EPD_DC = Native_Pin(14); +#elif IS_BTCLOCK_S3 +Native_Pin EPD_DC = Native_Pin(38); + +MCP23X17_Pin EPD_BUSY[NUM_SCREENS] = { + MCP23X17_Pin(mcp1, 8), + MCP23X17_Pin(mcp1, 9), + MCP23X17_Pin(mcp1, 10), + MCP23X17_Pin(mcp1, 11), + MCP23X17_Pin(mcp1, 12), + MCP23X17_Pin(mcp1, 13), + MCP23X17_Pin(mcp1, 14), + MCP23X17_Pin(mcp1, 4), +}; + +MCP23X17_Pin EPD_CS[NUM_SCREENS] = { + MCP23X17_Pin(mcp2, 8), MCP23X17_Pin(mcp2, 10), MCP23X17_Pin(mcp2, 12), + MCP23X17_Pin(mcp2, 14), MCP23X17_Pin(mcp2, 0), MCP23X17_Pin(mcp2, 2), + MCP23X17_Pin(mcp2, 4), MCP23X17_Pin(mcp2, 6)}; + +MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { + MCP23X17_Pin(mcp2, 9), + MCP23X17_Pin(mcp2, 11), + MCP23X17_Pin(mcp2, 13), + MCP23X17_Pin(mcp2, 15), + MCP23X17_Pin(mcp2, 1), + MCP23X17_Pin(mcp2, 3), + MCP23X17_Pin(mcp2, 5), + MCP23X17_Pin(mcp2, 7), +}; +#else Native_Pin EPD_CS[NUM_SCREENS] = { Native_Pin(2), Native_Pin(4), @@ -35,36 +94,6 @@ MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { }; Native_Pin EPD_DC = Native_Pin(14); -#else -Native_Pin EPD_DC = Native_Pin(38); - -MCP23X17_Pin EPD_BUSY[NUM_SCREENS] = { - MCP23X17_Pin(mcp1, 8), - MCP23X17_Pin(mcp1, 9), - MCP23X17_Pin(mcp1, 10), - MCP23X17_Pin(mcp1, 11), - MCP23X17_Pin(mcp1, 12), - MCP23X17_Pin(mcp1, 13), - MCP23X17_Pin(mcp1, 14), - MCP23X17_Pin(mcp1, 4), -}; - -MCP23X17_Pin EPD_CS[NUM_SCREENS] = { - MCP23X17_Pin(mcp2, 8), MCP23X17_Pin(mcp2, 10), MCP23X17_Pin(mcp2, 12), - MCP23X17_Pin(mcp2, 14), MCP23X17_Pin(mcp2, 0), MCP23X17_Pin(mcp2, 2), - MCP23X17_Pin(mcp2, 4), MCP23X17_Pin(mcp2, 6)}; - -MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { - MCP23X17_Pin(mcp2, 9), - MCP23X17_Pin(mcp2, 11), - MCP23X17_Pin(mcp2, 13), - MCP23X17_Pin(mcp2, 15), - MCP23X17_Pin(mcp2, 1), - MCP23X17_Pin(mcp2, 3), - MCP23X17_Pin(mcp2, 5), - MCP23X17_Pin(mcp2, 7), -}; - #endif GxEPD2_BW displays[NUM_SCREENS] = { @@ -141,7 +170,7 @@ void setupDisplays() updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem)); - xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", 4096, NULL, 11, NULL); + xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", 2048, NULL, 11, NULL); for (uint i = 0; i < NUM_SCREENS; i++) { diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index a809e75..8efe6d9 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -329,4 +329,40 @@ void ledTheaterChaseRainbow(int wait) { } } -Adafruit_NeoPixel getPixels() { return pixels; } \ No newline at end of file +Adafruit_NeoPixel getPixels() { return pixels; } + +#ifdef HAS_FRONTLIGHT +int flDelayTime = 10; + +void frontlightFadeInAll() { + for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) { + for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) { + flArray.setPWM(ledPin, 0, dutyCycle); + } + delay(flDelayTime); + } +} + +void frontlightFadeOutAll() { + for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= 5) { + for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) { + flArray.setPWM(ledPin, 0, dutyCycle); + } + delay(flDelayTime); + } +} + +void frontlightFadeIn(uint num) { + for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) { + flArray.setPWM(num, 0, dutyCycle); + delay(flDelayTime); + } +} + +void frontlightFadeOut(uint num) { + for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= 5) { + flArray.setPWM(num, 0, dutyCycle); + delay(flDelayTime); + } +} +#endif \ No newline at end of file diff --git a/src/lib/led_handler.hpp b/src/lib/led_handler.hpp index 8e064f5..c7771b2 100644 --- a/src/lib/led_handler.hpp +++ b/src/lib/led_handler.hpp @@ -56,4 +56,11 @@ void setLights(uint32_t color); void ledRainbow(int wait); void ledTheaterChaseRainbow(int wait); void ledTheaterChase(uint32_t color, int wait); -Adafruit_NeoPixel getPixels(); \ No newline at end of file +Adafruit_NeoPixel getPixels(); + +#ifdef HAS_FRONTLIGHT +void frontlightFadeInAll(); +void frontlightFadeOutAll(); +void frontlightFadeIn(uint num); +void frontlightFadeOut(uint num); +#endif \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 01f8c60..74e8db1 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -45,6 +45,7 @@ void setupWebserver() { "/api/show/custom", onApiShowTextAdvanced); server.addHandler(handler); + AsyncCallbackJsonWebHandler *lightsJsonHandler = new AsyncCallbackJsonWebHandler("/api/lights", onApiLightsSetJson); server.addHandler(lightsJsonHandler); @@ -301,7 +302,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) { } } - String uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness"}; + String uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness"}; for (String setting : uintSettings) { if (settings.containsKey(setting)) { @@ -418,6 +419,13 @@ void onApiSettingsGet(AsyncWebServerRequest *request) { root["ip"] = WiFi.localIP(); root["txPower"] = WiFi.getTxPower(); + #ifdef HAS_FRONTLIGHT + root["hasFrontlight"] = true; + root["flMaxBrightness"] = preferences.getUInt("flMaxBrightness", 4095); + #else + root["hasFrontlight"] = false; + #endif + #ifdef GIT_REV root["gitRev"] = String(GIT_REV); #endif @@ -761,6 +769,11 @@ void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json) { JsonArray lights = json.as(); if (lights.size() != pixels.numPixels()) { + if (!lights.size()) { + // if empty, assume off request + return onApiLightsOff(request); + } + Serial.printf("Invalid values for LED set %d\n", lights.size()); request->send(400); return;