679 lines
23 KiB
Python
679 lines
23 KiB
Python
# -*- coding: utf-8 -*-
|
||
from __future__ import annotations
|
||
|
||
__title__ = "burner"
|
||
__author__ = 'Mehmet Karatay & "Saraswati" (ChatGPT)'
|
||
__purpose__ = "Bina ve/veya dış ısıya göre brülör ve sirkülasyon kontrol çekirdeği"
|
||
__version__ = "0.4.3"
|
||
__date__ = "2025-11-22"
|
||
|
||
"""
|
||
ebuild/core/systems/burner.py
|
||
|
||
Revision : 2025-11-22
|
||
Authors : Mehmet Karatay & "Saraswati" (ChatGPT)
|
||
|
||
Amaç
|
||
-----
|
||
- BUILD_BURNER moduna göre (F/B) brülör ve sirkülasyon pompalarını yönetmek
|
||
- Bina ortalaması (B mod) veya dış ısı (F mod) üzerinden ısıtma isteği üretmek
|
||
- used_out_heat mantığı ile dış ısıya hafta sonu / konfor offset uygulamak
|
||
|
||
Bağımlılıklar
|
||
--------------
|
||
- building.Building
|
||
- environment.BuildingEnvironment
|
||
- season.SeasonController
|
||
- io.relay_driver.RelayDriver
|
||
- io.dbtext.DBText
|
||
- io.legacy_syslog (syslog/console çıktıları için)
|
||
- config_statics (cfg_s)
|
||
- config_runtime (cfg_v)
|
||
|
||
Notlar
|
||
------
|
||
- Brülör, igniter ve pompalar relay_driver içinde isimlendirilmiş kanallarla
|
||
temsil edilir.
|
||
- Bu dosya, eski sistemle uyum için mümkün olduğunca log formatını korumaya
|
||
çalışır.
|
||
"""
|
||
|
||
import datetime
|
||
import time as _time
|
||
from dataclasses import dataclass
|
||
from typing import Optional, Dict, Any, List, Tuple
|
||
|
||
from ..building import Building
|
||
from ..season import SeasonController
|
||
from ..environment import BuildingEnvironment
|
||
from ...io.relay_driver import RelayDriver
|
||
from ...io.dbtext import DBText
|
||
from ...io import legacy_syslog as lsys
|
||
from ... import config_statics as cfg_s
|
||
from ... import config_runtime as cfg_v
|
||
|
||
|
||
# -------------------------------------------------------------
|
||
# Yardımcı: DS18B20 okuma (hat sensörleri için)
|
||
# -------------------------------------------------------------
|
||
|
||
|
||
@dataclass
|
||
class BurnerState:
|
||
burner_on: bool
|
||
pumps_on: Tuple[str, ...]
|
||
fire_setpoint_c: float
|
||
last_change_ts: datetime.datetime
|
||
reason: str
|
||
last_building_avg: Optional[float]
|
||
last_outside_c: Optional[float]
|
||
last_used_out_c: Optional[float]
|
||
last_mode: str
|
||
|
||
|
||
# ----------------------------- Isı eğrisi --------------------
|
||
|
||
# Dış ısı → kazan çıkış setpoint haritası
|
||
# Örnek bir eğri; config_runtime ile override edilebilir.
|
||
BURNER_FIRE_SETPOINT_MAP: Dict[float, Dict[str, float]] = getattr(
|
||
cfg_v,
|
||
"BURNER_FIRE_SETPOINT_MAP",
|
||
{
|
||
-10.0: {"fire": 50.0},
|
||
-5.0: {"fire": 48.0},
|
||
0.0: {"fire": 46.0},
|
||
5.0: {"fire": 44.0},
|
||
10.0: {"fire": 40.0},
|
||
15.0: {"fire": 35.0},
|
||
20.0: {"fire": 30.0},
|
||
25.0: {"fire": 26.0},
|
||
},
|
||
)
|
||
|
||
|
||
class BurnerConfig:
|
||
"""
|
||
Brülör çalışma parametreleri (runtime config'ten override edilebilir).
|
||
"""
|
||
|
||
min_run_sec: int = 60 # brülör en az bu kadar saniye çalışsın
|
||
min_stop_sec: int = 60 # brülör en az bu kadar saniye duruşta kalsın
|
||
hysteresis_c: float = 0.5 # bina ortalaması için histerezis
|
||
|
||
|
||
# ---------------------------------------------------------
|
||
# Yardımcı fonksiyon: bina istatistikleri
|
||
# ---------------------------------------------------------
|
||
|
||
|
||
def _safe_float(value: Any, default: Optional[float] = None) -> Optional[float]:
|
||
try:
|
||
if value is None:
|
||
return default
|
||
return float(value)
|
||
except Exception:
|
||
return default
|
||
|
||
|
||
def _merge_stats(old: Optional[Dict[str, Any]], new: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""
|
||
Bina istatistiği için min/avg/max birleştirme.
|
||
"""
|
||
if old is None:
|
||
return dict(new)
|
||
|
||
def _pick(key: str, func):
|
||
a = old.get(key)
|
||
b = new.get(key)
|
||
if a is None:
|
||
return b
|
||
if b is None:
|
||
return a
|
||
return func(a, b)
|
||
|
||
return {
|
||
"min": _pick("min", min),
|
||
"avg": new.get("avg"),
|
||
"max": _pick("max", max),
|
||
}
|
||
|
||
|
||
# ---------------------------------------------------------
|
||
# used_out_heat hesabı
|
||
# ---------------------------------------------------------
|
||
|
||
|
||
def _apply_weekend_and_comfort(
|
||
used_out: Optional[float],
|
||
now: datetime.datetime,
|
||
weekend_boost_c: float,
|
||
comfort_offset_c: float,
|
||
) -> Optional[float]:
|
||
"""
|
||
Haftasonu ve konfor offset'ini used_out üzerine uygular.
|
||
"""
|
||
if used_out is None:
|
||
return None
|
||
|
||
result = float(used_out)
|
||
|
||
# Haftasonu boost: Cumartesi / Pazar
|
||
if now.weekday() >= 5 and weekend_boost_c != 0.0:
|
||
result -= weekend_boost_c
|
||
|
||
# Konfor offset'i
|
||
if comfort_offset_c != 0.0:
|
||
result -= comfort_offset_c
|
||
|
||
return result
|
||
|
||
|
||
def pick_fire_setpoint(outside_c: Optional[float]) -> float:
|
||
"""
|
||
Dış ısı (used_out_heat) için en yakın fire setpoint'i döndürür.
|
||
|
||
Eğer outside_c None ise, MAX_OUTLET_C kullanılır.
|
||
"""
|
||
if outside_c is None:
|
||
return float(getattr(cfg_v, "MAX_OUTLET_C", 45.0))
|
||
|
||
keys = sorted(BURNER_FIRE_SETPOINT_MAP.keys())
|
||
nearest_key = min(keys, key=lambda k: abs(k - outside_c))
|
||
mapping = BURNER_FIRE_SETPOINT_MAP.get(nearest_key, {})
|
||
return float(mapping.get("fire", getattr(cfg_v, "MAX_OUTLET_C", 45.0)))
|
||
|
||
|
||
# ---------------------------------------------------------
|
||
# Ana sınıf: BurnerController
|
||
# ---------------------------------------------------------
|
||
|
||
|
||
class BurnerController:
|
||
"""
|
||
F/B moduna göre brülör kontrolü yapan sınıf.
|
||
|
||
BUILD_BURNER = "B"
|
||
→ bina ortalama sıcaklığına göre kontrol
|
||
|
||
BUILD_BURNER = "F"
|
||
→ dış ısıya göre (OUTSIDE_LIMIT_HEAT_C) karar veren mod
|
||
(burada dış ısı olarak *used_out_heat* kullanılır).
|
||
"""
|
||
|
||
def __init__(
|
||
self,
|
||
building: Building,
|
||
relay_driver: RelayDriver,
|
||
logger: Optional[DBText] = None,
|
||
config: Optional[BurnerConfig] = None,
|
||
burner_id: Optional[int] = None,
|
||
environment: Optional[BuildingEnvironment] = None,
|
||
) -> None:
|
||
self.building = building
|
||
self.relays = relay_driver
|
||
# Runtime konfig: varsayılan BurnerConfig + config_runtime override
|
||
self.cfg = config or BurnerConfig()
|
||
try:
|
||
self.cfg.min_run_sec = int(getattr(cfg_v, "BURNER_MIN_RUN_SEC", self.cfg.min_run_sec))
|
||
self.cfg.min_stop_sec = int(getattr(cfg_v, "BURNER_MIN_STOP_SEC", self.cfg.min_stop_sec))
|
||
self.cfg.hysteresis_c = float(getattr(cfg_v, "BURNER_HYSTERESIS_C", self.cfg.hysteresis_c))
|
||
except Exception as e:
|
||
print("BurnerConfig override error:", e)
|
||
|
||
# Hangi brülör? → config_statics.BURNER_DEFAULT_ID veya parametre
|
||
default_id = int(getattr(cfg_s, "BURNER_DEFAULT_ID", 0))
|
||
self.burner_id = int(burner_id) if burner_id is not None else default_id
|
||
|
||
# DBText logger
|
||
log_file = getattr(cfg_s, "BURNER_LOG_FILE", "ebuild_burner_log.sql")
|
||
log_table = getattr(cfg_s, "BURNER_LOG_TABLE", "eburner_log")
|
||
self.logger = logger or DBText(
|
||
filename=log_file,
|
||
table=log_table,
|
||
app="EBURNER",
|
||
)
|
||
|
||
max_out = float(getattr(cfg_v, "MAX_OUTLET_C", 45.0))
|
||
|
||
# Röle kanal isimleri (eski yapı ile uyum için fallback)
|
||
self.igniter_ch: str = getattr(cfg_s, "BURNER_IGNITER_CH", "igniter")
|
||
self.pump_channels: List[str] = list(
|
||
getattr(cfg_s, "BURNER_PUMPS", ["circulation_a", "circulation_b"])
|
||
)
|
||
self.default_pumps: List[str] = list(
|
||
getattr(cfg_s, "BURNER_DEFAULT_PUMPS", ["circulation_a"])
|
||
)
|
||
|
||
# Bina okuma periyodu (BUILDING_READ_PERIOD_S)
|
||
self._building_last_read_ts: Optional[datetime.datetime] = None
|
||
self._building_read_period: float = float(
|
||
getattr(cfg_v, "BUILDING_READ_PERIOD_S", 60.0)
|
||
)
|
||
self._building_last_stats: Optional[Dict[str, Any]] = None
|
||
|
||
# used_out_heat için parametreler
|
||
self.used_out_c: Optional[float] = None
|
||
self._last_used_update_ts: Optional[datetime.datetime] = None
|
||
self.outside_smooth_sec: float = float(
|
||
getattr(cfg_v, "OUTSIDE_SMOOTH_SECONDS", 900.0)
|
||
)
|
||
self.weekend_boost_c: float = float(
|
||
getattr(cfg_v, "WEEKEND_HEAT_BOOST_C", 0.0)
|
||
)
|
||
self.comfort_offset_c: float = float(
|
||
getattr(cfg_v, "BURNER_COMFORT_OFFSET_C", 0.0)
|
||
)
|
||
|
||
# Ortam nesnesi (opsiyonel)
|
||
self.environment = environment
|
||
|
||
# Ortamdan başlangıç dış ısı alınabiliyorsa used_out'u hemen doldur
|
||
if self.environment is not None:
|
||
try:
|
||
first_out = self.environment.get_outside_temp_cached()
|
||
except Exception:
|
||
first_out = None
|
||
if first_out is not None:
|
||
self.used_out_c = first_out
|
||
self._last_used_update_ts = datetime.datetime.now()
|
||
|
||
# Çalışma modu
|
||
cfg_mode = str(getattr(cfg_s, "BUILD_BURNER", "F")).upper()
|
||
initial_mode = cfg_mode if cfg_mode in ("F", "B") else "F"
|
||
|
||
# Başlangıç state
|
||
self.state = BurnerState(
|
||
burner_on=False,
|
||
pumps_on=tuple(),
|
||
fire_setpoint_c=max_out,
|
||
last_change_ts=datetime.datetime.now(),
|
||
reason="init",
|
||
last_building_avg=None,
|
||
last_outside_c=None,
|
||
last_used_out_c=None,
|
||
last_mode=initial_mode,
|
||
)
|
||
|
||
# Mevsim / güneş bilgisi (syslog üst block için)
|
||
try:
|
||
self.season = SeasonController.from_now()
|
||
except Exception as e:
|
||
print("SeasonController.from_now() hata:", e)
|
||
self.season = None
|
||
|
||
# ---------------------------------------------------------
|
||
# Bina istatistikleri
|
||
# ---------------------------------------------------------
|
||
|
||
def _get_building_stats(self, now: datetime.datetime) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
Bina ortalaması / min / max gibi istatistikleri periyodik olarak okur.
|
||
BUILDING_READ_PERIOD_S içinde cache kullanır.
|
||
"""
|
||
if self._building_last_read_ts is None:
|
||
need_read = True
|
||
else:
|
||
delta = (now - self._building_last_read_ts).total_seconds()
|
||
need_read = delta >= self._building_read_period
|
||
|
||
if not need_read:
|
||
return self._building_last_stats
|
||
|
||
try:
|
||
stats = self.building.get_stats()
|
||
except Exception as e:
|
||
print("Building.get_stats() hata:", e)
|
||
return self._building_last_stats
|
||
|
||
self._building_last_read_ts = now
|
||
self._building_last_stats = stats
|
||
return stats
|
||
|
||
# ---------------------------------------------------------
|
||
# used_out_heat güncelleme
|
||
# ---------------------------------------------------------
|
||
|
||
def _update_used_out(self, now: datetime.datetime, outside_c: Optional[float]) -> Optional[float]:
|
||
"""
|
||
Dış ısı okumasına göre used_out_heat günceller.
|
||
- OUTSIDE_SMOOTH_SECONDS süresince eksponansiyel smoothing
|
||
- Haftasonu ve konfor offset'i eklenir.
|
||
"""
|
||
raw = outside_c
|
||
|
||
if raw is None:
|
||
return self.used_out_c
|
||
|
||
# Smooth
|
||
if self.used_out_c is None or self._last_used_update_ts is None:
|
||
smoothed = raw
|
||
else:
|
||
dt = (now - self._last_used_update_ts).total_seconds()
|
||
if dt <= 0:
|
||
smoothed = self.used_out_c
|
||
else:
|
||
tau = max(1.0, self.outside_smooth_sec)
|
||
alpha = min(1.0, dt / tau)
|
||
smoothed = (1.0 - alpha) * self.used_out_c + alpha * raw
|
||
|
||
self.used_out_c = smoothed
|
||
self._last_used_update_ts = now
|
||
|
||
# Haftasonu / konfor offset'i uygula
|
||
final_used = _apply_weekend_and_comfort(
|
||
smoothed,
|
||
now,
|
||
self.weekend_boost_c,
|
||
self.comfort_offset_c,
|
||
)
|
||
return final_used
|
||
|
||
# ---------------------------------------------------------
|
||
# Isı ihtiyacı kararları
|
||
# ---------------------------------------------------------
|
||
|
||
def _should_heat_by_outside(self, used_out: Optional[float]) -> bool:
|
||
"""
|
||
F modunda (dış ısıya göre) ısıtma isteği.
|
||
"""
|
||
limit = float(getattr(cfg_v, "OUTSIDE_HEAT_LIMIT_C", 17.0))
|
||
if used_out is None:
|
||
return False
|
||
|
||
want = used_out < limit
|
||
print(f"should_heat_by_outside: used={used_out:.3f}C limit={limit:.1f}C")
|
||
return want
|
||
|
||
def _should_heat_by_building(self, building_avg: Optional[float], now: datetime.datetime) -> bool:
|
||
"""
|
||
B modunda bina ortalaması + konfor setpoint'e göre ısıtma isteği.
|
||
"""
|
||
comfort = float(getattr(cfg_v, "COMFORT_SETPOINT_C", 23.0))
|
||
h = self.cfg.hysteresis_c
|
||
|
||
if building_avg is None:
|
||
return False
|
||
|
||
if building_avg < (comfort - h):
|
||
return True
|
||
if building_avg > (comfort + h):
|
||
return False
|
||
|
||
# Histerezis bandında önceki state'i koru
|
||
return self.state.burner_on
|
||
|
||
# ---------------------------------------------------------
|
||
# Min çalışma / durma süreleri
|
||
# ---------------------------------------------------------
|
||
|
||
def _respect_min_times(self, now: datetime.datetime, want_on: bool) -> bool:
|
||
"""
|
||
min_run_sec / min_stop_sec kurallarını uygular.
|
||
- İlk açılışta (state.reason == 'init') kısıtlama uygulanmaz.
|
||
"""
|
||
# İlk tick: min_run/min_stop uygulama
|
||
try:
|
||
if getattr(self.state, "reason", "") == "init":
|
||
return want_on
|
||
except Exception:
|
||
pass
|
||
|
||
elapsed = (now - self.state.last_change_ts).total_seconds()
|
||
|
||
if self.state.burner_on:
|
||
# Çalışırken min_run dolmadan kapatma
|
||
if not want_on and elapsed < self.cfg.min_run_sec:
|
||
return True
|
||
else:
|
||
# Kapalıyken min_stop dolmadan açma
|
||
if want_on and elapsed < self.cfg.min_stop_sec:
|
||
return False
|
||
|
||
return want_on
|
||
|
||
# ---------------------------------------------------------
|
||
# Çıkışları rölelere uygulama
|
||
# ---------------------------------------------------------
|
||
|
||
def _apply_outputs(
|
||
self,
|
||
now: datetime.datetime,
|
||
mode: str,
|
||
burner_on: bool,
|
||
pumps_on: Tuple[str, ...],
|
||
fire_setpoint_c: float,
|
||
reason: str,
|
||
) -> None:
|
||
"""
|
||
Röleleri sürer, state'i günceller, log ve syslog üretir.
|
||
"""
|
||
# 1) Röle sürücüsü (igniter + pompalar)
|
||
try:
|
||
# Yeni API: RelayDriver brülör-aware ise
|
||
if hasattr(self.relays, "set_igniter"):
|
||
# Brülör ateşleme
|
||
self.relays.set_igniter(self.burner_id, burner_on)
|
||
|
||
# Pompalar: her zaman kanal isimleri üzerinden sür
|
||
if hasattr(self.relays, "all_pumps"):
|
||
all_pumps = list(self.relays.all_pumps(self.burner_id)) # ['circulation_a', ...]
|
||
for ch in all_pumps:
|
||
self.relays.set_channel(ch, (ch in pumps_on))
|
||
else:
|
||
# all_pumps yoksa, config_statics'ten gelen pump_channels ile sür
|
||
for ch in self.pump_channels:
|
||
self.relays.set_channel(ch, (ch in pumps_on))
|
||
else:
|
||
# Eski/çok basit API: doğrudan kanal adları
|
||
self.relays.set_channel(self.igniter_ch, burner_on)
|
||
for ch in self.pump_channels:
|
||
self.relays.set_channel(ch, (ch in pumps_on))
|
||
except Exception as exc:
|
||
# legacy_syslog.log_error YOK, bu yüzden ya loga yaz ya da print et
|
||
try:
|
||
msg = f"[relay_error] igniter_ch={self.igniter_ch} burner_on={burner_on} pumps_on={pumps_on} exc={exc}"
|
||
lsys.send_legacy_syslog(lsys.format_line(98, msg))
|
||
except Exception:
|
||
print("Relay error in _apply_outputs:", exc)
|
||
|
||
# 2) State güncelle
|
||
if burner_on != self.state.burner_on or tuple(pumps_on) != self.state.pumps_on:
|
||
self.state.last_change_ts = now
|
||
|
||
self.state.burner_on = burner_on
|
||
self.state.pumps_on = tuple(pumps_on)
|
||
self.state.fire_setpoint_c = fire_setpoint_c
|
||
self.state.reason = reason
|
||
self.state.last_mode = mode
|
||
|
||
# 3) DBText logger'a yaz
|
||
try:
|
||
self.logger.insert(
|
||
{
|
||
"ts": now,
|
||
"mode": mode,
|
||
"burner_on": int(burner_on),
|
||
"pumps": ",".join(pumps_on),
|
||
"fire_sp": fire_setpoint_c,
|
||
"reason": reason,
|
||
"bavg": _safe_float(self.state.last_building_avg),
|
||
"out": _safe_float(self.state.last_outside_c),
|
||
"used": _safe_float(self.state.last_used_out_c),
|
||
}
|
||
)
|
||
except Exception:
|
||
pass
|
||
|
||
# 4) Syslog / console üst blok
|
||
try:
|
||
lsys.log_burner_header(
|
||
now=now,
|
||
mode=mode,
|
||
season=self.season,
|
||
building_avg=self.state.last_building_avg,
|
||
outside_c=self.state.last_outside_c,
|
||
used_out_c=self.state.last_used_out_c,
|
||
fire_sp=fire_setpoint_c,
|
||
burner_on=burner_on,
|
||
pumps_on=pumps_on,
|
||
)
|
||
except Exception as exc:
|
||
# Burayı tamamen sessize almayalım, hatayı konsola basalım
|
||
print("BRULOR lsys.log_burner_header error:", exc, "burner.py _apply_outputs()")
|
||
|
||
# ---------------------------------------------------------
|
||
# Ana tick fonksiyonu
|
||
# ---------------------------------------------------------
|
||
|
||
def tick(self, outside_c: Optional[float] = None) -> BurnerState:
|
||
"""
|
||
Tek bir kontrol adımı.
|
||
|
||
- Bina istatistiği BUILDING_READ_PERIOD_S periyodunda bir kez okunur,
|
||
aradaki tick'lerde cache kullanılır.
|
||
- F modunda kararlar *used_out_heat* üzerinden verilir.
|
||
"""
|
||
now = datetime.datetime.now()
|
||
cfg_mode = str(getattr(cfg_s, "BUILD_BURNER", "F")).upper()
|
||
mode = cfg_mode
|
||
|
||
print("tick outside_c:", outside_c)
|
||
# 0) dış ısı: parametre yoksa ortamdan al
|
||
if outside_c is None and getattr(self, "environment", None) is not None:
|
||
try:
|
||
outside_c = self.environment.get_outside_temp_cached()
|
||
except Exception:
|
||
outside_c = None
|
||
print("env:", getattr(self, "environment", None))
|
||
print("tick outside_c 2:", outside_c)
|
||
|
||
# 1) bina istatistiği (periyodik)
|
||
stats = self._get_building_stats(now)
|
||
building_avg = stats.get("avg") if stats else None
|
||
|
||
# 2) used_out_heat güncelle
|
||
used_out = self._update_used_out(now, outside_c)
|
||
|
||
self.state.last_building_avg = building_avg
|
||
self.state.last_outside_c = outside_c
|
||
self.state.last_used_out_c = used_out
|
||
|
||
# 3) ısıtma ihtiyacı
|
||
if mode == "F":
|
||
want_heat = self._should_heat_by_outside(used_out)
|
||
else:
|
||
mode = "B" # saçma değer gelirse B moduna zorla
|
||
want_heat = self._should_heat_by_building(building_avg, now)
|
||
|
||
want_heat = self._respect_min_times(now, want_heat)
|
||
|
||
# 4) fire setpoint – F modunda da used_out üzerinden okunur
|
||
fire_sp = pick_fire_setpoint(used_out)
|
||
max_out = float(getattr(cfg_v, "MAX_OUTLET_C", 45.0))
|
||
fire_sp = min(fire_sp, max_out)
|
||
|
||
# 5) pompalar
|
||
if want_heat:
|
||
if hasattr(self.relays, "enabled_pumps"):
|
||
try:
|
||
pumps_list = list(self.relays.enabled_pumps(self.burner_id))
|
||
pumps = tuple(pumps_list)
|
||
except Exception:
|
||
pumps = tuple(self.default_pumps)
|
||
else:
|
||
pumps = tuple(self.default_pumps)
|
||
else:
|
||
pumps = tuple()
|
||
|
||
reason = (
|
||
f"avg={building_avg}C "
|
||
f"outside_raw={outside_c}C "
|
||
f"used={used_out}C "
|
||
f"want_heat={want_heat}"
|
||
)
|
||
print("tick reason", reason)
|
||
|
||
# 7) Rölelere uygula
|
||
self._apply_outputs(
|
||
now=now,
|
||
mode=mode,
|
||
burner_on=bool(want_heat),
|
||
pumps_on=pumps,
|
||
fire_setpoint_c=fire_sp,
|
||
reason=reason,
|
||
)
|
||
print("state", self.state)
|
||
return self.state
|
||
|
||
|
||
# -------------------------------------------------------------
|
||
# CLI / demo
|
||
# -------------------------------------------------------------
|
||
|
||
|
||
def _demo() -> None:
|
||
"""
|
||
Basit demo: Building + RelayDriver + BuildingEnvironment ile
|
||
BurnerController'ı ayağa kaldır, tick() döngüsü yap.
|
||
"""
|
||
# 1) Bina
|
||
try:
|
||
building = Building()
|
||
print("✅ Building: statics yüklendi\n")
|
||
print(building.pretty_summary())
|
||
except Exception as e:
|
||
print("❌ Building oluşturulamadı:", e)
|
||
raise SystemExit(1)
|
||
|
||
# 2) Ortam (dış ısı, ADC vs.)
|
||
try:
|
||
env = BuildingEnvironment()
|
||
except Exception as e:
|
||
print("⚠️ BuildingEnvironment oluşturulamadı:", e)
|
||
env = None
|
||
|
||
# 3) Röle sürücüsü
|
||
rel = RelayDriver(onoff=False)
|
||
|
||
# 4) Denetleyici
|
||
ctrl = BurnerController(building, rel, environment=env)
|
||
|
||
print("🔥 BurnerController başlatıldı")
|
||
print(f" Burner ID : {ctrl.burner_id}")
|
||
print(f" Çalışma modu (BUILD_BURNER): {getattr(cfg_s, 'BUILD_BURNER', 'F')} (F=dış ısı, B=bina ort)")
|
||
print(f" Igniter kanalı : {ctrl.igniter_ch}")
|
||
print(f" Pompa kanalları : {ctrl.pump_channels}")
|
||
print(f" Varsayılan pompalar : {ctrl.default_pumps}")
|
||
print(f" Konfor setpoint (°C) : {getattr(cfg_v, 'COMFORT_SETPOINT_C', 23.0)}")
|
||
print(f" Histerezis (°C) : {ctrl.cfg.hysteresis_c}")
|
||
print(f" Dış ısı limiti (°C) : {getattr(cfg_v, 'OUTSIDE_HEAT_LIMIT_C', 17.0)}")
|
||
print(f" Max kazan çıkış (°C) : {getattr(cfg_v, 'MAX_OUTLET_C', 45.0)}")
|
||
print(f" Bina okuma periyodu (s) : {ctrl._building_read_period}")
|
||
print(f" OUTSIDE_SMOOTH_SECONDS : {ctrl.outside_smooth_sec}")
|
||
print(f" WEEKEND_HEAT_BOOST_C : {ctrl.weekend_boost_c}")
|
||
print(f" BURNER_COMFORT_OFFSET_C : {ctrl.comfort_offset_c}")
|
||
print("----------------------------------------------------")
|
||
print("BurnerController demo (Ctrl+C ile çık)…")
|
||
|
||
try:
|
||
while True:
|
||
ctrl.tick()
|
||
_time.sleep(5)
|
||
except KeyboardInterrupt:
|
||
print("\nCtrl+C alındı, çıkış hazırlanıyor…")
|
||
finally:
|
||
try:
|
||
rel.all_off()
|
||
print("🔌 Tüm röleler kapatıldı.")
|
||
except Exception as e:
|
||
print(f"⚠️ Röleleri kapatırken hata: {e}")
|
||
finally:
|
||
try:
|
||
rel.cleanup()
|
||
except Exception:
|
||
pass
|
||
|
||
|
||
if __name__ == "__main__":
|
||
_demo()
|