feat(power): Power1 стаб + dev-mock fake-ACC (feature dev-mocks)
PowerService (Power1, стаб running) + PowerMock (dev.PowerMock1, разделяет State через Arc<Mutex>). dev-mocks — default-фича; прод (--no-default-features) mock не регистрирует. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Alexander <akotenev2003@gmail.com>
This commit is contained in:
Generated
+14
@@ -800,6 +800,20 @@ dependencies = [
|
|||||||
"zbus",
|
"zbus",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shturman-power"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"shturman-common",
|
||||||
|
"shturman-ipc",
|
||||||
|
"shturman-sdk",
|
||||||
|
"tempfile",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"zbus",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shturman-sdk"
|
name = "shturman-sdk"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ members = [
|
|||||||
"crates/shturman-sdk",
|
"crates/shturman-sdk",
|
||||||
"crates/core/shturman-firstboot",
|
"crates/core/shturman-firstboot",
|
||||||
"crates/core/shturman-settings",
|
"crates/core/shturman-settings",
|
||||||
|
"crates/core/shturman-power",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
name = "shturman-power"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# dev-mocks — вкл. в dev (fake-ACC для тестов/v0.3); прод выключает `--no-default-features`.
|
||||||
|
default = ["dev-mocks"]
|
||||||
|
dev-mocks = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
shturman-ipc = { path = "../../shturman-ipc" }
|
||||||
|
shturman-common = { path = "../../shturman-common" }
|
||||||
|
zbus.workspace = true
|
||||||
|
tokio.workspace = true
|
||||||
|
anyhow.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile.workspace = true
|
||||||
|
shturman-sdk = { path = "../../shturman-sdk" }
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
//! `ru.shturman.Power1` — стаб питания/жизненного цикла (домен B).
|
||||||
|
//! v0: статичное состояние `running`, мутируется только dev-mock (fake-ACC). Полная FSM/секвенсинг — v0.3.
|
||||||
|
|
||||||
|
pub mod service;
|
||||||
|
|
||||||
|
pub use service::PowerService;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
//! `ru.shturman.Power1` — сервис. На шину выводит systemd (План 5). Полная FSM/секвенсинг — v0.3.
|
||||||
|
//! В dev-сборке дополнительно регистрирует `ru.shturman.dev.PowerMock1` (fake-ACC) на том же пути.
|
||||||
|
|
||||||
|
use shturman_common::init_tracing;
|
||||||
|
use shturman_ipc::{connect, names};
|
||||||
|
use shturman_power::PowerService;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
init_tracing("shturman-power");
|
||||||
|
let conn = connect().await?;
|
||||||
|
let svc = PowerService::new();
|
||||||
|
#[cfg(feature = "dev-mocks")]
|
||||||
|
let mock = svc.mock();
|
||||||
|
conn.object_server().at(names::power::PATH, svc).await?;
|
||||||
|
#[cfg(feature = "dev-mocks")]
|
||||||
|
conn.object_server().at(names::power::PATH, mock).await?;
|
||||||
|
conn.request_name(names::power::NAME).await?;
|
||||||
|
tracing::info!("ru.shturman.Power1 на шине");
|
||||||
|
std::future::pending::<()>().await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
//! Server-стаб `ru.shturman.Power1` + (feature `dev-mocks`) `ru.shturman.dev.PowerMock1` (fake-ACC).
|
||||||
|
//! zbus 4: несколько интерфейсов на одном объекте — это РАЗНЫЕ типы на одном пути, разделяющие
|
||||||
|
//! состояние через `Arc<Mutex<State>>` (а не два `#[interface]` на одном типе).
|
||||||
|
|
||||||
|
use shturman_common::monotonic_secs;
|
||||||
|
use shturman_ipc::types::{IgnitionState, PowerSource, PowerState};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use zbus::interface;
|
||||||
|
use zbus::object_server::SignalContext;
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
power: PowerState,
|
||||||
|
ignition: IgnitionState,
|
||||||
|
source: PowerSource,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for State {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
power: PowerState::Running,
|
||||||
|
ignition: IgnitionState::Running,
|
||||||
|
source: PowerSource::Vehicle12v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Стаб питания (`Power1`). В v0 стартует в `running`; запись/actuator отсутствуют (#2).
|
||||||
|
pub struct PowerService {
|
||||||
|
state: Arc<Mutex<State>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PowerService {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
state: Arc::new(Mutex::new(State::default())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PowerService {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inherent-аксессоры (тесты + источник для interface-методов).
|
||||||
|
pub fn power_state(&self) -> PowerState {
|
||||||
|
self.state.lock().unwrap().power
|
||||||
|
}
|
||||||
|
pub fn ignition(&self) -> IgnitionState {
|
||||||
|
self.state.lock().unwrap().ignition
|
||||||
|
}
|
||||||
|
pub fn source(&self) -> PowerSource {
|
||||||
|
self.state.lock().unwrap().source
|
||||||
|
}
|
||||||
|
|
||||||
|
/// dev-mock «fake-ACC», разделяющий состояние (только в dev-сборке).
|
||||||
|
#[cfg(feature = "dev-mocks")]
|
||||||
|
pub fn mock(&self) -> PowerMock {
|
||||||
|
PowerMock {
|
||||||
|
state: Arc::clone(&self.state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[interface(name = "ru.shturman.Power1")]
|
||||||
|
impl PowerService {
|
||||||
|
async fn get_power_state(&self) -> String {
|
||||||
|
self.power_state().as_str().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Внутренний; в v0-стабе — no-op (полная sleep/wake — v1/v2, B §7).
|
||||||
|
async fn request_sleep(&self) {}
|
||||||
|
|
||||||
|
#[zbus(property)]
|
||||||
|
async fn ignition_state(&self) -> String {
|
||||||
|
self.ignition().as_str().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property)]
|
||||||
|
async fn uptime(&self) -> u64 {
|
||||||
|
monotonic_secs()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property)]
|
||||||
|
async fn power_source(&self) -> String {
|
||||||
|
self.source().as_str().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(signal)]
|
||||||
|
async fn acc_changed(ctx: &SignalContext<'_>, on: bool) -> zbus::Result<()>;
|
||||||
|
#[zbus(signal)]
|
||||||
|
async fn shutdown_imminent(
|
||||||
|
ctx: &SignalContext<'_>,
|
||||||
|
seconds: u32,
|
||||||
|
reason: &str,
|
||||||
|
) -> zbus::Result<()>;
|
||||||
|
#[zbus(signal)]
|
||||||
|
async fn shutdown_aborted(ctx: &SignalContext<'_>) -> zbus::Result<()>;
|
||||||
|
#[zbus(signal)]
|
||||||
|
async fn sleep(ctx: &SignalContext<'_>) -> zbus::Result<()>;
|
||||||
|
#[zbus(signal)]
|
||||||
|
async fn wake(ctx: &SignalContext<'_>) -> zbus::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// dev-mock «fake-ACC» — отдельный тип на том же пути. Прод (`--no-default-features`) его НЕ регистрирует.
|
||||||
|
/// Методы возвращают `()` (ошибку эмита сигнала игнорируем — мок не отвечает D-Bus-ошибкой).
|
||||||
|
#[cfg(feature = "dev-mocks")]
|
||||||
|
pub struct PowerMock {
|
||||||
|
state: Arc<Mutex<State>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "dev-mocks")]
|
||||||
|
#[interface(name = "ru.shturman.dev.PowerMock1")]
|
||||||
|
impl PowerMock {
|
||||||
|
async fn set_acc(&self, on: bool, #[zbus(signal_context)] ctx: SignalContext<'_>) {
|
||||||
|
{
|
||||||
|
let mut st = self.state.lock().unwrap();
|
||||||
|
st.ignition = if on {
|
||||||
|
IgnitionState::Running
|
||||||
|
} else {
|
||||||
|
IgnitionState::Off
|
||||||
|
};
|
||||||
|
st.power = if on {
|
||||||
|
PowerState::Running
|
||||||
|
} else {
|
||||||
|
PowerState::Off
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Эмитим Power1-сигнал (тот же путь; имя интерфейса добавляет сама acc_changed).
|
||||||
|
let _ = PowerService::acc_changed(&ctx, on).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_ignition(&self, state: String) {
|
||||||
|
if let Ok(ig) = state.parse::<IgnitionState>() {
|
||||||
|
self.state.lock().unwrap().ignition = ig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn trigger_shutdown(
|
||||||
|
&self,
|
||||||
|
seconds: u32,
|
||||||
|
reason: String,
|
||||||
|
#[zbus(signal_context)] ctx: SignalContext<'_>,
|
||||||
|
) {
|
||||||
|
let _ = PowerService::shutdown_imminent(&ctx, seconds, &reason).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn abort_shutdown(&self, #[zbus(signal_context)] ctx: SignalContext<'_>) {
|
||||||
|
let _ = PowerService::shutdown_aborted(&ctx).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn defaults_running() {
|
||||||
|
let svc = PowerService::new();
|
||||||
|
assert_eq!(svc.power_state(), PowerState::Running);
|
||||||
|
assert_eq!(svc.ignition(), IgnitionState::Running);
|
||||||
|
assert_eq!(svc.source(), PowerSource::Vehicle12v);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user