-
Notifications
You must be signed in to change notification settings - Fork 0
/
mempoolBuddy.sketch
368 lines (312 loc) · 11.3 KB
/
mempoolBuddy.sketch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
#include <Arduino.h>
#include <TFT_eSPI.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
// Define the TFT_eSPI object
TFT_eSPI tft = TFT_eSPI();
// Define the maximum number of API URLs that can be cached
#define MAX_URLS 10
// Define an array to store the API URLs and their cached JSON responses
String apiUrls[MAX_URLS];
String apiResponses[MAX_URLS];
// Define a function to retrieve data from an API URL and cache the JSON response
String getApiResponse(String apiUrl) {
// Check if the API URL is already cached
for (int i = 0; i < MAX_URLS; i++) {
if (apiUrls[i] == apiUrl) {
// Return the cached JSON response
return apiResponses[i];
}
}
// If the API URL is not cached, retrieve the JSON response
HTTPClient http;
http.begin(apiUrl);
int httpCode = http.GET();
String jsonResponse = "";
if (httpCode == HTTP_CODE_OK) {
jsonResponse = http.getString();
// Cache the API URL and JSON response
for (int i = 0; i < MAX_URLS; i++) {
if (apiUrls[i] == "") {
apiUrls[i] = apiUrl;
apiResponses[i] = jsonResponse;
break;
}
}
}
http.end();
// Return the JSON response
return jsonResponse;
}
// Define the maximum number of projected and completed blocks to display
int maxProjectedBlocks = 3;
int maxCompletedBlocks = 3;
// Define an array to store the projected blocks data
int projectedBlockCounts[500];
int projectedBlockFees[500];
int projectedBlockSizes[500];
int projectedBlockTxCounts[500];
int projectedBlockEstimates[500];
// Define an array to store the completed blocks data
int completedBlockHeights[500];
int completedBlockSatsVb[500];
int completedBlockSizes[500];
int completedBlockTxCounts[500];
String completedBlockTimestamps[500];
// Constants
const uint8_t kButton1Pin = 34;
const uint8_t kButton2Pin = 35;
const uint16_t kCubeAnimationDelay = 100;
const uint16_t kCubeAnimationSteps = 10;
const uint16_t kCubeWidth = 10;
const uint16_t kCubeDepth = 10;
const uint16_t kCubeHeight = 40;
const uint16_t kCubePadding = 2;
const uint16_t kDisplayWidth = 240;
const uint16_t kDisplayHeight = 135;
const uint16_t kDisplayPadding = 5;
const uint16_t kBorderColor = TFT_BLUE;
const uint16_t kBackgroundColor = TFT_NAVY;
const uint16_t kProjectedCubeGradientStartColor = TFT_GREEN;
const uint16_t kProjectedCubeGradientEndColor = TFT_RED;
const uint8_t kMaxBlocksToDisplay = 3;
const uint32_t kCacheDuration = 300000; // Cache duration in ms (5 minutes)
// Variables
char *apiUrls[] = {
"https://mempool.space/api/v1/fees/mempool-blocks",
"https://mempool.space/api/v1/blocks/"
};
char *apiResponses[2];
uint32_t cacheTimestamps[2] = {0, 0};
int16_t currentDisplay = 0;
int16_t previousDisplay = -1;
int16_t displayCount = 0;
DynamicJsonDocument projectedBlocksDoc(2048);
DynamicJsonDocument completedBlocksDoc(4096);
// Functions
void updateApiData(int16_t index) {
// Check if data for this index has been cached
if (apiCache[index].length() > 0) {
// If cached data exists, parse the JSON data from cache
parseApiData(apiCache[index], index);
} else {
// If no cached data exists, query the API and cache the JSON response
String apiData = getApiResponse(apiUrls[index]);
if (apiData.length() > 0) {
apiCache[index] = apiData;
parseApiData(apiData, index);
}
}
}
// Function to update the display
void updateDisplay() {
// Handle button press events
handleButtonPress();
// Check if enough time has passed since the last button press to update the display
if (millis() - lastButtonPressTime >= BUTTON_PRESS_UPDATE_INTERVAL) {
// Update the current display
switch (currentDisplay) {
case BLOCK_TRAIN_DISPLAY:
updateProjectedBlocksData();
updateCompletedBlocksData();
drawProjectedBlocks();
drawCompletedBlocks();
break;
case UNCONFIRMED_TRANSACTIONS_DISPLAY:
updateUnconfirmedTransactionsData();
drawUnconfirmedTransactions();
break;
case ESTIMATED_FEES_DISPLAY:
updateEstimatedFeesData();
drawEstimatedFees();
break;
}
}
}
void updateProjectedBlocksData() {
String api_url = api_urls[0];
String cached_json = getCache(api_url);
if (cached_json == "") {
String json = httpGETRequest(api_url);
if (json != "") {
setCache(api_url, json);
cached_json = json;
}
}
if (cached_json != "") {
DynamicJsonDocument doc(JSON_ARRAY_SIZE(500));
DeserializationError error = deserializeJson(doc, cached_json);
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.c_str());
return;
}
projected_blocks.clear();
for (int i = 0; i < min(doc.size(), (size_t)projected_blocks_count); i++) {
JsonObject block = doc[i];
uint16_t median_fee = block["median_fee"].as<uint16_t>();
uint32_t size = block["size"].as<uint32_t>();
uint32_t vsize = block["vsize"].as<uint32_t>();
uint32_t weight = block["weight"].as<uint32_t>();
uint16_t fee_range_percentile = block["fee_range_percentiles"]["10"].as<uint16_t>();
uint16_t tx_count = block["tx_count"].as<uint16_t>();
uint32_t eta = block["eta"].as<uint32_t>();
projected_blocks.push_back({median_fee, size, vsize, weight, fee_range_percentile, tx_count, eta});
}
}
}
// Draw a block cube face
void drawCubeFace(int x, int y, int z, int color, String text) {
tft.pushRotatedCanvas(x, y, z, 0, 0, 0, CUBE_SIZE, CUBE_SIZE, CUBE_SIZE, 0);
tft.fillScreen(color);
if (text != "") {
drawTextOnCube(0, 0, CUBE_SIZE, text);
}
tft.popCanvas();
}
void updateCompletedBlocksData() {
// check if cache is still valid
if (millis() - cacheCompletedBlocksTime < cacheTimeout) {
return;
}
// update cache time
cacheCompletedBlocksTime = millis();
// update the data
for (int i = 0; i < numCompletedBlocks; i++) {
// get the API data for the block at the specified index
String url = completedBlocksUrls[i] + "?limit=" + String(blockLimit);
String json = getCachedApiData(url, cacheCompletedBlocks[i]);
if (json == "") continue; // skip if unable to get data
// parse the JSON data
DynamicJsonDocument doc(2048);
deserializeJson(doc, json);
// update the block data
String height = doc[0]["height"].as<String>();
String satsVb = doc[0]["sats_vb"].as<String>();
String feeRange = doc[0]["fee_range"].as<String>();
String size = doc[0]["size"].as<String>();
String txCount = doc[0]["tx_count"].as<String>();
uint32_t timestamp = doc[0]["timestamp"].as<uint32_t>();
completedBlocksData[i].height = height;
completedBlocksData[i].satsVb = satsVb;
completedBlocksData[i].feeRange = feeRange;
completedBlocksData[i].size = size;
completedBlocksData[i].txCount = txCount;
completedBlocksData[i].timestamp = timestamp;
}
}
void drawProjectedBlockCube(uint8_t index, JsonObject blockData) {
// Calculate cube dimensions and position
uint16_t x = 5 + index * (CUBE_SIZE + CUBE_SPACING);
uint16_t y = 35;
uint16_t z = 0;
uint16_t width = CUBE_SIZE;
uint16_t height = CUBE_SIZE;
uint16_t depth = CUBE_SIZE;
// Calculate color gradient based on median fee
float medianFee = blockData["medianFee"].as<float>();
uint16_t color = calculateColorGradient(medianFee);
// Draw cube
tft.drawGradientBox(x, y, z, width, height, depth, color, color, GRADIENT_VERTICAL);
// Draw cube face text
String feeRangeText = "Fee Range: " + String(blockData["minFee"].as<float>(), 0) + " - " + String(blockData["maxFee"].as<float>(), 0) + " sats/vB";
drawTextOnCube(x, y, z, width, height, depth, feeRangeText);
String blockSizeText = "Block Size: " + String(blockData["maxWeight"].as<float>() / 1000, 2) + " KB";
drawTextOnCube(x, y, z, width, height, depth, blockSizeText, 1);
String txCountText = "Tx Count: " + String(blockData["count"].as<uint32_t>());
drawTextOnCube(x, y, z, width, height, depth, txCountText, 2);
float timeEstimate = blockData["time"].as<float>();
String timeEstimateText = "ETA: " + String(timeEstimate, 0) + " min";
drawTextOnCube(x, y, z, width, height, depth, timeEstimateText, 3);
// Animate cube if it's the first cube
if (index == 0) {
animateCube(index, x, y, z, width, height, depth, color);
}
}
void drawCompletedBlockCube(uint8_t index, JsonObject blockData) {
// check if blockData is already cached
if (completedBlockCache[index].is<JsonObject>()) {
blockData = completedBlockCache[index].as<JsonObject>();
} else {
// cache the blockData for future use
completedBlockCache[index] = blockData;
}
// get the color of the block
uint16_t color = blockData["color"];
// get the position and dimensions of the block
uint16_t x = blockData["x"];
uint16_t y = blockData["y"];
uint16_t z = blockData["z"];
uint16_t width = blockData["width"];
uint16_t height = blockData["height"];
uint16_t depth = blockData["depth"];
// draw the block
animateCube(index, x, y, z, width, height, depth, color);
}
void drawTextOnCube(uint16_t x, uint16_t y, uint16_t z, uint16_t width, uint16_t height, uint16_t depth, const char *text) {
uint8_t charIndex = 0;
uint8_t charOffset = 0;
while (text[charIndex]) {
charOffset = drawCharOnCube(x + (charIndex * (FONT_WIDTH + 1)), y, z, width, height, depth, text[charIndex], true);
charIndex++;
}
}
void animateCube(uint8_t index, uint16_t x, uint16_t y, uint16_t z, uint16_t width, uint16_t height, uint16_t depth, uint16_t color) {
for (int i = 0; i < 4; i++) {
drawCube(index, x, y, z, width, height, depth, color);
delay(100);
drawCube(index, x, y, z, width, height, depth, 0);
delay(100);
}
}
// Function to handle button press events
void handleButtonPress() {
// Check if button 1 was pressed
if (digitalRead(BUTTON_1_PIN) == LOW) {
// Go to the next display
currentDisplay = (currentDisplay + 1) % DISPLAY_COUNT;
lastButtonPressTime = millis();
}
// Check if button 2 was pressed
if (digitalRead(BUTTON_2_PIN) == LOW) {
// Go to the previous display
currentDisplay = (currentDisplay + DISPLAY_COUNT - 1) % DISPLAY_COUNT;
lastButtonPressTime = millis();
}
}
void setup() {
// Initialize serial communication
Serial.begin(115200);
// Initialize the TFT screen
tft.init();
tft.setRotation(1);
tft.fillScreen(kBackgroundColor);
// Initialize the button pins as inputs
pinMode(kButton1Pin, INPUT_PULLUP);
pinMode(kButton2Pin, INPUT_PULLUP);
// Load initial API data
updateApiData(currentDisplay);
// Draw the border around the screen
tft.drawRect(0, 0, kDisplayWidth + (2 * kDisplayPadding), kDisplayHeight + (2 * kDisplayPadding), kBorderColor);
}
void loop() {
// Check if any button is pressed and handle the press
handleButtonPress();
// Update the completed blocks data every 5 minutes
if (millis() - lastCompletedBlocksUpdate >= COMPLETED_BLOCKS_UPDATE_INTERVAL) {
updateCompletedBlocksData();
lastCompletedBlocksUpdate = millis();
}
// Update the projected blocks data every 30 seconds
if (millis() - lastProjectedBlocksUpdate >= PROJECTED_BLOCKS_UPDATE_INTERVAL) {
updateProjectedBlocksData();
lastProjectedBlocksUpdate = millis();
}
// Animate the projected blocks
animateProjectedBlocks();
// Draw the completed blocks
drawCompletedBlocks();
// Draw the projected blocks
drawProjectedBlocks();
}