feat: Add LNBits multi currency price integration

This commit is contained in:
Djuri Baars 2025-04-15 22:39:27 +02:00
parent 3e54343da8
commit 19c877a254
No known key found for this signature in database
GPG Key ID: 61B9B2DDE5AA3AC1
12 changed files with 526 additions and 155 deletions

View File

@ -46,8 +46,11 @@ std::string getCurrencyCode(char input)
case CURRENCY_CAD:
return CURRENCY_CODE_CAD;
break;
default:
case CURRENCY_USD:
return CURRENCY_CODE_USD;
break;
default:
return CURRENCY_CODE_UNKNOWN;
}
}
@ -63,126 +66,172 @@ char getCurrencyChar(const std::string& input)
return CURRENCY_AUD;
else if (input == "CAD")
return CURRENCY_CAD;
else
else if (input == "USD")
return CURRENCY_USD; // Assuming USD is the default for unknown inputs
else
return ' ';
}
std::array<std::string, NUM_SCREENS> parsePriceData(std::uint32_t price, char currencySymbol, bool useSuffixFormat, bool mowMode, bool shareDot)
{
std::array<std::string, NUM_SCREENS> ret;
std::string priceString;
if (std::to_string(price).length() >= NUM_SCREENS || useSuffixFormat)
// Private helper methods
namespace {
std::array<std::string, NUM_SCREENS> formatPriceData(std::uint32_t price, char currencySymbol, const std::string& currencyCode, bool useSuffixFormat, bool mowMode, bool shareDot, bool useSymbol)
{
int numScreens = shareDot || mowMode ? NUM_SCREENS - 1 : NUM_SCREENS - 2;
priceString = getCurrencySymbol(currencySymbol) + formatNumberWithSuffix(price, numScreens, mowMode);
}
else
{
priceString = getCurrencySymbol(currencySymbol) + std::to_string(price);
}
std::uint32_t firstIndex = 0;
if ((shareDot && priceString.length() <= (NUM_SCREENS)) || priceString.length() < (NUM_SCREENS))
{
priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' ');
if (mowMode)
std::array<std::string, NUM_SCREENS> ret;
std::string priceString;
// If useSymbol is true, we're using the char version - use symbol as requested
// If useSymbol is false, we're using the string version - only use symbol if it's a recognized currency
bool shouldUseSymbol = useSymbol || (!useSymbol && currencySymbol != ' ');
if (std::to_string(price).length() >= NUM_SCREENS || useSuffixFormat)
{
ret[0] = "MOW/UNITS";
int numScreens = shareDot || mowMode ? NUM_SCREENS - 1 : NUM_SCREENS - 2;
if (shouldUseSymbol) {
priceString = getCurrencySymbol(currencySymbol) + formatNumberWithSuffix(price, numScreens, mowMode);
} else {
priceString = formatNumberWithSuffix(price, numScreens, mowMode);
}
}
else
{
ret[0] = "BTC/" + getCurrencyCode(currencySymbol);
}
firstIndex = 1;
}
size_t dotPosition = priceString.find('.');
if (shareDot && dotPosition != std::string::npos && dotPosition > 0)
{
std::vector<std::string> tempArray;
if (dotPosition != std::string::npos && dotPosition > 0)
{
for (size_t i = 0; i < priceString.length(); ++i)
{
if (i == dotPosition - 1)
{
tempArray.push_back(std::string(1, priceString[i]) + ".");
++i; // Skip the dot in the next iteration
}
else
{
tempArray.push_back(std::string(1, priceString[i]));
}
}
// Copy from tempArray to ret
for (std::uint32_t i = firstIndex; i < NUM_SCREENS && i - firstIndex < tempArray.size(); ++i)
{
ret[i] = tempArray[i - firstIndex];
if (shouldUseSymbol) {
priceString = getCurrencySymbol(currencySymbol) + std::to_string(price);
} else {
priceString = std::to_string(price);
}
}
}
else
{
for (std::uint32_t i = firstIndex; i < NUM_SCREENS; i++)
{
ret[i] = std::string(1, priceString[i]);
}
}
return ret;
}
std::array<std::string, NUM_SCREENS> parseSatsPerCurrency(std::uint32_t price,char currencySymbol, bool withSatsSymbol)
{
std::array<std::string, NUM_SCREENS> ret;
std::string priceString = std::to_string(int(round(1 / float(price) * 10e7)));
std::uint32_t firstIndex = 0;
std::uint8_t insertSatSymbol = NUM_SCREENS - priceString.length() - 1;
if (priceString.length() < (NUM_SCREENS))
{
// Check if price is greater than 1 billion
if (price >= 100000000)
{
double satsPerCurrency = (1.0 / static_cast<double>(price)) * 1e8; // Calculate satoshis
std::ostringstream oss;
oss << std::fixed << std::setprecision(3) << satsPerCurrency; // Format with 3 decimal places
priceString = oss.str();
}
else
{
priceString = std::to_string(static_cast<int>(round(1.0 / static_cast<double>(price) * 1e8))); // Default formatting
}
// Pad the string with spaces if necessary
if (priceString.length() < NUM_SCREENS)
std::uint32_t firstIndex = 0;
if ((shareDot && priceString.length() <= (NUM_SCREENS)) || priceString.length() < (NUM_SCREENS))
{
priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' ');
if (mowMode)
{
ret[0] = "MOW/UNITS";
}
else
{
ret[0] = "BTC/" + currencyCode;
}
firstIndex = 1;
}
size_t dotPosition = priceString.find('.');
if (currencySymbol != CURRENCY_USD || price >= 100000000) // no time anymore when earlier than 1
ret[0] = "SATS/" + getCurrencyCode(currencySymbol);
else
ret[0] = "MSCW/TIME";
firstIndex = 1;
for (std::uint32_t i = firstIndex; i < NUM_SCREENS; i++)
if (shareDot && dotPosition != std::string::npos && dotPosition > 0)
{
ret[i] = priceString[i];
std::vector<std::string> tempArray;
if (dotPosition != std::string::npos && dotPosition > 0)
{
for (size_t i = 0; i < priceString.length(); ++i)
{
if (i == dotPosition - 1)
{
tempArray.push_back(std::string(1, priceString[i]) + ".");
++i; // Skip the dot in the next iteration
}
else
{
tempArray.push_back(std::string(1, priceString[i]));
}
}
// Copy from tempArray to ret
for (std::uint32_t i = firstIndex; i < NUM_SCREENS && i - firstIndex < tempArray.size(); ++i)
{
ret[i] = tempArray[i - firstIndex];
}
}
}
else
{
for (std::uint32_t i = firstIndex; i < NUM_SCREENS; i++)
{
ret[i] = std::string(1, priceString[i]);
}
}
if (withSatsSymbol)
{
ret[insertSatSymbol] = "STS";
}
return ret;
}
return ret;
std::array<std::string, NUM_SCREENS> formatSatsPerCurrency(std::uint32_t price, char currencySymbol, const std::string& currencyCode, bool withSatsSymbol, bool alwaysShowSats)
{
std::array<std::string, NUM_SCREENS> ret;
double satsPerCurrency = (1.0 / static_cast<double>(price)) * 1e8;
std::string priceString;
// Handle values below 1 sat per currency with 3 decimal places
if (satsPerCurrency < 1.0) {
std::ostringstream oss;
oss << std::fixed << std::setprecision(3) << satsPerCurrency;
priceString = oss.str();
}
// // Check if price is greater than 1 billion (for displaying with 3 decimal places)
// else if (price >= 100000000) {
// std::ostringstream oss;
// oss << std::fixed << std::setprecision(3) << satsPerCurrency;
// priceString = oss.str();
// }
else {
// Default formatting for integer values
priceString = std::to_string(static_cast<int>(round(satsPerCurrency)));
}
std::uint32_t firstIndex = 0;
std::uint8_t insertSatSymbol = NUM_SCREENS - priceString.length() - 1;
if (priceString.length() < (NUM_SCREENS))
{
// Pad the string with spaces if necessary
if (priceString.length() < NUM_SCREENS)
{
priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' ');
}
if (alwaysShowSats || currencySymbol != CURRENCY_USD || price >= 100000000) // no time anymore when earlier than 1
ret[0] = "SATS/" + currencyCode;
else
ret[0] = "MSCW/TIME";
firstIndex = 1;
for (std::uint32_t i = firstIndex; i < NUM_SCREENS; i++)
{
ret[i] = priceString[i];
}
if (withSatsSymbol)
{
ret[insertSatSymbol] = "STS";
}
}
return ret;
}
}
// Updated public methods to use the helper methods
std::array<std::string, NUM_SCREENS> parsePriceData(std::uint32_t price, char currencySymbol, bool useSuffixFormat, bool mowMode, bool shareDot)
{
// For char version, always use the currency symbol
return formatPriceData(price, currencySymbol, getCurrencyCode(currencySymbol), useSuffixFormat, mowMode, shareDot, true);
}
std::array<std::string, NUM_SCREENS> parsePriceData(std::uint32_t price, const std::string& currencyCode, bool useSuffixFormat, bool mowMode, bool shareDot)
{
// For string version, let formatPriceData decide whether to use symbol based on if it's a recognized currency
char currencyChar = getCurrencyChar(currencyCode);
return formatPriceData(price, currencyChar, currencyCode, useSuffixFormat, mowMode, shareDot, false);
}
std::array<std::string, NUM_SCREENS> parseSatsPerCurrency(std::uint32_t price, char currencySymbol, bool withSatsSymbol)
{
return formatSatsPerCurrency(price, currencySymbol, getCurrencyCode(currencySymbol), withSatsSymbol, false);
}
std::array<std::string, NUM_SCREENS> parseSatsPerCurrency(std::uint32_t price, const std::string& currencyCode, bool withSatsSymbol)
{
return formatSatsPerCurrency(price, getCurrencyChar(currencyCode), currencyCode, withSatsSymbol, true);
}
std::array<std::string, NUM_SCREENS> parseBlockHeight(std::uint32_t blockHeight)
@ -350,7 +399,12 @@ emscripten::val parseBlockHeightArray(std::uint32_t blockHeight)
emscripten::val parsePriceDataArray(std::uint32_t price, const std::string &currencySymbol, bool useSuffixFormat = false, bool mowMode = false, bool shareDot = false)
{
return arrayToStringArray(parsePriceData(price, currencySymbol[0], useSuffixFormat, mowMode, shareDot));
// Handle both single character and three-character currency codes
if (currencySymbol.length() == 1) {
return arrayToStringArray(parsePriceData(price, currencySymbol[0], useSuffixFormat, mowMode, shareDot));
} else {
return arrayToStringArray(parsePriceData(price, currencySymbol, useSuffixFormat, mowMode, shareDot));
}
}
emscripten::val parseHalvingCountdownArray(std::uint32_t blockHeight, bool asBlocks)
@ -370,7 +424,12 @@ emscripten::val parseBlockFeesArray(std::uint16_t blockFees)
emscripten::val parseSatsPerCurrencyArray(std::uint32_t price, const std::string &currencySymbol, bool withSatsSymbol)
{
return arrayToStringArray(parseSatsPerCurrency(price, currencySymbol[0], withSatsSymbol));
// Handle both single character and three-character currency codes
if (currencySymbol.length() == 1) {
return arrayToStringArray(parseSatsPerCurrency(price, currencySymbol[0], withSatsSymbol));
} else {
return arrayToStringArray(parseSatsPerCurrency(price, currencySymbol, withSatsSymbol));
}
}
EMSCRIPTEN_BINDINGS(my_module)

