#include "Particle.h" // Ultra low power system settings for Boron LTE SYSTEM_MODE(MANUAL); // Manual control over cellular connection SYSTEM_THREAD(ENABLED); // Pin definitions for Boron const int MICROSWITCH_PIN = D2; // Microswitch (NC - normally closed) const int ALARM_PIN = D3; // Alarm output const int STATUS_LED = D7; // Built-in LED for status // Ultra low power settings for LTE Boron const unsigned long DAILY_BATTERY_REPORT = 86400; // 24 hours in seconds const unsigned long ALARM_DURATION = 10000; // 10 seconds alarm duration const unsigned long CELLULAR_TIMEOUT = 60000; // 60 seconds to connect to cellular const unsigned long PUBLISH_TIMEOUT = 30000; // 30 seconds to publish // Persistent state (survives STOP mode sleep) retained unsigned long lastBatteryReport = 0; retained unsigned long bootCount = 0; retained bool deviceInitialized = false; retained float lastReportedBattery = 100.0; // Wake up reasons for Boron enum BoronWakeReason { WAKE_SECURITY_BREACH, // Microswitch triggered WAKE_DAILY_REPORT, // Daily battery check WAKE_COLD_START // Power on/reset }; // Function prototypes BoronWakeReason determineWakeReason(); void handleSecurityBreach(); void handleDailyReport(); void handleColdStart(); void activateSecurityAlarm(); void maintainAlarmDuration(); bool connectToCellular(); void publishSecurityAlert(String alertData); String createSecurityAlert(); String createBatteryReport(float batteryLevel); String createStartupAlert(); void flashStatusLED(int count, int duration); void enterUltraLowPowerSleep(); void setup() { bootCount++; // Initialize pins immediately for security pinMode(MICROSWITCH_PIN, INPUT_PULLUP); // Pullup for NC switch pinMode(ALARM_PIN, OUTPUT); pinMode(STATUS_LED, OUTPUT); // Ensure alarm starts OFF digitalWrite(ALARM_PIN, LOW); digitalWrite(STATUS_LED, LOW); // Determine why we woke up BoronWakeReason wakeReason = determineWakeReason(); // Handle based on wake reason switch (wakeReason) { case WAKE_SECURITY_BREACH: handleSecurityBreach(); break; case WAKE_DAILY_REPORT: handleDailyReport(); break; case WAKE_COLD_START: handleColdStart(); break; } // Always go back to ultra low power sleep enterUltraLowPowerSleep(); } void loop() { // Should never reach here in ultra low power mode // If we do, go to sleep immediately enterUltraLowPowerSleep(); } BoronWakeReason determineWakeReason() { // Check if microswitch is open (security breach) // NC switch: HIGH = closed (normal), LOW = open (breach) bool switchOpen = (digitalRead(MICROSWITCH_PIN) == LOW); if (switchOpen) { return WAKE_SECURITY_BREACH; } // Check if it's time for daily battery report unsigned long currentTime = Time.now(); if (deviceInitialized && (currentTime - lastBatteryReport) >= DAILY_BATTERY_REPORT) { return WAKE_DAILY_REPORT; } // Must be a cold start (power on, reset, or first boot) return WAKE_COLD_START; } void handleSecurityBreach() { // IMMEDIATE ALARM ACTIVATION (before any network activity) activateSecurityAlarm(); // Status indication flashStatusLED(5, 100); // 5 rapid flashes // Now connect and send alert if (connectToCellular()) { String alertData = createSecurityAlert(); publishSecurityAlert(alertData); // Brief delay to ensure message is sent delay(5000); } // Keep alarm on for full duration even if network fails maintainAlarmDuration(); // Turn off alarm digitalWrite(ALARM_PIN, LOW); } void handleDailyReport() { // Quick status blink flashStatusLED(2, 200); // Connect and send daily battery report if (connectToCellular()) { float currentBattery = System.batteryCharge(); String reportData = createBatteryReport(currentBattery); publishSecurityAlert(reportData); // Update tracking lastBatteryReport = Time.now(); lastReportedBattery = currentBattery; delay(3000); // Ensure transmission completes } } void handleColdStart() { // Device just powered on or reset deviceInitialized = false; // Startup indication (3 slow blinks) flashStatusLED(3, 500); if (connectToCellular()) { String startupData = createStartupAlert(); publishSecurityAlert(startupData); // Initialize tracking deviceInitialized = true; lastBatteryReport = Time.now(); lastReportedBattery = System.batteryCharge(); delay(3000); } } void activateSecurityAlarm() { // IMMEDIATE alarm activation - highest priority digitalWrite(ALARM_PIN, HIGH); digitalWrite(STATUS_LED, HIGH); // Quick beep pattern to confirm activation // (Remove if you don't want any delay before network connection) for (int i = 0; i < 3; i++) { delay(100); digitalWrite(STATUS_LED, LOW); delay(100); digitalWrite(STATUS_LED, HIGH); } } void maintainAlarmDuration() { // Keep alarm on for specified duration unsigned long alarmStart = millis(); while (millis() - alarmStart < ALARM_DURATION) { // Flash status LED while alarm is active digitalWrite(STATUS_LED, HIGH); delay(250); digitalWrite(STATUS_LED, LOW); delay(250); // Check if switch closed again (breach ended) if (digitalRead(MICROSWITCH_PIN) == HIGH) { // Switch closed again - could end alarm early // Comment out next line if you want fixed duration regardless // break; } } } bool connectToCellular() { unsigned long startTime = millis(); // Status indication - connecting digitalWrite(STATUS_LED, HIGH); // Enable cellular radio Cellular.on(); // Connect to Particle cloud via cellular Particle.connect(); // Wait for connection with timeout while (!Particle.connected() && (millis() - startTime) < CELLULAR_TIMEOUT) { Particle.process(); // Blink during connection attempt digitalWrite(STATUS_LED, (millis() / 500) % 2); delay(100); } bool connected = Particle.connected(); // Status indication if (connected) { // Success - solid LED for 1 second digitalWrite(STATUS_LED, HIGH); delay(1000); } else { // Failed - rapid blinks flashStatusLED(10, 50); } digitalWrite(STATUS_LED, LOW); return connected; } void publishSecurityAlert(String alertData) { if (!Particle.connected()) { return; } // Try to publish with retries for (int attempts = 0; attempts < 3; attempts++) { bool success = Particle.publish("Security Alert", alertData, PRIVATE); if (success) { // Success indication flashStatusLED(2, 100); break; } else { // Retry indication flashStatusLED(1, 50); delay(2000); } } // Allow time for message to be sent unsigned long publishStart = millis(); while (millis() - publishStart < PUBLISH_TIMEOUT) { Particle.process(); delay(100); } } String createSecurityAlert() { float batteryLevel = System.batteryCharge(); CellularSignal signal = Cellular.RSSI(); String alertData = String::format( "Type:SECURITY_BREACH|Desc:Microswitch opened - INTRUDER DETECTED|Batt:%.1f%%|Signal:%d|Quality:%d|Boot:%lu|Alarm:ON", batteryLevel, signal.getStrength(), signal.getQuality(), bootCount ); return alertData; } String createBatteryReport(float batteryLevel) { CellularSignal signal = Cellular.RSSI(); String alertType; String description; if (batteryLevel <= 10.0 && batteryLevel > 0) { alertType = "CRITICAL_BATTERY"; description = String::format("CRITICAL: Battery at %.1f%% - Device may shutdown soon", batteryLevel); } else if (batteryLevel <= 20.0) { alertType = "LOW_BATTERY"; description = String::format("LOW: Battery at %.1f%% - Consider servicing", batteryLevel); } else { alertType = "DAILY_BATTERY_REPORT"; description = String::format("Daily report: Battery at %.1f%%, operating normally", batteryLevel); } String alertData = String::format( "Type:%s|Desc:%s|Batt:%.1f%%|Signal:%d|Quality:%d|Boot:%lu|Days:%lu", alertType.c_str(), description.c_str(), batteryLevel, signal.getStrength(), signal.getQuality(), bootCount, (Time.now() - lastBatteryReport) / 86400 ); return alertData; } String createStartupAlert() { float batteryLevel = System.batteryCharge(); CellularSignal signal = Cellular.RSSI(); String description = String::format( "Device startup - Boot #%lu, Battery: %.1f%%, Ready for security monitoring", bootCount, batteryLevel ); String alertData = String::format( "Type:DEVICE_STARTUP|Desc:%s|Batt:%.1f%%|Signal:%d|Quality:%d|Firmware:1.0.0", description.c_str(), batteryLevel, signal.getStrength(), signal.getQuality() ); return alertData; } void flashStatusLED(int count, int duration) { for (int i = 0; i < count; i++) { digitalWrite(STATUS_LED, HIGH); delay(duration); digitalWrite(STATUS_LED, LOW); delay(duration); } } void enterUltraLowPowerSleep() { // Disconnect from cellular to save maximum power if (Particle.connected()) { Particle.disconnect(); delay(2000); // Give time to disconnect cleanly } // Turn off cellular radio completely Cellular.off(); // Turn off status LED digitalWrite(STATUS_LED, LOW); // Configure ultra low power sleep for Boron SystemSleepConfiguration config; config.mode(SystemSleepMode::STOP) // STOP mode for Boron (supports RTC wake) .gpio(MICROSWITCH_PIN, FALLING) // Wake on switch opening (NC switch goes LOW) .duration(24h); // Wake once per day for battery report // Enter sleep - device will wake on pin interrupt OR after 24 hours SystemSleepResult result = System.sleep(config); // When we wake up, setup() will run again to handle the wake reason } // Optional: Handle system events for debugging void onCellularConnect() { // Cellular connected flashStatusLED(1, 200); } void onCellularDisconnect() { // Cellular disconnected flashStatusLED(2, 100); } // Uncomment for debugging (will increase power consumption) // STARTUP(cellular.on()); // SYSTEM(SYSTEM_MODE(SEMI_AUTOMATIC));