├── README.md ├── 3D_files ├── box.stl └── top.stl ├── images ├── Bitcoin.svg.png ├── Dogecoin_Logo.png └── Ethereum_logo_translucent.svg.png ├── cryptos.h └── CryptoTracker.ino /README.md: -------------------------------------------------------------------------------- 1 | # CryptoTracker 2 | ESP8266 cryptocurrency tracker with OLED display 3 | -------------------------------------------------------------------------------- /3D_files/box.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/font/CryptoTracker/main/3D_files/box.stl -------------------------------------------------------------------------------- /3D_files/top.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/font/CryptoTracker/main/3D_files/top.stl -------------------------------------------------------------------------------- /images/Bitcoin.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/font/CryptoTracker/main/images/Bitcoin.svg.png -------------------------------------------------------------------------------- /images/Dogecoin_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/font/CryptoTracker/main/images/Dogecoin_Logo.png -------------------------------------------------------------------------------- /images/Ethereum_logo_translucent.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/font/CryptoTracker/main/images/Ethereum_logo_translucent.svg.png -------------------------------------------------------------------------------- /cryptos.h: -------------------------------------------------------------------------------- 1 | struct Crypto 2 | { 3 | String apiName; 4 | char bitmap[128]; 5 | String symbol; 6 | double priceUsd; 7 | double dayChange; 8 | double weekChange; 9 | }; 10 | 11 | // ---------------------------- 12 | // Add your cryptos to the array below 13 | // 14 | // First argument is a valid coin id from coingecko api (find id of your coin here https://api.coingecko.com/api/v3/coins/list?include_platform=false) 15 | // Second argument is a bitmap of a 32x32 image with cryptocurrency logo 16 | // ---------------------------- 17 | 18 | Crypto cryptos[] = { 19 | {"bitcoin", 20 | {0x00, 0x7f, 0xfe, 0x00, 0x00, 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xe0, 21 | 0x0f, 0xff, 0xff, 0xf0, 0x1f, 0xfe, 0xff, 0xf8, 0x3f, 0xfe, 0x4f, 0xfc, 0x3f, 0xee, 0x4f, 0xfc, 22 | 0x7f, 0xe0, 0x4f, 0xfe, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xf0, 0xc1, 0xff, 23 | 0xff, 0xf0, 0xe1, 0xff, 0xff, 0xf1, 0xe1, 0xff, 0xff, 0xf0, 0xe1, 0xff, 0xff, 0xe0, 0x01, 0xff, 24 | 0xff, 0xe0, 0x03, 0xff, 0xff, 0xe1, 0xc1, 0xff, 0xff, 0xe3, 0xe1, 0xff, 0xff, 0x03, 0xe1, 0xff, 25 | 0xff, 0x00, 0x01, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xe0, 0x07, 0xff, 0x7f, 0xe4, 0xff, 0xfe, 26 | 0x3f, 0xe4, 0xff, 0xfc, 0x3f, 0xfc, 0xff, 0xfc, 0x1f, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xf0, 27 | 0x07, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xfe, 0x00}}, 28 | {"ethereum", 29 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x03, 0xc0, 0x00, 30 | 0x00, 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x0f, 0xf8, 0x00, 31 | 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x7f, 0xfe, 0x00, 32 | 0x00, 0x7f, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0x80, 33 | 0x03, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x01, 0x1f, 0xf9, 0x80, 34 | 0x00, 0xc7, 0xe3, 0x00, 0x00, 0x71, 0x8f, 0x00, 0x00, 0x78, 0x3e, 0x00, 0x00, 0x3e, 0x7c, 0x00, 35 | 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x0f, 0xf8, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x07, 0xe0, 0x00, 36 | 0x00, 0x03, 0xe0, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00}}, 37 | {"dogecoin", 38 | {0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0x80, 39 | 0x03, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xf0, 0x1f, 0xc0, 0x0f, 0xf8, 40 | 0x3f, 0xc0, 0x03, 0xfc, 0x3f, 0xc0, 0x01, 0xfc, 0x7f, 0xc0, 0x00, 0xfe, 0x7f, 0xc3, 0xe0, 0xfe, 41 | 0x7f, 0xc3, 0xf0, 0x7e, 0x7f, 0xc3, 0xf8, 0x7e, 0x7f, 0xc1, 0xf8, 0x7e, 0x7f, 0x00, 0x38, 0x7e, 42 | 0x7f, 0x00, 0x38, 0x7e, 0x7f, 0x80, 0x78, 0x7e, 0x7f, 0xc3, 0xf8, 0x7e, 0x7f, 0xc3, 0xf0, 0x7e, 43 | 0x7f, 0xc3, 0xe0, 0xfe, 0x7f, 0xc0, 0x00, 0xfe, 0x3f, 0xc0, 0x01, 0xfc, 0x3f, 0xc0, 0x03, 0xfc, 44 | 0x1f, 0xc0, 0x0f, 0xf8, 0x0f, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xc0, 45 | 0x01, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00}} 46 | }; 47 | -------------------------------------------------------------------------------- /CryptoTracker.ino: -------------------------------------------------------------------------------- 1 | #define ARDUINOJSON_USE_DOUBLE 1 2 | #define ARDUINOJSON_USE_LONG_LONG 1 3 | 4 | #include 5 | 6 | //#include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "cryptos.h" 13 | 14 | #include //https://github.com/tzapu/WiFiManager WiFi Configuration Magic 15 | 16 | 17 | WiFiManager wm; 18 | 19 | 20 | // ---------------------------- 21 | // Configurations 22 | // ---------------------------- 23 | unsigned long screenChangeDelay = 5000; //5seg between screen updates 24 | unsigned long apiUpdateRate = 240000; //4 min between api updates 25 | 26 | // ---------------------------- 27 | // ---------------------------- 28 | 29 | #define SCREEN_WIDTH 128 // OLED display width, in pixels 30 | #define SCREEN_HEIGHT 64 // OLED display height, in pixels 31 | 32 | // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) 33 | #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) 34 | #define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 35 | Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); //Display connected to SCL and SDA pinout 36 | 37 | 38 | int cryptosCount = (sizeof(cryptos) / sizeof(cryptos[0])); //Cryptos to show 39 | 40 | HTTPClient http; 41 | WiFiClientSecure client; 42 | 43 | #define LED D2 // Led in NodeMCU 44 | 45 | //Some variables 46 | unsigned long previousMillisApi = 0; 47 | unsigned long previousMillisScreen = 0; 48 | int currentCrypto = 0; 49 | bool startUpFlag = true; 50 | 51 | 52 | void displayShowText(String text) 53 | { 54 | display.clearDisplay(); 55 | display.setCursor(0,18); 56 | display.print(text); 57 | display.display(); 58 | } 59 | 60 | void displayStartUp() 61 | { 62 | display.clearDisplay(); 63 | 64 | display.setTextSize(2); 65 | display.setCursor(12,2); 66 | display.println(F("CRYPTO")); 67 | display.setCursor(40,18); 68 | display.println(F("Tracker")); 69 | 70 | display.setTextSize(1); 71 | display.setCursor(90,50); 72 | display.println(F("v1.0")); 73 | 74 | //Fancy graph 75 | display.drawLine(1,63,12,55,SSD1306_WHITE); 76 | display.drawLine(12,55,15,59,SSD1306_WHITE); 77 | display.drawLine(15,59,20,50,SSD1306_WHITE); 78 | display.drawLine(20,50,23,52,SSD1306_WHITE); 79 | display.drawLine(23,52,30,49,SSD1306_WHITE); 80 | display.drawLine(30,49,42,40,SSD1306_WHITE); 81 | 82 | display.display(); 83 | } 84 | 85 | void displayWifiManager(){ 86 | display.clearDisplay(); 87 | 88 | display.setTextSize(1); // Normal 1:1 pixel scale 89 | display.setCursor(20,2); 90 | display.println(F("No SSID found")); 91 | display.setCursor(2,12); 92 | display.println(F("Connect to:")); 93 | display.setCursor(10,22); 94 | display.println(F("default")); 95 | display.setCursor(10,32); 96 | display.println(F("Password: password")); 97 | 98 | display.setCursor(2,45); 99 | display.println(F("And configure a new")); 100 | display.setCursor(2,55); 101 | display.println(F("SSID")); 102 | 103 | display.display(); // Show text 104 | Serial.println("Connect to new ssid"); 105 | } 106 | 107 | void displayConnected(){ 108 | display.clearDisplay(); 109 | 110 | display.setTextSize(1); 111 | display.setCursor(30,2); 112 | display.println(F("Connected")); 113 | display.setCursor(2,14); 114 | display.println(F("IP address:")); 115 | display.setCursor(15,30); 116 | display.println(WiFi.localIP()); // Print wifi IP addess 117 | 118 | display.display(); 119 | 120 | delay(2500); 121 | } 122 | 123 | void waitConnection() 124 | { 125 | if (WiFi.status() != WL_CONNECTED) 126 | { 127 | displayWifiManager(); 128 | delay(300); 129 | } 130 | 131 | displayConnected(); 132 | Serial.println("connected"); 133 | } 134 | 135 | String getApiURL(String vsCurrency) 136 | { 137 | String cryptosString = ""; 138 | 139 | for (int i = 0; i < cryptosCount; i++) 140 | { 141 | cryptosString += cryptos[i].apiName + ","; 142 | } 143 | String result = "https://api.coingecko.com/api/v3/coins/markets?vs_currency=" + vsCurrency + "&ids=" + cryptosString + "&order=market_cap_desc&per_page=100&page=1&sparkline=false&price_change_percentage=24h%2C7d"; 144 | Serial.println("API URL:"+ result); 145 | 146 | return "https://api.coingecko.com/api/v3/coins/markets?vs_currency=" + vsCurrency + "&ids=" + cryptosString + "&order=market_cap_desc&per_page=100&page=1&sparkline=false&price_change_percentage=24h%2C7d"; 147 | } 148 | 149 | int getCryptoIndexById(String id) 150 | { 151 | for (int i = 0; i < cryptosCount; i++) 152 | { 153 | if (cryptos[i].apiName == id) 154 | return i; 155 | } 156 | return 0; 157 | } 158 | 159 | void downloadData(String vsCurrency) 160 | { 161 | digitalWrite(LED, HIGH);// turn the LED off.(Note that LOW is the voltage level but actually 162 | //the LED is on; this is because it is acive low on the ESP8266. 163 | 164 | http.useHTTP10(true); 165 | client.setInsecure(); 166 | 167 | String apiUrl = getApiURL(vsCurrency); 168 | 169 | client.connect(apiUrl, 443); 170 | http.begin(client, apiUrl); 171 | 172 | if (http.GET() != HTTP_CODE_OK) 173 | { 174 | displayShowText("Error connecting to API"); 175 | Serial.println("Error connecting to API"); 176 | return; 177 | } 178 | 179 | // Create a filter to reduce memory usage 180 | 181 | StaticJsonDocument<1024> filter; 182 | 183 | for (int i = 0; i < cryptosCount; i++) 184 | { 185 | filter[i]["id"] = true; 186 | filter[i]["symbol"] = true; 187 | filter[i]["current_price"] = true; 188 | filter[i]["price_change_percentage_24h_in_currency"] = true; 189 | filter[i]["price_change_percentage_7d_in_currency"] = true; 190 | } 191 | 192 | DynamicJsonDocument doc(8192); 193 | DeserializationError error = deserializeJson(doc, http.getStream(), DeserializationOption::Filter(filter)); 194 | 195 | if (error) 196 | { 197 | Serial.print(F("deserializeJson() failed: ")); 198 | Serial.println(error.f_str()); 199 | displayShowText("JSON deserialization error"); 200 | return; 201 | } 202 | 203 | Serial.println("Cryptos:"); 204 | for (int i = 0; i < cryptosCount; i++) 205 | { 206 | JsonObject json = doc[i]; 207 | String id = json["id"]; 208 | int cryptoIndex = getCryptoIndexById(id); 209 | String symbol = json["symbol"]; 210 | symbol.toUpperCase(); 211 | double currentPrice = json["current_price"]; 212 | double dayChange = json["price_change_percentage_24h_in_currency"]; 213 | double weekChange = json["price_change_percentage_7d_in_currency"]; 214 | 215 | cryptos[cryptoIndex].symbol = symbol; 216 | cryptos[cryptoIndex].priceUsd = currentPrice; 217 | cryptos[cryptoIndex].dayChange = dayChange; 218 | cryptos[cryptoIndex].weekChange = weekChange; 219 | Serial.println( cryptos[cryptoIndex].symbol); 220 | Serial.println( cryptos[cryptoIndex].priceUsd); 221 | Serial.println( cryptos[cryptoIndex].dayChange); 222 | Serial.println( cryptos[cryptoIndex].weekChange); 223 | } 224 | 225 | 226 | digitalWrite(LED, LOW); // turn the LED on. 227 | } 228 | 229 | String formatCurrency(float price) 230 | { 231 | int digitsAfterDecimal = 3; 232 | 233 | if (price >= 1000) 234 | { 235 | digitsAfterDecimal = 0; 236 | } 237 | else if (price >= 100) 238 | { 239 | digitsAfterDecimal = 3; 240 | } 241 | else if (price >= 1) 242 | { 243 | digitsAfterDecimal = 4; 244 | } 245 | else if (price < 1) 246 | { 247 | digitsAfterDecimal = 4; 248 | } 249 | 250 | return String(price, digitsAfterDecimal); 251 | } 252 | 253 | void displayCryptoLogo(Crypto crypto) 254 | { 255 | int offX = 90; 256 | int offY = 4; 257 | int width = 32; 258 | int height = 32; 259 | int iSize = 128; 260 | 261 | display.drawBitmap(offX, offY, (uint8_t *)(crypto.bitmap), width, height, WHITE); 262 | } 263 | 264 | String formatPercentageChange(double change) 265 | { 266 | if (change >= 0) 267 | { 268 | return "+" + String(change) + "%"; 269 | } 270 | else 271 | { 272 | return String(change) + "%"; 273 | } 274 | } 275 | 276 | void displayCrypto(Crypto crypto) 277 | { 278 | display.clearDisplay(); 279 | displayCryptoLogo(crypto); 280 | 281 | if (crypto.symbol.length() <= 3) 282 | { 283 | display.setTextSize(2); 284 | display.setCursor(85,45); 285 | display.print(crypto.symbol); 286 | } 287 | else 288 | { 289 | display.setTextSize(2); 290 | display.setCursor(78,45); 291 | display.print(crypto.symbol); 292 | } 293 | 294 | display.setTextSize(2); 295 | display.setCursor(0,6); 296 | display.print("$"); 297 | display.setTextSize(2); 298 | display.setCursor(12,6); 299 | display.print(formatCurrency(crypto.priceUsd)); 300 | display.setTextSize(1); 301 | display.setCursor(0,42); 302 | display.print("24h: " + formatPercentageChange(crypto.dayChange)); 303 | display.setCursor(0,54); 304 | display.print("7d: " + formatPercentageChange(crypto.weekChange)); 305 | display.display(); 306 | } 307 | 308 | void setup() 309 | { 310 | 311 | Serial.begin(115200); 312 | 313 | pinMode(LED, OUTPUT); // set the digital pin as output. 314 | 315 | 316 | //display.init(); 317 | if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { 318 | Serial.println(F("SSD1306 allocation failed")); 319 | for(;;); // Don't proceed, loop forever 320 | } 321 | // Clear the buffer 322 | display.clearDisplay(); 323 | display.setTextColor(SSD1306_WHITE); 324 | displayStartUp(); 325 | delay(2500); // Pause for 2.5 seconds 326 | 327 | 328 | WiFi.mode(WIFI_STA); 329 | 330 | wm.setConfigPortalBlocking(false); 331 | wm.setConfigPortalTimeout(180); 332 | 333 | bool res; 334 | wm.setDebugOutput(true); 335 | 336 | res = wm.autoConnect("default","password"); // password protected ap 337 | 338 | if(!res) { 339 | Serial.println("Failed to connect"); 340 | displayWifiManager(); 341 | } 342 | else { 343 | //if you get here you have connected to the WiFi 344 | Serial.println("connected...yeey :)"); 345 | wm.startConfigPortal(); 346 | } 347 | 348 | // Clear the buffer 349 | display.clearDisplay(); 350 | Serial.println("Colecting data"); 351 | downloadData("usd"); 352 | } 353 | 354 | void loop() 355 | { 356 | wm.process(); //WiFi Manager process 357 | 358 | if (WiFi.status() == WL_CONNECTED){ 359 | if (startUpFlag){ 360 | startUpFlag = false; 361 | displayConnected(); 362 | Serial.println("connected"); 363 | } 364 | 365 | unsigned long currentMillis = millis(); 366 | 367 | 368 | if (currentMillis - previousMillisApi >= apiUpdateRate) { 369 | 370 | previousMillisApi = currentMillis; 371 | 372 | Serial.println("Colecting data"); 373 | downloadData("usd"); 374 | } 375 | 376 | 377 | if (currentMillis - previousMillisScreen >= screenChangeDelay) { 378 | previousMillisScreen = currentMillis; 379 | 380 | if (currentCrypto < cryptosCount){ 381 | displayCrypto(cryptos[currentCrypto]); 382 | currentCrypto++; 383 | } else { 384 | currentCrypto = 0; //Restart crypto to show 385 | } 386 | } 387 | } 388 | 389 | 390 | } 391 | --------------------------------------------------------------------------------