454 lines
14 KiB
Python
454 lines
14 KiB
Python
# -*- 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))
|