Implement multi-currency support with MsgPack

This commit is contained in:
Djuri Baars 2024-09-05 14:00:15 +02:00
parent a4ff5a2f75
commit c276d32807
18 changed files with 528 additions and 541 deletions

2
data

@ -1 +1 @@
Subproject commit 34b09a2d1134d48d7733a3d11a9e6f3f15d080a9 Subproject commit 2fffb3ef0284b4262ca97a81eb979259186604e5

View file

@ -32,25 +32,41 @@ std::string getCurrencyCode(char input)
switch (input) switch (input)
{ {
case CURRENCY_EUR: case CURRENCY_EUR:
return "EUR"; return CURRENCY_CODE_EUR;
break; break;
case CURRENCY_GBP: case CURRENCY_GBP:
return "GBP"; return CURRENCY_CODE_GBP;
break; break;
case CURRENCY_JPY: case CURRENCY_JPY:
return "YEN"; return CURRENCY_CODE_JPY;
break; break;
case CURRENCY_AUD: case CURRENCY_AUD:
return "AUD"; return CURRENCY_CODE_AUD;
break; break;
case CURRENCY_CAD: case CURRENCY_CAD:
return "CAD"; return CURRENCY_CODE_CAD;
break; break;
default: default:
return "USD"; return CURRENCY_CODE_USD;
} }
} }
char getCurrencyChar(const std::string& input)
{
if (input == "EUR")
return CURRENCY_EUR;
else if (input == "GBP")
return CURRENCY_GBP;
else if (input == "JPY")
return CURRENCY_JPY;
else if (input == "AUD")
return CURRENCY_AUD;
else if (input == "CAD")
return CURRENCY_CAD;
else
return CURRENCY_USD; // Assuming USD is the default for unknown inputs
}
std::array<std::string, NUM_SCREENS> parsePriceData(std::uint32_t price, char currencySymbol, bool useSuffixFormat) std::array<std::string, NUM_SCREENS> parsePriceData(std::uint32_t price, char currencySymbol, bool useSuffixFormat)
{ {
std::array<std::string, NUM_SCREENS> ret; std::array<std::string, NUM_SCREENS> ret;
@ -205,14 +221,8 @@ std::array<std::string, NUM_SCREENS> parseMarketCap(std::uint32_t blockHeight, s
std::uint32_t firstIndex = 0; std::uint32_t firstIndex = 0;
double supply = getSupplyAtBlock(blockHeight); double supply = getSupplyAtBlock(blockHeight);
int64_t marketCap = static_cast<std::int64_t>(supply * double(price)); int64_t marketCap = static_cast<std::int64_t>(supply * double(price));
if (currencySymbol == '[')
{ ret[0] = getCurrencyCode(currencySymbol) + "/MCAP";
ret[0] = "EUR/MCAP";
}
else
{
ret[0] = "USD/MCAP";
}
if (bigChars) if (bigChars)
{ {

View file

@ -12,6 +12,13 @@ const char CURRENCY_JPY = '^';
const char CURRENCY_AUD = '_'; const char CURRENCY_AUD = '_';
const char CURRENCY_CAD = '`'; const char CURRENCY_CAD = '`';
const std::string CURRENCY_CODE_USD = "USD";
const std::string CURRENCY_CODE_EUR = "EUR";
const std::string CURRENCY_CODE_GBP = "GBP";
const std::string CURRENCY_CODE_JPY = "JPY";
const std::string CURRENCY_CODE_AUD = "AUD";
const std::string CURRENCY_CODE_CAD = "CAD";
std::array<std::string, NUM_SCREENS> parsePriceData(std::uint32_t price, char currency, bool useSuffixFormat = false); std::array<std::string, NUM_SCREENS> parsePriceData(std::uint32_t price, char currency, bool useSuffixFormat = false);
std::array<std::string, NUM_SCREENS> parseSatsPerCurrency(std::uint32_t price, char currencySymbol, bool withSatsSymbol); std::array<std::string, NUM_SCREENS> parseSatsPerCurrency(std::uint32_t price, char currencySymbol, bool withSatsSymbol);
std::array<std::string, NUM_SCREENS> parseBlockHeight(std::uint32_t blockHeight); std::array<std::string, NUM_SCREENS> parseBlockHeight(std::uint32_t blockHeight);
@ -20,4 +27,5 @@ std::array<std::string, NUM_SCREENS> parseMarketCap(std::uint32_t blockHeight, s
std::array<std::string, NUM_SCREENS> parseBlockFees(std::uint16_t blockFees); std::array<std::string, NUM_SCREENS> parseBlockFees(std::uint16_t blockFees);
char getCurrencySymbol(char input); char getCurrencySymbol(char input);
std::string getCurrencyCode(char input); std::string getCurrencyCode(char input);
char getCurrencyChar(const std::string& input);

View file

@ -66,7 +66,6 @@ void setup()
setupWebserver(); setupWebserver();
// setupWifi();
syncTime(); syncTime();
finishSetup(); finishSetup();
@ -268,24 +267,43 @@ void setupPreferences()
setFgColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR)); setFgColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR));
setBgColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR)); setBgColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR));
setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT)); setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT));
setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE)); setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE), CURRENCY_USD);
if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE))
setCurrentCurrency(preferences.getUChar("lastCurrency", CURRENCY_USD));
else
setCurrentCurrency(CURRENCY_USD);
addScreenMapping(SCREEN_BLOCK_HEIGHT, "Block Height"); addScreenMapping(SCREEN_BLOCK_HEIGHT, "Block Height");
addScreenMapping(SCREEN_MSCW_TIME, "Sats per dollar");
addScreenMapping(SCREEN_BTC_TICKER, "Ticker");
addScreenMapping(SCREEN_TIME, "Time"); addScreenMapping(SCREEN_TIME, "Time");
addScreenMapping(SCREEN_HALVING_COUNTDOWN, "Halving countdown"); addScreenMapping(SCREEN_HALVING_COUNTDOWN, "Halving countdown");
addScreenMapping(SCREEN_MARKET_CAP, "Market Cap");
addScreenMapping(SCREEN_BLOCK_FEE_RATE, "Block Fee Rate"); addScreenMapping(SCREEN_BLOCK_FEE_RATE, "Block Fee Rate");
addScreenMapping(SCREEN_SATS_PER_CURRENCY, "Sats per dollar");
addScreenMapping(SCREEN_BTC_TICKER, "Ticker");
addScreenMapping(SCREEN_MARKET_CAP, "Market Cap");
// addScreenMapping(SCREEN_SATS_PER_CURRENCY_USD, "Sats per USD");
// addScreenMapping(SCREEN_BTC_TICKER_USD, "Ticker USD");
// addScreenMapping(SCREEN_MARKET_CAP_USD, "Market Cap USD");
// addScreenMapping(SCREEN_SATS_PER_CURRENCY_EUR, "Sats per EUR");
// addScreenMapping(SCREEN_BTC_TICKER_EUR, "Ticker EUR");
// addScreenMapping(SCREEN_MARKET_CAP_EUR, "Market Cap EUR");
// screenNameMap[SCREEN_BLOCK_HEIGHT] = "Block Height"; // screenNameMap[SCREEN_BLOCK_HEIGHT] = "Block Height";
// screenNameMap[SCREEN_BLOCK_FEE_RATE] = "Block Fee Rate"; // screenNameMap[SCREEN_BLOCK_FEE_RATE] = "Block Fee Rate";
// screenNameMap[SCREEN_MSCW_TIME] = "Sats per dollar"; // screenNameMap[SCREEN_SATS_PER_CURRENCY] = "Sats per dollar";
// screenNameMap[SCREEN_BTC_TICKER] = "Ticker"; // screenNameMap[SCREEN_BTC_TICKER] = "Ticker";
// screenNameMap[SCREEN_TIME] = "Time"; // screenNameMap[SCREEN_TIME] = "Time";
// screenNameMap[SCREEN_HALVING_COUNTDOWN] = "Halving countdown"; // screenNameMap[SCREEN_HALVING_COUNTDOWN] = "Halving countdown";
// screenNameMap[SCREEN_MARKET_CAP] = "Market Cap"; // screenNameMap[SCREEN_MARKET_CAP] = "Market Cap";
//addCurrencyMappings(getActiveCurrencies());
if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED))
{ {
addScreenMapping(SCREEN_BITAXE_HASHRATE, "BitAxe Hashrate"); addScreenMapping(SCREEN_BITAXE_HASHRATE, "BitAxe Hashrate");
@ -293,16 +311,74 @@ void setupPreferences()
} }
} }
// void addCurrencyMappings(const std::vector<std::string>& currencies)
// {
// for (const auto& currency : currencies)
// {
// int satsPerCurrencyScreen;
// int btcTickerScreen;
// int marketCapScreen;
// // Determine the corresponding screen IDs based on the currency code
// if (currency == "USD")
// {
// satsPerCurrencyScreen = SCREEN_SATS_PER_CURRENCY_USD;
// btcTickerScreen = SCREEN_BTC_TICKER_USD;
// marketCapScreen = SCREEN_MARKET_CAP_USD;
// }
// else if (currency == "EUR")
// {
// satsPerCurrencyScreen = SCREEN_SATS_PER_CURRENCY_EUR;
// btcTickerScreen = SCREEN_BTC_TICKER_EUR;
// marketCapScreen = SCREEN_MARKET_CAP_EUR;
// }
// else if (currency == "GBP")
// {
// satsPerCurrencyScreen = SCREEN_SATS_PER_CURRENCY_GBP;
// btcTickerScreen = SCREEN_BTC_TICKER_GBP;
// marketCapScreen = SCREEN_MARKET_CAP_GBP;
// }
// else if (currency == "JPY")
// {
// satsPerCurrencyScreen = SCREEN_SATS_PER_CURRENCY_JPY;
// btcTickerScreen = SCREEN_BTC_TICKER_JPY;
// marketCapScreen = SCREEN_MARKET_CAP_JPY;
// }
// else if (currency == "AUD")
// {
// satsPerCurrencyScreen = SCREEN_SATS_PER_CURRENCY_AUD;
// btcTickerScreen = SCREEN_BTC_TICKER_AUD;
// marketCapScreen = SCREEN_MARKET_CAP_AUD;
// }
// else if (currency == "CAD")
// {
// satsPerCurrencyScreen = SCREEN_SATS_PER_CURRENCY_CAD;
// btcTickerScreen = SCREEN_BTC_TICKER_CAD;
// marketCapScreen = SCREEN_MARKET_CAP_CAD;
// }
// else
// {
// continue; // Unknown currency, skip it
// }
// // Create the string locally to ensure it persists
// std::string satsPerCurrencyString = "Sats per " + currency;
// std::string btcTickerString = "Ticker " + currency;
// std::string marketCapString = "Market Cap " + currency;
// // Pass the c_str() to the function
// addScreenMapping(satsPerCurrencyScreen, satsPerCurrencyString.c_str());
// addScreenMapping(btcTickerScreen, btcTickerString.c_str());
// addScreenMapping(marketCapScreen, marketCapString.c_str());
// }
// }
void setupWebsocketClients(void *pvParameters) void setupWebsocketClients(void *pvParameters)
{ {
setupBlockNotify(); if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE)) {
setupV2Notify();
if (preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE)) } else {
{ setupBlockNotify();
setupPriceFetchTask();
}
else
{
setupPriceNotify(); setupPriceNotify();
} }
@ -453,218 +529,6 @@ void setupHardware()
#endif #endif
} }
#ifdef IMPROV_ENABLED
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 onImprovErrorCallback(improv::Error err)
{
blinkDelayColor(100, 1, 255, 0, 0);
// 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);
queueLedEffect(LED_EFFECT_WIFI_CONNECTING);
if (improv_connectWifi(cmd.ssid, cmd.password))
{
queueLedEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS);
// std::array<String, NUM_SCREENS> epdContent = {"S", "U", "C", "C",
// "E", "S", "S"}; setEpdContent(epdContent);
preferences.putBool("wifiConfigured", true);
improv_set_state(improv::STATE_PROVISIONED);
std::vector<uint8_t> data = improv::build_rpc_response(
improv::WIFI_SETTINGS, getLocalUrl(), false);
improv_send_response(data);
delay(2500);
ESP.restart();
setupWebserver();
}
else
{
queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR);
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());
}
#endif
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
{ {
static bool first_connect = true; static bool first_connect = true;
@ -842,3 +706,37 @@ int findScreenIndexByValue(int value)
} }
return -1; // Return -1 if value is not found return -1; // Return -1 if value is not found
} }
std::vector<std::string> getAvailableCurrencies()
{
return {CURRENCY_CODE_USD, CURRENCY_CODE_EUR, CURRENCY_CODE_GBP, CURRENCY_CODE_JPY, CURRENCY_CODE_AUD, CURRENCY_CODE_CAD};
}
std::vector<std::string> getActiveCurrencies()
{
std::vector<std::string> result;
// Convert Arduino String to std::string
std::string stdString = preferences.getString("actCurrencies", DEFAULT_ACTIVE_CURRENCIES).c_str();
// Use a stringstream to split the string
std::stringstream ss(stdString);
std::string item;
// Split the string by comma and add each part to the vector
while (std::getline(ss, item, ','))
{
result.push_back(item);
}
return result;
}
bool isActiveCurrency(std::string &currency)
{
std::vector<std::string> ac = getActiveCurrencies();
if (std::find(ac.begin(), ac.end(), currency) != ac.end())
{
return true;
}
return false;
}

