Files
fgl-aircon/pyfglair/_cffi.py
T
petr.polezhaev b84bd8d555 ha(H1): исправления по ревью — жизненный цикл сессии, самодостаточный wheel
- 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 очередь
2026-09-29 11:48:32 +03:00

233 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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()