ha(H1): pyfglair — cffi-ядро (очередь событий), wheel, CLI discover/monitor
- CMake: shared-таргет fgl-aircon-shared (FGL_BUILD_SHARED, PIC, SOVERSION) - C-API: fgl_prop_raw_range + кольцевые очереди событий/логов (fgl_session_poll/wait_events, fgl_log_poll/wait_events): прямые колбэки из потоков ядра со стеком 8 КБ небезопасны для интерпретатора Python - pyfglair: _corebuild (cmake->wheel), _cffi (ABI), Session (reader-поток -> loop.call_soon_threadsafe), templates (интроспекция C-таблиц), provision (aiohttp), CLI discover/monitor + esphome-secrets - setup.py/pyproject: платформенный wheel py3-none-linux_x86_64 с .so - tests/pyfglair: templates, provision (мок-облако), session + CLI против mock_ac.py; scripts/py-ci.sh; ci.sh запускает python-часть
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
"""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
|
||||
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;
|
||||
fgl_log_event_t log;
|
||||
} 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);
|
||||
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
|
||||
candidates = sorted(
|
||||
p for p in pkg.glob("libfgl-aircon.so*") if p.is_file()
|
||||
)
|
||||
if candidates:
|
||||
return str(max(candidates, key=lambda p: p.stat().st_mtime))
|
||||
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()
|
||||
Reference in New Issue
Block a user