View file

@ -19,6 +19,8 @@
#include "lib/nostr_notify.hpp" #include "lib/nostr_notify.hpp"
#include "lib/bitaxe_fetch.hpp" #include "lib/bitaxe_fetch.hpp"
#include "lib/v2_notify.hpp"
#include "lib/price_notify.hpp" #include "lib/price_notify.hpp"
#include "lib/screen_handler.hpp" #include "lib/screen_handler.hpp"
#include "lib/shared.hpp" #include "lib/shared.hpp"
@ -64,6 +66,11 @@ std::vector<std::string> getLocalUrl();
// void improv_set_state(improv::State state); // void improv_set_state(improv::State state);
// void improv_send_response(std::vector<uint8_t> &response); // void improv_send_response(std::vector<uint8_t> &response);
// void improv_set_error(improv::Error error); // void improv_set_error(improv::Error error);
//void addCurrencyMappings(const std::vector<std::string>& currencies);
std::vector<std::string> getActiveCurrencies();
std::vector<std::string> getAvailableCurrencies();
bool isActiveCurrency(std::string &currency);
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info); void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
String getHwRev(); String getHwRev();
@ -71,4 +78,7 @@ bool isWhiteVersion();
String getFsRev(); String getFsRev();
void addScreenMapping(int value, const char* name); void addScreenMapping(int value, const char* name);
// void addScreenMapping(int value, const String& name);
// void addScreenMapping(int value, const std::string& name);
int findScreenIndexByValue(int value); int findScreenIndexByValue(int value);

