Skip to content

Commit

Permalink
feat: support optional tokio mutex
Browse files Browse the repository at this point in the history
  • Loading branch information
Bloeckchengrafik committed Oct 8, 2024
1 parent ad43ad1 commit 75c6f3f
Show file tree
Hide file tree
Showing 15 changed files with 110 additions and 76 deletions.
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ If you're here, you're probably already familiar with Rust. If not, you can find

```toml
[dependencies]
ftswarm = "0.2.3"
ftswarm = "0.2.4"
```

To use it in your code, look at the examples in the [crates/ftswarm/examples](crates/ftswarm/examples) directory.
Expand All @@ -54,7 +54,7 @@ that can be used to test your code without having a real ftSwarm. To use it, add

```toml
[dependencies]
ftswarm_emulator = "0.2.3"
ftswarm_emulator = "0.2.4"
```

## How can I contribute?
Expand Down
11 changes: 7 additions & 4 deletions crates/ftswarm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ftswarm"
version = "0.2.3"
version = "0.2.4"
edition = "2021"
description = "A simple swarm protocol communication library"
license = "MIT"
Expand All @@ -9,10 +9,13 @@ readme = "../../README.md"
keywords = ["ftswarm", "communication", "iot", "robotics"]
categories = ["network-programming", "science::robotics"]

[features]
tokio_mutex = []

