Add auto move, rework state machine
This commit is contained in:
@ -9,7 +9,7 @@ use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::dispatch::{Dispatch, RecvQ, SendQ};
|
||||
use crate::commands::Commands;
|
||||
use crate::commands::{Button, Commands};
|
||||
|
||||
// TODO HARDWARE: test these values to see if they are suitable
|
||||
const BLE_MIN_INTERVAL: u16 = 24; // x 1.25ms
|
||||
@ -37,6 +37,9 @@ const UUID_BUTTON_DOWN: BleUuid = uuid128!("c1401122-8dda-45a3-959b-d23a0f8f53d7
|
||||
const UUID_BUTTON_STOP: BleUuid = uuid128!("c1401123-8dda-45a3-959b-d23a0f8f53d7");
|
||||
const UUID_BLUETOOTH_NAME: BleUuid = uuid128!("c1401224-8dda-45a3-959b-d23a0f8f53d7");
|
||||
|
||||
const BLE_BUTTON_RELEASE: u8 = 0;
|
||||
const BLE_BUTTON_PRESS: u8 = 1;
|
||||
|
||||
pub struct BleServer {
|
||||
send_q: SendQ,
|
||||
recv_q: RecvQ,
|
||||
@ -48,9 +51,9 @@ pub struct BleServer {
|
||||
impl BleServer {
|
||||
pub fn new(dp: &mut Dispatch) -> Self {
|
||||
let cmds = vec![
|
||||
Commands::NotifyMotorDown { data: 0 },
|
||||
Commands::NotifyMotorStop { data: 0 },
|
||||
Commands::NotifyMotorUp { data: 0 },
|
||||
Commands::NotifyMotorDown { data: Button::Released },
|
||||
Commands::NotifyMotorStop { data: Button::Released },
|
||||
Commands::NotifyMotorUp { data: Button::Released },
|
||||
Commands::PairTimerExpired,
|
||||
Commands::AllowPairing,
|
||||
Commands::EraseBleBonds,
|
||||
@ -110,7 +113,7 @@ impl BleServer {
|
||||
);
|
||||
button_stop.lock().set_value(&[1])
|
||||
.on_write(closure!(clone sender, |args: &mut OnWriteArgs| {
|
||||
on_bluetooth_cmd(&sender, args, Commands::BluetoothStop {_data: 0})
|
||||
on_bluetooth_cmd(&sender, args, Commands::BluetoothStop {data: 0})
|
||||
}));
|
||||
// --- Device Name Bluetooth GATT --------------------------------------------------------
|
||||
let device_name = lift_service.lock().create_characteristic(
|
||||
@ -131,13 +134,13 @@ impl BleServer {
|
||||
match cmd {
|
||||
// TODO DISCUSS: This logic (if one button is pressed others are released) could be done in app instead.
|
||||
Commands::NotifyMotorUp{data} => {
|
||||
button_up.lock().set_value(&[data]).notify();
|
||||
button_up.lock().set_value(&button_to_ble_button(data)).notify();
|
||||
}
|
||||
Commands::NotifyMotorDown{data} => {
|
||||
button_down.lock().set_value(&[data]).notify();
|
||||
button_down.lock().set_value(&button_to_ble_button(data)).notify();
|
||||
}
|
||||
Commands::NotifyMotorStop{data} => {
|
||||
button_up.lock().set_value(&[data]).notify();
|
||||
button_up.lock().set_value(&button_to_ble_button(data)).notify();
|
||||
}
|
||||
Commands::PairTimerExpired => {
|
||||
self.advertise_unpairable()?;
|
||||
@ -216,9 +219,9 @@ fn on_bluetooth_cmd(sender: &SendQ, args: &mut OnWriteArgs, cmd: Commands) {
|
||||
sender.send_blocking(Commands::BluetoothDown {data: v[0]} )
|
||||
} else {error!("Received zero-byte bluetooth characteristic update {:?}", cmd); Ok(())}
|
||||
}
|
||||
Commands::BluetoothStop { _data: _ } => {
|
||||
Commands::BluetoothStop { data: _ } => {
|
||||
if v.len() > 0 {
|
||||
sender.send_blocking(Commands::BluetoothStop {_data: v[0]} )
|
||||
sender.send_blocking(Commands::BluetoothStop {data: v[0]} )
|
||||
} else {error!("Received zero-byte bluetooth characteristic update {:?}", cmd); Ok(())}
|
||||
}
|
||||
Commands::BluetoothName { data: _ } => {
|
||||
@ -272,5 +275,12 @@ fn set_server_callbacks(server: &mut BLEServer, sender: SendQ) {
|
||||
});
|
||||
}
|
||||
|
||||
fn button_to_ble_button(but: Button) -> [u8; 1] {
|
||||
match but {
|
||||
Button::Released => {[BLE_BUTTON_RELEASE]}
|
||||
Button::Pressed => {[BLE_BUTTON_PRESS]}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO set maximum pairs to remember?
|
||||
//TODO after disconnect, it returns to scanning - will it return to directed scanning? Find out when directed is working.
|
||||
@ -6,42 +6,58 @@ use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, EnumCountMacro, Debug)]
|
||||
pub enum Commands {
|
||||
// Use Arc for any data larger than 4 bytes, to keep message passing queue size down.
|
||||
|
||||
// Inputs sent from the PIC microcontroller
|
||||
PicRecvUp,
|
||||
PicRecvDown,
|
||||
PicRecvStop,
|
||||
// TODO: move these to buttons driver, for eventual move to ESP only?
|
||||
|
||||
// PIC button commands are considered "remote" due to the delay in sending notification
|
||||
PicRecvUp {data: u8},
|
||||
PicRecvDown {data: u8},
|
||||
PicRecvStop {data: u8},
|
||||
PicRecvLimitUp {data: u8}, // 0 for not hit, 1 for hit
|
||||
PicRecvLimitDown {data: u8}, // 0 for not hit, 1 for hit
|
||||
PicRecvAutoMode {data: u8}, // 0 for disallowed, 1 for allowed
|
||||
|
||||
// TODO: real hardware buttons - consider re-sending occasionally when pressed, so that transitions like holding up -> stopping -> holding down -> stopped -> (should go down but gets no new notice) work.
|
||||
|
||||
// Inputs from bluetooth
|
||||
BluetoothUp {data: u8},
|
||||
BluetoothUp {data: u8}, //TODO change these to real button states and change them on input.
|
||||
BluetoothDown {data: u8},
|
||||
BluetoothStop {_data: u8}, // There is no state where releasing the stop button induces a change.
|
||||
BluetoothName { data: Arc<String>},
|
||||
BluetoothStop {data: u8}, // There is no state where releasing the stop button induces a change.
|
||||
BluetoothName {data: Arc<String>},
|
||||
//TODO: Allow auto mode to be set via bluetooth as well
|
||||
|
||||
// Internal messages
|
||||
StopTimerExpired, // Sent when the 2 second stop sequence is complete
|
||||
StopTimerRestart,
|
||||
StopTimerClear,
|
||||
ButtonTimerExpired,
|
||||
ButtonTimerExpired, // TODO: these won't be necessary for hardware buttons; rename to PIC timer?
|
||||
ButtonTimerRestart,
|
||||
ButtonTimerClear,
|
||||
PairTimerExpired,
|
||||
AllowPairing, // Also serves as the timer restart command
|
||||
PairTimerClear,
|
||||
|
||||
NotifyMotorUp {data: u8},
|
||||
NotifyMotorDown {data: u8},
|
||||
NotifyMotorStop {data: u8},
|
||||
NotifyMotorUp {data: Button},
|
||||
NotifyMotorDown {data: Button},
|
||||
NotifyMotorStop {data: Button},
|
||||
|
||||
EraseBleBonds,
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct Button;
|
||||
/*pub struct Button;
|
||||
|
||||
impl Button {
|
||||
pub const PRESSED: u8 = 0x1;
|
||||
pub const RELEASED: u8 = 0x0;
|
||||
}*/
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Button {
|
||||
Released = 0,
|
||||
Pressed =1
|
||||
}
|
||||
|
||||
pub type CmdType = std::mem::Discriminant<Commands>;
|
||||
|
||||
@ -10,41 +10,65 @@ use crate::motor_driver::Commands as MotorCommands;
|
||||
use crate::motor_driver::SendQ as MotorSendQ;
|
||||
use crate::dispatch::{Dispatch, RecvQ, SendQ};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum ControllerStates {
|
||||
Stopped,
|
||||
Stopping,
|
||||
GoingUp,
|
||||
AutoUp,
|
||||
GoingDown,
|
||||
AutoDown,
|
||||
//TODO: AutoUp and AutoDown
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum AutoMode {
|
||||
Disallowed = 0,
|
||||
Allowed = 1,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum LimitState {
|
||||
NoLimitsHit,
|
||||
UpperHit,
|
||||
LowerHit,
|
||||
BothHit,
|
||||
}
|
||||
|
||||
pub struct Controller {
|
||||
state: ControllerStates,
|
||||
recv: RecvQ,
|
||||
send: SendQ,
|
||||
motor_q: MotorSendQ
|
||||
motor_q: MotorSendQ,
|
||||
auto_mode: AutoMode,
|
||||
limit_state: LimitState,
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
pub fn new(recv: RecvQ, send: SendQ, motor_q: MotorSendQ) -> Controller {
|
||||
pub fn new(recv: RecvQ, send: SendQ, motor_q: MotorSendQ) -> Self {
|
||||
Controller {
|
||||
state: ControllerStates::Stopping,
|
||||
recv: recv,
|
||||
send: send,
|
||||
motor_q: motor_q}
|
||||
motor_q: motor_q,
|
||||
auto_mode: AutoMode::Disallowed, // Use safe default
|
||||
limit_state: LimitState::BothHit, // Use safe default
|
||||
}
|
||||
}
|
||||
|
||||
/// Tell the message dispatch which messages we are interested in receiving, and get
|
||||
/// a callback channel that receives those messages.
|
||||
pub fn prepare_controller(dp: &mut Dispatch) -> RecvQ {
|
||||
let cmds = vec![
|
||||
Commands::PicRecvUp,
|
||||
Commands::PicRecvDown,
|
||||
Commands::PicRecvStop,
|
||||
Commands::PicRecvUp{data: 0},
|
||||
Commands::PicRecvDown{data: 0},
|
||||
Commands::PicRecvStop{data: 0},
|
||||
Commands::BluetoothUp{data: 0},
|
||||
Commands::BluetoothDown{data: 0},
|
||||
Commands::BluetoothStop{_data: 0},
|
||||
Commands::BluetoothStop{data: 0},
|
||||
Commands::PicRecvLimitUp{data: 0},
|
||||
Commands::PicRecvLimitDown{data: 0},
|
||||
Commands::PicRecvAutoMode{data: 0},
|
||||
Commands::StopTimerExpired,
|
||||
Commands::ButtonTimerExpired,
|
||||
];
|
||||
@ -55,34 +79,31 @@ impl Controller {
|
||||
match new_s {
|
||||
ControllerStates::Stopped => {
|
||||
// Other notify commands are sent directly from the motor controller
|
||||
self.send.send(Commands::NotifyMotorStop{data: Button::RELEASED}).await?;
|
||||
self.send.send(Commands::NotifyMotorStop{data: Button::Released}).await?;
|
||||
}
|
||||
ControllerStates::Stopping => {
|
||||
self.send.send(Commands::StopTimerRestart).await?;
|
||||
self.motor_q.send(MotorCommands::Stop).await?;
|
||||
self.send.send(Commands::NotifyMotorStop{data: Button::PRESSED}).await?;
|
||||
self.send.send(Commands::NotifyMotorStop{data: Button::Pressed}).await?;
|
||||
}
|
||||
ControllerStates::GoingUp => {
|
||||
self.send.send(Commands::ButtonTimerRestart).await?;
|
||||
self.motor_q.send(MotorCommands::StartUp).await?;
|
||||
self.send.send(Commands::NotifyMotorUp{data: Button::PRESSED}).await?;
|
||||
self.send.send(Commands::NotifyMotorUp{data: Button::Pressed}).await?;
|
||||
}
|
||||
ControllerStates::AutoUp => {
|
||||
self.motor_q.send(MotorCommands::StartUp).await?;
|
||||
self.send.send(Commands::NotifyMotorUp{data: Button::Pressed}).await?;
|
||||
}
|
||||
ControllerStates::GoingDown => {
|
||||
self.send.send(Commands::ButtonTimerRestart).await?;
|
||||
self.motor_q.send(MotorCommands::StartDown).await?;
|
||||
self.send.send(Commands::NotifyMotorUp{data: Button::PRESSED}).await?;
|
||||
self.send.send(Commands::NotifyMotorUp{data: Button::Pressed}).await?;
|
||||
}
|
||||
ControllerStates::AutoDown => {
|
||||
self.motor_q.send(MotorCommands::StartDown).await?;
|
||||
self.send.send(Commands::NotifyMotorUp{data: Button::Pressed}).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Maintain the current state - e.g. reset button timers, etc.
|
||||
async fn maintain_state(&mut self) -> Result<()> {
|
||||
match self.state {
|
||||
ControllerStates::Stopped => {}
|
||||
ControllerStates::Stopping => {} // Do not reset this timer; let it expire.
|
||||
ControllerStates::GoingUp => {self.send.send(Commands::ButtonTimerRestart).await?;}
|
||||
ControllerStates::GoingDown => {self.send.send(Commands::ButtonTimerRestart).await?;}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -95,80 +116,169 @@ impl Controller {
|
||||
}
|
||||
ControllerStates::GoingUp => {
|
||||
self.send.send(Commands::ButtonTimerClear).await?;
|
||||
self.send.send(Commands::NotifyMotorUp{data: Button::RELEASED}).await?;
|
||||
self.send.send(Commands::NotifyMotorUp{data: Button::Released}).await?;
|
||||
}
|
||||
ControllerStates::AutoUp => {
|
||||
self.send.send(Commands::NotifyMotorUp{data: Button::Released}).await?;
|
||||
}
|
||||
ControllerStates::GoingDown => {
|
||||
self.send.send(Commands::ButtonTimerClear).await?;
|
||||
self.send.send(Commands::NotifyMotorDown{data: Button::RELEASED}).await?;
|
||||
self.send.send(Commands::NotifyMotorDown{data: Button::Released}).await?;
|
||||
}
|
||||
ControllerStates::AutoDown => {
|
||||
self.send.send(Commands::NotifyMotorDown{data: Button::Released}).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Determines the state the controller should be in based on the command received.
|
||||
fn handle_cmd(&mut self, cmd: &Commands) -> ControllerStates {
|
||||
match self.state {
|
||||
|
||||
ControllerStates::Stopped => {
|
||||
match cmd {
|
||||
Commands::PicRecvUp => {return ControllerStates::GoingUp}
|
||||
Commands::PicRecvDown => {return ControllerStates::GoingDown}
|
||||
Commands::BluetoothUp { data } => {
|
||||
match data {
|
||||
0 => {}
|
||||
1 => {return ControllerStates::GoingUp}
|
||||
_ => {error!("Invalid data sent via BluetoothUp: {}", data); return ControllerStates::Stopping}
|
||||
}
|
||||
}
|
||||
Commands::BluetoothDown { data } => {
|
||||
match data {
|
||||
0 => {}
|
||||
1 => {return ControllerStates::GoingDown}
|
||||
_ => {error!("Invalid data sent via BluetoothDown: {}", data); return ControllerStates::Stopping}
|
||||
}
|
||||
}
|
||||
_ => {} // Ignore other commands when stopped.
|
||||
}
|
||||
}
|
||||
|
||||
ControllerStates::Stopping => {
|
||||
match cmd {
|
||||
Commands::StopTimerExpired => {return ControllerStates::Stopped}
|
||||
_ => {} // Ignore other commands when stopping
|
||||
}
|
||||
}
|
||||
|
||||
ControllerStates::GoingUp => {
|
||||
match cmd {
|
||||
Commands::PicRecvUp => {return self.state.clone()}
|
||||
Commands::BluetoothUp { data } => {
|
||||
match data {
|
||||
0 => {} // Not an error, but transition to stop
|
||||
1 => {return self.state.clone()}
|
||||
_ => {error!("Invalid data sent via BluetoothUp: {}", data)}
|
||||
}
|
||||
}
|
||||
_ => {} // fall through to GoingUp default of stopping
|
||||
}
|
||||
return ControllerStates::Stopping; // Almost every command will be interpreted as stop
|
||||
}
|
||||
|
||||
ControllerStates::GoingDown => {
|
||||
match cmd {
|
||||
Commands::PicRecvDown => {return self.state.clone()}
|
||||
Commands::BluetoothDown { data } => {
|
||||
match data {
|
||||
0 => {} // Not an error, but transition to stop
|
||||
1 => {return self.state.clone()}
|
||||
_ => {error!("Invalid data sent via BluetoothDown: {}", data)}
|
||||
}
|
||||
}
|
||||
_ => {} // fall through to GoingDown default of stopping
|
||||
}
|
||||
return ControllerStates::Stopping
|
||||
fn change_state_if_released(&self, data: &u8, new_state: ControllerStates) -> ControllerStates {
|
||||
match data {
|
||||
0 => {new_state}
|
||||
1 => {self.state.clone()}
|
||||
_ => {
|
||||
warn!("Data that was not a 0 or 1 received {}", data); //TODO: internal error
|
||||
self.state.clone()
|
||||
}
|
||||
}
|
||||
self.state.clone() //Don't transition states by default.
|
||||
}
|
||||
|
||||
fn change_state_if_pressed(&self, data: &u8, new_state: ControllerStates) -> ControllerStates {
|
||||
match data {
|
||||
0 => {self.state.clone()}
|
||||
1 => {new_state}
|
||||
_ => {
|
||||
warn!("Data that was not a 0 or 1 received {}", data);
|
||||
self.state.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines the state the controller should be in based on the command received.
|
||||
async fn handle_cmd(&mut self, cmd: &Commands) -> ControllerStates {
|
||||
match cmd {
|
||||
Commands::PicRecvUp { data } | Commands::BluetoothUp { data }=> {
|
||||
match self.state {
|
||||
ControllerStates::Stopped => {return self.change_state_if_pressed(data, self.remote_up_or_auto_up())}
|
||||
ControllerStates::Stopping => {}
|
||||
ControllerStates::GoingUp => {
|
||||
self.send.send(Commands::ButtonTimerRestart).await.expect("Failed to necessary timer");
|
||||
return self.change_state_if_released(data, ControllerStates::Stopping)
|
||||
}
|
||||
ControllerStates::AutoUp => {} // Don't stop auto on button release
|
||||
ControllerStates::GoingDown |
|
||||
ControllerStates::AutoDown => {
|
||||
return self.change_state_if_pressed(data, ControllerStates::Stopping)
|
||||
}
|
||||
}
|
||||
}
|
||||
Commands::PicRecvDown { data } | Commands::BluetoothDown { data } => {
|
||||
match self.state {
|
||||
ControllerStates::Stopped => {return self.change_state_if_pressed(data, self.remote_down_or_auto_down())}
|
||||
ControllerStates::Stopping => {}
|
||||
ControllerStates::GoingUp |
|
||||
ControllerStates::AutoUp => {
|
||||
return self.change_state_if_pressed(data, ControllerStates::Stopping)
|
||||
}
|
||||
ControllerStates::GoingDown => {
|
||||
self.send.send(Commands::ButtonTimerRestart).await.expect("Failed to necessary timer");
|
||||
return self.change_state_if_released(data, ControllerStates::Stopping)
|
||||
}
|
||||
ControllerStates::AutoDown => {}
|
||||
}
|
||||
}
|
||||
Commands::PicRecvStop { data } | Commands::BluetoothStop { data } => {
|
||||
match self.state {
|
||||
ControllerStates::Stopped => {}
|
||||
ControllerStates::Stopping => {}
|
||||
ControllerStates::GoingUp |
|
||||
ControllerStates::AutoUp |
|
||||
ControllerStates::GoingDown |
|
||||
ControllerStates::AutoDown => {
|
||||
return self.change_state_if_pressed(data, ControllerStates::Stopping)
|
||||
}
|
||||
}
|
||||
}
|
||||
Commands::PicRecvLimitUp { data } => {
|
||||
self.adjust_limit(LimitState::UpperHit, data);
|
||||
match self.state {
|
||||
ControllerStates::Stopped => {} // Ignore; this could just be our initial notification on startup.
|
||||
ControllerStates::Stopping => {}
|
||||
ControllerStates::GoingUp |
|
||||
ControllerStates::AutoUp=> {
|
||||
released_warning(data, "Limit switches may be installed incorrectly!");
|
||||
return self.change_state_if_pressed(data, ControllerStates::Stopping)
|
||||
}
|
||||
ControllerStates::GoingDown |
|
||||
ControllerStates::AutoDown=> {
|
||||
pressed_warning(data, "Limit switches may be installed incorrectly!");
|
||||
// Stop out of an abundance of caution. We should not get a limit press, even if it's the wrong one.
|
||||
return self.change_state_if_pressed(data, ControllerStates::Stopping)
|
||||
}
|
||||
}
|
||||
}
|
||||
Commands::PicRecvLimitDown { data } => {
|
||||
self.adjust_limit(LimitState::LowerHit, data);
|
||||
match self.state {
|
||||
ControllerStates::Stopped => {} // Ignore; this could just be our initial notification on startup.
|
||||
ControllerStates::Stopping => {}
|
||||
ControllerStates::GoingUp |
|
||||
ControllerStates::AutoUp => {
|
||||
pressed_warning(data, "Limit switches may be installed incorrectly!");
|
||||
// Stop out of an abundance of caution. We should not get a limit press, even if it's the wrong one.
|
||||
return self.change_state_if_pressed(data, ControllerStates::Stopping)
|
||||
}
|
||||
ControllerStates::GoingDown |
|
||||
ControllerStates::AutoDown => {
|
||||
released_warning(data, "Limit switches may be installed incorrectly!");
|
||||
return self.change_state_if_pressed(data, ControllerStates::Stopping)
|
||||
}
|
||||
}
|
||||
}
|
||||
Commands::PicRecvAutoMode { data } => {self.set_auto(data);}
|
||||
Commands::StopTimerExpired => {
|
||||
match self.state {
|
||||
ControllerStates::Stopped => {}
|
||||
ControllerStates::Stopping => {return ControllerStates::Stopped}
|
||||
ControllerStates::GoingUp |
|
||||
ControllerStates::AutoUp |
|
||||
ControllerStates::GoingDown |
|
||||
ControllerStates::AutoDown => {
|
||||
warn!("Stop timer returned in an inappropriate state! {:?}", self.state)
|
||||
}
|
||||
}
|
||||
}
|
||||
Commands::ButtonTimerExpired => {
|
||||
match self.state {
|
||||
ControllerStates::Stopped => {}
|
||||
ControllerStates::Stopping => {}
|
||||
ControllerStates::GoingUp => {return ControllerStates::Stopping}
|
||||
ControllerStates::AutoUp => {}
|
||||
ControllerStates::GoingDown => {return ControllerStates::Stopping}
|
||||
ControllerStates::AutoDown => {}
|
||||
}
|
||||
}
|
||||
// Commands we should ignore (not using _ because we want to ensure all commands are accounted for!)
|
||||
Commands::StopTimerRestart |
|
||||
Commands::StopTimerClear |
|
||||
Commands::ButtonTimerRestart |
|
||||
Commands::ButtonTimerClear |
|
||||
Commands::PairTimerClear |
|
||||
Commands::PairTimerExpired |
|
||||
Commands::AllowPairing |
|
||||
Commands::EraseBleBonds => {
|
||||
warn!("Unexpected command received by motor controller") // TODO: internal "us" error.
|
||||
}
|
||||
Commands::NotifyMotorDown { data } |
|
||||
Commands::NotifyMotorStop { data } |
|
||||
Commands::NotifyMotorUp { data } => {
|
||||
warn!("Unexpected command received by motor controller {:?}", data) // TODO: internal "us" error.
|
||||
}
|
||||
Commands::BluetoothName { data } => {
|
||||
warn!("Unexpected command received by motor controller {:?}", data) // TODO: internal "us" error.
|
||||
}
|
||||
}
|
||||
self.state.clone() // Don't transition by default
|
||||
}
|
||||
|
||||
async fn transition_state(&mut self, old_s: &ControllerStates, new_s: &ControllerStates) -> Result<()> {
|
||||
@ -176,9 +286,6 @@ impl Controller {
|
||||
self.exit_state(&old_s).await?;
|
||||
self.enter_state(&new_s).await?;
|
||||
}
|
||||
else {
|
||||
self.maintain_state().await?;
|
||||
}
|
||||
self.state = new_s.clone();
|
||||
Ok(())
|
||||
}
|
||||
@ -191,10 +298,150 @@ impl Controller {
|
||||
loop {
|
||||
let cmd = self.recv.recv().await.expect("Motor controller command queue unexpectedly failed");
|
||||
trace!("Got command {:?}",cmd);
|
||||
let new_s = self.handle_cmd(&cmd);
|
||||
let new_s = self.handle_cmd(&cmd).await;
|
||||
trace!("State current {:?} new {:?}", self.state, new_s);
|
||||
self.transition_state(&self.state.clone(), &new_s).await.expect("Unexpected state change failure in motor controller");
|
||||
}
|
||||
}
|
||||
|
||||
fn remote_up_or_auto_up(&self) -> ControllerStates {
|
||||
self.up_or_auto_up(ControllerStates::GoingUp)
|
||||
}
|
||||
|
||||
fn up_or_auto_up(&self, up: ControllerStates) -> ControllerStates {
|
||||
// Assume that checking the limit against the direction has already been performed.
|
||||
match self.auto_mode {
|
||||
AutoMode::Disallowed => {
|
||||
up // TODO: this allows manual buttons to override limits as long as "auto" mode is off.
|
||||
}
|
||||
AutoMode::Allowed => {
|
||||
match self.limit_state {
|
||||
LimitState::NoLimitsHit | LimitState::LowerHit=> {
|
||||
ControllerStates::AutoUp
|
||||
}
|
||||
LimitState::UpperHit => {
|
||||
ControllerStates::Stopping // Failsafe as we are already at our upper limit. TODO: maybe should be manual up?
|
||||
}
|
||||
LimitState::BothHit => {
|
||||
up
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remote_down_or_auto_down(&self) -> ControllerStates {
|
||||
self.down_or_auto_down(ControllerStates::GoingDown)
|
||||
}
|
||||
|
||||
fn down_or_auto_down(&self, down: ControllerStates) -> ControllerStates {
|
||||
// Assume that checking the limit against the direction has already been performed.
|
||||
match self.auto_mode {
|
||||
AutoMode::Disallowed => {
|
||||
down // TODO: this allows manual buttons to override limits as long as "auto" mode is off.
|
||||
}
|
||||
AutoMode::Allowed => {
|
||||
match self.limit_state {
|
||||
LimitState::NoLimitsHit | LimitState::LowerHit=> {
|
||||
ControllerStates::AutoDown
|
||||
}
|
||||
LimitState::UpperHit => {
|
||||
ControllerStates::Stopping // Failsafe as we are already at our upper limit. TODO: Maybe should be manual down?
|
||||
}
|
||||
LimitState::BothHit => {
|
||||
down
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_auto(&mut self, data: &u8) {
|
||||
match data {
|
||||
0 => {
|
||||
self.auto_mode = AutoMode::Disallowed;
|
||||
}
|
||||
1 => {
|
||||
if self.limit_state == LimitState::BothHit {
|
||||
warn!("Limit switches not detected. Aborting auto mode.");
|
||||
} else {
|
||||
self.auto_mode = AutoMode::Allowed;
|
||||
}
|
||||
}
|
||||
_ => {} // TODO: we should warn, but we're going to fix these types next.
|
||||
}
|
||||
}
|
||||
|
||||
fn adjust_limit(&mut self, limit: LimitState, pressed: &u8) {
|
||||
match pressed {
|
||||
0 => {
|
||||
match limit {
|
||||
LimitState::NoLimitsHit => {
|
||||
unreachable!("There is no way to press NoLimits")
|
||||
}
|
||||
LimitState::LowerHit => {
|
||||
match self.limit_state {
|
||||
LimitState::NoLimitsHit |
|
||||
LimitState::UpperHit => {warn!("removed limit we never hit {:?}", LimitState::LowerHit);} //TODO intenral error
|
||||
LimitState::LowerHit => {self.limit_state = LimitState::NoLimitsHit;}
|
||||
LimitState::BothHit => {self.limit_state = LimitState::UpperHit;}
|
||||
}
|
||||
}
|
||||
LimitState::UpperHit => {
|
||||
match self.limit_state {
|
||||
LimitState::NoLimitsHit |
|
||||
LimitState::LowerHit => {warn!("removed limit we never hit {:?}", LimitState::UpperHit);} //TODO intenral error
|
||||
LimitState::UpperHit => {self.limit_state = LimitState::NoLimitsHit;}
|
||||
LimitState::BothHit => {self.limit_state = LimitState::LowerHit;}
|
||||
}
|
||||
}
|
||||
LimitState::BothHit => {
|
||||
unreachable!("There is no way to press BothHit")
|
||||
}
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
match limit {
|
||||
LimitState::NoLimitsHit => {
|
||||
unreachable!("There is no way to press BothHit")
|
||||
}
|
||||
LimitState::LowerHit => {
|
||||
match self.limit_state {
|
||||
LimitState::NoLimitsHit => {self.limit_state = LimitState::LowerHit;}
|
||||
LimitState::LowerHit => {}
|
||||
LimitState::UpperHit => {self.limit_state = LimitState::BothHit;}
|
||||
LimitState::BothHit => {}
|
||||
}
|
||||
}
|
||||
LimitState::UpperHit => {
|
||||
match self.limit_state {
|
||||
LimitState::NoLimitsHit => {self.limit_state = LimitState::UpperHit;}
|
||||
LimitState::LowerHit => {self.limit_state = LimitState::BothHit;}
|
||||
LimitState::UpperHit => {}
|
||||
LimitState::BothHit => {}
|
||||
}
|
||||
}
|
||||
LimitState::BothHit => {
|
||||
unreachable!("There is no way to press BothHit")
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {} //TODO should warn but this will go away with real pressed types
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn pressed_warning(data: &u8, warn: &str) {
|
||||
match data {
|
||||
1 => {warn!("{}", warn);} // TODO: user warning, not intenral
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn released_warning(data: &u8, warn: &str) {
|
||||
match data {
|
||||
0 => {warn!("{}", warn);} // TODO: user warning, not intenral
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@ -15,7 +15,7 @@ pub type SendQ = Sender<Commands>;
|
||||
pub type RecvQ = Receiver<Commands>;
|
||||
|
||||
pub struct MotorDriverDebug{
|
||||
endpoint: SendQ,
|
||||
endpoint: SendQ, // Endpoint to hand to dispatch or anyone else sending commands here.
|
||||
recv_q: RecvQ,
|
||||
}
|
||||
|
||||
|
||||
@ -29,15 +29,24 @@ use async_channel::Sender;
|
||||
use log::*; //{trace, debug, info, warn, error}
|
||||
|
||||
#[derive(Command)]
|
||||
pub enum Menu<'a> {
|
||||
pub enum Menu{//<'a> {
|
||||
/// Simulate the PIC controller sending aus n Up character
|
||||
PicRecvUp,
|
||||
PicRecvUp {
|
||||
/// 0 for not pressed, 1 for pressed
|
||||
data: u8,
|
||||
},
|
||||
|
||||
/// Simulate the PIC controller sending us a Down character
|
||||
PicRecvDown,
|
||||
PicRecvDown {
|
||||
/// 0 for not pressed, 1 for pressed
|
||||
data: u8,
|
||||
},
|
||||
|
||||
/// Simulate the PIC controller sending us a Stop character
|
||||
PicRecvStop,
|
||||
PicRecvStop {
|
||||
/// 0 for not pressed, 1 for pressed
|
||||
data: u8,
|
||||
},
|
||||
|
||||
/// Simulate the PIC controller sending a "pair" button press
|
||||
PicRecvPair,
|
||||
@ -73,9 +82,9 @@ pub enum Menu<'a> {
|
||||
BluetoothBottomLimit { data: u8 },
|
||||
|
||||
/// Send a bluetooth characteristic: Wifi SSID
|
||||
BluetoothWifiSsid { ssid: &'a str },
|
||||
//BluetoothWifiSsid { ssid: &'a str },
|
||||
/// Send a bluetooth characteristic: Wifi Password
|
||||
BluetoothWifiPassword { wifipass: &'a str },
|
||||
//BluetoothWifiPassword { wifipass: &'a str },
|
||||
|
||||
/// Change log level (None: 0, .. Tracing: 5)
|
||||
Log { level: u8 },
|
||||
@ -86,27 +95,30 @@ pub enum Menu<'a> {
|
||||
/// Erase BLE Bonding information
|
||||
ClearBleBonds,
|
||||
|
||||
/// Whatever misc. output I need
|
||||
Misc,
|
||||
|
||||
}
|
||||
|
||||
pub fn process_menu(
|
||||
cli: &mut CliHandle<'_, SimpleWriter, Infallible>,
|
||||
command: Menu<'_>,
|
||||
command: Menu,//<'_>,
|
||||
dispatch: &Sender<Commands>,
|
||||
) -> Result<(), Infallible> {
|
||||
match command {
|
||||
// We ignore sending errors throughout because the Cli interface is only for
|
||||
// testing and debugging.
|
||||
Menu::PicRecvUp => {
|
||||
Menu::PicRecvUp {data} => {
|
||||
cli.writer().write_str("Sending PicButtonUp Received command")?;
|
||||
let _ = dispatch.send_blocking(Commands::PicRecvUp);
|
||||
let _ = dispatch.send_blocking(Commands::PicRecvUp{data});
|
||||
}
|
||||
Menu::PicRecvDown => {
|
||||
Menu::PicRecvDown{data} => {
|
||||
cli.writer().write_str("Sending PicButtonDown command")?;
|
||||
let _ = dispatch.send_blocking(Commands::PicRecvDown);
|
||||
let _ = dispatch.send_blocking(Commands::PicRecvDown{data});
|
||||
}
|
||||
Menu::PicRecvStop => {
|
||||
Menu::PicRecvStop{data} => {
|
||||
cli.writer().write_str("Sending PicButtonStop command")?;
|
||||
let _ = dispatch.send_blocking(Commands::PicRecvStop);
|
||||
let _ = dispatch.send_blocking(Commands::PicRecvStop{data});
|
||||
}
|
||||
Menu::PicRecvPair => {
|
||||
cli.writer().write_str("Sending PIC command and timer reset (the job of the pair button driver, once PIC interface exists)")?; //TODO: PIC get this.
|
||||
@ -125,7 +137,7 @@ pub fn process_menu(
|
||||
Menu::BluetoothStop { data } => {
|
||||
cli.writer()
|
||||
.write_str("SendingBluetoothStop")?;
|
||||
let _ = dispatch.send_blocking(Commands::BluetoothStop { _data: data });
|
||||
let _ = dispatch.send_blocking(Commands::BluetoothStop { data: data });
|
||||
}
|
||||
Menu::BluetoothLearn { data } => {
|
||||
cli.writer()
|
||||
@ -147,7 +159,7 @@ pub fn process_menu(
|
||||
.write_str("TODO: simulate bluetooth characteristic change")?;
|
||||
let _ = data;
|
||||
}
|
||||
Menu::BluetoothWifiSsid { ssid } => {
|
||||
/*Menu::BluetoothWifiSsid { ssid } => {
|
||||
cli.writer()
|
||||
.write_str("TODO: simulate bluetooth characteristic change")?;
|
||||
let _ = ssid;
|
||||
@ -156,7 +168,7 @@ pub fn process_menu(
|
||||
cli.writer()
|
||||
.write_str("TODO: simulate bluetooth characteristic change {}")?;
|
||||
let _ = wifipass;
|
||||
}
|
||||
}*/
|
||||
Menu::Log { level } => {
|
||||
match level {
|
||||
0 => {
|
||||
@ -194,6 +206,9 @@ pub fn process_menu(
|
||||
Menu::ClearBleBonds => {
|
||||
let _ = dispatch.send_blocking(Commands::EraseBleBonds);
|
||||
}
|
||||
Menu::Misc => {
|
||||
println!("string {}, arc string {}, enum value {}, discriminant {}", std::mem::size_of::<String>(), std::mem::size_of::<std::sync::Arc<String>>(), std::mem::size_of::<Menu>(), std::mem::size_of::<std::mem::Discriminant<Menu>>());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user