View file

@ -58,4 +58,6 @@
#define DEFAULT_HTTP_AUTH_ENABLED false #define DEFAULT_HTTP_AUTH_ENABLED false
#define DEFAULT_HTTP_AUTH_USERNAME "btclock" #define DEFAULT_HTTP_AUTH_USERNAME "btclock"
#define DEFAULT_HTTP_AUTH_PASSWORD "satoshi" #define DEFAULT_HTTP_AUTH_PASSWORD "satoshi"
#define DEFAULT_ACTIVE_CURRENCIES "USD,EUR,JPY"

View file

@ -168,7 +168,7 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even
{ {
if (typeValue.equals("priceUsd")) if (typeValue.equals("priceUsd"))
{ {
processNewPrice(obj["content"].as<uint>()); processNewPrice(obj["content"].as<uint>(), CURRENCY_USD);
} }
else if (typeValue.equals("blockHeight")) else if (typeValue.equals("blockHeight"))
{ {

View file

@ -29,9 +29,9 @@ void taskPriceFetch(void *pvParameters) {
// usdPrice = doc["bitcoin"]["usd"]; // usdPrice = doc["bitcoin"]["usd"];
eurPrice = doc["bitcoin"]["eur"].as<uint>(); eurPrice = doc["bitcoin"]["eur"].as<uint>();
setPrice(eurPrice); setPrice(eurPrice, CURRENCY_EUR);
if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER || if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER ||
getCurrentScreen() == SCREEN_MSCW_TIME || getCurrentScreen() == SCREEN_SATS_PER_CURRENCY ||
getCurrentScreen() == SCREEN_MARKET_CAP)) { getCurrentScreen() == SCREEN_MARKET_CAP)) {
WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0}; WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0};
xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); xQueueSend(workQueue, &priceUpdate, portMAX_DELAY);

View file

