BMS/simulators/bots/vesda.py
2026-03-19 11:32:17 +00:00

66 lines
2 KiB
Python

import random
from bots.base import BaseBot
# VESDA alarm level thresholds (obscuration % per metre)
LEVELS = [
("normal", 0.0, 0.08),
("alert", 0.08, 0.20),
("action", 0.20, 0.50),
("fire", 0.50, 1.00),
]
def _level(obscuration: float) -> str:
for name, lo, hi in LEVELS:
if lo <= obscuration < hi:
return name
return "fire"
class VesdaBot(BaseBot):
"""VESDA aspirating smoke detector for a fire zone."""
interval = 30
def __init__(self, site_id: str, zone_id: str, room_id: str) -> None:
super().__init__()
self.site_id = site_id
self.zone_id = zone_id
self.room_id = room_id
self._base_obscuration = random.uniform(0.005, 0.015) # normal background
self._alert = False
self._fire = False
def get_topic(self) -> str:
return f"bms/{self.site_id}/fire/{self.zone_id}"
def set_scenario(self, name: str | None) -> None:
super().set_scenario(name)
self._alert = (name == "VESDA_ALERT")
self._fire = (name == "VESDA_FIRE")
def get_payload(self) -> dict:
if self._fire:
# Escalate rapidly to fire level
obs = min(0.85, 0.50 + self._scenario_step * 0.03) + random.gauss(0, 0.01)
elif self._alert:
# Sit in alert/action band
obs = min(0.45, 0.08 + self._scenario_step * 0.012) + random.gauss(0, 0.005)
else:
# Normal background with tiny random drift
obs = self._base_obscuration + random.gauss(0, 0.002)
obs = max(0.001, obs)
level = _level(obs)
return {
"zone_id": self.zone_id,
"room_id": self.room_id,
"level": level,
"obscuration_pct_m": round(obs * 100, 3),
"detector_1_ok": True,
"detector_2_ok": True,
"power_ok": True,
"flow_ok": not self._fire, # flow sensor trips in severe fire
}