View File

@ -3,6 +3,8 @@
#include <cmath>
#include <cstdint>
#include <vector>
#include <sstream>
#include <iomanip>
#include "utils.hpp"
@ -19,9 +21,13 @@ 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";
const std::string CURRENCY_CODE_UNKNOWN = " ";
// Public methods
std::array<std::string, NUM_SCREENS> parsePriceData(std::uint32_t price, char currency, bool useSuffixFormat = false, bool mowMode = false, bool shareDot = false);
std::array<std::string, NUM_SCREENS> parsePriceData(std::uint32_t price, const std::string& currencyCode, bool useSuffixFormat = false, bool mowMode = false, bool shareDot = 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, const std::string& currencyCode, bool withSatsSymbol);
std::array<std::string, NUM_SCREENS> parseBlockHeight(std::uint32_t blockHeight);
std::array<std::string, NUM_SCREENS> parseHalvingCountdown(std::uint32_t blockHeight, bool asBlocks);
std::array<std::string, NUM_SCREENS> parseMarketCap(std::uint32_t blockHeight, std::uint32_t price, char currencySymbol, bool bigChars);
@ -29,4 +35,10 @@ std::array<std::string, NUM_SCREENS> parseBlockFees(std::uint16_t blockFees);
char getCurrencySymbol(char input);
std::string getCurrencyCode(char input);
char getCurrencyChar(const std::string& input);
char getCurrencyChar(const std::string& input);
// Private helper methods
namespace {
std::array<std::string, NUM_SCREENS> formatPriceData(std::uint32_t price, char currencySymbol, const std::string& currencyCode, bool useSuffixFormat, bool mowMode, bool shareDot, bool useSymbol = true);
std::array<std::string, NUM_SCREENS> formatSatsPerCurrency(std::uint32_t price, char currencySymbol, const std::string& currencyCode, bool withSatsSymbol, bool alwaysShowSats = false);
}