@ -5,36 +5,6 @@ const char *wsOwnServerV2 = "wss://ws-staging.btclock.dev/api/v2/ws";
const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin"; const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin";
// const char* coinCapWsCert = R"(-----BEGIN CERTIFICATE-----
// MIIFMjCCBNmgAwIBAgIQBtgXvFyc28MsvQ1HjCnXJTAKBggqhkjOPQQDAjBKMQsw
// CQYDVQQGEwJVUzEZMBcGA1UEChMQQ2xvdWRmbGFyZSwgSW5jLjEgMB4GA1UEAxMX
// Q2xvdWRmbGFyZSBJbmMgRUNDIENBLTMwHhcNMjMwNTEwMDAwMDAwWhcNMjQwNTA5
// MjM1OTU5WjB1MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQG
// A1UEBxMNU2FuIEZyYW5jaXNjbzEZMBcGA1UEChMQQ2xvdWRmbGFyZSwgSW5jLjEe
// MBwGA1UEAxMVc25pLmNsb3VkZmxhcmVzc2wuY29tMFkwEwYHKoZIzj0CAQYIKoZI
// zj0DAQcDQgAEpvFIXzQKHuqTo+IE6c6sB4p0PMXK1KsseEGf2UN/CNRhG5hO7lr8
// JtXrPZkawWBysZxOsEoetkPrDHMugCLfXKOCA3QwggNwMB8GA1UdIwQYMBaAFKXO
// N+rrsHUOlGeItEX62SQQh5YfMB0GA1UdDgQWBBShsZDJohaR1a5E0Qj7yblZjKDC
// gDA6BgNVHREEMzAxggwqLmNvaW5jYXAuaW+CCmNvaW5jYXAuaW+CFXNuaS5jbG91
// ZGZsYXJlc3NsLmNvbTAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUH
// AwEGCCsGAQUFBwMCMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9jcmwzLmRpZ2lj
// ZXJ0LmNvbS9DbG91ZGZsYXJlSW5jRUNDQ0EtMy5jcmwwN6A1oDOGMWh0dHA6Ly9j
// cmw0LmRpZ2ljZXJ0LmNvbS9DbG91ZGZsYXJlSW5jRUNDQ0EtMy5jcmwwPgYDVR0g
// BDcwNTAzBgZngQwBAgIwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2Vy
// dC5jb20vQ1BTMHYGCCsGAQUFBwEBBGowaDAkBggrBgEFBQcwAYYYaHR0cDovL29j
// c3AuZGlnaWNlcnQuY29tMEAGCCsGAQUFBzAChjRodHRwOi8vY2FjZXJ0cy5kaWdp
// Y2VydC5jb20vQ2xvdWRmbGFyZUluY0VDQ0NBLTMuY3J0MAwGA1UdEwEB/wQCMAAw
// ggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoBaAB1AO7N0GTV2xrOxVy3nbTNE6Iyh0Z8
// vOzew1FIWUZxH7WbAAABiAPnoRAAAAQDAEYwRAIgAP2W09OozuhmKeKKMsaVBcae
// o+nPHF1WUWk0i387YYYCIDIM1Wll7/4O3GNx2/Fx9bC6pi69Uya4pLxsCfW3fZMe
// AHYASLDja9qmRzQP5WoC+p0w6xxSActW3SyB2bu/qznYhHMAAAGIA+eg+QAABAMA
// RzBFAiEAuNpSqrbx47gYBgBMz5M6q0CnV/WMJqWQOxYFKrwfwVACIH3nCs4bKToT
// e+MiBrqSDaekixk4kPFEQESO9qHCkWY5AHcA2ra/az+1tiKfm8K7XGvocJFxbLtR
// hIU0vaQ9MEjX+6sAAAGIA+eg1gAABAMASDBGAiEAolCFl2IfbOHUPAOxoi4BLclS
// v9FVXb7LwIvTuCfyrEQCIQDcvehwhV9XGopKGl17F2LYYKI7hvlO3RmpPZQJt1da
// MDAKBggqhkjOPQQDAgNHADBEAiAXRWZ/JVMsfpSFFTHQHUSqRnQ/7cCOWx+9svIy
// mYnFZQIgHMEG0Cm7O4cn5KUzKOsTwwK+2U15s/jPUQi2n2IDTEM=
// -----END CERTIFICATE-----)";
// WebsocketsClient client; // WebsocketsClient client;
esp_websocket_client_handle_t clientPrice = NULL; esp_websocket_client_handle_t clientPrice = NULL;
@ -42,6 +12,8 @@ esp_websocket_client_config_t config;
uint currentPrice = 50000; uint currentPrice = 50000;
unsigned long int lastPriceUpdate; unsigned long int lastPriceUpdate;
bool priceNotifyInit = false; bool priceNotifyInit = false;
std::map<char, std::uint64_t> currencyMap;
std::map<char, unsigned long int> lastUpdateMap;
void setupPriceNotify() void setupPriceNotify()
{ {
@ -100,48 +72,59 @@ void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data)
{ {
if (currentPrice != doc["bitcoin"].as<long>()) if (currentPrice != doc["bitcoin"].as<long>())
{ {
processNewPrice(doc["bitcoin"].as<long>()); processNewPrice(doc["bitcoin"].as<long>(), CURRENCY_USD);
} }
} }
} }
void processNewPrice(uint newPrice) void processNewPrice(uint newPrice, char currency)
{ {
uint minSecPriceUpd = preferences.getUInt( uint minSecPriceUpd = preferences.getUInt(
"minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE); "minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE);
uint currentTime = esp_timer_get_time() / 1000000; uint currentTime = esp_timer_get_time() / 1000000;
if (lastPriceUpdate == 0 || if (lastUpdateMap.find(currency) == lastUpdateMap.end()||
(currentTime - lastPriceUpdate) > minSecPriceUpd) (currentTime - lastUpdateMap[currency]) > minSecPriceUpd)
{ {
// const unsigned long oldPrice = currentPrice; // const unsigned long oldPrice = currentPrice;
currentPrice = newPrice; currencyMap[currency] = newPrice;
if (lastPriceUpdate == 0 || // if (lastUpdateMap[currency] == 0 ||
(currentTime - lastPriceUpdate) > 120) // (currentTime - lastUpdateMap[currency]) > 120)
{ // {
preferences.putUInt("lastPrice", currentPrice); // preferences.putUInt("lastPrice", currentPrice);
} // }
lastPriceUpdate = currentTime; lastUpdateMap[currency] = currentTime;
// if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) { // if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) {
if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER || if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER ||
getCurrentScreen() == SCREEN_MSCW_TIME || getCurrentScreen() == SCREEN_SATS_PER_CURRENCY ||
getCurrentScreen() == SCREEN_MARKET_CAP)) getCurrentScreen() == SCREEN_MARKET_CAP))
{ {
WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0}; WorkItem priceUpdate = {TASK_PRICE_UPDATE, currency};
xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); xQueueSend(workQueue, &priceUpdate, portMAX_DELAY);
} }
//} //}
} }
} }
uint getLastPriceUpdate() uint getLastPriceUpdate(char currency)
{ {
return lastPriceUpdate; if (lastUpdateMap.find(currency) == lastUpdateMap.end()) {
return 0;
}
return lastUpdateMap[currency];
} }
uint getPrice() { return currentPrice; } uint getPrice(char currency) {
if (currencyMap.find(currency) == currencyMap.end()) {
return 0;
}
return currencyMap[currency];
}
void setPrice(uint newPrice) { currentPrice = newPrice; } void setPrice(uint newPrice, char currency) {
currencyMap[currency] = newPrice;
}
bool isPriceNotifyConnected() bool isPriceNotifyConnected()
{ {

View file

@ -14,14 +14,15 @@ void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data); int32_t event_id, void *event_data);
void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data); void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data);
uint getPrice(); uint getPrice(char currency);
void setPrice(uint newPrice); void setPrice(uint newPrice, char currency);
void processNewPrice(uint newPrice); //void processNewPrice(uint newPrice);
void processNewPrice(uint newPrice, char currency);
bool isPriceNotifyConnected(); bool isPriceNotifyConnected();
void stopPriceNotify(); void stopPriceNotify();
void restartPriceNotify(); void restartPriceNotify();
bool getPriceNotifyInit(); bool getPriceNotifyInit();
uint getLastPriceUpdate(); uint getLastPriceUpdate(char currency);

View file

