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

128 lines
5.3 KiB
Python

import random
from bots.base import BaseBot
class GeneratorBot(BaseBot):
"""Diesel generator — normally on standby, runs on GENERATOR_FAILURE / ATS_TRANSFER scenarios."""
interval = 60
RATED_KW = 500.0
TANK_LITRES = 2_000.0 # full tank
BURN_RATE_LH = 90.0 # litres/hour at full load (~45 l/h at 50% load)
def __init__(self, site_id: str, gen_id: str) -> None:
super().__init__()
self.site_id = site_id
self.gen_id = gen_id
self._running = False
self._testing = False
self._fault = False
self._fuel_l = self.TANK_LITRES * (0.75 + random.uniform(-0.05, 0.10))
self._run_hours = 1_200 + random.randint(0, 400)
self._load_kw = 0.0
def get_topic(self) -> str:
return f"bms/{self.site_id}/generator/{self.gen_id}"
def set_scenario(self, name: str | None) -> None:
super().set_scenario(name)
self._running = name in ("GENERATOR_FAILURE", "ATS_TRANSFER")
self._testing = (name == "GENERATOR_TEST")
self._fault = (name == "GENERATOR_FAULT")
if name is None:
self._running = False
self._testing = False
self._fault = False
def get_payload(self) -> dict:
hour_inc = self.interval / 3_600
if self._fault:
return {
"state": "fault",
"fuel_pct": round(self._fuel_l / self.TANK_LITRES * 100, 1),
"fuel_litres": round(self._fuel_l, 0),
"fuel_rate_lph": 0.0,
"load_kw": 0.0,
"load_pct": 0.0,
"run_hours": round(self._run_hours, 1),
"voltage_v": 0.0,
"frequency_hz": 0.0,
"engine_rpm": 0.0,
"oil_pressure_bar": 0.0,
"coolant_temp_c": 0.0,
"exhaust_temp_c": 28.0,
"alternator_temp_c": 30.0,
"power_factor": 0.0,
"battery_v": 0.0,
}
if self._running or self._testing:
self._run_hours += hour_inc
target_load = (
self.RATED_KW * (0.55 + random.gauss(0, 0.02))
if self._running
else self.RATED_KW * (0.20 + random.gauss(0, 0.01))
)
self._load_kw = target_load
load_frac = self._load_kw / self.RATED_KW
# Burn fuel
burn = self.BURN_RATE_LH * load_frac * hour_inc
self._fuel_l = max(0.0, self._fuel_l - burn)
# Low fuel scenario
if self._scenario == "GENERATOR_LOW_FUEL":
self._fuel_l = max(0.0, self._fuel_l - 8.0)
state = "running" if self._running else "test"
voltage = 415.0 + random.gauss(0, 1.5)
frequency = 50.0 + random.gauss(0, 0.05)
oil_p = 4.2 + random.gauss(0, 0.1)
coolant = 78.0 + random.gauss(0, 2.0)
battery_v = 24.3 + random.gauss(0, 0.1)
# 1500 RPM at 50 Hz; slight droop under load
engine_rpm = 1500.0 - (load_frac * 8.0) + random.gauss(0, 2.0)
# Exhaust rises with load: ~180°C at idle, ~430°C at full load
exhaust_temp = 180.0 + load_frac * 250.0 + random.gauss(0, 5.0)
# Alternator winding temperature
alt_temp = 45.0 + load_frac * 35.0 + random.gauss(0, 1.5)
# Power factor typical for resistive/inductive DC loads
pf = 0.87 + load_frac * 0.05 + random.gauss(0, 0.005)
pf = round(min(0.99, max(0.80, pf)), 3)
else:
self._load_kw = 0.0
load_frac = 0.0
state = "standby"
voltage = 0.0
frequency = 0.0
oil_p = 0.0
coolant = 25.0 + random.gauss(0, 1.0)
battery_v = 27.1 + random.gauss(0, 0.1) # trickle-charged standby battery
engine_rpm = 0.0
exhaust_temp = 28.0 + random.gauss(0, 1.0) # ambient exhaust stack temp
alt_temp = 30.0 + random.gauss(0, 0.5)
pf = 0.0
fuel_pct = min(100.0, self._fuel_l / self.TANK_LITRES * 100)
# Actual fuel consumption rate for this interval
fuel_rate = self.BURN_RATE_LH * load_frac if load_frac > 0 else 0.0
return {
"state": state,
"fuel_pct": round(fuel_pct, 1),
"fuel_litres": round(self._fuel_l, 0),
"fuel_rate_lph": round(fuel_rate, 1),
"load_kw": round(self._load_kw, 1),
"load_pct": round(load_frac * 100, 1),
"run_hours": round(self._run_hours, 1),
"voltage_v": round(voltage, 1),
"frequency_hz": round(frequency, 2),
"engine_rpm": round(engine_rpm, 0),
"oil_pressure_bar": round(oil_p, 2),
"coolant_temp_c": round(coolant, 1),
"exhaust_temp_c": round(exhaust_temp, 1),
"alternator_temp_c": round(alt_temp, 1),
"power_factor": pf,
"battery_v": round(battery_v, 2),
}