Missing files in commit
This commit is contained in:
parent
4538326990
commit
687bc1f60d
23 changed files with 1485 additions and 61 deletions
8
scripts/git_rev.py
Normal file
8
scripts/git_rev.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
import subprocess
|
||||
|
||||
revision = (
|
||||
subprocess.check_output(["git", "rev-parse", "HEAD"])
|
||||
.strip()
|
||||
.decode("utf-8")
|
||||
)
|
||||
print("'-DGIT_REV=\"%s\"'" % revision)
|
6
src/idf_component.yml
Normal file
6
src/idf_component.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
dependencies:
|
||||
# Required IDF version
|
||||
idf: ">=4.4"
|
||||
|
||||
esp_littlefs:
|
||||
git: https://github.com/joltwallet/esp_littlefs.git
|
|
@ -7,21 +7,41 @@ unsigned long int currentBlockHeight;
|
|||
|
||||
void setupBlockNotify()
|
||||
{
|
||||
IPAddress result;
|
||||
|
||||
int dnsErr = -1;
|
||||
String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
|
||||
|
||||
while (dnsErr != 1) {
|
||||
dnsErr = WiFi.hostByName(mempoolInstance.c_str(), result);
|
||||
|
||||
if (dnsErr != 1) {
|
||||
Serial.print(mempoolInstance);
|
||||
Serial.println("mempool DNS could not be resolved");
|
||||
WiFi.reconnect();
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
Serial.println("mempool DNS can be resolved");
|
||||
|
||||
// Get current block height through regular API
|
||||
HTTPClient *http = new HTTPClient();
|
||||
http->begin("https://mempool.space/api/blocks/tip/height");
|
||||
http->begin("https://" + mempoolInstance + "/api/blocks/tip/height");
|
||||
int httpCode = http->GET();
|
||||
|
||||
if (httpCode > 0 && httpCode == HTTP_CODE_OK)
|
||||
{
|
||||
String blockHeightStr = http->getString();
|
||||
currentBlockHeight = blockHeightStr.toInt();
|
||||
xTaskNotifyGive(blockUpdateTaskHandle);
|
||||
}
|
||||
|
||||
esp_websocket_client_config_t config = {
|
||||
.uri = "wss://mempool.space/api/v1/ws",
|
||||
.uri = "wss://mempool.bitcoin.nl/api/v1/ws",
|
||||
};
|
||||
|
||||
Serial.printf("Connecting to %s\r\n", config.uri);
|
||||
|
||||
client = esp_websocket_client_init(&config);
|
||||
esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, onWebsocketEvent, client);
|
||||
esp_websocket_client_start(client);
|
||||
|
@ -36,14 +56,11 @@ void onWebsocketEvent(void *handler_args, esp_event_base_t base, int32_t event_i
|
|||
{
|
||||
case WEBSOCKET_EVENT_CONNECTED:
|
||||
Serial.println("Connected to Mempool.space WebSocket");
|
||||
// init = "{\"action\": \"init\"}";
|
||||
// if(esp_websocket_client_send_text(client, init.c_str(), init.length(), portMAX_DELAY) == -1) {
|
||||
// Serial.println("Error init");
|
||||
// }
|
||||
|
||||
sub = "{\"action\": \"want\", \"data\":[\"blocks\"]}";
|
||||
if (esp_websocket_client_send_text(client, sub.c_str(), sub.length(), portMAX_DELAY) == -1)
|
||||
{
|
||||
Serial.println("Error want");
|
||||
Serial.println("Mempool.space WS Block Subscribe Error");
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -52,17 +69,17 @@ void onWebsocketEvent(void *handler_args, esp_event_base_t base, int32_t event_i
|
|||
// Handle the received WebSocket message (block notifications) here
|
||||
break;
|
||||
case WEBSOCKET_EVENT_ERROR:
|
||||
Serial.println("Connnection error");
|
||||
Serial.println("Mempool.space WS Connnection error");
|
||||
break;
|
||||
case WEBSOCKET_EVENT_DISCONNECTED:
|
||||
Serial.println("Connnection Closed");
|
||||
Serial.println("Mempool.space WS Connnection Closed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void onWebsocketMessage(esp_websocket_event_data_t *event_data)
|
||||
{
|
||||
DynamicJsonDocument doc(event_data->data_len);
|
||||
SpiRamJsonDocument doc(event_data->data_len);
|
||||
|
||||
deserializeJson(doc, (char *)event_data->data_ptr);
|
||||
// serializeJsonPretty(doc, Serial);
|
||||
|
@ -82,7 +99,6 @@ void onWebsocketMessage(esp_websocket_event_data_t *event_data)
|
|||
doc.clear();
|
||||
}
|
||||
|
||||
|
||||
unsigned long getBlockHeight()
|
||||
{
|
||||
return currentBlockHeight;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <Arduino.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include "shared.hpp"
|
||||
|
||||
#include "esp_websocket_client.h"
|
||||
#include "screen_handler.hpp"
|
||||
|
|
63
src/lib/button_handler.cpp
Normal file
63
src/lib/button_handler.cpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
#include "button_handler.hpp"
|
||||
|
||||
TaskHandle_t buttonTaskHandle = NULL;
|
||||
const TickType_t debounceDelay = pdMS_TO_TICKS(50);
|
||||
TickType_t lastDebounceTime = 0;
|
||||
|
||||
void buttonTask(void *parameter)
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
TickType_t currentTime = xTaskGetTickCount();
|
||||
if ((currentTime - lastDebounceTime) >= debounceDelay)
|
||||
{
|
||||
lastDebounceTime = currentTime;
|
||||
|
||||
if (!digitalRead(MCP_INT_PIN))
|
||||
{
|
||||
uint pin = mcp.getLastInterruptPin();
|
||||
|
||||
Serial.printf("Button pressed: %d", pin);
|
||||
// switch (pin)
|
||||
// {
|
||||
// case 3:
|
||||
// toggleScreenTimer();
|
||||
// break;
|
||||
// case 2:
|
||||
// nextScreen();
|
||||
// break;
|
||||
// case 1:
|
||||
// previousScreen();
|
||||
// break;
|
||||
// case 0:
|
||||
// showNetworkSettings();
|
||||
// break;
|
||||
// }
|
||||
}
|
||||
mcp.clearInterrupts();
|
||||
// Very ugly, but for some reason this is necessary
|
||||
while (!digitalRead(MCP_INT_PIN))
|
||||
{
|
||||
mcp.clearInterrupts();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR handleButtonInterrupt()
|
||||
{
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
xTaskNotifyFromISR(buttonTaskHandle, 0, eNoAction, &xHigherPriorityTaskWoken);
|
||||
if (xHigherPriorityTaskWoken == pdTRUE)
|
||||
{
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
void setupButtonTask()
|
||||
{
|
||||
xTaskCreate(buttonTask, "ButtonTask", 4096, NULL, tskIDLE_PRIORITY, &buttonTaskHandle); // Create the FreeRTOS task
|
||||
// Use interrupt instead of task
|
||||
attachInterrupt(MCP_INT_PIN, handleButtonInterrupt, CHANGE);
|
||||
}
|
12
src/lib/button_handler.hpp
Normal file
12
src/lib/button_handler.hpp
Normal file
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include "shared.hpp"
|
||||
|
||||
extern TaskHandle_t buttonTaskHandle;
|
||||
|
||||
void buttonTask(void *pvParameters);
|
||||
void IRAM_ATTR handleButtonInterrupt();
|
||||
void setupButtonTask();
|
377
src/lib/config.cpp
Normal file
377
src/lib/config.cpp
Normal file
|
@ -0,0 +1,377 @@
|
|||
#include "config.hpp"
|
||||
|
||||
#ifndef NEOPIXEL_PIN
|
||||
#define NEOPIXEL_PIN 34
|
||||
#endif
|
||||
#ifndef NEOPIXEL_COUNT
|
||||
#define NEOPIXEL_COUNT 4
|
||||
#endif
|
||||
|
||||
#define MAX_ATTEMPTS_WIFI_CONNECTION 20
|
||||
|
||||
Preferences preferences;
|
||||
Adafruit_MCP23X17 mcp;
|
||||
Adafruit_NeoPixel pixels(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
|
||||
|
||||
void setup()
|
||||
{
|
||||
setupHardware();
|
||||
if (mcp.digitalRead(3) == LOW) {
|
||||
WiFi.eraseAP();
|
||||
blinkDelay(100, 3);
|
||||
}
|
||||
|
||||
tryImprovSetup();
|
||||
|
||||
setupDisplays();
|
||||
setupPreferences();
|
||||
setupWebserver();
|
||||
|
||||
// setupWifi();
|
||||
setupTime();
|
||||
finishSetup();
|
||||
|
||||
setupTasks();
|
||||
setupTimers();
|
||||
setupWebsocketClients();
|
||||
|
||||
setupButtonTask();
|
||||
}
|
||||
|
||||
void tryImprovSetup()
|
||||
{
|
||||
// if (mcp.digitalRead(3) == LOW)
|
||||
// {
|
||||
// WiFi.persistent(false);
|
||||
// blinkDelay(100, 3);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// WiFi.begin();
|
||||
// }
|
||||
|
||||
uint8_t x_buffer[16];
|
||||
uint8_t x_position = 0;
|
||||
|
||||
while (WiFi.status() != WL_CONNECTED)
|
||||
{
|
||||
if (Serial.available() > 0)
|
||||
{
|
||||
uint8_t b = Serial.read();
|
||||
|
||||
if (parse_improv_serial_byte(x_position, b, x_buffer, onImprovCommandCallback, onImprovErrorCallback))
|
||||
{
|
||||
x_buffer[x_position++] = b;
|
||||
}
|
||||
else
|
||||
{
|
||||
x_position = 0;
|
||||
}
|
||||
}
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
void setupTime()
|
||||
{
|
||||
configTime(preferences.getInt("gmtOffset", TIME_OFFSET_SECONDS), 0, NTP_SERVER);
|
||||
struct tm timeinfo;
|
||||
|
||||
while (!getLocalTime(&timeinfo))
|
||||
{
|
||||
configTime(preferences.getInt("gmtOffset", TIME_OFFSET_SECONDS), 0, NTP_SERVER);
|
||||
delay(500);
|
||||
Serial.println(F("Retry set time"));
|
||||
}
|
||||
|
||||
Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
|
||||
}
|
||||
|
||||
void setupPreferences()
|
||||
{
|
||||
preferences.begin("btclock", false);
|
||||
}
|
||||
|
||||
void setupWebsocketClients()
|
||||
{
|
||||
setupBlockNotify();
|
||||
setupPriceNotify();
|
||||
}
|
||||
|
||||
void setupTimers()
|
||||
{
|
||||
xTaskCreate(setupTimeUpdateTimer, "setupTimeUpdateTimer", 4096, NULL, 1, NULL);
|
||||
xTaskCreate(setupScreenRotateTimer, "setupScreenRotateTimer", 4096, NULL, 1, NULL);
|
||||
}
|
||||
|
||||
void finishSetup()
|
||||
{
|
||||
pixels.clear();
|
||||
pixels.show();
|
||||
}
|
||||
|
||||
void setupHardware()
|
||||
{
|
||||
pixels.begin();
|
||||
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();
|
||||
|
||||
if (psramInit())
|
||||
{
|
||||
Serial.println(F("PSRAM is correctly initialized"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println(F("PSRAM not available"));
|
||||
}
|
||||
|
||||
|
||||
Wire.begin(35, 36, 400000);
|
||||
|
||||
if (!mcp.begin_I2C(0x20))
|
||||
{
|
||||
Serial.println(F("Error MCP23017"));
|
||||
|
||||
// while (1)
|
||||
// ;
|
||||
}
|
||||
else
|
||||
{
|
||||
pinMode(MCP_INT_PIN, INPUT_PULLUP);
|
||||
mcp.setupInterrupts(false, false, LOW);
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
mcp.pinMode(i, INPUT_PULLUP);
|
||||
mcp.setupInterruptPin(i, LOW);
|
||||
}
|
||||
for (int i = 8; i <= 14; i++)
|
||||
{
|
||||
mcp.pinMode(i, OUTPUT);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void improvGetAvailableWifiNetworks()
|
||||
{
|
||||
int networkNum = WiFi.scanNetworks();
|
||||
|
||||
for (int id = 0; id < networkNum; ++id)
|
||||
{
|
||||
std::vector<uint8_t> data = improv::build_rpc_response(
|
||||
improv::GET_WIFI_NETWORKS, {WiFi.SSID(id), String(WiFi.RSSI(id)), (WiFi.encryptionType(id) == WIFI_AUTH_OPEN ? "NO" : "YES")}, false);
|
||||
improv_send_response(data);
|
||||
}
|
||||
// final response
|
||||
std::vector<uint8_t> data =
|
||||
improv::build_rpc_response(improv::GET_WIFI_NETWORKS, std::vector<std::string>{}, false);
|
||||
improv_send_response(data);
|
||||
}
|
||||
|
||||
bool improv_connectWifi(std::string ssid, std::string password)
|
||||
{
|
||||
uint8_t count = 0;
|
||||
|
||||
WiFi.begin(ssid.c_str(), password.c_str());
|
||||
|
||||
while (WiFi.status() != WL_CONNECTED)
|
||||
{
|
||||
blinkDelay(500, 2);
|
||||
|
||||
if (count > MAX_ATTEMPTS_WIFI_CONNECTION)
|
||||
{
|
||||
WiFi.disconnect();
|
||||
return false;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void blinkDelay(int d, int times)
|
||||
{
|
||||
for (int j = 0; j < times; j++)
|
||||
{
|
||||
|
||||
pixels.setPixelColor(0, pixels.Color(255, 0, 0));
|
||||
pixels.setPixelColor(1, pixels.Color(0, 255, 0));
|
||||
pixels.setPixelColor(2, pixels.Color(255, 0, 0));
|
||||
pixels.setPixelColor(3, pixels.Color(0, 255, 0));
|
||||
pixels.show();
|
||||
vTaskDelay(pdMS_TO_TICKS(d));
|
||||
|
||||
pixels.setPixelColor(0, pixels.Color(255, 255, 0));
|
||||
pixels.setPixelColor(1, pixels.Color(0, 255, 255));
|
||||
pixels.setPixelColor(2, pixels.Color(255, 255, 0));
|
||||
pixels.setPixelColor(3, pixels.Color(0, 255, 255));
|
||||
pixels.show();
|
||||
vTaskDelay(pdMS_TO_TICKS(d));
|
||||
}
|
||||
pixels.clear();
|
||||
pixels.show();
|
||||
}
|
||||
|
||||
void onImprovErrorCallback(improv::Error err)
|
||||
{
|
||||
pixels.setPixelColor(0, pixels.Color(255, 0, 0));
|
||||
pixels.setPixelColor(1, pixels.Color(255, 0, 0));
|
||||
pixels.setPixelColor(2, pixels.Color(255, 0, 0));
|
||||
pixels.setPixelColor(3, pixels.Color(255, 0, 0));
|
||||
pixels.show();
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
pixels.clear();
|
||||
pixels.show();
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
std::vector<std::string> getLocalUrl()
|
||||
{
|
||||
return {
|
||||
// URL where user can finish onboarding or use device
|
||||
// Recommended to use website hosted by device
|
||||
String("http://" + WiFi.localIP().toString()).c_str()};
|
||||
}
|
||||
|
||||
bool onImprovCommandCallback(improv::ImprovCommand cmd)
|
||||
{
|
||||
|
||||
switch (cmd.command)
|
||||
{
|
||||
case improv::Command::GET_CURRENT_STATE:
|
||||
{
|
||||
if ((WiFi.status() == WL_CONNECTED))
|
||||
{
|
||||
improv_set_state(improv::State::STATE_PROVISIONED);
|
||||
std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_CURRENT_STATE, getLocalUrl(), false);
|
||||
improv_send_response(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
improv_set_state(improv::State::STATE_AUTHORIZED);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case improv::Command::WIFI_SETTINGS:
|
||||
{
|
||||
if (cmd.ssid.length() == 0)
|
||||
{
|
||||
improv_set_error(improv::Error::ERROR_INVALID_RPC);
|
||||
break;
|
||||
}
|
||||
|
||||
improv_set_state(improv::STATE_PROVISIONING);
|
||||
|
||||
if (improv_connectWifi(cmd.ssid, cmd.password))
|
||||
{
|
||||
|
||||
blinkDelay(100, 3);
|
||||
// std::array<String, NUM_SCREENS> epdContent = {"S", "U", "C", "C", "E", "S", "S"};
|
||||
// setEpdContent(epdContent);
|
||||
|
||||
improv_set_state(improv::STATE_PROVISIONED);
|
||||
std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, getLocalUrl(), false);
|
||||
improv_send_response(data);
|
||||
setupWebserver();
|
||||
}
|
||||
else
|
||||
{
|
||||
improv_set_state(improv::STATE_STOPPED);
|
||||
improv_set_error(improv::Error::ERROR_UNABLE_TO_CONNECT);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case improv::Command::GET_DEVICE_INFO:
|
||||
{
|
||||
std::vector<std::string> infos = {
|
||||
// Firmware name
|
||||
"BTClock",
|
||||
// Firmware version
|
||||
"1.0.0",
|
||||
// Hardware chip/variant
|
||||
"ESP32S3",
|
||||
// Device name
|
||||
"BTClock"};
|
||||
std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
|
||||
improv_send_response(data);
|
||||
break;
|
||||
}
|
||||
|
||||
case improv::Command::GET_WIFI_NETWORKS:
|
||||
{
|
||||
improvGetAvailableWifiNetworks();
|
||||
// std::array<String, NUM_SCREENS> epdContent = {"W", "E", "B", "W", "I", "F", "I"};
|
||||
// setEpdContent(epdContent);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
improv_set_error(improv::ERROR_UNKNOWN_RPC);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void improv_set_state(improv::State state)
|
||||
{
|
||||
|
||||
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
|
||||
data.resize(11);
|
||||
data[6] = improv::IMPROV_SERIAL_VERSION;
|
||||
data[7] = improv::TYPE_CURRENT_STATE;
|
||||
data[8] = 1;
|
||||
data[9] = state;
|
||||
|
||||
uint8_t checksum = 0x00;
|
||||
for (uint8_t d : data)
|
||||
checksum += d;
|
||||
data[10] = checksum;
|
||||
|
||||
Serial.write(data.data(), data.size());
|
||||
}
|
||||
|
||||
void improv_send_response(std::vector<uint8_t> &response)
|
||||
{
|
||||
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
|
||||
data.resize(9);
|
||||
data[6] = improv::IMPROV_SERIAL_VERSION;
|
||||
data[7] = improv::TYPE_RPC_RESPONSE;
|
||||
data[8] = response.size();
|
||||
data.insert(data.end(), response.begin(), response.end());
|
||||
|
||||
uint8_t checksum = 0x00;
|
||||
for (uint8_t d : data)
|
||||
checksum += d;
|
||||
data.push_back(checksum);
|
||||
|
||||
Serial.write(data.data(), data.size());
|
||||
}
|
||||
|
||||
void improv_set_error(improv::Error error)
|
||||
{
|
||||
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
|
||||
data.resize(11);
|
||||
data[6] = improv::IMPROV_SERIAL_VERSION;
|
||||
data[7] = improv::TYPE_ERROR_STATE;
|
||||
data[8] = 1;
|
||||
data[9] = error;
|
||||
|
||||
uint8_t checksum = 0x00;
|
||||
for (uint8_t d : data)
|
||||
checksum += d;
|
||||
data[10] = checksum;
|
||||
|
||||
Serial.write(data.data(), data.size());
|
||||
}
|
|
@ -2,22 +2,47 @@
|
|||
#include <WiFiClientSecure.h>
|
||||
#include <Preferences.h>
|
||||
#include <Adafruit_MCP23X17.h>
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
|
||||
#include "shared.hpp"
|
||||
#include <esp_system.h>
|
||||
#include <esp_netif.h>
|
||||
#include <esp_sntp.h>
|
||||
#include "epd.hpp"
|
||||
#include "improv.hpp"
|
||||
|
||||
#include "lib/screen_handler.hpp"
|
||||
#include "lib/webserver.hpp"
|
||||
#include "lib/block_notify.hpp"
|
||||
#include "lib/price_notify.hpp"
|
||||
#include "lib/button_handler.hpp"
|
||||
|
||||
#define NTP_SERVER "pool.ntp.org"
|
||||
#define DEFAULT_MEMPOOL_INSTANCE "mempool.space"
|
||||
#define TIME_OFFSET_SECONDS 3600
|
||||
#define USER_AGENT "BTClock/2.0"
|
||||
#define MCP_DEV_ADDR 0x20
|
||||
|
||||
#define BITCOIND_HOST ""
|
||||
#define BITCOIND_PORT 8332
|
||||
#define BITCOIND_RPC_USER ""
|
||||
#define BITCOIND_RPC_PASS ""
|
||||
|
||||
void setup();
|
||||
void setupTime();
|
||||
void setupPreferences();
|
||||
void setupWifi();
|
||||
void setupWebsocketClients();
|
||||
void setupHardware();
|
||||
void tryImprovSetup();
|
||||
void setupTimers();
|
||||
void finishSetup();
|
||||
|
||||
std::vector<std::string> getLocalUrl();
|
||||
bool improv_connectWifi(std::string ssid, std::string password);
|
||||
void improvGetAvailableWifiNetworks();
|
||||
bool onImprovCommandCallback(improv::ImprovCommand cmd);
|
||||
void onImprovErrorCallback(improv::Error err);
|
||||
void improv_set_state(improv::State state);
|
||||
void improv_send_response(std::vector<uint8_t> &response);
|
||||
void improv_set_error(improv::Error error);
|
||||
void blinkDelay(int d, int times);
|
|
@ -169,7 +169,6 @@ extern "C" void updateDisplay(void *pvParameters) noexcept
|
|||
showDigit(epdIndex, epdContent[epdIndex].c_str()[0], updatePartial, &FONT_BIG);
|
||||
}
|
||||
|
||||
#ifdef USE_UNIVERSAL_PIN
|
||||
char tries = 0;
|
||||
while (tries < 3)
|
||||
{
|
||||
|
@ -180,14 +179,9 @@ extern "C" void updateDisplay(void *pvParameters) noexcept
|
|||
break;
|
||||
}
|
||||
|
||||
delay(100);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
tries++;
|
||||
}
|
||||
#else
|
||||
displays[epdIndex].display(updatePartial);
|
||||
displays[epdIndex].hibernate();
|
||||
currentEpdContent[epdIndex] = epdContent[epdIndex];
|
||||
#endif
|
||||
}
|
||||
xSemaphoreGive(epdUpdateSemaphore[epdIndex]);
|
||||
}
|
||||
|
@ -265,3 +259,13 @@ void setFgColor(int color)
|
|||
{
|
||||
fgColor = color;
|
||||
}
|
||||
|
||||
std::array<String, NUM_SCREENS> getCurrentEpdContent()
|
||||
{
|
||||
// Serial.println("currentEpdContent");
|
||||
|
||||
// for (int i = 0; i < NUM_SCREENS; i++) {
|
||||
// Serial.printf("%d = %s", i, currentEpdContent[i]);
|
||||
// }
|
||||
return currentEpdContent;
|
||||
}
|
||||
|
|
|
@ -20,3 +20,4 @@ void setBgColor(int color);
|
|||
void setFgColor(int color);
|
||||
|
||||
void setEpdContent(std::array<String, NUM_SCREENS> newEpdContent);
|
||||
std::array<String, NUM_SCREENS> getCurrentEpdContent();
|
||||
|
|
145
src/lib/improv.cpp
Normal file
145
src/lib/improv.cpp
Normal file
|
@ -0,0 +1,145 @@
|
|||
#include "improv.h"
|
||||
|
||||
namespace improv {
|
||||
|
||||
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data, bool check_checksum) {
|
||||
return parse_improv_data(data.data(), data.size(), check_checksum);
|
||||
}
|
||||
|
||||
ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum) {
|
||||
ImprovCommand improv_command;
|
||||
Command command = (Command) data[0];
|
||||
uint8_t data_length = data[1];
|
||||
|
||||
if (data_length != length - 2 - check_checksum) {
|
||||
improv_command.command = UNKNOWN;
|
||||
return improv_command;
|
||||
}
|
||||
|
||||
if (check_checksum) {
|
||||
uint8_t checksum = data[length - 1];
|
||||
|
||||
uint32_t calculated_checksum = 0;
|
||||
for (uint8_t i = 0; i < length - 1; i++) {
|
||||
calculated_checksum += data[i];
|
||||
}
|
||||
|
||||
if ((uint8_t) calculated_checksum != checksum) {
|
||||
improv_command.command = BAD_CHECKSUM;
|
||||
return improv_command;
|
||||
}
|
||||
}
|
||||
|
||||
if (command == WIFI_SETTINGS) {
|
||||
uint8_t ssid_length = data[2];
|
||||
uint8_t ssid_start = 3;
|
||||
size_t ssid_end = ssid_start + ssid_length;
|
||||
|
||||
uint8_t pass_length = data[ssid_end];
|
||||
size_t pass_start = ssid_end + 1;
|
||||
size_t pass_end = pass_start + pass_length;
|
||||
|
||||
std::string ssid(data + ssid_start, data + ssid_end);
|
||||
std::string password(data + pass_start, data + pass_end);
|
||||
return {.command = command, .ssid = ssid, .password = password};
|
||||
}
|
||||
|
||||
improv_command.command = command;
|
||||
return improv_command;
|
||||
}
|
||||
|
||||
bool parse_improv_serial_byte(size_t position, uint8_t byte, const uint8_t *buffer,
|
||||
std::function<bool(ImprovCommand)> &&callback, std::function<void(Error)> &&on_error) {
|
||||
if (position == 0)
|
||||
return byte == 'I';
|
||||
if (position == 1)
|
||||
return byte == 'M';
|
||||
if (position == 2)
|
||||
return byte == 'P';
|
||||
if (position == 3)
|
||||
return byte == 'R';
|
||||
if (position == 4)
|
||||
return byte == 'O';
|
||||
if (position == 5)
|
||||
return byte == 'V';
|
||||
|
||||
if (position == 6)
|
||||
return byte == IMPROV_SERIAL_VERSION;
|
||||
|
||||
if (position <= 8)
|
||||
return true;
|
||||
|
||||
uint8_t type = buffer[7];
|
||||
uint8_t data_len = buffer[8];
|
||||
|
||||
if (position <= 8 + data_len)
|
||||
return true;
|
||||
|
||||
if (position == 8 + data_len + 1) {
|
||||
uint8_t checksum = 0x00;
|
||||
for (size_t i = 0; i < position; i++)
|
||||
checksum += buffer[i];
|
||||
|
||||
if (checksum != byte) {
|
||||
on_error(ERROR_INVALID_RPC);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type == TYPE_RPC) {
|
||||
auto command = parse_improv_data(&buffer[9], data_len, false);
|
||||
return callback(command);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum, bool add_checksum) {
|
||||
std::vector<uint8_t> out;
|
||||
uint32_t length = 0;
|
||||
out.push_back(command);
|
||||
for (const auto &str : datum) {
|
||||
uint8_t len = str.length();
|
||||
length += len + 1;
|
||||
out.push_back(len);
|
||||
out.insert(out.end(), str.begin(), str.end());
|
||||
}
|
||||
out.insert(out.begin() + 1, length);
|
||||
|
||||
if (add_checksum) {
|
||||
uint32_t calculated_checksum = 0;
|
||||
|
||||
for (uint8_t byte : out) {
|
||||
calculated_checksum += byte;
|
||||
}
|
||||
out.push_back(calculated_checksum);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
#ifdef ARDUINO
|
||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum, bool add_checksum) {
|
||||
std::vector<uint8_t> out;
|
||||
uint32_t length = 0;
|
||||
out.push_back(command);
|
||||
for (const auto &str : datum) {
|
||||
uint8_t len = str.length();
|
||||
length += len;
|
||||
out.push_back(len);
|
||||
out.insert(out.end(), str.begin(), str.end());
|
||||
}
|
||||
out.insert(out.begin() + 1, length);
|
||||
|
||||
if (add_checksum) {
|
||||
uint32_t calculated_checksum = 0;
|
||||
|
||||
for (uint8_t byte : out) {
|
||||
calculated_checksum += byte;
|
||||
}
|
||||
out.push_back(calculated_checksum);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
#endif // ARDUINO
|
||||
|
||||
} // namespace improv
|
76
src/lib/improv.hpp
Normal file
76
src/lib/improv.hpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <Arduino.h>
|
||||
#endif // ARDUINO
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace improv {
|
||||
|
||||
static const char *const SERVICE_UUID = "00467768-6228-2272-4663-277478268000";
|
||||
static const char *const STATUS_UUID = "00467768-6228-2272-4663-277478268001";
|
||||
static const char *const ERROR_UUID = "00467768-6228-2272-4663-277478268002";
|
||||
static const char *const RPC_COMMAND_UUID = "00467768-6228-2272-4663-277478268003";
|
||||
static const char *const RPC_RESULT_UUID = "00467768-6228-2272-4663-277478268004";
|
||||
static const char *const CAPABILITIES_UUID = "00467768-6228-2272-4663-277478268005";
|
||||
|
||||
enum Error : uint8_t {
|
||||
ERROR_NONE = 0x00,
|
||||
ERROR_INVALID_RPC = 0x01,
|
||||
ERROR_UNKNOWN_RPC = 0x02,
|
||||
ERROR_UNABLE_TO_CONNECT = 0x03,
|
||||
ERROR_NOT_AUTHORIZED = 0x04,
|
||||
ERROR_UNKNOWN = 0xFF,
|
||||
};
|
||||
|
||||
enum State : uint8_t {
|
||||
STATE_STOPPED = 0x00,
|
||||
STATE_AWAITING_AUTHORIZATION = 0x01,
|
||||
STATE_AUTHORIZED = 0x02,
|
||||
STATE_PROVISIONING = 0x03,
|
||||
STATE_PROVISIONED = 0x04,
|
||||
};
|
||||
|
||||
enum Command : uint8_t {
|
||||
UNKNOWN = 0x00,
|
||||
WIFI_SETTINGS = 0x01,
|
||||
IDENTIFY = 0x02,
|
||||
GET_CURRENT_STATE = 0x02,
|
||||
GET_DEVICE_INFO = 0x03,
|
||||
GET_WIFI_NETWORKS = 0x04,
|
||||
BAD_CHECKSUM = 0xFF,
|
||||
};
|
||||
|
||||
static const uint8_t CAPABILITY_IDENTIFY = 0x01;
|
||||
static const uint8_t IMPROV_SERIAL_VERSION = 1;
|
||||
|
||||
enum ImprovSerialType : uint8_t {
|
||||
TYPE_CURRENT_STATE = 0x01,
|
||||
TYPE_ERROR_STATE = 0x02,
|
||||
TYPE_RPC = 0x03,
|
||||
TYPE_RPC_RESPONSE = 0x04
|
||||
};
|
||||
|
||||
struct ImprovCommand {
|
||||
Command command;
|
||||
std::string ssid;
|
||||
std::string password;
|
||||
};
|
||||
|
||||
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data, bool check_checksum = true);
|
||||
ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum = true);
|
||||
|
||||
bool parse_improv_serial_byte(size_t position, uint8_t byte, const uint8_t *buffer,
|
||||
std::function<bool(ImprovCommand)> &&callback, std::function<void(Error)> &&on_error);
|
||||
|
||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum,
|
||||
bool add_checksum = true);
|
||||
#ifdef ARDUINO
|
||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum, bool add_checksum = true);
|
||||
#endif // ARDUINO
|
||||
|
||||
} // namespace improv
|
18
src/lib/led_handler.cpp
Normal file
18
src/lib/led_handler.cpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
#include "led_handler.hpp"
|
||||
|
||||
TaskHandle_t ledTaskHandle = NULL;
|
||||
const TickType_t debounceDelay = pdMS_TO_TICKS(50);
|
||||
|
||||
void ledTask(void *parameter)
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void setupLedTask()
|
||||
{
|
||||
xTaskCreate(ledTask, "LedTask", 4096, NULL, tskIDLE_PRIORITY, &ledTaskHandle); // Create the FreeRTOS task
|
||||
}
|
12
src/lib/led_handler.hpp
Normal file
12
src/lib/led_handler.hpp
Normal file
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
#include "shared.hpp"
|
||||
|
||||
extern TaskHandle_t ledTaskHandle;
|
||||
|
||||
void ledTask(void *pvParameters);
|
||||
void setupLedTask();
|
|
@ -40,19 +40,19 @@ void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, int32_t ev
|
|||
|
||||
void onWebsocketPriceMessage(esp_websocket_event_data_t* event_data)
|
||||
{
|
||||
DynamicJsonDocument doc(event_data->data_len);
|
||||
SpiRamJsonDocument doc(event_data->data_len);
|
||||
|
||||
deserializeJson(doc, (char *)event_data->data_ptr);
|
||||
|
||||
if (doc.containsKey("bitcoin")) {
|
||||
if (currentPrice != doc["bitcoin"].as<long>()) {
|
||||
Serial.printf("New price %lu\r\n", currentPrice);
|
||||
// Serial.printf("New price %lu\r\n", currentPrice);
|
||||
|
||||
const unsigned long oldPrice = currentPrice;
|
||||
currentPrice = doc["bitcoin"].as<long>();
|
||||
|
||||
// if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) {
|
||||
if (priceUpdateTaskHandle != nullptr)
|
||||
if (priceUpdateTaskHandle != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER || getCurrentScreen() == SCREEN_MSCW_TIME))
|
||||
xTaskNotifyGive(priceUpdateTaskHandle);
|
||||
//}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,18 @@
|
|||
|
||||
TaskHandle_t priceUpdateTaskHandle;
|
||||
TaskHandle_t blockUpdateTaskHandle;
|
||||
TaskHandle_t timeUpdateTaskHandle;
|
||||
TaskHandle_t taskScreenRotateTaskHandle;
|
||||
esp_timer_handle_t screenRotateTimer;
|
||||
esp_timer_handle_t minuteTimer;
|
||||
|
||||
std::array<String, NUM_SCREENS> taskEpdContent = {"", "", "", "", "", "", ""};
|
||||
std::string priceString;
|
||||
const int usPerSecond = 1000000;
|
||||
const int usPerMinute = 60 * usPerSecond;
|
||||
int64_t next_callback_time = 0;
|
||||
|
||||
uint currentScreen;
|
||||
|
||||
void taskPriceUpdate(void *pvParameters)
|
||||
{
|
||||
|
@ -14,18 +23,23 @@ void taskPriceUpdate(void *pvParameters)
|
|||
|
||||
unsigned long price = getPrice();
|
||||
uint firstIndex = 0;
|
||||
if (false) {
|
||||
if (getCurrentScreen() == SCREEN_BTC_TICKER)
|
||||
{
|
||||
priceString = ("$" + String(price)).c_str();
|
||||
|
||||
if (priceString.length() < (NUM_SCREENS)) {
|
||||
if (priceString.length() < (NUM_SCREENS))
|
||||
{
|
||||
priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' ');
|
||||
taskEpdContent[0] = "BTC/USD";
|
||||
firstIndex = 1;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
priceString = String(int(round(1 / float(price) * 10e7))).c_str();
|
||||
|
||||
if (priceString.length() < (NUM_SCREENS)) {
|
||||
if (priceString.length() < (NUM_SCREENS))
|
||||
{
|
||||
priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' ');
|
||||
taskEpdContent[0] = "MSCW/TIME";
|
||||
firstIndex = 1;
|
||||
|
@ -41,6 +55,16 @@ void taskPriceUpdate(void *pvParameters)
|
|||
}
|
||||
}
|
||||
|
||||
void taskScreenRotate(void *pvParameters)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
|
||||
setCurrentScreen((currentScreen+1) % 5);
|
||||
}
|
||||
}
|
||||
|
||||
void taskBlockUpdate(void *pvParameters)
|
||||
{
|
||||
for (;;)
|
||||
|
@ -49,7 +73,11 @@ void taskBlockUpdate(void *pvParameters)
|
|||
|
||||
std::string blockNrString = String(getBlockHeight()).c_str();
|
||||
uint firstIndex = 0;
|
||||
if (blockNrString.length() < NUM_SCREENS) {
|
||||
|
||||
if (getCurrentScreen() != SCREEN_HALVING_COUNTDOWN)
|
||||
{
|
||||
if (blockNrString.length() < NUM_SCREENS)
|
||||
{
|
||||
blockNrString.insert(blockNrString.begin(), NUM_SCREENS - blockNrString.length(), ' ');
|
||||
taskEpdContent[0] = "BLOCK/HEIGHT";
|
||||
firstIndex = 1;
|
||||
|
@ -59,14 +87,192 @@ void taskBlockUpdate(void *pvParameters)
|
|||
{
|
||||
taskEpdContent[i] = blockNrString[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint nextHalvingBlock = 210000 - (getBlockHeight() % 210000);
|
||||
const uint minutesToHalving = nextHalvingBlock * 10;
|
||||
|
||||
const int years = floor(minutesToHalving / 525600);
|
||||
const int days = floor((minutesToHalving - (years * 525600)) / (24 * 60));
|
||||
const int hours = floor((minutesToHalving - (years * 525600) - (days * (24 * 60))) / 60);
|
||||
const int mins = floor(minutesToHalving - (years * 525600) - (days * (24 * 60)) - (hours * 60));
|
||||
taskEpdContent[0] = "BIT/COIN";
|
||||
taskEpdContent[1] = "HALV/ING";
|
||||
taskEpdContent[(NUM_SCREENS - 5)] = String(years) + "/YRS";
|
||||
taskEpdContent[(NUM_SCREENS - 4)] = String(days) + "/DAYS";
|
||||
taskEpdContent[(NUM_SCREENS - 3)] = String(days) + "/HRS";
|
||||
taskEpdContent[(NUM_SCREENS - 2)] = String(mins) + "/MINS";
|
||||
taskEpdContent[(NUM_SCREENS - 1)] = "TO/GO";
|
||||
}
|
||||
|
||||
setEpdContent(taskEpdContent);
|
||||
}
|
||||
}
|
||||
|
||||
void taskTimeUpdate(void *pvParameters)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
if (getCurrentScreen() == SCREEN_TIME)
|
||||
{
|
||||
time_t currentTime;
|
||||
struct tm timeinfo;
|
||||
time(¤tTime);
|
||||
localtime_r(¤tTime, &timeinfo);
|
||||
std::string timeString;
|
||||
|
||||
String minute = String(timeinfo.tm_min);
|
||||
if (minute.length() < 2)
|
||||
{
|
||||
minute = "0" + minute;
|
||||
}
|
||||
|
||||
timeString = std::to_string(timeinfo.tm_hour) + ":" + minute.c_str();
|
||||
timeString.insert(timeString.begin(), NUM_SCREENS - timeString.length(), ' ');
|
||||
taskEpdContent[0] = String(timeinfo.tm_mday) + "/" + String(timeinfo.tm_mon + 1);
|
||||
|
||||
for (uint i = 1; i < NUM_SCREENS; i++)
|
||||
{
|
||||
taskEpdContent[i] = timeString[i];
|
||||
}
|
||||
setEpdContent(taskEpdContent);
|
||||
}
|
||||
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
const char* int64_to_iso8601(int64_t timestamp) {
|
||||
time_t seconds = timestamp / 1000000; // Convert microseconds to seconds
|
||||
struct tm timeinfo;
|
||||
gmtime_r(&seconds, &timeinfo);
|
||||
|
||||
// Define a buffer to store the formatted time string
|
||||
static char iso8601[21]; // ISO 8601 time string has the format "YYYY-MM-DDTHH:MM:SSZ"
|
||||
|
||||
// Format the time into the buffer
|
||||
strftime(iso8601, sizeof(iso8601), "%Y-%m-%dT%H:%M:%SZ", &timeinfo);
|
||||
|
||||
return iso8601;
|
||||
}
|
||||
|
||||
void IRAM_ATTR minuteTimerISR(void *arg)
|
||||
{
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
vTaskNotifyGiveFromISR(timeUpdateTaskHandle, &xHigherPriorityTaskWoken);
|
||||
if (xHigherPriorityTaskWoken == pdTRUE)
|
||||
{
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
int64_t current_time = esp_timer_get_time();
|
||||
next_callback_time = current_time + usPerMinute;
|
||||
}
|
||||
|
||||
void IRAM_ATTR screenRotateTimerISR(void *arg)
|
||||
{
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
vTaskNotifyGiveFromISR(taskScreenRotateTaskHandle, &xHigherPriorityTaskWoken);
|
||||
if (xHigherPriorityTaskWoken == pdTRUE)
|
||||
{
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
void setupTasks()
|
||||
{
|
||||
xTaskCreate(taskPriceUpdate, "updatePrice", 1024, NULL, 1, &priceUpdateTaskHandle);
|
||||
xTaskCreate(taskBlockUpdate, "updateBlock", 1024, NULL, 1, &blockUpdateTaskHandle);
|
||||
xTaskCreate(taskPriceUpdate, "updatePrice", 2048, NULL, tskIDLE_PRIORITY, &priceUpdateTaskHandle);
|
||||
xTaskCreate(taskBlockUpdate, "updateBlock", 2048, NULL, tskIDLE_PRIORITY, &blockUpdateTaskHandle);
|
||||
xTaskCreate(taskTimeUpdate, "updateTime", 4096, NULL, tskIDLE_PRIORITY, &timeUpdateTaskHandle);
|
||||
xTaskCreate(taskScreenRotate, "rotateScreen", 2048, NULL, tskIDLE_PRIORITY, &taskScreenRotateTaskHandle);
|
||||
}
|
||||
|
||||
void setupTimeUpdateTimer(void *pvParameters)
|
||||
{
|
||||
const esp_timer_create_args_t minuteTimerConfig = {
|
||||
.callback = &minuteTimerISR,
|
||||
.name = "minute_timer"};
|
||||
|
||||
esp_timer_create(&minuteTimerConfig, &minuteTimer);
|
||||
|
||||
time_t currentTime;
|
||||
struct tm timeinfo;
|
||||
time(¤tTime);
|
||||
localtime_r(¤tTime, &timeinfo);
|
||||
uint32_t secondsUntilNextMinute = 60 - timeinfo.tm_sec;
|
||||
|
||||
if (secondsUntilNextMinute > 0)
|
||||
vTaskDelay(pdMS_TO_TICKS((secondsUntilNextMinute * 1000)));
|
||||
|
||||
esp_timer_start_periodic(minuteTimer, usPerMinute);
|
||||
xTaskNotifyGive(timeUpdateTaskHandle);
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void setupScreenRotateTimer(void *pvParameters)
|
||||
{
|
||||
const esp_timer_create_args_t screenRotateTimerConfig = {
|
||||
.callback = &screenRotateTimerISR,
|
||||
.name = "screen_rotate_timer"};
|
||||
|
||||
esp_timer_create(&screenRotateTimerConfig, &screenRotateTimer);
|
||||
|
||||
esp_timer_start_periodic(screenRotateTimer, getTimerSeconds() * usPerSecond);
|
||||
|
||||
Serial.println("Set up Screen Rotate Timer");
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
uint getTimerSeconds()
|
||||
{
|
||||
return preferences.getUInt("timerSeconds", 1800);
|
||||
}
|
||||
|
||||
bool isTimerActive()
|
||||
{
|
||||
return esp_timer_is_active(screenRotateTimer);
|
||||
}
|
||||
|
||||
void setTimerActive(bool status)
|
||||
{
|
||||
if (status)
|
||||
{
|
||||
esp_timer_start_periodic(screenRotateTimer, getTimerSeconds() * usPerSecond);
|
||||
}
|
||||
else
|
||||
{
|
||||
esp_timer_stop(screenRotateTimer);
|
||||
}
|
||||
}
|
||||
|
||||
uint getCurrentScreen()
|
||||
{
|
||||
return currentScreen;
|
||||
}
|
||||
|
||||
void setCurrentScreen(uint newScreen)
|
||||
{
|
||||
if (newScreen != SCREEN_CUSTOM)
|
||||
{
|
||||
preferences.putUInt("currentScreen", newScreen);
|
||||
}
|
||||
|
||||
currentScreen = newScreen;
|
||||
|
||||
switch (currentScreen)
|
||||
{
|
||||
case SCREEN_TIME:
|
||||
xTaskNotifyGive(timeUpdateTaskHandle);
|
||||
break;
|
||||
case SCREEN_HALVING_COUNTDOWN:
|
||||
case SCREEN_BLOCK_HEIGHT:
|
||||
xTaskNotifyGive(blockUpdateTaskHandle);
|
||||
break;
|
||||
case SCREEN_MSCW_TIME:
|
||||
case SCREEN_BTC_TICKER:
|
||||
xTaskNotifyGive(priceUpdateTaskHandle);
|
||||
break;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <esp_timer.h>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
|
@ -9,8 +11,27 @@
|
|||
|
||||
extern TaskHandle_t priceUpdateTaskHandle;
|
||||
extern TaskHandle_t blockUpdateTaskHandle;
|
||||
extern TaskHandle_t timeUpdateTaskHandle;
|
||||
extern TaskHandle_t taskScreenRotateTaskHandle;
|
||||
|
||||
uint getCurrentScreen();
|
||||
void setCurrentScreen(uint newScreen);
|
||||
|
||||
void setupTimeUpdateTimer(void *pvParameters);
|
||||
void setupScreenRotateTimer(void *pvParameters);
|
||||
|
||||
void IRAM_ATTR minuteTimerISR(void* arg);
|
||||
void IRAM_ATTR screenRotateTimerISR(void* arg);
|
||||
|
||||
void taskPriceUpdate(void *pvParameters);
|
||||
void taskBlockUpdate(void *pvParameters);
|
||||
void taskTimeUpdate(void *pvParameters);
|
||||
void taskScreenRotate(void *pvParameters);
|
||||
|
||||
uint getTimerSeconds();
|
||||
bool isTimerActive();
|
||||
void setTimerActive(bool status);
|
||||
|
||||
|
||||
void setupTasks();
|
||||
const char* int64_to_iso8601(int64_t timestamp);
|
|
@ -1,7 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include <Adafruit_MCP23X17.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <Preferences.h>
|
||||
|
||||
extern Adafruit_MCP23X17 mcp;
|
||||
extern Preferences preferences;
|
||||
|
||||
const PROGMEM int SCREEN_BLOCK_HEIGHT = 0;
|
||||
const PROGMEM int SCREEN_MSCW_TIME = 1;
|
||||
const PROGMEM int SCREEN_BTC_TICKER = 2;
|
||||
const PROGMEM int SCREEN_TIME = 3;
|
||||
const PROGMEM int SCREEN_HALVING_COUNTDOWN = 4;
|
||||
const PROGMEM int SCREEN_COUNTDOWN = 98;
|
||||
const PROGMEM int SCREEN_CUSTOM = 99;
|
||||
const PROGMEM int screens[5] = { SCREEN_BLOCK_HEIGHT, SCREEN_MSCW_TIME, SCREEN_BTC_TICKER, SCREEN_TIME, SCREEN_HALVING_COUNTDOWN };
|
||||
|
||||
struct SpiRamAllocator {
|
||||
void* allocate(size_t size) {
|
||||
return heap_caps_malloc(size, MALLOC_CAP_SPIRAM);
|
||||
}
|
||||
|
||||
void deallocate(void* pointer) {
|
||||
heap_caps_free(pointer);
|
||||
}
|
||||
|
||||
void* reallocate(void* ptr, size_t new_size) {
|
||||
return heap_caps_realloc(ptr, new_size, MALLOC_CAP_SPIRAM);
|
||||
}
|
||||
};
|
||||
|
||||
using SpiRamJsonDocument = BasicJsonDocument<SpiRamAllocator>;
|
||||
|
|
|
@ -4,14 +4,54 @@ AsyncWebServer server(80);
|
|||
|
||||
void setupWebserver()
|
||||
{
|
||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
request->send(200, "text/plain", "Hello, world");
|
||||
});
|
||||
if (!LittleFS.begin(true))
|
||||
{
|
||||
Serial.println(F("An Error has occurred while mounting LittleFS"));
|
||||
return;
|
||||
}
|
||||
|
||||
server.on("/status", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
server.serveStatic("/css", LittleFS, "/css/");
|
||||
server.serveStatic("/js", LittleFS, "/js/");
|
||||
server.serveStatic("/font", LittleFS, "/font/");
|
||||
|
||||
server.on("/", HTTP_GET, onIndex);
|
||||
|
||||
server.on("/api/status", HTTP_GET, onApiStatus);
|
||||
server.on("/api/system_status", HTTP_GET, onApiSystemStatus);
|
||||
|
||||
server.on("/api/action/pause", HTTP_GET, onApiActionPause);
|
||||
server.on("/api/action/timer_restart", HTTP_GET, onApiActionTimerRestart);
|
||||
|
||||
server.on("/api/settings", HTTP_GET, onApiSettingsGet);
|
||||
server.on("/api/settings", HTTP_POST, onApiSettingsPost);
|
||||
|
||||
server.on("/api/show/screen", HTTP_GET, onApiShowScreen);
|
||||
server.on("/api/show/text", HTTP_GET, onApiShowText);
|
||||
|
||||
server.on("/api/restart", HTTP_GET, onApiRestart);
|
||||
|
||||
server.addRewrite(new OneParamRewrite("/api/show/screen/{s}", "/api/show/screen?s={s}"));
|
||||
server.addRewrite(new OneParamRewrite("/api/show/text/{text}", "/api/show/text?t={text}"));
|
||||
server.addRewrite(new OneParamRewrite("/api/show/number/{number}", "/api/show/text?t={text}"));
|
||||
|
||||
server.onNotFound(onNotFound);
|
||||
|
||||
server.begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Api
|
||||
* @Path("/api/status")
|
||||
*/
|
||||
void onApiStatus(AsyncWebServerRequest *request)
|
||||
{
|
||||
AsyncResponseStream *response = request->beginResponseStream("application/json");
|
||||
StaticJsonDocument<128> root;
|
||||
StaticJsonDocument<512> root;
|
||||
|
||||
root["currentScreen"] = getCurrentScreen();
|
||||
root["numScreens"] = NUM_SCREENS;
|
||||
root["timerRunning"] = isTimerActive();;
|
||||
root["espUptime"] = esp_timer_get_time() / 1000000;
|
||||
root["currentPrice"] = getPrice();
|
||||
root["currentBlockHeight"] = getBlockHeight();
|
||||
root["espFreeHeap"] = ESP.getFreeHeap();
|
||||
|
@ -19,17 +59,310 @@ void setupWebserver()
|
|||
root["espFreePsram"] = ESP.getFreePsram();
|
||||
root["espPsramSize"] = ESP.getPsramSize();
|
||||
|
||||
JsonArray data = root.createNestedArray("data");
|
||||
JsonArray rendered = root.createNestedArray("rendered");
|
||||
String epdContent[NUM_SCREENS];
|
||||
|
||||
std::array<String, NUM_SCREENS> retEpdContent = getCurrentEpdContent();
|
||||
|
||||
std::copy(std::begin(retEpdContent), std::end(retEpdContent), epdContent);
|
||||
|
||||
copyArray(epdContent, data);
|
||||
copyArray(epdContent, rendered);
|
||||
serializeJson(root, *response);
|
||||
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Api
|
||||
* @Path("/api/action/pause")
|
||||
*/
|
||||
void onApiActionPause(AsyncWebServerRequest *request)
|
||||
{
|
||||
setTimerActive(false);
|
||||
Serial.println(F("Update timer paused"));
|
||||
|
||||
request->send(200);
|
||||
};
|
||||
|
||||
/**
|
||||
* @Api
|
||||
* @Path("/api/action/timer_restart")
|
||||
*/
|
||||
void onApiActionTimerRestart(AsyncWebServerRequest *request)
|
||||
{
|
||||
// moment = millis();
|
||||
setTimerActive(true);
|
||||
Serial.println(F("Update timer restarted"));
|
||||
|
||||
request->send(200);
|
||||
}
|
||||
|
||||
|
||||
void onApiShowScreen(AsyncWebServerRequest *request)
|
||||
{
|
||||
if (request->hasParam("s"))
|
||||
{
|
||||
AsyncWebParameter *p = request->getParam("s");
|
||||
uint currentScreen = p->value().toInt();
|
||||
setCurrentScreen(currentScreen);
|
||||
}
|
||||
request->send(200);
|
||||
}
|
||||
|
||||
void onApiShowText(AsyncWebServerRequest *request)
|
||||
{
|
||||
if (request->hasParam("t"))
|
||||
{
|
||||
AsyncWebParameter *p = request->getParam("t");
|
||||
String t = p->value();
|
||||
t.toUpperCase(); // This is needed as long as lowercase letters are glitchy
|
||||
|
||||
std::array<String, NUM_SCREENS> textEpdContent;
|
||||
for (uint i = 0; i < NUM_SCREENS; i++) {
|
||||
textEpdContent[i] = t[i];
|
||||
}
|
||||
|
||||
setEpdContent(textEpdContent);
|
||||
}
|
||||
//setCurrentScreen(SCREEN_CUSTOM);
|
||||
request->send(200);
|
||||
}
|
||||
|
||||
void onApiRestart(AsyncWebServerRequest *request)
|
||||
{
|
||||
request->send(200);
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Api
|
||||
* @Method GET
|
||||
* @Path("/api/settings")
|
||||
*/
|
||||
void onApiSettingsGet(AsyncWebServerRequest *request)
|
||||
{
|
||||
StaticJsonDocument<768> root;
|
||||
root["numScreens"] = NUM_SCREENS;
|
||||
root["fgColor"] = getFgColor();
|
||||
root["bgColor"] = getBgColor();
|
||||
root["timerSeconds"] = getTimerSeconds();
|
||||
root["timerRunning"] = isTimerActive();;
|
||||
root["fullRefreshMin"] = preferences.getUInt("fullRefreshMin", 30);
|
||||
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["epdColors"] = 2;
|
||||
root["ledFlashOnUpdate"] = preferences.getBool("ledFlashOnUpd", false);
|
||||
root["ledBrightness"] = preferences.getUInt("ledBrightness", 128);
|
||||
|
||||
#ifdef GIT_REV
|
||||
root["gitRev"] = String(GIT_REV);
|
||||
#endif
|
||||
#ifdef LAST_BUILD_TIME
|
||||
root["lastBuildTime"] = String(LAST_BUILD_TIME);
|
||||
#endif
|
||||
JsonArray screens = root.createNestedArray("screens");
|
||||
|
||||
// for (int i = 0; i < screenNameMap.size(); i++)
|
||||
// {
|
||||
// JsonObject o = screens.createNestedObject();
|
||||
// String key = "screen" + String(i) + "Visible";
|
||||
// o["id"] = i;
|
||||
// o["name"] = screenNameMap[i];
|
||||
// o["enabled"] = preferences.getBool(key.c_str(), true);
|
||||
// }
|
||||
|
||||
AsyncResponseStream *response = request->beginResponseStream("application/json");
|
||||
serializeJson(root, *response);
|
||||
|
||||
request->send(response);
|
||||
});
|
||||
|
||||
server.onNotFound(onNotFound);
|
||||
|
||||
server.begin();
|
||||
}
|
||||
|
||||
bool processEpdColorSettings(AsyncWebServerRequest *request)
|
||||
{
|
||||
bool settingsChanged = false;
|
||||
if (request->hasParam("fgColor", true))
|
||||
{
|
||||
AsyncWebParameter *fgColor = request->getParam("fgColor", true);
|
||||
preferences.putUInt("fgColor", strtol(fgColor->value().c_str(), NULL, 16));
|
||||
setFgColor(int(strtol(fgColor->value().c_str(), NULL, 16)));
|
||||
Serial.print(F("Setting foreground color to "));
|
||||
Serial.println(fgColor->value().c_str());
|
||||
settingsChanged = true;
|
||||
}
|
||||
if (request->hasParam("bgColor", true))
|
||||
{
|
||||
AsyncWebParameter *bgColor = request->getParam("bgColor", true);
|
||||
|
||||
preferences.putUInt("bgColor", strtol(bgColor->value().c_str(), NULL, 16));
|
||||
setBgColor(int(strtol(bgColor->value().c_str(), NULL, 16)));
|
||||
Serial.print(F("Setting background color to "));
|
||||
Serial.println(bgColor->value().c_str());
|
||||
settingsChanged = true;
|
||||
}
|
||||
|
||||
return settingsChanged;
|
||||
}
|
||||
|
||||
void onApiSettingsPost(AsyncWebServerRequest *request)
|
||||
{
|
||||
int params = request->params();
|
||||
bool settingsChanged = false;
|
||||
|
||||
settingsChanged = processEpdColorSettings(request);
|
||||
|
||||
if (request->hasParam("ledFlashOnUpd", true))
|
||||
{
|
||||
AsyncWebParameter *ledFlashOnUpdate = request->getParam("ledFlashOnUpd", true);
|
||||
|
||||
preferences.putBool("ledFlashOnUpd", ledFlashOnUpdate->value().toInt());
|
||||
Serial.print("Setting led flash on update to ");
|
||||
Serial.println(ledFlashOnUpdate->value().c_str());
|
||||
settingsChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
preferences.putBool("ledFlashOnUpd", 0);
|
||||
Serial.print("Setting led flash on update to false");
|
||||
settingsChanged = true;
|
||||
}
|
||||
|
||||
if (request->hasParam("mempoolInstance", true))
|
||||
{
|
||||
AsyncWebParameter *mempoolInstance = request->getParam("mempoolInstance", true);
|
||||
|
||||
preferences.putString("mempoolInstance", mempoolInstance->value().c_str());
|
||||
Serial.print("Setting mempool instance to ");
|
||||
Serial.println(mempoolInstance->value().c_str());
|
||||
settingsChanged = true;
|
||||
}
|
||||
|
||||
if (request->hasParam("ledBrightness", true))
|
||||
{
|
||||
AsyncWebParameter *ledBrightness = request->getParam("ledBrightness", true);
|
||||
|
||||
preferences.putUInt("ledBrightness", ledBrightness->value().toInt());
|
||||
Serial.print("Setting brightness to ");
|
||||
Serial.println(ledBrightness->value().c_str());
|
||||
settingsChanged = true;
|
||||
}
|
||||
|
||||
if (request->hasParam("fullRefreshMin", true))
|
||||
{
|
||||
AsyncWebParameter *fullRefreshMin = request->getParam("fullRefreshMin", true);
|
||||
|
||||
preferences.putUInt("fullRefreshMin", fullRefreshMin->value().toInt());
|
||||
Serial.print("Set full refresh minutes to ");
|
||||
Serial.println(fullRefreshMin->value().c_str());
|
||||
settingsChanged = true;
|
||||
}
|
||||
|
||||
if (request->hasParam("wpTimeout", true))
|
||||
{
|
||||
AsyncWebParameter *wpTimeout = request->getParam("wpTimeout", true);
|
||||
|
||||
preferences.putUInt("wpTimeout", wpTimeout->value().toInt());
|
||||
Serial.print("Set WiFi portal timeout seconds to ");
|
||||
Serial.println(wpTimeout->value().c_str());
|
||||
settingsChanged = true;
|
||||
}
|
||||
|
||||
// for (int i = 0; i < screenNameMap.size(); i++)
|
||||
// {
|
||||
// String key = "screen[" + String(i) + "]";
|
||||
// String prefKey = "screen" + String(i) + "Visible";
|
||||
// bool visible = false;
|
||||
// if (request->hasParam(key, true))
|
||||
// {
|
||||
// AsyncWebParameter *screenParam = request->getParam(key, true);
|
||||
// visible = screenParam->value().toInt();
|
||||
// }
|
||||
// Serial.print("Setting screen " + String(i) + " to ");
|
||||
// Serial.println(visible);
|
||||
|
||||
// preferences.putBool(prefKey.c_str(), visible);
|
||||
// }
|
||||
|
||||
if (request->hasParam("tzOffset", true))
|
||||
{
|
||||
AsyncWebParameter *p = request->getParam("tzOffset", true);
|
||||
int tzOffsetSeconds = p->value().toInt() * 60;
|
||||
preferences.putInt("gmtOffset", tzOffsetSeconds);
|
||||
Serial.print("Setting tz offset to ");
|
||||
Serial.println(tzOffsetSeconds);
|
||||
settingsChanged = true;
|
||||
}
|
||||
|
||||
if (request->hasParam("timePerScreen", true))
|
||||
{
|
||||
AsyncWebParameter *p = request->getParam("timePerScreen", true);
|
||||
uint timerSeconds = p->value().toInt() * 60;
|
||||
preferences.putUInt("timerSeconds", timerSeconds);
|
||||
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)
|
||||
{
|
||||
//flashTemporaryLights(0, 255, 0);
|
||||
|
||||
Serial.println(F("Settings changed"));
|
||||
}
|
||||
}
|
||||
|
||||
void onApiSystemStatus(AsyncWebServerRequest *request)
|
||||
{
|
||||
AsyncResponseStream *response = request->beginResponseStream("application/json");
|
||||
|
||||
StaticJsonDocument<128> root;
|
||||
|
||||
root["espFreeHeap"] = ESP.getFreeHeap();
|
||||
root["espHeapSize"] = ESP.getHeapSize();
|
||||
root["espFreePsram"] = ESP.getFreePsram();
|
||||
root["espPsramSize"] = ESP.getPsramSize();
|
||||
|
||||
serializeJson(root, *response);
|
||||
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void onIndex(AsyncWebServerRequest *request) { request->send(LittleFS, "/index.html", String(), false); }
|
||||
|
||||
void onNotFound(AsyncWebServerRequest *request)
|
||||
{
|
||||
if (request->method() == HTTP_OPTIONS)
|
||||
|
|
|
@ -2,9 +2,30 @@
|
|||
|
||||
#include "ESPAsyncWebServer.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include <LittleFS.h>
|
||||
|
||||
#include "lib/block_notify.hpp"
|
||||
#include "lib/price_notify.hpp"
|
||||
#include "lib/screen_handler.hpp"
|
||||
|
||||
|
||||
#include "webserver/OneParamRewrite.hpp"
|
||||
|
||||
void setupWebserver();
|
||||
bool processEpdColorSettings(AsyncWebServerRequest *request);
|
||||
|
||||
void onApiStatus(AsyncWebServerRequest *request);
|
||||
void onApiSystemStatus(AsyncWebServerRequest *request);
|
||||
|
||||
void onApiShowScreen(AsyncWebServerRequest *request);
|
||||
void onApiShowText(AsyncWebServerRequest *request);
|
||||
|
||||
void onApiActionPause(AsyncWebServerRequest *request);
|
||||
void onApiActionTimerRestart(AsyncWebServerRequest *request);
|
||||
void onApiSettingsGet(AsyncWebServerRequest *request);
|
||||
void onApiSettingsPost(AsyncWebServerRequest *request);
|
||||
|
||||
void onApiRestart(AsyncWebServerRequest *request);
|
||||
|
||||
void onIndex(AsyncWebServerRequest *request);
|
||||
void onNotFound(AsyncWebServerRequest *request);
|
43
src/lib/webserver/OneParamRewrite.cpp
Normal file
43
src/lib/webserver/OneParamRewrite.cpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
#include "OneParamRewrite.hpp"
|
||||
|
||||
OneParamRewrite::OneParamRewrite(const char *from, const char *to)
|
||||
: AsyncWebRewrite(from, to)
|
||||
{
|
||||
|
||||
_paramIndex = _from.indexOf('{');
|
||||
|
||||
if (_paramIndex >= 0 && _from.endsWith("}"))
|
||||
{
|
||||
_urlPrefix = _from.substring(0, _paramIndex);
|
||||
int index = _params.indexOf('{');
|
||||
if (index >= 0)
|
||||
{
|
||||
_params = _params.substring(0, index);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_urlPrefix = _from;
|
||||
}
|
||||
_paramsBackup = _params;
|
||||
}
|
||||
|
||||
bool OneParamRewrite::match(AsyncWebServerRequest *request)
|
||||
{
|
||||
if (request->url().startsWith(_urlPrefix))
|
||||
{
|
||||
if (_paramIndex >= 0)
|
||||
{
|
||||
_params = _paramsBackup + request->url().substring(_paramIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
_params = _paramsBackup;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
15
src/lib/webserver/OneParamRewrite.hpp
Normal file
15
src/lib/webserver/OneParamRewrite.hpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include "ESPAsyncWebServer.h"
|
||||
|
||||
class OneParamRewrite : public AsyncWebRewrite
|
||||
{
|
||||
protected:
|
||||
String _urlPrefix;
|
||||
int _paramIndex;
|
||||
String _paramsBackup;
|
||||
|
||||
public:
|
||||
OneParamRewrite(const char *from, const char *to);
|
||||
bool match(AsyncWebServerRequest *request) override;
|
||||
};
|
|
@ -1,20 +1,15 @@
|
|||
#include "Arduino.h"
|
||||
#include "lib/config.hpp"
|
||||
|
||||
|
||||
extern "C" void app_main()
|
||||
{
|
||||
initArduino();
|
||||
|
||||
setup();
|
||||
Serial.begin(115200);
|
||||
static char sBuffer[240];
|
||||
setup();
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Serial.println("-------");
|
||||
// vTaskGetRunTimeStats((char *)sBuffer);
|
||||
// Serial.println(sBuffer);
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue