Websocket sources working

This commit is contained in:
Djuri 2023-11-07 01:11:12 +01:00
parent c2226f73e0
commit 562348b5ea
28 changed files with 32411 additions and 6 deletions

89
src/lib/block_notify.cpp Normal file
View file

@ -0,0 +1,89 @@
#include "block_notify.hpp"
const char *wsServer = "wss://mempool.space/api/v1/ws";
// WebsocketsClient client;
esp_websocket_client_handle_t client;
unsigned long int currentBlockHeight;
void setupBlockNotify()
{
// Get current block height through regular API
HTTPClient *http = new HTTPClient();
http->begin("https://mempool.space/api/blocks/tip/height");
int httpCode = http->GET();
if (httpCode > 0 && httpCode == HTTP_CODE_OK)
{
String blockHeightStr = http->getString();
currentBlockHeight = blockHeightStr.toInt();
}
esp_websocket_client_config_t config = {
.uri = "wss://mempool.space/api/v1/ws",
};
client = esp_websocket_client_init(&config);
esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, onWebsocketEvent, client);
esp_websocket_client_start(client);
}
void onWebsocketEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
String init;
String sub;
switch (event_id)
{
case WEBSOCKET_EVENT_CONNECTED:
Serial.println("Connected to Mempool.space WebSocket");
// init = "{\"action\": \"init\"}";
// if(esp_websocket_client_send_text(client, init.c_str(), init.length(), portMAX_DELAY) == -1) {
// Serial.println("Error init");
// }
sub = "{\"action\": \"want\", \"data\":[\"blocks\"]}";
if (esp_websocket_client_send_text(client, sub.c_str(), sub.length(), portMAX_DELAY) == -1)
{
Serial.println("Error want");
}
break;
case WEBSOCKET_EVENT_DATA:
onWebsocketMessage(data);
// Handle the received WebSocket message (block notifications) here
break;
case WEBSOCKET_EVENT_ERROR:
Serial.println("Connnection error");
break;
case WEBSOCKET_EVENT_DISCONNECTED:
Serial.println("Connnection Closed");
break;
}
}
void onWebsocketMessage(esp_websocket_event_data_t *event_data)
{
DynamicJsonDocument doc(event_data->data_len);
deserializeJson(doc, (char *)event_data->data_ptr);
// serializeJsonPretty(doc, Serial);
if (doc.containsKey("block"))
{
JsonObject block = doc["block"];
currentBlockHeight = block["height"].as<long>();
Serial.print("New block found: ");
Serial.println(block["height"].as<long>());
if (blockUpdateTaskHandle != nullptr)
xTaskNotifyGive(blockUpdateTaskHandle);
}
doc.clear();
}
unsigned long getBlockHeight()
{
return currentBlockHeight;
}

18
src/lib/block_notify.hpp Normal file
View file

@ -0,0 +1,18 @@
#pragma once
#include <string>
#include <Arduino.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include "esp_websocket_client.h"
#include "screen_handler.hpp"
//using namespace websockets;
void setupBlockNotify();
void onWebsocketEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data);
void onWebsocketMessage(esp_websocket_event_data_t* event_data);
unsigned long getBlockHeight();

23
src/lib/config.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once;
#include <WiFiClientSecure.h>
#include <Preferences.h>
#include <Adafruit_MCP23X17.h>
#include "shared.hpp"
#include <esp_system.h>
#include <esp_netif.h>
#include <esp_sntp.h>
#include "epd.hpp"
#include "lib/screen_handler.hpp"
#include "lib/webserver.hpp"
#include "lib/block_notify.hpp"
#include "lib/price_notify.hpp"
void setup();
void setupTime();
void setupPreferences();
void setupWifi();
void setupWebsocketClients();
void setupHardware();

267
src/lib/epd.cpp Normal file
View file

@ -0,0 +1,267 @@
#include "epd.hpp"
Native_Pin EPD_CS[NUM_SCREENS] = {
Native_Pin(2),
Native_Pin(4),
Native_Pin(6),
Native_Pin(10),
Native_Pin(33),
Native_Pin(21),
Native_Pin(17),
#if NUM_SCREENS == 9
// MCP23X17_Pin(mcp2, 7),
Native_Pin(-1),
Native_Pin(-1),
#endif
};
Native_Pin EPD_BUSY[NUM_SCREENS] = {
Native_Pin(3),
Native_Pin(5),
Native_Pin(7),
Native_Pin(9),
Native_Pin(37),
Native_Pin(18),
Native_Pin(16),
};
MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = {
MCP23X17_Pin(mcp, 8),
MCP23X17_Pin(mcp, 9),
MCP23X17_Pin(mcp, 10),
MCP23X17_Pin(mcp, 11),
MCP23X17_Pin(mcp, 12),
MCP23X17_Pin(mcp, 13),
MCP23X17_Pin(mcp, 14),
};
Native_Pin EPD_DC = Native_Pin(14);
GxEPD2_BW<GxEPD2_213_B74, GxEPD2_213_B74::HEIGHT> displays[NUM_SCREENS] = {
GxEPD2_213_B74(&EPD_CS[0], &EPD_DC, &EPD_RESET_MPD[0], &EPD_BUSY[0]),
GxEPD2_213_B74(&EPD_CS[1], &EPD_DC, &EPD_RESET_MPD[1], &EPD_BUSY[1]),
GxEPD2_213_B74(&EPD_CS[2], &EPD_DC, &EPD_RESET_MPD[2], &EPD_BUSY[2]),
GxEPD2_213_B74(&EPD_CS[3], &EPD_DC, &EPD_RESET_MPD[3], &EPD_BUSY[3]),
GxEPD2_213_B74(&EPD_CS[4], &EPD_DC, &EPD_RESET_MPD[4], &EPD_BUSY[4]),
GxEPD2_213_B74(&EPD_CS[5], &EPD_DC, &EPD_RESET_MPD[5], &EPD_BUSY[5]),
GxEPD2_213_B74(&EPD_CS[6], &EPD_DC, &EPD_RESET_MPD[6], &EPD_BUSY[6]),
#if NUM_SCREENS == 9
GxEPD2_213_B74(&EPD8_CS, &EPD_DC, &EPD_RESET_MPD[7], &EPD8_BUSY),
GxEPD2_213_B74(&EPD9_CS, &EPD_DC, &EPD_RESET_MPD[8], &EPD9_BUSY),
#endif
};
std::array<String, NUM_SCREENS> currentEpdContent;
std::array<String, NUM_SCREENS> epdContent;
uint32_t lastFullRefresh[NUM_SCREENS];
TaskHandle_t tasks[NUM_SCREENS];
SemaphoreHandle_t epdUpdateSemaphore[NUM_SCREENS];
int fgColor = GxEPD_WHITE;
int bgColor = GxEPD_BLACK;
#define FONT_SMALL Antonio_SemiBold20pt7b
#define FONT_BIG Antonio_SemiBold90pt7b
void setupDisplays()
{
for (uint i = 0; i < NUM_SCREENS; i++)
{
displays[i].init();
}
for (uint i = 0; i < NUM_SCREENS; i++)
{
epdUpdateSemaphore[i] = xSemaphoreCreateBinary();
xSemaphoreGive(epdUpdateSemaphore[i]);
int *taskParam = new int;
*taskParam = i;
xTaskCreate(updateDisplay, "EpdUpd" + char(i), 4096, taskParam, 1, &tasks[i]); // create task
}
epdContent = {"B",
"T",
"C",
"L",
"O",
"C",
"K"};
for (uint i = 0; i < NUM_SCREENS; i++)
{
xTaskNotifyGive(tasks[i]);
}
xTaskCreate(taskEpd, "epd_task", 2048, NULL, 1, NULL);
}
void taskEpd(void *pvParameters)
{
while (1)
{
bool updatedThisCycle = false;
for (uint i = 0; i < NUM_SCREENS; i++)
{
if (epdContent[i].compareTo(currentEpdContent[i]) != 0)
{
if (!updatedThisCycle)
{
updatedThisCycle = true;
}
if (xSemaphoreTake(epdUpdateSemaphore[i], pdMS_TO_TICKS(5000)) == pdTRUE)
{
xTaskNotifyGive(tasks[i]);
}
else
{
Serial.println("Couldnt get screen" + String(i));
}
}
}
#ifdef WITH_RGB_LED
if (updatedThisCycle && preferences.getBool("ledFlashOnUpd", false))
{
xTaskNotifyGive(ledHandlerTaskHandle);
}
#endif
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void setEpdContent(std::array<String, NUM_SCREENS> newEpdContent)
{
epdContent = newEpdContent;
}
extern "C" void updateDisplay(void *pvParameters) noexcept
{
const int epdIndex = *(int *)pvParameters;
delete (int *)pvParameters;
for (;;)
{
// Wait for the task notification
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (epdContent[epdIndex].compareTo(currentEpdContent[epdIndex]) != 0)
{
displays[epdIndex].init(0, false); // Little longer reset duration because of MCP
bool updatePartial = true;
// Full Refresh every half hour
if (!lastFullRefresh[epdIndex] || (millis() - lastFullRefresh[epdIndex]) > (preferences.getUInt("fullRefreshMin", 30) * 60 * 1000))
{
updatePartial = false;
lastFullRefresh[epdIndex] = millis();
}
if (strstr(epdContent[epdIndex].c_str(), "/") != NULL)
{
String top = epdContent[epdIndex].substring(0, epdContent[epdIndex].indexOf("/"));
String bottom = epdContent[epdIndex].substring(epdContent[epdIndex].indexOf("/") + 1);
splitText(epdIndex, top, bottom, updatePartial);
}
else
{
showDigit(epdIndex, epdContent[epdIndex].c_str()[0], updatePartial, &FONT_BIG);
}
#ifdef USE_UNIVERSAL_PIN
char tries = 0;
while (tries < 3)
{
if (displays[epdIndex].displayWithReturn(updatePartial))
{
displays[epdIndex].hibernate();
currentEpdContent[epdIndex] = epdContent[epdIndex];
break;
}
delay(100);
tries++;
}
#else
displays[epdIndex].display(updatePartial);
displays[epdIndex].hibernate();
currentEpdContent[epdIndex] = epdContent[epdIndex];
#endif
}
xSemaphoreGive(epdUpdateSemaphore[epdIndex]);
}
}
void splitText(const uint dispNum, String top, String bottom, bool partial)
{
displays[dispNum].setRotation(2);
displays[dispNum].setFont(&FONT_SMALL);
displays[dispNum].setTextColor(getFgColor());
// Top text
int16_t ttbx, ttby;
uint16_t ttbw, ttbh;
displays[dispNum].getTextBounds(top, 0, 0, &ttbx, &ttby, &ttbw, &ttbh);
uint16_t tx = ((displays[dispNum].width() - ttbw) / 2) - ttbx;
uint16_t ty = ((displays[dispNum].height() - ttbh) / 2) - ttby - ttbh / 2 - 12;
// Bottom text
int16_t tbbx, tbby;
uint16_t tbbw, tbbh;
displays[dispNum].getTextBounds(bottom, 0, 0, &tbbx, &tbby, &tbbw, &tbbh);
uint16_t bx = ((displays[dispNum].width() - tbbw) / 2) - tbbx;
uint16_t by = ((displays[dispNum].height() - tbbh) / 2) - tbby + tbbh / 2 + 12;
// Make separator as wide as the shortest text.
uint16_t lineWidth, lineX;
if (tbbw < ttbh)
lineWidth = tbbw;
else
lineWidth = ttbw;
lineX = round((displays[dispNum].width() - lineWidth) / 2);
displays[dispNum].fillScreen(getBgColor());
displays[dispNum].setCursor(tx, ty);
displays[dispNum].print(top);
displays[dispNum].fillRoundRect(lineX, displays[dispNum].height() / 2 - 3, lineWidth, 6, 3, getFgColor());
displays[dispNum].setCursor(bx, by);
displays[dispNum].print(bottom);
}
void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font)
{
String str(chr);
displays[dispNum].setRotation(2);
displays[dispNum].setFont(font);
displays[dispNum].setTextColor(getFgColor());
int16_t tbx, tby;
uint16_t tbw, tbh;
displays[dispNum].getTextBounds(str, 0, 0, &tbx, &tby, &tbw, &tbh);
// center the bounding box by transposition of the origin:
uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx;
uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby;
displays[dispNum].fillScreen(getBgColor());
displays[dispNum].setCursor(x, y);
displays[dispNum].print(str);
}
int getBgColor()
{
return bgColor;
}
int getFgColor()
{
return fgColor;
}
void setBgColor(int color)
{
bgColor = color;
}
void setFgColor(int color)
{
fgColor = color;
}

22
src/lib/epd.hpp Normal file
View file

@ -0,0 +1,22 @@
#pragma once
#include <GxEPD2_BW.h>
#include <native_pin.hpp>
#include <mcp23x17_pin.hpp>
#include "shared.hpp"
#include "config.hpp"
#include "fonts/fonts.hpp"
void setupDisplays();
void taskEpd(void *pvParameters);
void splitText(const uint dispNum, String top, String bottom, bool partial);
void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font);
extern "C" void updateDisplay(void *pvParameters) noexcept;
int getBgColor();
int getFgColor();
void setBgColor(int color);
void setFgColor(int color);
void setEpdContent(std::array<String, NUM_SCREENS> newEpdContent);

64
src/lib/price_notify.cpp Normal file
View file

@ -0,0 +1,64 @@
#include "price_notify.hpp"
const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin";
// WebsocketsClient client;
esp_websocket_client_handle_t clientPrice;
unsigned long int currentPrice;
void setupPriceNotify()
{
esp_websocket_client_config_t config = {
.uri = wsServerPrice,
};
clientPrice = esp_websocket_client_init(&config);
esp_websocket_register_events(clientPrice, WEBSOCKET_EVENT_ANY, onWebsocketPriceEvent, clientPrice);
esp_websocket_client_start(clientPrice);
}
void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
String init;
String sub;
switch (event_id)
{
case WEBSOCKET_EVENT_CONNECTED:
Serial.println("Connected to CoinCap.io WebSocket");
break;
case WEBSOCKET_EVENT_DATA:
onWebsocketPriceMessage(data);
break;
case WEBSOCKET_EVENT_ERROR:
Serial.println("Connnection error");
break;
case WEBSOCKET_EVENT_DISCONNECTED:
Serial.println("Connnection Closed");
break;
}
}
void onWebsocketPriceMessage(esp_websocket_event_data_t* event_data)
{
DynamicJsonDocument doc(event_data->data_len);
deserializeJson(doc, (char *)event_data->data_ptr);
if (doc.containsKey("bitcoin")) {
if (currentPrice != doc["bitcoin"].as<long>()) {
Serial.printf("New price %lu\r\n", currentPrice);
const unsigned long oldPrice = currentPrice;
currentPrice = doc["bitcoin"].as<long>();
// if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) {
if (priceUpdateTaskHandle != nullptr)
xTaskNotifyGive(priceUpdateTaskHandle);
//}
}
}
}
unsigned long getPrice() {
return currentPrice;
}

