Aquest grup va fer un visualitzador del preu de criptomonedes. Amb aquest dispositiu, l'usuari pot veure el preu actual i la variació en un determinat període de les criptomonedes que triï.

Les dades s'obtenen en format JSON d'una API pública de CoinPaprika.
El programa del microcontrolador és el següent:
#include <WiFiNINA.h> #include <ArduinoJson.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define NUM_CRYPTOS 3 #define POLS_BTC 7 #define POLS_ETH 5 #define POLS_BNB 4 #define DEBOUNCE_MS 300 #define PERIODE_REFRESH 15000UL
//Wi-Fi
const char idXarxa[] = "iPhone de Pepe"; //Idenitifació de xarxa Wi-Fi
const char contrasenya[] = "12345678";
char server[] = "api.coinpaprika.com";
// Les 3 criptomonedes
struct Crypto { const char* nom; const char* id; }; //API Criptomonedes
Crypto cryptos[NUM_CRYPTOS] = {
{ "Bitcoin", "btc-bitcoin" },
{ "Ethereum", "eth-ethereum" },
{ "BNB", "bnb-binance-coin" }
};
// Intervals: 0=1h 1=24h 2=7d
int intervalCripto[NUM_CRYPTOS] = { 0, 0, 0 };
const char* nomInterval[] = { "1 h", "24 h", "7 d" };
int criptoActual = -1;
//Pantalla OLED
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// Botons, un per cripto
unsigned long darreraPremuda = 0;
//Dades guardades
float preuActual = 0.0; //Preu actual de la criptomoneda
float canvis[3] = { 0.0, 0.0, 0.0 }; // 0=1 h, 1=24 h, 2=7 d
// Xarxa
int status = WL_IDLE_STATUS;
WiFiSSLClient client;
String line = "";
bool esperantDades = false;
unsigned long darreraConnexio = 0;
// SETUP, selecció de polsadors
void setup() {
Serial.begin(9600);
while (!Serial) {
;
}
pinMode(POLS_BTC, INPUT_PULLUP);
pinMode(POLS_ETH, INPUT_PULLUP);
pinMode(POLS_BNB, INPUT_PULLUP);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("Error OLED");
while (true);
}
display.clearDisplay(); //Mostrar "Inici..." a la pantalla
display.setTextColor(WHITE);
display.setTextSize(2);
display.setCursor(0, 20);
display.println("Inici...");
display.display();
if (WiFi.status() == WL_NO_MODULE) {
Serial.println("No s'ha trobat el dispositiu Wi-Fi");
while (true);
}
String versio = WiFi.firmwareVersion();
if (versio < "1.0.0") {
Serial.println("Convindria actualitzar el firmware");
}
while (status != WL_CONNECTED) {
Serial.print("Connectant a la xarxa ");
Serial.println(idXarxa);
status = WiFi.begin(idXarxa, contrasenya);
delay(10000);
}
Serial.print("Connectat a ");
Serial.println(WiFi.SSID());
Serial.print("Estat de la connexió: ");
Serial.println(WiFi.status());
Serial.print("Adreça IP del dispositiu: ");
Serial.println(WiFi.localIP());
Serial.print("Intensitat del senyal: ");
Serial.print(WiFi.RSSI());
Serial.println(" dBm");
Serial.println();
Serial.println("Anem a connectar al servidor");
mostrarIdle();
}
// LOOP
void loop() {
if (botoPremat(POLS_BTC)) seleccionarCripto(0); //Es criden les funcions per escollir el interval
if (botoPremat(POLS_ETH)) seleccionarCripto(1);
if (botoPremat(POLS_BNB)) seleccionarCripto(2);
if (esperantDades) processarResposta();
if (criptoActual >= 0 && !esperantDades && darreraConnexio > 0 &&
millis() - darreraConnexio > PERIODE_REFRESH) {
mostrarCarregant();
fetchDades(criptoActual);
}
}
// LÒGICA PRINCIPAL
void seleccionarCripto(int idx) {
if (criptoActual == idx) {
// Mateixa cripto, cicla l'interval sense haver de tornar a cridar l'API
intervalCripto[idx] = (intervalCripto[idx] + 1) % 3;
mostrarDetall();
} else {
// Cripto diferent, cal demanar dades noves
criptoActual = idx;
mostrarCarregant();
fetchDades(idx);
}
}
// COMUNICACIÓ API
void fetchDades(int idx) {
char pagina[70];
snprintf(pagina, sizeof(pagina), "/v1/tickers/%s?quotes=EUR", cryptos[idx].id); //Accedeix a la API
client.stop();
if (client.connect(server, 443)) {
Serial.print("Connexio OK: ");
Serial.println(cryptos[idx].id);
client.print("GET ");
client.print(pagina);
client.println(" HTTP/1.1");
client.print("Host: ");
client.println(server);
client.println("Connection: close");
client.println();
darreraConnexio = millis();
esperantDades = true;
} else {
Serial.println("Connexio fallida");
mostrarError();
delay(2000);
if (criptoActual >= 0) mostrarDetall();
else mostrarIdle();
}
}
void processarResposta() {
while (client.available()) {
line = client.readStringUntil('\r');
if (line.indexOf('{') >= 0) {
DynamicJsonDocument doc(8000);
DeserializationError err = deserializeJson(doc, line);
if (!err) {
preuActual = doc["quotes"]["EUR"]["price"].as<float>(); //Extreiem la informació necessària del JSON
canvis[0] = doc["quotes"]["EUR"]["percent_change_1h"].as<float>();
canvis[1] = doc["quotes"]["EUR"]["percent_change_24h"].as<float>();
canvis[2] = doc["quotes"]["EUR"]["percent_change_7d"].as<float>();
esperantDades = false;
mostrarDetall();
} else {
Serial.print("JSON error: ");
Serial.println(err.c_str());
esperantDades = false;
mostrarError();
delay(2000);
mostrarIdle();
}
}
}
}
// PANTALLES
void mostrarIdle() {
display.clearDisplay();
display.setTextColor(WHITE); //S'ha d'especificar el color perquè sinó no apareix
// Zona groga
display.setTextSize(1);
display.setCursor(22, 4);
display.println("CRYPTOTRACKER");
display.drawLine(0, 15, 127, 15, WHITE);
// Zona blava, instruccions inicials
display.setCursor(0, 20);
display.println("Prem un boto:");
display.setCursor(0, 32);
display.println("1: Bitcoin");
display.setCursor(0, 42);
display.println("2: Ethereum");
display.setCursor(0, 52);
display.println("3: BNB");
display.display();
}
void mostrarDetall() {
display.clearDisplay();
display.setTextColor(WHITE);
// Zona groga: nom cripto + interval actiu
display.setTextSize(1);
display.setCursor(0, 4);
display.print(cryptos[criptoActual].nom);
display.print(" [");
display.print(nomInterval[intervalCripto[criptoActual]]);
display.print("]");
display.drawLine(0, 15, 127, 15, WHITE);
// Zona blava: preu en EUR
display.setTextSize(1);
display.setCursor(0, 18);
display.print("Preu: ");
if (preuActual >= 10000) display.print(preuActual, 0);
else if (preuActual >= 1) display.print(preuActual, 2);
else display.print(preuActual, 5);
display.print(" EUR");
display.drawLine(0, 30, 127, 30, WHITE);
// Canvi de punt a coma
float val = canvis[intervalCripto[criptoActual]];
display.setTextSize(2);
display.setCursor(0, 38);
if (val >= 0) display.print("+");
else display.print("-");
String s = String(abs(val), 2);
s.replace(".", ",");
display.print(s);
display.print(" %");
display.display();
}
void mostrarCarregant() {
display.clearDisplay();
display.setTextColor(WHITE);
// Zona groga
display.setTextSize(1);
display.setCursor(28, 4);
display.println("Carregant...");
display.drawLine(0, 15, 127, 15, WHITE);
// Zona blava
if (criptoActual >= 0) {
display.setCursor(0, 22);
display.print("Cripto: ");
display.println(cryptos[criptoActual].nom);
display.setCursor(0, 38);
display.print("Interval: ");
display.println(nomInterval[intervalCripto[criptoActual]]);
}
display.display();
}
void mostrarError() { // mostrar error si no es conecta
display.clearDisplay();
display.setTextColor(WHITE);
// Zona groga
display.setTextSize(1);
display.setCursor(20, 4);
display.println("!! ERROR !!");
display.drawLine(0, 15, 127, 15, WHITE);
// Zona blava
display.setCursor(4, 22);
display.println("Error de connexio");
display.setCursor(4, 38);
display.display();
}
// utilitats del polsadors
bool botoPremat(int pin) {
if (digitalRead(pin) == LOW) {
unsigned long ara = millis();
if (ara - darreraPremuda > DEBOUNCE_MS) {
darreraPremuda = ara;
return true;
}
}
return false;
}

Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.