@ -16,6 +16,7 @@ std::string priceString;
QueueHandle_t workQueue = NULL; QueueHandle_t workQueue = NULL;
uint currentScreen; uint currentScreen;
uint currentCurrency = CURRENCY_USD;
void workerTask(void *pvParameters) { void workerTask(void *pvParameters) {
WorkItem receivedItem; WorkItem receivedItem;
@ -40,18 +41,19 @@ void workerTask(void *pvParameters) {
} }
break; break;
case TASK_PRICE_UPDATE: { case TASK_PRICE_UPDATE: {
uint price = getPrice(); uint currency = getCurrentCurrency();
u_char priceSymbol = '$'; uint price = getPrice(currency);
if (preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE)) { // u_char priceSymbol = '$';
priceSymbol = '['; // if (preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE)) {
} // priceSymbol = '[';
// }
if (getCurrentScreen() == SCREEN_BTC_TICKER) { if (getCurrentScreen() == SCREEN_BTC_TICKER) {
taskEpdContent = parsePriceData(price, priceSymbol, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE)); taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE));
} else if (getCurrentScreen() == SCREEN_MSCW_TIME) { } else if (getCurrentScreen() == SCREEN_SATS_PER_CURRENCY) {
taskEpdContent = parseSatsPerCurrency(price, priceSymbol, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)); taskEpdContent = parseSatsPerCurrency(price, currency, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL));
} else { } else {
taskEpdContent = taskEpdContent =
parseMarketCap(getBlockHeight(), price, priceSymbol, parseMarketCap(getBlockHeight(), price, currency,
preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR)); preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR));
} }
@ -152,7 +154,7 @@ void setupTasks() {
xTaskCreate(workerTask, "workerTask", 4096, NULL, tskIDLE_PRIORITY, xTaskCreate(workerTask, "workerTask", 4096, NULL, tskIDLE_PRIORITY,
&workerTaskHandle); &workerTaskHandle);
xTaskCreate(taskScreenRotate, "rotateScreen", 2048, NULL, tskIDLE_PRIORITY, xTaskCreate(taskScreenRotate, "rotateScreen", 4096, NULL, tskIDLE_PRIORITY,
&taskScreenRotateTaskHandle); &taskScreenRotateTaskHandle);
waitUntilNoneBusy(); waitUntilNoneBusy();
@ -242,7 +244,7 @@ void setCurrentScreen(uint newScreen) {
break; break;
} }
case SCREEN_MARKET_CAP: case SCREEN_MARKET_CAP:
case SCREEN_MSCW_TIME: case SCREEN_SATS_PER_CURRENCY:
case SCREEN_BTC_TICKER: { case SCREEN_BTC_TICKER: {
WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0}; WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0};
xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); xQueueSend(workQueue, &priceUpdate, portMAX_DELAY);
@ -270,10 +272,36 @@ void setCurrentScreen(uint newScreen) {
if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle); if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle);
} }
bool isCurrencySpecific(uint screen) {
switch (screen) {
case SCREEN_BTC_TICKER:
case SCREEN_SATS_PER_CURRENCY:
case SCREEN_MARKET_CAP:
return true;
default:
return false;
}
}
void nextScreen() { void nextScreen() {
int currentIndex = findScreenIndexByValue(getCurrentScreen()); int currentIndex = findScreenIndexByValue(getCurrentScreen());
std::vector<ScreenMapping> screenMappings = getScreenNameMap(); std::vector<ScreenMapping> screenMappings = getScreenNameMap();
if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE) && isCurrencySpecific(getCurrentScreen())) {
std::vector<std::string> ac = getActiveCurrencies();
std::string curCode = getCurrencyCode(getCurrentCurrency());
if (getCurrencyCode(getCurrentCurrency()) != ac.back()) {
auto it = std::find(ac.begin(), ac.end(), curCode);
if (it != ac.end()) {
size_t index = std::distance(ac.begin(), it);
setCurrentCurrency(getCurrencyChar(ac.at(index+1)));
setCurrentScreen(getCurrentScreen());
return;
}
}
setCurrentCurrency(getCurrencyChar(ac.front()));
}
int newCurrentScreen; int newCurrentScreen;
if (currentIndex < screenMappings.size() - 1) { if (currentIndex < screenMappings.size() - 1) {
@ -302,6 +330,23 @@ void previousScreen() {
int currentIndex = findScreenIndexByValue(getCurrentScreen()); int currentIndex = findScreenIndexByValue(getCurrentScreen());
std::vector<ScreenMapping> screenMappings = getScreenNameMap(); std::vector<ScreenMapping> screenMappings = getScreenNameMap();
if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE) && isCurrencySpecific(getCurrentScreen())) {
std::vector<std::string> ac = getActiveCurrencies();
std::string curCode = getCurrencyCode(getCurrentCurrency());
if (getCurrencyCode(getCurrentCurrency()) != ac.front()) {
auto it = std::find(ac.begin(), ac.end(), curCode);
if (it != ac.end()) {
size_t index = std::distance(ac.begin(), it);
setCurrentCurrency(getCurrencyChar(ac.at(index-1)));
setCurrentScreen(getCurrentScreen());
return;
}
}
setCurrentCurrency(getCurrencyChar(ac.back()));
}
int newCurrentScreen; int newCurrentScreen;
if (currentIndex > 0) { if (currentIndex > 0) {
@ -352,4 +397,13 @@ void showSystemStatusScreen() {
(int)round(ESP.getHeapSize() / 1024); (int)round(ESP.getHeapSize() / 1024);
setCurrentScreen(SCREEN_CUSTOM); setCurrentScreen(SCREEN_CUSTOM);
setEpdContent(sysStatusEpdContent); setEpdContent(sysStatusEpdContent);
}
void setCurrentCurrency(char currency) {
currentCurrency = currency;
preferences.putUChar("lastCurrency", currency);
}
uint getCurrentCurrency() {
return currentCurrency;
} }

View file

@ -60,3 +60,6 @@ void setTimerActive(bool status);
void toggleTimerActive(); void toggleTimerActive();
void setupTasks(); void setupTasks();
void setCurrentCurrency(char currency);
uint getCurrentCurrency();

View file

@ -29,12 +29,29 @@ extern std::mutex mcpMutex;
#endif #endif
const PROGMEM int SCREEN_BLOCK_HEIGHT = 0; 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_TIME = 3;
const PROGMEM int SCREEN_HALVING_COUNTDOWN = 4; const PROGMEM int SCREEN_HALVING_COUNTDOWN = 4;
const PROGMEM int SCREEN_MARKET_CAP = 5;
const PROGMEM int SCREEN_BLOCK_FEE_RATE = 6; const PROGMEM int SCREEN_BLOCK_FEE_RATE = 6;
const PROGMEM int SCREEN_SATS_PER_CURRENCY = 10;
const PROGMEM int SCREEN_BTC_TICKER = 20;
// const PROGMEM int SCREEN_BTC_TICKER_USD = 20;
// const PROGMEM int SCREEN_BTC_TICKER_EUR = 21;
// const PROGMEM int SCREEN_BTC_TICKER_GBP = 22;
// const PROGMEM int SCREEN_BTC_TICKER_JPY = 23;
// const PROGMEM int SCREEN_BTC_TICKER_AUD = 24;
// const PROGMEM int SCREEN_BTC_TICKER_CAD = 25;
const PROGMEM int SCREEN_MARKET_CAP = 30;
// const PROGMEM int SCREEN_MARKET_CAP_USD = 30;
// const PROGMEM int SCREEN_MARKET_CAP_EUR = 31;
// const PROGMEM int SCREEN_MARKET_CAP_GBP = 32;
// const PROGMEM int SCREEN_MARKET_CAP_JPY = 33;
// const PROGMEM int SCREEN_MARKET_CAP_AUD = 34;
// const PROGMEM int SCREEN_MARKET_CAP_CAD = 35;
const PROGMEM int SCREEN_BITAXE_HASHRATE = 80; const PROGMEM int SCREEN_BITAXE_HASHRATE = 80;
const PROGMEM int SCREEN_BITAXE_BESTDIFF = 81; const PROGMEM int SCREEN_BITAXE_BESTDIFF = 81;
@ -42,7 +59,7 @@ const PROGMEM int SCREEN_COUNTDOWN = 98;
const PROGMEM int SCREEN_CUSTOM = 99; const PROGMEM int SCREEN_CUSTOM = 99;
const int SCREEN_COUNT = 7; const int SCREEN_COUNT = 7;
const PROGMEM int screens[SCREEN_COUNT] = { const PROGMEM int screens[SCREEN_COUNT] = {
SCREEN_BLOCK_HEIGHT, SCREEN_MSCW_TIME, SCREEN_BTC_TICKER, SCREEN_BLOCK_HEIGHT, SCREEN_SATS_PER_CURRENCY, SCREEN_BTC_TICKER,
SCREEN_TIME, SCREEN_HALVING_COUNTDOWN, SCREEN_MARKET_CAP, SCREEN_TIME, SCREEN_HALVING_COUNTDOWN, SCREEN_MARKET_CAP,
SCREEN_BLOCK_FEE_RATE}; SCREEN_BLOCK_FEE_RATE};
const int usPerSecond = 1000000; const int usPerSecond = 1000000;

142
src/lib/v2_notify.cpp Normal file
View file

@ -0,0 +1,142 @@
#include "v2_notify.hpp"
WebSocketsClient webSocket;
TaskHandle_t v2NotifyTaskHandle;
void setupV2Notify()
{
String hostname = "ws.btclock.dev";
if ( preferences.getBool("stagingSource", DEFAULT_STAGING_SOURCE)) {
Serial.println(F("Connecting to V2 staging source"));
hostname = "ws-staging.btclock.dev";
}
webSocket.beginSSL(hostname, 443, "/api/v2/ws");
webSocket.onEvent(onWebsocketV2Event);
webSocket.setReconnectInterval(5000);
webSocket.enableHeartbeat(15000, 3000, 2);
setupV2NotifyTask();
}
void onWebsocketV2Event(WStype_t type, uint8_t * payload, size_t length) {
switch(type) {
case WStype_DISCONNECTED:
Serial.printf("[WSc] Disconnected!\n");
break;
case WStype_CONNECTED:
{
Serial.printf("[WSc] Connected to url: %s\n", payload);
JsonDocument response;
response["type"] = "subscribe";
response["eventType"] = "blockfee";
size_t responseLength = measureMsgPack(response);
uint8_t* buffer = new uint8_t[responseLength];
serializeMsgPack(response, buffer, responseLength);
webSocket.sendBIN(buffer, responseLength);
delete[] buffer;
buffer = new uint8_t[responseLength];
response["type"] = "subscribe";
response["eventType"] = "blockheight";
responseLength = measureMsgPack(response);
buffer = new uint8_t[responseLength];
serializeMsgPack(response, buffer, responseLength);
webSocket.sendBIN(buffer, responseLength);
delete[] buffer;
buffer = new uint8_t[responseLength];
response["type"] = "subscribe";
response["eventType"] = "price";
JsonArray currenciesArray = response["currencies"].to<JsonArray>();
for (const auto &str : getActiveCurrencies())
{
currenciesArray.add(str);
}
// response["currencies"] = currenciesArray;
responseLength = measureMsgPack(response);
buffer = new uint8_t[responseLength];
serializeMsgPack(response, buffer, responseLength);
webSocket.sendBIN(buffer, responseLength);
break;
}
case WStype_TEXT:
Serial.printf("[WSc] get text: %s\n", payload);
// send message to server
// webSocket.sendTXT("message here");
break;
case WStype_BIN:
{
JsonDocument doc;
DeserializationError error = deserializeMsgPack(doc, payload, length);
handleV2Message(doc);
break;
}
case WStype_ERROR:
case WStype_FRAGMENT_TEXT_START:
case WStype_FRAGMENT_BIN_START:
case WStype_FRAGMENT:
case WStype_PING:
case WStype_PONG:
case WStype_FRAGMENT_FIN:
break;
}
}
void handleV2Message(JsonDocument doc) {
if (doc.containsKey("blockheight"))
{
uint newBlockHeight = doc["blockheight"].as<uint>();
if (newBlockHeight == getBlockHeight()) {
return;
}
processNewBlock(newBlockHeight);
}
else if (doc.containsKey("blockfee"))
{
uint medianFee = doc["blockfee"].as<uint>();
processNewBlockFee(medianFee);
} else if (doc.containsKey("price"))
{
// Iterate through the key-value pairs of the "price" object
for (JsonPair kv : doc["price"].as<JsonObject>()) {
const char* currency = kv.key().c_str();
uint newPrice = kv.value().as<uint>();
processNewPrice(newPrice, getCurrencyChar(currency));
}
}
}
void taskV2Notify(void *pvParameters) {
for(;;) {
webSocket.loop();
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
void setupV2NotifyTask() {
xTaskCreate(taskV2Notify, "v2Notify", (6 * 1024), NULL, tskIDLE_PRIORITY,
&v2NotifyTaskHandle);
}
bool isV2NotifyConnected()
{
return webSocket.isConnected();
}

24
src/lib/v2_notify.hpp Normal file
View file

@ -0,0 +1,24 @@
#pragma once
#include <Arduino.h>
#include <ArduinoJson.h>
#include <esp_websocket_client.h>
#include "block_notify.hpp"
#include <string>
#include "lib/screen_handler.hpp"
extern TaskHandle_t v2NotifyTaskHandle;
void setupV2NotifyTask();
void taskV2Notify(void *pvParameters);
void setupV2Notify();
void onWebsocketV2Event(WStype_t type, uint8_t * payload, size_t length);
void handleV2Message(JsonDocument doc);
bool isV2NotifyConnected();
// void stopV2Notify();
// void restartV2Notify();
// bool getPriceNotifyInit();
// uint getLastPriceUpdate();

View file

@ -41,9 +41,10 @@ void setupWebserver()
server.on("/api/action/timer_restart", HTTP_GET, onApiActionTimerRestart); server.on("/api/action/timer_restart", HTTP_GET, onApiActionTimerRestart);
server.on("/api/settings", HTTP_GET, onApiSettingsGet); 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/screen", HTTP_GET, onApiShowScreen);
server.on("/api/show/currency", HTTP_GET, onApiShowCurrency);
server.on("/api/show/text", HTTP_GET, onApiShowText); server.on("/api/show/text", HTTP_GET, onApiShowText);
server.on("/api/screen/next", HTTP_GET, onApiScreenNext); server.on("/api/screen/next", HTTP_GET, onApiScreenNext);
@ -88,6 +89,8 @@ void setupWebserver()
} }
server.on("/api/restart", HTTP_GET, onApiRestart); server.on("/api/restart", HTTP_GET, onApiRestart);
server.addRewrite(
new OneParamRewrite("/api/show/currency/{c}", "/api/show/currency?c={c}"));
server.addRewrite(new OneParamRewrite("/api/lights/color/{color}", server.addRewrite(new OneParamRewrite("/api/lights/color/{color}",
"/api/lights/color?c={color}")); "/api/lights/color?c={color}"));
server.addRewrite( server.addRewrite(
@ -241,10 +244,12 @@ JsonDocument getStatusObject()
JsonObject conStatus = root["connectionStatus"].to<JsonObject>(); JsonObject conStatus = root["connectionStatus"].to<JsonObject>();
conStatus["price"] = isPriceNotifyConnected(); conStatus["price"] = isPriceNotifyConnected();
conStatus["blocks"] = isBlockNotifyConnected(); conStatus["blocks"] = isBlockNotifyConnected();
conStatus["V2"] = isV2NotifyConnected();
conStatus["nostr"] = nostrConnected(); conStatus["nostr"] = nostrConnected();
root["rssi"] = WiFi.RSSI(); root["rssi"] = WiFi.RSSI();
root["currency"] = getCurrencyCode(getCurrentCurrency());
#ifdef HAS_FRONTLIGHT #ifdef HAS_FRONTLIGHT
std::vector<uint16_t> statuses = frontlightGetStatus(); std::vector<uint16_t> statuses = frontlightGetStatus();
uint16_t arr[NUM_SCREENS]; uint16_t arr[NUM_SCREENS];
@ -548,6 +553,23 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json)
} }
} }
if (settings.containsKey("actCurrencies"))
{
String actCurrencies;
for (JsonVariant cur : settings["actCurrencies"].as<JsonArray>())
{
if (!actCurrencies.isEmpty())
{
actCurrencies += ",";
}
actCurrencies += cur.as<String>();
}
preferences.putString("actCurrencies", actCurrencies.c_str());
Serial.printf("Set actCurrencies: %s\n", actCurrencies);
}
if (settings.containsKey("txPower")) if (settings.containsKey("txPower"))
{ {
int txPower = settings["txPower"].as<int>(); int txPower = settings["txPower"].as<int>();
@ -696,6 +718,18 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
#endif #endif
JsonArray screens = root["screens"].to<JsonArray>(); JsonArray screens = root["screens"].to<JsonArray>();
JsonArray actCurrencies = root["actCurrencies"].to<JsonArray>();
for (const auto &str : getActiveCurrencies())
{
actCurrencies.add(str);
}
JsonArray availableCurrencies = root["availableCurrencies"].to<JsonArray>();
for (const auto &str : getAvailableCurrencies())
{
availableCurrencies.add(str);
}
std::vector<ScreenMapping> screenNameMap = getScreenNameMap(); std::vector<ScreenMapping> screenNameMap = getScreenNameMap();
for (int i = 0; i < screenNameMap.size(); i++) for (int i = 0; i < screenNameMap.size(); i++)
@ -703,7 +737,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
JsonObject o = screens.add<JsonObject>(); JsonObject o = screens.add<JsonObject>();
String key = "screen" + String(screenNameMap.at(i).value) + "Visible"; String key = "screen" + String(screenNameMap.at(i).value) + "Visible";
o["id"] = screenNameMap.at(i).value; o["id"] = screenNameMap.at(i).value;
o["name"] = screenNameMap.at(i).name; o["name"] = String(screenNameMap.at(i).name);
o["enabled"] = preferences.getBool(key.c_str(), true); o["enabled"] = preferences.getBool(key.c_str(), true);
} }
@ -740,230 +774,6 @@ bool processEpdColorSettings(AsyncWebServerRequest *request)
return settingsChanged; return settingsChanged;
} }
void onApiSettingsPost(AsyncWebServerRequest *request)
{
// bool settingsChanged = false;
// settingsChanged = processEpdColorSettings(request);
// int headers = request->headers();
// int i;
// for (i = 0; i < headers; i++)
// {
// AsyncWebHeader *h = request->getHeader(i);
// Serial.printf("HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str());
// }
// int params = request->params();
// for (int i = 0; i < params; i++)
// {
// const AsyncWebParameter *p = request->getParam(i);
// if (p->isFile())
// { // p->isPost() is also true
// Serial.printf("FILE[%s]: %s, size: %u\n", p->name().c_str(),
// p->value().c_str(), p->size());
// }
// else if (p->isPost())
// {
// Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
// }
// else
// {
// Serial.printf("GET[%s]: %s\n", p->name().c_str(), p->value().c_str());
// }
// }
// if (request->hasParam("fetchEurPrice", true))
// {
// const AsyncWebParameter *fetchEurPrice = request->getParam("fetchEurPrice", true);
// preferences.putBool("fetchEurPrice", fetchEurPrice->value().toInt());
// settingsChanged = true;
// }
// else
// {
// preferences.putBool("fetchEurPrice", 0);
// settingsChanged = true;
// }
// if (request->hasParam("ledTestOnPower", true))
// {
// const 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))
// {
// const AsyncWebParameter *ledFlashOnUpdate =
// request->getParam("ledFlashOnUpd", true);
// preferences.putBool("ledFlashOnUpd", ledFlashOnUpdate->value().toInt());
// settingsChanged = true;
// }
// else
// {
// preferences.putBool("ledFlashOnUpd", 0);
// settingsChanged = true;
// }
// if (request->hasParam("mdnsEnabled", true))
// {
// const 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))
// {
// const AsyncWebParameter *otaEnabled = request->getParam("otaEnabled", true);
// preferences.putBool("otaEnabled", otaEnabled->value().toInt());
// settingsChanged = true;
// }
// else
// {
// preferences.putBool("otaEnabled", 0);
// settingsChanged = true;
// }
// if (request->hasParam("stealFocus", false))
// {
// const AsyncWebParameter *stealFocusOnBlock =
// request->getParam("stealFocus", false);
// preferences.putBool("stealFocus", stealFocusOnBlock->value().toInt());
// settingsChanged = true;
// }
// else
// {
// preferences.putBool("stealFocus", 0);
// settingsChanged = true;
// }
// if (request->hasParam("mcapBigChar", true))
// {
// const AsyncWebParameter *mcapBigChar = request->getParam("mcapBigChar", true);
// preferences.putBool("mcapBigChar", mcapBigChar->value().toInt());
// settingsChanged = true;
// }
// else
// {
// preferences.putBool("mcapBigChar", 0);
// settingsChanged = true;
// }
// if (request->hasParam("mempoolInstance", true))
// {
// const AsyncWebParameter *mempoolInstance =
// request->getParam("mempoolInstance", true);
// preferences.putString("mempoolInstance", mempoolInstance->value().c_str());
// settingsChanged = true;
// }
// if (request->hasParam("hostnamePrefix", true))
// {
// const AsyncWebParameter *hostnamePrefix =
// request->getParam("hostnamePrefix", true);
// preferences.putString("hostnamePrefix", hostnamePrefix->value().c_str());
// settingsChanged = true;
// }
// if (request->hasParam("ledBrightness", true))
// {
// const AsyncWebParameter *ledBrightness = request->getParam("ledBrightness", true);
// preferences.putUInt("ledBrightness", ledBrightness->value().toInt());
// settingsChanged = true;
// }
// if (request->hasParam("fullRefreshMin", true))
// {
// const AsyncWebParameter *fullRefreshMin =
// request->getParam("fullRefreshMin", true);
// preferences.putUInt("fullRefreshMin", fullRefreshMin->value().toInt());
// settingsChanged = true;
// }
// if (request->hasParam("wpTimeout", true))
// {
// const AsyncWebParameter *wpTimeout = request->getParam("wpTimeout", true);
// preferences.putUInt("wpTimeout", wpTimeout->value().toInt());
// settingsChanged = true;
// }
// std::vector<std::string> screenNameMap = getScreenNameMap();
// if (request->hasParam("screens"))
// {
// const AsyncWebParameter *screenParam = request->getParam("screens", true);
// Serial.printf(screenParam->value().c_str());
// }
// 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))
// {
// const AsyncWebParameter *screenParam = request->getParam(key, true);
// visible = screenParam->value().toInt();
// }
// preferences.putBool(prefKey.c_str(), visible);
// }
// if (request->hasParam("tzOffset", true))
// {
// const AsyncWebParameter *p = request->getParam("tzOffset", true);
// int tzOffsetSeconds = p->value().toInt() * 60;
// preferences.putInt("gmtOffset", tzOffsetSeconds);
// settingsChanged = true;
// }
// if (request->hasParam("minSecPriceUpd", true))
// {
// const AsyncWebParameter *p = request->getParam("minSecPriceUpd", true);
// int minSecPriceUpd = p->value().toInt();
// preferences.putUInt("minSecPriceUpd", minSecPriceUpd);
// settingsChanged = true;
// }
// if (request->hasParam("timePerScreen", true))
// {
// const AsyncWebParameter *p = request->getParam("timePerScreen", true);
// uint timerSeconds = p->value().toInt() * 60;
// preferences.putUInt("timerSeconds", timerSeconds);
// settingsChanged = true;
// }
// request->send(200);
// if (settingsChanged)
// {
// queueLedEffect(LED_FLASH_SUCCESS);
// }
}
void onApiSystemStatus(AsyncWebServerRequest *request) void onApiSystemStatus(AsyncWebServerRequest *request)
{ {
AsyncResponseStream *response = AsyncResponseStream *response =
@ -1209,6 +1019,30 @@ void eventSourceTask(void *pvParameters)
} }
} }
void onApiShowCurrency(AsyncWebServerRequest *request)
{
if (request->hasParam("c"))
{
const AsyncWebParameter *p = request->getParam("c");
std::string currency = p->value().c_str();
if (!isActiveCurrency(currency))
{
request->send(404);
return;
}
char curChar = getCurrencyChar(currency);
setCurrentCurrency(curChar);
setCurrentScreen(getCurrentScreen());
request->send(200);
return;
}
request->send(404);
}
#ifdef HAS_FRONTLIGHT #ifdef HAS_FRONTLIGHT
void onApiFrontlightOn(AsyncWebServerRequest *request) void onApiFrontlightOn(AsyncWebServerRequest *request)
{ {

View file

@ -30,6 +30,8 @@ void onApiScreenNext(AsyncWebServerRequest *request);
void onApiScreenPrevious(AsyncWebServerRequest *request); void onApiScreenPrevious(AsyncWebServerRequest *request);
void onApiShowScreen(AsyncWebServerRequest *request); void onApiShowScreen(AsyncWebServerRequest *request);
void onApiShowCurrency(AsyncWebServerRequest *request);
void onApiShowText(AsyncWebServerRequest *request); void onApiShowText(AsyncWebServerRequest *request);
void onApiIdentify(AsyncWebServerRequest *request); void onApiIdentify(AsyncWebServerRequest *request);
@ -38,7 +40,6 @@ void onApiShowTextAdvanced(AsyncWebServerRequest *request, JsonVariant &json);
void onApiActionPause(AsyncWebServerRequest *request); void onApiActionPause(AsyncWebServerRequest *request);
void onApiActionTimerRestart(AsyncWebServerRequest *request); void onApiActionTimerRestart(AsyncWebServerRequest *request);
void onApiSettingsGet(AsyncWebServerRequest *request); void onApiSettingsGet(AsyncWebServerRequest *request);
void onApiSettingsPost(AsyncWebServerRequest *request);
void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json); void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json);
void onApiFullRefresh(AsyncWebServerRequest *request); void onApiFullRefresh(AsyncWebServerRequest *request);

View file

@ -134,7 +134,7 @@ extern "C" void app_main()
} }
// if more than 5 price updates are missed, there is probably something wrong, reconnect // if more than 5 price updates are missed, there is probably something wrong, reconnect
if ((getLastPriceUpdate() - currentUptime) > (preferences.getUInt("minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE) * 5)) if ((getLastPriceUpdate(CURRENCY_USD) - currentUptime) > (preferences.getUInt("minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE) * 5))
{ {
Serial.println(F("Detected 5 missed price updates... restarting price handler.")); Serial.println(F("Detected 5 missed price updates... restarting price handler."));