View File

@ -1,5 +1,6 @@
#include "config.hpp"
#include "led_handler.hpp"
#include "lnbits.hpp"
#define MAX_ATTEMPTS_WIFI_CONNECTION 20
@ -392,6 +393,10 @@ String replaceAmbiguousChars(String input)
void setupWebsocketClients(void *pvParameters)
{
DataSourceType dataSource = getDataSource();
if (preferences.getBool("lnbitsEnabled", DEFAULT_LNBITS_ENABLED)) {
setupLnbits();
}
if (dataSource == BTCLOCK_SOURCE || dataSource == CUSTOM_SOURCE)
{

View File

@ -99,4 +99,8 @@ enum DataSourceType {
#ifndef DEFAULT_BOOT_TEXT
#define DEFAULT_BOOT_TEXT "BTCLOCK"
#endif
#endif
#define DEFAULT_LNBITS_INSTANCE "demo.lnbits.com"
#define DEFAULT_LNBITS_HTTPS true
#define DEFAULT_LNBITS_ENABLED false

151
src/lib/lnbits.cpp Normal file
View File

@ -0,0 +1,151 @@
#include "lnbits.hpp"
#include "lib/shared.hpp"
LNBitsFetch* LNBitsFetch::instance = nullptr;
LNBitsFetch::LNBitsFetch() : initialized(false), taskHandle(nullptr) {}
LNBitsFetch& LNBitsFetch::getInstance() {
if (instance == nullptr) {
instance = new LNBitsFetch();
}
return *instance;
}
void LNBitsFetch::setup() {
if (initialized) return;
currentCurrency = getActiveCurrencies().front();
xTaskCreate(taskLNBits, "lnbitsTask", 6 * 1024, NULL, tskIDLE_PRIORITY, &taskHandle);
initialized = true;
// Fetch rates immediately when setup
fetchRates();
}
String LNBitsFetch::buildApiUrl(const std::string& currency) {
String instance = preferences.getString("lnbitsInstance", DEFAULT_LNBITS_INSTANCE);
bool useHttps = preferences.getBool("lnbitsHttps", DEFAULT_LNBITS_HTTPS);
String protocol = useHttps ? "https" : "http";
return protocol + "://" + instance + "/api/v1/rate/" + String(currency.c_str());
}
void LNBitsFetch::fetchRates() {
if (!initialized) return;
std::vector<std::string> currencies = getActiveCurrencies();
HTTPClient http;
for (const std::string& currency : currencies) {
String apiUrl = buildApiUrl(currency);
if (debugLogEnabled()) {
Serial.printf("Fetching LNBits rate for %s from %s\n", currency.c_str(), apiUrl.c_str());
}
http.begin(apiUrl);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
JsonDocument doc;
DeserializationError error = deserializeJson(doc, payload);
if (!error) {
if (doc.containsKey("price")) {
uint price = doc["price"].as<uint>();
if (price > 0) {
priceMap[currency] = price;
unsigned long int currentTime = esp_timer_get_time() / 1000000;
lastUpdateMap[currency] = currentTime;
if (debugLogEnabled()) {
Serial.printf("Updated LNBits price for %s: %d\n", currency.c_str(), price);
}
// Update the screen if it's currently displaying price-related information
if (workQueue != nullptr && (
ScreenHandler::getCurrentScreen() == SCREEN_BTC_TICKER ||
ScreenHandler::getCurrentScreen() == SCREEN_SATS_PER_CURRENCY ||
ScreenHandler::getCurrentScreen() == SCREEN_MARKET_CAP)) {
WorkItem rateUpdate = {TASK_PRICE_UPDATE, currency[0]};
xQueueSend(workQueue, &rateUpdate, portMAX_DELAY);
}
}
}
} else if (debugLogEnabled()) {
Serial.printf("JSON parse error for %s: %s\n", currency.c_str(), error.c_str());
}
} else if (debugLogEnabled()) {
Serial.printf("HTTP error for %s: %d\n", currency.c_str(), httpCode);
}
http.end();
}
}
void LNBitsFetch::stop() {
if (taskHandle != nullptr) {
vTaskDelete(taskHandle);
taskHandle = nullptr;
}
initialized = false;
}
void LNBitsFetch::restart() {
stop();
setup();
}
bool LNBitsFetch::isInitialized() const {
return initialized;
}
TaskHandle_t LNBitsFetch::getTaskHandle() const {
return taskHandle;
}
uint LNBitsFetch::getPrice(const std::string& currencyCode) {
if (priceMap.find(currencyCode) == priceMap.end()) {
return 0;
}
return priceMap[currencyCode];
}
void LNBitsFetch::setPrice(uint newPrice, const std::string& currencyCode) {
priceMap[currencyCode] = newPrice;
}
unsigned long int LNBitsFetch::getLastPriceUpdate(const std::string& currencyCode) {
if (lastUpdateMap.find(currencyCode) == lastUpdateMap.end()) {
return 0;
}
return lastUpdateMap[currencyCode];
}
void setupLnbits() {
if (preferences.getBool("lnbitsEnabled", DEFAULT_LNBITS_ENABLED)) {
LNBitsFetch::getInstance().setup();
}
}
void taskLNBits(void* pvParameters) {
for (;;) {
// Wait for notification from timer
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// Fetch rates
LNBitsFetch::getInstance().fetchRates();
}
}
void LNBitsFetch::setCurrentCurrency(const std::string& currency) {
currentCurrency = currency;
}
std::string LNBitsFetch::getCurrentCurrency() const {
return currentCurrency;
}

43
src/lib/lnbits.hpp Normal file
View File

@ -0,0 +1,43 @@
#pragma once
#include <Arduino.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include <memory>
#include <string>
#include <map>
#include "lib/screen_handler.hpp"
class LNBitsFetch {
private:
LNBitsFetch();
static LNBitsFetch* instance;
bool initialized;
TaskHandle_t taskHandle;
std::map<std::string, uint> priceMap;
std::map<std::string, unsigned long int> lastUpdateMap;
std::string currentCurrency;
String buildApiUrl(const std::string& currency);
public:
static LNBitsFetch& getInstance();
void setup();
void fetchRates();
void stop();
void restart();
void setCurrentCurrency(const std::string& currency);
std::string getCurrentCurrency() const;
bool isInitialized() const;
TaskHandle_t getTaskHandle() const;
uint getPrice(const std::string& currencyCode);
void setPrice(uint newPrice, const std::string& currencyCode);
unsigned long int getLastPriceUpdate(const std::string& currencyCode);
};
void setupLnbits();
void taskLNBits(void* pvParameters);

View File

@ -89,24 +89,47 @@ bool ScreenHandler::handleCurrencyRotation(bool forward) {
std::vector<std::string> ac = getActiveCurrencies();
if (ac.empty()) return false;
std::string curCode = getCurrencyCode(getCurrentCurrency());
auto it = std::find(ac.begin(), ac.end(), curCode);
if (it == ac.end()) {
// Current currency not found in active currencies - initialize based on direction
setCurrentCurrency(getCurrencyChar(forward ? ac.front() : ac.back()));
setCurrentScreen(getCurrentScreen());
return true;
} else if (forward && curCode != ac.back()) {
// Moving forward and not at last currency
setCurrentCurrency(getCurrencyChar(ac.at(std::distance(ac.begin(), it) + 1)));
setCurrentScreen(getCurrentScreen());
return true;
} else if (!forward && curCode != ac.front()) {
// Moving backward and not at first currency
setCurrentCurrency(getCurrencyChar(ac.at(std::distance(ac.begin(), it) - 1)));
setCurrentScreen(getCurrentScreen());
return true;
if (preferences.getBool("lnbitsEnabled", DEFAULT_LNBITS_ENABLED)) {
std::string curCode = LNBitsFetch::getInstance().getCurrentCurrency();
auto it = std::find(ac.begin(), ac.end(), curCode);
if (it == ac.end()) {
// Current currency not found in active currencies - initialize based on direction
LNBitsFetch::getInstance().setCurrentCurrency(forward ? ac.front() : ac.back());
setCurrentScreen(getCurrentScreen());
return true;
} else if (forward && curCode != ac.back()) {
// Moving forward and not at last currency
LNBitsFetch::getInstance().setCurrentCurrency(ac.at(std::distance(ac.begin(), it) + 1));
setCurrentScreen(getCurrentScreen());
return true;
} else if (!forward && curCode != ac.front()) {
// Moving backward and not at first currency
LNBitsFetch::getInstance().setCurrentCurrency(ac.at(std::distance(ac.begin(), it) - 1));
setCurrentScreen(getCurrentScreen());
return true;
}
} else {
std::string curCode = getCurrencyCode(getCurrentCurrency());
auto it = std::find(ac.begin(), ac.end(), curCode);
if (it == ac.end()) {
// Current currency not found in active currencies - initialize based on direction
setCurrentCurrency(getCurrencyChar(forward ? ac.front() : ac.back()));
setCurrentScreen(getCurrentScreen());
return true;
} else if (forward && curCode != ac.back()) {
// Moving forward and not at last currency
setCurrentCurrency(getCurrencyChar(ac.at(std::distance(ac.begin(), it) + 1)));
setCurrentScreen(getCurrentScreen());
return true;
} else if (!forward && curCode != ac.front()) {
// Moving backward and not at first currency
setCurrentCurrency(getCurrencyChar(ac.at(std::distance(ac.begin(), it) - 1)));
setCurrentScreen(getCurrentScreen());
return true;
}
}
// If we're at the last/first currency of current screen, let nextScreen/previousScreen handle it
return false;
@ -241,15 +264,32 @@ void workerTask(void *pvParameters) {
case TASK_PRICE_UPDATE: {
uint currency = ScreenHandler::getCurrentCurrency();
uint price = getPrice(currency);
uint price;
if (preferences.getBool("lnbitsEnabled", DEFAULT_LNBITS_ENABLED)) {
price = LNBitsFetch::getInstance().getPrice(LNBitsFetch::getInstance().getCurrentCurrency());
} else {
price = getPrice(currency);
}
if (currentScreenValue == SCREEN_BTC_TICKER) {
taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE),
preferences.getBool("mowMode", DEFAULT_MOW_MODE),
preferences.getBool("suffixShareDot", DEFAULT_SUFFIX_SHARE_DOT)
);
if (preferences.getBool("lnbitsEnabled", DEFAULT_LNBITS_ENABLED)) {
taskEpdContent = parsePriceData(price, LNBitsFetch::getInstance().getCurrentCurrency(), preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE),
preferences.getBool("mowMode", DEFAULT_MOW_MODE),
preferences.getBool("suffixShareDot", DEFAULT_SUFFIX_SHARE_DOT)
);
} else {
taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE),
preferences.getBool("mowMode", DEFAULT_MOW_MODE),
preferences.getBool("suffixShareDot", DEFAULT_SUFFIX_SHARE_DOT)
);
}
} else if (currentScreenValue == SCREEN_SATS_PER_CURRENCY) {
taskEpdContent = parseSatsPerCurrency(price, currency, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL));
if (preferences.getBool("lnbitsEnabled", DEFAULT_LNBITS_ENABLED)) {
taskEpdContent = parseSatsPerCurrency(price, LNBitsFetch::getInstance().getCurrentCurrency(), preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL));
} else {
taskEpdContent = parseSatsPerCurrency(price, currency, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL));
}
} else {
auto& blockNotify = BlockNotify::getInstance();
taskEpdContent = parseMarketCap(blockNotify.getBlockHeight(), price, currency, preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR));

