Compare commits
1 commit
main
...
feature/pr
Author | SHA1 | Date | |
---|---|---|---|
e987e9afd5 |
18 changed files with 628 additions and 297 deletions
|
@ -1,7 +1,7 @@
|
|||
#include "config.hpp"
|
||||
#include "led_handler.hpp"
|
||||
|
||||
#define MAX_ATTEMPTS_WIFI_CONNECTION 20
|
||||
// Global instance definitions
|
||||
PriceNotify::PriceNotifyManager priceManager;
|
||||
|
||||
// zlib_turbo zt;
|
||||
|
||||
|
@ -266,7 +266,7 @@ void setupPreferences()
|
|||
EPDManager::getInstance().setForegroundColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR));
|
||||
EPDManager::getInstance().setBackgroundColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR));
|
||||
setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT));
|
||||
setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE), CURRENCY_USD);
|
||||
priceManager.processNewPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE), CURRENCY_USD);
|
||||
|
||||
if (!preferences.isKey("enableDebugLog")) {
|
||||
preferences.putBool("enableDebugLog", DEFAULT_ENABLE_DEBUG_LOG);
|
||||
|
@ -375,6 +375,14 @@ void setupWebsocketClients(void *pvParameters)
|
|||
{
|
||||
setupBlockNotify();
|
||||
setupPriceNotify();
|
||||
|
||||
// Create task for price manager loop
|
||||
xTaskCreate([](void* param) {
|
||||
for (;;) {
|
||||
priceManager.loop();
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}, "priceManagerLoop", (6 * 1024), NULL, tskIDLE_PRIORITY, NULL);
|
||||
}
|
||||
|
||||
vTaskDelete(NULL);
|
||||
|
@ -753,3 +761,17 @@ DataSourceType getDataSource() {
|
|||
void setDataSource(DataSourceType source) {
|
||||
preferences.putUChar("dataSource", static_cast<uint8_t>(source));
|
||||
}
|
||||
|
||||
void setupPriceNotify() {
|
||||
priceManager.init(PriceNotify::PriceSource::COINCAP);
|
||||
priceManager.onPriceUpdate([](PriceNotify::Currency currency, uint64_t price) {
|
||||
if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_BTC_TICKER ||
|
||||
ScreenHandler::getCurrentScreen() == SCREEN_SATS_PER_CURRENCY ||
|
||||
ScreenHandler::getCurrentScreen() == SCREEN_MARKET_CAP))
|
||||
{
|
||||
WorkItem priceUpdate = {TASK_PRICE_UPDATE, static_cast<char>(currency)};
|
||||
xQueueSend(workQueue, &priceUpdate, portMAX_DELAY);
|
||||
}
|
||||
});
|
||||
priceManager.connect();
|
||||
}
|
||||
|
|
|
@ -1,107 +1,88 @@
|
|||
#pragma once
|
||||
|
||||
#include <MCP23017.h>
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <Preferences.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#include <WiFiManager.h>
|
||||
#include <base64.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
|
||||
#include <nvs_flash.h>
|
||||
#include <map>
|
||||
|
||||
#include "lib/block_notify.hpp"
|
||||
#include "lib/button_handler.hpp"
|
||||
#include "lib/epd.hpp"
|
||||
// #include "lib/improv.hpp"
|
||||
#include "lib/led_handler.hpp"
|
||||
#include "lib/ota.hpp"
|
||||
#include "lib/nostr_notify.hpp"
|
||||
#include "lib/bitaxe_fetch.hpp"
|
||||
#include "lib/mining_pool_stats_fetch.hpp"
|
||||
|
||||
#include "lib/v2_notify.hpp"
|
||||
|
||||
#include "lib/price_notify.hpp"
|
||||
#include "lib/screen_handler.hpp"
|
||||
#include "lib/shared.hpp"
|
||||
#include "lib/webserver.hpp"
|
||||
#ifdef HAS_FRONTLIGHT
|
||||
#include "PCA9685.h"
|
||||
#include "BH1750.h"
|
||||
#endif
|
||||
#include <WiFiManager.h>
|
||||
#include <ESPmDNS.h>
|
||||
|
||||
#include "block_notify.hpp"
|
||||
#include "led_handler.hpp"
|
||||
#include "nostr_notify.hpp"
|
||||
#include "price_notify/price_notify.hpp"
|
||||
#include "screen_handler.hpp"
|
||||
#include "shared.hpp"
|
||||
#include "defaults.hpp"
|
||||
#include "timers.hpp"
|
||||
#include "v2_notify.hpp"
|
||||
#include "webserver.hpp"
|
||||
#include "button_handler.hpp"
|
||||
#include "bitaxe_fetch.hpp"
|
||||
#include "mining_pool_stats_fetch.hpp"
|
||||
#include "epd.hpp"
|
||||
|
||||
#define NTP_SERVER "pool.ntp.org"
|
||||
#define DEFAULT_TIME_OFFSET_SECONDS 3600
|
||||
#ifndef MCP_DEV_ADDR
|
||||
#define MCP_DEV_ADDR 0x20
|
||||
#ifdef HAS_FRONTLIGHT
|
||||
#include <BH1750.h>
|
||||
#include <PCA9685.h>
|
||||
#endif
|
||||
|
||||
extern Preferences preferences;
|
||||
extern QueueHandle_t workQueue;
|
||||
extern PriceNotify::PriceNotifyManager priceManager;
|
||||
|
||||
void setup();
|
||||
void syncTime();
|
||||
uint getLastTimeSync();
|
||||
void setupPreferences();
|
||||
void setupWebsocketClients(void *pvParameters);
|
||||
void setupHardware();
|
||||
void setupConfig();
|
||||
void setupWifi();
|
||||
void setupOTA();
|
||||
void setupMDNS();
|
||||
void setupDataSources();
|
||||
void stopDataSources();
|
||||
void restartDataSources();
|
||||
void setupTasks();
|
||||
void setupTimers();
|
||||
void setupLittleFS();
|
||||
void setupPreferences();
|
||||
void setupWDT();
|
||||
void setupWorkQueue();
|
||||
void setupLedHandler();
|
||||
void setupScreenHandler();
|
||||
void setupWebserver();
|
||||
void setupNostrNotify();
|
||||
void setupBlockNotify();
|
||||
void setupV2Notify();
|
||||
void setupPriceNotify();
|
||||
void setupHardware();
|
||||
void finishSetup();
|
||||
void setupMcp();
|
||||
void syncTime();
|
||||
|
||||
void handleOTA();
|
||||
void handleWifi();
|
||||
void handleWDT();
|
||||
void handleWorkQueue();
|
||||
|
||||
void setWifiTxPower(int8_t power);
|
||||
void onWifiEvent(WiFiEvent_t event);
|
||||
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
|
||||
DataSourceType getDataSource();
|
||||
void setDataSource(DataSourceType source);
|
||||
String getMyHostname();
|
||||
uint getLastTimeSync();
|
||||
bool debugLogEnabled();
|
||||
|
||||
#ifdef HAS_FRONTLIGHT
|
||||
extern BH1750 bh1750;
|
||||
extern bool hasLuxSensor;
|
||||
float getLightLevel();
|
||||
bool hasLightLevel();
|
||||
|
||||
extern PCA9685 flArray;
|
||||
#endif
|
||||
|
||||
String getMyHostname();
|
||||
std::vector<ScreenMapping> getScreenNameMap();
|
||||
|
||||
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 addCurrencyMappings(const std::vector<std::string>& currencies);
|
||||
std::vector<std::string> getActiveCurrencies();
|
||||
std::vector<std::string> getAvailableCurrencies();
|
||||
|
||||
bool isActiveCurrency(std::string ¤cy);
|
||||
|
||||
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
String getHwRev();
|
||||
bool isWhiteVersion();
|
||||
String getFsRev();
|
||||
|
||||
bool debugLogEnabled();
|
||||
|
||||
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);
|
||||
String replaceAmbiguousChars(String input);
|
||||
const char* getFirmwareFilename();
|
||||
const char* getWebUiFilename();
|
||||
// void loadIcons();
|
||||
|
||||
extern Preferences preferences;
|
||||
extern MCP23017 mcp1;
|
||||
#ifdef IS_BTCLOCK_V8
|
||||
extern MCP23017 mcp2;
|
||||
#endif
|
||||
|
||||
#ifdef HAS_FRONTLIGHT
|
||||
extern PCA9685 flArray;
|
||||
#endif
|
||||
|
||||
// Expose DataSourceType enum
|
||||
extern DataSourceType getDataSource();
|
||||
extern void setDataSource(DataSourceType source);
|
||||
#define NTP_SERVER "pool.ntp.org"
|
||||
#define DEFAULT_TIME_OFFSET_SECONDS 3600
|
6
src/lib/globals.hpp
Normal file
6
src/lib/globals.hpp
Normal file
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "price_notify/price_notify.hpp"
|
||||
|
||||
// Global instances
|
||||
extern PriceNotify::PriceNotifyManager priceManager;
|
|
@ -1,5 +1,6 @@
|
|||
#include "nostr_notify.hpp"
|
||||
#include "led_handler.hpp"
|
||||
#include "globals.hpp"
|
||||
|
||||
std::vector<nostr::NostrPool *> pools;
|
||||
nostr::Transport *transport;
|
||||
|
@ -171,7 +172,7 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even
|
|||
// Process the data
|
||||
if (!typeValue.isEmpty()) {
|
||||
if (typeValue == "priceUsd") {
|
||||
processNewPrice(obj["content"].as<uint>(), CURRENCY_USD);
|
||||
priceManager.processNewPrice(obj["content"].as<uint>(), static_cast<PriceNotify::Currency>(CURRENCY_USD));
|
||||
}
|
||||
else if (typeValue == "blockHeight") {
|
||||
processNewBlock(obj["content"].as<uint>());
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
#include "NostrEvent.h"
|
||||
#include "NostrPool.h"
|
||||
|
||||
#include "price_notify.hpp"
|
||||
#include "block_notify.hpp"
|
||||
#include "price_notify/price_notify.hpp"
|
||||
#include "lib/timers.hpp"
|
||||
|
||||
void setupNostrNotify(bool asDatasource, bool zapNotify);
|
||||
|
|
|
@ -1,175 +0,0 @@
|
|||
#include "price_notify.hpp"
|
||||
|
||||
const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin";
|
||||
|
||||
WebSocketsClient webSocket;
|
||||
uint currentPrice = 90000;
|
||||
unsigned long int lastPriceUpdate;
|
||||
bool priceNotifyInit = false;
|
||||
std::map<char, std::uint64_t> currencyMap;
|
||||
std::map<char, unsigned long int> lastUpdateMap;
|
||||
TaskHandle_t priceNotifyTaskHandle;
|
||||
|
||||
void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length);
|
||||
|
||||
void setupPriceNotify()
|
||||
{
|
||||
webSocket.beginSSL("ws.coincap.io", 443, "/prices?assets=bitcoin");
|
||||
webSocket.onEvent([](WStype_t type, uint8_t * payload, size_t length) {
|
||||
onWebsocketPriceEvent(type, payload, length);
|
||||
});
|
||||
webSocket.setReconnectInterval(5000);
|
||||
webSocket.enableHeartbeat(15000, 3000, 2);
|
||||
|
||||
setupPriceNotifyTask();
|
||||
}
|
||||
|
||||
void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) {
|
||||
switch(type) {
|
||||
case WStype_DISCONNECTED:
|
||||
Serial.println(F("Price WS Connection Closed"));
|
||||
break;
|
||||
case WStype_CONNECTED:
|
||||
{
|
||||
Serial.println("Connected to " + String(wsServerPrice));
|
||||
priceNotifyInit = true;
|
||||
break;
|
||||
}
|
||||
case WStype_TEXT:
|
||||
{
|
||||
JsonDocument doc;
|
||||
deserializeJson(doc, (char *)payload);
|
||||
|
||||
if (doc["bitcoin"].is<JsonObject>())
|
||||
{
|
||||
if (currentPrice != doc["bitcoin"].as<long>())
|
||||
{
|
||||
processNewPrice(doc["bitcoin"].as<long>(), CURRENCY_USD);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WStype_BIN:
|
||||
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 processNewPrice(uint newPrice, char currency)
|
||||
{
|
||||
uint minSecPriceUpd = preferences.getUInt(
|
||||
"minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE);
|
||||
uint currentTime = esp_timer_get_time() / 1000000;
|
||||
|
||||
if (lastUpdateMap.find(currency) == lastUpdateMap.end() ||
|
||||
(currentTime - lastUpdateMap[currency]) > minSecPriceUpd)
|
||||
{
|
||||
currencyMap[currency] = newPrice;
|
||||
|
||||
// Store price in preferences if enough time has passed
|
||||
if (lastUpdateMap[currency] == 0 || (currentTime - lastUpdateMap[currency]) > 120)
|
||||
{
|
||||
String prefKey = String("lastPrice_") + getCurrencyCode(currency).c_str();
|
||||
preferences.putUInt(prefKey.c_str(), newPrice);
|
||||
}
|
||||
|
||||
lastUpdateMap[currency] = currentTime;
|
||||
|
||||
if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_BTC_TICKER ||
|
||||
ScreenHandler::getCurrentScreen() == SCREEN_SATS_PER_CURRENCY ||
|
||||
ScreenHandler::getCurrentScreen() == SCREEN_MARKET_CAP))
|
||||
{
|
||||
WorkItem priceUpdate = {TASK_PRICE_UPDATE, currency};
|
||||
xQueueSend(workQueue, &priceUpdate, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadStoredPrices()
|
||||
{
|
||||
// Load prices for all supported currencies
|
||||
std::vector<std::string> currencies = getAvailableCurrencies();
|
||||
|
||||
for (const std::string ¤cy : currencies) {
|
||||
// Get first character as the currency identifier
|
||||
String prefKey = String("lastPrice_") + currency.c_str();
|
||||
uint storedPrice = preferences.getUInt(prefKey.c_str(), 0);
|
||||
|
||||
if (storedPrice > 0) {
|
||||
currencyMap[getCurrencyChar(currency)] = storedPrice;
|
||||
// Initialize lastUpdateMap to 0 so next update will store immediately
|
||||
lastUpdateMap[getCurrencyChar(currency)] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint getLastPriceUpdate(char currency)
|
||||
{
|
||||
if (lastUpdateMap.find(currency) == lastUpdateMap.end())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return lastUpdateMap[currency];
|
||||
}
|
||||
|
||||
uint getPrice(char currency)
|
||||
{
|
||||
if (currencyMap.find(currency) == currencyMap.end())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return currencyMap[currency];
|
||||
}
|
||||
|
||||
void setPrice(uint newPrice, char currency)
|
||||
{
|
||||
currencyMap[currency] = newPrice;
|
||||
}
|
||||
|
||||
bool isPriceNotifyConnected()
|
||||
{
|
||||
return webSocket.isConnected();
|
||||
}
|
||||
|
||||
bool getPriceNotifyInit()
|
||||
{
|
||||
return priceNotifyInit;
|
||||
}
|
||||
|
||||
void stopPriceNotify()
|
||||
{
|
||||
webSocket.disconnect();
|
||||
if (priceNotifyTaskHandle != NULL) {
|
||||
vTaskDelete(priceNotifyTaskHandle);
|
||||
priceNotifyTaskHandle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void restartPriceNotify()
|
||||
{
|
||||
stopPriceNotify();
|
||||
setupPriceNotify();
|
||||
}
|
||||
|
||||
void taskPriceNotify(void *pvParameters)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
webSocket.loop();
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void setupPriceNotifyTask()
|
||||
{
|
||||
xTaskCreate(taskPriceNotify, "priceNotify", (6 * 1024), NULL, tskIDLE_PRIORITY,
|
||||
&priceNotifyTaskHandle);
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <WebSocketsClient.h>
|
||||
#include <string>
|
||||
|
||||
#include "lib/screen_handler.hpp"
|
||||
|
||||
extern TaskHandle_t priceNotifyTaskHandle;
|
||||
|
||||
void setupPriceNotify();
|
||||
void setupPriceNotifyTask();
|
||||
void taskPriceNotify(void *pvParameters);
|
||||
|
||||
void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length);
|
||||
|
||||
uint getPrice(char currency);
|
||||
void setPrice(uint newPrice, char currency);
|
||||
|
||||
void processNewPrice(uint newPrice, char currency);
|
||||
|
||||
bool isPriceNotifyConnected();
|
||||
void stopPriceNotify();
|
||||
void restartPriceNotify();
|
||||
|
||||
bool getPriceNotifyInit();
|
||||
uint getLastPriceUpdate(char currency);
|
||||
void loadStoredPrices();
|
53
src/lib/price_notify/interfaces/price_source.hpp
Normal file
53
src/lib/price_notify/interfaces/price_source.hpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <WebSocketsClient.h>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace PriceNotify {
|
||||
|
||||
using std::function;
|
||||
using std::map;
|
||||
using std::string;
|
||||
|
||||
// Convert char-based currency to enum for type safety
|
||||
enum class Currency : char {
|
||||
USD = '$',
|
||||
EUR = '[',
|
||||
GBP = ']',
|
||||
JPY = '^',
|
||||
AUD = '_',
|
||||
CAD = '`'
|
||||
};
|
||||
|
||||
class IPriceSource {
|
||||
public:
|
||||
virtual ~IPriceSource() = default;
|
||||
|
||||
// Initialize and connect to the websocket
|
||||
virtual void connect() = 0;
|
||||
|
||||
// Disconnect and cleanup
|
||||
virtual void disconnect() = 0;
|
||||
|
||||
// Check connection status
|
||||
virtual bool isConnected() const = 0;
|
||||
|
||||
// Get the last known price for a currency
|
||||
virtual uint64_t getPrice(Currency currency) const = 0;
|
||||
|
||||
// Get the last update timestamp for a currency
|
||||
virtual uint32_t getLastUpdate(Currency currency) const = 0;
|
||||
|
||||
// Set callback for price updates
|
||||
virtual void onPriceUpdate(function<void(Currency, uint64_t)> callback) = 0;
|
||||
|
||||
// Process websocket loop - should be called regularly
|
||||
virtual void loop() = 0;
|
||||
|
||||
protected:
|
||||
function<void(Currency, uint64_t)> priceUpdateCallback;
|
||||
};
|
||||
} // namespace PriceNotify
|
115
src/lib/price_notify/price_notify.cpp
Normal file
115
src/lib/price_notify/price_notify.cpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
#include "price_notify.hpp"
|
||||
#include <esp_timer.h>
|
||||
|
||||
using std::make_unique;
|
||||
|
||||
namespace PriceNotify {
|
||||
|
||||
PriceNotifyManager::PriceNotifyManager() : currentSource(PriceSource::NONE) {}
|
||||
|
||||
PriceNotifyManager::~PriceNotifyManager() {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
void PriceNotifyManager::init(PriceSource source) {
|
||||
currentSource = source;
|
||||
setPriceSource(source);
|
||||
}
|
||||
|
||||
void PriceNotifyManager::connect() {
|
||||
if (priceSource) {
|
||||
priceSource->connect();
|
||||
}
|
||||
}
|
||||
|
||||
void PriceNotifyManager::disconnect() {
|
||||
if (priceSource) {
|
||||
priceSource->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
bool PriceNotifyManager::isConnected() const {
|
||||
return priceSource ? priceSource->isConnected() : false;
|
||||
}
|
||||
|
||||
uint64_t PriceNotifyManager::getPrice(Currency currency) const {
|
||||
if (priceSource) {
|
||||
return priceSource->getPrice(currency);
|
||||
}
|
||||
// If no price source, return from internal storage
|
||||
auto it = prices.find(currency);
|
||||
return (it != prices.end()) ? it->second : 0;
|
||||
}
|
||||
|
||||
uint32_t PriceNotifyManager::getLastUpdate(Currency currency) const {
|
||||
if (priceSource) {
|
||||
return priceSource->getLastUpdate(currency);
|
||||
}
|
||||
// If no price source, return from internal storage
|
||||
auto it = lastUpdates.find(currency);
|
||||
return (it != lastUpdates.end()) ? it->second : 0;
|
||||
}
|
||||
|
||||
void PriceNotifyManager::onPriceUpdate(function<void(Currency, uint64_t)> callback) {
|
||||
userCallback = callback;
|
||||
if (priceSource) {
|
||||
priceSource->onPriceUpdate([this](Currency currency, uint64_t price) {
|
||||
if (userCallback) {
|
||||
userCallback(currency, price);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void PriceNotifyManager::loop() {
|
||||
if (priceSource) {
|
||||
priceSource->loop();
|
||||
}
|
||||
}
|
||||
|
||||
void PriceNotifyManager::setPriceSource(PriceSource source) {
|
||||
// Store the callback before destroying the old source
|
||||
auto oldCallback = userCallback;
|
||||
|
||||
// Disconnect and destroy old source
|
||||
if (priceSource) {
|
||||
priceSource->disconnect();
|
||||
}
|
||||
|
||||
// Create new source
|
||||
switch (source) {
|
||||
case PriceSource::COINCAP:
|
||||
priceSource = make_unique<CoinCapSource>();
|
||||
break;
|
||||
case PriceSource::KRAKEN:
|
||||
priceSource = make_unique<KrakenSource>();
|
||||
break;
|
||||
case PriceSource::NONE:
|
||||
priceSource.reset();
|
||||
break;
|
||||
}
|
||||
|
||||
currentSource = source;
|
||||
|
||||
// Restore callback if it exists
|
||||
if (oldCallback) {
|
||||
onPriceUpdate(oldCallback);
|
||||
}
|
||||
}
|
||||
|
||||
PriceSource PriceNotifyManager::getCurrentSource() const {
|
||||
return currentSource;
|
||||
}
|
||||
|
||||
void PriceNotifyManager::processNewPrice(uint64_t price, Currency currency) {
|
||||
// Store the price and timestamp
|
||||
prices[currency] = price;
|
||||
lastUpdates[currency] = esp_timer_get_time() / 1000000; // Current time in seconds
|
||||
|
||||
// If we have a callback, notify about the price update
|
||||
if (userCallback) {
|
||||
userCallback(currency, price);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace PriceNotify
|
68
src/lib/price_notify/price_notify.hpp
Normal file
68
src/lib/price_notify/price_notify.hpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
#pragma once
|
||||
|
||||
#include "interfaces/price_source.hpp"
|
||||
#include "sources/coincap_source.hpp"
|
||||
#include "sources/kraken_source.hpp"
|
||||
#include <memory>
|
||||
#include <map>
|
||||
|
||||
namespace PriceNotify {
|
||||
|
||||
using std::unique_ptr;
|
||||
using std::function;
|
||||
using std::map;
|
||||
|
||||
enum class PriceSource {
|
||||
NONE, // Added for when no explicit source is set
|
||||
COINCAP,
|
||||
KRAKEN
|
||||
};
|
||||
|
||||
class PriceNotifyManager {
|
||||
public:
|
||||
PriceNotifyManager();
|
||||
~PriceNotifyManager();
|
||||
|
||||
// Initialize with a specific price source
|
||||
void init(PriceSource source);
|
||||
|
||||
// Connect to the price source
|
||||
void connect();
|
||||
|
||||
// Disconnect from the price source
|
||||
void disconnect();
|
||||
|
||||
// Check if connected to price source
|
||||
bool isConnected() const;
|
||||
|
||||
// Get the last known price for a currency
|
||||
uint64_t getPrice(Currency currency) const;
|
||||
|
||||
// Get the last update timestamp for a currency
|
||||
uint32_t getLastUpdate(Currency currency) const;
|
||||
|
||||
// Set callback for price updates
|
||||
void onPriceUpdate(function<void(Currency, uint64_t)> callback);
|
||||
|
||||
// Process websocket loop - should be called regularly
|
||||
void loop();
|
||||
|
||||
// Change the price source
|
||||
void setPriceSource(PriceSource source);
|
||||
|
||||
// Get current price source
|
||||
PriceSource getCurrentSource() const;
|
||||
|
||||
// Process new price from external source
|
||||
void processNewPrice(uint64_t price, Currency currency);
|
||||
|
||||
private:
|
||||
unique_ptr<IPriceSource> priceSource;
|
||||
PriceSource currentSource;
|
||||
function<void(Currency, uint64_t)> userCallback;
|
||||
|
||||
// For storing prices when no explicit source is set
|
||||
map<Currency, uint64_t> prices;
|
||||
map<Currency, uint32_t> lastUpdates;
|
||||
};
|
||||
} // namespace PriceNotify
|
96
src/lib/price_notify/sources/coincap_source.cpp
Normal file
96
src/lib/price_notify/sources/coincap_source.cpp
Normal file
|
@ -0,0 +1,96 @@
|
|||
#include "coincap_source.hpp"
|
||||
|
||||
namespace PriceNotify {
|
||||
|
||||
CoinCapSource* CoinCapSource::instance = nullptr;
|
||||
|
||||
CoinCapSource::CoinCapSource() : connected(false) {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
CoinCapSource::~CoinCapSource() {
|
||||
disconnect();
|
||||
instance = nullptr;
|
||||
}
|
||||
|
||||
void CoinCapSource::connect() {
|
||||
webSocket.beginSSL("ws.coincap.io", 443, "/prices?assets=bitcoin");
|
||||
webSocket.onEvent([](WStype_t type, uint8_t* payload, size_t length) {
|
||||
if (instance) {
|
||||
instance->handleWebSocketEvent(type, payload, length);
|
||||
}
|
||||
});
|
||||
webSocket.setReconnectInterval(5000);
|
||||
webSocket.enableHeartbeat(15000, 3000, 2);
|
||||
}
|
||||
|
||||
void CoinCapSource::disconnect() {
|
||||
webSocket.disconnect();
|
||||
connected = false;
|
||||
}
|
||||
|
||||
bool CoinCapSource::isConnected() const {
|
||||
return connected && webSocket.isConnected();
|
||||
}
|
||||
|
||||
uint64_t CoinCapSource::getPrice(Currency currency) const {
|
||||
auto it = prices.find(currency);
|
||||
return (it != prices.end()) ? it->second : 0;
|
||||
}
|
||||
|
||||
uint32_t CoinCapSource::getLastUpdate(Currency currency) const {
|
||||
auto it = lastUpdates.find(currency);
|
||||
return (it != lastUpdates.end()) ? it->second : 0;
|
||||
}
|
||||
|
||||
void CoinCapSource::onPriceUpdate(function<void(Currency, uint64_t)> callback) {
|
||||
priceUpdateCallback = callback;
|
||||
}
|
||||
|
||||
void CoinCapSource::loop() {
|
||||
webSocket.loop();
|
||||
}
|
||||
|
||||
void CoinCapSource::handleWebSocketEvent(WStype_t type, uint8_t* payload, size_t length) {
|
||||
switch(type) {
|
||||
case WStype_DISCONNECTED:
|
||||
Serial.println(F("CoinCap WS Disconnected"));
|
||||
connected = false;
|
||||
break;
|
||||
|
||||
case WStype_CONNECTED:
|
||||
Serial.println(F("CoinCap WS Connected"));
|
||||
connected = true;
|
||||
break;
|
||||
|
||||
case WStype_TEXT:
|
||||
processMessage((char*)payload);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CoinCapSource::processMessage(const char* payload) {
|
||||
StaticJsonDocument<512> doc;
|
||||
DeserializationError error = deserializeJson(doc, payload);
|
||||
|
||||
if (error) {
|
||||
Serial.println(F("Failed to parse CoinCap message"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (doc["bitcoin"].is<JsonObject>()) {
|
||||
uint64_t price = doc["bitcoin"].as<uint64_t>();
|
||||
uint32_t timestamp = esp_timer_get_time() / 1000000; // Current time in seconds
|
||||
|
||||
prices[Currency::USD] = price;
|
||||
lastUpdates[Currency::USD] = timestamp;
|
||||
|
||||
if (priceUpdateCallback) {
|
||||
priceUpdateCallback(Currency::USD, price);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace PriceNotify
|
36
src/lib/price_notify/sources/coincap_source.hpp
Normal file
36
src/lib/price_notify/sources/coincap_source.hpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include "../interfaces/price_source.hpp"
|
||||
#include <ArduinoJson.h>
|
||||
#include <esp_timer.h>
|
||||
|
||||
namespace PriceNotify {
|
||||
|
||||
using std::map;
|
||||
|
||||
class CoinCapSource : public IPriceSource {
|
||||
public:
|
||||
CoinCapSource();
|
||||
~CoinCapSource() override;
|
||||
|
||||
void connect() override;
|
||||
void disconnect() override;
|
||||
bool isConnected() const override;
|
||||
uint64_t getPrice(Currency currency) const override;
|
||||
uint32_t getLastUpdate(Currency currency) const override;
|
||||
void onPriceUpdate(function<void(Currency, uint64_t)> callback) override;
|
||||
void loop() override;
|
||||
|
||||
private:
|
||||
static void onWebSocketEvent(WStype_t type, uint8_t* payload, size_t length);
|
||||
void handleWebSocketEvent(WStype_t type, uint8_t* payload, size_t length);
|
||||
void processMessage(const char* payload);
|
||||
|
||||
WebSocketsClient webSocket;
|
||||
map<Currency, uint64_t> prices;
|
||||
map<Currency, uint32_t> lastUpdates;
|
||||
bool connected;
|
||||
|
||||
static CoinCapSource* instance; // For callback handling
|
||||
};
|
||||
} // namespace PriceNotify
|
118
src/lib/price_notify/sources/kraken_source.cpp
Normal file
118
src/lib/price_notify/sources/kraken_source.cpp
Normal file
|
@ -0,0 +1,118 @@
|
|||
#include "kraken_source.hpp"
|
||||
|
||||
namespace PriceNotify {
|
||||
|
||||
KrakenSource* KrakenSource::instance = nullptr;
|
||||
|
||||
KrakenSource::KrakenSource() : connected(false) {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
KrakenSource::~KrakenSource() {
|
||||
disconnect();
|
||||
instance = nullptr;
|
||||
}
|
||||
|
||||
void KrakenSource::connect() {
|
||||
webSocket.beginSSL("ws.kraken.com", 443, "/ws");
|
||||
webSocket.onEvent([](WStype_t type, uint8_t* payload, size_t length) {
|
||||
if (instance) {
|
||||
instance->handleWebSocketEvent(type, payload, length);
|
||||
}
|
||||
});
|
||||
webSocket.setReconnectInterval(5000);
|
||||
webSocket.enableHeartbeat(15000, 3000, 2);
|
||||
}
|
||||
|
||||
void KrakenSource::disconnect() {
|
||||
webSocket.disconnect();
|
||||
connected = false;
|
||||
}
|
||||
|
||||
bool KrakenSource::isConnected() const {
|
||||
return connected && webSocket.isConnected();
|
||||
}
|
||||
|
||||
uint64_t KrakenSource::getPrice(Currency currency) const {
|
||||
auto it = prices.find(currency);
|
||||
return (it != prices.end()) ? it->second : 0;
|
||||
}
|
||||
|
||||
uint32_t KrakenSource::getLastUpdate(Currency currency) const {
|
||||
auto it = lastUpdates.find(currency);
|
||||
return (it != lastUpdates.end()) ? it->second : 0;
|
||||
}
|
||||
|
||||
void KrakenSource::onPriceUpdate(function<void(Currency, uint64_t)> callback) {
|
||||
priceUpdateCallback = callback;
|
||||
}
|
||||
|
||||
void KrakenSource::loop() {
|
||||
webSocket.loop();
|
||||
}
|
||||
|
||||
void KrakenSource::handleWebSocketEvent(WStype_t type, uint8_t* payload, size_t length) {
|
||||
switch(type) {
|
||||
case WStype_DISCONNECTED:
|
||||
Serial.println(F("Kraken WS Disconnected"));
|
||||
connected = false;
|
||||
break;
|
||||
|
||||
case WStype_CONNECTED:
|
||||
Serial.println(F("Kraken WS Connected"));
|
||||
connected = true;
|
||||
subscribe();
|
||||
break;
|
||||
|
||||
case WStype_TEXT:
|
||||
processMessage((char*)payload);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void KrakenSource::subscribe() {
|
||||
// Subscribe to XBT/USD ticker
|
||||
StaticJsonDocument<256> doc;
|
||||
doc["event"] = "subscribe";
|
||||
JsonArray pair = doc.createNestedArray("pair");
|
||||
pair.add("XBT/USD");
|
||||
doc["subscription"]["name"] = "ticker";
|
||||
|
||||
String message;
|
||||
serializeJson(doc, message);
|
||||
webSocket.sendTXT(message);
|
||||
}
|
||||
|
||||
void KrakenSource::processMessage(const char* payload) {
|
||||
StaticJsonDocument<512> doc;
|
||||
DeserializationError error = deserializeJson(doc, payload);
|
||||
|
||||
if (error) {
|
||||
Serial.println(F("Failed to parse Kraken message"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if it's a ticker update (array format)
|
||||
if (doc.is<JsonArray>() && doc.size() >= 4) {
|
||||
JsonArray arr = doc.as<JsonArray>();
|
||||
if (arr[2] == "ticker" && arr[3] == "XBT/USD") {
|
||||
JsonObject tickerData = arr[1].as<JsonObject>();
|
||||
if (tickerData.containsKey("c")) {
|
||||
// Get the first value from the "c" array which is the last trade price
|
||||
uint64_t price = tickerData["c"][0].as<float>() * 100; // Convert to cents
|
||||
uint32_t timestamp = esp_timer_get_time() / 1000000; // Current time in seconds
|
||||
|
||||
prices[Currency::USD] = price;
|
||||
lastUpdates[Currency::USD] = timestamp;
|
||||
|
||||
if (priceUpdateCallback) {
|
||||
priceUpdateCallback(Currency::USD, price);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace PriceNotify
|
37
src/lib/price_notify/sources/kraken_source.hpp
Normal file
37
src/lib/price_notify/sources/kraken_source.hpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include "../interfaces/price_source.hpp"
|
||||
#include <ArduinoJson.h>
|
||||
#include <esp_timer.h>
|
||||
|
||||
namespace PriceNotify {
|
||||
|
||||
using std::map;
|
||||
|
||||
class KrakenSource : public IPriceSource {
|
||||
public:
|
||||
KrakenSource();
|
||||
~KrakenSource() override;
|
||||
|
||||
void connect() override;
|
||||
void disconnect() override;
|
||||
bool isConnected() const override;
|
||||
uint64_t getPrice(Currency currency) const override;
|
||||
uint32_t getLastUpdate(Currency currency) const override;
|
||||
void onPriceUpdate(function<void(Currency, uint64_t)> callback) override;
|
||||
void loop() override;
|
||||
|
||||
private:
|
||||
static void onWebSocketEvent(WStype_t type, uint8_t* payload, size_t length);
|
||||
void handleWebSocketEvent(WStype_t type, uint8_t* payload, size_t length);
|
||||
void processMessage(const char* payload);
|
||||
void subscribe();
|
||||
|
||||
WebSocketsClient webSocket;
|
||||
map<Currency, uint64_t> prices;
|
||||
map<Currency, uint32_t> lastUpdates;
|
||||
bool connected;
|
||||
|
||||
static KrakenSource* instance; // For callback handling
|
||||
};
|
||||
} // namespace PriceNotify
|
|
@ -241,7 +241,7 @@ void workerTask(void *pvParameters) {
|
|||
|
||||
case TASK_PRICE_UPDATE: {
|
||||
uint currency = ScreenHandler::getCurrentCurrency();
|
||||
uint price = getPrice(currency);
|
||||
uint price = priceManager.getPrice(static_cast<PriceNotify::Currency>(currency));
|
||||
|
||||
if (currentScreenValue == SCREEN_BTC_TICKER) {
|
||||
taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "v2_notify.hpp"
|
||||
#include "globals.hpp"
|
||||
|
||||
using namespace V2Notify;
|
||||
|
||||
|
@ -146,14 +147,15 @@ namespace V2Notify
|
|||
}
|
||||
else if (doc["price"].is<JsonObject>())
|
||||
{
|
||||
|
||||
// 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));
|
||||
// Convert currency string to PriceNotify::Currency using data_handler's conversion
|
||||
char currencyChar = getCurrencyChar(currency);
|
||||
priceManager.processNewPrice(newPrice, static_cast<PriceNotify::Currency>(currencyChar));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -246,7 +246,7 @@ JsonDocument getStatusObject()
|
|||
|
||||
JsonObject conStatus = root["connectionStatus"].to<JsonObject>();
|
||||
|
||||
conStatus["price"] = isPriceNotifyConnected();
|
||||
conStatus["price"] = priceManager.isConnected();
|
||||
conStatus["blocks"] = isBlockNotifyConnected();
|
||||
conStatus["V2"] = V2Notify::isV2NotifyConnected();
|
||||
conStatus["nostr"] = nostrConnected();
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
#include "lib/block_notify.hpp"
|
||||
#include "lib/led_handler.hpp"
|
||||
#include "lib/price_notify.hpp"
|
||||
|
||||
#include "lib/screen_handler.hpp"
|
||||
#include "webserver/OneParamRewrite.hpp"
|
||||
#include "lib/mining_pool/pool_factory.hpp"
|
||||
|
|
Loading…
Add table
Reference in a new issue