Files
shturman/docs/specs/plans/02-ipc-and-sdk.md
T
2026-06-24 12:05:30 +03:00

8.2 KiB

План 2 — shturman-ipc (контракт шины) + shturman-sdk (клиент)

REQUIRED SUB-SKILL: superpowers:executing-plans + TDD. Полный код пишется в src по ходу (inline-исполнение); здесь — дизайн, задачи, ключевые сигнатуры, тест-кейсы, acceptance.

Goal: зафиксировать контракт D-Bus в коде (shturman-ipc: ошибки, имена/пути/версии, enum-типы, #[proxy] Power1/Settings1) и публичный клиентский слой (shturman-sdk: connect, SettingsClient/PowerClient, схема манифеста). Граница: ядро-сервисы будут зависеть от ipc; апы/плагины — от sdk (architecture §3, #1).

Architecture: ipccommon; sdkipc + common. Enum-типы сериализуются на провод как строки (spec §5.2): D-Bus-сигнатуры используют String/Variant, Rust-сторона конвертит через as_str/FromStr. Bus-зависимые обёртки (clients) компилируются здесь, проверяются на живой шине в Плане 3.

Tech Stack: zbus 4 (#[proxy], zbus::DBusError, zvariant), serde/serde_yaml (манифест), thiserror.

Контекст: спека §4–§5, ipc.md, data-model §2, plugin-sdk §2. Ветка feat/v0-foundation, коммиты -s + Co-Authored-By.


Task 1: shturman-ipc — крейт + Error + names

Files: crates/shturman-ipc/Cargo.toml, src/lib.rs, src/error.rs, src/names.rs; Cargo.toml (members).

  • Добавить crates/shturman-ipc в members. Deps: zbus, serde, thiserror, shturman-common.
  • error.rsru.shturman.Error.* (ipc §2):
#[derive(Debug, thiserror::Error, zbus::DBusError)]
#[zbus(prefix = "ru.shturman.Error")]
pub enum Error {
    #[zbus(error)]
    ZBus(zbus::Error),                 // catch-all для транспортных ошибок
    #[error("permission denied: {0}")]
    PermissionDenied(String),
    #[error("not available: {0}")]
    NotAvailable(String),
    #[error("stale: {0}")]
    Stale(String),
    #[error("timeout: {0}")]
    Timeout(String),
    #[error("read only: {0}")]
    ReadOnly(String),
    #[error("invalid argument: {0}")]
    InvalidArgument(String),
    #[error("unsupported: {0}")]
    Unsupported(String),
}
  • names.rs — well-known имена/пути (ipc §2):
pub mod power {
    pub const NAME: &str = "ru.shturman.Power";
    pub const PATH: &str = "/ru/shturman/Power";
    pub const IFACE: &str = "ru.shturman.Power1";
}
pub mod settings {
    pub const NAME: &str = "ru.shturman.Settings";
    pub const PATH: &str = "/ru/shturman/Settings";
    pub const IFACE: &str = "ru.shturman.Settings1";
}
  • TDD-тест (error): Error::InvalidArgument("x").to_string() содержит "invalid argument"; компиляция zbus::DBusError подтверждает маппинг имён. Тест (names): консты соответствуют конвенции ru.shturman.* / /ru/shturman/* (sanity).
  • Build + cargo test -p shturman-ipc зелёный → commit feat(ipc): Error + well-known имена.

Task 2: ipc::types — enum-типы (TDD round-trip строк)

Files: crates/shturman-ipc/src/types.rs.

  • Enums (data-model/spec §5.2): PowerState {off, accessory, running, shutting_down, sleep, battery_cutoff}, IgnitionState {off, accessory, running}, PowerSource {vehicle_12v, holdup_cap, sleep_rail, low_battery}, ShutdownReason {acc_off, under_voltage, thermal, battery_cutoff}. Каждому — as_str(&self) -> &'static str и FromStr (snake_case на проводе).
  • TDD-тесты: для каждого enum — parse(as_str(v)) == v по всем вариантам; неизвестная строка → Err.
  • Падающий тест → реализация → зелёно → commit feat(ipc): enum-типы Power/Ignition/Source/Reason (string round-trip).

Task 3: ipc::proxyPower1/Settings1 (#[proxy])

Files: crates/shturman-ipc/src/proxy.rs, обновить lib.rs (re-export).

  • #[proxy(interface="ru.shturman.Power1", default_service="ru.shturman.Power", default_path="/ru/shturman/Power")] трейт Power1: метод get_power_state() -> zbus::Result<String>, request_sleep(); #[zbus(property)] ignition_state()->String, uptime()->u64, power_source()->String; #[zbus(signal)] acc_changed(on: bool), shutdown_imminent(seconds: u32, reason: String), shutdown_aborted(), sleep(), wake().
  • Settings1 трейт: get(key)->OwnedValue, set(key, value: &Value), list(prefix)->Vec<String>, reset(key); #[zbus(signal)] changed(key: String, value: OwnedValue).
  • Проверка — компиляция (zbus-макро генерит клиент). Build зелёный → commit feat(ipc): zbus-proxy Power1/Settings1.

Task 4: shturman-sdk — крейт + manifest (TDD)

Files: crates/shturman-sdk/Cargo.toml, src/lib.rs, src/manifest.rs; Cargo.toml (members).

  • Добавить crates/shturman-sdk в members. Deps: zbus, serde, serde_yaml, tokio, shturman-ipc, shturman-common.
  • manifest.rs — serde-схема (plugin-sdk §2): Manifest{ plugin: Plugin, capabilities: Capabilities, extension_points: Option<ExtensionPoints> }; Plugin{id,name,version,description,author,shturman_api}; Capabilities{ vehicle_read: Vec<String>, assistant_intents: …, ui_tiles: u32, ui_screens: u32, storage: bool, network: Option<…> } (поля #[serde(default)] где опц.).
  • TDD-тесты: распарсить валидный YAML (пример fuel-tracker, plugin-sdk §8) → поля верны; битый YAML → Err; отсутствие обязательного plugin.idErr.
  • Падающий тест → реализация → зелёно → commit feat(sdk): схема манифеста (plugin-sdk §2).

Task 5: sdkconnect + PowerClient + SettingsClient (build; bus — План 3)

Files: crates/shturman-sdk/src/{connect.rs,power.rs,settings.rs}, lib.rs (re-export).

  • connect()zbus::Connection к системной шине (Connection::system()); env SHTURMAN_BUS=session для тестов/dev → Connection::session(). (Вызов на живой шине — План 3.)
  • PowerClient{ proxy: Power1Proxy }: power_state()->Result<PowerState> (парс строки в enum через ipc::types), ignition_state()->Result<IgnitionState>, uptime()->Result<u64>; подписки на сигналы (stream).
  • SettingsClient{ proxy: Settings1Proxy }: get_string(key), set(key, value), list(prefix), reset(key), поток changed.
  • Проверка — компиляция + cargo test --workspace (unit, без шины) + just ci. Re-exports в lib.rs.
  • commit feat(sdk): connect + Power/Settings клиенты (bus-тест — План 3).

Acceptance (Плана 2)

  • cargo build --workspace зелёный (ipc + sdk компилируются, proxy-макро ок).
  • cargo test --workspace — round-trip enum (ipc) + парс манифеста (sdk) зелёные; common-тесты не сломаны.
  • just ci зелёный (fmt + clippy -D + test + deny). Новые транзитивные лицензии (zbus и пр.) — в allow или добавить.
  • Границы: ipc не зависит от sdk; сервисы (План 3) будут импортировать ipc; sdk — для апов.

Дальше

План 3 — стаб-сервисы firstboot/settings/power + интеграция на живой шине (здесь всплывёт вопрос D-Bus на macOS-хосте: brew install dbus + session-bus, либо перенести Lima-настройку раньше).