View File

@ -1,5 +1,6 @@
#include "timers.hpp"
#include "led_handler.hpp"
#include "lnbits.hpp"
esp_timer_handle_t screenRotateTimer;
esp_timer_handle_t minuteTimer;
@ -78,6 +79,11 @@ void IRAM_ATTR minuteTimerISR(void *arg) {
if (miningPoolHandle != NULL) {
vTaskNotifyGiveFromISR(miningPoolHandle, &xHigherPriorityTaskWoken);
}
TaskHandle_t lnbitsHandle = LNBitsFetch::getInstance().getTaskHandle();
if (lnbitsHandle != NULL) {
vTaskNotifyGiveFromISR(lnbitsHandle, &xHigherPriorityTaskWoken);
}
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();

View File

@ -77,21 +77,22 @@ namespace V2Notify
buffer = new uint8_t[responseLength];
response["type"] = "subscribe";
response["eventType"] = "price";
if (!preferences.getBool("lnbitsEnabled", DEFAULT_LNBITS_ENABLED)) {
response["type"] = "subscribe";
response["eventType"] = "price";
JsonArray currenciesArray = response["currencies"].to<JsonArray>();
JsonArray currenciesArray = response["currencies"].to<JsonArray>();
for (const auto &str : getActiveCurrencies())
{
currenciesArray.add(str);
for (const auto &str : getActiveCurrencies())
{
currenciesArray.add(str);
}
responseLength = measureMsgPack(response);
buffer = new uint8_t[responseLength];
serializeMsgPack(response, buffer, responseLength);
webSocket.sendBIN(buffer, responseLength);
}
// response["currencies"] = currenciesArray;
responseLength = measureMsgPack(response);
buffer = new uint8_t[responseLength];
serializeMsgPack(response, buffer, responseLength);
webSocket.sendBIN(buffer, responseLength);
break;
}
case WStype_TEXT:

View File

@ -5,7 +5,7 @@
static const char* JSON_CONTENT = "application/json";
static const char *const PROGMEM strSettings[] = {
"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint", "fontName", "localPoolEndpoint", "tzString"};
"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint", "fontName", "localPoolEndpoint", "tzString", "lnbitsInstance"};
static const char *const PROGMEM uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout"};
@ -18,7 +18,7 @@ static const char *const PROGMEM boolSettings[] = {"ledTestOnPower", "ledFlashOn
"mempoolSecure", "bitaxeEnabled",
"miningPoolStats", "verticalDesc",
"nostrZapNotify", "httpAuthEnabled",
"enableDebugLog", "ceDisableSSL", "dndEnabled", "dndTimeBasedEnabled"};
"enableDebugLog", "ceDisableSSL", "dndEnabled", "dndTimeBasedEnabled", "lnbitsEnabled", "lnbitsHttps"};
AsyncWebServer server(80);
AsyncEventSource events("/events");
@ -252,9 +252,15 @@ JsonDocument getStatusObject()
conStatus["blocks"] = blockNotify.isConnected();
conStatus["V2"] = V2Notify::isV2NotifyConnected();
conStatus["nostr"] = nostrConnected();
conStatus["lnbits"] = LNBitsFetch::getInstance().isInitialized();
root["rssi"] = WiFi.RSSI();
root["currency"] = getCurrencyCode(ScreenHandler::getCurrentCurrency());
if (preferences.getBool("lnbitsEnabled", DEFAULT_LNBITS_ENABLED)) {
root["currency"] = LNBitsFetch::getInstance().getCurrentCurrency();
} else {
root["currency"] = getCurrencyCode(ScreenHandler::getCurrentCurrency());
}
#ifdef HAS_FRONTLIGHT
std::vector<uint16_t> statuses = ledHandler.frontlightGetStatus();
@ -713,6 +719,12 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
root["ledFlashOnZap"] = preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP);
root["fontName"] = preferences.getString("fontName", DEFAULT_FONT_NAME);
root["availableFonts"] = FontNames::getAvailableFonts();
// LNBits settings
root["lnbitsEnabled"] = preferences.getBool("lnbitsEnabled", DEFAULT_LNBITS_ENABLED);
root["lnbitsInstance"] = preferences.getString("lnbitsInstance", DEFAULT_LNBITS_INSTANCE);
root["lnbitsHttps"] = preferences.getBool("lnbitsHttps", DEFAULT_LNBITS_HTTPS);
// Custom endpoint settings (only used for CUSTOM_SOURCE)
root["customEndpoint"] = preferences.getString("customEndpoint", DEFAULT_CUSTOM_ENDPOINT);
root["customEndpointDisableSSL"] = preferences.getBool("customEndpointDisableSSL", DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL);
@ -917,6 +929,7 @@ void onApiStopDataSources(AsyncWebServerRequest *request)
stopPriceNotify();
BlockNotify::getInstance().stop();
LNBitsFetch::getInstance().stop();
request->send(response);
}
@ -928,6 +941,9 @@ void onApiRestartDataSources(AsyncWebServerRequest *request)
restartPriceNotify();
BlockNotify::getInstance().restart();
if (preferences.getBool("lnbitsEnabled", DEFAULT_LNBITS_ENABLED)) {
LNBitsFetch::getInstance().restart();
}
request->send(response);
}
@ -1077,10 +1093,17 @@ void onApiShowCurrency(AsyncWebServerRequest *request)
return;
}
char curChar = getCurrencyChar(currency);
// LNbits: set currency using a string
if (preferences.getBool("lnbitsEnabled", DEFAULT_LNBITS_ENABLED)) {
LNBitsFetch::getInstance().setCurrentCurrency(currency);
ScreenHandler::setCurrentScreen(ScreenHandler::getCurrentScreen());
ScreenHandler::setCurrentCurrency(curChar);
ScreenHandler::setCurrentScreen(ScreenHandler::getCurrentScreen());
} else {
// Normal situation: set currency using a char
char curChar = getCurrencyChar(currency);
ScreenHandler::setCurrentCurrency(curChar);
ScreenHandler::setCurrentScreen(ScreenHandler::getCurrentScreen());
}
request->send(HTTP_OK);
return;

