// Тесты публичной сессии (aircon-слой) против mock-модуля: маппинг // имя↔enum, конверсии в событиях, оптимистичный кэш, batch, C-API. // Запускается python-оркестратором (tests/aircon/test_aircon_mock.py). #include #include #include #include #include #include "fgl-aircon/c_api.h" #include "fgl-aircon/session.hpp" #include "fgl-aircon/templates.hpp" using fgl::aircon::Prop; using fgl::aircon::Session; using fgl::aircon::State; static const char* state_name(State st) { switch (st) { case State::kIdle: return "idle"; case State::kRegistering: return "registering"; case State::kOnline: return "online"; case State::kRecovering: return "recovering"; case State::kOffline: return "offline"; case State::kKeyError: return "key_error"; } return "?"; } static void on_state(void*, State st, fgl::aircon::Error err) { printf("STATE %s %d\n", state_name(st), static_cast(err)); fflush(stdout); } static void on_property(void*, const fgl::aircon::PropertyEvent& ev) { const char* n = fgl::aircon::prop_enum_name(ev.prop); if (ev.value.kind == fgl::aircon::ValueKind::kString) { printf("PROP %s %d %d s:%s\n", n, ev.cmd_id, ev.status, ev.value.s); } else { printf("PROP %s %d %d %c:%lld\n", n, ev.cmd_id, ev.status, ev.value.kind == fgl::aircon::ValueKind::kBool ? 'b' : 'i', static_cast(ev.value.i)); } fflush(stdout); } static uint32_t env_u32(const char* name, uint32_t def) { const char* v = getenv(name); return v != nullptr ? static_cast(atoi(v)) : def; } int main(int argc, char** argv) { if (argc < 6) { fprintf(stderr, "usage: %s " " [prop-enum-name ...]\n", argv[0]); return 2; } fgl::aircon::Config cfg{}; cfg.host = argv[1]; cfg.device_port = static_cast(atoi(argv[2])); cfg.listen_port = static_cast(atoi(argv[3])); cfg.dsn = argv[4]; cfg.lanip_key = argv[5]; cfg.lanip_key_id = static_cast(atoi(argv[6])); cfg.tmpl = fgl::aircon::Template::kA; int duration_sec = atoi(argv[7]); fgl::aircon::Callbacks cbs{}; cbs.on_state = on_state; cbs.on_property = on_property; Session* s = Session::create(cfg, cbs); if (s == nullptr) { printf("CREATE_FAILED\n"); return 2; } // Тест C-API интроспекции. printf("CVER %s\n", fgl_version()); printf("CNAME %s\n", fgl_prop_name(FGL_TEMPLATE_A, FGL_PROP_FAN_SPEED)); printf("CCONV %lld\n", static_cast( fgl_convert_to_display(FGL_TEMPLATE_A, FGL_PROP_DISPLAY_TEMPERATURE, 7000))); if (!s->start()) { printf("START_FAILED\n"); return 2; } // Начальная синхронизация: пакет GET всех свойств шаблона A (батч). if (argc > 8) { s->batch_begin(); for (int i = 8; i < argc; i++) { // argv: имена enum'ов ("OperationMode") for (uint8_t p = 0; p < static_cast(Prop::kCount); p++) { if (strcmp(fgl::aircon::prop_enum_name(static_cast(p)), argv[i]) == 0) { if (!s->get_prop(static_cast(p))) { printf("GET_FAIL %s\n", argv[i]); } break; } } } if (!s->batch_commit()) printf("BATCH_PARTIAL\n"); } else { // По умолчанию — все int/bool свойства шаблола A одним батчем // (строковые пропускаются: модуль-мок их не генерирует; на приборе // строковый push проверялся вручную через aircon-события). s->batch_begin(); for (auto* info = fgl::aircon::prop_info_begin(cfg.tmpl); info != fgl::aircon::prop_info_begin(cfg.tmpl) + fgl::aircon::prop_info_count(cfg.tmpl); info++) { if (info->kind != fgl::aircon::ValueKind::kString) { if (!s->get_prop(info->prop)) { printf("GET_FAIL %s\n", fgl::aircon::prop_enum_name(info->prop)); } } } if (!s->batch_commit()) printf("BATCH_PARTIAL\n"); } const char* set_prop = getenv("RUNNER_SET_PROP"); // enum-имя const char* set_val = getenv("RUNNER_SET_VALUE"); uint32_t set_at = env_u32("RUNNER_SET_AT", 0); if (set_prop != nullptr && set_val != nullptr && set_at > 0) { std::this_thread::sleep_for(std::chrono::seconds(set_at)); for (uint8_t p = 0; p < static_cast(Prop::kCount); p++) { if (strcmp(fgl::aircon::prop_enum_name(static_cast(p)), set_prop) == 0) { bool ok = s->set_int(static_cast(p), atoll(set_val)); printf("SET_DONE %s=%s ok=%d\n", set_prop, set_val, ok ? 1 : 0); // Оптимистичный кэш сразу доступен. fgl::aircon::Value v{}; if (s->cached(static_cast(p), &v)) { printf("CACHED %s %lld\n", set_prop, static_cast(v.i)); } fflush(stdout); break; } } } auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(duration_sec); while (std::chrono::steady_clock::now() < deadline) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } // Проверки отказов (после duration, до stop): // - read-only свойство (ErrorCode) — отказ; // - свойство не из шаблона A (AfVerticalMoveStep1, только B) — отказ; // - клэмпинг: AdjustTemperature=1000 -> raw max 450. printf("RO_SET %d\n", s->set_int(Prop::ErrorCode, 5) ? 1 : 0); printf("NOTMPL_SET %d\n", s->set_int(Prop::AfVerticalMoveStep1, 1) ? 1 : 0); printf("CLAMP_SET %d\n", s->set_int(Prop::AdjustTemperature, 1000) ? 1 : 0); s->stop(); printf("DELETED\n"); printf("STATS rekeys=%u pushes_ok=%u pushes_bad=%u cmds=%u state=%s\n", s->rekey_count(), s->pushes_ok(), s->pushes_bad(), s->commands_served(), state_name(s->state())); fflush(stdout); delete s; return 0; }