- C1: единственный владелец stop+destroy — reader-поток; close() только выставляет флаг и ждёт; повторный close и close из колбэка безопасны (регресс-тесты; TSan на close с мёртвым модулем — 0 гонок) - M1: wheel собирается с bundled mbedtls (нет внешней soname mbedcrypto); попутно исправлена bundled-ветка CMake (-lSTATIC) - C-API: счётчики потерянных событий/логов, лог-кольцо без union-балласта, атомарные fn/ctx лог-синка - provision: валидация формата облачных ответов; CLI: человеческие ошибки (exit 2), --out с правами 0600, console_script pyfglair - упаковка: MANIFEST.in (sdist с C++-исходниками), очистка stale .so, readme в метаданных; find_library без ложных .bak - тесты: close-сценарии, доставка логов, ошибки CLI/provision, C-API очередь
233 lines
6.7 KiB
Python
233 lines
6.7 KiB
Python
"""cffi-декларации поверх include/fgl-aircon/c_api.h (ABI-режим, dlopen).
|
||
|
||
Библиотека ищется в порядке: $FGL_AIRCON_LIB, рядом с пакетом
|
||
(pyfglair/libfgl-aircon.so*), затем по системному имени (ldconfig).
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import ctypes.util
|
||
import os
|
||
import re
|
||
from pathlib import Path
|
||
|
||
from cffi import FFI
|
||
|
||
ffi = FFI()
|
||
|
||
ffi.cdef(
|
||
"""
|
||
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);
|
||
|
||
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;
|
||
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;
|
||
|
||
typedef enum {
|
||
FGL_EVENT_STATE = 0,
|
||
FGL_EVENT_PROPERTY = 1,
|
||
} fgl_event_type_t;
|
||
|
||
typedef struct {
|
||
int level;
|
||
char msg[160];
|
||
} fgl_log_event_t;
|
||
|
||
typedef struct {
|
||
fgl_event_type_t type;
|
||
union {
|
||
struct {
|
||
fgl_state_t state;
|
||
fgl_error_t error;
|
||
} state;
|
||
fgl_property_event_t property;
|
||
} data;
|
||
} fgl_event_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);
|
||
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);
|
||
|
||
int fgl_session_poll_events(fgl_session_t* s, fgl_event_t* out, int max_events);
|
||
int fgl_session_wait_events(fgl_session_t* s, fgl_event_t* out, int max_events,
|
||
int timeout_ms);
|
||
uint64_t fgl_session_events_dropped(const fgl_session_t* s);
|
||
uint64_t fgl_log_events_dropped(void);
|
||
int fgl_log_poll_events(fgl_log_event_t* out, int max_events);
|
||
int fgl_log_wait_events(fgl_log_event_t* out, int max_events, int timeout_ms);
|
||
|
||
int fgl_template_detect(const char* oem_model);
|
||
const char* fgl_prop_name(fgl_template_t t, fgl_prop_t prop);
|
||
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);
|
||
int fgl_prop_raw_range(fgl_template_t t, fgl_prop_t prop, int64_t* min,
|
||
int64_t* max);
|
||
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);
|
||
"""
|
||
)
|
||
|
||
_cdll = None
|
||
|
||
|
||
def find_library() -> str:
|
||
env = os.environ.get("FGL_AIRCON_LIB")
|
||
if env:
|
||
if not Path(env).exists():
|
||
raise FileNotFoundError(f"FGL_AIRCON_LIB={env} не существует")
|
||
return env
|
||
pkg = Path(__file__).resolve().parent
|
||
pattern = re.compile(r"^libfgl-aircon\.(so(\.[0-9.]+)?|dylib|\d+\.dylib)$")
|
||
candidates = sorted(
|
||
(p for p in pkg.iterdir() if p.is_file() and pattern.match(p.name)),
|
||
key=lambda p: (p.name.count("."), len(p.name)),
|
||
)
|
||
if candidates:
|
||
return str(candidates[0])
|
||
system = ctypes.util.find_library("fgl-aircon")
|
||
if system:
|
||
return system
|
||
raise OSError(
|
||
"библиотека fgl-aircon не найдена. Соберите её из корня монорепо: "
|
||
"python -m pyfglair._corebuild --inplace (или задайте FGL_AIRCON_LIB)"
|
||
)
|
||
|
||
|
||
def get_library():
|
||
"""dlopen ядра (однократно на процесс)."""
|
||
global _cdll
|
||
if _cdll is None:
|
||
_cdll = ffi.dlopen(find_library())
|
||
return _cdll
|
||
|
||
|
||
lib = get_library()
|