first commit

This commit is contained in:
2025-07-06 21:54:54 -06:00
commit 0fdf023af1
5 changed files with 677 additions and 0 deletions

378
Particle/main.ino Normal file
View File

@ -0,0 +1,378 @@
#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));