core(M3): aircon-слой — шаблоны A/B/F, конверсии+override, публичный API + C-API
- include/fgl-aircon: types.hpp (Prop/State/Error/Value/Template/ Conversion-linear-custom/PropOverride/Config с задокументированными lifetime-контрактами), templates.hpp (PropInfo-таблицы, интроспекция, конверсии c контрактом Linear num/den!=0), session.hpp (Session: set_int/bool/string, get_prop, batch, cached, set_log_sink с контрактом), c_api.h (extern "C" для cffi/pyfglair). - src/aircon: tables.cpp (шаблоны A=33/B=20/F=34 по PROTOCOL §8.2, template_detect + template_is_known; конверсии: default тождественно, DisplayTemperature raw 0.01°C(+5000)→API 0.1°C; linear с инверсией raw=(api-offset)*den/num и насыщением __builtin_*_overflow; custom fn; af_*_swing base_type integer, kind bool), session.cpp (маппинг имя↔Prop, коэрсинг int-push к kBool по таблице, кэш со спинлоком, оптимистичный кэш, валидация overrides: терминатор/linear/custom_fn), c_api.cpp (шейм, static_assert'ы на все enum-значения, fgl_template_detect -1 для неизвестных). - ayla: set_property_string (Command.str_value, coalescing копирует строку в т.ч. в batch-ветке); kMaxQueue 48 + static_assert (полный батч A=33); commit_batch всегда ставит notify; oversized-строка дропает событие (кэш не затирается); max_queue default 40. - tests/aircon: test_tables (составы/атрибуты/дубли/template_detect/ swings-integer), test_convert (default/linear/custom/диапазоны/насыщение в обе стороны/UB-экстремумы), aircon_runner + test_aircon_mock (маппинг+конверсии+batch=1-notify с ожиданием async REG; SET+кэш+ RO/не-шаблон отказы+клэмпинг raw 450 на модуле; C-API smoke). - Прибор AP-WC1E: полный батч шаблона A одним notify — 28 int/bool свойств (DisplayTemperature 7000→200=20.0°C, DeviceCapabilities=5119), boolean SET (JSON true) принят, RO/клэмп/NOTMPL отказы корректны. - CI: 10/10 ×3 стабильно (3 полных прогона); ESP-IDF esp32 build complete. Ревью независимым агентом: 3 круга — B1 kMaxQueue<батча, B2 linear div/0, B3 c_api контракт -1, B4 batch-строки, BL1 INT64_MIN negation UB, BL2 DisplayTemperature экстремумы, flake aircon_mock — всё закрыто; APPROVED (условие круга 3: фикс + зелёный CI ×3).
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
// Публичные типы fgl-aircon (уровень aircon: свойства, конверсии, конфиг).
|
||||
// Протокольные детали скрыты в src/ayla; этот заголовок — стабильный API
|
||||
// для интеграций (ESPHome, HA/pyfglair, CLI).
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace fgl::aircon {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Состояние сессии (зеркалирует ayla-слой).
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class State : uint8_t {
|
||||
kIdle = 0, // создан, не запущен
|
||||
kRegistering, // local_reg отправлен, ждём key exchange
|
||||
kOnline, // сессия активна
|
||||
kRecovering, // самолечение (тишина → re-key)
|
||||
kOffline, // модуль недоступен (backoff) или нет слотов
|
||||
kKeyError, // lanip_key_id не совпал — смена конфига вручную
|
||||
};
|
||||
|
||||
enum class Error : int {
|
||||
kNone = 0,
|
||||
kNoSlot = 1,
|
||||
kUnreachable = 2,
|
||||
kKeyMismatch = 3,
|
||||
kBadKeyExchange = 4,
|
||||
kActivationTimeout = 5,
|
||||
kDecryptFailed = 6,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Свойства (шаблоны A/B/F; PROTOCOL.md §8.2)
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class Prop : uint8_t {
|
||||
OperationMode = 0,
|
||||
FanSpeed,
|
||||
AdjustTemperature,
|
||||
DisplayTemperature,
|
||||
AfVerticalDirection,
|
||||
AfVerticalSwing,
|
||||
AfHorizontalDirection,
|
||||
AfHorizontalSwing,
|
||||
AfVerticalMoveStep1,
|
||||
AfHorizontalMoveStep1,
|
||||
OutdoorLowNoise,
|
||||
IndoorFanControl,
|
||||
HumanDetAutoSave,
|
||||
MinHeat,
|
||||
PowerfulMode,
|
||||
CoilDryMode,
|
||||
EconomyMode,
|
||||
MasterTimerOnOff1,
|
||||
MasterTimerOnOff2,
|
||||
ErrorCode,
|
||||
DemandControl,
|
||||
FilterSignResetDisplay,
|
||||
FilterSignReset,
|
||||
OpStatus,
|
||||
DeviceName,
|
||||
BuildingName,
|
||||
WifiLedEnable,
|
||||
ServiceContactName,
|
||||
ServiceContactPhone,
|
||||
ServiceContactEmail,
|
||||
AfHorizontalNumDir,
|
||||
AfVerticalNumDir,
|
||||
DeviceCapabilities,
|
||||
GetProp,
|
||||
HumanDet,
|
||||
Monitor1,
|
||||
Refresh,
|
||||
kCount
|
||||
};
|
||||
|
||||
// Тип значения свойства.
|
||||
enum class ValueKind : uint8_t { kInt = 0, kBool, kString };
|
||||
|
||||
struct Value {
|
||||
ValueKind kind = ValueKind::kInt;
|
||||
int64_t i = 0; // kInt / kBool (0|1)
|
||||
char s[64] = {}; // kString
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Шаблоны устройств (oem_model → шаблон, PROTOCOL.md §8.1)
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class Template : uint8_t { kA = 0, kB, kF };
|
||||
|
||||
// Определение шаблона по oem_model ("AP-WC1E" → kA). Неизвестные модели
|
||||
// трактуются как kA (как и legacy) — различить можно template_is_known.
|
||||
Template template_detect(const char* oem_model);
|
||||
// Известна ли модель (false → template_detect вернёт kA как fallback).
|
||||
bool template_is_known(const char* oem_model);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Конверсии (PROTOCOL.md §8.3; требования плана §4)
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class ConvKind : uint8_t {
|
||||
kTemplateDefault = 0, // из таблицы шаблона
|
||||
kLinear, // disp = raw*num/den + offset (int64, округление к нулю)
|
||||
kCustomFn, // disp = fn(raw, ctx)
|
||||
};
|
||||
|
||||
struct Linear {
|
||||
int64_t num = 1;
|
||||
int64_t den = 1;
|
||||
int64_t offset = 0;
|
||||
// to_display: api = raw*num/den + offset
|
||||
// from_input: raw = (api - offset)*den/num (инверсия)
|
||||
};
|
||||
|
||||
using ConvFn = int64_t (*)(int64_t raw, void* ctx);
|
||||
|
||||
struct Conversion {
|
||||
ConvKind kind = ConvKind::kTemplateDefault;
|
||||
Linear linear{}; // при kLinear
|
||||
ConvFn fn = nullptr; // при kCustomFn
|
||||
void* ctx = nullptr; // контекст kCustomFn
|
||||
};
|
||||
|
||||
// Точечная настройка одного свойства (nullptr-terminnated массив в конфиге).
|
||||
struct PropOverride {
|
||||
Prop prop;
|
||||
bool has_range = false;
|
||||
int64_t min = 0; // в API-единицах (после конверсии)
|
||||
int64_t max = 0;
|
||||
Conversion to_display; // raw -> API
|
||||
Conversion from_input; // API -> raw
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Конфигурация и колбэки сессии
|
||||
// ---------------------------------------------------------------------------
|
||||
struct Config {
|
||||
// host/dsn/lanip_key копируются при create; overrides — нет (см. ниже).
|
||||
const char* host = nullptr; // DNS-имя или IP модуля
|
||||
uint16_t device_port = 80;
|
||||
const char* dsn = nullptr; // "AC000W00XXXXXXX"
|
||||
const char* lanip_key = nullptr; // base64-строка как есть
|
||||
uint32_t lanip_key_id = 0;
|
||||
Template tmpl = Template::kA;
|
||||
uint16_t listen_port = 10275;
|
||||
uint32_t keepalive_ms = 15000;
|
||||
uint8_t max_queue = 40; // >= полного батча шаблона (33)
|
||||
// Переопределения конверсий. МАССИВ ДОЛЖЕН ПЕРЕЖИТЬ СЕССИЮ (не копируется;
|
||||
// читается при каждом set/push). Терминатор: элемент с prop == kCount.
|
||||
const PropOverride* overrides = nullptr;
|
||||
};
|
||||
|
||||
struct PropertyEvent {
|
||||
Prop prop;
|
||||
Value value; // уже сконвертированное (to_display)
|
||||
int cmd_id = -1; // ответ на GET; -1 — спонтанное обновление
|
||||
int status = 0;
|
||||
};
|
||||
|
||||
struct Callbacks {
|
||||
// КОНТРАКТ: вызываются из потоков ядра; быстрые и реентерабельные;
|
||||
// вызывать stop() из колбэка запрещено.
|
||||
void (*on_state)(void* ctx, State st, Error err);
|
||||
void (*on_property)(void* ctx, const PropertyEvent& ev);
|
||||
void* ctx = nullptr;
|
||||
};
|
||||
|
||||
} // namespace fgl::aircon
|
||||
Reference in New Issue
Block a user