Skip to content

Commit

Permalink
use a finite state machine to handle state changes
Browse files Browse the repository at this point in the history
  • Loading branch information
doums committed Sep 14, 2021
1 parent 4bb12db commit 32f9829
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 114 deletions.
52 changes: 38 additions & 14 deletions 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 Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bato"
version = "0.1.4"
version = "0.1.5"
authors = ["pierre <dommerc.pierre@gmail.com>"]
edition = "2018"
links = "notilus"
Expand Down
141 changes: 141 additions & 0 deletions src/battery.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use crate::{
fsm::{Fsm, FsmState, StateMap},
notify::{send, NotifyNotification},
Notification,
};
use std::{collections::HashMap, hash::Hash};

struct ChargingState;
struct DischargingState;
struct FullState;
struct LowState;
struct CriticalState;

#[derive(Hash, Eq, PartialEq)]
pub enum State {
Charging,
Discharging,
Full,
Low,
Critical,
}

pub struct Data<'data> {
pub current_level: u32,
pub status: String,
pub low_level: u32,
pub critical_level: u32,
pub critical: Option<&'data Notification>,
pub low: Option<&'data Notification>,
pub full: Option<&'data Notification>,
pub charging: Option<&'data Notification>,
pub discharging: Option<&'data Notification>,
pub notification: *mut NotifyNotification,
}

impl<'data> FsmState<State, Data<'data>> for ChargingState {
fn enter(&mut self, data: &mut Data) {
if let Some(n) = data.charging {
send(data.notification, n);
}
}

fn next_state(&self, data: &mut Data) -> Option<State> {
match (data.status.as_str(), data.current_level) {
("Full", _) => Some(State::Full),
("Discharging", l) if l <= data.critical_level => Some(State::Critical),
("Discharging", l) if l <= data.low_level => Some(State::Low),
("Discharging", _) => Some(State::Discharging),
_ => None,
}
}

fn exit(&mut self, _data: &mut Data) {}
}

impl<'data> FsmState<State, Data<'data>> for DischargingState {
fn enter(&mut self, data: &mut Data) {
if let Some(n) = data.discharging {
send(data.notification, n);
}
}

fn next_state(&self, data: &mut Data) -> Option<State> {
match (data.status.as_str(), data.current_level) {
("Charging", _) => Some(State::Charging),
("Full", _) => Some(State::Full), // add this just in case
("Discharging", l) if l <= data.critical_level => Some(State::Critical),
("Discharging", l) if l <= data.low_level => Some(State::Low),
_ => None,
}
}

fn exit(&mut self, _data: &mut Data) {}
}

impl<'data> FsmState<State, Data<'data>> for FullState {
fn enter(&mut self, data: &mut Data) {
if let Some(n) = data.full {
send(data.notification, n);
}
}

fn next_state(&self, data: &mut Data) -> Option<State> {
match data.status.as_str() {
"Charging" => Some(State::Charging),
"Discharging" => Some(State::Discharging),
_ => None,
}
}

fn exit(&mut self, _data: &mut Data) {}
}

impl<'data> FsmState<State, Data<'data>> for LowState {
fn enter(&mut self, data: &mut Data) {
if let Some(n) = data.low {
send(data.notification, n);
}
}

fn next_state(&self, data: &mut Data) -> Option<State> {
match (data.status.as_str(), data.current_level) {
("Charging", _) => Some(State::Charging),
("Discharging", l) if l <= data.critical_level => Some(State::Critical),
_ => None,
}
}

fn exit(&mut self, _data: &mut Data) {}
}

impl<'data> FsmState<State, Data<'data>> for CriticalState {
fn enter(&mut self, data: &mut Data) {
if let Some(n) = data.critical {
send(data.notification, n);
}
}

fn next_state(&self, data: &mut Data) -> Option<State> {
if data.status == "Charging" {
return Some(State::Charging);
}
None
}

fn exit(&mut self, _data: &mut Data) {}
}

pub fn create_fsm<'data>() -> Fsm<State, Data<'data>> {
let mut states: StateMap<State, Data> = HashMap::new();
states.insert(State::Full, Box::new(FullState));
states.insert(State::Charging, Box::new(ChargingState));
states.insert(State::Discharging, Box::new(DischargingState));
states.insert(State::Low, Box::new(LowState));
states.insert(State::Critical, Box::new(CriticalState));
Fsm::new(State::Discharging, states)
}
53 changes: 53 additions & 0 deletions src/fsm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use std::{collections::HashMap, hash::Hash};

pub type StateMap<K, D> = HashMap<K, Box<dyn FsmState<K, D>>>;

pub struct Fsm<K, D>
where
K: Eq + Hash,
{
current_state: K,
states: StateMap<K, D>,
}

impl<K, D> Fsm<K, D>
where
K: Eq + Hash,
{
pub fn new(init_state: K, states: StateMap<K, D>) -> Self {
Fsm {
current_state: init_state,
states,
}
}

fn set_state(&mut self, new_state: K, data: &mut D) {
self.states.get_mut(&self.current_state).unwrap().exit(data);
self.states.get_mut(&new_state).unwrap().enter(data);
self.current_state = new_state;
}

pub fn shift(&mut self, data: &mut D) {
if let Some(next_state) = self
.states
.get_mut(&self.current_state)
.unwrap()
.next_state(data)
{
self.set_state(next_state, data);
}
}
}

pub trait FsmState<K, D>
where
K: Eq + Hash,
{
fn enter(&mut self, data: &mut D);
fn next_state(&self, data: &mut D) -> Option<K>;
fn exit(&mut self, data: &mut D);
}
Loading

0 comments on commit 32f9829

Please sign in to comment.