17
src/lib/price_notify.hpp Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include <string>
#include <Arduino.h>
#include <ArduinoJson.h>
#include "esp_websocket_client.h"
#include "screen_handler.hpp"
//using namespace websockets;
void setupPriceNotify();
void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data);
void onWebsocketPriceMessage(esp_websocket_event_data_t* event_data);
unsigned long getPrice();

View file

@ -0,0 +1,72 @@
#include "screen_handler.hpp"
TaskHandle_t priceUpdateTaskHandle;
TaskHandle_t blockUpdateTaskHandle;
std::array<String, NUM_SCREENS> taskEpdContent = {"", "", "", "", "", "", ""};
std::string priceString;
void taskPriceUpdate(void *pvParameters)
{
for (;;)
{
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
unsigned long price = getPrice();
uint firstIndex = 0;
if (false) {
priceString = ("$" + String(price)).c_str();
if (priceString.length() < (NUM_SCREENS)) {
priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' ');
taskEpdContent[0] = "BTC/USD";
firstIndex = 1;
}
} else {
priceString = String(int(round(1 / float(price) * 10e7))).c_str();
if (priceString.length() < (NUM_SCREENS)) {
priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' ');
taskEpdContent[0] = "MSCW/TIME";
firstIndex = 1;
}
}
for (uint i = firstIndex; i < NUM_SCREENS; i++)
{
taskEpdContent[i] = priceString[i];
}
setEpdContent(taskEpdContent);
}
}
void taskBlockUpdate(void *pvParameters)
{
for (;;)
{
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
std::string blockNrString = String(getBlockHeight()).c_str();
uint firstIndex = 0;
if (blockNrString.length() < NUM_SCREENS) {
blockNrString.insert(blockNrString.begin(), NUM_SCREENS - blockNrString.length(), ' ');
taskEpdContent[0] = "BLOCK/HEIGHT";
firstIndex = 1;
}
for (uint i = firstIndex; i < NUM_SCREENS; i++)
{
taskEpdContent[i] = blockNrString[i];
}
setEpdContent(taskEpdContent);
}
}
void setupTasks()
{
xTaskCreate(taskPriceUpdate, "updatePrice", 1024, NULL, 1, &priceUpdateTaskHandle);
xTaskCreate(taskBlockUpdate, "updateBlock", 1024, NULL, 1, &blockUpdateTaskHandle);
}

