This commit is contained in:
SpudGunMan
2025-10-28 17:21:46 -07:00
parent 9371e96feb
commit f73bef5894
3 changed files with 184 additions and 116 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ The enhanced checklist module provides asset tracking and accountability feature
- Notes support for additional context
### ⏰ Safety Monitoring with Time Intervals
- Set expected check-in intervals for safety monitoring
- Set expected check-in intervals for safety (minimal 20min)
- Automatic tracking of overdue check-ins
- Ideal for solo activities, remote work, or high-risk operations
- Get alerts when someone hasn't checked in within their expected timeframe
+60 -22
View File
@@ -35,6 +35,17 @@ def initialize_checklist_database():
except sqlite3.OperationalError:
pass # Column already exists
try:
c.execute("ALTER TABLE checkin ADD COLUMN removed INTEGER DEFAULT 0")
except sqlite3.OperationalError:
pass # Column already exists
# Add this to your DB init (if not already present)
try:
c.execute("ALTER TABLE checkout ADD COLUMN removed INTEGER DEFAULT 0")
except sqlite3.OperationalError:
pass # Column already exists
conn.commit()
conn.close()
return True
@@ -203,7 +214,7 @@ def get_overdue_checkins():
try:
c.execute("""
SELECT checkin_id, checkin_name, checkin_date, checkin_time, expected_checkin_interval, location
SELECT checkin_id, checkin_name, checkin_date, checkin_time, expected_checkin_interval, location, checkin_notes
FROM checkin
WHERE expected_checkin_interval > 0
AND approved = 1
@@ -218,7 +229,7 @@ def get_overdue_checkins():
conn.close()
overdue_list = []
for checkin_id, name, date, time_str, interval, location in active_checkins:
for checkin_id, name, date, time_str, interval, location, notes in active_checkins:
checkin_datetime = time.mktime(time.strptime(f"{date} {time_str}", "%Y-%m-%d %H:%M:%S"))
time_since_checkin = (current_time - checkin_datetime) / 60 # in minutes
@@ -229,7 +240,8 @@ def get_overdue_checkins():
'name': name,
'location': location,
'overdue_minutes': overdue_minutes,
'interval': interval
'interval': interval,
'checkin_notes': notes
})
return overdue_list
@@ -239,21 +251,28 @@ def get_overdue_checkins():
return []
def format_overdue_alert():
"""Format overdue check-ins as an alert message"""
overdue = get_overdue_checkins()
if not overdue:
try:
"""Format overdue check-ins as an alert message"""
overdue = get_overdue_checkins()
logger.debug(f"Overdue check-ins: {overdue}") # Add this line
if not overdue:
return None
alert = "⚠️ OVERDUE CHECK-INS:\n"
for entry in overdue:
hours = entry['overdue_minutes'] // 60
minutes = entry['overdue_minutes'] % 60
alert += f"{entry['name']}: {hours}h {minutes}m overdue"
# if entry['location']:
# alert += f" @ {entry['location']}"
if entry['checkin_notes']:
alert += f" 📝{entry['checkin_notes']}"
alert += "\n"
return alert.rstrip()
except Exception as e:
logger.error(f"Checklist: Error formatting overdue alert: {e}")
return None
alert = "⚠️ OVERDUE CHECK-INS:\n"
for entry in overdue:
hours = entry['overdue_minutes'] // 60
minutes = entry['overdue_minutes'] % 60
alert += f"{entry['name']}: {hours}h {minutes}m overdue"
if entry['location']:
alert += f" @ {entry['location']}"
alert += "\n"
return alert.rstrip()
def list_checkin():
# list checkins
@@ -262,7 +281,8 @@ def list_checkin():
try:
c.execute("""
SELECT * FROM checkin
WHERE checkin_id NOT IN (
WHERE removed = 0
AND checkin_id NOT IN (
SELECT checkin_id FROM checkout
WHERE checkout_date > checkin_date OR (checkout_date = checkin_date AND checkout_time > checkin_time)
)
@@ -291,7 +311,7 @@ def list_checkin():
timeCheckedIn = f"{days}d {hours:02}:{minutes:02}:{seconds:02}"
else:
timeCheckedIn = f"{hours:02}:{minutes:02}:{seconds:02}"
checkin_list += "ID: " + row[1] + " checked-In for " + timeCheckedIn
checkin_list += "ID: " + str(row[0]) + " " + row[1] + " checked-In for " + timeCheckedIn
if row[5] != "":
checkin_list += "📝" + row[5]
if row != rows[-1]:
@@ -339,10 +359,10 @@ def process_checklist_command(nodeID, message, name="none", location="none"):
return checkout(name, current_date, current_time, location, comment)
elif "purgein" in message_lower:
return delete_checkin(nodeID)
return mark_checkin_removed_by_name(name)
elif "purgeout" in message_lower:
return delete_checkout(nodeID)
return mark_checkout_removed_by_name(name)
elif message_lower.startswith("checklistapprove "):
try:
@@ -380,4 +400,22 @@ def process_checklist_command(nodeID, message, name="none", location="none"):
return list_checkin()
else:
return "Invalid command."
return "Invalid command."
def mark_checkin_removed_by_name(name):
conn = sqlite3.connect(checklist_db)
c = conn.cursor()
c.execute("UPDATE checkin SET removed = 1 WHERE checkin_name = ?", (name,))
affected = c.rowcount
conn.commit()
conn.close()
return f"Marked {affected} check-in(s) as removed for {name}."
def mark_checkout_removed_by_name(name):
conn = sqlite3.connect(checklist_db)
c = conn.cursor()
c.execute("UPDATE checkout SET removed = 1 WHERE checkout_name = ?", (name,))
affected = c.rowcount
conn.commit()
conn.close()
return f"Marked {affected} checkout(s) as removed for {name}."
+123 -93
View File
@@ -1115,105 +1115,135 @@ priorVolcanoAlert = ""
priorEmergencyAlert = ""
priorWxAlert = ""
def handleAlertBroadcast(deviceID=1):
global priorVolcanoAlert, priorEmergencyAlert, priorWxAlert
alertUk = NO_ALERTS
alertDe = NO_ALERTS
alertFema = NO_ALERTS
wxAlert = NO_ALERTS
volcanoAlert = NO_ALERTS
alertWx = False
# only allow API call every 20 minutes
# the watchdog will call this function 3 times, seeing possible throttling on the API
clock = datetime.now()
if clock.minute % 20 != 0:
return False
if clock.second > 17:
return False
# check for alerts
if wxAlertBroadcastEnabled:
alertWx = alertBrodcastNOAA()
try:
global priorVolcanoAlert, priorEmergencyAlert, priorWxAlert
alertUk = NO_ALERTS
alertDe = NO_ALERTS
alertFema = NO_ALERTS
wxAlert = NO_ALERTS
volcanoAlert = NO_ALERTS
overdueAlerts = NO_ALERTS
alertWx = False
# only allow API call every 20 minutes
# the watchdog will call this function 3 times, seeing possible throttling on the API
clock = datetime.now()
if clock.minute % 20 != 0:
return False
if clock.second > 17:
return False
# check for alerts
if wxAlertBroadcastEnabled:
alertWx = alertBrodcastNOAA()
if emergencyAlertBrodcastEnabled:
if enableDEalerts:
alertDe = get_nina_alerts()
if enableGBalerts:
alertUk = get_govUK_alerts()
if emergencyAlertBrodcastEnabled:
if enableDEalerts:
alertDe = get_nina_alerts()
if enableGBalerts:
alertUk = get_govUK_alerts()
else:
# default USA alerts
alertFema = getIpawsAlert(latitudeValue,longitudeValue, shortAlerts=True)
if checklist_enabled:
overdueAlerts = format_overdue_alert()
# format alert
if alertWx:
wxAlert = f"🚨 {alertWx[1]} EAS-WX ALERT: {alertWx[0]}"
else:
# default USA alerts
alertFema = getIpawsAlert(latitudeValue,longitudeValue, shortAlerts=True)
wxAlert = False
# format alert
if alertWx:
wxAlert = f"🚨 {alertWx[1]} EAS-WX ALERT: {alertWx[0]}"
else:
wxAlert = False
femaAlert = alertFema
ukAlert = alertUk
deAlert = alertDe
femaAlert = alertFema
ukAlert = alertUk
deAlert = alertDe
if overdueAlerts != NO_ALERTS and overdueAlerts != None:
logger.debug("System: Adding overdue checkin to emergency alerts")
if femaAlert and NO_ALERTS not in femaAlert and ERROR_FETCHING_DATA not in femaAlert:
femaAlert += "\n\n" + overdueAlerts
elif ukAlert and NO_ALERTS not in ukAlert and ERROR_FETCHING_DATA not in ukAlert:
ukAlert += "\n\n" + overdueAlerts
elif deAlert and NO_ALERTS not in deAlert and ERROR_FETCHING_DATA not in deAlert:
deAlert += "\n\n" + overdueAlerts
else:
# only overdue alerts to send
if overdueAlerts != "" and overdueAlerts is not None and overdueAlerts != NO_ALERTS:
if overdueAlerts != priorEmergencyAlert:
priorEmergencyAlert = overdueAlerts
else:
return False
if isinstance(emergencyAlertBroadcastCh, list):
for channel in emergencyAlertBroadcastCh:
send_message(overdueAlerts, int(channel), 0, deviceID)
else:
send_message(overdueAlerts, emergencyAlertBroadcastCh, 0, deviceID)
return True
if emergencyAlertBrodcastEnabled:
if NO_ALERTS not in femaAlert and ERROR_FETCHING_DATA not in femaAlert:
if femaAlert != priorEmergencyAlert:
priorEmergencyAlert = femaAlert
else:
return False
if isinstance(emergencyAlertBroadcastCh, list):
for channel in emergencyAlertBroadcastCh:
send_message(femaAlert, int(channel), 0, deviceID)
else:
send_message(femaAlert, emergencyAlertBroadcastCh, 0, deviceID)
return True
if NO_ALERTS not in ukAlert:
if ukAlert != priorEmergencyAlert:
priorEmergencyAlert = ukAlert
else:
return False
if isinstance(emergencyAlertBroadcastCh, list):
for channel in emergencyAlertBroadcastCh:
send_message(ukAlert, int(channel), 0, deviceID)
else:
send_message(ukAlert, emergencyAlertBroadcastCh, 0, deviceID)
return True
if NO_ALERTS not in alertDe:
if deAlert != priorEmergencyAlert:
priorEmergencyAlert = deAlert
else:
return False
if isinstance(emergencyAlertBroadcastCh, list):
for channel in emergencyAlertBroadcastCh:
send_message(deAlert, int(channel), 0, deviceID)
else:
send_message(deAlert, emergencyAlertBroadcastCh, 0, deviceID)
return True
if wxAlertBroadcastEnabled:
if wxAlert:
if wxAlert != priorWxAlert:
priorWxAlert = wxAlert
else:
return False
if isinstance(wxAlertBroadcastChannel, list):
for channel in wxAlertBroadcastChannel:
send_message(wxAlert, int(channel), 0, deviceID)
else:
send_message(wxAlert, wxAlertBroadcastChannel, 0, deviceID)
return True
if volcanoAlertBroadcastEnabled:
volcanoAlert = get_volcano_usgs(latitudeValue, longitudeValue)
if volcanoAlert and NO_ALERTS not in volcanoAlert and ERROR_FETCHING_DATA not in volcanoAlert:
# check if the alert is different from the last one
if volcanoAlert != priorVolcanoAlert:
priorVolcanoAlert = volcanoAlert
if isinstance(volcanoAlertBroadcastChannel, list):
for channel in volcanoAlertBroadcastChannel:
send_message(volcanoAlert, int(channel), 0, deviceID)
if emergencyAlertBrodcastEnabled:
if NO_ALERTS not in femaAlert and ERROR_FETCHING_DATA not in femaAlert:
if femaAlert != priorEmergencyAlert:
priorEmergencyAlert = femaAlert
else:
send_message(volcanoAlert, volcanoAlertBroadcastChannel, 0, deviceID)
return False
if isinstance(emergencyAlertBroadcastCh, list):
for channel in emergencyAlertBroadcastCh:
send_message(femaAlert, int(channel), 0, deviceID)
else:
send_message(femaAlert, emergencyAlertBroadcastCh, 0, deviceID)
return True
if NO_ALERTS not in ukAlert:
if ukAlert != priorEmergencyAlert:
priorEmergencyAlert = ukAlert
else:
return False
if isinstance(emergencyAlertBroadcastCh, list):
for channel in emergencyAlertBroadcastCh:
send_message(ukAlert, int(channel), 0, deviceID)
else:
send_message(ukAlert, emergencyAlertBroadcastCh, 0, deviceID)
return True
if NO_ALERTS not in alertDe:
if deAlert != priorEmergencyAlert:
priorEmergencyAlert = deAlert
else:
return False
if isinstance(emergencyAlertBroadcastCh, list):
for channel in emergencyAlertBroadcastCh:
send_message(deAlert, int(channel), 0, deviceID)
else:
send_message(deAlert, emergencyAlertBroadcastCh, 0, deviceID)
return True
if wxAlertBroadcastEnabled:
if wxAlert:
if wxAlert != priorWxAlert:
priorWxAlert = wxAlert
else:
return False
if isinstance(wxAlertBroadcastChannel, list):
for channel in wxAlertBroadcastChannel:
send_message(wxAlert, int(channel), 0, deviceID)
else:
send_message(wxAlert, wxAlertBroadcastChannel, 0, deviceID)
return True
if volcanoAlertBroadcastEnabled:
volcanoAlert = get_volcano_usgs(latitudeValue, longitudeValue)
if volcanoAlert and NO_ALERTS not in volcanoAlert and ERROR_FETCHING_DATA not in volcanoAlert:
# check if the alert is different from the last one
if volcanoAlert != priorVolcanoAlert:
priorVolcanoAlert = volcanoAlert
if isinstance(volcanoAlertBroadcastChannel, list):
for channel in volcanoAlertBroadcastChannel:
send_message(volcanoAlert, int(channel), 0, deviceID)
else:
send_message(volcanoAlert, volcanoAlertBroadcastChannel, 0, deviceID)
return True
except Exception as e:
logger.error(f"System: Error in handleAlertBroadcast: {e}")
return False
def onDisconnect(interface):
# Handle disconnection of the interface
@@ -2141,7 +2171,7 @@ async def watchdog():
handleMultiPing(0, i)
if wxAlertBroadcastEnabled or emergencyAlertBrodcastEnabled or volcanoAlertBroadcastEnabled:
if wxAlertBroadcastEnabled or emergencyAlertBrodcastEnabled or volcanoAlertBroadcastEnabled or checklist_enabled:
handleAlertBroadcast(i)
intData = displayNodeTelemetry(0, i)