diff --git a/fan-controller/bacon.toml b/fan-controller/bacon.toml new file mode 100644 index 0000000..917449a --- /dev/null +++ b/fan-controller/bacon.toml @@ -0,0 +1,111 @@ +# This is a configuration file for the bacon tool +# +# Complete help on configuration: https://dystroy.org/bacon/config/ +# +# You may check the current default at +# https://github.com/Canop/bacon/blob/main/defaults/default-bacon.toml + +default_job = "check" +env.CARGO_TERM_COLOR = "always" + +[jobs.check] +command = ["cargo", "check"] +need_stdout = false + +[jobs.check-all] +command = ["cargo", "check", "--all-targets"] +need_stdout = false + +# Run clippy on the default target +[jobs.clippy] +command = ["cargo", "clippy"] +need_stdout = false + +# Run clippy on all targets +# To disable some lints, you may change the job this way: +# [jobs.clippy-all] +# command = [ +# "cargo", "clippy", +# "--all-targets", +# "--", +# "-A", "clippy::bool_to_int_with_if", +# "-A", "clippy::collapsible_if", +# "-A", "clippy::derive_partial_eq_without_eq", +# ] +# need_stdout = false +[jobs.clippy-all] +command = ["cargo", "clippy", "--all-targets"] +need_stdout = false + +# This job lets you run +# - all tests: bacon test +# - a specific test: bacon test -- config::test_default_files +# - the tests of a package: bacon test -- -- -p config +[jobs.test] +command = ["cargo", "test"] +need_stdout = true + +[jobs.nextest] +command = [ + "cargo", "nextest", "run", + "--hide-progress-bar", "--failure-output", "final" +] +need_stdout = true +analyzer = "nextest" + +[jobs.doc] +command = ["cargo", "doc", "--no-deps"] +need_stdout = false + +# If the doc compiles, then it opens in your browser and bacon switches +# to the previous job +[jobs.doc-open] +command = ["cargo", "doc", "--no-deps", "--open"] +need_stdout = false +on_success = "back" # so that we don't open the browser at each change + +# You can run your application and have the result displayed in bacon, +# if it makes sense for this crate. +[jobs.run] +command = [ + "cargo", "run", + # put launch parameters for your program behind a `--` separator +] +need_stdout = true +allow_warnings = true +background = true + +# Run your long-running application (eg server) and have the result displayed in bacon. +# For programs that never stop (eg a server), `background` is set to false +# to have the cargo run output immediately displayed instead of waiting for +# program's end. +# 'on_change_strategy' is set to `kill_then_restart` to have your program restart +# on every change (an alternative would be to use the 'F5' key manually in bacon). +# If you often use this job, it makes sense to override the 'r' key by adding +# a binding `r = job:run-long` at the end of this file . +[jobs.run-long] +command = [ + "cargo", "run", + # put launch parameters for your program behind a `--` separator +] +need_stdout = true +allow_warnings = true +background = false +on_change_strategy = "kill_then_restart" + +# This parameterized job runs the example of your choice, as soon +# as the code compiles. +# Call it as +# bacon ex -- my-example +[jobs.ex] +command = ["cargo", "run", "--example"] +need_stdout = true +allow_warnings = true + +# You may define here keybindings that would be specific to +# a project, for example a shortcut to launch a specific job. +# Shortcuts to internal functions (scrolling, toggling, etc.) +# should go in your personal global prefs.toml file instead. +[keybindings] +# alt-m = "job:my-job" +c = "job:clippy-all" # comment this to have 'c' run clippy on only the default target diff --git a/fan-controller/src/main.rs b/fan-controller/src/main.rs index a8cf81d..02c8162 100644 --- a/fan-controller/src/main.rs +++ b/fan-controller/src/main.rs @@ -73,6 +73,7 @@ mod debounce; mod fan; mod modbus; mod mqtt; +mod task; bind_interrupts!(struct Irqs { PIO0_IRQ_0 => PioInterruptHandler; @@ -479,7 +480,7 @@ async fn mqtt_task( /// A handler that takes MQTT publishes and sets the fan settings accordingly async fn handle_publish<'f>(publish: &'f Publish<'f>) { info!("Handling publish"); - let sender = FAN_1_STATE.sender(); + let sender = FAN_CONTROLLER.fan_states.0.sender(); // This part is not MQTT and application specific match publish.topic_name { @@ -949,7 +950,7 @@ async fn mqtt_task( // Future 5 update homeassistant when change occurs async fn update_homeassistant() { - let Some(mut receiver) = FAN_1_STATE.receiver() else { + let Some(mut receiver) = FAN_CONTROLLER.fan_states.0.receiver() else { error!("Fan state receiver was not set up. Cannot update Homeassistant"); return; }; @@ -1023,13 +1024,13 @@ async fn mqtt_task( /// This task handles inputs from physical buttons to change the fan speed #[embassy_executor::task] -async fn input_task(pin_18: PIN_18) { +async fn input(pin_18: PIN_18) { // The button just rotates through fan settings. This is because we currently only have one button // Will probably use something more advanced in the future let mut button = Debouncer::new(Input::new(pin_18, Pull::Up), Duration::from_millis(250)); let mut fan_state = fan::State::default(); - let sender = FAN_1_STATE.sender(); + let sender = FAN_CONTROLLER.fan_states.0.sender(); loop { // Falling edge for our button -> button down (pressing down @@ -1045,7 +1046,9 @@ async fn input_task(pin_18: PIN_18) { let state = match fan_state { fan::State::Off => { - let setting = FAN_1_STATE + let setting = FAN_CONTROLLER + .fan_states + .0 .try_get() .map(|state| state.setting) .unwrap_or(fan::Setting::ZERO); @@ -1076,14 +1079,14 @@ async fn input_task(pin_18: PIN_18) { /// Update fans whenenver the fan setting or on state changes #[embassy_executor::task] async fn update_fans() { - let Some(mut receiver) = FAN_1_STATE.receiver() else { + let Some(mut receiver) = FAN_CONTROLLER.fan_states.0.receiver() else { // Not using asserts because they are hard to debug on embedded where it crashed error!("No receiver for fan is on state. This should never happen."); return; }; // Only comparing on state causes button triggers to be ignored - let mut previous = FAN_1_STATE.try_get().unwrap_or(FanState { + let mut previous = FAN_CONTROLLER.fan_states.0.try_get().unwrap_or(FanState { is_on: false, setting: fan::Setting::ZERO, }); @@ -1139,17 +1142,31 @@ struct FanState { setting: fan::Setting, } -/// Is on and the setting update independent of each other on homeassistant. -/// Update this state even though it might not yet be set on the fan devices. -/// Use optimistic updates. -/// Senders: -/// - MQTT (server to client) -/// - Button -/// Receivers: -/// - Fan -/// - MQTT (client to server) -static FAN_1_STATE: Watch = Watch::new(); -static FAN_2_STATE: Watch = Watch::new(); +struct FanController { + /// Is on and the setting update independent of each other on homeassistant. + /// Update this state even though it might not yet be set on the fan devices. + /// Use optimistic updates. + /// Senders: + /// - MQTT (server to client) + /// - Button + /// Receivers: + /// - Fan + /// - MQTT (client to server) + fan_states: ( + Watch, + Watch, + ), +} + +impl FanController { + const fn new() -> Self { + Self { + fan_states: (Watch::new(), Watch::new()), + } + } +} + +static FAN_CONTROLLER: FanController = FanController::new(); /// Displays fan status with 2 LEDs: /// Off Off -> Fans Off @@ -1170,14 +1187,14 @@ async fn display_status(pin_21: PIN_21, pin_20: PIN_20) { led_1.set_low(); led_2.set_low(); - let Some(mut receiver) = FAN_1_STATE.receiver() else { + let Some(mut receiver) = FAN_CONTROLLER.fan_states.0.receiver() else { // Not using asserts because they are hard to debug on embedded where it crashed error!("No receiver for fan is on state. This should never happen."); return; }; // Set initial state - let mut current_state = FAN_1_STATE.try_get().unwrap_or(FanState { + let mut current_state = FAN_CONTROLLER.fan_states.0.try_get().unwrap_or(FanState { is_on: false, setting: fan::Setting::ZERO, }); @@ -1256,7 +1273,7 @@ async fn main(spawner: Spawner) { } // Button input task waits for button presses and send according signals to the modbus task - unwrap!(spawner.spawn(input_task(pin_18))); + unwrap!(spawner.spawn(input(pin_18))); unwrap!(spawner.spawn(update_fans())); // The MQTT task waits for publishes from MQTT and sends them to the modbus task. // It also sends updates from the modbus task that happen through button inputs to MQTT diff --git a/fan-controller/src/task.rs b/fan-controller/src/task.rs new file mode 100644 index 0000000..dcdffd0 --- /dev/null +++ b/fan-controller/src/task.rs @@ -0,0 +1 @@ +//! Tasks that can be extracted so they don't clutter main.rs