snapshot
This commit is contained in:
@@ -1,214 +1,24 @@
|
||||
/*
|
||||
* KLUBHAUS v3.9.4-ESP32S3-LCD147
|
||||
* Fixed: timeClient is object, not pointer (use . not ->)
|
||||
*/
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <TFT_eSPI.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include <NTPClient.h>
|
||||
|
||||
// ============== CONFIG ==============
|
||||
const char* WIFI_SSID = "IoT-2GHz";
|
||||
const char* WIFI_PASS = "lesson-greater";
|
||||
|
||||
const char* ALERT_TOPIC = "https://ntfy.sh/ALERT_klubhaus_topic/json?since=all";
|
||||
const char* SILENCE_TOPIC = "https://ntfy.sh/SILENCE_klubhaus_topic/json?since=all";
|
||||
const char* ADMIN_TOPIC = "https://ntfy.sh/ADMIN_klubhaus_topic/json?since=all";
|
||||
const char* STATUS_TOPIC = "https://ntfy.sh/STATUS_klubhaus_topic";
|
||||
|
||||
// ============== GLOBALS ==============
|
||||
TFT_eSPI tft;
|
||||
WiFiUDP ntpUDP;
|
||||
NTPClient timeClient(ntpUDP, "pool.ntp.org", 0, 60000); // Stack object, not pointer
|
||||
|
||||
enum State { SILENT, ALERTING } state = SILENT;
|
||||
String alertMsg = "";
|
||||
bool ledOnly = false;
|
||||
|
||||
unsigned long bootMs = 0;
|
||||
bool gracePeriod = true;
|
||||
bool ntpOk = false;
|
||||
time_t wallClock = 0;
|
||||
unsigned long lastPoll = 0;
|
||||
|
||||
String lastAlertId, lastSilenceId, lastAdminId;
|
||||
|
||||
// ============== SETUP ==============
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(2000);
|
||||
|
||||
bootMs = millis();
|
||||
Serial.println("\n[BOOT] v3.9.4 ESP32-S3-LCD-1.47");
|
||||
// DEBUG: Print which pins TFT_eSPI thinks it's using
|
||||
Serial.println("TFT_eSPI Pin Configuration:");
|
||||
Serial.printf("TFT_MOSI: %d\n", TFT_MOSI);
|
||||
Serial.printf("TFT_SCLK: %d\n", TFT_SCLK);
|
||||
Serial.printf("TFT_CS: %d\n", TFT_CS);
|
||||
Serial.printf("TFT_DC: %d\n", TFT_DC);
|
||||
Serial.printf("TFT_RST: %d\n", TFT_RST);
|
||||
Serial.printf("TFT_BL: %d\n", TFT_BL);
|
||||
|
||||
tft.init();
|
||||
tft.setRotation(1);
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
||||
tft.setTextDatum(MC_DATUM);
|
||||
tft.setTextSize(2);
|
||||
tft.drawString("KLUBHAUS", 160, 76);
|
||||
tft.setTextSize(1);
|
||||
tft.drawString("v3.9.4", 160, 96);
|
||||
tft.drawString("WiFi...", 160, 110);
|
||||
Serial.println("Expected: MOSI=45, SCLK=40, CS=42, DC=41, RST=39, BL=48");
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(WIFI_SSID, WIFI_PASS);
|
||||
|
||||
int attempts = 0;
|
||||
while (WiFi.status() != WL_CONNECTED && attempts < 30) {
|
||||
delay(500);
|
||||
attempts++;
|
||||
}
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.setTextSize(2);
|
||||
tft.drawString(WiFi.status() == WL_CONNECTED ? "WIFI OK" : "WIFI FAIL", 160, 86);
|
||||
delay(1000);
|
||||
|
||||
timeClient.begin(); // Object method: use .
|
||||
if (timeClient.update()) { // Object method: use .
|
||||
ntpOk = true;
|
||||
wallClock = timeClient.getEpochTime(); // FIXED: was timeClient->getEpochTime()
|
||||
}
|
||||
|
||||
Serial.println("[BOOT] Grace: 30s");
|
||||
tft.init(); // This is where it crashes if pins are wrong
|
||||
}
|
||||
|
||||
// ============== LOOP ==============
|
||||
void loop() {
|
||||
unsigned long now = millis();
|
||||
|
||||
if (timeClient.update()) { // Object method: use .
|
||||
ntpOk = true;
|
||||
wallClock = timeClient.getEpochTime(); // FIXED: was timeClient->getEpochTime()
|
||||
}
|
||||
|
||||
if (gracePeriod && now - bootMs >= 30000) {
|
||||
gracePeriod = false;
|
||||
Serial.println("[BOOT] Grace ended");
|
||||
}
|
||||
|
||||
if (now - lastPoll > 5000 && !gracePeriod && ntpOk && WiFi.status() == WL_CONNECTED) {
|
||||
lastPoll = now;
|
||||
poll(ALERT_TOPIC, "ALERT", lastAlertId, [](const String& m){ setAlert(m); });
|
||||
poll(SILENCE_TOPIC, "SILENCE", lastSilenceId, [](const String& m){ setSilent(); });
|
||||
poll(ADMIN_TOPIC, "ADMIN", lastAdminId, handleAdmin);
|
||||
}
|
||||
|
||||
if (state == ALERTING && !ledOnly) {
|
||||
static unsigned long lastBlink = 0;
|
||||
static bool blinkOn = false;
|
||||
if (now - lastBlink > 500) {
|
||||
lastBlink = now;
|
||||
blinkOn = !blinkOn;
|
||||
drawAlert(blinkOn ? 0x07D7 : 0xF81F);
|
||||
}
|
||||
}
|
||||
|
||||
delay(50);
|
||||
}
|
||||
|
||||
// ============== NTFY ==============
|
||||
void poll(const char* url, const char* name, String& lastId, void (*cb)(const String&)) {
|
||||
HTTPClient http;
|
||||
http.setTimeout(8000);
|
||||
|
||||
if (!http.begin(url)) return;
|
||||
|
||||
int code = http.GET();
|
||||
if (code == 200) {
|
||||
String body = http.getString();
|
||||
parse(body, name, lastId, cb);
|
||||
}
|
||||
http.end();
|
||||
}
|
||||
|
||||
void parse(const String& body, const char* name, String& lastId, void (*cb)(const String&)) {
|
||||
int pos = 0;
|
||||
while (pos < body.length()) {
|
||||
int end = body.indexOf('\n', pos);
|
||||
if (end < 0) end = body.length();
|
||||
|
||||
String line = body.substring(pos, end);
|
||||
line.trim();
|
||||
|
||||
if (line.length() > 10 && line.indexOf('{') >= 0) {
|
||||
StaticJsonDocument<512> doc;
|
||||
if (!deserializeJson(doc, line)) {
|
||||
const char* id = doc["id"];
|
||||
const char* msg = doc["message"];
|
||||
time_t t = doc["time"] | 0;
|
||||
|
||||
if (msg && strlen(msg) > 0) {
|
||||
String idStr = id ? String(id) : "";
|
||||
String msgStr = String(msg);
|
||||
|
||||
if (idStr == lastId) { pos = end + 1; continue; }
|
||||
|
||||
if (t > 0 && wallClock - t > 600) {
|
||||
Serial.println(String("[STALE] ") + name);
|
||||
pos = end + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
Serial.println(String("[") + name + "] " + msgStr.substring(0, 40));
|
||||
lastId = idStr;
|
||||
cb(msgStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
pos = end + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ============== ACTIONS ==============
|
||||
void setAlert(const String& m) {
|
||||
state = ALERTING;
|
||||
alertMsg = m;
|
||||
drawAlert(0x07D7);
|
||||
}
|
||||
|
||||
void setSilent() {
|
||||
state = SILENT;
|
||||
alertMsg = "";
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
||||
tft.setTextSize(2);
|
||||
tft.setTextDatum(MC_DATUM);
|
||||
tft.drawString("SILENT", 160, 86);
|
||||
}
|
||||
|
||||
void handleAdmin(const String& m) {
|
||||
Serial.println(String("[ADMIN] ") + m);
|
||||
if (m == "SILENCE") setSilent();
|
||||
else if (m == "REBOOT") ESP.restart();
|
||||
}
|
||||
|
||||
// ============== DISPLAY ==============
|
||||
void drawAlert(uint16_t color) {
|
||||
if (ledOnly) return;
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.setTextColor(color, TFT_BLACK);
|
||||
|
||||
int sz = alertMsg.length() > 10 ? 3 : 4;
|
||||
if (alertMsg.length() > 20) sz = 2;
|
||||
|
||||
tft.setTextSize(sz);
|
||||
tft.setTextDatum(MC_DATUM);
|
||||
|
||||
if (alertMsg.length() > 15) {
|
||||
int mid = alertMsg.length() / 2;
|
||||
int sp = alertMsg.lastIndexOf(' ', mid);
|
||||
if (sp < 0) sp = mid;
|
||||
tft.drawString(alertMsg.substring(0, sp), 160, 70);
|
||||
tft.drawString(alertMsg.substring(sp + 1), 160, 102);
|
||||
} else {
|
||||
tft.drawString(alertMsg, 160, 86);
|
||||
}
|
||||
}
|
||||
void loop() {}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
default_port: /dev/ttyACM0
|
||||
# default_fqbn: esp32:esp32:esp32c6:CDCOnBoot=cdc
|
||||
default_fqbn: esp32:esp32:esp32s3
|
||||
default_fqbn: esp32:esp32:esp32s3:FlashSize=16M,PartitionScheme=app3M_fat9M_16MB,PSRAM=opi
|
||||
default_programmer: esptool
|
||||
|
||||
Reference in New Issue
Block a user