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,166 @@
|
||||
// C-API (extern "C") fgl-aircon — для cffi/pyfglair (Home Assistant).
|
||||
// C++-пользователям — fgl-aircon/*.hpp.
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Версия библиотеки.
|
||||
const char* fgl_version(void);
|
||||
|
||||
// Логирование.
|
||||
typedef void (*fgl_log_sink_fn)(int level, const char* msg, unsigned len,
|
||||
void* ctx);
|
||||
void fgl_log_set_sink(fgl_log_sink_fn sink, void* ctx);
|
||||
void fgl_log_set_level(int min_level);
|
||||
|
||||
// Состояния/ошибки (значения совпадают с fgl::aircon::State/Error).
|
||||
typedef enum {
|
||||
FGL_STATE_IDLE = 0,
|
||||
FGL_STATE_REGISTERING = 1,
|
||||
FGL_STATE_ONLINE = 2,
|
||||
FGL_STATE_RECOVERING = 3,
|
||||
FGL_STATE_OFFLINE = 4,
|
||||
FGL_STATE_KEY_ERROR = 5,
|
||||
} fgl_state_t;
|
||||
|
||||
typedef enum {
|
||||
FGL_ERR_NONE = 0,
|
||||
FGL_ERR_NO_SLOT = 1,
|
||||
FGL_ERR_UNREACHABLE = 2,
|
||||
FGL_ERR_KEY_MISMATCH = 3,
|
||||
FGL_ERR_BAD_KEY_EXCHANGE = 4,
|
||||
FGL_ERR_ACTIVATION_TIMEOUT = 5,
|
||||
FGL_ERR_DECRYPT_FAILED = 6,
|
||||
} fgl_error_t;
|
||||
|
||||
typedef enum {
|
||||
FGL_PROP_OPERATION_MODE = 0,
|
||||
FGL_PROP_FAN_SPEED = 1,
|
||||
FGL_PROP_ADJUST_TEMPERATURE = 2,
|
||||
FGL_PROP_DISPLAY_TEMPERATURE = 3,
|
||||
FGL_PROP_AF_VERTICAL_DIRECTION = 4,
|
||||
FGL_PROP_AF_VERTICAL_SWING = 5,
|
||||
FGL_PROP_AF_HORIZONTAL_DIRECTION = 6,
|
||||
FGL_PROP_AF_HORIZONTAL_SWING = 7,
|
||||
FGL_PROP_AF_VERTICAL_MOVE_STEP1 = 8,
|
||||
FGL_PROP_AF_HORIZONTAL_MOVE_STEP1 = 9,
|
||||
FGL_PROP_OUTDOOR_LOW_NOISE = 10,
|
||||
FGL_PROP_INDOOR_FAN_CONTROL = 11,
|
||||
FGL_PROP_HUMAN_DET_AUTO_SAVE = 12,
|
||||
FGL_PROP_MIN_HEAT = 13,
|
||||
FGL_PROP_POWERFUL_MODE = 14,
|
||||
FGL_PROP_COIL_DRY_MODE = 15,
|
||||
FGL_PROP_ECONOMY_MODE = 16,
|
||||
FGL_PROP_MASTER_TIMER_ON_OFF_1 = 17,
|
||||
FGL_PROP_MASTER_TIMER_ON_OFF_2 = 18,
|
||||
FGL_PROP_ERROR_CODE = 19,
|
||||
FGL_PROP_DEMAND_CONTROL = 20,
|
||||
FGL_PROP_FILTER_SIGN_RESET_DISPLAY = 21,
|
||||
FGL_PROP_FILTER_SIGN_RESET = 22,
|
||||
FGL_PROP_OP_STATUS = 23,
|
||||
FGL_PROP_DEVICE_NAME = 24,
|
||||
FGL_PROP_BUILDING_NAME = 25,
|
||||
FGL_PROP_WIFI_LED_ENABLE = 26,
|
||||
FGL_PROP_SERVICE_CONTACT_NAME = 27,
|
||||
FGL_PROP_SERVICE_CONTACT_PHONE = 28,
|
||||
FGL_PROP_SERVICE_CONTACT_EMAIL = 29,
|
||||
FGL_PROP_AF_HORIZONTAL_NUM_DIR = 30,
|
||||
FGL_PROP_AF_VERTICAL_NUM_DIR = 31,
|
||||
FGL_PROP_DEVICE_CAPABILITIES = 32,
|
||||
FGL_PROP_GET_PROP = 33,
|
||||
FGL_PROP_HUMAN_DET = 34,
|
||||
FGL_PROP_MONITOR1 = 35,
|
||||
FGL_PROP_REFRESH = 36,
|
||||
FGL_PROP_COUNT = 37,
|
||||
} fgl_prop_t;
|
||||
|
||||
typedef enum {
|
||||
FGL_VALUE_INT = 0,
|
||||
FGL_VALUE_BOOL = 1,
|
||||
FGL_VALUE_STRING = 2,
|
||||
} fgl_value_kind_t;
|
||||
|
||||
typedef struct {
|
||||
fgl_value_kind_t kind;
|
||||
int64_t i;
|
||||
char s[64];
|
||||
} fgl_value_t;
|
||||
|
||||
typedef enum {
|
||||
FGL_TEMPLATE_A = 0,
|
||||
FGL_TEMPLATE_B = 1,
|
||||
FGL_TEMPLATE_F = 2,
|
||||
} fgl_template_t;
|
||||
|
||||
// Конфигурация. Строки копируются при создании.
|
||||
typedef struct {
|
||||
const char* host;
|
||||
uint16_t device_port;
|
||||
const char* dsn;
|
||||
const char* lanip_key;
|
||||
uint32_t lanip_key_id;
|
||||
fgl_template_t tmpl;
|
||||
uint16_t listen_port;
|
||||
uint32_t keepalive_ms;
|
||||
uint8_t max_queue;
|
||||
} fgl_config_t;
|
||||
|
||||
typedef struct {
|
||||
fgl_prop_t prop;
|
||||
int32_t cmd_id; // -1 — спонтанное обновление
|
||||
int32_t status;
|
||||
fgl_value_t value;
|
||||
} fgl_property_event_t;
|
||||
|
||||
typedef void (*fgl_on_state_fn)(void* ctx, fgl_state_t st, fgl_error_t err);
|
||||
typedef void (*fgl_on_property_fn)(void* ctx,
|
||||
const fgl_property_event_t* ev);
|
||||
|
||||
typedef struct {
|
||||
fgl_on_state_fn on_state;
|
||||
fgl_on_property_fn on_property;
|
||||
void* ctx;
|
||||
} fgl_callbacks_t;
|
||||
|
||||
// Непрозрачная сессия (определение — в реализации).
|
||||
typedef struct fgl_session fgl_session_t;
|
||||
|
||||
fgl_session_t* fgl_session_create(const fgl_config_t* cfg,
|
||||
const fgl_callbacks_t* cbs);
|
||||
void fgl_session_destroy(fgl_session_t* s);
|
||||
int fgl_session_start(fgl_session_t* s); // 1 ok / 0 ошибка
|
||||
void fgl_session_stop(fgl_session_t* s);
|
||||
fgl_state_t fgl_session_state(const fgl_session_t* s);
|
||||
fgl_error_t fgl_session_last_error(const fgl_session_t* s);
|
||||
|
||||
int fgl_session_set_int(fgl_session_t* s, fgl_prop_t prop, int64_t value);
|
||||
int fgl_session_set_bool(fgl_session_t* s, fgl_prop_t prop, int value);
|
||||
int fgl_session_set_string(fgl_session_t* s, fgl_prop_t prop,
|
||||
const char* value);
|
||||
int fgl_session_get_prop(fgl_session_t* s, fgl_prop_t prop);
|
||||
|
||||
int fgl_session_batch_begin(fgl_session_t* s);
|
||||
int fgl_session_batch_commit(fgl_session_t* s);
|
||||
int fgl_session_batch_abort(fgl_session_t* s);
|
||||
|
||||
int fgl_session_cached(const fgl_session_t* s, fgl_prop_t prop,
|
||||
fgl_value_t* out);
|
||||
|
||||
// Интроспекция шаблонов (превью HA, CLI).
|
||||
int fgl_template_detect(const char* oem_model); // -1 — неизвестно
|
||||
const char* fgl_prop_name(fgl_template_t t, fgl_prop_t prop); // NULL — нет
|
||||
const char* fgl_prop_base_type(fgl_template_t t, fgl_prop_t prop);
|
||||
int fgl_prop_read_only(fgl_template_t t, fgl_prop_t prop); // -1 — нет
|
||||
int64_t fgl_convert_to_display(fgl_template_t t, fgl_prop_t prop,
|
||||
int64_t raw);
|
||||
int64_t fgl_convert_from_input(fgl_template_t t, fgl_prop_t prop,
|
||||
int64_t api_value);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
Reference in New Issue
Block a user