diff --git a/.gitignore b/.gitignore index 7ddd7f5..37e353c 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,7 @@ build/ dist/ *.so out*/ + +# Dev +*.patch +robstridev2/ diff --git a/README.md b/README.md index 3c1f88c..aefc051 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,11 @@ You can specify logging levels for individual modules by adding `module_name=log RUST_LOG=debug,krec=warn cargo run --features stub ``` +To save trace logs to a file, pass the `--log` flag: +```bash +cargo run --features stub -- --log +``` + ## Contributing - Use `cargo fmt --all` to format the code. - Use `cargo clippy` to check for lint errors. diff --git a/daemon/Cargo.toml b/daemon/Cargo.toml index eaad24f..fd6d01c 100644 --- a/daemon/Cargo.toml +++ b/daemon/Cargo.toml @@ -20,6 +20,11 @@ tower = "0.5" gstreamer = "0.23" gstreamer-base = "0.23" glib = "0.17" +tracing-appender = "0.2" +clap = { version = "4.0", features = ["derive"] } +chrono = "0.4" +directories = "5.0" +flate2 = "1.0" kos-sim = { version = "0.1.0", path = "../platforms/sim", optional = true } kos-stub = { version = "0.1.0", path = "../platforms/stub", optional = true } diff --git a/daemon/src/file_logging.rs b/daemon/src/file_logging.rs new file mode 100644 index 0000000..20c018c --- /dev/null +++ b/daemon/src/file_logging.rs @@ -0,0 +1,176 @@ +use chrono::Local; +use directories::BaseDirs; +use eyre::Result; +use flate2::write::GzEncoder; +use flate2::Compression; +use std::fs::File; +use std::io::{self, BufWriter, Write}; +use std::path::PathBuf; +use tracing::{info, error}; +use tracing_subscriber::prelude::*; +use tracing_subscriber::{filter::EnvFilter, Layer}; + +pub struct CompressedWriter { + encoder: Option>>, + path: PathBuf, +} + +impl CompressedWriter { + pub fn new(path: impl AsRef) -> io::Result { + let file = File::create(path.as_ref())?; + let buffered = BufWriter::new(file); + Ok(Self { + encoder: Some(GzEncoder::new(buffered, Compression::new(6))), + path: path.as_ref().to_path_buf(), + }) + } + + fn sync(&mut self) -> io::Result<()> { + if let Some(encoder) = &mut self.encoder { + encoder.flush()?; + let buf_writer = encoder.get_mut(); + buf_writer.flush()?; + let file = buf_writer.get_mut(); + file.sync_all()?; + } + Ok(()) + } + + pub fn finalize(&mut self) -> io::Result<()> { + if let Some(encoder) = self.encoder.take() { + info!("Finalizing compressed log {}", self.path.display()); + // Finish the compression and get the BufWriter back + let mut buf_writer = encoder.finish()?; + // Flush the buffer + buf_writer.flush()?; + // Sync to disk + buf_writer.get_mut().sync_all()?; + info!("Successfully finalized log {}", self.path.display()); + } + Ok(()) + } +} + +impl Write for CompressedWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + if let Some(encoder) = &mut self.encoder { + match encoder.write(buf) { + Ok(size) => { + if size > 0 && buf.contains(&b'\n') { + self.sync()?; + } + Ok(size) + } + Err(e) => { + error!( + "Failed to write to compressed log {}: {}", + self.path.display(), + e + ); + Err(e) + } + } + } else { + error!( + "Attempted to write to finalized log {}", + self.path.display() + ); + Err(io::Error::new( + io::ErrorKind::Other, + "Writer has been finalized", + )) + } + } + + fn flush(&mut self) -> io::Result<()> { + self.sync() + } +} + +impl Drop for CompressedWriter { + fn drop(&mut self) { + if let Err(e) = self.finalize() { + error!( + "Failed to finalize compressed log {}: {}", + self.path.display(), + e + ); + } + } +} + +pub fn setup_logging( + enable_file_logging: bool, + log_level: &str, +) -> Result> { + let level_filter = match log_level.to_lowercase().as_str() { + "trace" => tracing_subscriber::filter::LevelFilter::TRACE, + "debug" => tracing_subscriber::filter::LevelFilter::DEBUG, + "info" => tracing_subscriber::filter::LevelFilter::INFO, + "warn" => tracing_subscriber::filter::LevelFilter::WARN, + "error" => tracing_subscriber::filter::LevelFilter::ERROR, + _ => { + eprintln!("Invalid log level '{}', defaulting to 'info'", log_level); + tracing_subscriber::filter::LevelFilter::INFO + } + }; + + let subscriber = tracing_subscriber::registry(); + + // Update stdout layer to use the specified level + let stdout_layer = tracing_subscriber::fmt::layer() + .with_writer(std::io::stdout) + .with_filter( + EnvFilter::from_default_env() + .add_directive(format!("kos={}", log_level).parse().unwrap()) + .add_directive("h2=error".parse().unwrap()) + .add_directive("grpc=error".parse().unwrap()) + .add_directive("rumqttc=error".parse().unwrap()) + .add_directive("kos_core::telemetry=error".parse().unwrap()) + .add_directive("polling=error".parse().unwrap()) + .add_directive("async_io=error".parse().unwrap()) + .add_directive("krec=error".parse().unwrap()), + ); + + let subscriber = subscriber.with(stdout_layer); + + if enable_file_logging { + let log_dir = if let Some(base_dirs) = BaseDirs::new() { + base_dirs.data_local_dir().join("kos").join("logs") + } else { + PathBuf::from("~/.local/share/kos/logs") + }; + + std::fs::create_dir_all(&log_dir)?; + + let timestamp = Local::now().format("%Y%m%d_%H%M%S"); + let final_name = format!("kos-daemon_{}.log.gz", timestamp); + let log_path = log_dir.join(&final_name); + + let compressed_writer = CompressedWriter::new(&log_path)?; + let (non_blocking, guard) = tracing_appender::non_blocking(compressed_writer); + + let file_layer = tracing_subscriber::fmt::layer() + .with_thread_ids(true) + .with_target(true) + .with_file(true) + .with_line_number(true) + .with_writer(non_blocking) + .with_filter(level_filter); + + subscriber.with(file_layer).init(); + Ok(Some(guard)) + } else { + subscriber.init(); + Ok(None) + } +} + +pub fn cleanup_logging(guard: Option) { + if let Some(guard) = guard { + // Ensure we flush any pending writes before dropping + drop(guard); + // Give a small amount of time for the worker to finish + std::thread::sleep(std::time::Duration::from_millis(100)); + } +} diff --git a/daemon/src/main.rs b/daemon/src/main.rs index 0513a30..814ab75 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -2,6 +2,7 @@ // This will run the gRPC server and, if applicable, a runtime loop // (e.g., actuator polling, loaded model inference). +use clap::Parser; use eyre::Result; use kos_core::google_proto::longrunning::operations_server::OperationsServer; use kos_core::services::OperationsServiceImpl; @@ -10,10 +11,13 @@ use kos_core::Platform; use kos_core::ServiceEnum; use std::collections::HashMap; use std::sync::Arc; +use tokio::signal; use tokio::sync::Mutex; use tonic::transport::Server; use tracing::{debug, error, info}; use tracing_subscriber::filter::EnvFilter; +use tracing_subscriber::prelude::*; +use tracing_subscriber::Layer; #[cfg(not(any(feature = "kos-sim", feature = "kos-zeroth-01", feature = "kos-kbot")))] use kos_stub::StubPlatform as PlatformImpl; @@ -27,6 +31,21 @@ use kos_zeroth_01::Zeroth01Platform as PlatformImpl; #[cfg(feature = "kos-kbot")] use kos_kbot::KbotPlatform as PlatformImpl; +mod file_logging; +use file_logging::{setup_logging, cleanup_logging}; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// Enable file logging + #[arg(long, default_value_t = false)] + log: bool, + + /// Log level (trace, debug, info, warn, error) + #[arg(long, default_value = "info")] + log_level: String, +} + fn add_service_to_router( router: tonic::transport::server::Router, service: ServiceEnum, @@ -63,26 +82,53 @@ async fn run_server( Ok(()) } +struct DaemonState { + _guard: Option, + platform: PlatformImpl, +} + #[tokio::main] async fn main() -> Result<()> { - // logging - tracing_subscriber::fmt() - .with_env_filter( + let args = Args::parse(); + + // tracing + let subscriber = tracing_subscriber::registry(); + + // Always add stdout layer + let stdout_layer = tracing_subscriber::fmt::layer() + .with_writer(std::io::stdout) + .with_filter( EnvFilter::from_default_env() .add_directive("h2=error".parse().unwrap()) .add_directive("grpc=error".parse().unwrap()) .add_directive("rumqttc=error".parse().unwrap()) .add_directive("kos_core::telemetry=error".parse().unwrap()) .add_directive("polling=error".parse().unwrap()) - .add_directive("async_io=error".parse().unwrap()), - ) - .init(); + .add_directive("async_io=error".parse().unwrap()) + .add_directive("krec=error".parse().unwrap()), + ); + + let _subscriber = subscriber.with(stdout_layer); + + let guard = setup_logging(args.log, &args.log_level)?; - let mut platform = PlatformImpl::new(); + let mut state = DaemonState { + _guard: guard, + platform: PlatformImpl::new(), + }; + + // Setup signal handler + let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel(); + + tokio::spawn(async move { + if let Ok(()) = signal::ctrl_c().await { + let _ = shutdown_tx.send(()); + } + }); // telemetry Telemetry::initialize( - format!("{}-{}", platform.name(), platform.serial()).as_str(), + format!("{}-{}", state.platform.name(), state.platform.serial()).as_str(), "localhost", 1883, ) @@ -91,11 +137,19 @@ async fn main() -> Result<()> { let operations_store = Arc::new(Mutex::new(HashMap::new())); let operations_service = Arc::new(OperationsServiceImpl::new(operations_store)); - platform.initialize(operations_service.clone())?; - - if let Err(e) = run_server(&platform, operations_service).await { - error!("Server error: {:?}", e); - std::process::exit(1); + state.platform.initialize(operations_service.clone())?; + + tokio::select! { + res = run_server(&state.platform, operations_service) => { + if let Err(e) = res { + error!("Server error: {:?}", e); + std::process::exit(1); + } + } + _ = shutdown_rx => { + info!("Received shutdown signal, cleaning up..."); + cleanup_logging(state._guard.take()); + } } Ok(()) diff --git a/kos_core/Cargo.toml b/kos_core/Cargo.toml index 5ff0b5e..665615a 100644 --- a/kos_core/Cargo.toml +++ b/kos_core/Cargo.toml @@ -26,7 +26,7 @@ eyre = "0.6" hyper = "0.14" tracing = "0.1" lazy_static = "1.4" -krec = "0.1" +krec = "0.2" [build-dependencies] tonic-build = "0.12" diff --git a/kos_core/src/lib.rs b/kos_core/src/lib.rs index a5ac1ee..3b36bb2 100644 --- a/kos_core/src/lib.rs +++ b/kos_core/src/lib.rs @@ -12,7 +12,6 @@ pub use grpc_interface::google as google_proto; pub use grpc_interface::kos as kos_proto; use async_trait::async_trait; -use eyre::Result; use hal::actuator_service_server::ActuatorServiceServer; use hal::imu_service_server::ImuServiceServer; use hal::process_manager_service_server::ProcessManagerServiceServer; diff --git a/kos_core/src/services/krec_logger.rs b/kos_core/src/services/krec_logger.rs index 0956cd0..017133c 100644 --- a/kos_core/src/services/krec_logger.rs +++ b/kos_core/src/services/krec_logger.rs @@ -1,7 +1,4 @@ -use crate::kos_proto::{ - actuator::ActuatorStateResponse, - imu::{ImuValuesResponse, QuaternionResponse}, -}; +use crate::kos_proto::imu::{ImuValuesResponse, QuaternionResponse}; use eyre::Result; use krec::{ ActuatorCommand, ActuatorState, ImuQuaternion, ImuValues, KRec, KRecFrame, KRecHeader, Vec3, @@ -29,6 +26,27 @@ struct ActuatorCommandItem { torque: Option, } +#[derive(Deserialize, Debug)] +struct ActuatorStateData { + actuator_id: u32, + online: bool, + position: Option, + velocity: Option, + torque: Option, + temperature: Option, + voltage: Option, + current: Option, +} + +#[derive(Deserialize, Debug)] +#[allow(unused)] +struct ActuatorStateList { + frame_number: u64, + video_timestamp: u64, + inference_step: u64, + data: Vec, +} + pub struct TelemetryLogger { krec: Arc>, _mqtt_client: AsyncClient, @@ -158,26 +176,36 @@ impl TelemetryLogger { tracing::error!("Failed to decode QuaternionResponse {:?}", payload); } } else if topic.contains("/actuator/state") { - if let Ok(state) = ActuatorStateResponse::decode(payload.as_ref()) { - frame.actuator_states.push(ActuatorState { - actuator_id: state.actuator_id, - online: state.online, - position: state.position, - velocity: state.velocity, - torque: state.torque, - temperature: state.temperature, - voltage: state.voltage, - current: state.current, - }); - } else { - tracing::error!("Failed to decode ActuatorStateResponse {:?}", payload); + match serde_json::from_slice::(payload) { + Ok(state_list) => { + for state in state_list.data { + frame.actuator_states.push(ActuatorState { + actuator_id: state.actuator_id, + online: state.online, + position: state.position, + velocity: state.velocity, + torque: state.torque, + temperature: state.temperature, + voltage: state.voltage, + current: state.current, + }); + } + } + Err(e) => { + tracing::error!("Failed to parse actuator state JSON: {:?}", e); + } } } else if topic.contains("/actuator/command") { - match serde_json::from_slice::(&payload) { + match serde_json::from_slice::(payload) { Ok(command_data) => { frame.inference_step = command_data.inference_step; frame.video_timestamp = command_data.video_timestamp; - frame.frame_number = command_data.frame_number; + frame.video_frame_number = command_data.frame_number; + frame.real_timestamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_nanos() + as u64; for item in command_data.data { frame.actuator_commands.push(ActuatorCommand { @@ -187,7 +215,6 @@ impl TelemetryLogger { torque: item.torque.unwrap_or_default() as f32, }); } - tracing::debug!("Parsed actuator command: {:?}", frame); } Err(e) => { tracing::error!("Failed to parse actuator command JSON: {:?}", e); diff --git a/platforms/kbot/Cargo.toml b/platforms/kbot/Cargo.toml index 71b61f4..12b6e3f 100644 --- a/platforms/kbot/Cargo.toml +++ b/platforms/kbot/Cargo.toml @@ -13,7 +13,7 @@ eyre = "0.6" krec = "0.2" tracing = "0.1" async-trait = "0.1" -robstridev2 = "0.2" +robstridev2 = "0.3" gstreamer = "0.20" gstreamer-app = "0.20" gstreamer-video = "0.20" @@ -21,4 +21,4 @@ uuid = "1" tokio = { version = "1", features = ["full"] } [target.'cfg(target_os = "linux")'.dependencies] imu = ">=0.1.6" -chrono = "0.4" \ No newline at end of file +chrono = "0.4" diff --git a/platforms/kbot/src/actuator.rs b/platforms/kbot/src/actuator.rs index 41a2d0d..c5857a3 100644 --- a/platforms/kbot/src/actuator.rs +++ b/platforms/kbot/src/actuator.rs @@ -24,9 +24,10 @@ impl KBotActuator { ports: Vec<&str>, actuator_timeout: Duration, polling_interval: Duration, - desired_actuator_types: &[(u8, robstridev2::ActuatorType)], + actuators_config: &[(u8, robstridev2::ActuatorConfiguration)], ) -> Result { let mut supervisor = Supervisor::new(actuator_timeout)?; + let mut found_motors = vec![false; actuators_config.len()]; for port in ports.clone() { if port.starts_with("/dev/tty") { @@ -52,9 +53,23 @@ impl KBotActuator { }); for port in ports.clone() { - supervisor - .scan_bus(0xFD, port, desired_actuator_types) - .await?; + let discovered_ids = supervisor.scan_bus(0xFD, port, actuators_config).await?; + + for (idx, (motor_id, _)) in actuators_config.iter().enumerate() { + if discovered_ids.contains(motor_id) { + found_motors[idx] = true; + } + } + } + + for (idx, (motor_id, _)) in actuators_config.iter().enumerate() { + if !found_motors[idx] { + tracing::warn!( + "Configured motor not found - ID: {}, Type: {:?}", + motor_id, + actuators_config[idx].1.actuator_type + ); + } } Ok(KBotActuator { @@ -106,7 +121,7 @@ impl Actuator for KBotActuator { let control_config = ControlConfig { kp: config.kp.unwrap_or(0.0) as f32, kd: config.kd.unwrap_or(0.0) as f32, - max_torque: Some(2.0), + max_torque: Some(config.max_torque.unwrap_or(2.0) as f32), max_velocity: Some(5.0), max_current: Some(10.0), }; diff --git a/platforms/kbot/src/lib.rs b/platforms/kbot/src/lib.rs index 92afb8d..cb988ea 100644 --- a/platforms/kbot/src/lib.rs +++ b/platforms/kbot/src/lib.rs @@ -5,7 +5,7 @@ mod process_manager; mod hexmove; pub use actuator::*; -pub use robstridev2::ActuatorType; +pub use robstridev2::{ActuatorConfiguration, ActuatorType}; #[cfg(target_os = "linux")] pub use hexmove::*; @@ -61,42 +61,206 @@ impl Platform for KbotPlatform { ) -> Pin>> + Send + 'a>> { Box::pin(async move { if cfg!(target_os = "linux") { + tracing::debug!("Initializing KBot services for Linux"); + let process_manager = KBotProcessManager::new(self.name().to_string(), self.serial()) .wrap_err("Failed to initialize GStreamer process manager")?; let actuator = KBotActuator::new( operations_service, - vec!["can0"], + vec![ + // "/dev/ttyCH341USB0", + // "/dev/ttyCH341USB1", + // "/dev/ttyCH341USB2", + // "/dev/ttyCH341USB3", + // "can0", + "can1", "can2", + ], Duration::from_secs(1), - Duration::from_nanos(3_333_333), + // Duration::from_nanos(3_333_333), + Duration::from_millis(7), &[ - // Right Leg - (10, ActuatorType::RobStride04), - (11, ActuatorType::RobStride03), - (12, ActuatorType::RobStride03), - (13, ActuatorType::RobStride04), - (14, ActuatorType::RobStride02), - // Left Leg - (20, ActuatorType::RobStride04), - (21, ActuatorType::RobStride03), - (22, ActuatorType::RobStride03), - (23, ActuatorType::RobStride04), - (24, ActuatorType::RobStride02), - // Right Arm - (30, ActuatorType::RobStride03), - (31, ActuatorType::RobStride03), - (32, ActuatorType::RobStride02), - (33, ActuatorType::RobStride02), - (34, ActuatorType::RobStride02), - (35, ActuatorType::RobStride00), // Left Arm - (40, ActuatorType::RobStride03), - (41, ActuatorType::RobStride03), - (42, ActuatorType::RobStride02), - (43, ActuatorType::RobStride02), - (44, ActuatorType::RobStride02), - (45, ActuatorType::RobStride00), + ( + 11, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride03, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + ( + 12, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride03, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + ( + 13, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride02, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + ( + 14, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride02, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + ( + 15, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride02, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + ( + 16, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride00, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + // Right Arm + ( + 21, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride03, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + ( + 22, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride03, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + ( + 23, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride02, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + ( + 24, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride02, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + ( + 25, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride02, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + ( + 26, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride00, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + // Left Leg + ( + 31, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride04, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + ( + 32, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride03, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + ( + 33, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride03, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + ( + 34, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride04, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + ( + 35, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride02, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + // Right Leg + ( + 41, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride04, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + ( + 42, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride03, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + ( + 43, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride03, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + ( + 44, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride04, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), + ( + 45, + ActuatorConfiguration { + actuator_type: ActuatorType::RobStride02, + max_angle_change: Some(4.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + ), ], ) .await @@ -116,7 +280,14 @@ impl Platform for KbotPlatform { vec!["can0"], Duration::from_secs(1), Duration::from_nanos(3_333_333), - &[(1, robstridev2::ActuatorType::RobStride04)], + &[( + 1, + robstridev2::ActuatorConfiguration { + actuator_type: ActuatorType::RobStride04, + max_angle_change: Some(2.0f32.to_radians()), + max_velocity: Some(10.0f32.to_radians()), + }, + )], ) .await .wrap_err("Failed to create actuator")?; diff --git a/platforms/stub/src/lib.rs b/platforms/stub/src/lib.rs index 3220fac..371f0b7 100644 --- a/platforms/stub/src/lib.rs +++ b/platforms/stub/src/lib.rs @@ -6,14 +6,7 @@ pub use imu::*; pub use process_manager::*; use async_trait::async_trait; -use eyre::Result; use kos_core::hal::Operation; -use kos_core::kos_proto::{ - actuator::actuator_service_server::ActuatorServiceServer, - imu::imu_service_server::ImuServiceServer, - process_manager::process_manager_service_server::ProcessManagerServiceServer, -}; -use kos_core::services::{ActuatorServiceImpl, IMUServiceImpl, ProcessManagerServiceImpl}; use kos_core::{services::OperationsServiceImpl, Platform, ServiceEnum}; use std::future::Future; use std::pin::Pin; diff --git a/platforms/stub/src/process_manager.rs b/platforms/stub/src/process_manager.rs index 5354966..13959cc 100644 --- a/platforms/stub/src/process_manager.rs +++ b/platforms/stub/src/process_manager.rs @@ -9,6 +9,12 @@ pub struct StubProcessManager { kclip_uuid: Mutex>, } +impl Default for StubProcessManager { + fn default() -> Self { + Self::new() + } +} + impl StubProcessManager { pub fn new() -> Self { StubProcessManager { diff --git a/pykos/pykos/services/process_manager.py b/pykos/pykos/services/process_manager.py index d83f9b4..c0a35c6 100644 --- a/pykos/pykos/services/process_manager.py +++ b/pykos/pykos/services/process_manager.py @@ -14,14 +14,18 @@ class ProcessManagerServiceClient: def __init__(self, channel: grpc.Channel) -> None: self.stub = process_manager_pb2_grpc.ProcessManagerServiceStub(channel) - def start_kclip(self, request: KClipStartRequest) -> Tuple[Optional[str], Optional[Error]]: + def start_kclip(self, action: str) -> Tuple[Optional[str], Optional[Error]]: """Start KClip recording. + Args: + action: The action string for the KClip request + Returns: Tuple containing: - clip_uuid (str): UUID of the started clip, if successful - error (Error): Error details if the operation failed """ + request = KClipStartRequest(action=action) response = self.stub.StartKClip(request) return response.clip_uuid, response.error if response.HasField("error") else None