Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions Cargo.lock

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

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ members = [
"battery-service",
"battery-service-messages",
"thermal-service",
"thermal-service-messages",
"thermal-service-interface",
"thermal-service-relay",
"cfu-service",
"embedded-service",
"espi-service",
Expand Down Expand Up @@ -105,7 +106,8 @@ rstest = { version = "0.26.1", default-features = false }
serde = { version = "1.0.*", default-features = false }
static_cell = "2.1.0"
toml = { version = "0.8", default-features = false }
thermal-service-messages = { path = "./thermal-service-messages" }
thermal-service-interface = { path = "./thermal-service-interface" }
thermal-service-relay = { path = "./thermal-service-relay" }
time-alarm-service-interface = { path = "./time-alarm-service-interface" }
time-alarm-service-relay = { path = "./time-alarm-service-relay" }
type-c-interface = { path = "./type-c-interface" }
Expand Down
23 changes: 22 additions & 1 deletion embedded-service/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use core::{future::ready, marker::PhantomData};
use crate::error;

use embassy_sync::{
channel::{DynamicReceiver, DynamicSender},
blocking_mutex::raw::RawMutex,
channel::{DynamicReceiver, DynamicSender, Receiver as ChannelReceiver, Sender as ChannelSender},
pubsub::{DynImmediatePublisher, DynSubscriber, WaitResult},
};

Expand Down Expand Up @@ -84,6 +85,26 @@ impl<E: Clone> Receiver<E> for DynSubscriber<'_, E> {
}
}

impl<M: RawMutex, E, const N: usize> Sender<E> for ChannelSender<'_, M, E, N> {
fn try_send(&mut self, event: E) -> Option<()> {
ChannelSender::try_send(self, event).ok()
}

fn send(&mut self, event: E) -> impl Future<Output = ()> {
ChannelSender::send(self, event)
}
}

impl<M: RawMutex, E, const N: usize> Receiver<E> for ChannelReceiver<'_, M, E, N> {
fn try_next(&mut self) -> Option<E> {
ChannelReceiver::try_receive(self).ok()
}

fn wait_next(&mut self) -> impl Future<Output = E> {
ChannelReceiver::receive(self)
}
}

/// A sender that discards all events sent to it.
pub struct NoopSender;

Expand Down
16 changes: 6 additions & 10 deletions examples/std/Cargo.lock

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

2 changes: 1 addition & 1 deletion examples/std/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type-c-interface = { path = "../../type-c-interface", features = ["log"] }
embedded-sensors-hal-async = "0.3.0"
embedded-fans-async = "0.2.0"
thermal-service = { path = "../../thermal-service", features = ["log", "mock"] }
thermal-service-messages = { path = "../../thermal-service-messages" }
thermal-service-interface = { path = "../../thermal-service-interface" }

env_logger = "0.11.8"
log = "0.4.14"
Expand Down
115 changes: 77 additions & 38 deletions examples/std/src/bin/thermal.rs
Original file line number Diff line number Diff line change
@@ -1,47 +1,83 @@
use embassy_executor::{Executor, Spawner};
use embassy_sync::once_lock::OnceLock;
use embassy_sync::channel::{Channel, Receiver as ChannelReceiver, Sender as ChannelSender};
use embassy_time::Timer;
use embedded_services::{error, info};
use embedded_services::GlobalRawMutex;
use embedded_services::{info, warn};
use static_cell::StaticCell;
use thermal_service as ts;
use thermal_service_interface::ThermalService;
use thermal_service_interface::fan::FanService;
use thermal_service_interface::sensor;
use thermal_service_interface::sensor::SensorService;

// More readable type aliases for sensor, fan, and thermal services used in this example
type MockSensorService = ts::sensor::Service<
'static,
ts::mock::sensor::MockSensor,
ChannelSender<'static, GlobalRawMutex, sensor::Event, 4>,
16,
>;
type MockFanService =
ts::fan::Service<'static, ts::mock::fan::MockFan, MockSensorService, embedded_services::event::NoopSender, 16>;
type MockThermalService = ts::Service<'static, MockSensorService, MockFanService>;

