/* * ============================================ * Barduino 4.0.2 – Alarm Clock Network Node * Gabriel Stacey-Chartrand | Fab Academy * MQTT over WebSockets (WSS) – port 8884 * ============================================ * * Touch pad TOUCH01 (GPIO1) mirrors the alarm clock button: * - Short tap (<2 s): pause/resume pomodoro; show alarm time when idle; * dismiss alarm when firing * - Hold (≥2 s): reset pomodoro when running; toggle alarm ON/OFF when idle * * Topics (shared with alarm clock board and HTML interface): * Subscribe: * s_c/alarm/set "HH:MM" * s_c/alarm/status "ON" / "OFF" * s_c/alarm/pomodoro "WORK_START:Xmin" / "BREAK_START:Xmin" / * "WORK_END" / "BREAK_END" / "PAUSED" / "RESUMED" / "RESET" * Publish: * s_c/alarm/status "ON" / "OFF" * s_c/alarm/pomodoro "PAUSED" / "RESUMED" / "RESET" * * Touch calibration: * On ESP32-S3, touchRead() returns higher values when touched. * Run with CALIBRATE_TOUCH defined once to find your baseline, then * set TOUCH_THRESHOLD to ~(baseline + 20000). * * Libraries required: * - WebSockets by Markus Sattler * - MQTTPubSubClient by hideakitai * ============================================ */ #include #include #include // ── Wi-Fi & MQTT ───────────────────────────────────────────── const char* WIFI_SSID = "SSID"; const char* WIFI_PASS = "PASSWORD"; const char* MQTT_BROKER = "broker.hivemq.com"; const int MQTT_PORT = 8884; const char* MQTT_CLIENT = "gsc_barduino_node_4b9k"; // unique — must differ from alarm clock const char* TOPIC_SET = "s_c/alarm/set"; const char* TOPIC_STATUS = "s_c/alarm/status"; const char* TOPIC_POM = "s_c/alarm/pomodoro"; // ── Touch pad ──────────────────────────────────────────────── // TOUCH01 pad on Barduino 4.0.2 = GPIO1 #define TOUCH_PIN 4 // On ESP32-S3, touchRead() returns higher values when the pad is touched. // Read the serial output on first boot with no touch to find your baseline, // then set TOUCH_THRESHOLD to (baseline + 20000) or so. // Default threshold assumes ~30000 baseline (untouched) on a fresh board. #define TOUCH_THRESHOLD 100000 #define TOUCH_DEBOUNCE_MS 20 #define TOUCH_HOLD_MS 2000 // Uncomment to print raw touch values every 200 ms for calibration: // #define CALIBRATE_TOUCH // ── LED (onboard) ──────────────────────────────────────────── #define LED_PIN 21 // adjust if Barduino uses a different LED GPIO // ── State mirrored from MQTT ───────────────────────────────── bool alarmOn = false; bool alarmFiring = false; enum PomState { POM_IDLE, POM_WORK, POM_BREAK, POM_PAUSED_WORK, POM_PAUSED_BREAK }; PomState pomState = POM_IDLE; // ── Objects ────────────────────────────────────────────────── WebSocketsClient wsClient; MQTTPubSubClient mqtt; // ── Touch state ────────────────────────────────────────────── bool touchDown = false; unsigned long touchDownAt = 0; bool touchHoldFired = false; unsigned long lastTouchRead = 0; // ───────────────────────────────────────────────────────────── // MQTT HELPERS // ───────────────────────────────────────────────────────────── void mqttPublish(const char* topic, const char* payload) { if (mqtt.isConnected()) { mqtt.publish(topic, payload); Serial.printf("PUB [%s]: %s\n", topic, payload); } } // ───────────────────────────────────────────────────────────── // BUTTON ACTIONS (same logic as alarm clock onShortPress / // onHoldPress — no display, just MQTT publishes) // ───────────────────────────────────────────────────────────── void onShortTouch() { Serial.println("TOUCH short"); if (alarmFiring) { // Dismiss alarm alarmFiring = false; alarmOn = false; mqttPublish(TOPIC_STATUS, "OFF"); return; } if (pomState == POM_WORK || pomState == POM_BREAK) { pomState = (pomState == POM_WORK) ? POM_PAUSED_WORK : POM_PAUSED_BREAK; mqttPublish(TOPIC_POM, "PAUSED"); return; } if (pomState == POM_PAUSED_WORK) { pomState = POM_WORK; mqttPublish(TOPIC_POM, "RESUMED"); return; } if (pomState == POM_PAUSED_BREAK) { pomState = POM_BREAK; mqttPublish(TOPIC_POM, "RESUMED"); return; } // Idle: nothing to do locally — alarm time display is on the clock board Serial.println("(idle — short touch ignored)"); } void onHoldTouch() { Serial.println("TOUCH hold"); if (pomState != POM_IDLE) { pomState = POM_IDLE; mqttPublish(TOPIC_POM, "RESET"); return; } alarmOn = !alarmOn; mqttPublish(TOPIC_STATUS, alarmOn ? "ON" : "OFF"); } // ───────────────────────────────────────────────────────────── // TOUCH HANDLING // ───────────────────────────────────────────────────────────── void handleTouch() { unsigned long now = millis(); // Sample at most every TOUCH_DEBOUNCE_MS to avoid bus flooding if (now - lastTouchRead < TOUCH_DEBOUNCE_MS) return; lastTouchRead = now; uint32_t val = touchRead(TOUCH_PIN); #ifdef CALIBRATE_TOUCH static unsigned long lastPrint = 0; if (now - lastPrint >= 200) { lastPrint = now; Serial.printf("touchRead(GPIO%d) = %lu\n", TOUCH_PIN, (unsigned long)val); } #endif bool isTouched = (val > TOUCH_THRESHOLD); if (isTouched && !touchDown) { touchDown = true; touchDownAt = now; touchHoldFired = false; } else if (!isTouched && touchDown) { if (!touchHoldFired) onShortTouch(); touchDown = false; } if (touchDown && !touchHoldFired && (now - touchDownAt >= TOUCH_HOLD_MS)) { touchHoldFired = true; onHoldTouch(); } } // ───────────────────────────────────────────────────────────── // MQTT MESSAGE HANDLER (mirrors state for correct button logic) // ───────────────────────────────────────────────────────────── void onMqttMessage(const String& topic, const String& payload) { Serial.printf("SUB [%s]: %s\n", topic.c_str(), payload.c_str()); if (topic == TOPIC_STATUS) { alarmOn = (payload == "ON"); alarmFiring = false; // any status update clears firing } else if (topic == TOPIC_POM) { if (payload.startsWith("WORK_START:")) { pomState = POM_WORK; } else if (payload.startsWith("BREAK_START:")) { pomState = POM_BREAK; } else if (payload == "PAUSED") { if (pomState == POM_WORK) pomState = POM_PAUSED_WORK; if (pomState == POM_BREAK) pomState = POM_PAUSED_BREAK; } else if (payload == "RESUMED") { if (pomState == POM_PAUSED_WORK) pomState = POM_WORK; if (pomState == POM_PAUSED_BREAK) pomState = POM_BREAK; } else if (payload == "RESET") { pomState = POM_IDLE; } else if (payload == "WORK_END" || payload == "BREAK_END") { // Phase transitions handled by WORK_START / BREAK_START that follow } } // TOPIC_SET (alarm time) is not relevant to this node's button logic } // ───────────────────────────────────────────────────────────── // MQTT CONNECT // ───────────────────────────────────────────────────────────── void connectMQTT() { Serial.print("Connecting to MQTT"); while (!mqtt.connect(MQTT_CLIENT)) { Serial.print("."); delay(1000); } Serial.println(" connected!"); mqtt.subscribe(TOPIC_STATUS, [](const String& p, const size_t){ onMqttMessage(TOPIC_STATUS, p); }); mqtt.subscribe(TOPIC_POM, [](const String& p, const size_t){ onMqttMessage(TOPIC_POM, p); }); // We do not need TOPIC_SET — alarm time display is on the clock board } // ───────────────────────────────────────────────────────────── // SETUP // ───────────────────────────────────────────────────────────── void setup() { Serial.begin(115200); delay(1000); Serial.println("\n=== Barduino Alarm Node ==="); pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW); Serial.print("Wi-Fi"); WiFi.begin(WIFI_SSID, WIFI_PASS); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(" OK IP: " + WiFi.localIP().toString()); digitalWrite(LED_PIN, HIGH); wsClient.beginSSL(MQTT_BROKER, MQTT_PORT, "/mqtt"); wsClient.setReconnectInterval(2000); mqtt.begin(wsClient); connectMQTT(); Serial.println("Ready — touch TOUCH01 (GPIO1)"); } // ───────────────────────────────────────────────────────────── // LOOP // ───────────────────────────────────────────────────────────── void loop() { wsClient.loop(); mqtt.update(); if (!mqtt.isConnected()) { Serial.print("MQTT reconnect"); connectMQTT(); } handleTouch(); delay(5); }