"""Интроспекция шаблонов A/B/F через C-ядро (единый источник — таблицы src/aircon/tables.cpp; дублей в Python нет).""" from __future__ import annotations from dataclasses import dataclass from functools import lru_cache from typing import Optional, Tuple from ._cffi import ffi, lib from .const import Prop, Template, ValueKind _BASE_TYPE_KINDS = { "integer": ValueKind.INT, "boolean": ValueKind.BOOL, "string": ValueKind.STRING, } @dataclass(frozen=True) class PropertyInfo: prop: Prop name: str base_type: str kind: ValueKind read_only: bool raw_min: Optional[int] raw_max: Optional[int] def detect(oem_model: Optional[str]) -> Optional[Template]: """Шаблон по oem_model; None — модель неизвестна C-ядру.""" if not oem_model: return None result = int(lib.fgl_template_detect(oem_model.encode("utf-8"))) if result < 0: return None return Template(result) @lru_cache(maxsize=None) def template_info(template: Template) -> Tuple[PropertyInfo, ...]: """Все свойства шаблона (порядок констант Prop).""" out = [] for prop in Prop: name = lib.fgl_prop_name(int(template), int(prop)) if name == ffi.NULL: continue base_type = ffi.string( lib.fgl_prop_base_type(int(template), int(prop)) ).decode("ascii") read_only = int(lib.fgl_prop_read_only(int(template), int(prop))) == 1 raw_min = ffi.new("int64_t*") raw_max = ffi.new("int64_t*") has_range = ( int(lib.fgl_prop_raw_range(int(template), int(prop), raw_min, raw_max)) == 1 ) out.append( PropertyInfo( prop=prop, name=ffi.string(name).decode("ascii"), base_type=base_type, kind=_BASE_TYPE_KINDS.get(base_type, ValueKind.INT), read_only=read_only, raw_min=int(raw_min[0]) if has_range else None, raw_max=int(raw_max[0]) if has_range else None, ) ) return tuple(out) @lru_cache(maxsize=None) def prop_info(template: Template, prop: Prop) -> Optional[PropertyInfo]: for info in template_info(template): if info.prop == prop: return info return None def convert_to_display(template: Template, prop: Prop, raw: int) -> int: return int(lib.fgl_convert_to_display(int(template), int(prop), raw)) def convert_from_input(template: Template, prop: Prop, api_value: int) -> int: return int(lib.fgl_convert_from_input(int(template), int(prop), api_value)) def api_range(template: Template, prop: Prop) -> Optional[Tuple[int, int]]: """Диапазон API-значений (после to_display); None — диапазон не задан.""" info = prop_info(template, prop) if info is None or info.raw_min is None or info.raw_max is None: return None lo = convert_to_display(template, prop, info.raw_min) hi = convert_to_display(template, prop, info.raw_max) return (min(lo, hi), max(lo, hi))