464 lines
14 KiB
Python
464 lines
14 KiB
Python
# -*- coding: utf-8 -*-
|
||
from __future__ import annotations
|
||
|
||
"""
|
||
ebuild/io/legacy_syslog.py
|
||
|
||
Legacy tarzı BRULOR syslog çıktısı üretmek için yardımcı fonksiyonlar.
|
||
- Syslog (/dev/log) + stdout'a aynı formatta basar.
|
||
- burner.BurnerController tarafından her tick'te çağrılan log_burner_header()
|
||
ile eski sistemdeki benzer satırları üretir.
|
||
"""
|
||
|
||
__title__ = "legacy_syslog"
|
||
__author__ = 'Mehmet Karatay & "Saraswati" (ChatGPT)'
|
||
__purpose__ = "Legacy tarzı syslog çıktısı üreten köprü"
|
||
__version__ = "0.3.0"
|
||
__date__ = "2025-11-23"
|
||
|
||
from datetime import datetime, time, timedelta
|
||
from typing import Optional, Iterable, Tuple, Dict
|
||
|
||
import logging
|
||
import logging.handlers
|
||
|
||
try:
|
||
# Mevsim + güneş bilgileri için
|
||
from ..core.season import SeasonController # type: ignore
|
||
from .. import config_statics as cfg # type: ignore
|
||
except ImportError: # test / standalone
|
||
SeasonController = None # type: ignore
|
||
cfg = None # type: ignore
|
||
|
||
try:
|
||
# Çalışma parametreleri (konfor offset, max çıkış vb.)
|
||
from .. import config_runtime as cfg_v # type: ignore
|
||
except ImportError:
|
||
cfg_v = None # type: ignore
|
||
|
||
try:
|
||
# Hat sensörlerinden doğrudan okuma için
|
||
from ..io.ds18b20 import DS18B20Sensor # type: ignore
|
||
except Exception:
|
||
DS18B20Sensor = None # type: ignore
|
||
|
||
#print("legacy_syslog IMPORT EDİLDİ:", __file__)
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Logger kurulumu (Syslog + stdout)
|
||
# ----------------------------------------------------------------------
|
||
_LOGGER: Optional[logging.Logger] = None
|
||
|
||
|
||
def _get_logger() -> logging.Logger:
|
||
global _LOGGER
|
||
if _LOGGER is not None:
|
||
return _LOGGER
|
||
|
||
logger = logging.getLogger("BRULOR")
|
||
logger.setLevel(logging.INFO)
|
||
|
||
if not logger.handlers:
|
||
# Syslog handler (Linux: /dev/log)
|
||
try:
|
||
syslog_handler = logging.handlers.SysLogHandler(address="/dev/log")
|
||
# Syslog mesaj formatı: "BRULOR: [ 1 ... ]"
|
||
fmt = logging.Formatter("%(name)s: %(message)s")
|
||
syslog_handler.setFormatter(fmt)
|
||
logger.addHandler(syslog_handler)
|
||
except Exception as e:
|
||
print("legacy_syslog: SysLogHandler eklenemedi:", e)
|
||
|
||
# Ayrıca stdout'a da yaz (debug için)
|
||
stream_handler = logging.StreamHandler()
|
||
stream_fmt = logging.Formatter("BRULOR: %(message)s")
|
||
stream_handler.setFormatter(stream_fmt)
|
||
logger.addHandler(stream_handler)
|
||
|
||
_LOGGER = logger
|
||
return logger
|
||
|
||
|
||
def send_legacy_syslog(message: str) -> None:
|
||
"""
|
||
Verilen mesajı legacy syslog formatına uygun şekilde ilgili hedefe gönderir.
|
||
- Syslog (/dev/log) → program adı: BRULOR
|
||
- Aynı zamanda stdout'a da yazar (DEBUG amaçlı)
|
||
"""
|
||
try:
|
||
logger = _get_logger()
|
||
logger.info(message)
|
||
except Exception as e:
|
||
# Logger bir sebeple çökerse bile BRULOR satırını kaybetmeyelim
|
||
print("BRULOR:", message, f"(logger error: {e})")
|
||
|
||
|
||
def format_line(line_no: int, body: str) -> str:
|
||
"""
|
||
BRULOR satırını klasik formata göre hazırlar.
|
||
|
||
line_no = 2, body = "Sunrise:07:39 Sunset:17:29 Sistem: On Lic:10094"
|
||
→ "[ 2 Sunrise:07:39 Sunset:17:29 Sistem: On Lic:10094]"
|
||
"""
|
||
return f"[{line_no:3d} {body}]"
|
||
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Header yardımcıları
|
||
# ----------------------------------------------------------------------
|
||
def _format_version_3part(ver: str) -> str:
|
||
"""
|
||
"0.2.1" → "00.02.01" formatı.
|
||
"""
|
||
parts = (ver or "").split(".")
|
||
nums = []
|
||
for p in parts:
|
||
try:
|
||
nums.append(int(p))
|
||
except ValueError:
|
||
nums.append(0)
|
||
while len(nums) < 3:
|
||
nums.append(0)
|
||
return f"{nums[0]:02d}.{nums[1]:02d}.{nums[2]:02d}"
|
||
|
||
|
||
def emit_header_version(line_no: int, now: datetime) -> int:
|
||
"""
|
||
1. satır: versiyon + zaman bilgisi.
|
||
|
||
************** 00.02.01 2025-11-22 18:15:00 *************
|
||
"""
|
||
v_str = _format_version_3part(__version__)
|
||
body = f"************** {v_str} {now.strftime('%Y-%m-%d %H:%M:%S')} *************"
|
||
send_legacy_syslog(format_line(line_no, body))
|
||
return line_no + 1
|
||
|
||
|
||
def emit_header_sunrise(
|
||
line_no: int,
|
||
sunrise: time,
|
||
sunset: time,
|
||
system_on: bool,
|
||
licence_id: int,
|
||
) -> int:
|
||
"""
|
||
2. satır: Sunrise/Sunset + sistem On/Off + Lisans id satırı.
|
||
|
||
Sunrise:07:39 Sunset:17:29 Sistem: On Lic:10094
|
||
"""
|
||
def _fmt_t(t: time) -> str:
|
||
if not t:
|
||
return "--:--"
|
||
return t.strftime("%H:%M")
|
||
|
||
sun_str = f"Sunrise:{_fmt_t(sunrise)} Sunset:{_fmt_t(sunset)} "
|
||
sys_str = "On" if system_on else "Off"
|
||
body = f"{sun_str}Sistem: {sys_str} Lic:{licence_id}"
|
||
send_legacy_syslog(format_line(line_no, body))
|
||
return line_no + 1
|
||
|
||
|
||
def _only_date(s: str) -> str:
|
||
"""
|
||
ISO tarih-zaman stringinden sadece YYYY-MM-DD kısmını alır.
|
||
"""
|
||
if not s:
|
||
return "--"
|
||
s = s.strip()
|
||
if "T" in s:
|
||
s = s.split("T", 1)[0]
|
||
return s
|
||
|
||
def emit_header_season(line_no: int, season_ctrl) -> int:
|
||
info = getattr(season_ctrl, "info", season_ctrl)
|
||
|
||
# 1) SEASON satırı (bunu zaten yazıyorsun)
|
||
season_name = getattr(info, "season", "Unknown")
|
||
s_start = getattr(info, "season_start", "")
|
||
s_end = getattr(info, "season_end", "")
|
||
day_total = getattr(info, "season_day", 0) or 0
|
||
day_passed = getattr(info, "season_passed", 0) or 0
|
||
day_left = getattr(info, "season_remaining", 0) or 0
|
||
|
||
body = (
|
||
f"season : {season_name} {s_start} - {s_end} "
|
||
f"[{day_total} pass:{day_passed} kalan:{day_left}]"
|
||
)
|
||
send_legacy_syslog(format_line(line_no, body))
|
||
line_no += 1
|
||
|
||
# 2) BAHAR / TASARRUF SATIRI
|
||
# SeasonInfo'dan tasarruf penceresini alıyoruz
|
||
saving_start = getattr(info, "saving_start", None)
|
||
saving_stop = getattr(info, "saving_stop", None)
|
||
today = getattr(info, "date", None)
|
||
|
||
# saving_start/stop yoksa bahar satırı YOK
|
||
if saving_start is None or saving_stop is None or today is None:
|
||
return line_no
|
||
|
||
# Gösterim penceresi: saving_start - 3 gün .. saving_stop + 3 gün
|
||
window_start = saving_start - timedelta(days=3)
|
||
window_stop = saving_stop + timedelta(days=3)
|
||
|
||
if window_start <= today <= window_stop:
|
||
# Syslog'ta görünen tarih aralığı GERÇEK tasarruf aralığı olsun:
|
||
# saving_start / saving_stop
|
||
bahar_bas = saving_start.isoformat()
|
||
bahar_bit = saving_stop.isoformat()
|
||
body2 = f"bahar : {bahar_bas} - {bahar_bit}"
|
||
send_legacy_syslog(format_line(line_no, body2))
|
||
line_no += 1
|
||
|
||
return line_no
|
||
|
||
def emit_header_holiday(
|
||
line_no: int,
|
||
is_holiday: bool,
|
||
holiday_label: str,
|
||
) -> int:
|
||
"""
|
||
Tatil satırı (sunrise + season altına).
|
||
"""
|
||
if not is_holiday:
|
||
return line_no
|
||
label = holiday_label or ""
|
||
body = f"Tatil: True Adı: {label}"
|
||
send_legacy_syslog(format_line(line_no, body))
|
||
return line_no + 1
|
||
|
||
|
||
def emit_top_block(
|
||
now: datetime,
|
||
season_ctrl: "SeasonController",
|
||
) -> int:
|
||
"""
|
||
F veya B modundan bağımsız olarak, her tick başında üst bilgiyi üretir.
|
||
Dönüş: bir sonraki satır numarası.
|
||
"""
|
||
line_no = 1
|
||
# 1: versiyon + zaman
|
||
line_no = emit_header_version(line_no, now)
|
||
|
||
# 2: güneş bilgisi
|
||
info = getattr(season_ctrl, "info", season_ctrl)
|
||
sunrise = getattr(info, "sunrise", None)
|
||
sunset = getattr(info, "sunset", None)
|
||
system_on = bool(getattr(info, "system_on", True))
|
||
licence_id = int(
|
||
getattr(info, "licence_id", getattr(cfg, "BUILDING_LICENCEID", 0))
|
||
if cfg is not None
|
||
else 0
|
||
)
|
||
|
||
line_no = emit_header_sunrise(
|
||
line_no=line_no,
|
||
sunrise=sunrise,
|
||
sunset=sunset,
|
||
system_on=system_on,
|
||
licence_id=licence_id,
|
||
)
|
||
|
||
# 3: mevsim
|
||
line_no = emit_header_season(line_no, season_ctrl)
|
||
|
||
# 4: tatil (varsa)
|
||
is_hol = bool(getattr(info, "is_holiday", False))
|
||
hol_label = getattr(info, "holiday_label", "")
|
||
line_no = emit_header_holiday(line_no, is_hol, hol_label)
|
||
|
||
return line_no
|
||
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Hat sensörleri + brülör header
|
||
# ----------------------------------------------------------------------
|
||
def _fmt_temp(t: Optional[float]) -> str:
|
||
if t is None:
|
||
return "None°C"
|
||
return f"{t:5.2f}°C"
|
||
|
||
|
||
def _read_line_temps_from_config() -> Dict[str, Optional[float]]:
|
||
"""
|
||
OUTSIDE_SENSOR_ID, BURNER_OUT_SENSOR_ID ve RETURN_LINE_SENSOR_IDS
|
||
için DS18B20 üzerinden anlık sıcaklıkları okur.
|
||
"""
|
||
temps: Dict[str, Optional[float]] = {}
|
||
if DS18B20Sensor is None or cfg is None:
|
||
return temps
|
||
|
||
outside_id = getattr(cfg, "OUTSIDE_SENSOR_ID", "") or ""
|
||
out_id = getattr(cfg, "BURNER_OUT_SENSOR_ID", "") or ""
|
||
ret_ids = list(getattr(cfg, "RETURN_LINE_SENSOR_IDS", []) or [])
|
||
|
||
all_ids = []
|
||
if outside_id:
|
||
all_ids.append(outside_id)
|
||
if out_id:
|
||
all_ids.append(out_id)
|
||
all_ids.extend([sid for sid in ret_ids if sid])
|
||
|
||
for sid in all_ids:
|
||
try:
|
||
sensor = DS18B20Sensor(serial=sid)
|
||
temps[sid] = sensor.read_temperature()
|
||
except Exception:
|
||
temps[sid] = None
|
||
|
||
return temps
|
||
|
||
|
||
def log_burner_header(
|
||
now: datetime,
|
||
mode: str,
|
||
season: "SeasonController",
|
||
building_avg: Optional[float],
|
||
outside_c: Optional[float],
|
||
used_out_c: Optional[float],
|
||
fire_sp: float,
|
||
burner_on: bool,
|
||
pumps_on: Iterable[str],
|
||
) -> None:
|
||
"""
|
||
burner.BurnerController.tick() her çağrıldığında; header + bina özet +
|
||
hat sensörleri + sistem ısı + brülör/devirdaim satırlarını üretir.
|
||
"""
|
||
try:
|
||
# 1) Üst blok
|
||
if season is not None and SeasonController is not None:
|
||
line_no = emit_top_block(now, season)
|
||
else:
|
||
line_no = emit_header_version(1, now)
|
||
|
||
# 2) Bina ısı satırı
|
||
mode = (mode or "?").upper()
|
||
cfg_mode = ( str(getattr(cfg, "BUILD_BURNER", mode)).upper() if cfg is not None else mode )
|
||
outside_limit = float(getattr(cfg_v, "OUTSIDE_LIMIT_HEAT_C", 0.0))
|
||
min_c = None
|
||
max_c = None
|
||
avg_c = building_avg
|
||
|
||
body_build = (
|
||
f"Build [{mode}-{cfg_mode}] "
|
||
f"Heats[Min:{_fmt_temp(min_c)} Avg:{_fmt_temp(avg_c)} Max:{_fmt_temp(max_c)}] L:{outside_limit}"
|
||
)
|
||
send_legacy_syslog(format_line(line_no, body_build))
|
||
line_no += 1
|
||
|
||
# 3) Hat sensörleri
|
||
line_temps = _read_line_temps_from_config()
|
||
outside_id = getattr(cfg, "OUTSIDE_SENSOR_ID", "") if cfg is not None else ""
|
||
outside_name = (
|
||
getattr(cfg, "OUTSIDE_SENSOR_NAME", "Dış Isı 1")
|
||
if cfg is not None
|
||
else "Dış Isı 1"
|
||
)
|
||
out_id = getattr(cfg, "BURNER_OUT_SENSOR_ID", "") if cfg is not None else ""
|
||
out_name = (
|
||
getattr(cfg, "BURNER_OUT_SENSOR_NAME", "Çıkış Isı 2")
|
||
if cfg is not None
|
||
else "Çıkış Isı 2"
|
||
)
|
||
ret_ids = (
|
||
list(getattr(cfg, "RETURN_LINE_SENSOR_IDS", []) or [])
|
||
if cfg is not None
|
||
else []
|
||
)
|
||
name_map = getattr(cfg, "RETURN_LINE_SENSOR_NAME_MAP", {}) if cfg is not None else {}
|
||
|
||
if outside_id:
|
||
t = line_temps.get(outside_id, outside_c)
|
||
body = f"{outside_name:<15}: {_fmt_temp(t)} - {outside_id} "
|
||
send_legacy_syslog(format_line(line_no, body))
|
||
line_no += 1
|
||
|
||
if out_id:
|
||
t = line_temps.get(out_id)
|
||
body = f"{out_name:<15}: {_fmt_temp(t)} - {out_id} "
|
||
send_legacy_syslog(format_line(line_no, body))
|
||
line_no += 1
|
||
|
||
for sid in ret_ids:
|
||
if not sid:
|
||
continue
|
||
t = line_temps.get(sid)
|
||
nm = name_map.get(sid, sid)
|
||
body = f"{nm:<15}: {_fmt_temp(t)} - {sid} "
|
||
send_legacy_syslog(format_line(line_no, body))
|
||
line_no += 1
|
||
|
||
# 4) Sistem ısı satırı (used_out + weekend/comfort offset)
|
||
weekend_boost = (
|
||
float(getattr(cfg_v, "WEEKEND_HEAT_BOOST_C", 0.0))
|
||
if cfg_v is not None
|
||
else 0.0
|
||
)
|
||
comfort_off = (
|
||
float(getattr(cfg_v, "BURNER_COMFORT_OFFSET_C", 0.0))
|
||
if cfg_v is not None
|
||
else 0.0
|
||
)
|
||
body_sys = (
|
||
f"Sistem Isı : {_fmt_temp(used_out_c)} "
|
||
f"[w:{weekend_boost:g} c:{comfort_off:g}]"
|
||
)
|
||
send_legacy_syslog(format_line(line_no, body_sys))
|
||
line_no += 1
|
||
|
||
# 5) Brülör / devirdaim satırları (istatistikler şimdilik 0)
|
||
max_out = (
|
||
float(getattr(cfg_v, "MAX_OUTLET_C", fire_sp))
|
||
if cfg_v is not None
|
||
else fire_sp
|
||
)
|
||
br_status = "<CALISIYOR>" if burner_on else "<CALISMIYOR>"
|
||
br_flag = 1 if burner_on else 0
|
||
|
||
body_br = (
|
||
f"Brulor Motor : {br_status} [{br_flag}] "
|
||
f"0 00:00:00 00:00:00 L:{max_out:.1f}"
|
||
)
|
||
send_legacy_syslog(format_line(line_no, body_br))
|
||
line_no += 1
|
||
|
||
pumps_on_list = list(pumps_on or [])
|
||
pump_count = len(pumps_on_list)
|
||
dev_status = "<CALISIYOR>" if pump_count > 0 else "<CALISMIYOR>"
|
||
min_ret = (
|
||
float(getattr(cfg_v, "CIRCULATION_MIN_RETURN_C", 25.0))
|
||
if cfg_v is not None
|
||
else 25.0
|
||
)
|
||
pumps_str = ",".join(pumps_on_list) if pumps_on_list else "-"
|
||
|
||
body_dev = (
|
||
f"Devirdaim Mot: {dev_status} "
|
||
f"[{pump_count}] 0 00:00:00 00:00:00 "
|
||
f"L:{pumps_str} {min_ret:.1f}"
|
||
)
|
||
send_legacy_syslog(format_line(line_no, body_dev))
|
||
|
||
except Exception as exc:
|
||
print("BRULOR log_burner_header ERROR:", exc)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
# Basit standalone test
|
||
now = datetime.now()
|
||
if SeasonController is None:
|
||
emit_header_version(1, now)
|
||
else:
|
||
sc = SeasonController()
|
||
log_burner_header(
|
||
now=now,
|
||
mode="F",
|
||
season=sc,
|
||
building_avg=None,
|
||
outside_c=None,
|
||
used_out_c=None,
|
||
fire_sp=45.0,
|
||
burner_on=False,
|
||
pumps_on=(),
|
||
)
|