demo version prepped
This commit is contained in:
253
backend/simulator/__init__.py
Normal file
253
backend/simulator/__init__.py
Normal file
@@ -0,0 +1,253 @@
|
||||
import database as db
|
||||
import sqlite3
|
||||
from datetime import datetime, timezone
|
||||
import random
|
||||
import asyncio
|
||||
import time
|
||||
from .sim_config import ALARM_TEXTS, TICKET_TEXTS, ASSIGNEES, SITE_IDS
|
||||
from logger import get_logger
|
||||
logger = get_logger(__name__)
|
||||
|
||||
next_alarm_cleanup_at: float = 0.0
|
||||
next_incident_cleanup_at: float = 0.0
|
||||
|
||||
# TODO: Simulate weather / natural disasters
|
||||
|
||||
async def ticket_simulator(interval=60):
|
||||
# Generates tickets off of alarming sites.
|
||||
while True:
|
||||
conn = db.connect_to_db()
|
||||
try:
|
||||
logger.debug("Simulating tickets...")
|
||||
cur = conn.cursor()
|
||||
now = int(datetime.now(timezone.utc).timestamp())
|
||||
|
||||
# Find sites that have unassigned simulator alarms. Will be used to assigned to bot
|
||||
rows = cur.execute("""
|
||||
SELECT DISTINCT site_id FROM alarms
|
||||
WHERE incident_id IS NULL AND created_by = 'simulator'
|
||||
""").fetchall()
|
||||
|
||||
if not rows:
|
||||
logger.debug("No unassigned alarms — skipping ticket creation.")
|
||||
conn.close()
|
||||
await asyncio.sleep(interval)
|
||||
continue
|
||||
|
||||
site_id = random.choice(rows)[0]
|
||||
text = random.choice(TICKET_TEXTS)
|
||||
assigned_to = random.choice(ASSIGNEES)
|
||||
|
||||
# NOTE: Get the worse severity alarm that isn't assigned out.
|
||||
worst = cur.execute("""
|
||||
SELECT MIN(severity) FROM alarms
|
||||
WHERE site_id = ? AND incident_id IS NULL
|
||||
""", (site_id,)).fetchone()[0]
|
||||
severity = worst or random.randint(1, 5)
|
||||
|
||||
cur.execute("""
|
||||
INSERT INTO incidents (text, severity, site_id, created, updated, assigned_to, status, created_by)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'active', 'simulator')
|
||||
""", (text, severity, site_id, now, now, assigned_to))
|
||||
incident_id = cur.lastrowid
|
||||
|
||||
cur.execute("""
|
||||
UPDATE alarms SET incident_id = ?, updated = ?
|
||||
WHERE site_id = ? AND incident_id IS NULL
|
||||
""", (incident_id, now, site_id))
|
||||
|
||||
conn.commit()
|
||||
|
||||
# Scan for idle robots for tickets
|
||||
idle = cur.execute("""
|
||||
SELECT * FROM robots WHERE current_incident_id IS NULL ORDER BY RANDOM() LIMIT 1
|
||||
""").fetchone()
|
||||
|
||||
# when found, assign them a ticket.
|
||||
# TODO: Make this smarter, maybe path or proximity. right now they fly everywhere.
|
||||
if idle:
|
||||
site = cur.execute("SELECT lat, lon FROM cellsites WHERE id = ?", (site_id,)).fetchone()
|
||||
cur.execute("""
|
||||
UPDATE robots SET current_incident_id = ?, target_lat = ?, target_lon = ?, updated = ?
|
||||
WHERE id = ?
|
||||
""", (incident_id, site[0], site[1], now, idle[0]))
|
||||
cur.execute("""
|
||||
UPDATE incidents SET assigned_to = ? WHERE id = ?
|
||||
""", (idle[1], incident_id))
|
||||
conn.commit()
|
||||
logger.info(f"Robot {idle[1]} dispatched to site {site_id}")
|
||||
|
||||
|
||||
conn.close()
|
||||
logger.info(f"Ticket created — INC #{incident_id} site={site_id} sev={severity}")
|
||||
except Exception as e:
|
||||
logger.error(f"Ticket simulator error: {e}", exc_info=True)
|
||||
finally:
|
||||
conn.close()
|
||||
await asyncio.sleep(interval)
|
||||
|
||||
async def alarm_simulator(interval=60):
|
||||
# Generates random alarms off sites
|
||||
while True:
|
||||
conn = db.connect_to_db()
|
||||
try:
|
||||
logger.debug("Simulating alarms...")
|
||||
cur = conn.cursor()
|
||||
now = int(datetime.now(timezone.utc).timestamp())
|
||||
site_id = random.choice(SITE_IDS)
|
||||
severity = random.randint(1, 5)
|
||||
text = random.choice(ALARM_TEXTS)
|
||||
status = "active"
|
||||
created_by = "simulator"
|
||||
cur.execute("""
|
||||
INSERT INTO alarms (text, severity, site_id, created, updated, status, created_by)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""", (text, severity, site_id, now, now, status, created_by))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
logger.debug(f"Alarm created — site={site_id} sev={severity}")
|
||||
except Exception as e:
|
||||
logger.error(f"Alarm simulator error: {e}", exc_info=True)
|
||||
finally:
|
||||
conn.close()
|
||||
await asyncio.sleep(interval)
|
||||
|
||||
async def cleanup_incidents(interval=300, max_age_seconds=3600):
|
||||
global next_incident_cleanup_at
|
||||
while True:
|
||||
next_incident_cleanup_at = time.time() + interval
|
||||
await asyncio.sleep(interval)
|
||||
conn = db.connect_to_db()
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cutoff = int(datetime.now(timezone.utc).timestamp()) - max_age_seconds
|
||||
cur.execute("DELETE FROM incidents WHERE created_by = 'simulator' AND created < ?", (cutoff,))
|
||||
# NOTE: robots need to be reset or else will have deleted tickets
|
||||
cur.execute("""
|
||||
UPDATE robots SET
|
||||
current_incident_id = NULL,
|
||||
current_site_id = NULL,
|
||||
target_lat = base_lat,
|
||||
target_lon = base_lon
|
||||
WHERE current_incident_id IS NOT NULL
|
||||
AND current_incident_id NOT IN (SELECT id FROM incidents)
|
||||
""")
|
||||
affected = cur.rowcount
|
||||
if affected:
|
||||
logger.info(f"Reset {affected} robots orphaned by incident cleanup")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
logger.info(f"Incident cleanup done — removed records older than {max_age_seconds}s")
|
||||
except Exception as e:
|
||||
logger.error(f"Incident cleanup error: {e}", exc_info=True)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
async def cleanup_alarms(interval=300, max_age_seconds=3600):
|
||||
global next_alarm_cleanup_at
|
||||
while True:
|
||||
next_alarm_cleanup_at = time.time() + interval
|
||||
await asyncio.sleep(interval)
|
||||
conn = db.connect_to_db()
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cutoff = int(datetime.now(timezone.utc).timestamp()) - max_age_seconds
|
||||
cur.execute("DELETE FROM alarms WHERE created_by = 'simulator' AND created < ?", (cutoff,))
|
||||
|
||||
# NOTE: Here we clear any oprhaned incident_ids off the alarm after deleted.
|
||||
cur.execute("""
|
||||
UPDATE alarms SET incident_id = NULL
|
||||
WHERE incident_id IS NOT NULL
|
||||
AND incident_id NOT IN (SELECT id FROM incidents)
|
||||
""")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
logger.info(f"Alarm cleanup done — removed records older than {max_age_seconds}s")
|
||||
except Exception as e:
|
||||
logger.error(f"Alarm cleanup error: {e}", exc_info=True)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
async def robot_simulator(interval=3):
|
||||
"""
|
||||
This is the robot sim that will fly around and "fix" sites after being assigned tickets.
|
||||
The robot has a fixed time to "work" and then will close the ticket and move on.
|
||||
"""
|
||||
while True:
|
||||
conn = db.connect_to_db()
|
||||
try:
|
||||
conn.row_factory = sqlite3.Row
|
||||
cur = conn.cursor()
|
||||
now = int(time.time())
|
||||
|
||||
in_transit = cur.execute("""
|
||||
SELECT r.id, r.lat, r.lon, r.target_lat, r.target_lon,
|
||||
r.current_incident_id, i.site_id as target_site_id
|
||||
FROM robots r
|
||||
JOIN incidents i ON r.current_incident_id = i.id
|
||||
WHERE r.current_incident_id IS NOT NULL
|
||||
AND r.current_site_id IS NULL
|
||||
AND r.target_lat IS NOT NULL
|
||||
""").fetchall()
|
||||
|
||||
STEP = 0.2
|
||||
THRESHOLD = 0.05
|
||||
|
||||
for robot in in_transit:
|
||||
dlat = robot['target_lat'] - robot['lat']
|
||||
dlon = robot['target_lon'] - robot['lon']
|
||||
dist = (dlat**2 + dlon**2) ** 0.5
|
||||
|
||||
if dist < THRESHOLD:
|
||||
cur.execute("""
|
||||
UPDATE robots SET lat = ?, lon = ?, current_site_id = ?, updated = ?
|
||||
WHERE id = ?
|
||||
""", (robot['target_lat'], robot['target_lon'], robot['target_site_id'], now, robot['id']))
|
||||
logger.info(f"Robot {robot['id']} arrived at site {robot['target_site_id']}")
|
||||
else:
|
||||
factor = min(STEP / dist, 1.0)
|
||||
new_lat = robot['lat'] + dlat * factor
|
||||
new_lon = robot['lon'] + dlon * factor
|
||||
cur.execute("""
|
||||
UPDATE robots SET lat = ?, lon = ?, updated = ? WHERE id = ?
|
||||
""", (new_lat, new_lon, now, robot['id']))
|
||||
|
||||
WORK_DURATION = 60 # seconds on site before closing ticket
|
||||
|
||||
on_site = cur.execute("""
|
||||
SELECT id, current_incident_id, current_site_id, base_lat, base_lon, updated
|
||||
FROM robots
|
||||
WHERE current_site_id IS NOT NULL
|
||||
AND current_incident_id IS NOT NULL
|
||||
""").fetchall()
|
||||
|
||||
|
||||
for robot in on_site:
|
||||
time_on_site = now - robot['updated']
|
||||
if time_on_site < WORK_DURATION:
|
||||
continue
|
||||
|
||||
# Close the incident <- not delete
|
||||
cur.execute("""
|
||||
UPDATE incidents SET status = 'closed', updated = ? WHERE id = ?
|
||||
""", (now, robot['current_incident_id']))
|
||||
|
||||
# End assignment and return, for more work
|
||||
cur.execute("""
|
||||
UPDATE robots SET
|
||||
current_incident_id = NULL,
|
||||
current_site_id = NULL,
|
||||
target_lat = base_lat,
|
||||
target_lon = base_lon,
|
||||
updated = ?
|
||||
WHERE id = ?
|
||||
""", (now, robot['id']))
|
||||
logger.info(f"Robot {robot['id']} finished INC #{robot['current_incident_id']} at site {robot['current_site_id']} — returning to base")
|
||||
|
||||
conn.commit()
|
||||
except Exception as e:
|
||||
logger.error(f"Robot simulator error: {e}", exc_info=True)
|
||||
finally:
|
||||
conn.close()
|
||||
await asyncio.sleep(interval)
|
||||
30
backend/simulator/sim_config.py
Normal file
30
backend/simulator/sim_config.py
Normal file
@@ -0,0 +1,30 @@
|
||||
TICKET_TEXTS: list[str] = [
|
||||
"Investigate signal degradation at site",
|
||||
"Replace faulty hardware unit",
|
||||
"Perform antenna realignment",
|
||||
"Inspect backhaul connection",
|
||||
"Review capacity thresholds",
|
||||
"Check power supply unit",
|
||||
"Diagnose interference source",
|
||||
"Schedule maintenance window"
|
||||
]
|
||||
|
||||
ASSIGNEES: list[str] = [
|
||||
"r2d2", "c3po", "hal9000", "glados", "tars",
|
||||
"case", "wall_e", "data", "bishop", "ash"
|
||||
]
|
||||
|
||||
SITE_IDS: list[int] = [
|
||||
30859, 31387, 31388, 31389, 31390, 31391, 42041, 42913, 43330, 44117, 44900, 45124, 45356, 46716, 46774, 46780, 46781, 46782, 46827, 47084, 47146, 47157, 47164, 47165, 47240, 48329, 51843, 52032, 53375, 53573, 54492, 56076, 56861, 57316, 57319, 57424, 57564, 57601, 57616, 57805, 57958, 57977, 57978, 58023, 58024, 58033, 58741, 58808, 58874, 58875, 58876, 58877, 58878, 58879, 58880, 58881, 58882, 58883, 58932, 59232, 59258, 59297, 59453, 59566, 59656, 59659, 59865, 59884, 59887, 60402, 60427, 61121, 61122, 61353, 61823, 62029, 62030, 62149, 62210, 62217, 62299, 62463, 62641, 63249, 63295, 64077, 64202, 64361, 64384, 64385, 65709, 65949, 65950, 66608, 67052, 67096, 67097, 68042, 68695, 68696, 68697, 68698, 68699, 68700, 68726, 68779, 69597, 69872, 69873, 70161, 70233, 70234, 70235, 70236, 70237, 70308, 70550, 71094, 71673, 71674, 71675, 71676, 71677, 71678, 71679, 71680, 71969, 72006, 72233, 72316, 72513, 72719, 72761, 72762, 72763, 72764, 73114, 73125, 73141, 73142, 73143, 73213, 73413, 73414, 74068, 74492, 74584, 75083, 75693, 76850, 77873, 78320, 78435, 78644, 79086, 79292, 79305, 79658, 80752, 82021, 82416, 82454, 83444, 83706, 83793, 84308, 84499, 84656, 85437, 85438, 86094, 86424, 86576, 87114, 87115, 87663, 87839, 88598, 88599, 88600, 88782, 88783, 88877, 88878, 89112, 89113, 89266, 89285, 89286, 89287, 89288, 89711, 89712, 90471, 90472, 90473, 90474, 90475, 90476, 90477, 90527, 90528, 90695, 90696, 90697, 90698, 91013, 91159, 91160, 91586, 92163, 93413, 93414, 93415, 93416, 93503, 94041, 94042, 94046, 94047, 94194, 94285, 95112, 95113, 95114, 95115, 95116, 95117, 95118, 95119, 95120, 95121, 95122, 95123, 95124, 95125, 95126, 95127, 95128, 95129, 95130, 95131, 95132, 95133, 95134, 95135, 95136, 95137, 95138, 95139, 96041, 96723, 96808, 97416, 97503, 97505, 97557, 97558, 97594, 97596, 97602, 97853, 97862, 97873, 98150, 98164, 98165, 98208, 98209, 98210, 98211, 98212, 98226, 98227, 98228, 98229, 98602, 98662, 98663, 98664, 98665, 98666, 98667, 98668, 98669, 98670, 98671, 98672, 98673, 98674, 98675, 98676, 98677, 98678, 98679, 98680, 98681, 98760, 98772, 98773, 98774, 98775, 98776, 98901, 98902, 98903, 98904, 98927, 99525, 99528, 99529, 99531, 99758, 99965, 100356, 100568, 100570, 100614, 100615, 101221, 101398, 101469, 101470, 101471, 101602, 101720, 101790, 101814, 102059, 102065, 102108, 102109, 102110, 102163, 102255, 102805, 104288, 104289, 104303, 104307, 104960, 105551, 105552, 105554, 105555, 105620, 105621, 106103, 107003, 107163, 107164, 107165, 107166, 107167, 107168, 109188, 110321, 110322, 110323, 110324, 111233, 111462, 112488, 116358, 116359, 116360, 116361, 116362, 116363, 116364, 116365, 116366, 116367, 116368, 116369, 116370, 116371, 116372, 116373, 116374, 116375, 116376, 116377, 116378, 116379, 116380, 116381, 116382, 116383, 116384, 116385, 116386, 116387, 116388, 116389, 116390, 116391, 116392, 116393, 116394, 116395, 116396, 116397, 116398, 116399, 116400, 116401, 116402, 116403, 116404, 116405, 116406, 116407, 116408, 116409, 116410, 116411, 116412, 116413, 116414, 116415, 116416, 116417, 116418, 116419, 116420, 116421, 116422, 116423, 116424, 116425, 116426, 116427, 116428, 116429, 116430, 116431, 116432, 116433, 116434, 116435, 116436, 116437, 116438, 116439, 116440, 116441, 116442, 116443, 116444, 116445, 116446, 116447, 116448, 116449, 116450, 116451, 116452, 116453, 116454, 116455, 116456, 116457, 116458, 116459, 116460, 116461, 116462, 116463, 116464, 116465, 116466, 116467, 116468, 116469, 116470, 116471, 116472, 116473, 116474, 116475, 116476, 116477, 116478, 116479, 116480, 116481, 116482, 116483, 116484, 116485, 116486, 116487, 116488, 116489, 116490, 116491, 116492, 116493, 116494
|
||||
]
|
||||
|
||||
ALARM_TEXTS: list[str] = [
|
||||
"Signal degradation detected",
|
||||
"High interference on frequency",
|
||||
"Connection timeout",
|
||||
"Hardware fault reported",
|
||||
"Capacity threshold exceeded",
|
||||
"Power supply warning",
|
||||
"Antenna misalignment detected",
|
||||
"Backhaul link degraded"
|
||||
]
|
||||
Reference in New Issue
Block a user