ebuild_rasp2/ebuild/io/legacy_syslog.py

454 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
__title__ = "legacy_syslog"
__author__ = 'Mehmet Karatay & "Saraswati" (ChatGPT)'
__purpose__ = "Legacy tarzı syslog çıktısı üreten köprü"
__version__ = "0.2.1"
__date__ = "2025-11-22"
"""
ebuild/io/legacy_syslog.py
Revision : 2025-11-22
Authors : Mehmet Karatay & "Saraswati" (ChatGPT)
Amaç
-----
- Eski /brulor.py'nin syslog formatına yakın satırlar üretmek.
- Python logging + SysLogHandler kullanarak:
program adı: BRULOR
mesaj : "[ 1 .... ]" formatında
Bu modül:
- send_legacy_syslog(message) → tek satır yazar
- emit_top_block(now, SeasonController) →
1) versiyon satırı
2) güneş (sunrise/sunset) + sistem/licence
3) mevsim + bahar dönemi + tatil satırları
- log_burner_header(...) → BurnerController.tick() için üst blok +
sistem ısı + motor bilgilerini basar.
"""
from datetime import datetime, time, timedelta, date
from typing import Optional
import logging
import logging.handlers
try:
# SeasonController ve konfig
from ..core.season import SeasonController
from .. import config_statics as cfg
from .. import config_runtime as cfg_v
except ImportError: # test / standalone
SeasonController = None # type: ignore
cfg = None # type: ignore
cfg_v = None # type: ignore
# ----------------------------------------------------------------------
# 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)
# Aynı handler'ları ikinci kez eklemeyelim
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:
# /dev/log yoksa sessizce geç; sadece stdout'a yazacağız
pass
# Konsol çıktısı (debug için)
stream_handler = logging.StreamHandler()
stream_fmt = logging.Formatter("INFO:BRULOR:%(message)s")
stream_handler.setFormatter(stream_fmt)
logger.addHandler(stream_handler)
_LOGGER = logger
return logger
# ----------------------------------------------------------------------
# Temel çıktı fonksiyonları
# ----------------------------------------------------------------------
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.
Örnek:
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]"
Not:
Burada "BRULOR" yazmıyoruz; syslog program adı zaten BRULOR olacak.
"""
return f"[{line_no:3d} {body}]"
def _format_version_3part(ver: str) -> str:
"""
__version__ string'ini "00.02.01" formatına çevirir.
Örnek:
"0.2.1""00.02.01"
"""
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}"
# ----------------------------------------------------------------------
# Üst blok: versiyon + güneş + mevsim + tatil
# ----------------------------------------------------------------------
def emit_header_version(line_no: int, now: datetime) -> int:
"""
1. satır: Versiyon ve zaman bilgisi.
Örnek:
[ 1 ************** 00.02.01 2025-11-22 22:20:19 *************]
"""
ver = _format_version_3part(__version__)
ts = now.strftime("%Y-%m-%d %H:%M:%S")
body = f"************** {ver} {ts} *************"
send_legacy_syslog(format_line(line_no, body))
return line_no + 1
def emit_header_sunrise_sunset(
line_no: int,
sunrise: Optional[time],
sunset: Optional[time],
system_on: bool,
licence_id: int,
) -> int:
"""
2. satır: Güneş bilgisi + Sistem On/Off + Lisans id.
Örnek:
[ 2 Sunrise:07:39 Sunset:17:29 Sistem: On Lic:10094]
"""
sun_str = ""
if sunrise is not None:
sun_str += f"Sunrise:{sunrise.strftime('%H:%M')} "
if sunset is not None:
sun_str += f"Sunset:{sunset.strftime('%H:%M')} "
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 _normalize_iso_date(s: Optional[str]) -> str:
"""
SeasonInfo.season_start / season_end alanlarını sadeleştirir.
Örn: '2025-09-23T16:33:10.687982+03:00''2025-09-23'
"""
if not s:
return "--"
s = s.strip()
if "T" in s:
return s.split("T", 1)[0]
return s
def emit_header_season(
line_no: int,
season_ctrl: SeasonController,
) -> int:
"""
Sunrise satırının altına mevsim + (varsa) bahar tasarruf dönemi satırını basar.
Beklenen format:
BRULOR [ 3 season : Sonbahar 2025-09-23 - 2025-12-20 [89 pass:60 kalan:28] ]
BRULOR [ 4 bahar : 2025-09-23 - 2025-10-13 ]
Notlar:
- Bilgiler SeasonController.info içinden okunur (dict veya obje olabilir).
- bahar_tasarruf True DEĞİLSE bahar satırı hiç basılmaz.
"""
if season_ctrl is None:
return line_no
info = getattr(season_ctrl, "info", None)
if info is None:
return line_no
# dataclass benzeri objeden alanları çek
season = getattr(info, "season", "Unknown")
s_start = _normalize_iso_date(getattr(info, "season_start", ""))
s_end = _normalize_iso_date(getattr(info, "season_end", ""))
s_day = int(getattr(info, "season_day", 0) or 0)
s_pass = int(getattr(info, "season_passed", 0) or 0)
s_rem = int(getattr(info, "season_remaining", 0) or 0)
body = (
f"season : {season} {s_start} - {s_end} "
f"[{s_day} pass:{s_pass} kalan:{s_rem}]"
)
send_legacy_syslog(format_line(line_no, body))
line_no += 1
# Bahar / tasarruf dönemi bilgileri
is_season = bool(getattr(info, "is_season", False))
saving_start = getattr(info, "saving_start", None)
saving_stop = getattr(info, "saving_stop", None)
if is_season and isinstance(saving_start, date) and isinstance(saving_stop, date):
# Kullanıcı isteği: öncesi/sonrası 3'er gün göster
show_start = saving_start - timedelta(days=3)
show_stop = saving_stop + timedelta(days=3)
body = (
f"bahar : {show_start.isoformat()} - {show_stop.isoformat()}"
)
send_legacy_syslog(format_line(line_no, body))
line_no += 1
# Eğer resmi tatil bilgisi de varsa opsiyonel satır
is_holiday = bool(getattr(info, "is_holiday", False))
holiday_label = getattr(info, "holiday_label", "")
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
# ----------------------------------------------------------------------
# Dışarıdan çağrılacak üst-blok helper
# ----------------------------------------------------------------------
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.
Sıra:
1) Versiyon + zaman
2) Sunrise / Sunset / Sistem: On/Off / Lic
3) Mevsim bilgisi (SeasonController.to_syslog_lines() → sadeleştirilmiş)
4) Tatil bilgisi (sadece tatil varsa)
5) Bir sonraki satır numarasını döndürür (bina ısı satırları için).
"""
line_no = 1
# 1) Versiyon
line_no = emit_header_version(line_no, now)
# Konfigten sistem ve lisans bilgileri
if cfg is not None:
licence_id = int(getattr(cfg, "BUILDING_LICENCEID", 0))
system_onoff = int(getattr(cfg, "BUILDING_SYSTEMONOFF", 1))
else:
licence_id = 0
system_onoff = 1
# SeasonController.info'dan sunrise/sunset okumayı dene
sunrise = None
sunset = None
if season_ctrl is not None:
info = getattr(season_ctrl, "info", None)
if info is not None:
sunrise = getattr(info, "sunrise", None)
sunset = getattr(info, "sunset", None)
line_no = emit_header_sunrise_sunset(
line_no=line_no,
sunrise=sunrise,
sunset=sunset,
system_on=bool(system_onoff),
licence_id=licence_id,
)
# Mevsim + bahar dönemi
line_no = emit_header_season(line_no, season_ctrl)
# Sonraki satır: bina ısı / dış ısı / F-B detayları için kullanılacak
return line_no
# ----------------------------------------------------------------------
# BurnerController entegrasyonu
# ----------------------------------------------------------------------
def _fmt_c(val: Optional[float]) -> str:
"""Dereceyi 'None°C' veya '23.4°C' gibi tek tip formatlar."""
if val is None:
return "None°C"
try:
return f"{float(val):.2f}°C"
except Exception:
return "None°C"
def log_burner_header(
now: datetime,
mode: str,
season,
building_avg: Optional[float],
outside_c: Optional[float],
used_out_c: Optional[float],
fire_sp: float,
burner_on: bool,
pumps_on,
) -> None:
"""BurnerController.tick() için tek entry-point.
Buradan:
1) Versiyon + güneş + mevsim / bahar / tatil blokları
2) Bina / sistem ısı bilgisi
3) Brülör ve devirdaim motor satırları
syslog'a basılır.
"""
# 1) Üst blok
try:
line_no = emit_top_block(now, season)
except Exception as exc:
# Üst blok patlasa bile alttakileri basalım ki log tamamen kaybolmasın
send_legacy_syslog(format_line(1, f"emit_top_block error: {exc}"))
line_no = 2
# 2) Bina/sistem ısı satırı
try:
# Çalışma modu: F (dış ısı) / B (bina ort)
cfg_mode = getattr(cfg, "BUILD_BURNER", "F") if cfg is not None else "F"
mode_cfg = str(cfg_mode).upper()
# Isı limiti (dış ısı limiti)
limit = None
if cfg_v is not None:
try:
limit = float(getattr(cfg_v, "OUTSIDE_HEAT_LIMIT_C", 0.0))
except Exception:
limit = None
body = (
f"Build [{mode}-{mode_cfg}] "
f"Heats[Min:{_fmt_c(None)} Avg:{_fmt_c(building_avg)} Max:{_fmt_c(None)}]"
)
if limit is not None:
# Son köşeli parantezi atmamak için ufak hack
body = body[:-1] + f" L:{limit:.1f}]"
send_legacy_syslog(format_line(line_no, body))
line_no += 1
# Eski formata yakın "Sistem Isı" satırı
w_boost = 0.0
c_off = 0.0
if cfg_v is not None:
try:
w_boost = float(getattr(cfg_v, "WEEKEND_HEAT_BOOST_C", 0.0))
except Exception:
pass
try:
c_off = float(getattr(cfg_v, "BURNER_COMFORT_OFFSET_C", 0.0))
except Exception:
pass
body = f"Sistem Isı : {_fmt_c(used_out_c)} [w:{int(w_boost)} c:{int(c_off)}]"
send_legacy_syslog(format_line(line_no, body))
line_no += 1
# 3) Brülör motor satırı
br_state = "<CALISIYOR>" if burner_on else "<CALISMIYOR>"
br_flag = 1 if burner_on else 0
body = (
f"Brulor Motor : {br_state} [{br_flag}] 0 "
f"00:00:00 00:00:00 L:{fire_sp:.1f}"
)
send_legacy_syslog(format_line(line_no, body))
line_no += 1
# 4) Devirdaim pompa satırı
pumps_on = tuple(pumps_on or ())
pump_state = "<CALISIYOR>" if pumps_on else "<CALISMIYOR>"
pump_flag = 1 if pumps_on else 0
pump_label = pumps_on[0] if pumps_on else "-"
circ_limit = None
if cfg_v is not None:
try:
circ_limit = float(getattr(cfg_v, "CIRCULATION_MIN_RETURN_C", 25.0))
except Exception:
circ_limit = None
if circ_limit is None:
body = (
f"Devirdaim Mot: {pump_state} [{pump_flag}] 0 "
f"00:00:00 00:00:00 L:{pump_label}"
)
else:
body = (
f"Devirdaim Mot: {pump_state} [{pump_flag}] 0 "
f"00:00:00 00:00:00 L:{pump_label} {circ_limit:.1f}"
)
send_legacy_syslog(format_line(line_no, body))
except Exception as exc:
# Her türlü hata durumunda sessiz kalmak yerine loga yaz
send_legacy_syslog(format_line(line_no, f"log_burner_header error: {exc}"))
# ----------------------------------------------------------------------
# Örnek kullanım (standalone test)
# ----------------------------------------------------------------------
if __name__ == "__main__":
# Bu blok sadece modülü tek başına test etmek için:
# python3 -m ebuild.io.legacy_syslog
if SeasonController is None:
raise SystemExit("SeasonController import edilemedi (test ortamı).")
now = datetime.now()
# SeasonController.from_now() kullanıyorsan:
try:
season = SeasonController.from_now()
except Exception as e:
raise SystemExit(f"SeasonController.from_now() hata: {e}")
next_line = emit_top_block(now, season)
# Test için bina ısısını dummy bas:
body = "Bina Isı : [ 20.10 - 22.30 - 24.50 ]"
send_legacy_syslog(format_line(next_line, body))