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 }