// Реализация extern "C" шейма (cffi/pyfglair). Значения enum'ов C-API // обязаны совпадать с C++ enum'ами — проверено static_assert'ами. #include "fgl-aircon/c_api.h" #include #include #include #include #include #include #include #include "fgl-aircon/session.hpp" #include "fgl-aircon/templates.hpp" #include "fgl-aircon/version.hpp" using fgl::aircon::Callbacks; using fgl::aircon::Config; using fgl::aircon::Error; using fgl::aircon::Prop; using fgl::aircon::PropInfo; using fgl::aircon::State; using fgl::aircon::Template; using fgl::aircon::Value; static_assert(FGL_STATE_IDLE == static_cast(State::kIdle)); static_assert(FGL_STATE_REGISTERING == static_cast(State::kRegistering)); static_assert(FGL_STATE_ONLINE == static_cast(State::kOnline)); static_assert(FGL_STATE_RECOVERING == static_cast(State::kRecovering)); static_assert(FGL_STATE_OFFLINE == static_cast(State::kOffline)); static_assert(FGL_STATE_KEY_ERROR == static_cast(State::kKeyError)); static_assert(FGL_ERR_NONE == static_cast(Error::kNone)); static_assert(FGL_ERR_NO_SLOT == static_cast(Error::kNoSlot)); static_assert(FGL_ERR_UNREACHABLE == static_cast(Error::kUnreachable)); static_assert(FGL_ERR_KEY_MISMATCH == static_cast(Error::kKeyMismatch)); static_assert(FGL_ERR_BAD_KEY_EXCHANGE == static_cast(Error::kBadKeyExchange)); static_assert(FGL_ERR_ACTIVATION_TIMEOUT == static_cast(Error::kActivationTimeout)); static_assert(FGL_ERR_DECRYPT_FAILED == static_cast(Error::kDecryptFailed)); static_assert(FGL_PROP_OPERATION_MODE == static_cast(Prop::OperationMode)); static_assert(FGL_PROP_FAN_SPEED == static_cast(Prop::FanSpeed)); static_assert(FGL_PROP_ADJUST_TEMPERATURE == static_cast(Prop::AdjustTemperature)); static_assert(FGL_PROP_DISPLAY_TEMPERATURE == static_cast(Prop::DisplayTemperature)); static_assert(FGL_PROP_ECONOMY_MODE == static_cast(Prop::EconomyMode)); static_assert(FGL_PROP_ERROR_CODE == static_cast(Prop::ErrorCode)); static_assert(FGL_PROP_DEVICE_CAPABILITIES == static_cast(Prop::DeviceCapabilities)); static_assert(FGL_PROP_REFRESH == static_cast(Prop::Refresh)); static_assert(FGL_PROP_COUNT == static_cast(Prop::kCount)); static_assert(FGL_TEMPLATE_A == static_cast(Template::kA)); static_assert(FGL_TEMPLATE_B == static_cast(Template::kB)); static_assert(FGL_TEMPLATE_F == static_cast(Template::kF)); static_assert(FGL_VALUE_INT == static_cast(fgl::aircon::ValueKind::kInt)); static_assert(FGL_VALUE_BOOL == static_cast(fgl::aircon::ValueKind::kBool)); static_assert(FGL_VALUE_STRING == static_cast(fgl::aircon::ValueKind::kString)); const char* fgl_version(void) { return FGL_AIRCON_VERSION_STRING; } // --------------------------------------------------------------------------- // Кольцевые буферы событий/логов (fixed-size, без аллокаций в рантайме) // --------------------------------------------------------------------------- namespace { template struct Ring { std::mutex mtx; std::condition_variable cv; T buf[N]; size_t head = 0; size_t count = 0; uint64_t dropped = 0; void push(const T& value) { std::lock_guard lock(mtx); if (count == N) { buf[head] = value; head = (head + 1) % N; dropped++; } else { buf[(head + count) % N] = value; count++; } cv.notify_all(); } int drain(T* out, int max_events) { std::lock_guard lock(mtx); int n = 0; while (n < max_events && count > 0) { out[n++] = buf[head]; head = (head + 1) % N; count--; } return n; } int wait(T* out, int max_events, int timeout_ms) { std::unique_lock lock(mtx); if (count == 0) { if (timeout_ms < 0) { cv.wait(lock, [this] { return count > 0; }); } else { cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), [this] { return count > 0; }); } } int n = 0; while (n < max_events && count > 0) { out[n++] = buf[head]; head = (head + 1) % N; count--; } return n; } uint64_t dropped_count() { std::lock_guard lock(mtx); return dropped; } }; constexpr size_t kSessionEventCap = 64; constexpr size_t kLogEventCap = 64; Ring g_log_ring; struct LogWrap { std::atomic fn{nullptr}; std::atomic ctx{nullptr}; }; LogWrap g_log_wrap; std::once_flag g_log_installed; void log_wrap_trampoline(int level, const char* msg, size_t len, void*) { fgl_log_event_t ev{}; ev.level = level; size_t n = len < sizeof(ev.msg) - 1 ? len : sizeof(ev.msg) - 1; memcpy(ev.msg, msg, n); ev.msg[n] = '\0'; g_log_ring.push(ev); auto fn = g_log_wrap.fn.load(std::memory_order_acquire); if (fn != nullptr) { fn(level, msg, len, g_log_wrap.ctx.load(std::memory_order_relaxed)); } } void ensure_log_pipe() { std::call_once(g_log_installed, [] { fgl::aircon::set_log_sink(log_wrap_trampoline, nullptr); }); } } // namespace void fgl_log_set_sink(fgl_log_sink_fn sink, void* ctx) { g_log_wrap.ctx.store(ctx, std::memory_order_relaxed); g_log_wrap.fn.store(sink, std::memory_order_release); // Ядро всегда пишет в кольцевой буфер (его читает pyfglair); sink — // дополнительный прямой колбэк для C/C++ (только малые стеки!). ensure_log_pipe(); } void fgl_log_set_level(int min_level) { fgl::aircon::set_log_level(min_level); } int fgl_log_poll_events(fgl_log_event_t* out, int max_events) { if (out == nullptr || max_events <= 0) return 0; return g_log_ring.drain(out, max_events); } int fgl_log_wait_events(fgl_log_event_t* out, int max_events, int timeout_ms) { if (out == nullptr || max_events <= 0) return 0; return g_log_ring.wait(out, max_events, timeout_ms); } // --------------------------------------------------------------------------- // Сессия // --------------------------------------------------------------------------- struct fgl_session { fgl::aircon::Session* s; Ring* events; fgl_on_state_fn on_state; fgl_on_property_fn on_property; void* user; }; namespace { void on_state_trampoline(void* ctx, State st, Error err) { auto* s = static_cast(ctx); if (s->events != nullptr) { fgl_event_t ev{}; ev.type = FGL_EVENT_STATE; ev.data.state.state = static_cast(st); ev.data.state.error = static_cast(err); s->events->push(ev); } if (s->on_state != nullptr) { s->on_state(s->user, static_cast(st), static_cast(err)); } } void on_property_trampoline(void* ctx, const fgl::aircon::PropertyEvent& ev) { auto* s = static_cast(ctx); if (s->events != nullptr) { fgl_event_t out{}; out.type = FGL_EVENT_PROPERTY; out.data.property.prop = static_cast(ev.prop); out.data.property.cmd_id = ev.cmd_id; out.data.property.status = ev.status; out.data.property.value.kind = static_cast(ev.value.kind); out.data.property.value.i = ev.value.i; memcpy(out.data.property.value.s, ev.value.s, sizeof(out.data.property.value.s)); s->events->push(out); } if (s->on_property != nullptr) { fgl_property_event_t out{}; out.prop = static_cast(ev.prop); out.cmd_id = ev.cmd_id; out.status = ev.status; out.value.kind = static_cast(ev.value.kind); out.value.i = ev.value.i; memcpy(out.value.s, ev.value.s, sizeof(out.value.s)); s->on_property(s->user, &out); } } } // namespace fgl_session_t* fgl_session_create(const fgl_config_t* cfg, const fgl_callbacks_t* cbs) { if (cfg == nullptr) return nullptr; Config cc{}; cc.host = cfg->host; cc.device_port = cfg->device_port; cc.dsn = cfg->dsn; cc.lanip_key = cfg->lanip_key; cc.lanip_key_id = cfg->lanip_key_id; cc.tmpl = static_cast