215 lines
5.3 KiB
C++
215 lines
5.3 KiB
C++
/*
|
|
* KLUBHAUS ALERT v3.9.2-STABLE
|
|
* Ultra-defensive: minimal HTTP, no NTP dependency, graceful degradation
|
|
*/
|
|
|
|
#include <WiFi.h>
|
|
#include <HTTPClient.h>
|
|
#include <ArduinoJson.h>
|
|
#include <TFT_eSPI.h>
|
|
|
|
// ============== CONFIG ==============
|
|
const char* WIFI_SSID = "Dobro Veče";
|
|
const char* WIFI_PASS = "goodnight";
|
|
|
|
const char* ALERT_URL = "https://ntfy.sh/ALERT_klubhaus_topic/json?since=all";
|
|
const char* SILENCE_URL = "https://ntfy.sh/SILENCE_klubhaus_topic/json?since=all";
|
|
|
|
// ============== GLOBALS ==============
|
|
TFT_eSPI tft;
|
|
|
|
enum State { SILENT, ALERTING };
|
|
State state = SILENT;
|
|
String alertMessage = "";
|
|
|
|
unsigned long lastPoll = 0;
|
|
unsigned long lastReconnect = 0;
|
|
unsigned long bootMillis = 0;
|
|
bool wifiReady = false;
|
|
|
|
// ============== SETUP ==============
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
delay(1000); // Let USB stabilize
|
|
|
|
bootMillis = millis();
|
|
Serial.println("\n=== KLUBHAUS v3.9.2-STABLE ===");
|
|
Serial.println("Boot at millis=" + String(bootMillis));
|
|
|
|
// Init display early for feedback
|
|
tft.init();
|
|
tft.setRotation(1);
|
|
tft.fillScreen(0x0000);
|
|
tft.setTextColor(0xFFDF, 0x0000);
|
|
tft.setTextDatum(4); // MC_DATUM
|
|
tft.setTextSize(2);
|
|
tft.drawString("BOOTING...", 160, 86);
|
|
|
|
// Connect WiFi with timeout
|
|
WiFi.mode(WIFI_STA);
|
|
WiFi.begin(WIFI_SSID, WIFI_PASS);
|
|
|
|
Serial.print("WiFi connecting");
|
|
int attempts = 0;
|
|
while (WiFi.status() != WL_CONNECTED && attempts < 50) {
|
|
delay(500);
|
|
Serial.print(".");
|
|
attempts++;
|
|
if (attempts % 10 == 0) {
|
|
Serial.println("\nWiFi status: " + String(WiFi.status()));
|
|
}
|
|
}
|
|
|
|
if (WiFi.status() == WL_CONNECTED) {
|
|
wifiReady = true;
|
|
Serial.println("\nWiFi OK: " + WiFi.localIP().toString());
|
|
tft.drawString("WIFI OK", 160, 110);
|
|
} else {
|
|
Serial.println("\nWiFi FAILED, continuing offline");
|
|
tft.drawString("WIFI FAIL", 160, 110);
|
|
}
|
|
|
|
delay(1000);
|
|
drawSilent();
|
|
Serial.println("Setup complete, entering loop");
|
|
}
|
|
|
|
// ============== LOOP ==============
|
|
void loop() {
|
|
unsigned long now = millis();
|
|
|
|
// WiFi reconnect logic (throttled)
|
|
if (!wifiReady && now - lastReconnect > 30000) {
|
|
lastReconnect = now;
|
|
Serial.println("WiFi reconnect attempt...");
|
|
WiFi.reconnect();
|
|
if (WiFi.status() == WL_CONNECTED) {
|
|
wifiReady = true;
|
|
Serial.println("WiFi reconnected");
|
|
}
|
|
}
|
|
|
|
// Poll ntfy (throttled, guarded)
|
|
if (wifiReady && now - lastPoll > 10000) { // 10 second min interval
|
|
lastPoll = now;
|
|
pollNtfy();
|
|
}
|
|
|
|
delay(100); // Yield to RTOS
|
|
}
|
|
|
|
// ============== NTFY POLLING (DEFENSIVE) ==============
|
|
void pollNtfy() {
|
|
Serial.println("Polling ntfy...");
|
|
|
|
HTTPClient http;
|
|
http.setTimeout(8000); // 8 second timeout
|
|
http.setConnectTimeout(5000); // 5 second connect timeout
|
|
|
|
// Poll ALERT first
|
|
if (http.begin(ALERT_URL)) {
|
|
int code = http.GET();
|
|
Serial.println("ALERT HTTP: " + String(code));
|
|
|
|
if (code == 200) {
|
|
String body = http.getString();
|
|
Serial.println("ALERT body len: " + String(body.length()));
|
|
processBody(body, "ALERT");
|
|
}
|
|
http.end();
|
|
}
|
|
|
|
delay(100); // Brief pause between requests
|
|
|
|
// Poll SILENCE
|
|
if (http.begin(SILENCE_URL)) {
|
|
int code = http.GET();
|
|
Serial.println("SILENCE HTTP: " + String(code));
|
|
|
|
if (code == 200) {
|
|
String body = http.getString();
|
|
processBody(body, "SILENCE");
|
|
}
|
|
http.end();
|
|
}
|
|
}
|
|
|
|
void processBody(const String& body, const char* type) {
|
|
// Simple line-by-line parsing, no heavy JSON
|
|
int pos = 0;
|
|
while (pos < body.length()) {
|
|
int end = body.indexOf('\n', pos);
|
|
if (end == -1) end = body.length();
|
|
|
|
String line = body.substring(pos, end);
|
|
line.trim();
|
|
|
|
// Look for "message":"..." pattern
|
|
int msgStart = line.indexOf("\"message\":\"");
|
|
if (msgStart >= 0) {
|
|
msgStart += 11; // Skip "message":"
|
|
int msgEnd = line.indexOf("\"", msgStart);
|
|
if (msgEnd > msgStart) {
|
|
String msg = line.substring(msgStart, msgEnd);
|
|
|
|
// Unescape simple cases
|
|
msg.replace("\\n", "\n");
|
|
msg.replace("\\\"", "\"");
|
|
|
|
Serial.println(String(type) + " msg: " + msg);
|
|
|
|
if (strcmp(type, "ALERT") == 0 && msg.length() > 0) {
|
|
alertMessage = msg;
|
|
state = ALERTING;
|
|
drawAlert();
|
|
}
|
|
else if (strcmp(type, "SILENCE") == 0) {
|
|
state = SILENT;
|
|
alertMessage = "";
|
|
drawSilent();
|
|
}
|
|
}
|
|
}
|
|
|
|
pos = end + 1;
|
|
}
|
|
}
|
|
|
|
// ============== DISPLAY ==============
|
|
void drawSilent() {
|
|
tft.fillScreen(0x0000);
|
|
tft.setTextColor(0xFFDF, 0x0000);
|
|
tft.setTextSize(2);
|
|
tft.setTextDatum(4);
|
|
tft.drawString("SILENT", 160, 86);
|
|
}
|
|
|
|
void drawAlert() {
|
|
tft.fillScreen(0x0000);
|
|
|
|
// Blink color
|
|
uint16_t color = (millis() / 500) % 2 ? 0x07D7 : 0xF81F;
|
|
tft.setTextColor(color, 0x0000);
|
|
|
|
// Auto size
|
|
int size = 4;
|
|
if (alertMessage.length() > 8) size = 3;
|
|
if (alertMessage.length() > 16) size = 2;
|
|
|
|
tft.setTextSize(size);
|
|
tft.setTextDatum(4);
|
|
|
|
// Simple wrap
|
|
if (alertMessage.length() > 20) {
|
|
int mid = alertMessage.length() / 2;
|
|
int split = alertMessage.lastIndexOf(' ', mid);
|
|
if (split < 0) split = mid;
|
|
|
|
tft.drawString(alertMessage.substring(0, split), 160, 70);
|
|
tft.drawString(alertMessage.substring(split + 1), 160, 102);
|
|
} else {
|
|
tft.drawString(alertMessage, 160, 86);
|
|
}
|
|
}
|
|
|