- config flow: template (A/B/F с пояснениями, значения с устройства, определение по oem_model; для неизвестных моделей — пробинг A/B/F тремя короткими сессиями) -> capabilities (23 тумблера с описаниями, дефолты из device_capabilities/num_dir/ответов) -> limits (диапазон/шаг, ручная конверсия); каждая страница с пометкой «значения определены автоматически, можно пропустить» - features.py: FeatureSet/LiveValues/device_default/resolve_features; entry.data["features"] хранит выбор пользователя; reconfigure предзаполняет; YAML-import получает дефолты - сущности создаются только для включённых фич: climate (режимы/скорости/ swing/пресеты), switch, select заслонок (caps+num_dir), датчик комнаты; исправлено появление select без ламелей - trial: caps/num_dir и presence-зонды (необязательные свойства), answered; координатор ждёт первые свойства перед созданием сущностей (feature- дефолты без гонки) - атрибут description у сущностей (HA не поддерживает тултипы) + описания фич в мастере (en/ru) - тесты: features, probing, обновлённые flow/entities/repairs; 57 components
264 lines
11 KiB
Python
264 lines
11 KiB
Python
"""Константы и словари значений интеграции fglair (PROTOCOL §8)."""
|
|
from __future__ import annotations
|
|
|
|
from homeassistant.components.climate import HVACMode
|
|
|
|
from pyfglair import Prop, State, Template
|
|
from pyfglair.provision import REGIONS as _PROVISION_REGIONS
|
|
|
|
DOMAIN = "fglair"
|
|
|
|
CONF_HOST = "host"
|
|
CONF_DEVICE_PORT = "device_port"
|
|
CONF_DSN = "dsn"
|
|
CONF_LANIP_KEY = "lanip_key"
|
|
CONF_LANIP_KEY_ID = "lanip_key_id"
|
|
CONF_MODEL = "model"
|
|
CONF_TEMPLATE = "template"
|
|
CONF_NAME = "name"
|
|
CONF_LISTEN_PORT = "listen_port"
|
|
CONF_KEEPALIVE_MS = "keepalive_ms"
|
|
CONF_REGION = "region"
|
|
CONF_EMAIL = "email"
|
|
CONF_PASSWORD = "password"
|
|
CONF_CONFIG_JSON = "config_json"
|
|
CONF_OVERRIDES = "overrides"
|
|
CONF_TEMP_STEP = "temp_step"
|
|
CONF_FEATURES = "features"
|
|
CONF_TEMP_MIN = "temp_min"
|
|
CONF_TEMP_MAX = "temp_max"
|
|
CONF_TEMP_NUM = "temp_num"
|
|
CONF_TEMP_DEN = "temp_den"
|
|
CONF_TEMP_OFFSET = "temp_offset"
|
|
|
|
DEFAULT_DEVICE_PORT = 80
|
|
# 0 => свободный эфемерный порт: несколько ConfigEntry (несколько
|
|
# кондиционеров) не конфликтуют; модулю фактический порт сообщается в
|
|
# local_reg (PROTOCOL §1).
|
|
DEFAULT_LISTEN_PORT = 0
|
|
DEFAULT_KEEPALIVE_MS = 15000
|
|
TRIAL_TIMEOUT = 10.0
|
|
|
|
REGIONS = sorted(_PROVISION_REGIONS)
|
|
|
|
TRIAL_ERRORS = {
|
|
"no_slots": "no_slots",
|
|
"key_mismatch": "key_mismatch",
|
|
"unreachable": "cannot_connect",
|
|
"timeout": "timeout",
|
|
"start_failed": "cannot_connect",
|
|
"properties_missing": "properties_missing",
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# operation_mode (PROTOCOL §8.3): 0=OFF, 1=ON, 2=AUTO, 3=COOL, 4=DRY,
|
|
# 5=FAN, 6=HEAT
|
|
# ---------------------------------------------------------------------------
|
|
OPERATION_MODE_OFF = 0
|
|
OPERATION_MODE_ON = 1
|
|
HVAC_MODE_TO_VALUE = {
|
|
HVACMode.AUTO: 2,
|
|
HVACMode.COOL: 3,
|
|
HVACMode.DRY: 4,
|
|
HVACMode.FAN_ONLY: 5,
|
|
HVACMode.HEAT: 6,
|
|
}
|
|
HVAC_VALUE_TO_MODE = {value: mode for mode, value in HVAC_MODE_TO_VALUE.items()}
|
|
|
|
# device_capabilities, биты (PROTOCOL §8.5)
|
|
CAP_HVAC = {
|
|
HVACMode.AUTO: 4,
|
|
HVACMode.COOL: 0,
|
|
HVACMode.DRY: 1,
|
|
HVACMode.FAN_ONLY: 2,
|
|
HVACMode.HEAT: 3,
|
|
}
|
|
CAP_FAN = {"auto": 5, "high": 6, "medium": 7, "low": 8, "quiet": 9}
|
|
CAP_SWING_VERTICAL = 10
|
|
CAP_SWING_HORIZONTAL = 11
|
|
CAP_ECONOMY = 12
|
|
CAP_MIN_HEAT = 13
|
|
CAP_INDOOR_FAN_CONTROL = 14
|
|
CAP_POWERFUL = 16
|
|
CAP_OUTDOOR_LOW_NOISE = 17
|
|
CAP_COIL_DRY = 18
|
|
|
|
# fan_speed (PROTOCOL §8.3)
|
|
FAN_VALUE_TO_MODE = {0: "quiet", 1: "low", 2: "medium", 3: "high", 4: "auto"}
|
|
FAN_MODE_TO_VALUE = {mode: value for value, mode in FAN_VALUE_TO_MODE.items()}
|
|
|
|
# preset: ECO = economy_mode, BOOST = powerful_mode
|
|
PRESET_ECO = "eco"
|
|
PRESET_BOOST = "boost"
|
|
PRESET_PROP = {
|
|
PRESET_ECO: Prop.ECONOMY_MODE,
|
|
PRESET_BOOST: Prop.POWERFUL_MODE,
|
|
}
|
|
PRESET_CAP = {
|
|
PRESET_ECO: CAP_ECONOMY,
|
|
PRESET_BOOST: CAP_POWERFUL,
|
|
}
|
|
|
|
SWING_MODES = ["off", "on"]
|
|
|
|
# switch-свойства: prop -> (translation_key, capability bit | None)
|
|
SWITCH_PROPS: dict[Prop, tuple[str, int | None]] = {
|
|
Prop.ECONOMY_MODE: ("economy_mode", CAP_ECONOMY),
|
|
Prop.POWERFUL_MODE: ("powerful_mode", CAP_POWERFUL),
|
|
Prop.COIL_DRY_MODE: ("coil_dry_mode", CAP_COIL_DRY),
|
|
Prop.MIN_HEAT: ("min_heat", CAP_MIN_HEAT),
|
|
Prop.OUTDOOR_LOW_NOISE: ("outdoor_low_noise", CAP_OUTDOOR_LOW_NOISE),
|
|
Prop.HUMAN_DET_AUTO_SAVE: ("human_det_auto_save", None),
|
|
Prop.WIFI_LED_ENABLE: ("wifi_led_enable", None),
|
|
Prop.INDOOR_FAN_CONTROL: ("indoor_fan_control", CAP_INDOOR_FAN_CONTROL),
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Feature-модель (план: страница capabilities в мастере + фильтрация сущностей).
|
|
# key -> (capability bit | None, свойство-источник | None).
|
|
# Для битовых фич дефолт = бит в device_capabilities; для небитовых —
|
|
# наличие свойства в ответах устройства; для заслонок — num_dir > 1.
|
|
# ---------------------------------------------------------------------------
|
|
FEATURES: dict[str, tuple[int | None, Prop | None]] = {
|
|
"mode_cool": (CAP_HVAC[HVACMode.COOL], Prop.OPERATION_MODE),
|
|
"mode_dry": (CAP_HVAC[HVACMode.DRY], Prop.OPERATION_MODE),
|
|
"mode_fan": (CAP_HVAC[HVACMode.FAN_ONLY], Prop.OPERATION_MODE),
|
|
"mode_heat": (CAP_HVAC[HVACMode.HEAT], Prop.OPERATION_MODE),
|
|
"mode_auto": (CAP_HVAC[HVACMode.AUTO], Prop.OPERATION_MODE),
|
|
"fan_quiet": (CAP_FAN["quiet"], Prop.FAN_SPEED),
|
|
"fan_low": (CAP_FAN["low"], Prop.FAN_SPEED),
|
|
"fan_medium": (CAP_FAN["medium"], Prop.FAN_SPEED),
|
|
"fan_high": (CAP_FAN["high"], Prop.FAN_SPEED),
|
|
"fan_auto": (CAP_FAN["auto"], Prop.FAN_SPEED),
|
|
"swing_vertical": (CAP_SWING_VERTICAL, Prop.AF_VERTICAL_SWING),
|
|
"swing_horizontal": (CAP_SWING_HORIZONTAL, Prop.AF_HORIZONTAL_SWING),
|
|
"economy_mode": (CAP_ECONOMY, Prop.ECONOMY_MODE),
|
|
"min_heat": (CAP_MIN_HEAT, Prop.MIN_HEAT),
|
|
"indoor_fan_control": (CAP_INDOOR_FAN_CONTROL, Prop.INDOOR_FAN_CONTROL),
|
|
"powerful_mode": (CAP_POWERFUL, Prop.POWERFUL_MODE),
|
|
"outdoor_low_noise": (CAP_OUTDOOR_LOW_NOISE, Prop.OUTDOOR_LOW_NOISE),
|
|
"coil_dry_mode": (CAP_COIL_DRY, Prop.COIL_DRY_MODE),
|
|
"display_temperature": (None, Prop.DISPLAY_TEMPERATURE),
|
|
"human_det_auto_save": (None, Prop.HUMAN_DET_AUTO_SAVE),
|
|
"wifi_led_enable": (None, Prop.WIFI_LED_ENABLE),
|
|
"af_vertical_direction": (None, Prop.AF_VERTICAL_DIRECTION),
|
|
"af_horizontal_direction": (None, Prop.AF_HORIZONTAL_DIRECTION),
|
|
}
|
|
|
|
# Описания для мастера и для атрибута description сущностей.
|
|
FEATURE_DESCRIPTIONS = {
|
|
"ru": {
|
|
"mode_cool": "Охлаждение",
|
|
"mode_dry": "Осушение",
|
|
"mode_fan": "Вентиляция (без нагрева/охлаждения)",
|
|
"mode_heat": "Обогрев",
|
|
"mode_auto": "Автоматический режим",
|
|
"fan_auto": "Автоскорость вентилятора",
|
|
"fan_quiet": "Тихая скорость",
|
|
"fan_low": "Низкая скорость",
|
|
"fan_medium": "Средняя скорость",
|
|
"fan_high": "Высокая скорость",
|
|
"swing_vertical": "Качание заслонки по вертикали",
|
|
"swing_horizontal": "Качание заслонки по горизонтали",
|
|
"economy_mode": "Экономичный режим",
|
|
"min_heat": "Минимальный обогрев (поддержание +10 °C)",
|
|
"indoor_fan_control": "Управление вентилятором при работе наружного блока",
|
|
"powerful_mode": "Мощный режим (ускоренный выход на режим)",
|
|
"outdoor_low_noise": "Тихий наружный блок",
|
|
"coil_dry_mode": "Сушка испарителя",
|
|
"display_temperature": "Датчик температуры помещения",
|
|
"human_det_auto_save": "Автосохранение при отсутствии людей (датчик присутствия)",
|
|
"wifi_led_enable": "Индикатор Wi-Fi на модуле",
|
|
"af_vertical_direction": "Выбор положения вертикальной заслонки",
|
|
"af_horizontal_direction": "Выбор положения горизонтальной заслонки",
|
|
},
|
|
"en": {
|
|
"mode_cool": "Cooling",
|
|
"mode_dry": "Dry",
|
|
"mode_fan": "Fan only",
|
|
"mode_heat": "Heating",
|
|
"mode_auto": "Automatic mode",
|
|
"fan_auto": "Auto fan speed",
|
|
"fan_quiet": "Quiet fan speed",
|
|
"fan_low": "Low fan speed",
|
|
"fan_medium": "Medium fan speed",
|
|
"fan_high": "High fan speed",
|
|
"swing_vertical": "Vertical louver swing",
|
|
"swing_horizontal": "Horizontal louver swing",
|
|
"economy_mode": "Economy mode",
|
|
"min_heat": "Minimum heat (keep +10 °C)",
|
|
"indoor_fan_control": "Indoor fan control while outdoor unit runs",
|
|
"powerful_mode": "Powerful mode",
|
|
"outdoor_low_noise": "Outdoor unit low noise",
|
|
"coil_dry_mode": "Coil dry",
|
|
"display_temperature": "Room temperature sensor",
|
|
"human_det_auto_save": "Human detection auto save",
|
|
"wifi_led_enable": "Wi-Fi LED indicator",
|
|
"af_vertical_direction": "Vertical louver position",
|
|
"af_horizontal_direction": "Horizontal louver position",
|
|
},
|
|
}
|
|
|
|
ENTITY_DESCRIPTIONS = {
|
|
"ru": {
|
|
"climate": "Управление кондиционером: режим, скорость, уставка, "
|
|
"заслонки, пресеты",
|
|
"room_temperature": "Температура воздуха в помещении",
|
|
"error_code": "Код ошибки кондиционера (0 — ошибок нет)",
|
|
"connection_state": "Состояние связи с модулем кондиционера",
|
|
"connectivity": "Есть ли связь с модулем",
|
|
"defrost": "Идёт разморозка наружного блока",
|
|
"maintenance": "Требуется обслуживание",
|
|
"oil_recovery": "Возврат масла в компрессор",
|
|
"pump_down": "Откачка хладагента (pump down)",
|
|
"check_operation": "Проверочный прогон",
|
|
"simultaneous": "Одновременные операции",
|
|
"different_modes": "Разморозка/масло/прочие режимы (шаблон B)",
|
|
},
|
|
"en": {
|
|
"climate": "Air conditioner control: mode, fan, setpoint, louvers, presets",
|
|
"room_temperature": "Room air temperature",
|
|
"error_code": "Air conditioner error code (0 — no errors)",
|
|
"connection_state": "Connection state with the AC module",
|
|
"connectivity": "Whether the module is reachable",
|
|
"defrost": "Outdoor unit defrost in progress",
|
|
"maintenance": "Maintenance required",
|
|
"oil_recovery": "Compressor oil recovery",
|
|
"pump_down": "Refrigerant pump down",
|
|
"check_operation": "Check operation",
|
|
"simultaneous": "Simultaneous operations",
|
|
"different_modes": "Defrost/oil/other modes (template B)",
|
|
},
|
|
}
|
|
|
|
|
|
def entity_description(language: str, key: str) -> str | None:
|
|
lang = "ru" if str(language).lower().startswith("ru") else "en"
|
|
return ENTITY_DESCRIPTIONS[lang].get(key) or FEATURE_DESCRIPTIONS[lang].get(
|
|
key
|
|
)
|
|
|
|
# op_status, биты (PROTOCOL §8.4): translation_key -> bit
|
|
OP_STATUS_BITS: dict[str, int] = {
|
|
"defrost": 24,
|
|
"maintenance": 22,
|
|
"oil_recovery": 28,
|
|
"pump_down": 29,
|
|
"check_operation": 30,
|
|
"simultaneous": 25,
|
|
"different_modes": 21,
|
|
}
|
|
|
|
# Уставка: фактические ограничения бытового прибора (PROTOCOL §8.3 [LEGACY]);
|
|
# единицы API = 0.1 °C.
|
|
TEMP_MIN_TENTHS = 160
|
|
TEMP_MAX_TENTHS = 300
|
|
TEMP_STEP_TENTHS = {
|
|
Template.A: 5,
|
|
Template.B: 10,
|
|
Template.F: 5,
|
|
}
|
|
|
|
# Состояния, в которых сущности доступны (PROTOCOL/план §5).
|
|
AVAILABLE_STATES = (State.ONLINE, State.RECOVERING)
|