Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,27 @@ With the USB-C port to the right:

[![Ver video aquí](https://img.youtube.com/vi/POUT2R_opDs/0.jpg)](https://youtu.be/POUT2R_opDs)

## How the Price History Graph Works
The NerdMiner v2 displays Bitcoin price history graphs over different time periods. The process for generating these graphs is divided into two main steps: data collection and display visualization.

### 1. Data Collection and Storage

- **Frequency:** Every minute, the device queries an API to get the current Bitcoin price.
- **Initial Storage:** This price is stored in an array that holds the data for the last 300 minutes (5 hours). This is the highest resolution graph.
- **Data Consolidation:** To generate graphs for longer periods (daily, weekly, and monthly), the system performs a cascading consolidation process:
- **Daily Graph (last 25 hours):** Every 5 minutes, the average of the last 5 1-minute data points is calculated and stored.
- **Weekly Graph (last 6.25 days):** Every 30 minutes, the average of the last 6 5-minute data points is calculated and stored.
- **Monthly Graph (last 25 days):** Every 2 hours, the average of the last 4 30-minute data points is calculated and stored.
- **Circular Buffers:** All data is stored in "circular buffers" of 300 points, where the oldest data is replaced by the newest, keeping memory usage constant.

### 2. Visualization on the Display

- **Rendering:** The graph screen is updated to display the different periods, cycling through them every 20 seconds.
- **Graph Scaling:** Before drawing, the system analyzes the 300 data points of the selected period to find the minimum and maximum price. This allows the graph to be "normalized," meaning it is adjusted to occupy the full available height on the screen, making the most of the space.
- **Line Drawing:** With the scale defined, the system draws the graph point by point, connecting each price value with a line to form the visual representation of the history.

This process ensures that the NerdMiner v2 can display both short-term price fluctuations and long-term trends, all with efficient use of the device's memory.

## Developers

### Project guidelines
Expand Down
208 changes: 189 additions & 19 deletions src/drivers/displays/esp23_2432s028r.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ extern bool invertColors;
extern TSettings Settings;
bool hasChangedScreen = true;

int priceHistoryGraphColors[4] = { TFT_PURPLE, TFT_RED, TFT_BLUE, TFT_BLACK };

void getChipInfo(void){
Serial.print("Chip: ");
Serial.println(ESP.getChipModel());
Expand Down Expand Up @@ -221,13 +223,18 @@ void esp32_2432S028R_MinerScreen(unsigned long mElapsed)

int wdtOffset = 190;
// Recreate sprite to the right side of the screen
createBackgroundSprite(WIDTH-5, HEIGHT-7);
createBackgroundSprite(WIDTH-5, HEIGHT-70);
//Print background screen
background.pushImage(-190, 0, MinerWidth, MinerHeight, MinerScreen);

// Total hashes
render.setFontSize(18);
render.rdrawString(data.totalMHashes.c_str(), 268-wdtOffset, 138, TFT_BLACK);

// Print Temp
render.setFontSize(10);
render.setAlignment(Align::TopCenter);
render.rdrawString(data.temp.c_str(), 239-wdtOffset, 1, TFT_BLACK);

// Print Hour
render.setFontSize(10);
render.rdrawString(data.currentTime.c_str(), 286-wdtOffset, 1, TFT_BLACK);

// Block templates
render.setFontSize(18);
Expand All @@ -238,28 +245,34 @@ void esp32_2432S028R_MinerScreen(unsigned long mElapsed)
// 32Bit shares
render.setFontSize(18);
render.drawString(data.completedShares.c_str(), 189-wdtOffset, 76, 0xDEDB);
// Hores
render.setFontSize(14);
render.rdrawString(data.timeMining.c_str(), 315-wdtOffset, 104, 0xDEDB);

// Valid Blocks
render.setFontSize(24);
render.setAlignment(Align::TopCenter);
render.drawString(data.valids.c_str(), 290-wdtOffset, 56, 0xDEDB);

// Print Temp
render.setFontSize(10);
render.rdrawString(data.temp.c_str(), 239-wdtOffset, 1, TFT_BLACK);
// Push prepared background to screen
background.pushSprite(wdtOffset, 0);
background.deleteSprite();

render.setFontSize(4);
render.rdrawString(String(0).c_str(), 244-wdtOffset, 3, TFT_BLACK);
int heightOffset = 100;
// Recreate sprite to the bottom right side of the screen
createBackgroundSprite(WIDTH-5, HEIGHT-90);
//Print background screen
background.pushImage(0-wdtOffset, 0-heightOffset, MinerWidth, MinerHeight, MinerScreen);

// Print Hour
render.setFontSize(10);
render.rdrawString(data.currentTime.c_str(), 286-wdtOffset, 1, TFT_BLACK);
// Hores
render.setFontSize(14);
render.setAlignment(Align::TopLeft);
render.rdrawString(data.timeMining.c_str(), 315-wdtOffset, 104-heightOffset, 0xDEDB);

// Total hashes
render.setFontSize(16);
render.setAlignment(Align::TopCenter);
render.rdrawString(data.totalMHashes.c_str(), 268-wdtOffset, 138-heightOffset, TFT_BLACK);

// Push prepared background to screen
background.pushSprite(190, 0);
background.pushSprite(wdtOffset, heightOffset);

// Delete sprite to free the memory heap
background.deleteSprite();
Expand Down Expand Up @@ -506,6 +519,163 @@ void esp32_2432S028R_BTCprice(unsigned long mElapsed)
#endif
}

void drawLine(int sx, int sy, int ex, int ey, int strokeWidth, uint32_t color)
{
int dx = abs(ex - sx);
int dy = abs(ey - sy);
int sx_step = (sx < ex) ? 1 : -1;
int sy_step = (sy < ey) ? 1 : -1;
int err = dx - dy;

while (true)
{
// Desenha o círculo para "espessar" a linha
tft.drawCircle(sx, sy, strokeWidth, color);

// Se chegou ao destino, para
if (sx == ex && sy == ey) break;

int e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
sx += sx_step;
}
if (e2 < dx) {
err += dx;
sy += sy_step;
}
}
}

void plotGraph(int graphID, int *minPrice, int *maxPrice)
{
int graphW = 300, graphH = 170, graphOffsetX = 10, graphOffsetY = 35;

//BTC_PRICE_HISTORY_GRAPH_MIN
*maxPrice = 0;
*minPrice = INT_MAX;
for (int x=graphW; x >= 0; x--) {
int stepAgo = (graphW - x);
int price = getBTCpriceHistory(stepAgo, graphID);
if (price == 0) break;

if (price < *minPrice) *minPrice = price;
if (price > *maxPrice) *maxPrice = price;
}

if (*maxPrice == 0)
return; // No data

// Plot graph
int range = *maxPrice - *minPrice;
if (range == 0) range = 1;

int oldPrice = getBTCpriceHistory(0, graphID);
int oldX = graphW;
int oldY = (graphH - (graphH * ((oldPrice - *minPrice)) / range));

for (int x=graphW; x >= 0; x--) {
int stepAgo = (graphW - x);
int price = getBTCpriceHistory(stepAgo, graphID);
if (price == 0) break;

int y = (graphH - (graphH * ((price - *minPrice)) / range));

drawLine(oldX+graphOffsetX, oldY+graphOffsetY, x+graphOffsetX, y+graphOffsetY, 2, priceHistoryGraphColors[graphID]);

oldX = x;
oldY = y;
}
}

String getGraphLegend(int graphID, int lineIndex)
{
char legend[20];
unsigned long secondsAgo = getBTCpriceLegendSecsAgo(graphID) * lineIndex;
unsigned long legendTime = getNow() - secondsAgo;

struct tm *tm = localtime((time_t *)&legendTime);

if (secondsAgo < (24 * 60 * 60)) {
int hours = tm->tm_hour;
int minutes = tm->tm_min;

sprintf(legend, "%02d:%02d", hours, minutes);
} else {
// int year = tm->tm_year + 1900; // tm_year es el número de años desde 1900
int month = tm->tm_mon + 1; // tm_mon es el mes del año desde 0 (enero) hasta 11 (diciembre)
int day = tm->tm_mday; // tm_mday es el día del mes

sprintf(legend, "%02d/%02d", day, month);
}

return String(legend);
}

int oldHistoryIndex = -1;
int oldGraphID = -1;
int showGraphs = 1;
void esp32_2432S028R_BTCpriceHistory(unsigned long mElapsed)
{
if (showGraphs < 2 && getBTCpriceHistoryIndex(BTC_PRICE_HISTORY_GRAPH_5MIN) > 120) {
// Wait to have some data to show Daily Graph
showGraphs = 2;
}
if (showGraphs < 3 && getBTCpriceHistoryIndex(BTC_PRICE_HISTORY_GRAPH_30MIN) > 120) {
// Wait to have some data to show Weekly Graph
showGraphs = 3;
}
if (showGraphs < 4 && getBTCpriceHistoryIndex(BTC_PRICE_HISTORY_GRAPH_WEEKLY) > 120) {
// Wait to have some data to show Monthly Graph
showGraphs = 4;
}

uint32_t secsElapsed = millis() / 1000;
int graphID = (secsElapsed / 20) % showGraphs; // 20 secs for each graph

// Detect Screen change
if (graphID == oldGraphID) {
int historyIndex = getBTCpriceHistoryIndex(graphID);
if (historyIndex == oldHistoryIndex) return;
oldHistoryIndex = historyIndex;
}
oldGraphID = graphID;

int curPrice = getBTCpriceHistory(0, graphID);
clock_data data = getClockData(mElapsed);

tft.pushImage(0, 0, BTCgraphScreenWidth, BTCgraphScreenHeight, BTCgraphScreen);

int maxPrice = 0, minPrice = INT_MAX;
plotGraph(graphID, &minPrice, &maxPrice);

if (maxPrice == 0) {
tft.setTextColor(TFT_BLACK);
tft.drawString("Waiting Data", 130, 220, FONT2);
return;
}

tft.setTextColor(TFT_SKYBLUE);
tft.drawString(String("$ ")+String(curPrice), 250, 5, FONT2);

tft.setTextColor(TFT_WHITE);
tft.drawString(String("$ ")+String(maxPrice), 60, 5, FONT2);
tft.drawString(String("$ ")+String(minPrice), 60, 220, FONT2);

String dateDayMonth = data.currentDate.substring(0, 5); // Remove the year
tft.drawString(dateDayMonth.c_str(), 210, 220, FONT2);
tft.drawString(data.currentTime.c_str(), 280, 220, FONT2);

tft.setTextColor(priceHistoryGraphColors[graphID]);
tft.drawString(String("Last ") + getBTCpriceHistoryName(graphID), 130, 220, FONT2);

tft.setTextColor(TFT_BLACK);
for (int i=1; i<=5; i++) {
int px = 260 - ((i - 1) * 60);
tft.drawString(getGraphLegend(graphID, i), px, 180, FONT2);
}
}

void esp32_2432S028R_LoadingScreen(void)
{
tft.fillScreen(TFT_BLACK);
Expand Down Expand Up @@ -566,7 +736,7 @@ void esp32_2432S028R_DoLedStuff(unsigned long frame)
previousTouchMillis = currentMillis;
}

if (currentScreen != currentDisplayDriver->current_cyclic_screen) hasChangedScreen ^= true;
if (currentScreen != currentDisplayDriver->current_cyclic_screen) hasChangedScreen = true;
currentScreen = currentDisplayDriver->current_cyclic_screen;

switch (mMonitor.NerdStatus)
Expand Down Expand Up @@ -599,7 +769,7 @@ void esp32_2432S028R_DoLedStuff(unsigned long frame)

}

CyclicScreenFunction esp32_2432S028RCyclicScreens[] = {esp32_2432S028R_MinerScreen, esp32_2432S028R_ClockScreen, esp32_2432S028R_GlobalHashScreen, esp32_2432S028R_BTCprice};
CyclicScreenFunction esp32_2432S028RCyclicScreens[] = {esp32_2432S028R_MinerScreen, esp32_2432S028R_ClockScreen, esp32_2432S028R_GlobalHashScreen, esp32_2432S028R_BTCprice, esp32_2432S028R_BTCpriceHistory};

DisplayDriver esp32_2432S028RDriver = {
esp32_2432S028R_Init,
Expand Down
Loading