first commit
This commit is contained in:
commit
4b98219bf7
144 changed files with 31561 additions and 0 deletions
184
simulators/bots/crac.py
Normal file
184
simulators/bots/crac.py
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
import math
|
||||
import random
|
||||
from bots.base import BaseBot
|
||||
|
||||
|
||||
class CracBot(BaseBot):
|
||||
"""CRAC/cooling unit monitor — full sensor set."""
|
||||
|
||||
interval = 60
|
||||
|
||||
# Physical constants
|
||||
RATED_CAPACITY_KW = 80.0
|
||||
MAX_AIRFLOW_CFM = 9_000.0
|
||||
AIR_DENSITY = 1.2 # kg/m³
|
||||
AIR_CP = 1_005.0 # J/kg·K
|
||||
MAX_FAN_RPM = 3_000
|
||||
RATED_VOLTAGE = 415.0 # V, 3-phase
|
||||
|
||||
def __init__(self, site_id: str, crac_id: str) -> None:
|
||||
super().__init__()
|
||||
self.site_id = site_id
|
||||
self.crac_id = crac_id
|
||||
self._faulted = False
|
||||
self._compressor_fault = False
|
||||
self._dirty_filter = False
|
||||
# Persisted state — realistic starting values
|
||||
self._comp_hours = 12_000 + random.randint(0, 4_000)
|
||||
self._fan_hours = self._comp_hours + random.randint(500, 1_500)
|
||||
self._filter_dp_base = 28.0 + random.uniform(-5, 5)
|
||||
|
||||
def get_topic(self) -> str:
|
||||
return f"bms/{self.site_id}/cooling/{self.crac_id}"
|
||||
|
||||
def set_scenario(self, name: str | None) -> None:
|
||||
super().set_scenario(name)
|
||||
self._faulted = (name == "COOLING_FAILURE")
|
||||
self._compressor_fault = (name == "COMPRESSOR_FAULT")
|
||||
self._dirty_filter = (name == "DIRTY_FILTER")
|
||||
|
||||
def get_payload(self) -> dict:
|
||||
# Accumulate run hours (interval seconds → fraction of an hour)
|
||||
hour_inc = self.interval / 3_600
|
||||
self._comp_hours += hour_inc
|
||||
self._fan_hours += hour_inc
|
||||
|
||||
# ── Hard fault — unit completely offline ──────────────────────
|
||||
if self._faulted:
|
||||
return {
|
||||
"state": "fault",
|
||||
"mode": "fault",
|
||||
"setpoint": 22.0,
|
||||
"supply_temp": None, "return_temp": None,
|
||||
"supply_humidity": None, "return_humidity": None,
|
||||
"airflow_cfm": 0, "filter_dp_pa": round(self._filter_dp_base, 1),
|
||||
"cooling_capacity_kw": 0, "cooling_capacity_rated_kw": self.RATED_CAPACITY_KW,
|
||||
"cooling_capacity_pct": 0, "cop": 0, "sensible_heat_ratio": 0,
|
||||
"compressor_state": 0, "compressor_load_pct": 0,
|
||||
"compressor_power_kw": 0, "compressor_run_hours": round(self._comp_hours),
|
||||
"high_pressure_bar": 0, "low_pressure_bar": 0,
|
||||
"discharge_superheat_c": 0, "liquid_subcooling_c": 0,
|
||||
"fan_pct": 0, "fan_rpm": 0, "fan_power_kw": 0,
|
||||
"fan_run_hours": round(self._fan_hours),
|
||||
"total_unit_power_kw": 0, "input_voltage_v": self.RATED_VOLTAGE,
|
||||
"input_current_a": 0, "power_factor": 0,
|
||||
}
|
||||
|
||||
# ── Normal operating base values ──────────────────────────────
|
||||
supply = 17.5 + random.gauss(0, 0.3)
|
||||
return_temp = 28.0 + random.gauss(0, 0.5)
|
||||
fan_pct = 62.0 + random.gauss(0, 3)
|
||||
|
||||
# ── Scenarios ─────────────────────────────────────────────────
|
||||
if self._scenario == "FAN_DEGRADATION":
|
||||
fan_pct = max(22.0, 62.0 - self._scenario_step * 1.6) + random.gauss(0, 1.5)
|
||||
return_temp = 28.0 + min(self._scenario_step * 0.45, 11.0) + random.gauss(0, 0.5)
|
||||
supply = 17.5 + min(self._scenario_step * 0.08, 2.0) + random.gauss(0, 0.3)
|
||||
|
||||
fan_pct = max(10.0, min(100.0, fan_pct))
|
||||
|
||||
# ── Filter differential pressure ──────────────────────────────
|
||||
if self._dirty_filter:
|
||||
filter_dp = self._filter_dp_base + min(self._scenario_step * 4.5, 110.0)
|
||||
else:
|
||||
# Slow natural fouling (resets to base on RESET via _scenario_step=0)
|
||||
filter_dp = self._filter_dp_base + self._scenario_step * 0.01
|
||||
|
||||
# Filter fouling reduces airflow
|
||||
airflow_factor = max(0.50, 1.0 - max(0.0, filter_dp - 30) / 300.0)
|
||||
airflow_cfm = self.MAX_AIRFLOW_CFM * (fan_pct / 100.0) * airflow_factor
|
||||
airflow_m3s = airflow_cfm * 0.000_471_947
|
||||
|
||||
# ── Cooling capacity ──────────────────────────────────────────
|
||||
delta_t = return_temp - supply
|
||||
cooling_kw = airflow_m3s * self.AIR_DENSITY * self.AIR_CP * delta_t / 1_000.0
|
||||
|
||||
compressor_running = True
|
||||
if self._compressor_fault:
|
||||
# Fan-only — minimal sensible cooling only
|
||||
cooling_kw = cooling_kw * 0.08
|
||||
fan_pct = 100.0 # fans go full trying to compensate
|
||||
compressor_running = False
|
||||
|
||||
cooling_kw = max(0.0, cooling_kw)
|
||||
cooling_cap_pct = min(100.0, (cooling_kw / self.RATED_CAPACITY_KW) * 100.0)
|
||||
|
||||
# ── Compressor ────────────────────────────────────────────────
|
||||
comp_load_pct = min(100.0, (cooling_kw / self.RATED_CAPACITY_KW) * 110.0) if compressor_running else 0.0
|
||||
comp_power_kw = (comp_load_pct / 100.0) * 26.0 if compressor_running else 0.0
|
||||
|
||||
# Refrigerant pressures (R410A-like behaviour)
|
||||
if compressor_running:
|
||||
high_p = 17.5 + (comp_load_pct / 100.0) * 3.5 + random.gauss(0, 0.2)
|
||||
low_p = 5.2 - (comp_load_pct / 100.0) * 0.8 + random.gauss(0, 0.1)
|
||||
superheat = 8.0 + random.gauss(0, 0.8)
|
||||
subcooling = 4.5 + random.gauss(0, 0.4)
|
||||
else:
|
||||
# Pressures equalise when compressor is off
|
||||
high_p = low_p = 8.0 + random.gauss(0, 0.1)
|
||||
superheat = subcooling = 0.0
|
||||
|
||||
# ── Fan ───────────────────────────────────────────────────────
|
||||
fan_rpm = (fan_pct / 100.0) * self.MAX_FAN_RPM
|
||||
# Fan power: approximately cubic with speed fraction
|
||||
fan_power_kw = 3.5 * (fan_pct / 100.0) ** 3
|
||||
|
||||
# ── Electrical ────────────────────────────────────────────────
|
||||
total_power_kw = comp_power_kw + fan_power_kw + 0.4 # 0.4 kW for controls
|
||||
power_factor = max(0.85, min(0.99, 0.93 + random.gauss(0, 0.01)))
|
||||
voltage = self.RATED_VOLTAGE + random.gauss(0, 2.0)
|
||||
current_a = (total_power_kw * 1_000) / (math.sqrt(3) * voltage * power_factor) if total_power_kw > 0 else 0.0
|
||||
|
||||
# ── COP & SHR ─────────────────────────────────────────────────
|
||||
cop = (cooling_kw / total_power_kw) if total_power_kw > 0 else 0.0
|
||||
shr = max(0.0, min(1.0, 0.92 + random.gauss(0, 0.01)))
|
||||
|
||||
# ── Humidity ──────────────────────────────────────────────────
|
||||
supply_humidity = max(20.0, min(80.0, 45.0 + random.gauss(0, 2.0)))
|
||||
return_humidity = max(30.0, min(80.0, 55.0 + random.gauss(0, 2.0)))
|
||||
|
||||
# ── Operating mode ────────────────────────────────────────────
|
||||
if self._compressor_fault:
|
||||
mode = "fan_only"
|
||||
elif self._scenario == "FAN_DEGRADATION":
|
||||
mode = "cooling" # still cooling, just degraded
|
||||
else:
|
||||
mode = "cooling"
|
||||
|
||||
return {
|
||||
"state": "online",
|
||||
"mode": mode,
|
||||
"setpoint": 22.0,
|
||||
# Thermal
|
||||
"supply_temp": round(supply, 2),
|
||||
"return_temp": round(return_temp, 2),
|
||||
"supply_humidity": round(supply_humidity, 1),
|
||||
"return_humidity": round(return_humidity, 1),
|
||||
"airflow_cfm": round(airflow_cfm),
|
||||
"filter_dp_pa": round(filter_dp, 1),
|
||||
# Capacity
|
||||
"cooling_capacity_kw": round(cooling_kw, 2),
|
||||
"cooling_capacity_rated_kw": self.RATED_CAPACITY_KW,
|
||||
"cooling_capacity_pct": round(cooling_cap_pct, 1),
|
||||
"cop": round(cop, 2),
|
||||
"sensible_heat_ratio": round(shr, 2),
|
||||
# Compressor
|
||||
"compressor_state": 1 if compressor_running else 0,
|
||||
"compressor_load_pct": round(comp_load_pct, 1),
|
||||
"compressor_power_kw": round(comp_power_kw, 2),
|
||||
"compressor_run_hours": round(self._comp_hours),
|
||||
"high_pressure_bar": round(max(0.0, high_p), 2),
|
||||
"low_pressure_bar": round(max(0.0, low_p), 2),
|
||||
"discharge_superheat_c": round(max(0.0, superheat), 1),
|
||||
"liquid_subcooling_c": round(max(0.0, subcooling), 1),
|
||||
# Fan
|
||||
"fan_pct": round(fan_pct, 1),
|
||||
"fan_rpm": round(fan_rpm),
|
||||
"fan_power_kw": round(fan_power_kw, 2),
|
||||
"fan_run_hours": round(self._fan_hours),
|
||||
# Electrical
|
||||
"total_unit_power_kw": round(total_power_kw, 2),
|
||||
"input_voltage_v": round(voltage, 1),
|
||||
"input_current_a": round(current_a, 1),
|
||||
"power_factor": round(power_factor, 3),
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue