diff --git a/data/src/css/style.scss b/data/src/css/style.scss index 6c9ed74..194a86e 100644 --- a/data/src/css/style.scss +++ b/data/src/css/style.scss @@ -126,4 +126,11 @@ nav { #toggleTimerArea { cursor: pointer; +} + +#system_info { + padding: 0; + li { + list-style: none; + } } \ No newline at end of file diff --git a/data/src/index.html b/data/src/index.html index 2865177..ec16f39 100644 --- a/data/src/index.html +++ b/data/src/index.html @@ -107,7 +107,6 @@
@@ -122,7 +121,19 @@ +
+

System info

+ + + + +
Loading, please wait...
@@ -191,6 +202,14 @@
Short amounts might shorten lifespan.
+
+
+
+ + +
+
+
@@ -215,6 +234,21 @@
+
+
+
+ + +
+
+
+
+
+
+ + +
+
@@ -232,9 +266,10 @@ {{#each screens }}
-
- - +
+ + +
{{/each}} @@ -250,10 +285,7 @@
- - - - +
diff --git a/data/src/js/script.ts b/data/src/js/script.ts index 9523f42..ad48c44 100644 --- a/data/src/js/script.ts +++ b/data/src/js/script.ts @@ -43,25 +43,38 @@ let processStatusData = (jsonData) => { if (!!window.EventSource) { - var source = new EventSource('/events'); + const connectEventSource = () => { + let source = new EventSource('/events'); - source.addEventListener('open', function (e) { - console.log("Status EventSource Connected"); - if (e.data) { + source.addEventListener('open', (e) => { + console.log("Status EventSource Connected"); + if (e.data) { + processStatusData(JSON.parse(e.data)); + } + }, false); + + source.addEventListener('error', (e) => { + if (e.target.readyState != EventSource.OPEN) { + console.log("Status EventSource Disconnected"); + setTimeout(connectEventSource, 10000); + } + source.close(); + }, false); + + source.addEventListener('status', (e) => { processStatusData(JSON.parse(e.data)); - } - }, false); + }, false); - source.addEventListener('error', function (e) { - if (e.target.readyState != EventSource.OPEN) { - console.log("Status EventSource Disconnected"); - } - source.close(); - }, false); + source.addEventListener('message', (e) => { + if (e.data == "closing") { + console.log("Closing EventSource, trying to reconnect in 10 seconds") + source.close(); + setTimeout(connectEventSource, 10000); + } + }, false); + }; - source.addEventListener('status', function (e) { - processStatusData(JSON.parse(e.data)); - }, false); + connectEventSource(); } @@ -106,8 +119,14 @@ fetch('/api/settings', { if (jsonData.mcapBigChar) document.getElementById('mcapBigChar').checked = true; - if (jsonData.useBitcoinNode) - document.getElementById('useBitcoinNode').checked = true; + if (jsonData.mdnsEnabled) + document.getElementById('mdnsEnabled').checked = true; + + if (jsonData.otaEnabled) + document.getElementById('otaEnabled').checked = true; + + if (jsonData.ledTestOnPower) + document.getElementById('ledTestOnPower').checked = true; // let nodeFields = ["rpcHost", "rpcPort", "rpcUser", "tzOffset"]; @@ -123,10 +142,16 @@ fetch('/api/settings', { document.getElementById('minSecPriceUpd').value = jsonData.minSecPriceUpd; if (jsonData.gitRev) - document.getElementById('gitRev').innerHTML = "Version: " + jsonData.gitRev; + document.getElementById('gitRev').innerHTML = jsonData.gitRev; + + if (jsonData.hostname) + document.getElementById('hostname').innerHTML = jsonData.hostname; + + if (jsonData.ip) + document.getElementById('ipAddress').innerHTML = jsonData.ip; if (jsonData.lastBuildTime) - document.getElementById('lastBuildTime').innerHTML = " / " + new Date((jsonData.lastBuildTime * 1000)).toLocaleString(); + document.getElementById('lastBuildTime').innerHTML = new Date((jsonData.lastBuildTime * 1000)).toLocaleString(); var source = document.getElementById("screens-template").innerHTML; var template = Handlebars.compile(source); @@ -160,6 +185,11 @@ document.getElementById('restartBtn').onclick = (event) => { return false; } +document.getElementById('forceFullRefresh').onclick = (event) => { + fetch('/api/full_refresh'); + return false; +} + var ledsForm = document.querySelector('#ledsForm'); ledsForm.onsubmit = (event) => { diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 21f808d..68e3c75 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -42,6 +42,8 @@ void setup() void tryImprovSetup() { + WiFi.onEvent(WiFiEvent); + if (!preferences.getBool("wifiConfigured", false)) { setFgColor(GxEPD_BLACK); @@ -434,4 +436,66 @@ void improv_set_error(improv::Error error) data[10] = checksum; Serial.write(data.data(), data.size()); -} \ No newline at end of file +} + +void WiFiEvent(WiFiEvent_t event) +{ + Serial.printf("[WiFi-event] event: %d\n", event); + + switch (event) { + case ARDUINO_EVENT_WIFI_READY: + Serial.println("WiFi interface ready"); + break; + case ARDUINO_EVENT_WIFI_SCAN_DONE: + Serial.println("Completed scan for access points"); + break; + case ARDUINO_EVENT_WIFI_STA_START: + Serial.println("WiFi client started"); + break; + case ARDUINO_EVENT_WIFI_STA_STOP: + Serial.println("WiFi clients stopped"); + break; + case ARDUINO_EVENT_WIFI_STA_CONNECTED: + Serial.println("Connected to access point"); + break; + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: + Serial.println("Disconnected from WiFi access point"); + queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR); + break; + case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: + Serial.println("Authentication mode of access point has changed"); + break; + case ARDUINO_EVENT_WIFI_STA_GOT_IP: + Serial.print("Obtained IP address: "); + Serial.println(WiFi.localIP()); + break; + case ARDUINO_EVENT_WIFI_STA_LOST_IP: + Serial.println("Lost IP address and IP address is reset to 0"); + queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR); + break; + case ARDUINO_EVENT_WIFI_AP_START: + Serial.println("WiFi access point started"); + break; + case ARDUINO_EVENT_WIFI_AP_STOP: + Serial.println("WiFi access point stopped"); + break; + case ARDUINO_EVENT_WIFI_AP_STACONNECTED: + Serial.println("Client connected"); + break; + case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: + Serial.println("Client disconnected"); + break; + case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: + Serial.println("Assigned IP address to client"); + break; + case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: + Serial.println("Received probe request"); + break; + case ARDUINO_EVENT_WIFI_AP_GOT_IP6: + Serial.println("AP IPv6 is preferred"); + break; + case ARDUINO_EVENT_WIFI_STA_GOT_IP6: + Serial.println("STA IPv6 is preferred"); + break; + default: break; + }} \ No newline at end of file diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 6537bfc..7f6d5cb 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -56,3 +56,5 @@ void onImprovErrorCallback(improv::Error err); void improv_set_state(improv::State state); void improv_send_response(std::vector &response); void improv_set_error(improv::Error error); + +void WiFiEvent(WiFiEvent_t event); \ No newline at end of file diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 248a035..560bfef 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -68,6 +68,14 @@ int bgColor = GxEPD_BLACK; uint8_t qrcode[800]; +void forceFullRefresh() +{ + for (uint i = 0; i < NUM_SCREENS; i++) + { + lastFullRefresh[i] = NULL; + } +} + void setupDisplays() { for (uint i = 0; i < NUM_SCREENS; i++) @@ -77,7 +85,7 @@ void setupDisplays() updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem)); - xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", 4096, NULL, tskIDLE_PRIORITY, NULL); + xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", 4096, NULL, 11, NULL); for (uint i = 0; i < NUM_SCREENS; i++) { @@ -86,8 +94,8 @@ void setupDisplays() int *taskParam = new int; *taskParam = i; - - xTaskCreate(updateDisplay, ("EpdUpd" + String(i)).c_str(), 2048, taskParam, tskIDLE_PRIORITY, &tasks[i]); // create task + + xTaskCreate(updateDisplay, ("EpdUpd" + String(i)).c_str(), 2048, taskParam, 11, &tasks[i]); // create task } epdContent = {"B", @@ -103,12 +111,16 @@ void setupDisplays() void setEpdContent(std::array newEpdContent) { - epdContent = newEpdContent; + setEpdContent(newEpdContent, false); +} +void setEpdContent(std::array newEpdContent, bool forceUpdate) +{ for (uint i = 0; i < NUM_SCREENS; i++) { - if (epdContent[i].compareTo(currentEpdContent[i]) != 0) + if (newEpdContent[i].compareTo(currentEpdContent[i]) != 0 || forceUpdate) { + epdContent[i] = newEpdContent[i]; UpdateDisplayTaskItem dispUpdate = {i}; xQueueSend(updateQueue, &dispUpdate, portMAX_DELAY); // if (xSemaphoreTake(epdUpdateSemaphore[i], pdMS_TO_TICKS(5000)) == pdTRUE) @@ -132,21 +144,11 @@ void prepareDisplayUpdateTask(void *pvParameters) if (xQueueReceive(updateQueue, &receivedItem, portMAX_DELAY)) { uint epdIndex = receivedItem.dispNum; - if (epdContent[epdIndex].compareTo(currentEpdContent[epdIndex]) != 0) - { - - displays[epdIndex].init(0, false); // Little longer reset duration because of MCP - } + + displays[epdIndex].init(0, false); // Little longer reset duration because of MCP bool updatePartial = true; - // // Full Refresh every half hour - // if (!lastFullRefresh[epdIndex] || (millis() - lastFullRefresh[epdIndex]) > (preferences.getUInt("fullRefreshMin", 30) * 60 * 1000)) - // { - // updatePartial = false; - // lastFullRefresh[epdIndex] = millis(); - // } - if (strstr(epdContent[epdIndex].c_str(), "/") != NULL) { String top = epdContent[epdIndex].substring(0, epdContent[epdIndex].indexOf("/")); @@ -174,10 +176,7 @@ void prepareDisplayUpdateTask(void *pvParameters) } } - if (xSemaphoreTake(epdUpdateSemaphore[epdIndex], pdMS_TO_TICKS(5000)) == pdTRUE) - { - xTaskNotifyGive(tasks[epdIndex]); - } + xTaskNotifyGive(tasks[epdIndex]); } } } @@ -192,10 +191,8 @@ extern "C" void updateDisplay(void *pvParameters) noexcept // Wait for the task notification ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - // if (epdContent[epdIndex].compareTo(currentEpdContent[epdIndex]) != 0) + // if (xSemaphoreTake(epdUpdateSemaphore[epdIndex], pdMS_TO_TICKS(5000)) == pdTRUE) // { - - // displays[epdIndex].init(0, false); // Little longer reset duration because of MCP uint count = 0; while (EPD_BUSY[epdIndex].digitalRead() == HIGH || count < 10) { @@ -213,75 +210,29 @@ extern "C" void updateDisplay(void *pvParameters) noexcept if (!lastFullRefresh[epdIndex] || (millis() - lastFullRefresh[epdIndex]) > (preferences.getUInt("fullRefreshMin", 30) * 60 * 1000)) { updatePartial = false; - lastFullRefresh[epdIndex] = millis(); } -// // if (updatePartial) -// // { -// // displays[epdIndex].setPartialWindow(0, 0, displays[i].width(), display[i].height()); -// // } - -// if (strstr(epdContent[epdIndex].c_str(), "/") != NULL) -// { -// String top = epdContent[epdIndex].substring(0, epdContent[epdIndex].indexOf("/")); -// String bottom = epdContent[epdIndex].substring(epdContent[epdIndex].indexOf("/") + 1); -// #ifdef PAGED_WRITE -// splitTextPaged(epdIndex, top, bottom, updatePartial); -// #else -// splitText(epdIndex, top, bottom, updatePartial); -// #endif -// } -// else if (epdContent[epdIndex].startsWith(F("qr"))) -// { -// renderQr(epdIndex, epdContent[epdIndex], updatePartial); -// } -// else if (epdContent[epdIndex].length() > 5) -// { -// renderText(epdIndex, epdContent[epdIndex], updatePartial); -// } -// else -// { - -// #ifdef PAGED_WRITE -// showDigitPaged(epdIndex, epdContent[epdIndex].c_str()[0], updatePartial, &FONT_BIG); -// #else -// if (epdContent[epdIndex].length() > 1) -// { -// showChars(epdIndex, epdContent[epdIndex], updatePartial, &Antonio_SemiBold30pt7b); -// } -// else -// { -// showDigit(epdIndex, epdContent[epdIndex].c_str()[0], updatePartial, &FONT_BIG); -// } -// #endif -// } - -// #ifdef PAGED_WRITE -// currentEpdContent[epdIndex] = epdContent[epdIndex]; -// #else char tries = 0; while (tries < 3) { if (displays[epdIndex].displayWithReturn(updatePartial)) { displays[epdIndex].hibernate(); + currentEpdContent[epdIndex] = epdContent[epdIndex]; + if (!updatePartial) + lastFullRefresh[epdIndex] = millis(); break; } vTaskDelay(pdMS_TO_TICKS(100)); tries++; } - currentEpdContent[epdIndex] = epdContent[epdIndex]; -// #endif + // xSemaphoreGive(epdUpdateSemaphore[epdIndex]); // } - xSemaphoreGive(epdUpdateSemaphore[epdIndex]); } } -void updateDisplayAlt(int epdIndex) -{ -} void splitText(const uint dispNum, const String &top, const String &bottom, bool partial) { @@ -319,55 +270,6 @@ void splitText(const uint dispNum, const String &top, const String &bottom, bool displays[dispNum].print(bottom); } -// void splitTextPaged(const uint dispNum, String top, String bottom, bool partial) -// { -// displays[dispNum].setRotation(2); -// displays[dispNum].setFont(&FONT_SMALL); -// displays[dispNum].setTextColor(getFgColor()); - -// // Top text -// int16_t ttbx, ttby; -// uint16_t ttbw, ttbh; -// displays[dispNum].getTextBounds(top, 0, 0, &ttbx, &ttby, &ttbw, &ttbh); -// uint16_t tx = ((displays[dispNum].width() - ttbw) / 2) - ttbx; -// uint16_t ty = ((displays[dispNum].height() - ttbh) / 2) - ttby - ttbh / 2 - 12; - -// // Bottom text -// int16_t tbbx, tbby; -// uint16_t tbbw, tbbh; -// displays[dispNum].getTextBounds(bottom, 0, 0, &tbbx, &tbby, &tbbw, &tbbh); -// uint16_t bx = ((displays[dispNum].width() - tbbw) / 2) - tbbx; -// uint16_t by = ((displays[dispNum].height() - tbbh) / 2) - tbby + tbbh / 2 + 12; - -// // Make separator as wide as the shortest text. -// uint16_t lineWidth, lineX; -// if (tbbw < ttbh) -// lineWidth = tbbw; -// else -// lineWidth = ttbw; -// lineX = round((displays[dispNum].width() - lineWidth) / 2); - -// if (partial) -// { -// displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), displays[dispNum].height()); -// } -// else -// { -// displays[dispNum].setFullWindow(); -// } -// displays[dispNum].firstPage(); - -// do -// { -// displays[dispNum].fillScreen(getBgColor()); -// displays[dispNum].setCursor(tx, ty); -// displays[dispNum].print(top); -// displays[dispNum].fillRoundRect(lineX, displays[dispNum].height() / 2 - 3, lineWidth, 6, 3, getFgColor()); -// displays[dispNum].setCursor(bx, by); -// displays[dispNum].print(bottom); -// } while (displays[dispNum].nextPage()); -// } - void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font) { String str(chr); @@ -401,36 +303,6 @@ void showChars(const uint dispNum, const String &chars, bool partial, const GFXf displays[dispNum].print(chars); } -// void showDigitPaged(const uint dispNum, char chr, bool partial, const GFXfont *font) -// { -// String str(chr); -// displays[dispNum].setRotation(2); -// displays[dispNum].setFont(font); -// displays[dispNum].setTextColor(getFgColor()); -// int16_t tbx, tby; -// uint16_t tbw, tbh; -// displays[dispNum].getTextBounds(str, 0, 0, &tbx, &tby, &tbw, &tbh); -// // center the bounding box by transposition of the origin: -// uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx; -// uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby; -// if (partial) -// { -// displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), displays[dispNum].height()); -// } -// else -// { -// displays[dispNum].setFullWindow(); -// } -// displays[dispNum].firstPage(); - -// do -// { -// displays[dispNum].fillScreen(getBgColor()); -// displays[dispNum].setCursor(x, y); -// displays[dispNum].print(str); -// } while (displays[dispNum].nextPage()); -// } - int getBgColor() { return bgColor; @@ -526,9 +398,14 @@ void waitUntilNoneBusy() { for (int i = 0; i < NUM_SCREENS; i++) { + uint count = 0; while (EPD_BUSY[i].digitalRead()) { + count++; vTaskDelay(10); + if (count > 200) { + displays[i].init(0, false); + } } } } \ No newline at end of file diff --git a/src/lib/epd.hpp b/src/lib/epd.hpp index b4601fc..5f34f06 100644 --- a/src/lib/epd.hpp +++ b/src/lib/epd.hpp @@ -20,17 +20,14 @@ typedef struct char dispNum; } UpdateDisplayTaskItem; +void forceFullRefresh(); void setupDisplays(); -// void taskEpd(void *pvParameters); void splitText(const uint dispNum, const String& top, const String& bottom, bool partial); -//void splitTextPaged(const uint dispNum, String top, String bottom, bool partial); void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font); void showChars(const uint dispNum, const String& chars, bool partial, const GFXfont *font); -//void showDigitPaged(const uint dispNum, char chr, bool partial, const GFXfont *font); - extern "C" void updateDisplay(void *pvParameters) noexcept; void updateDisplayAlt(int epdIndex); void prepareDisplayUpdateTask(void *pvParameters); @@ -43,6 +40,7 @@ void setFgColor(int color); void renderText(const uint dispNum, const String& text, bool partial); void renderQr(const uint dispNum, const String& text, bool partial); +void setEpdContent(std::array newEpdContent, bool forceUpdate); void setEpdContent(std::array newEpdContent); std::array getCurrentEpdContent(); void waitUntilNoneBusy(); \ No newline at end of file diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index eaa3ae8..fa49108 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -24,14 +24,12 @@ void ledTask(void *parameter) switch (ledTaskParams) { case LED_POWER_TEST: - pixels.setPixelColor(0, pixels.Color(255, 0, 0)); - pixels.setPixelColor(1, pixels.Color(0, 255, 0)); - pixels.setPixelColor(2, pixels.Color(0, 0, 255)); - pixels.setPixelColor(3, pixels.Color(255, 255, 255)); - pixels.show(); - vTaskDelay(pdMS_TO_TICKS(1000)); + ledRainbow(20); + pixels.clear(); break; case LED_EFFECT_WIFI_CONNECT_ERROR: + blinkDelayTwoColor(100, 1, pixels.Color(8, 161, 236), pixels.Color(255, 0, 0)); + break; case LED_FLASH_ERROR: blinkDelayColor(250, 3, 255, 0, 0); break; @@ -39,6 +37,16 @@ void ledTask(void *parameter) case LED_FLASH_SUCCESS: blinkDelayColor(150, 3, 0, 255, 0); break; + case LED_PROGRESS_100: + pixels.setPixelColor(0, pixels.Color(0, 255, 0)); + case LED_PROGRESS_75: + pixels.setPixelColor(1, pixels.Color(0, 255, 0)); + case LED_PROGRESS_50: + pixels.setPixelColor(2, pixels.Color(0, 255, 0)); + case LED_PROGRESS_25: + pixels.setPixelColor(3, pixels.Color(0, 255, 0)); + pixels.show(); + break; case LED_FLASH_UPDATE: break; case LED_FLASH_BLOCK_NOTIFY: @@ -122,14 +130,12 @@ void ledTask(void *parameter) // revert to previous state unless power test - if (ledTaskParams != LED_POWER_TEST) { - for (int i = 0; i < NEOPIXEL_COUNT; i++) - { - pixels.setPixelColor(i, oldLights[i]); - } - - pixels.show(); + for (int i = 0; i < NEOPIXEL_COUNT; i++) + { + pixels.setPixelColor(i, oldLights[i]); } + + pixels.show(); } } } @@ -240,7 +246,13 @@ void setLights(int r, int g, int b) void setLights(uint32_t color) { preferences.putUInt("ledColor", color); - preferences.putBool("ledStatus", true); + + bool ledStatus = true; + if (color == pixels.Color(0, 0, 0)) + { + ledStatus = false; + } + preferences.putBool("ledStatus", false); for (int i = 0; i < NEOPIXEL_COUNT; i++) { @@ -264,4 +276,74 @@ bool queueLedEffect(uint effect) uint flashType = effect; xQueueSend(ledTaskQueue, &flashType, portMAX_DELAY); +} + +void ledRainbow(int wait) +{ + // Hue of first pixel runs 5 complete loops through the color wheel. + // Color wheel has a range of 65536 but it's OK if we roll over, so + // just count from 0 to 5*65536. Adding 256 to firstPixelHue each time + // means we'll make 5*65536/256 = 1280 passes through this loop: + for (long firstPixelHue = 0; firstPixelHue < 5 * 65536; firstPixelHue += 256) + { + // strip.rainbow() can take a single argument (first pixel hue) or + // optionally a few extras: number of rainbow repetitions (default 1), + // saturation and value (brightness) (both 0-255, similar to the + // ColorHSV() function, default 255), and a true/false flag for whether + // to apply gamma correction to provide 'truer' colors (default true). + pixels.rainbow(firstPixelHue); + // Above line is equivalent to: + // strip.rainbow(firstPixelHue, 1, 255, 255, true); + pixels.show(); // Update strip with new contents + delayMicroseconds(wait); + // vTaskDelay(pdMS_TO_TICKS(wait)); // Pause for a moment + } +} + +void ledTheaterChase(uint32_t color, int wait) +{ + for (int a = 0; a < 10; a++) + { // Repeat 10 times... + for (int b = 0; b < 3; b++) + { // 'b' counts from 0 to 2... + pixels.clear(); // Set all pixels in RAM to 0 (off) + // 'c' counts up from 'b' to end of strip in steps of 3... + for (int c = b; c < pixels.numPixels(); c += 3) + { + pixels.setPixelColor(c, color); // Set pixel 'c' to value 'color' + } + pixels.show(); // Update strip with new contents + vTaskDelay(pdMS_TO_TICKS(wait)); // Pause for a moment + } + } +} + +void ledTheaterChaseRainbow(int wait) +{ + int firstPixelHue = 0; // First pixel starts at red (hue 0) + for (int a = 0; a < 30; a++) + { // Repeat 30 times... + for (int b = 0; b < 3; b++) + { // 'b' counts from 0 to 2... + pixels.clear(); // Set all pixels in RAM to 0 (off) + // 'c' counts up from 'b' to end of strip in increments of 3... + for (int c = b; c < pixels.numPixels(); c += 3) + { + // hue of pixel 'c' is offset by an amount to make one full + // revolution of the color wheel (range 65536) along the length + // of the strip (strip.numPixels() steps): + int hue = firstPixelHue + c * 65536L / pixels.numPixels(); + uint32_t color = pixels.gamma32(pixels.ColorHSV(hue)); // hue -> RGB + pixels.setPixelColor(c, color); // Set pixel 'c' to value 'color' + } + pixels.show(); // Update strip with new contents + vTaskDelay(pdMS_TO_TICKS(wait)); // Pause for a moment + firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames + } + } +} + +Adafruit_NeoPixel getPixels() +{ + return pixels; } \ No newline at end of file diff --git a/src/lib/led_handler.hpp b/src/lib/led_handler.hpp index 987b688..acca0bf 100644 --- a/src/lib/led_handler.hpp +++ b/src/lib/led_handler.hpp @@ -22,8 +22,13 @@ const int LED_EFFECT_WIFI_CONNECTING = 101; const int LED_EFFECT_WIFI_CONNECT_ERROR = 102; const int LED_EFFECT_WIFI_CONNECT_SUCCESS = 103; const int LED_EFFECT_WIFI_ERASE_SETTINGS = 104; +const int LED_PROGRESS_25 = 200; +const int LED_PROGRESS_50 = 201; +const int LED_PROGRESS_75 = 202; +const int LED_PROGRESS_100 = 203; const int LED_POWER_TEST = 999; extern TaskHandle_t ledTaskHandle; +extern Adafruit_NeoPixel pixels; void ledTask(void *pvParameters); void setupLeds(); @@ -35,4 +40,8 @@ void clearLeds(); QueueHandle_t getLedTaskQueue(); bool queueLedEffect(uint effect); void setLights(int r, int g, int b); -void setLights(uint32_t color); \ No newline at end of file +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 diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index ad5800d..7740027 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -2,53 +2,89 @@ TaskHandle_t taskOtaHandle = NULL; - void setupOTA() { + if (preferences.getBool("otaEnabled", true)) + { ArduinoOTA.onStart(onOTAStart); - ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) - { Serial.printf("OTA Progress: %u%%\r", (progress / (total / 100))); }); - - ArduinoOTA.onEnd([]() - { Serial.println("\nOTA update finished"); }); + ArduinoOTA.onProgress(onOTAProgress); + ArduinoOTA.onEnd(onOTAComplete); ArduinoOTA.setHostname(getMyHostname().c_str()); ArduinoOTA.setMdnsEnabled(false); + ArduinoOTA.setRebootOnSuccess(false); ArduinoOTA.begin(); xTaskCreate(handleOTATask, "handleOTA", 4096, NULL, tskIDLE_PRIORITY, &taskOtaHandle); + } +} +void onOTAProgress(unsigned int progress, unsigned int total) +{ + uint percentage = progress / (total / 100); + pixels.fill(pixels.Color(0, 255, 0)); + if (percentage < 100) + { + pixels.setPixelColor(0, pixels.Color(0, 0, 0)); + } + if (percentage < 75) + { + pixels.setPixelColor(1, pixels.Color(0, 0, 0)); + } + if (percentage < 50) + { + pixels.setPixelColor(2, pixels.Color(0, 0, 0)); + } + if (percentage < 25) + { + pixels.setPixelColor(3, pixels.Color(0, 0, 0)); + } + pixels.show(); } void onOTAStart() { - // Stop all timers - esp_timer_stop(screenRotateTimer); - esp_timer_stop(minuteTimer); + forceFullRefresh(); + std::array epdContent = {"U", "P", "D", "A", "T", "E", "!"}; + setEpdContent(epdContent); + // Stop all timers + esp_timer_stop(screenRotateTimer); + esp_timer_stop(minuteTimer); - // Stop or suspend all tasks + // Stop or suspend all tasks // vTaskSuspend(priceUpdateTaskHandle); -// vTaskSuspend(blockUpdateTaskHandle); - vTaskSuspend(workerTaskHandle); - vTaskSuspend(taskScreenRotateTaskHandle); + // vTaskSuspend(blockUpdateTaskHandle); + vTaskSuspend(workerTaskHandle); + vTaskSuspend(taskScreenRotateTaskHandle); - vTaskSuspend(ledTaskHandle); - vTaskSuspend(buttonTaskHandle); + vTaskSuspend(ledTaskHandle); + vTaskSuspend(buttonTaskHandle); - stopWebServer(); - stopBlockNotify(); - stopPriceNotify(); + stopWebServer(); + stopBlockNotify(); + stopPriceNotify(); } -void handleOTATask(void *parameter) { - for (;;) { - // Task 1 code - ArduinoOTA.handle(); // Allow OTA updates to occur +void handleOTATask(void *parameter) +{ + for (;;) + { + ArduinoOTA.handle(); // Allow OTA updates to occur vTaskDelay(pdMS_TO_TICKS(2500)); } } -void downloadUpdate() { -} \ No newline at end of file +void downloadUpdate() +{ +} + +void onOTAComplete() +{ + Serial.println("\nOTA update finished"); + Wire.end(); + SPI.end(); + delay(1000); + ESP.restart(); +} diff --git a/src/lib/ota.hpp b/src/lib/ota.hpp index 7a6ec81..69b5613 100644 --- a/src/lib/ota.hpp +++ b/src/lib/ota.hpp @@ -6,4 +6,6 @@ void setupOTA(); void onOTAStart(); void handleOTATask(void *parameter); -void downloadUpdate(); \ No newline at end of file +void onOTAProgress(unsigned int progress, unsigned int total); +void downloadUpdate(); +void onOTAComplete(); \ No newline at end of file diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index bef59cf..e92040b 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -27,7 +27,7 @@ const int usPerMinute = 60 * usPerSecond; // } WorkItem; #define WORK_QUEUE_SIZE 10 -QueueHandle_t workQueue; +QueueHandle_t workQueue = NULL; uint currentScreen; @@ -257,6 +257,7 @@ void setupTasks() xTaskCreate(taskScreenRotate, "rotateScreen", 2048, NULL, tskIDLE_PRIORITY, &taskScreenRotateTaskHandle); + waitUntilNoneBusy(); setCurrentScreen(preferences.getUInt("currentScreen", 0)); } diff --git a/src/lib/utils.cpp b/src/lib/utils.cpp index 5eaf958..abf5d51 100644 --- a/src/lib/utils.cpp +++ b/src/lib/utils.cpp @@ -6,9 +6,13 @@ int modulo(int x, int N) } String getMyHostname() { - byte mac[6]; - WiFi.macAddress(mac); - return "btclock" + String(mac[4], 16) = String(mac[5], 16); + uint8_t mac[6]; + //WiFi.macAddress(mac); + esp_efuse_mac_get_default(mac); + char hostname[15]; + snprintf(hostname, sizeof(hostname), "btclock-%02x%02x%02x", + mac[3], mac[4], mac[5]); + return hostname; } double getSupplyAtBlock(uint blockNr) { diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index d94c129..2c4c958 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -24,6 +24,7 @@ void setupWebserver() server.on("/api/status", HTTP_GET, onApiStatus); server.on("/api/system_status", HTTP_GET, onApiSystemStatus); + server.on("/api/full_refresh", HTTP_GET, onApiFullRefresh); server.on("/api/action/pause", HTTP_GET, onApiActionPause); server.on("/api/action/timer_restart", HTTP_GET, onApiActionTimerRestart); @@ -50,15 +51,22 @@ void setupWebserver() DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); server.begin(); - // if (!MDNS.begin(getMyHostname())) - // { - // Serial.println(F("Error setting up MDNS responder!")); - // while (1) - // { - // delay(1000); - // } - // } - // MDNS.addService("http", "tcp", 80); + + if (preferences.getBool("mdnsEnabled", true)) + { + if (!MDNS.begin(getMyHostname())) + { + Serial.println(F("Error setting up MDNS responder!")); + while (1) + { + delay(1000); + } + } + MDNS.addService("http", "tcp", 80); + MDNS.addServiceTxt("http", "tcp", "model", "BTClock"); + MDNS.addServiceTxt("http", "tcp", "version", "3.0"); + MDNS.addServiceTxt("http", "tcp", "rev", GIT_REV); + } xTaskCreate(eventSourceTask, "eventSourceTask", 4096, NULL, tskIDLE_PRIORITY, &eventSourceTaskHandle); } @@ -152,6 +160,20 @@ void onApiActionTimerRestart(AsyncWebServerRequest *request) request->send(200); } +/** + * @Api + * @Path("/api/full_refresh") + */ +void onApiFullRefresh(AsyncWebServerRequest *request) +{ + forceFullRefresh(); + std::array newEpdContent = getCurrentEpdContent(); + + setEpdContent(newEpdContent, true); + + request->send(200); +} + void onApiShowScreen(AsyncWebServerRequest *request) { if (request->hasParam("s")) @@ -186,6 +208,12 @@ void onApiShowText(AsyncWebServerRequest *request) void onApiRestart(AsyncWebServerRequest *request) { request->send(200); + + if (events.count()) + events.send("closing"); + + delay(500); + esp_restart(); } @@ -207,15 +235,17 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["wpTimeout"] = preferences.getUInt("wpTimeout", 600); root["tzOffset"] = preferences.getInt("gmtOffset", TIME_OFFSET_SECONDS) / 60; root["useBitcoinNode"] = preferences.getBool("useNode", false); - // root["rpcPort"] = preferences.getUInt("rpcPort", BITCOIND_PORT); - // root["rpcUser"] = preferences.getString("rpcUser", BITCOIND_RPC_USER); - // root["rpcHost"] = preferences.getString("rpcHost", BITCOIND_HOST); root["mempoolInstance"] = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); root["ledTestOnPower"] = preferences.getBool("ledTestOnPower", true); root["ledFlashOnUpdate"] = preferences.getBool("ledFlashOnUpd", false); root["ledBrightness"] = preferences.getUInt("ledBrightness", 128); root["stealFocusOnBlock"] = preferences.getBool("stealFocus", true); root["mcapBigChar"] = preferences.getBool("mcapBigChar", true); + root["mdnsEnabled"] = preferences.getBool("mdnsEnabled", true); + root["otaEnabled"] = preferences.getBool("otaEnabled", true); + + root["hostname"] = getMyHostname(); + root["ip"] = WiFi.localIP(); #ifdef GIT_REV root["gitRev"] = String(GIT_REV); @@ -274,18 +304,55 @@ void onApiSettingsPost(AsyncWebServerRequest *request) settingsChanged = processEpdColorSettings(request); + if (request->hasParam("ledTestOnPower", true)) + { + AsyncWebParameter *ledTestOnPower = request->getParam("ledTestOnPower", true); + + preferences.putBool("ledTestOnPower", ledTestOnPower->value().toInt()); + settingsChanged = true; + } + else + { + preferences.putBool("ledTestOnPower", 0); + settingsChanged = true; + } + if (request->hasParam("ledFlashOnUpd", true)) { AsyncWebParameter *ledFlashOnUpdate = request->getParam("ledFlashOnUpd", true); preferences.putBool("ledFlashOnUpd", ledFlashOnUpdate->value().toInt()); -// Serial.printf("Setting led flash on update to %d\r\n", ledFlashOnUpdate->value().toInt()); settingsChanged = true; } else { preferences.putBool("ledFlashOnUpd", 0); -// Serial.print("Setting led flash on update to false"); + settingsChanged = true; + } + + if (request->hasParam("mdnsEnabled", true)) + { + AsyncWebParameter *mdnsEnabled = request->getParam("mdnsEnabled", true); + + preferences.putBool("mdnsEnabled", mdnsEnabled->value().toInt()); + settingsChanged = true; + } + else + { + preferences.putBool("mdnsEnabled", 0); + settingsChanged = true; + } + + if (request->hasParam("otaEnabled", true)) + { + AsyncWebParameter *otaEnabled = request->getParam("otaEnabled", true); + + preferences.putBool("otaEnabled", otaEnabled->value().toInt()); + settingsChanged = true; + } + else + { + preferences.putBool("otaEnabled", 0); settingsChanged = true; } @@ -294,13 +361,11 @@ void onApiSettingsPost(AsyncWebServerRequest *request) AsyncWebParameter *stealFocusOnBlock = request->getParam("stealFocusOnBlock", true); preferences.putBool("stealFocus", stealFocusOnBlock->value().toInt()); -// Serial.printf("Setting steal focus on new block to %d\r\n", stealFocusOnBlock->value().toInt()); settingsChanged = true; } else { preferences.putBool("stealFocus", 0); -// Serial.print("Setting steal focus on new block to false"); settingsChanged = true; } @@ -309,13 +374,11 @@ void onApiSettingsPost(AsyncWebServerRequest *request) AsyncWebParameter *mcapBigChar = request->getParam("mcapBigChar", true); preferences.putBool("mcapBigChar", mcapBigChar->value().toInt()); - Serial.printf("Setting big characters for market cap to %d\r\n", mcapBigChar->value().toInt()); settingsChanged = true; } else { preferences.putBool("mcapBigChar", 0); -// Serial.print("Setting big characters for market cap to false"); settingsChanged = true; } @@ -324,7 +387,6 @@ void onApiSettingsPost(AsyncWebServerRequest *request) AsyncWebParameter *mempoolInstance = request->getParam("mempoolInstance", true); preferences.putString("mempoolInstance", mempoolInstance->value().c_str()); -// Serial.printf("Setting mempool instance to %s\r\n", mempoolInstance->value().c_str()); settingsChanged = true; } @@ -333,7 +395,6 @@ void onApiSettingsPost(AsyncWebServerRequest *request) AsyncWebParameter *ledBrightness = request->getParam("ledBrightness", true); preferences.putUInt("ledBrightness", ledBrightness->value().toInt()); -// Serial.printf("Setting brightness to %d\r\n", ledBrightness->value().toInt()); settingsChanged = true; } @@ -342,7 +403,6 @@ void onApiSettingsPost(AsyncWebServerRequest *request) AsyncWebParameter *fullRefreshMin = request->getParam("fullRefreshMin", true); preferences.putUInt("fullRefreshMin", fullRefreshMin->value().toInt()); -// Serial.printf("Set full refresh minutes to %d\r\n", fullRefreshMin->value().toInt()); settingsChanged = true; } @@ -351,7 +411,6 @@ void onApiSettingsPost(AsyncWebServerRequest *request) AsyncWebParameter *wpTimeout = request->getParam("wpTimeout", true); preferences.putUInt("wpTimeout", wpTimeout->value().toInt()); -// Serial.printf("Set WiFi portal timeout seconds to %ld\r\n", wpTimeout->value().toInt()); settingsChanged = true; } @@ -367,7 +426,6 @@ void onApiSettingsPost(AsyncWebServerRequest *request) AsyncWebParameter *screenParam = request->getParam(key, true); visible = screenParam->value().toInt(); } -// Serial.printf("Setting screen %d to %d\r\n", i, visible); preferences.putBool(prefKey.c_str(), visible); } @@ -377,7 +435,6 @@ void onApiSettingsPost(AsyncWebServerRequest *request) AsyncWebParameter *p = request->getParam("tzOffset", true); int tzOffsetSeconds = p->value().toInt() * 60; preferences.putInt("gmtOffset", tzOffsetSeconds); -// Serial.printf("Setting tz offset to %d\r\n", tzOffsetSeconds); settingsChanged = true; } @@ -386,7 +443,6 @@ void onApiSettingsPost(AsyncWebServerRequest *request) AsyncWebParameter *p = request->getParam("minSecPriceUpd", true); int minSecPriceUpd = p->value().toInt(); preferences.putUInt("minSecPriceUpd", minSecPriceUpd); -// Serial.printf("Setting minSecPriceUpd to %d\r\n", minSecPriceUpd); settingsChanged = true; } @@ -398,34 +454,6 @@ void onApiSettingsPost(AsyncWebServerRequest *request) settingsChanged = true; } - // if (request->hasParam("useBitcoinNode", true)) - // { - // AsyncWebParameter *p = request->getParam("useBitcoinNode", true); - // bool useBitcoinNode = p->value().toInt(); - // preferences.putBool("useNode", useBitcoinNode); - // settingsChanged = true; - - // String rpcVars[] = {"rpcHost", "rpcPort", "rpcUser", "rpcPass"}; - - // for (String v : rpcVars) - // { - // if (request->hasParam(v, true)) - // { - // AsyncWebParameter *pv = request->getParam(v, true); - // // Don't store an empty password, probably new settings save - // if (!(v.equals("rpcPass") && pv->value().length() == 0)) - // { - // preferences.putString(v.c_str(), pv->value().c_str()); - // } - // } - // } - // } - // else - // { - // preferences.putBool("useNode", false); - // settingsChanged = true; - // } - request->send(200); if (settingsChanged) { @@ -460,9 +488,14 @@ void onApiLightsSetColor(AsyncWebServerRequest *request) if (request->hasParam("c")) { String rgbColor = request->getParam("c")->value(); - uint r, g, b; - sscanf(rgbColor.c_str(), "%02x%02x%02x", &r, &g, &b); - setLights(r, g, b); + + if (rgbColor.compareTo("off") == 0) { + setLights(0, 0, 0); + } else { + uint r, g, b; + sscanf(rgbColor.c_str(), "%02x%02x%02x", &r, &g, &b); + setLights(r, g, b); + } request->send(200, "text/plain", rgbColor); } } diff --git a/src/lib/webserver.hpp b/src/lib/webserver.hpp index 7af562e..70613dc 100644 --- a/src/lib/webserver.hpp +++ b/src/lib/webserver.hpp @@ -3,7 +3,7 @@ #include "ESPAsyncWebServer.h" #include #include -// #include +#include #include "lib/block_notify.hpp" #include "lib/price_notify.hpp" @@ -28,6 +28,7 @@ void onApiActionPause(AsyncWebServerRequest *request); void onApiActionTimerRestart(AsyncWebServerRequest *request); void onApiSettingsGet(AsyncWebServerRequest *request); void onApiSettingsPost(AsyncWebServerRequest *request); +void onApiFullRefresh(AsyncWebServerRequest *request); void onApiLightsOff(AsyncWebServerRequest *request); void onApiLightsSetColor(AsyncWebServerRequest *request);