View file

@ -0,0 +1,16 @@
#pragma once
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "lib/block_notify.hpp"
#include "lib/price_notify.hpp"
#include "lib/epd.hpp"
extern TaskHandle_t priceUpdateTaskHandle;
extern TaskHandle_t blockUpdateTaskHandle;
void taskPriceUpdate(void *pvParameters);
void taskBlockUpdate(void *pvParameters);
void setupTasks();

7
src/lib/shared.hpp Normal file
View file

@ -0,0 +1,7 @@
#pragma once
#include <Adafruit_MCP23X17.h>
#include <Preferences.h>
extern Adafruit_MCP23X17 mcp;
extern Preferences preferences;

43
src/lib/webserver.cpp Normal file
View file

@ -0,0 +1,43 @@
#include "webserver.hpp"
AsyncWebServer server(80);
void setupWebserver()
{
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", "Hello, world");
});
server.on("/status", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncResponseStream *response = request->beginResponseStream("application/json");
StaticJsonDocument<128> root;
root["currentPrice"] = getPrice();
root["currentBlockHeight"] = getBlockHeight();
root["espFreeHeap"] = ESP.getFreeHeap();
root["espHeapSize"] = ESP.getHeapSize();
root["espFreePsram"] = ESP.getFreePsram();
root["espPsramSize"] = ESP.getPsramSize();
serializeJson(root, *response);
request->send(response);
});
server.onNotFound(onNotFound);
server.begin();
}
void onNotFound(AsyncWebServerRequest *request)
{
if (request->method() == HTTP_OPTIONS)
{
request->send(200);
}
else
{
request->send(404);
}
};

10
src/lib/webserver.hpp Normal file
View file

@ -0,0 +1,10 @@
#pragma once
#include "ESPAsyncWebServer.h"
#include <ArduinoJson.h>
#include "lib/block_notify.hpp"
#include "lib/price_notify.hpp"
void setupWebserver();
void onNotFound(AsyncWebServerRequest *request);