ebuild_rasp2/ebuild/io/z3legacy_syslog.py

464 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- 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=(),
)