#[embassy_executor::task]
async fn run(spawner: Spawner) {
embedded_services::init().await;

static SENSOR: StaticCell<ts::mock::TsMockSensor> = StaticCell::new();
let sensor = SENSOR.init(ts::mock::new_sensor());

static FAN: StaticCell<ts::mock::TsMockFan> = StaticCell::new();
let fan = FAN.init(ts::mock::new_fan());

static SENSORS: StaticCell<[&'static ts::sensor::Device; 1]> = StaticCell::new();
let sensors = SENSORS.init([sensor.device()]);
// Create a backing channel for sensor events to be sent on
static SENSOR_EVENT_CHANNEL: StaticCell<Channel<GlobalRawMutex, sensor::Event, 4>> = StaticCell::new();
let sensor_event_channel = SENSOR_EVENT_CHANNEL.init(Channel::new());

static FANS: StaticCell<[&'static ts::fan::Device; 1]> = StaticCell::new();
let fans = FANS.init([fan.device()]);
// Then create the list of senders for the sensor service to use
// Though we are only using one sender in this example, an abitrary number could be used
static SENSOR_SENDERS: StaticCell<[ChannelSender<'static, GlobalRawMutex, sensor::Event, 4>; 1]> =
StaticCell::new();
let event_senders = SENSOR_SENDERS.init([sensor_event_channel.sender()]);

static STORAGE: OnceLock<ts::Service<'static>> = OnceLock::new();
let thermal_service = ts::Service::init(&STORAGE, sensors, fans).await;

let _fan_service = odp_service_common::spawn_service!(
// Spawn the sensor service which will begin running and generating events
let sensor_service = odp_service_common::spawn_service!(
spawner,
ts::fan::Service<'static, ts::mock::fan::MockFan, 16>,
ts::fan::InitParams { fan, thermal_service }
MockSensorService,
ts::sensor::InitParams {
driver: ts::mock::sensor::MockSensor::new(),
config: ts::mock::sensor::MockSensor::config(),
event_senders,
}
)
.expect("Failed to spawn fan service");
.expect("Failed to spawn sensor service");

let _sensor_service = odp_service_common::spawn_service!(
// Spawn the fan service which uses the above sensor service for automatic speed control
// In this example, we use an empty event sender list since the fan won't generate any events
let fan_service = odp_service_common::spawn_service!(
spawner,
ts::sensor::Service<'static, ts::mock::sensor::MockSensor, 16>,
ts::sensor::InitParams {
sensor,
thermal_service
MockFanService,
ts::fan::InitParams {
driver: ts::mock::fan::MockFan::new(),
config: ts::mock::fan::MockFan::config(),
sensor_service,
event_senders: &mut [],
}
)
.expect("Failed to spawn sensor service");
.expect("Failed to spawn fan service");

// The thermal service accepts slices of associated sensors and fans,
// so we need static lifetime here since the thermal service handle is passed to task
static SENSORS: StaticCell<[MockSensorService; 1]> = StaticCell::new();
let sensors = SENSORS.init([sensor_service]);

static FANS: StaticCell<[MockFanService; 1]> = StaticCell::new();
let fans = FANS.init([fan_service]);

// The thermal service handle mainly exists for host relaying, but this example does not make use of that
//
// However, we can still use the thermal service handle to access registered sensors and fans by id
static RESOURCES: StaticCell<ts::Resources<MockSensorService, MockFanService>> = StaticCell::new();
let resources = RESOURCES.init(ts::Resources::default());
let thermal_service = ts::Service::init(resources, ts::InitParams { sensors, fans });

spawner.must_spawn(monitor(thermal_service));
spawner.must_spawn(sensor_event_listener(sensor_event_channel.receiver()));
}

fn main() {
Expand All @@ -55,21 +91,24 @@ fn main() {
}

#[embassy_executor::task]
async fn monitor(service: &'static ts::Service<'static>) {
async fn sensor_event_listener(receiver: ChannelReceiver<'static, GlobalRawMutex, sensor::Event, 4>) {
loop {
let event = receiver.receive().await;
warn!("Sensor event: {:?}", event);
}
}

#[embassy_executor::task]
async fn monitor(service: MockThermalService) {
loop {
match service
.execute_sensor_request(ts::mock::MOCK_SENSOR_ID, ts::sensor::Request::GetTemp)
.await
{
Ok(ts::sensor::ResponseData::Temp(temp)) => info!("Mock sensor temp: {} C", temp),
_ => error!("Failed to monitor mock sensor temp"),
if let Some(sensor) = service.sensor(0) {
let temp = sensor.temperature().await;
info!("Mock sensor temp: {} C", temp);
}
match service
.execute_fan_request(ts::mock::MOCK_FAN_ID, ts::fan::Request::GetRpm)
.await
{
Ok(ts::fan::ResponseData::Rpm(rpm)) => info!("Mock fan RPM: {}", rpm),
_ => error!("Failed to monitor mock fan RPM"),

if let Some(fan) = service.fan(0) {
let rpm = fan.rpm().await;
info!("Mock fan RPM: {}", rpm);
}

Timer::after_secs(1).await;
Expand Down
23 changes: 23 additions & 0 deletions thermal-service-interface/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "thermal-service-interface"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true

[lints]
workspace = true

[dependencies]
defmt = { workspace = true, optional = true }
embassy-time.workspace = true
embedded-fans-async = "0.2.0"
embedded-sensors-hal-async = "0.3.0"

[features]
defmt = [
"dep:defmt",
"embassy-time/defmt",
"embedded-fans-async/defmt",
"embedded-sensors-hal-async/defmt",
]
Loading
Loading