Ernest_tested_6_25_2025
This commit is contained in:
parent
cb57ae8d7b
commit
4cc8509f9c
792
well-alerts.py
792
well-alerts.py
@ -24,6 +24,7 @@ import psutil
|
||||
import requests
|
||||
import copy
|
||||
import re
|
||||
import ast
|
||||
import random
|
||||
import traceback
|
||||
#import math
|
||||
@ -1077,7 +1078,6 @@ def load_device_configurations():
|
||||
mac_to_device_id[device_detail[2]] = device_detail[0]
|
||||
alarm_device_settings_str = GetDeviceAlarmSettings(device_id)
|
||||
|
||||
|
||||
#if alarm_device_settings != "":
|
||||
# print(alarm_device_settings)
|
||||
deployment_id = device_detail[6]
|
||||
@ -1085,6 +1085,11 @@ def load_device_configurations():
|
||||
# print(device_id)
|
||||
device_to_deployment[device_id] = deployment_id
|
||||
|
||||
if alarm_device_settings_str != None and alarm_device_settings_str != "":
|
||||
device_alerts_all[device_id] = json.loads(alarm_device_settings_str)
|
||||
else:
|
||||
device_alerts_all[device_id] = {}
|
||||
|
||||
if deployment_id not in deployment_id_list:
|
||||
deployment_id_list.append(deployment_id)
|
||||
|
||||
@ -1124,6 +1129,7 @@ def load_device_configurations():
|
||||
last_seen = GetRedisFloat('lastseen_'+device_id_s)
|
||||
|
||||
if last_seen == None:
|
||||
#Check from DB
|
||||
last_seen = GetLastDetected(device_id_s, radar_threshold_signal, radar_threshold_value)
|
||||
if last_seen == None:
|
||||
last_seen = 0
|
||||
@ -1146,7 +1152,7 @@ def load_device_configurations():
|
||||
if len(alarm_settings_str) > 2:
|
||||
alarm_settings = json.loads(alarm_settings_str)
|
||||
alarm_armed_settings = alarm_settings['enabled']
|
||||
alarm_settings["armed"] = alarm_armed_settings
|
||||
alarm_settings["armed_states"] = alarm_armed_settings
|
||||
alarms_settings_all[deployment_id] = alarm_settings
|
||||
#lets reset bit 1, so armed alarm is recognized on start if present... start of what???
|
||||
#alarm_armed_settings = ClearBit(alarm_armed_settings, 1)
|
||||
@ -3084,6 +3090,9 @@ def PrepareMP3(text_to_speak):
|
||||
|
||||
def SendPhoneCall(phone_nr, text_str):
|
||||
|
||||
if phone_nr[0] == "-" or phone_nr[0] == "_":
|
||||
return
|
||||
|
||||
phone_nr = normalize_phone_number(phone_nr)
|
||||
#TELNYX_WEBHOOK_URL_VOICE = "http://eluxnetworks.net:1998/telnyx-webhook"
|
||||
uuid_str = PrepareMP3(text_str)
|
||||
@ -3124,7 +3133,7 @@ def SendPhoneCall(phone_nr, text_str):
|
||||
def SendAlerts(deployment_id, method, text_str, subject, user_name):
|
||||
|
||||
global sender
|
||||
|
||||
#return #todo remove it in production
|
||||
if user_name == "" or user_name == None: #real request so send to all caretakers
|
||||
|
||||
care_takers = care_takers_all[int(deployment_id)]
|
||||
@ -3222,15 +3231,77 @@ def GetTimeZoneOfDeployment(deployment_id):
|
||||
|
||||
def StoreLastSentToRedis(deployment_id):
|
||||
alarms_settings = alarms_settings_all[deployment_id]
|
||||
alarms_settings["last_triggered_utc"] = datetime.datetime.utcnow()
|
||||
alarm_deployment_settings_str = json.dumps(alarms_settings)
|
||||
redis_conn.set('alarm_deployment_settings_'+str(deployment_id), alarm_deployment_settings_str)
|
||||
|
||||
alarms_settings["last_triggered_utc"] = datetime.datetime.utcnow().isoformat()
|
||||
try:
|
||||
alarm_deployment_settings_str = json.dumps(alarms_settings)
|
||||
redis_conn.set('alarm_deployment_settings_'+str(deployment_id), alarm_deployment_settings_str)
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
def StoreDeviceAlarmsToRedis(device_id, device_alarm_settings):
|
||||
alarm_device_settings_str = json.dumps(device_alarm_settings)
|
||||
redis_conn.set('alarm_device_settings_'+str(device_id), alarm_device_settings_str)
|
||||
|
||||
def StoreDeviceAlarmsToDB(device_id, device_alarm_settings):
|
||||
|
||||
alarm_device_settings_str = json.dumps(device_alarm_settings)
|
||||
|
||||
conn = get_db_connection()
|
||||
if not conn:
|
||||
return False
|
||||
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
|
||||
cur.execute("UPDATE devices SET alert_details = %s WHERE device_id = %s",
|
||||
(alarm_device_settings_str, device_id))
|
||||
|
||||
print(f"Updated alert_details for {device_id} to {alarm_device_settings_str}")
|
||||
|
||||
conn.commit()
|
||||
# Update the ACL file
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Database error: {e}")
|
||||
conn.rollback()
|
||||
return False
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def StoreLastSentToDB(deployment_id):
|
||||
alarms_settings = alarms_settings_all[deployment_id]
|
||||
alarm_deployment_settings_str = json.dumps(alarms_settings)
|
||||
|
||||
conn = get_db_connection()
|
||||
if not conn:
|
||||
return False
|
||||
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
|
||||
cur.execute("UPDATE deployments SET alarm_details = %s WHERE deployment_id = %s",
|
||||
(alarm_deployment_settings_str, deployment_id))
|
||||
|
||||
print(f"Updated alert_details for {deployment_id} to {alarm_deployment_settings_str}")
|
||||
|
||||
conn.commit()
|
||||
# Update the ACL file
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Database error: {e}")
|
||||
conn.rollback()
|
||||
return False
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def StoreAllToRedisAndDB(deployment_id, device_id):
|
||||
StoreLastSentToRedis(deployment_id)
|
||||
StoreLastSentToDB(deployment_id)
|
||||
device_alarm_settings = device_alerts_all[device_id]
|
||||
StoreDeviceAlarmsToRedis(device_id, device_alarm_settings)
|
||||
StoreDeviceAlarmsToDB(device_id, device_alarm_settings)
|
||||
|
||||
def SetupTasks():
|
||||
|
||||
global local_daily_minute_last
|
||||
@ -3261,8 +3332,8 @@ def SetupTasks():
|
||||
else:
|
||||
alarms_settings = json.loads(alarm_deployment_settings_str)
|
||||
|
||||
if "armed" in alarms_settings:
|
||||
alarm_armed_settings_str = alarms_settings["armed"]
|
||||
if "enabled" in alarms_settings:
|
||||
alarm_armed_settings_str = alarms_settings["enabled"]
|
||||
else:
|
||||
alarm_armed_settings_str = "000"
|
||||
|
||||
@ -3277,14 +3348,6 @@ def SetupTasks():
|
||||
rearm_policy = "Never"
|
||||
#alarm_armed_settings_str = GetRedisString(f'alarm_armed_settings_{deployment_id}')
|
||||
|
||||
#alert_mode bits:
|
||||
#0: 0 = default, 1 = set (so I can distinquish between nothing set (0s default) and purposefully set to 0s)
|
||||
#1: Home Security bit 0 = not used, 1 = alarm armed
|
||||
#2: Warning level environmental condition (Yellow) Temperatures High/Low
|
||||
#3: Alarm level environmental condition (Red) Temperatures High/Low
|
||||
#4: Warning level medical condition (Yellow) Too long present/absent
|
||||
#5: Alarm level medical condition (Red) Too long present/absent
|
||||
#6: Alarm if alone too long
|
||||
|
||||
utc_time = datetime.datetime.utcnow()
|
||||
# Calculate minutes since start of day
|
||||
@ -3316,154 +3379,171 @@ def SetupTasks():
|
||||
elif rearm_policy == "Never":
|
||||
pass
|
||||
|
||||
devices_lst = deployments_devices[deployment_id]
|
||||
if do_rearm:
|
||||
alarm_armed_settings, alarm_settings_str = GetAlarmSettings(deployment_id)
|
||||
if len(alarm_settings_str) > 2:
|
||||
#No need to re-arm Burglar alarm it remains armed untill manually disarmed
|
||||
alarm_settings_str = GetAlarmSettings(deployment_id)
|
||||
if alarm_settings_str != "{}":
|
||||
alarm_settings = json.loads(alarm_settings_str)
|
||||
alarm_settings["armed"] = alarm_armed_settings
|
||||
alarms_settings_all[deployment_id] = alarm_settings
|
||||
alarm_armed_settings_str = alarms_settings["armed"]
|
||||
#alarm_settings["enabled"] bits:
|
||||
# bit 2: Burglar Alarm (index 0), bit 1: Time alone Alarm is Set (index 1), bit 0: Time alone Warning is Set (index 2)
|
||||
if alarm_settings["enabled"][1] == "1": #Time alone Alarm
|
||||
alarm_settings["alone_alarm_armed"] = True
|
||||
|
||||
devices_lst = deployments_devices[deployment_id]
|
||||
if alarm_settings["enabled"][2] == "1": #Time alone Warning
|
||||
alarm_settings["alone_warning_armed"] = True
|
||||
|
||||
alarms_settings_all[deployment_id] = alarm_settings
|
||||
StoreLastSentToRedis(deployment_id)
|
||||
StoreLastSentToDB(deployment_id)
|
||||
alarm_armed_settings_str = alarms_settings["armed_states"]
|
||||
|
||||
for device_id in devices_lst:
|
||||
if device_id in device_alerts_all:
|
||||
device_id_s = str(device_id)
|
||||
alarm_device_settings_str = GetDeviceAlarmSettings(device_id)
|
||||
redis_conn.set('alarm_device_settings_'+device_id_s, alarm_device_settings_str)
|
||||
StoreDeviceAlarmsToRedis(device_id, alarm_device_settings_str)
|
||||
StoreDeviceAlarmsToDB(device_id, device_alarm_settings)
|
||||
|
||||
|
||||
if alarm_armed_settings_str != "":
|
||||
if alarm_armed_settings_str[-1] == "1": #used
|
||||
#print(alarm_armed_settings_str)
|
||||
if alarm_armed_settings_str[-2] == "0": #alarm not armed, so compare individual conditions
|
||||
if alarm_armed_settings_str[-5] == "1" or alarm_armed_settings_str[-6] == "1": #Too long present/absent
|
||||
devices_lst = deployments_devices[deployment_id]
|
||||
numbers = []
|
||||
if alarm_armed_settings_str[0] == "1": #"100" Burglar alarm
|
||||
#Burglar alarm is triggerred on sensor read not time so nothing to be done here
|
||||
pass
|
||||
|
||||
for device_id in devices_lst:
|
||||
if device_id in device_alerts_all:
|
||||
since_seen = time.time() - GetRedisFloat('lastseen_'+str(device_id))
|
||||
numbers.append((since_seen, device_id))
|
||||
#lets check if alone
|
||||
if "1" in alarm_armed_settings_str[1:]: #"010" alone warning or alarm
|
||||
pass #todo
|
||||
|
||||
sorted_numbers = numbers
|
||||
second_smallest = -1
|
||||
if len(numbers) == 1:
|
||||
smallest = sorted_numbers[0]
|
||||
if len(numbers) >= 2:
|
||||
sorted_numbers = sorted(numbers, key=lambda x: x[0])
|
||||
smallest = sorted_numbers[0]
|
||||
second_smallest = sorted_numbers[1]
|
||||
device_id_to_last_seen = {}
|
||||
for tpl in sorted_numbers:
|
||||
last_seen_ago = tpl[0]
|
||||
device_id = tpl[1]
|
||||
#device_id_to_last_seen[tpl[1]] = tpl[0]
|
||||
device_alarm_settings = device_alerts_all[device_id]
|
||||
enabled_alarms_str = device_alarm_settings["enabled_alarms"]
|
||||
#lets check larm first, because if satisfied, no need to check for warning
|
||||
if enabled_alarms_str[-2] == "1": #Too long present alarm
|
||||
if device_id == smallest[1]: #now present... how long?
|
||||
if (second_smallest[0] - smallest[0]) > device_alarm_settings["stuck_minutes_alarm"] * 60:
|
||||
#cancel alarm and warning, until re-armed
|
||||
enabled_alarms_str = set_character(enabled_alarms_str, 0, "0")
|
||||
enabled_alarms_str = set_character(enabled_alarms_str, 1, "0")
|
||||
dev_det = devices_details_map[device_id]
|
||||
location = (dev_det[4] + " " + dev_det[5].strip()).strip()
|
||||
method = device_alarm_settings["stuck_alarm_method_1"]
|
||||
if method.upper() != "PHONE":
|
||||
SendAlerts(deployment_id, method, f"Alarm: {user_first_last_name} way too long ({int((second_smallest[0] - smallest[0]) / 60)} minutes) in {location}", "", "")
|
||||
else:
|
||||
SendAlerts(deployment_id, method, f"Alarm: {user_first_last_name} way too long in {location}", "", "")
|
||||
numbers = []
|
||||
for device_id in devices_lst:
|
||||
if device_id in device_alerts_all:
|
||||
since_seen = time.time() - GetRedisFloat('lastseen_'+str(device_id))
|
||||
numbers.append((since_seen, device_id))
|
||||
|
||||
device_alarm_settings["enabled_alarms"] = enabled_alarms_str
|
||||
StoreLastSentToRedis(deployment_id)
|
||||
device_alerts_all[device_id] = device_alarm_settings
|
||||
StoreDeviceAlarmsToRedis(device_id, device_alarm_settings)
|
||||
else:
|
||||
check_after = device_alarm_settings["stuck_minutes_alarm"] - int((second_smallest[0] - smallest[0])/60)
|
||||
if check_after < next_run_in_minutes:
|
||||
next_run_in_minutes = check_after
|
||||
#In order to understand if somebody is too long in some place, or to long since it visited, we use following logic:
|
||||
#For too long since last visit: we check if since_seen is larger than absent_minutes_alarm and absent_minutes_warning value
|
||||
#For too long (stuck) in same place: we check (time_of_last_read_of_the_device - time_of_last_read_of_any_other_device) larger than stuck_minutes_warning and stuck_minutes_alarm value
|
||||
second_smallest = -1
|
||||
if len(numbers) <2:
|
||||
#needs at least 2 devices for proper operation
|
||||
pass
|
||||
else:
|
||||
sorted_numbers = sorted(numbers, key=lambda x: x[0])
|
||||
smallest = sorted_numbers[0]
|
||||
second_smallest = sorted_numbers[1]
|
||||
device_id_to_last_seen = {}
|
||||
for tpl in sorted_numbers:
|
||||
last_seen_ago = tpl[0]
|
||||
device_id = tpl[1]
|
||||
#device_id_to_last_seen[tpl[1]] = tpl[0]
|
||||
device_alarm_settings = device_alerts_all[device_id]
|
||||
if "enabled_alarms" in device_alarm_settings:
|
||||
enabled_alarms_str = device_alarm_settings["enabled_alarms"]
|
||||
armed_states = "00000000000"
|
||||
if "armed_states" in device_alarm_settings:
|
||||
armed_states = device_alarm_settings["armed_states"]
|
||||
#lets check alarms first, because if satisfied, no need to check for warning
|
||||
if enabled_alarms_str[BitIndex(1)] == "1" and armed_states[BitIndex(1)] == "1": #Too long present alarm if enabled and not already triggerred (triggered = 0)!
|
||||
if device_id == smallest[1]: #now present... how long?
|
||||
if (second_smallest[0] - smallest[0]) > device_alarm_settings["stuck_minutes_alarm"] * 60:
|
||||
#cancel alarm and warning, until re-armed
|
||||
armed_states = set_character(armed_states, 0, "0")
|
||||
armed_states = set_character(armed_states, 1, "0")
|
||||
dev_det = devices_details_map[device_id]
|
||||
location = (dev_det[4] + " " + dev_det[5].strip()).strip()
|
||||
method = device_alarm_settings["stuck_alarm_method_1"]
|
||||
if method.upper() != "PHONE":
|
||||
SendAlerts(deployment_id, method, f"Alarm: {user_first_last_name} way too long ({int((second_smallest[0] - smallest[0]) / 60)} minutes) in {location}", "", "")
|
||||
else:
|
||||
SendAlerts(deployment_id, method, f"Alarm: {user_first_last_name} way too long in {location}", "", "")
|
||||
|
||||
elif enabled_alarms_str[-1] == "1": #Too long present warning
|
||||
if (second_smallest[0] - smallest[0]) > device_alarm_settings["stuck_minutes_warning"] * 60:
|
||||
#cancel warning, until re-armed
|
||||
enabled_alarms_str = set_character(enabled_alarms_str, 0, "0")
|
||||
dev_det = devices_details_map[device_id]
|
||||
location = (dev_det[4] + " " + dev_det[5].strip()).strip()
|
||||
method = device_alarm_settings["stuck_warning_method_0"]
|
||||
if method.upper() != "PHONE":
|
||||
SendAlerts(deployment_id, method, f"Warning: {user_first_last_name} too long ({int((second_smallest[0] - smallest[0]) / 60)} minutes) in {location}", "", "")
|
||||
else:
|
||||
SendAlerts(deployment_id, method, f"Warning: {user_first_last_name} too long in {location}", "", "")
|
||||
device_alarm_settings["armed_states"] = armed_states
|
||||
StoreLastSentToRedis(deployment_id)
|
||||
device_alerts_all[device_id] = device_alarm_settings
|
||||
StoreDeviceAlarmsToRedis(device_id, device_alarm_settings)
|
||||
else:
|
||||
check_after = device_alarm_settings["stuck_minutes_alarm"] - int((second_smallest[0] - smallest[0])/60)
|
||||
if check_after < next_run_in_minutes:
|
||||
next_run_in_minutes = check_after
|
||||
|
||||
StoreLastSentToRedis(deployment_id)
|
||||
device_alarm_settings["enabled_alarms"] = enabled_alarms_str
|
||||
device_alerts_all[device_id] = device_alarm_settings
|
||||
StoreDeviceAlarmsToRedis(device_id, device_alarm_settings)
|
||||
else:
|
||||
check_after = device_alarm_settings["stuck_minutes_warning"] - int((second_smallest[0] - smallest[0])/60)
|
||||
if check_after < next_run_in_minutes:
|
||||
next_run_in_minutes = check_after
|
||||
elif enabled_alarms_str[BitIndex(0)] == "1" and armed_states[BitIndex(0)] == "1": #Too long present warning
|
||||
if (second_smallest[0] - smallest[0]) > device_alarm_settings["stuck_minutes_warning"] * 60:
|
||||
#cancel warning, until re-armed
|
||||
armed_states = set_character(armed_states, 0, "0")
|
||||
dev_det = devices_details_map[device_id]
|
||||
location = (dev_det[4] + " " + dev_det[5].strip()).strip()
|
||||
method = device_alarm_settings["stuck_warning_method_0"]
|
||||
if method.upper() != "PHONE":
|
||||
SendAlerts(deployment_id, method, f"Warning: {user_first_last_name} too long ({int((second_smallest[0] - smallest[0]) / 60)} minutes) in {location}", "", "")
|
||||
else:
|
||||
SendAlerts(deployment_id, method, f"Warning: {user_first_last_name} too long in {location}", "", "")
|
||||
|
||||
StoreLastSentToRedis(deployment_id)
|
||||
device_alarm_settings["armed_states"] = armed_states
|
||||
device_alerts_all[device_id] = device_alarm_settings
|
||||
StoreDeviceAlarmsToRedis(device_id, device_alarm_settings)
|
||||
else:
|
||||
check_after = device_alarm_settings["stuck_minutes_warning"] - int((second_smallest[0] - smallest[0])/60)
|
||||
if check_after < next_run_in_minutes:
|
||||
next_run_in_minutes = check_after
|
||||
|
||||
|
||||
if enabled_alarms_str[-4] == "1": #Too long absent alarm
|
||||
if last_seen_ago > device_alarm_settings["absent_minutes_alarm"] * 60:
|
||||
if enabled_alarms_str[BitIndex(3)] == "1" and armed_states[BitIndex(3)] == "1": #Too long absent alarm
|
||||
if last_seen_ago > device_alarm_settings["absent_minutes_alarm"] * 60:
|
||||
|
||||
enabled_alarms_str = set_character(enabled_alarms_str, 3, "0")
|
||||
enabled_alarms_str = set_character(enabled_alarms_str, 2, "0")
|
||||
dev_det = devices_details_map[device_id]
|
||||
location = (dev_det[4] + " " + dev_det[5].strip()).strip()
|
||||
method = device_alarm_settings["absent_alarm_method_3"]
|
||||
if method.upper() != "PHONE":
|
||||
SendAlerts(deployment_id, method, f"Alarm: Way too long since {user_first_last_name} visited {location} ({int(last_seen_ago / 60)} minutes)", "", "")
|
||||
else:
|
||||
SendAlerts(deployment_id, method, f"Alarm: Way too long since {user_first_last_name} visited {location}", "", "")
|
||||
device_alarm_settings["enabled_alarms"] = enabled_alarms_str
|
||||
device_alerts_all[device_id] = device_alarm_settings
|
||||
StoreLastSentToRedis(deployment_id)
|
||||
StoreDeviceAlarmsToRedis(device_id, device_alarm_settings)
|
||||
else:
|
||||
check_after = device_alarm_settings["absent_minutes_alarm"] - int(last_seen_ago/60)
|
||||
if check_after < next_run_in_minutes:
|
||||
next_run_in_minutes = check_after
|
||||
armed_states = set_character(armed_states, 3, "0")
|
||||
armed_states = set_character(armed_states, 2, "0")
|
||||
dev_det = devices_details_map[device_id]
|
||||
location = (dev_det[4] + " " + dev_det[5].strip()).strip()
|
||||
method = device_alarm_settings["absent_alarm_method_3"]
|
||||
if method.upper() != "PHONE":
|
||||
SendAlerts(deployment_id, method, f"Alarm: Way too long since {user_first_last_name} visited {location} ({int(last_seen_ago / 60)} minutes)", "", "")
|
||||
else:
|
||||
SendAlerts(deployment_id, method, f"Alarm: Way too long since {user_first_last_name} visited {location}", "", "")
|
||||
device_alarm_settings["armed_states"] = armed_states
|
||||
device_alerts_all[device_id] = device_alarm_settings
|
||||
StoreLastSentToRedis(deployment_id)
|
||||
StoreDeviceAlarmsToRedis(device_id, device_alarm_settings)
|
||||
else:
|
||||
check_after = device_alarm_settings["absent_minutes_alarm"] - int(last_seen_ago/60)
|
||||
if check_after < next_run_in_minutes:
|
||||
next_run_in_minutes = check_after
|
||||
|
||||
if enabled_alarms_str[-3] == "1": #Too long absent alarm
|
||||
if last_seen_ago > device_alarm_settings["absent_minutes_warning"] * 60:
|
||||
if enabled_alarms_str[BitIndex(2)] == "1" and armed_states[BitIndex(2)] == "1": #Too long absent alarm
|
||||
if last_seen_ago > device_alarm_settings["absent_minutes_warning"] * 60:
|
||||
|
||||
enabled_alarms_str = set_character(enabled_alarms_str, 2, "0")
|
||||
dev_det = devices_details_map[device_id]
|
||||
location = (dev_det[4] + " " + dev_det[5].strip()).strip()
|
||||
method = device_alarm_settings["absent_warning_method_2"]
|
||||
if method.upper() != "PHONE":
|
||||
SendAlerts(deployment_id, method, f"Warning: Too long since {user_first_last_name} visited {location} ({int(last_seen_ago / 60)} minutes)", "", "")
|
||||
else:
|
||||
SendAlerts(deployment_id, method, f"Warning: Too long since {user_first_last_name} visited {location}", "", "")
|
||||
armed_states = set_character(armed_states, 2, "0")
|
||||
dev_det = devices_details_map[device_id]
|
||||
location = (dev_det[4] + " " + dev_det[5].strip()).strip()
|
||||
method = device_alarm_settings["absent_warning_method_2"]
|
||||
if method.upper() != "PHONE":
|
||||
SendAlerts(deployment_id, method, f"Warning: Too long since {user_first_last_name} visited {location} ({int(last_seen_ago / 60)} minutes)", "", "")
|
||||
else:
|
||||
SendAlerts(deployment_id, method, f"Warning: Too long since {user_first_last_name} visited {location}", "", "")
|
||||
|
||||
device_alarm_settings["enabled_alarms"] = enabled_alarms_str
|
||||
device_alerts_all[device_id] = device_alarm_settings
|
||||
StoreLastSentToRedis(deployment_id)
|
||||
StoreDeviceAlarmsToRedis(device_id, device_alarm_settings)
|
||||
else:
|
||||
check_after = device_alarm_settings["absent_minutes_warning"] - int(last_seen_ago/60)
|
||||
if check_after < next_run_in_minutes:
|
||||
next_run_in_minutes = check_after
|
||||
device_alarm_settings["armed_states"] = armed_states
|
||||
device_alerts_all[device_id] = device_alarm_settings
|
||||
StoreLastSentToRedis(deployment_id)
|
||||
StoreDeviceAlarmsToRedis(device_id, device_alarm_settings)
|
||||
else:
|
||||
check_after = device_alarm_settings["absent_minutes_warning"] - int(last_seen_ago/60)
|
||||
if check_after < next_run_in_minutes:
|
||||
next_run_in_minutes = check_after
|
||||
|
||||
|
||||
#"stuck_warning_method_0": "SMS",
|
||||
#"stuck_alarm_method_1": "PHONE",
|
||||
#"absent_warning_method_2": "SMS",
|
||||
#"absent_alarm_method_3": "PHONE",
|
||||
#"temperature_high_warning_method_4": "SMS",
|
||||
#"temperature_high_alarm_method_5": "PHONE",
|
||||
#"temperature_low_warning_method_6": "SMS",
|
||||
#"temperature_low_alarm_method_7": "PHONE",
|
||||
#"radar_alarm_method_8":"MSG",
|
||||
#"pressure_alarm_method_9":"MSG",
|
||||
#"light_alarm_method_10":"MSG"
|
||||
#how to determine when user arived here (time of any other place!)
|
||||
if alarm_armed_settings_str[-7] == "1": #Too long alone
|
||||
pass #todo
|
||||
#"stuck_warning_method_0": "SMS",
|
||||
#"stuck_alarm_method_1": "PHONE",
|
||||
#"absent_warning_method_2": "SMS",
|
||||
#"absent_alarm_method_3": "PHONE",
|
||||
#"temperature_high_warning_method_4": "SMS",
|
||||
#"temperature_high_alarm_method_5": "PHONE",
|
||||
#"temperature_low_warning_method_6": "SMS",
|
||||
#"temperature_low_alarm_method_7": "PHONE",
|
||||
#"radar_alarm_method_8":"MSG",
|
||||
#"pressure_alarm_method_9":"MSG",
|
||||
#"light_alarm_method_10":"MSG"
|
||||
#how to determine when user arived here (time of any other place!)
|
||||
|
||||
|
||||
if next_run_in_minutes > 1:
|
||||
next_run_in_minutes = 1
|
||||
@ -3760,6 +3840,53 @@ def FahrenheitToCelsius(F):
|
||||
C = (F - 32) * 5/9
|
||||
return C
|
||||
|
||||
def BitIndex(bit_nr):
|
||||
return -bit_nr - 1
|
||||
|
||||
def RadarLarger(radar, radar_threshold):
|
||||
|
||||
thr_parts = radar_threshold[0].split("_")
|
||||
field = thr_parts[0]
|
||||
if field == "s28":
|
||||
radar_val = sum(radar[1][3:]) / (7 * radar[1][0])
|
||||
elif field == "m0":
|
||||
radar_val = radar[0][5] / radar[1][0]
|
||||
elif field == "m1":
|
||||
radar_val = radar[0][6] / radar[1][0]
|
||||
elif field == "m2":
|
||||
radar_val = radar[0][7] / radar[1][0]
|
||||
elif field == "m3":
|
||||
radar_val = radar[0][8] / radar[1][0]
|
||||
elif field == "m4":
|
||||
radar_val = radar[0][9] / radar[1][0]
|
||||
elif field == "m5":
|
||||
radar_val = radar[0][10] / radar[1][0]
|
||||
elif field == "m6":
|
||||
radar_val = radar[0][11] / radar[1][0]
|
||||
elif field == "m7":
|
||||
radar_val = radar[0][12] / radar[1][0]
|
||||
elif field == "m8":
|
||||
radar_val = radar[0][13] / radar[1][0]
|
||||
elif field == "s2":
|
||||
radar_val = radar[1][3] / radar[1][0]
|
||||
elif field == "s3":
|
||||
radar_val = radar[1][4] / radar[1][0]
|
||||
elif field == "s4":
|
||||
radar_val = radar[1][5] / radar[1][0]
|
||||
elif field == "s5":
|
||||
radar_val = radar[1][6] / radar[1][0]
|
||||
elif field == "s6":
|
||||
radar_val = radar[1][7] / radar[1][0]
|
||||
elif field == "s7":
|
||||
radar_val = radar[1][8] / radar[1][0]
|
||||
elif field == "s8":
|
||||
radar_val = radar[1][9] / radar[1][0]
|
||||
|
||||
if radar_val > radar_threshold[1]:
|
||||
return radar_val, True
|
||||
else:
|
||||
return radar_val, False
|
||||
|
||||
def ProcessQueue():
|
||||
#here we are looking for alarm conditions in data
|
||||
global in_queue
|
||||
@ -3774,209 +3901,265 @@ def ProcessQueue():
|
||||
deployment_id = device_to_deployment[device_id]
|
||||
if deployment_id in alarms_settings_all:
|
||||
alarms_settings = alarms_settings_all[deployment_id]
|
||||
alarm_armed_settings_str = alarms_settings["armed"]
|
||||
alarm_armed_settings_str = alarms_settings["enabled"]
|
||||
# bit 2: Burglar Alarm (index 0), bit 1: Time Alone Alarm is Set (index 1), bit 0: Time alone Warning is Set (index 2)
|
||||
|
||||
#0: 0 = default, 1 = set (so I can distinquish between nothing set (0s default) and purposefully set to 0s)
|
||||
#1: Home Security bit 0 = not used, 1 = alarm armed
|
||||
#2: Warning level environmental condition (Yellow) Temperatures High/Low
|
||||
#3: Alarm level environmental condition (Red) Temperatures High/Low
|
||||
#4: Warning level medical condition (Yellow) Too long present/absent
|
||||
#5: Alarm level medical condition (Red) Too long present/absent
|
||||
#6: Alarm if alone too long
|
||||
temp_offset = -16.0
|
||||
if alarm_armed_settings_str[-1] == "1": #used
|
||||
if device_id in device_alerts_all:
|
||||
device_alarm_settings = device_alerts_all[device_id]
|
||||
message_dict = json.loads(messagein.decode('utf-8'))
|
||||
#print(alarm_armed_settings_str)
|
||||
if alarm_armed_settings_str[-2] == "0": #alarm not armed, so compare individual conditions
|
||||
if alarm_armed_settings_str[-3] == "1" or alarm_armed_settings_str[-4] == "1": #Temperatures Too High/Low
|
||||
if "enabled_alarms" in device_alarm_settings:
|
||||
#Lets check temperatures
|
||||
temp_offset = -16.0
|
||||
if "1" in device_alarm_settings["enabled_alarms"][BitIndex(7):BitIndex(3)]: #Temperatures Too High/Low trigger used?
|
||||
enabled_alarms_str = device_alarm_settings["enabled_alarms"]
|
||||
armed_states = device_alarm_settings["armed_states"] #1=armed 0=triggered
|
||||
#print(alarm_armed_settings_str)
|
||||
|
||||
if "temperature" in message_dict:
|
||||
temperature = message_dict["temperature"] + temp_offset
|
||||
#at this point temperature is in C
|
||||
|
||||
if temperature > 0 and temperature < 100: #ignore others
|
||||
if device_id in device_alerts_all:
|
||||
|
||||
device_alarm_settings = device_alerts_all[device_id]
|
||||
enabled_alarms_str = device_alarm_settings["enabled_alarms"]
|
||||
|
||||
'''
|
||||
{
|
||||
"enabled_alarms":"000000000101",
|
||||
"armed_states":"000000000000",
|
||||
"stuck_minutes_warning":"771.3",
|
||||
"stuck_warning_method_0":"SMS",
|
||||
"stuck_minutes_alarm":600,
|
||||
"stuck_alarm_method_1":"PHONE",
|
||||
"absent_minutes_warning":"-1013.4",
|
||||
"absent_warning_method_2":"SMS",
|
||||
"absent_minutes_alarm":30,
|
||||
"absent_alarm_method_3":"PHONE",
|
||||
"temperature_high_warning":"85",
|
||||
"temperature_high_warning_method_4":
|
||||
"SMS","temperature_high_alarm":"95",
|
||||
"temperature_high_alarm_method_5":"PHONE",
|
||||
"temperature_low_warning":"60",
|
||||
"temperature_low_warning_method_6":"SMS",
|
||||
"temperature_low_alarm":"50",
|
||||
"temperature_low_alarm_method_7":"PHONE",
|
||||
"radar_alarm_method_8":"MSG",
|
||||
"pressure_alarm_method_9":"MSG",
|
||||
"light_alarm_method_10":"MSG",
|
||||
"smell_alarm_method_11":"EMAIL",
|
||||
"rearm_policy":"At midnight"
|
||||
}
|
||||
'''
|
||||
'''
|
||||
{
|
||||
"enabled_alarms":"000000000101",
|
||||
"armed_states":"000000000000",
|
||||
"stuck_minutes_warning":"771.3",
|
||||
"stuck_warning_method_0":"SMS",
|
||||
"stuck_minutes_alarm":600,
|
||||
"stuck_alarm_method_1":"PHONE",
|
||||
"absent_minutes_warning":"-1013.4",
|
||||
"absent_warning_method_2":"SMS",
|
||||
"absent_minutes_alarm":30,
|
||||
"absent_alarm_method_3":"PHONE",
|
||||
"temperature_high_warning":"85",
|
||||
"temperature_high_warning_method_4":
|
||||
"SMS","temperature_high_alarm":"95",
|
||||
"temperature_high_alarm_method_5":"PHONE",
|
||||
"temperature_low_warning":"60",
|
||||
"temperature_low_warning_method_6":"SMS",
|
||||
"temperature_low_alarm":"50",
|
||||
"temperature_low_alarm_method_7":"PHONE",
|
||||
"radar_alarm_method_8":"MSG",
|
||||
"pressure_alarm_method_9":"MSG",
|
||||
"light_alarm_method_10":"MSG",
|
||||
"smell_alarm_method_11":"EMAIL",
|
||||
"rearm_policy":"At midnight"
|
||||
}
|
||||
'''
|
||||
#bits in enabled_alarms and armed_states explained
|
||||
#"stuck_warning_method_0": "SMS", -1
|
||||
#"stuck_alarm_method_1": "PHONE", -2
|
||||
#"absent_warning_method_2": "SMS", -3
|
||||
#"absent_alarm_method_3": "PHONE", -4
|
||||
#"temperature_high_warning_method_4": "SMS", -5
|
||||
#"temperature_high_alarm_method_5": "PHONE", -6
|
||||
#"temperature_low_warning_method_6": "SMS", -7
|
||||
#"temperature_low_alarm_method_7": "PHONE", -8
|
||||
#"radar_alarm_method_8":"MSG", -9
|
||||
#"pressure_alarm_method_9":"MSG", -10
|
||||
#"light_alarm_method_10":"MSG" -11
|
||||
#"smell_alarm_method_11":"MSG" -12
|
||||
#hast ot be enabled and not triggerred to continue comparing
|
||||
if enabled_alarms_str[BitIndex(5)] == "1" and armed_states[BitIndex(5)] == "1": #Temperatures Too High Alarm!
|
||||
if temperature > FahrenheitToCelsius(device_alarm_settings["temperature_high_alarm"]):
|
||||
#cancel alarm and warning, until re-armed
|
||||
armed_states = set_character(armed_states, 5, "0")
|
||||
armed_states = set_character(armed_states, 4, "0")
|
||||
dev_det = devices_details_map[device_id]
|
||||
location = (dev_det[4] + " " + dev_det[5].strip()).strip()
|
||||
method = device_alarm_settings["temperature_high_alarm_method_5"]
|
||||
first_last_name = GetBeneficiaryFromDeployment(deployment_id)
|
||||
SendAlerts(deployment_id, method, f"Alarm @ {first_last_name}: Temperature too high! ({temperature:.1f} C) in {location}", "", "")
|
||||
device_alarm_settings["armed_states"] = armed_states
|
||||
device_alerts_all[device_id] = device_alarm_settings
|
||||
StoreAllToRedisAndDB(deployment_id, device_id)
|
||||
|
||||
#"stuck_warning_method_0": "SMS", -1
|
||||
#"stuck_alarm_method_1": "PHONE", -2
|
||||
#"absent_warning_method_2": "SMS", -3
|
||||
#"absent_alarm_method_3": "PHONE", -4
|
||||
#"temperature_high_warning_method_4": "SMS", -5
|
||||
#"temperature_high_alarm_method_5": "PHONE", -6
|
||||
#"temperature_low_warning_method_6": "SMS", -7
|
||||
#"temperature_low_alarm_method_7": "PHONE", -8
|
||||
#"radar_alarm_method_8":"MSG", -9
|
||||
#"pressure_alarm_method_9":"MSG", -10
|
||||
#"light_alarm_method_10":"MSG" -11
|
||||
#"smell_alarm_method_11":"MSG" -12
|
||||
#at this point we also need to store it to DB, otherwise when HTML GUI is loaded fact that trigger happened will not be reflected!
|
||||
elif enabled_alarms_str[BitIndex(4)] == "1" and armed_states[BitIndex(4)] == "1": #Temperatures Too High Warning!
|
||||
|
||||
if enabled_alarms_str[-6] == "1": #Temperatures Too High Alarm!
|
||||
if temperature > FahrenheitToCelsius(device_alarm_settings["temperature_high_alarm"]):
|
||||
#cancel alarm and warning, until re-armed
|
||||
enabled_alarms_str = set_character(enabled_alarms_str, 5, "0")
|
||||
enabled_alarms_str = set_character(enabled_alarms_str, 4, "0")
|
||||
if temperature > FahrenheitToCelsius(device_alarm_settings["temperature_high_warning"]):
|
||||
#cancel warning, until re-armed
|
||||
armed_states = set_character(armed_states, 4, "0")
|
||||
dev_det = devices_details_map[device_id]
|
||||
location = (dev_det[4] + " " + dev_det[5].strip()).strip()
|
||||
method = device_alarm_settings["temperature_high_warning_method_4"]
|
||||
first_last_name = GetBeneficiaryFromDeployment(deployment_id)
|
||||
SendAlerts(deployment_id, method, f"Warning @ {first_last_name}: Temperature too high! ({temperature:.1f} C) in {location}", "", "")
|
||||
device_alarm_settings["armed_states"] = armed_states
|
||||
device_alerts_all[device_id] = device_alarm_settings
|
||||
StoreAllToRedisAndDB(deployment_id, device_id)
|
||||
|
||||
if armed_states[BitIndex(7)] == "1" and armed_states[BitIndex(7)] == "1": #Temperatures Too Low Alarm!
|
||||
if temperature < FahrenheitToCelsius(device_alarm_settings["temperature_low_alarm"]):
|
||||
#cancel alarm and warning, until re-armed
|
||||
armed_states = set_character(armed_states, 7, "0")
|
||||
armed_states = set_character(armed_states, 6, "0")
|
||||
dev_det = devices_details_map[device_id]
|
||||
location = (dev_det[4] + " " + dev_det[5].strip()).strip()
|
||||
method = device_alarm_settings["temperature_low_alarm_method_7"]
|
||||
first_last_name = GetBeneficiaryFromDeployment(deployment_id)
|
||||
SendAlerts(deployment_id, method, f"Alarm @ {first_last_name} Temperature too low! ({temperature:.1f} C) in {location}", "", "")
|
||||
device_alarm_settings["armed_states"] = armed_states
|
||||
device_alerts_all[device_id] = device_alarm_settings
|
||||
StoreAllToRedisAndDB(deployment_id, device_id)
|
||||
|
||||
elif armed_states[BitIndex(6)] == "1" and armed_states[BitIndex(6)] == "1": #Temperatures Too Low Warning!
|
||||
|
||||
if temperature < FahrenheitToCelsius(device_alarm_settings["temperature_low_warning"]):
|
||||
#cancel warning, until re-armed
|
||||
armed_states = set_character(armed_states, 6, "0")
|
||||
dev_det = devices_details_map[device_id]
|
||||
location = (dev_det[4] + " " + dev_det[5].strip()).strip()
|
||||
method = device_alarm_settings["temperature_high_alarm_method_5"]
|
||||
SendAlerts(deployment_id, method, f"Alarm @ {first_last_name}: Temperature too high! ({temperature} C) in {location}", "", "")
|
||||
device_alarm_settings["enabled_alarms"] = enabled_alarms_str
|
||||
method = device_alarm_settings["temperature_low_warning_method_6"]
|
||||
first_last_name = GetBeneficiaryFromDeployment(deployment_id)
|
||||
SendAlerts(deployment_id, method, f"Warning @ {first_last_name} Temperature too low! ({temperature:.1f} C) in {location}", "", "")
|
||||
device_alarm_settings["armed_states"] = armed_states
|
||||
device_alerts_all[device_id] = device_alarm_settings
|
||||
StoreLastSentToRedis(deployment_id)
|
||||
StoreDeviceAlarmsToRedis(device_id, device_alarm_settings)
|
||||
elif enabled_alarms_str[-5] == "1": #Temperatures Too High Warning!
|
||||
StoreAllToRedisAndDB(deployment_id, device_id)
|
||||
|
||||
if temperature > FahrenheitToCelsius(device_alarm_settings["temperature_high_warning"]):
|
||||
#cancel alarm and warning, until re-armed
|
||||
enabled_alarms_str = set_character(enabled_alarms_str, 4, "0")
|
||||
dev_det = devices_details_map[device_id]
|
||||
location = (dev_det[4] + " " + dev_det[5].strip()).strip()
|
||||
method = device_alarm_settings["temperature_high_warning_method_4"]
|
||||
SendAlerts(deployment_id, method, f"Warning @ {first_last_name}: Temperature too high! ({temperature} C) in {location}", "", "")
|
||||
device_alarm_settings["enabled_alarms"] = enabled_alarms_str
|
||||
device_alerts_all[device_id] = device_alarm_settings
|
||||
StoreLastSentToRedis(deployment_id)
|
||||
StoreDeviceAlarmsToRedis(device_id, device_alarm_settings)
|
||||
if enabled_alarms_str[-8] == "1": #Temperatures Too Low Alarm!
|
||||
if temperature < FahrenheitToCelsius(device_alarm_settings["temperature_low_alarm"]):
|
||||
#cancel alarm and warning, until re-armed
|
||||
enabled_alarms_str = set_character(enabled_alarms_str, 7, "0")
|
||||
enabled_alarms_str = set_character(enabled_alarms_str, 6, "0")
|
||||
dev_det = devices_details_map[device_id]
|
||||
location = (dev_det[4] + " " + dev_det[5].strip()).strip()
|
||||
method = device_alarm_settings["temperature_low_alarm_method_7"]
|
||||
SendAlerts(deployment_id, method, f"Alarm @ {first_last_name} Temperature too low! ({temperature} C) in {location}", "", "")
|
||||
device_alarm_settings["enabled_alarms"] = enabled_alarms_str
|
||||
device_alerts_all[device_id] = device_alarm_settings
|
||||
StoreLastSentToRedis(deployment_id)
|
||||
StoreDeviceAlarmsToRedis(device_id, device_alarm_settings)
|
||||
|
||||
elif enabled_alarms_str[-7] == "1": #Temperatures Too Low Warning!
|
||||
|
||||
if temperature < FahrenheitToCelsius(device_alarm_settings["temperature_low_warning"]):
|
||||
#cancel alarm and warning, until re-armed
|
||||
enabled_alarms_str = set_character(enabled_alarms_str, 6, "0")
|
||||
dev_det = devices_details_map[device_id]
|
||||
location = (dev_det[4] + " " + dev_det[5].strip()).strip()
|
||||
method = device_alarm_settings["temperature_low_warning_method_6"]
|
||||
SendAlerts(deployment_id, method, f"Warning @ {first_last_name} Temperature too low! ({temperature} C) in {location}", "", "")
|
||||
device_alarm_settings["enabled_alarms"] = enabled_alarms_str
|
||||
device_alerts_all[device_id] = device_alarm_settings
|
||||
StoreLastSentToRedis(deployment_id)
|
||||
StoreDeviceAlarmsToRedis(device_id, device_alarm_settings)
|
||||
|
||||
logger.info(f"{tim}, {mac}, {temperature}")
|
||||
#logger.info(f"{tim}, {mac}, {temperature}")
|
||||
else: #radar packet
|
||||
pass
|
||||
else: #alarm is armed
|
||||
if "radar" in message_dict:
|
||||
radar = message_dict["radar"]
|
||||
|
||||
#lets check if alarm condition
|
||||
if alarm_armed_settings_str[BitIndex(2)] == "1": #alarm is armed!
|
||||
if "radar" in message_dict:
|
||||
enabled_alarms_str = device_alarm_settings["enabled_alarms"]
|
||||
armed_states = device_alarm_settings["armed_states"] #1=armed 0=triggered
|
||||
if enabled_alarms_str[BitIndex(8)] == "1" and armed_states[BitIndex(8)] == "1":
|
||||
radar = message_dict["radar"]
|
||||
radar_threshold_str = GetRedisString(f"radar_threshold{device_id}")
|
||||
radar_threshold = ast.literal_eval(radar_threshold_str)
|
||||
radar_value, larger = RadarLarger(radar, radar_threshold)
|
||||
print(device_id, radar_value, larger)
|
||||
if larger:
|
||||
|
||||
#cancel alarm for this room, until re-armed
|
||||
armed_states = set_character(armed_states, 8, "0")
|
||||
dev_det = devices_details_map[device_id]
|
||||
location = (dev_det[4] + " " + dev_det[5].strip()).strip()
|
||||
method = device_alarm_settings["radar_alarm_method_8"]
|
||||
first_last_name = GetBeneficiaryFromDeployment(deployment_id)
|
||||
SendAlerts(deployment_id, method, f"At {first_last_name} burglar alarm is triggered in the {location}", "", "")
|
||||
device_alarm_settings["armed_states"] = armed_states
|
||||
device_alerts_all[device_id] = device_alarm_settings
|
||||
StoreAllToRedisAndDB(deployment_id, device_id)
|
||||
|
||||
|
||||
|
||||
else:
|
||||
pass #alarm not setup for this device
|
||||
#print(f"{deployment_id} not in {alarms_settings_all}")
|
||||
else:
|
||||
logger.error(f"MAC: {mac} not part of any deployment")
|
||||
pass
|
||||
#logger.error(f"MAC: {mac} not part of any deployment")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error: {str(e)} {traceback.format_exc()}")
|
||||
|
||||
def CheckMessageSends():
|
||||
def CheckRedisMessages():
|
||||
|
||||
requests_count = 0
|
||||
|
||||
#print(f"CheckRedisMessages")
|
||||
# Check if queue exists and has items. These items are from manual GUI interactions in alerts page
|
||||
|
||||
#Any test messages requested to be sent?
|
||||
queue_length = redis_conn.llen('send_requests')
|
||||
if queue_length > 0:
|
||||
|
||||
if queue_length == 0:
|
||||
return 0
|
||||
print(f"Processing send_requests message from queue...")
|
||||
|
||||
print(f"Processing {queue_length} messages from queue...")
|
||||
# Process each item
|
||||
for i in range(queue_length):
|
||||
item_json = redis_conn.rpop('send_requests')
|
||||
|
||||
# Process each item
|
||||
for i in range(queue_length):
|
||||
item_json = redis_conn.rpop('send_requests')
|
||||
if item_json is None:
|
||||
break
|
||||
|
||||
if item_json is None:
|
||||
break
|
||||
try:
|
||||
record = json.loads(item_json)
|
||||
requests_count += 1
|
||||
|
||||
try:
|
||||
record = json.loads(item_json)
|
||||
requests_count += 1
|
||||
# Print the record
|
||||
print(f"Request #{requests_count}:")
|
||||
for key, value in record.items():
|
||||
print(f" {key}: {value}")
|
||||
|
||||
# Print the record
|
||||
print(f"Request #{requests_count}:")
|
||||
for key, value in record.items():
|
||||
print(f" {key}: {value}")
|
||||
method = record["method"]
|
||||
location_str = record["location"]
|
||||
location = location_str.split("_")[2]
|
||||
deployment_id = record["deployment_id"]
|
||||
content = record["content"]
|
||||
feature = record["feature"]
|
||||
enabledCellContent = record["enabledCellContent"]
|
||||
currentUnits = record["currentUnits"]
|
||||
currentAlertTableMode = record["currentAlertTableMode"] #Warning/Alarm
|
||||
test_only = record["test_only"]
|
||||
#action = record["action"]
|
||||
user_name = record["user_name"]
|
||||
user_first_last_name = GetBeneficiaryFromDeployment(deployment_id)
|
||||
|
||||
method = record["method"]
|
||||
location_str = record["location"]
|
||||
location = location_str.split("_")[2]
|
||||
deployment_id = record["deployment_id"]
|
||||
content = record["content"]
|
||||
feature = record["feature"]
|
||||
enabledCellContent = record["enabledCellContent"]
|
||||
currentUnits = record["currentUnits"]
|
||||
currentAlertTableMode = record["currentAlertTableMode"] #Warning/Alarm
|
||||
test_only = record["test_only"]
|
||||
#action = record["action"]
|
||||
user_name = record["user_name"]
|
||||
user_first_last_name = GetBeneficiaryFromDeployment(deployment_id)
|
||||
if feature == "stuck":
|
||||
msg_ext = f"{currentAlertTableMode}: {content} {user_first_last_name} is spending more than {enabledCellContent} in {location}"
|
||||
elif feature == "absent":
|
||||
msg_ext = f"{currentAlertTableMode}: {content} {user_first_last_name} did not visit {location} in more than {enabledCellContent[1:-1]} {currentUnits}"
|
||||
elif feature == "tempLow":
|
||||
msg_ext = f"{currentAlertTableMode}: {content} temperature is lower then {enabledCellContent} {currentUnits} in {location} at {user_first_last_name}"
|
||||
elif feature == "tempHigh":
|
||||
msg_ext = f"{currentAlertTableMode}: {content} temperature is higher then {enabledCellContent} {currentUnits} in {location} at {user_first_last_name}"
|
||||
elif feature == "pressure":
|
||||
msg_ext = f"{currentAlertTableMode}: {content} door was opened or closed in the {location} at {user_first_last_name}"
|
||||
elif feature == "radar":
|
||||
msg_ext = f"{currentAlertTableMode}: {content} motion detected in the {location} at {user_first_last_name}"
|
||||
else:
|
||||
msg_ext = f"{currentAlertTableMode}: {content} {feature} in {location} at {user_first_last_name}"
|
||||
|
||||
if feature == "stuck":
|
||||
msg_ext = f"{currentAlertTableMode}: {content} {user_first_last_name} is spending more than {enabledCellContent} in {location}"
|
||||
elif feature == "absent":
|
||||
msg_ext = f"{currentAlertTableMode}: {content} {user_first_last_name} did not visit {location} in more than {enabledCellContent[1:-1]} {currentUnits}"
|
||||
elif feature == "tempLow":
|
||||
msg_ext = f"{currentAlertTableMode}: {content} temperature is lower then {enabledCellContent} {currentUnits} in {location} at {user_first_last_name}"
|
||||
elif feature == "tempHigh":
|
||||
msg_ext = f"{currentAlertTableMode}: {content} temperature is higher then {enabledCellContent} {currentUnits} in {location} at {user_first_last_name}"
|
||||
elif feature == "pressure":
|
||||
msg_ext = f"{currentAlertTableMode}: {content} door was opened or closed in the {location} at {user_first_last_name}"
|
||||
elif feature == "radar":
|
||||
msg_ext = f"{currentAlertTableMode}: {content} motion detected in the {location} at {user_first_last_name}"
|
||||
else:
|
||||
msg_ext = f"{currentAlertTableMode}: {content} {feature} in {location} at {user_first_last_name}"
|
||||
SendAlerts(deployment_id, method, msg_ext, "Test message", user_name)
|
||||
#these are testing messages, so do not count them as real triggered... so do not update in REDIS
|
||||
#StoreLastSentToRedis(deployment_id)
|
||||
|
||||
SendAlerts(deployment_id, method, msg_ext, "Test message", user_name)
|
||||
#these are testing messages, so do not count them as real triggered... so do not update in REDIS
|
||||
#StoreLastSentToRedis(deployment_id)
|
||||
print("-" * 40)
|
||||
|
||||
print("-" * 40)
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Failed to parse JSON from queue item: {e}")
|
||||
continue
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Failed to parse JSON from queue item: {e}")
|
||||
continue
|
||||
#Any test messages requested to be sent?
|
||||
queue_length = redis_conn.llen('new_alarms')
|
||||
if queue_length > 0:
|
||||
for i in range(queue_length):
|
||||
|
||||
print(f"Total requests processed: {requests_count}")
|
||||
print(f"Processing send_requests message from queue...")
|
||||
item_json = redis_conn.rpop('new_alarms')
|
||||
|
||||
if item_json is None:
|
||||
break
|
||||
|
||||
try:
|
||||
record = json.loads(item_json)
|
||||
deployment_id = int(record["deployment_id"])
|
||||
device_id = int(record["device_id"])
|
||||
print(record)
|
||||
|
||||
device_alarms_json = GetRedisString('alarm_device_settings_'+str(device_id))
|
||||
deployment_alarms_json = GetRedisString('alarm_deployment_settings_'+str(deployment_id))
|
||||
|
||||
alarms_settings_all[deployment_id] = json.loads(deployment_alarms_json)
|
||||
device_alerts_all[device_id] = json.loads(device_alarms_json)
|
||||
print(device_alarms_json)
|
||||
print(deployment_alarms_json)
|
||||
|
||||
#method = record["method"]
|
||||
#location_str = record["location"]
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Failed to parse JSON from queue item: {e}")
|
||||
continue
|
||||
|
||||
#print(f"Total requests processed: {requests_count}")
|
||||
return requests_count
|
||||
|
||||
# --- Main Execution ---
|
||||
@ -4015,6 +4198,7 @@ if __name__ == "__main__":
|
||||
SendAlerts(21, "EMAIL", f"well-alert was started", "Program started", "robster")
|
||||
#SendAlerts(21, "SMS", f"Test: User way too long ({120} minutes) in Bathroom", "")
|
||||
#SendAlerts(21, "PHONE", f"Test: User way too long ({120} minutes) in Bathroom", "")
|
||||
SendPhoneCall("-4086462191", "Hi Robert. How are you?")
|
||||
#SendPhoneCall("4086462191", "Hi Robert. How are you?", "")
|
||||
#SendPhoneCall("4085505424", "Hi Fred. How are you? Are you hungry?", "")
|
||||
#SendPhoneCall("4087055709", "Hi Bernhard. How are you? Are you hungry?", "")
|
||||
@ -4033,7 +4217,7 @@ if __name__ == "__main__":
|
||||
# Keep the main thread alive until stop_event is set by signal or error
|
||||
while not stop_event.is_set():
|
||||
# Can add periodic health checks here if needed
|
||||
CheckMessageSends()
|
||||
CheckRedisMessages()
|
||||
time.sleep(1) # Check stop_event periodically
|
||||
|
||||
logger.info("Stop event received, waiting for monitoring thread to finish...")
|
||||
|
||||
222
wh1998v2.py
Normal file
222
wh1998v2.py
Normal file
@ -0,0 +1,222 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
import re
|
||||
import base64
|
||||
from flask import Flask, request, Response, jsonify
|
||||
import logging
|
||||
import logging.handlers
|
||||
import sys
|
||||
import argparse
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# --- Configuration Loading ---
|
||||
def load_env_file(filepath):
|
||||
if not os.path.exists(filepath): return False
|
||||
print(f"INFO: Loading environment file: {filepath}")
|
||||
try:
|
||||
load_dotenv(dotenv_path=filepath, override=True); return True
|
||||
except Exception as e: print(f"ERROR: Failed to load env file {filepath}: {e}"); return False
|
||||
|
||||
env_loaded = any(load_env_file(os.path.join(os.getcwd(), f)) for f in ['.env', 'env'])
|
||||
if not env_loaded: print("INFO: No .env or env file found.")
|
||||
|
||||
# --- Global Configuration ---
|
||||
LOG_LEVEL_DEFAULT = os.environ.get("LOG_LEVEL", "INFO").upper()
|
||||
LOG_FILE_NAME_DEFAULT = os.environ.get("LOG_FILE_PATH", "webhook.log")
|
||||
TELNYX_API_KEY_DEFAULT = os.environ.get("TELNYX_API_KEY", None)
|
||||
TELNYX_API_BASE_URL = os.environ.get("TELNYX_API_BASE_URL", "https://api.telnyx.com/v2")
|
||||
DEFAULT_TTS_VOICE_DEFAULT = os.environ.get("TELNYX_DEFAULT_TTS_VOICE", "female")
|
||||
DEFAULT_TTS_LANGUAGE_DEFAULT = os.environ.get("TELNYX_DEFAULT_TTS_LANGUAGE", "en-US")
|
||||
CLIENT_STATE_PREFIX_DEFAULT = os.environ.get("TELNYX_CLIENT_STATE_PREFIX", "app_state")
|
||||
DTMF_TIMEOUT_SECONDS_DEFAULT = int(os.environ.get("DTMF_TIMEOUT_SECONDS", 10))
|
||||
INBOUND_GREETING_DEFAULT = os.environ.get("INBOUND_GREETING", "Thank you for calling Wellnuo. We are processing your request. Goodbye.")
|
||||
|
||||
# --- Application Setup ---
|
||||
app = Flask(__name__)
|
||||
app_logger = logging.getLogger("TelnyxWebhookApp_Fixed")
|
||||
|
||||
# --- Helper Functions ---
|
||||
def setup_logging(level, file_path, name="TelnyxWebhookApp_Fixed"):
|
||||
global app_logger; app_logger = logging.getLogger(name)
|
||||
numeric_level = getattr(logging, level.upper(), logging.INFO)
|
||||
app_logger.setLevel(numeric_level); app_logger.propagate = False
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - [%(funcName)s:%(lineno)d] - %(message)s')
|
||||
if app_logger.hasHandlers(): app_logger.handlers.clear()
|
||||
console_handler = logging.StreamHandler(sys.stdout); console_handler.setFormatter(formatter); app_logger.addHandler(console_handler)
|
||||
try:
|
||||
file_handler = logging.handlers.RotatingFileHandler(file_path, maxBytes=10*1024*1024, backupCount=5, encoding='utf-8')
|
||||
file_handler.setFormatter(formatter); app_logger.addHandler(file_handler)
|
||||
app_logger.info(f"Logging configured. Level: {level}. File: {os.path.abspath(file_path)}")
|
||||
except Exception as e: app_logger.error(f"Failed to config file logger at {file_path}: {e}")
|
||||
|
||||
def find_custom_header(headers, name):
|
||||
if not headers: return None
|
||||
for header in headers:
|
||||
if header.get('name', '').lower() == name.lower(): return header.get('value')
|
||||
return None
|
||||
|
||||
def encode_state(parts):
|
||||
plain_state = "|".join(map(str, parts))
|
||||
base64_state = base64.b64encode(plain_state.encode('utf-8')).decode('ascii')
|
||||
app_logger.debug(f"Encoded state: '{plain_state}' -> '{base64_state}'")
|
||||
return base64_state
|
||||
|
||||
def decode_state(b64_state):
|
||||
if not b64_state: return []
|
||||
try:
|
||||
decoded_plain = base64.b64decode(b64_state).decode('utf-8')
|
||||
parts = decoded_plain.split('|'); app_logger.debug(f"Decoded state: '{b64_state}' -> '{decoded_plain}' -> {parts}"); return parts
|
||||
except Exception as e: app_logger.error(f"Failed to decode client_state '{b64_state}': {e}"); return []
|
||||
|
||||
def send_telnyx_command(action_path, params, api_key):
|
||||
if not api_key: app_logger.error(f"CMDFAIL ('{action_path}'): API_KEY not set."); return None
|
||||
ccid = params.get("call_control_id");
|
||||
if not ccid: app_logger.error(f"CMDFAIL ('{action_path}'): call_control_id missing."); return None
|
||||
endpoint = f"{TELNYX_API_BASE_URL}/calls/{ccid}/{action_path}";
|
||||
body = {k: v for k, v in params.items() if k != 'call_control_id'}
|
||||
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "Accept": "application/json"}
|
||||
app_logger.info(f"SENDCMD ('{action_path}')"); app_logger.debug(f" Endpoint: POST {endpoint}"); app_logger.debug(f" JSON Payload: {json.dumps(body, indent=2)}")
|
||||
try:
|
||||
r = requests.post(endpoint, json=body, headers=headers, timeout=10)
|
||||
r.raise_for_status()
|
||||
app_logger.info(f"CMDOK ('{action_path}'): Telnyx accepted. Status: {r.status_code}"); return r.json()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
app_logger.error(f"CMDFAIL ('{action_path}'): Telnyx rejected. Status: {e.response.status_code}")
|
||||
try: app_logger.error(f" Telnyx Err Detail: {json.dumps(e.response.json(), indent=2)}")
|
||||
except json.JSONDecodeError: app_logger.error(f" Raw Err Body: {e.response.text[:500]}")
|
||||
except requests.exceptions.RequestException as e: app_logger.exception(f"CMDFAIL ('{action_path}'): Network error")
|
||||
return None
|
||||
|
||||
# --- Webhook Route Handler ---
|
||||
@app.route('/<path:webhook_path_received>', methods=['POST'])
|
||||
def handle_telnyx_webhook(webhook_path_received):
|
||||
global app_args
|
||||
if f"/{webhook_path_received}" != app_args.webhook_path: app_logger.warning(f"REQ Unknown Path: '/{webhook_path_received}'"); return "Not found", 404
|
||||
app_logger.info(f"REQ <<< Path: '/{webhook_path_received}', From: {request.remote_addr}")
|
||||
try:
|
||||
webhook_data = request.get_json(); app_logger.debug(f"REQ Payload Full: {json.dumps(webhook_data, indent=2)}")
|
||||
data, payload = webhook_data.get('data', {}), webhook_data.get('data', {}).get('payload', {})
|
||||
event_type, record_type, ccid = data.get('event_type'), data.get('record_type'), payload.get('call_control_id')
|
||||
app_logger.info(f"EVENT '{event_type}' ({record_type})" + (f", CCID: {ccid}" if ccid else ""))
|
||||
|
||||
if record_type == 'message':
|
||||
app_logger.info(f" -> SMS Event received. From: {payload.get('from',{}).get('phone_number')}, Text: '{payload.get('text','')}'")
|
||||
return Response(status=204)
|
||||
elif record_type != 'event':
|
||||
app_logger.warning(f" Unknown Record Type '{record_type}'. Ignoring."); return Response(status=204)
|
||||
|
||||
b64_client_state = payload.get("client_state"); decoded_parts = decode_state(b64_client_state)
|
||||
state_name = decoded_parts[0] if decoded_parts else None
|
||||
if state_name: app_logger.info(f" State Name Received: '{state_name}'")
|
||||
current_api_key = app_args.api_key
|
||||
|
||||
# --- State Machine Logic ---
|
||||
if event_type == 'call.answered':
|
||||
if payload.get('direction') == 'incoming':
|
||||
app_logger.info(" -> Inbound call detected. Playing generic greeting and hanging up.")
|
||||
next_state = encode_state(['INBOUND_GREETING_HUP'])
|
||||
speak_params = {"payload": app_args.inbound_greeting, "voice": app_args.default_tts_voice, "language": app_args.default_tts_language, "call_control_id": ccid, "client_state": next_state}
|
||||
send_telnyx_command("actions/speak", speak_params, current_api_key)
|
||||
else: # Outgoing call
|
||||
audio_url = find_custom_header(payload.get('custom_headers'), 'X-Audio-Url')
|
||||
tts_payload = find_custom_header(payload.get('custom_headers'), 'X-TTS-Payload')
|
||||
media_type = "audio" if audio_url else "tts" if tts_payload else "none"
|
||||
media_value = audio_url or tts_payload
|
||||
if media_value:
|
||||
app_logger.info(f" -> Outbound call answered. Playing main message directly.")
|
||||
next_state = encode_state(['MAIN_MEDIA_PLAYED', media_type, media_value])
|
||||
if media_type == "audio":
|
||||
send_telnyx_command("actions/playback_start", {"audio_url": media_value, "call_control_id": ccid, "client_state": next_state}, current_api_key)
|
||||
elif media_type == "tts":
|
||||
speak_params = {"payload": media_value, "voice": app_args.default_tts_voice, "language": app_args.default_tts_language, "call_control_id": ccid, "client_state": next_state}
|
||||
send_telnyx_command("actions/speak", speak_params, current_api_key)
|
||||
else:
|
||||
app_logger.warning(" -> Outbound call, but no audio/tts payload. Hanging up.")
|
||||
send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key)
|
||||
|
||||
elif event_type in ['call.speak.ended', 'call.playback.ended']:
|
||||
app_logger.info(f" Playback/Speak Ended: Status='{payload.get('status')}'")
|
||||
if state_name == 'INBOUND_GREETING_HUP':
|
||||
app_logger.info(" -> Inbound greeting finished. Hanging up.")
|
||||
send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key)
|
||||
elif state_name in ['MAIN_MEDIA_PLAYED', 'REPLAYING_MEDIA']:
|
||||
app_logger.info(f" -> Main message finished. Playing options menu.")
|
||||
_, media_type, media_value = decoded_parts
|
||||
next_state = encode_state(['WAITING_DTMF', media_type, media_value])
|
||||
options_prompt = "press 0 to repeat the message or press pound to hang up."
|
||||
gather_params = {
|
||||
"payload": options_prompt, "voice": app_args.default_tts_voice, "language": app_args.default_tts_language,
|
||||
"valid_digits": "0#", "max_digits": 1, "timeout_millis": app_args.dtmf_timeout_seconds * 1000, "terminating_digit": "#",
|
||||
"call_control_id": ccid, "client_state": next_state
|
||||
}
|
||||
send_telnyx_command("actions/gather_using_speak", gather_params, current_api_key)
|
||||
else:
|
||||
app_logger.warning(f" -> Playback/Speak ended with unhandled state '{state_name}'. Hanging up.")
|
||||
send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key)
|
||||
|
||||
elif event_type == 'call.dtmf.received':
|
||||
digit = payload.get('digit')
|
||||
app_logger.info(f" DTMF Received: Digit='{digit}'")
|
||||
if digit == '#':
|
||||
app_logger.info(" -> '#' received. Terminating call immediately.")
|
||||
send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key)
|
||||
|
||||
elif event_type == 'call.gather.ended':
|
||||
app_logger.info(f" -> Gather ended. Digits received: '{payload.get('digits')}', Status: '{payload.get('status')}'")
|
||||
if state_name == 'WAITING_DTMF':
|
||||
digits = payload.get('digits')
|
||||
_, media_type, media_value = decoded_parts
|
||||
if digits == "0":
|
||||
app_logger.info(" -> '0' pressed. Replaying main message.")
|
||||
# Note: No silence buffer on replay for responsiveness.
|
||||
next_state = encode_state(['REPLAYING_MEDIA', media_type, media_value])
|
||||
if media_type == "audio":
|
||||
send_telnyx_command("actions/playback_start", {"audio_url": media_value, "call_control_id": ccid, "client_state": next_state}, current_api_key)
|
||||
elif media_type == "tts":
|
||||
speak_params = {"payload": media_value, "voice": app_args.default_tts_voice, "language": app_args.default_tts_language, "call_control_id": ccid, "client_state": next_state}
|
||||
send_telnyx_command("actions/speak", speak_params, current_api_key)
|
||||
else: # Includes '#' having already triggered hangup, timeout, or other hangup condition
|
||||
app_logger.info(" -> Gather ended with non-repeat condition. Hanging up.")
|
||||
send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key)
|
||||
else:
|
||||
app_logger.warning(f" -> Gather ended with unhandled state '{state_name}'.")
|
||||
|
||||
elif event_type == 'call.hangup':
|
||||
app_logger.info(f" Call Hangup Event: Cause='{payload.get('cause')}'")
|
||||
else:
|
||||
app_logger.info(f" -> Unhandled Voice Event: '{event_type}' with state '{state_name}'.")
|
||||
|
||||
return Response(status=204)
|
||||
except Exception as e: app_logger.exception("REQFAIL Unhandled Ex"); return "Internal Server Error", 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description="Telnyx IVR Webhook (Fixed): Async, Repeat, DTMF Hangup.", formatter_class=argparse.RawTextHelpFormatter)
|
||||
parser.add_argument('--port', type=int, default=1998, help='Port (default: 1998).')
|
||||
parser.add_argument('--host', default='0.0.0.0', help='Host address (default: 0.0.0.0).')
|
||||
parser.add_argument('--webhook-path', default='/telnyx-webhook', help="URL path (default: /telnyx-webhook).")
|
||||
parser.add_argument('--log-level', default=LOG_LEVEL_DEFAULT, choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], help=f'Log level (default: {LOG_LEVEL_DEFAULT}).')
|
||||
parser.add_argument('--log-file', default=LOG_FILE_NAME_DEFAULT, help=f'Log file path (default: {LOG_FILE_NAME_DEFAULT}).')
|
||||
parser.add_argument('--api-key', default=TELNYX_API_KEY_DEFAULT, help='Telnyx API Key (default: from env).')
|
||||
parser.add_argument('--dtmf-timeout-seconds', type=int, default=DTMF_TIMEOUT_SECONDS_DEFAULT, help=f'Timeout for DTMF (default: {DTMF_TIMEOUT_SECONDS_DEFAULT}s).')
|
||||
parser.add_argument('--default-tts-voice', default=DEFAULT_TTS_VOICE_DEFAULT, help=f'Default TTS voice (default: {DEFAULT_TTS_VOICE_DEFAULT}).')
|
||||
parser.add_argument('--default-tts-language', default=DEFAULT_TTS_LANGUAGE_DEFAULT, help=f'Default TTS language (default: {DEFAULT_TTS_LANGUAGE_DEFAULT}).')
|
||||
parser.add_argument('--client-state-prefix', default=CLIENT_STATE_PREFIX_DEFAULT, help=f'Prefix for client_state (default: {CLIENT_STATE_PREFIX_DEFAULT}).')
|
||||
parser.add_argument('--inbound-greeting', default=INBOUND_GREETING_DEFAULT, help=f'TTS greeting for inbound calls.')
|
||||
parser.add_argument('--debug', action='store_true', help='Run Flask in debug mode.')
|
||||
app_args = parser.parse_args()
|
||||
|
||||
if not app_args.webhook_path.startswith('/'): app_args.webhook_path = '/' + app_args.webhook_path
|
||||
if not app_args.api_key: print("CRITICAL WARNING: Telnyx API Key not set. Voice commands will FAIL.")
|
||||
|
||||
setup_logging("DEBUG" if app_args.debug else app_args.log_level.upper(), app_args.log_file, name="TelnyxWebhookApp_Final")
|
||||
if not app_args.debug: logging.getLogger('werkzeug').setLevel(logging.WARNING)
|
||||
|
||||
app_logger.info("--- Telnyx Webhook Listener Starting (Final) ---")
|
||||
app_logger.info(f" Configuration: {vars(app_args)}")
|
||||
app_logger.info("------------------------------------------------")
|
||||
|
||||
app.run(host=app_args.host, port=app_args.port, debug=app_args.debug, use_reloader=app_args.debug)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user