View File

@ -8,6 +8,11 @@
#include <ESPmDNS.h>
#include "AsyncJson.h"
#include <iostream>
#include <AsyncTCP.h>
#include <ESPmDNS.h>
#include <Update.h>
#include <WiFi.h>
#include <sstream>
#include "lib/block_notify.hpp"
#include "lib/led_handler.hpp"
@ -15,6 +20,11 @@
#include "lib/screen_handler.hpp"
#include "webserver/OneParamRewrite.hpp"
#include "lib/mining_pool/pool_factory.hpp"
#include "lib/lnbits.hpp"
#include "lib/v2_notify.hpp"
#include "lib/ota.hpp"
#include "lib/shared.hpp"
extern TaskHandle_t eventSourceTaskHandle;

View File

@ -36,12 +36,15 @@ void test_CorrectSatsPerDollarConversion(void)
void test_SatsPerDollarAfter1B(void)
{
std::array<std::string, NUM_SCREENS> output = parseSatsPerCurrency(120000000, CURRENCY_USD, false);
TEST_ASSERT_EQUAL_STRING("SATS/USD", output[0].c_str());
TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 5].c_str());
TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS - 4].c_str());
TEST_ASSERT_EQUAL_STRING("8", output[NUM_SCREENS - 3].c_str());
TEST_ASSERT_EQUAL_STRING("3", output[NUM_SCREENS - 2].c_str());
TEST_ASSERT_EQUAL_STRING("3", output[NUM_SCREENS - 1].c_str());
std::string joined = joinArrayWithBrackets(output);
TEST_ASSERT_EQUAL_STRING_MESSAGE("SATS/USD", output[0].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output[NUM_SCREENS - 5].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE(".", output[NUM_SCREENS - 4].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("8", output[NUM_SCREENS - 3].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("3", output[NUM_SCREENS - 2].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("3", output[NUM_SCREENS - 1].c_str(), joined.c_str());
}
void test_CorrectSatsPerPoundConversion(void)
@ -272,6 +275,18 @@ void test_Mcap1TrillionJpySmallChars(void)
TEST_ASSERT_EQUAL_STRING("000", output[NUM_SCREENS - 1].c_str());
}
void test_PriceDataWithCurrencyCode(void)
{
std::array<std::string, NUM_SCREENS> output = parsePriceData(100000, "PYG", false, false, false);
TEST_ASSERT_EQUAL_STRING("BTC/PYG", output[0].c_str());
}
void test_SatsPerCurrencyWithCurrencyCode(void)
{
std::array<std::string, NUM_SCREENS> output = parseSatsPerCurrency(100000, "ZAR", false);
TEST_ASSERT_EQUAL_STRING("SATS/ZAR", output[0].c_str());
}
// not needed when using generate_test_runner.rb
int runUnityTests(void)
{
@ -295,6 +310,8 @@ int runUnityTests(void)
RUN_TEST(test_PriceSuffixModeCompact2);
RUN_TEST(test_PriceSuffixModeMow);
RUN_TEST(test_PriceSuffixModeMowCompact);
RUN_TEST(test_PriceDataWithCurrencyCode);
RUN_TEST(test_SatsPerCurrencyWithCurrencyCode);
return UNITY_END();
}