[dependencies]
ftswarm_proto = { path = "../ftswarm_proto", version = "0.2.3" }
ftswarm_serial = { path = "../ftswarm_serial", version = "0.2.3" }
ftswarm_macros = { path = "../ftswarm_macros", version = "0.2.3" }
ftswarm_proto = { path = "../ftswarm_proto", version = "0.2.4" }
ftswarm_serial = { path = "../ftswarm_serial", version = "0.2.4" }
ftswarm_macros = { path = "../ftswarm_macros", version = "0.2.4" }
rand = "0.9.0-alpha.0"
tokio.workspace = true
log.workspace = true
Expand Down
14 changes: 7 additions & 7 deletions crates/ftswarm/examples/hello_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,29 +35,29 @@ async fn main() -> Result<(), String> {
info!("WhoAmI: {}", response);

info!("Halting motors");
swarm.halt();
swarm.halt().await;

info!("Uptime: {:?}", swarm.uptime().await?);

let switch = Digital::create(&swarm, Aliases::SWITCH, NormallyOpen::Open).await;
let led1 = Led::create(&swarm, Aliases::LED1, ()).await;
let led2 = Led::create(&swarm, Aliases::LED2, ()).await;

led1.lock().unwrap().set_color(LedColor::blue()).await.unwrap();
led2.lock().unwrap().set_color(LedColor::cyan()).await.unwrap();
led1.lock().await.set_color(LedColor::blue()).await?;
led2.lock().await.set_color(LedColor::cyan()).await?;

let mut switch_state = switch.lock().unwrap().value;
let mut switch_state = switch.lock().await.value;
loop {
let value = switch.lock().unwrap().value;
let value = switch.lock().await.value;

if switch_state != value {
switch_state = value;
info!("Switch state: {}", switch_state);

let new_led_color = recv_color.recv().await.unwrap();
let color = LedColor::hsl(new_led_color, 100, 50);
led1.lock().unwrap().set_color(color.clone()).await.unwrap();
led2.lock().unwrap().set_color(color).await.unwrap();
led1.lock().await.set_color(color.clone()).await?;
led2.lock().await.set_color(color).await?;
}
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;

Expand Down
52 changes: 36 additions & 16 deletions crates/ftswarm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::sync::Arc;

#[cfg(not(feature = "tokio_mutex"))]
use std::sync::Mutex as StdMutex;

#[cfg(feature = "tokio_mutex")]
use tokio::sync::Mutex as TokioMutex;

use proto::message_parser::subscription::Subscription;
use tokio::task::JoinHandle;
use tokio::time::{Duration, sleep};
Expand All @@ -24,6 +31,22 @@ pub mod prelude;
#[cfg(test)]
mod tests;

// Set mutex type based on the feature flag
#[cfg(feature = "tokio_mutex")]
pub type Mutex<T> = TokioMutex<T>;
#[cfg(not(feature = "tokio_mutex"))]
pub type Mutex<T> = StdMutex<T>;

#[cfg(feature = "tokio_mutex")]
async fn lock<T>(mutex: &Mutex<T>) -> tokio::sync::MutexGuard<T> {
mutex.lock().await
}

#[cfg(not(feature = "tokio_mutex"))]
async fn lock<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<T> {
mutex.lock().unwrap()
}

/// A macro to create a struct with static string aliases
///
/// # Example
Expand All @@ -39,9 +62,7 @@ mod tests;
/// }
/// }
///
/// fn main() {
/// println!("Switch alias: {}", Aliases::SWITCH);
/// }
/// println!("Switch alias: {}", Aliases::SWITCH);
/// ```
///
/// This is useful for creating type-safe alias names for ftSwarm objects
Expand Down Expand Up @@ -101,7 +122,6 @@ impl FtSwarm {

let handle = tokio::spawn(async move {
FtSwarm::input_loop(inner_for_thread, serial).await;
()
});

FtSwarm {
Expand All @@ -116,7 +136,7 @@ impl FtSwarm {
let line = serial_port.read_line().expect("Readline failure").replace("\n", "").replace("\r", "");
let response = S2RMessage::from(line);
{
let mut inner = inner_ft_swarm.lock().unwrap();
let mut inner = lock(&inner_ft_swarm).await;
if let S2RMessage::Subscription(subscription) = response {
if let Ok(subscription) = Subscription::try_from(subscription) {
if let Some(object) = inner.objects.get(&subscription.port_name) {
Expand All @@ -130,7 +150,7 @@ impl FtSwarm {
}

{
let mut inner = inner_ft_swarm.lock().unwrap();
let mut inner = lock(&inner_ft_swarm).await;

// Handle outputs
if let Some(data) = inner.write_queue.pop() {
Expand All @@ -143,29 +163,29 @@ impl FtSwarm {
}


pub(crate) fn push_cache(&self, object: Box<dyn Fn(RPCReturnParam) + Send>, name: &str) {
let mut inner = self.inner.lock().unwrap();
pub(crate) async fn push_cache(&self, object: Box<dyn Fn(RPCReturnParam) + Send>, name: &str) {
let mut inner = lock(&self.inner).await;
inner.objects.insert(name.to_string(), object);
}

/// Low-level method to send a command to the ftSwarm. Only use this as a last resort
pub fn send_command(&self, command: FtSwarmCommand) {
let mut inner = self.inner.lock().unwrap();
pub async fn send_command(&self, command: FtSwarmCommand) {
let mut inner = lock(&self.inner).await;
inner.write_queue.push(command);
}

/// Low-level method to receive a response to the ftSwarm. Only use this as a last resort
pub async fn read_response(&self) -> Result<RPCReturnParam, String> {
let (handle, mut recv) = SenderHandle::create();
{
let mut inner = self.inner.lock().unwrap();
let mut inner = lock(&self.inner).await;
inner.message_queue.push_sender(&handle);
}

let response = recv.recv().await.unwrap();

{
let mut inner = self.inner.lock().unwrap();
let mut inner = lock(&self.inner).await;
inner.message_queue.drop_sender(&handle);
}

Expand All @@ -185,7 +205,7 @@ pub async fn transact(&self, command: FtSwarmCommand) -> Result<RPCReturnParam,
_ => false,
};

self.send_command(command);
self.send_command(command).await;

if is_subscription {
return Ok(RPCReturnParam::Ok);
Expand All @@ -205,8 +225,8 @@ pub async fn whoami(&self) -> Result<WhoamiResponse, String> {
}

/// Stop all connected motors and turn off all LEDs (except for RGB LEDs)
pub fn halt(&self) {
self.send_command(FtSwarmCommand::Direct(FtSwarmDirectCommand::Halt));
pub async fn halt(&self) {
self.send_command(FtSwarmCommand::Direct(FtSwarmDirectCommand::Halt)).await;
}

/// Return the uptime of the connected ftSwarm (max precision: seconds)
Expand Down
28 changes: 18 additions & 10 deletions crates/ftswarm/src/swarm_object.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::{future::Future, sync::{Arc, Mutex}};
use std::{future::Future, sync::Arc};

use ftswarm_macros::Updateable;
use ftswarm_proto::{command::{argument::Argument, FtSwarmCommand, rpc::{FtSwarmRPCCommand, RpcFunction}}, message_parser::rpc::RPCReturnParam};
use ftswarm_proto::{command::{argument::Argument, rpc::{FtSwarmRPCCommand, RpcFunction}, FtSwarmCommand}, message_parser::rpc::RPCReturnParam};

use crate::FtSwarm;
use crate::{lock, FtSwarm, Mutex};

pub mod analog;
pub mod digital;
Expand All @@ -13,6 +13,7 @@ pub mod actor;
pub mod led;
pub mod controller;


pub type Io<T> = Arc<Mutex<Box<T>>>;

pub trait Updateable {
Expand All @@ -29,17 +30,24 @@ pub trait NewSwarmObject<Params> {
}

pub trait SwarmObject<Params>: NewSwarmObject<Params> + Updateable + Clone + Sync + Send {
fn create(swarm: &FtSwarm, name: &str, params: Params) -> impl Future<Output=Io<Self>> where Self: 'static {
fn create(swarm: &FtSwarm, name: &str, params: Params) -> impl Future<Output=Io<Self>>
where
Self: 'static,
{
let obj = Self::new(name, swarm.clone(), params);
let arc = Arc::new(Mutex::new(obj));
let for_closure = arc.clone();
swarm.push_cache(Box::new(move |subscription| {
let mut obj = for_closure.lock().unwrap();
obj.handle_subscription(&subscription);
}), name);

async move {
swarm.push_cache(Box::new(move |subscription| {
let for_task = for_closure.clone();
tokio::spawn(async move {
let mut obj = lock(&for_task).await;
obj.handle_subscription(&subscription);
});
}), name).await;
{
let mut obj = arc.lock().unwrap();
let mut obj = lock(&arc).await;
obj.init().await;
}
arc
Expand All @@ -53,7 +61,7 @@ pub trait SwarmObject<Params>: NewSwarmObject<Params> + Updateable + Clone + Syn
args,
};

return self.swarm().transact(FtSwarmCommand::RPC(command));
self.swarm().transact(FtSwarmCommand::RPC(command))
}
}

Expand Down
6 changes: 3 additions & 3 deletions crates/ftswarm/src/swarm_object/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ impl From<bool> for ValueState {
}
}

impl Into<i64> for ValueState {
fn into(self) -> i64 {
match self {
impl From<ValueState> for i64 {
fn from(value: ValueState) -> Self {
match value {
ValueState::High => 255,
ValueState::Reverse => -255,
ValueState::Low => 0,
Expand Down
16 changes: 8 additions & 8 deletions crates/ftswarm/src/swarm_object/analog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,35 @@ analog_swarm_object!(Voltmeter);

impl Thermometer {
pub async fn get_kelvin(&self) -> Result<f32, String> {
return self.run_command(RpcFunction::GetKelvin, vec![])
self.run_command(RpcFunction::GetKelvin, vec![])
.await
.and_then(|param| param.as_float().ok_or("Failed to get kelvin".to_string()));
.and_then(|param| param.as_float().ok_or("Failed to get kelvin".to_string()))
}

pub async fn get_celsius(&self) -> Result<f32, String> {
return self.run_command(RpcFunction::GetCelsius, vec![])
self.run_command(RpcFunction::GetCelsius, vec![])
.await
.and_then(|param| param.as_float().ok_or("Failed to get celsius".to_string()));
.and_then(|param| param.as_float().ok_or("Failed to get celsius".to_string()))
}

pub async fn get_fahrenheit(&self) -> Result<f32, String> {
return self.run_command(RpcFunction::GetFahrenheit, vec![])
self.run_command(RpcFunction::GetFahrenheit, vec![])
.await
.and_then(|param| param.as_float().ok_or("Failed to get fahrenheit".to_string()));
.and_then(|param| param.as_float().ok_or("Failed to get fahrenheit".to_string()))
}
}

impl Ohmmeter {
pub async fn get_resistance(&self) -> Result<f32, String> {
return self.run_command(RpcFunction::GetResistance, vec![])
self.run_command(RpcFunction::GetResistance, vec![])
.await
.and_then(|param| param.as_float().ok_or("Failed to get resistance".to_string()))
}
}

impl Voltmeter {
pub async fn get_voltage(&self) -> Result<f32, String> {
return self.run_command(RpcFunction::GetVoltage, vec![])
self.run_command(RpcFunction::GetVoltage, vec![])
.await
.and_then(|param| param.as_float().ok_or("Failed to get voltage".to_string()))
}
Expand Down
2 changes: 1 addition & 1 deletion crates/ftswarm/src/swarm_object/led.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ impl Led {
}

pub async fn set_brightness(&self, brightness: i32) -> Result<(), String> {
let brightness = brightness.max(255).min(0);
let brightness = brightness.min(255).max(0);
self.run_command(RpcFunction::SetBrightness, vec![Argument::Int(brightness as i64)]).await
.map(|_| ())
}
Expand Down
Loading

0 comments on commit 75c6f3f

Please sign in to comment.