/*
* Virus Barometer Dashboard - Arduino ESP32 + ILI9341 LCD
* Versie 3.2: Finale layout en HTTPS-fix.
* - Gebruikt WiFiClientSecure voor betrouwbare HTTPS-verbindingen.
* - Layout en titel aangepast voor betere leesbaarheid.
*/
#include <WiFi.h>
#include <WiFiClientSecure.h> // Voor betrouwbare HTTPS
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
// --- CONFIGURATIE ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* api_key = "49ef8ce4d6a082af34fd1f41fbfbe03e3636d77320f92261";
const char* api_endpoint = "https://iotsolutions.be/api/iot/virus-data";
// Favorietenlijst voor de drukknop
String favoriteStations[] = {"België", "Gent", "Oostende", "Harelbeke"};
int currentFavoriteIndex = 0;
int numFavoriteStations = 4;
// Pin configuratie
#define BTN_PIN 5
#define TFT_DC 2
#define TFT_CS 15
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
// Data structure
struct VirusLevels {
String stationName;
String covid;
String griep;
String rsv;
String lastUpdate;
};
VirusLevels virusLevels = {"Laden...", "missing", "missing", "missing", "--/--/----"};
// Timing voor knop debounce
unsigned long lastButtonPress = 0;
// --- FUNCTIES ---
uint16_t getColorForLevel(String level) {
if (level == "low") return ILI9341_GREEN;
if (level == "moderate") return ILI9341_YELLOW;
if (level == "high") return ILI9341_ORANGE;
if (level == "very_high") return ILI9341_RED;
return ILI9341_DARKGREY;
}
void setup() {
Serial.begin(115200);
pinMode(BTN_PIN, INPUT_PULLUP);
tft.begin();
tft.setRotation(1);
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 0);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(2);
tft.println("Verbinden met WiFi...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi verbonden!");
fetchDataAndUpdate(favoriteStations[currentFavoriteIndex]);
promptForInput();
}
void loop() {
if (Serial.available() > 0) {
String input = Serial.readStringUntil('\n');
input.trim();
if (input.length() > 0) {
Serial.println("Input via terminal: " + input);
fetchDataAndUpdate(input);
promptForInput();
}
}
if (digitalRead(BTN_PIN) == LOW && (millis() - lastButtonPress > 500)) {
Serial.println("Input via knop: fietsen naar volgende favoriet.");
currentFavoriteIndex = (currentFavoriteIndex + 1) % numFavoriteStations;
fetchDataAndUpdate(favoriteStations[currentFavoriteIndex]);
lastButtonPress = millis();
promptForInput();
}
}
void promptForInput() {
Serial.println("\n========================================");
Serial.println("Voer een stationsnaam in en druk op Enter,");
Serial.println("of druk op de knop om naar de volgende favoriet te gaan.");
Serial.println("========================================");
}
void fetchDataAndUpdate(String stationToFetch) {
Serial.println("Data ophalen voor: " + stationToFetch);
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(60, 100);
tft.setTextSize(3);
tft.setTextColor(ILI9341_WHITE);
tft.print("Laden...");
if (String(api_key).indexOf("PLAK_HIER") != -1) {
virusLevels.stationName = "API KEY FOUT";
updateVirusDisplay();
return;
}
// FIX 1: Gebruik een WiFiClientSecure object voor betrouwbare HTTPS
WiFiClientSecure client;
// Wokwi vereist soms dat we onveilige root CAs accepteren
client.setInsecure();
HTTPClient http;
// Geef de client door aan de http.begin() methode
if (http.begin(client, api_endpoint)) {
http.addHeader("Content-Type", "application/json");
http.addHeader("X-API-KEY", api_key);
String jsonPayload = "{\"station\":\"" + stationToFetch + "\"}";
int httpCode = http.POST(jsonPayload);
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
parseJsonAndUpdateStruct(payload);
} else {
Serial.printf("HTTP Fout! Code: %d\n", httpCode);
virusLevels.stationName = "Fout " + String(httpCode);
}
} else {
Serial.println("Kon geen verbinding maken met de server.");
virusLevels.stationName = "Verbindingsfout";
}
http.end();
updateVirusDisplay();
}
void parseJsonAndUpdateStruct(String jsonString) {
DynamicJsonDocument doc(2048);
DeserializationError error = deserializeJson(doc, jsonString);
if (error) {
virusLevels.stationName = "JSON Fout";
return;
}
if (doc["success"]) {
JsonObject data = doc["data"];
virusLevels.stationName = data["station_name"].as<String>();
virusLevels.covid = data["covid"]["activity_level"].as<String>();
virusLevels.griep = data["influenza"]["activity_level"].as<String>();
virusLevels.rsv = data["rsv"]["activity_level"].as<String>();
virusLevels.lastUpdate = data["last_update"].as<String>();
} else {
String apiError = doc["error"];
virusLevels.stationName = "Fout: " + apiError;
}
}
void updateVirusDisplay() {
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 5);
// FIX 2: Aangepaste titel
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(2);
tft.println("Sciensano Virusbarometer");
String displayName = virusLevels.stationName;
displayName.replace("ë", "e");
tft.setCursor(0, 30);
tft.setTextSize(2);
tft.setTextColor(ILI9341_CYAN);
tft.println(displayName);
// FIX 3: Aangepaste x-coördinaten voor meer ruimte
drawBar(35, 90, virusLevels.covid, "COVID");
drawBar(130, 90, virusLevels.griep, "GRIEP");
drawBar(225, 90, virusLevels.rsv, "RSV");
// Footer
tft.setCursor(5, 225);
tft.setTextSize(1);
tft.setTextColor(ILI9341_WHITE);
tft.print("Update: " + virusLevels.lastUpdate);
Serial.println("Display bijgewerkt.");
}
void drawBar(int x, int y, String level, String label) {
int barWidth = 60;
int barHeight = 100;
tft.drawRect(x, y, barWidth, barHeight, ILI9341_DARKGREY);
uint16_t color = getColorForLevel(level);
int levelHeight = 0;
if (level == "low") levelHeight = 25;
else if (level == "moderate") levelHeight = 50;
else if (level == "high") levelHeight = 75;
else if (level == "very_high") levelHeight = 100;
if (levelHeight > 0) {
tft.fillRect(x + 1, y + (barHeight - levelHeight), barWidth - 2, levelHeight - 1, color);
}
tft.setCursor(x, y - 15);
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.print(label);
tft.setCursor(x, y + barHeight + 5);
tft.setTextSize(1);
tft.setTextColor(color);
level.toUpperCase();
tft.print(level);
}