Add guard zones
This commit is contained in:
@@ -15,6 +15,8 @@ Mega SX, ZTC и др.) в формате Prometheus.
|
||||
ZONT_POLL_INTERVAL — интервал опроса, сек (по умолчанию 60)
|
||||
EXPORTER_PORT — порт HTTP-сервера метрик (по умолчанию 9101)
|
||||
LOG_LEVEL — DEBUG/INFO/WARNING/ERROR (по умолчанию INFO)
|
||||
ZONT_DOOR_INVERT — инвертировать логику двери (1=да, 0=нет, по умолчанию 0)
|
||||
ZONT_DOOR_SENSOR_TYPES — через запятую, sensor_type дискретных датчиков (по умолчанию 20)
|
||||
"""
|
||||
import os
|
||||
import time
|
||||
@@ -31,7 +33,13 @@ DEVICE_ID = int(os.getenv('ZONT_DEVICE_ID'))
|
||||
API_BASE = "https://my.zont.online/api"
|
||||
SCRAPE_INTERVAL = int(os.getenv('SCRAPE_INTERVAL', 60))
|
||||
EXPORTER_PORT = int(os.getenv('EXPORTER_PORT', 9101))
|
||||
LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO').upper()
|
||||
LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO').upper()
|
||||
|
||||
# Логика двери
|
||||
DOOR_INVERT = os.getenv('ZONT_DOOR_INVERT', '0') == '1'
|
||||
DOOR_SENSOR_TYPES = set(
|
||||
int(x) for x in os.getenv('ZONT_DOOR_SENSOR_TYPES', '20').split(',') if x.strip()
|
||||
)
|
||||
|
||||
# --- Метрики Prometheus ---
|
||||
|
||||
@@ -73,8 +81,28 @@ BOILER_DS = Gauge('zont_boiler_ds_celsius', 'Boiler DHW setpoint', ['device_id',
|
||||
BOILER_CONNECTED = Gauge('zont_boiler_connected', 'Boiler connected (1=yes,0=no)', ['device_id', 'adapter_id'])
|
||||
BOILER_FAIL = Gauge('zont_boiler_fail', 'Boiler failure (1=fail)', ['device_id', 'adapter_id'])
|
||||
|
||||
# Дискретные датчики (дверь, окно и т.п.) на аналоговых входах
|
||||
DOOR_OPEN = Gauge(
|
||||
'zont_door_open',
|
||||
'Door open (1=open, 0=closed)',
|
||||
['device_id', 'input_id', 'name']
|
||||
)
|
||||
DOOR_VOLTAGE = Gauge(
|
||||
'zont_door_voltage_volts',
|
||||
'Voltage on door sensor analog input',
|
||||
['device_id', 'input_id', 'name']
|
||||
)
|
||||
|
||||
# Охранные зоны (помещения)
|
||||
GUARD_ZONE_ONGUARD = Gauge(
|
||||
'zont_guard_zone_onguard',
|
||||
'Guard zone on guard (1=yes, 0=no)',
|
||||
['device_id', 'zone_id', 'name']
|
||||
)
|
||||
|
||||
# Справочники ID -> имя для читаемых меток
|
||||
_name_cache = {}
|
||||
_analog_input_types = {} # {input_id: sensor_type}
|
||||
|
||||
auth_token = None
|
||||
|
||||
@@ -115,9 +143,19 @@ def build_name_cache(device):
|
||||
cache[hc['id']] = hc.get('name') or f"hc_{hc['id']}"
|
||||
for b in z3k.get('boiler_adapters', []):
|
||||
cache[b['id']] = b.get('name') or f"boiler_{b['id']}"
|
||||
for gz in z3k.get('guard_zones', []):
|
||||
cache[gz['id']] = gz.get('name') or f"zone_{gz['id']}"
|
||||
return cache
|
||||
|
||||
|
||||
def build_analog_input_types(device):
|
||||
"""Возвращает {input_id: sensor_type} для всех аналоговых входов."""
|
||||
result = {}
|
||||
for ai in device.get('z3k_config', {}).get('analog_inputs', []):
|
||||
result[ai['id']] = ai.get('sensor_type')
|
||||
return result
|
||||
|
||||
|
||||
GSM_STATE_MAP = {
|
||||
'not-registered': 0,
|
||||
'home-network': 1,
|
||||
@@ -128,7 +166,7 @@ GSM_STATE_MAP = {
|
||||
|
||||
|
||||
def collect_metrics():
|
||||
global auth_token, _name_cache
|
||||
global auth_token, _name_cache, _analog_input_types
|
||||
|
||||
if not auth_token:
|
||||
get_auth_token()
|
||||
@@ -161,8 +199,9 @@ def collect_metrics():
|
||||
logging.warning(f"Device {DEVICE_ID} not found in account.")
|
||||
return
|
||||
|
||||
# Обновляем кэш имён
|
||||
# Обновляем кэши
|
||||
_name_cache = build_name_cache(device)
|
||||
_analog_input_types = build_analog_input_types(device)
|
||||
|
||||
did = str(DEVICE_ID)
|
||||
|
||||
@@ -214,11 +253,29 @@ def collect_metrics():
|
||||
if 'sensor_ok' in obj:
|
||||
SENSOR_OK.labels(device_id=did, object_id=obj_id, name=name).set(1 if obj['sensor_ok'] else 0)
|
||||
|
||||
# Аналоговый вход (напряжение)
|
||||
# Аналоговый вход для контроля напряжения (id=20550) — оставляем отдельную метрику
|
||||
if isinstance(obj, dict) and 'voltage' in obj and obj_id == '20550':
|
||||
if obj['voltage'] is not None:
|
||||
ANALOG_VOLTAGE.labels(device_id=did, object_id=obj_id, name=name).set(obj['voltage'])
|
||||
|
||||
# Дискретные датчики (дверь/окно) — есть поля voltage и value,
|
||||
# а в конфиге sensor_type входит в DOOR_SENSOR_TYPES
|
||||
if (isinstance(obj, dict) and 'value' in obj and 'voltage' in obj
|
||||
and _analog_input_types.get(int(obj_id)) in DOOR_SENSOR_TYPES):
|
||||
if obj.get('voltage') is not None:
|
||||
DOOR_VOLTAGE.labels(device_id=did, input_id=obj_id, name=name).set(obj['voltage'])
|
||||
if obj.get('value') is not None:
|
||||
door_value = obj['value']
|
||||
if DOOR_INVERT:
|
||||
door_value = 1 - door_value
|
||||
DOOR_OPEN.labels(device_id=did, input_id=obj_id, name=name).set(door_value)
|
||||
|
||||
# Охранные зоны (есть поле onguard)
|
||||
if isinstance(obj, dict) and 'onguard' in obj:
|
||||
GUARD_ZONE_ONGUARD.labels(device_id=did, zone_id=obj_id, name=name).set(
|
||||
1 if obj['onguard'] else 0
|
||||
)
|
||||
|
||||
# Отопительные контуры
|
||||
if isinstance(obj, dict) and 'target_temp' in obj and 'worktime' in obj:
|
||||
if obj.get('target_temp') is not None:
|
||||
@@ -257,7 +314,7 @@ if __name__ == '__main__':
|
||||
exit(1)
|
||||
|
||||
get_auth_token()
|
||||
start_http_server(EXPORTER_PORT) # <-- было 9101
|
||||
start_http_server(EXPORTER_PORT)
|
||||
logging.info(f"Prometheus exporter started on port {EXPORTER_PORT}")
|
||||
|
||||
while True:
|
||||
|
||||
Reference in New Issue
Block a user