split into hardware dependent / independent
This commit is contained in:
4
gem-remotes-esp32/.gitignore
vendored
4
gem-remotes-esp32/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
/.vscode
|
||||
/.embuild
|
||||
/target
|
||||
/Cargo.lock
|
||||
@ -28,6 +28,7 @@ experimental = ["esp-idf-svc/experimental"]
|
||||
embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-svc/embassy-time-driver"]
|
||||
|
||||
[dependencies]
|
||||
gem-remotes-lib = { path = "../gem-remotes-lib" }
|
||||
log = { version = "0.4", default-features = false, features = ["max_level_trace"] }
|
||||
esp-idf-svc = { version = "0.49", default-features = false }
|
||||
esp32-nimble = "0.7.0"
|
||||
|
||||
@ -8,8 +8,13 @@ use bitflags::bitflags;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::dispatch::{Dispatch, RecvQ, SendQ};
|
||||
use crate::commands::{Button, Commands};
|
||||
use gem_remotes_lib::{
|
||||
Dispatch,
|
||||
DispatchRecvQ,
|
||||
DispatchSendQ,
|
||||
Commands,
|
||||
Button,
|
||||
};
|
||||
|
||||
// TODO HARDWARE: test these values to see if they are suitable
|
||||
const BLE_MIN_INTERVAL: u16 = 24; // x 1.25ms
|
||||
@ -41,8 +46,8 @@ const BLE_BUTTON_RELEASE: u8 = 0;
|
||||
const BLE_BUTTON_PRESS: u8 = 1;
|
||||
|
||||
pub struct BleServer {
|
||||
send_q: SendQ,
|
||||
recv_q: RecvQ,
|
||||
send_q: DispatchSendQ,
|
||||
recv_q: DispatchRecvQ,
|
||||
svc_flags: SvcFlags,
|
||||
dev_name: String,
|
||||
|
||||
@ -205,7 +210,7 @@ impl BleServer {
|
||||
|
||||
}
|
||||
|
||||
fn on_bluetooth_cmd(sender: &SendQ, args: &mut OnWriteArgs, cmd: Commands) {
|
||||
fn on_bluetooth_cmd(sender: &DispatchSendQ, args: &mut OnWriteArgs, cmd: Commands) {
|
||||
let v = args.recv_data();
|
||||
// receiving incorrect data isn't fatal, but being unable to send events is.
|
||||
let attempt = match cmd {
|
||||
@ -264,7 +269,7 @@ fn set_device_security(dev: &mut BLEDevice) {
|
||||
.resolve_rpa();
|
||||
}
|
||||
|
||||
fn set_server_callbacks(server: &mut BLEServer, sender: SendQ) {
|
||||
fn set_server_callbacks(server: &mut BLEServer, sender: DispatchSendQ) {
|
||||
server.on_connect(move |server, clntdesc| {
|
||||
// Print connected client data
|
||||
info!("client connected: {:?}", clntdesc);
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
/// An enum of all commands passed into and out of the microcontroller
|
||||
|
||||
use strum_macros::EnumCount as EnumCountMacro;
|
||||
use std::mem::discriminant;
|
||||
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
|
||||
// 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: Button},
|
||||
PicRecvDown {data: Button},
|
||||
PicRecvStop {data: Button},
|
||||
PicRecvLimitUp {data: Button}, // 0 for not hit, 1 for hit
|
||||
PicRecvLimitDown {data: Button}, // 0 for not hit, 1 for hit
|
||||
PicRecvAutoMode {data: Button}, // 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: Button}, //TODO change these to real button states and change them on input.
|
||||
BluetoothDown {data: Button},
|
||||
BluetoothStop {data: Button}, // 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, // 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: Button},
|
||||
NotifyMotorDown {data: Button},
|
||||
NotifyMotorStop {data: Button},
|
||||
|
||||
EraseBleBonds,
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
/*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>;
|
||||
|
||||
/// Consider commands equal if they have the same command type, but different values
|
||||
impl PartialEq for Commands {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
discriminant(self) == discriminant(other)
|
||||
}
|
||||
}
|
||||
@ -1,75 +0,0 @@
|
||||
|
||||
use std::mem::discriminant;
|
||||
use std::collections::HashMap;
|
||||
use std::vec::Vec;
|
||||
use async_channel::{unbounded, Receiver, Sender};
|
||||
use strum::EnumCount;
|
||||
|
||||
use crate::commands::{Commands, CmdType};
|
||||
use log::*; //{trace, debug, info, warn, error}
|
||||
|
||||
pub type SendQ = Sender<Commands>;
|
||||
pub type RecvQ = Receiver<Commands>;
|
||||
|
||||
//TODO: Making this generic over a <C> for commands would make it a useful small event handler.
|
||||
|
||||
pub struct Dispatch {
|
||||
callbacks: HashMap<CmdType, Vec<SendQ> >,
|
||||
recv: RecvQ, // Channel to listen to incomming commands
|
||||
endpoint: SendQ, // Endpoint to clone to hand to other modules, so that they can send commands
|
||||
}
|
||||
|
||||
impl Dispatch {
|
||||
pub fn new() -> Dispatch {
|
||||
let (s, r) = unbounded(); //This should always be unbounded, because some callbacks have to send_blocking to it, and if the thread blocks, other tasks can't empty the queue!
|
||||
let mut hmap = HashMap::new();
|
||||
hmap.reserve(Commands::COUNT);
|
||||
Dispatch { callbacks: hmap, recv: r, endpoint: s}
|
||||
}
|
||||
|
||||
/// Get a channel receiver that will get callbacks for all commands in the listen_for vec.
|
||||
pub fn get_callback_channel(&mut self, listen_for: &Vec<Commands>) -> RecvQ {
|
||||
let (send, rec) = unbounded(); // TODO: these could be bounded instead, as these calls are all non-blocking.
|
||||
for cmd in listen_for {
|
||||
let callback_list = self.callbacks.get_mut(&discriminant(&cmd));
|
||||
match callback_list {
|
||||
Some(callback) => {
|
||||
callback.push(send.clone());
|
||||
trace!("Adding {:?} to callbacks", cmd);
|
||||
}
|
||||
None => {
|
||||
let mut callback = Vec::new();
|
||||
callback.push(send.clone());
|
||||
self.callbacks.insert(discriminant(&cmd), callback);
|
||||
trace!("Created {:?} callback", cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
rec
|
||||
}
|
||||
|
||||
/// Get a channel sender that will send commands to this dispatcher
|
||||
pub fn get_cmd_channel(&self) -> SendQ {
|
||||
self.endpoint.clone()
|
||||
}
|
||||
|
||||
/// Wait on incomming commands and dispatch them
|
||||
pub async fn cmd_loop(&self) -> anyhow::Result<()> {
|
||||
loop {
|
||||
debug!("Dispatch waiting on commands");
|
||||
let cmd = self.recv.recv().await.expect("Incoming event queue failed unexpectedly");
|
||||
debug!("Dispatch got command {:?}", cmd);
|
||||
let cmd_type = discriminant(&cmd);
|
||||
let found_listeners = self.callbacks.get(&cmd_type);
|
||||
match found_listeners {
|
||||
Some(listeners) => {
|
||||
for listener in listeners {
|
||||
trace!("Sending cmd {:?}", cmd);
|
||||
listener.send(cmd.clone()).await.expect("Outgoing event queue failed unexpectedly");
|
||||
}
|
||||
}
|
||||
None => {debug!("Dispatch found no listeners for a command")}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
const BUTTON_HOLD_TIME_MS: u64 = 500;
|
||||
const STOP_SAFETY_TIME_MS: u64 = 2000;
|
||||
const PAIR_TIME_MS: u64 = 10000; //180000; TODO:Debug go back to 3 minutes from 10s
|
||||
const BUTTON_HOLD_TIME_MS: u64 = 1_200;
|
||||
const STOP_SAFETY_TIME_MS: u64 = 2_000;
|
||||
const PAIR_TIME_MS: u64 = 30_000;
|
||||
|
||||
// Crates used in release
|
||||
use log::*; //{trace, debug, info, warn, error}
|
||||
@ -10,20 +10,23 @@ use futures_lite::future;
|
||||
use std::panic;
|
||||
use std::ops::Deref;
|
||||
|
||||
use gem_remotes_lib::{
|
||||
Commands,
|
||||
Controller,
|
||||
Dispatch,
|
||||
};
|
||||
|
||||
// Debug modules
|
||||
mod test_console;
|
||||
|
||||
// Release modules
|
||||
mod commands;
|
||||
mod dispatch;
|
||||
mod motor_controller;
|
||||
mod motor_driver;
|
||||
mod message_timer;
|
||||
mod ble_server;
|
||||
mod pair_button_driver;
|
||||
|
||||
use crate::message_timer::MessageTimer;
|
||||
use crate::commands::Commands;
|
||||
//use crate::commands::Commands;
|
||||
|
||||
//TODO: limit switch driver, would be good in long run if it checked limit switches periodically (every 1s?) to ensure they are still functioning
|
||||
|
||||
@ -67,14 +70,14 @@ async fn main_loop() -> Result<()> {
|
||||
info!("Entering main loop");
|
||||
|
||||
// Create dispatch early so it can outlive most other things
|
||||
let mut dp = dispatch::Dispatch::new();
|
||||
let mut dp = Dispatch::new();
|
||||
|
||||
// Debug Drivers (TODO DEBUG: remove debug)
|
||||
let motor_driver = motor_driver::MotorDriverDebug::new();
|
||||
|
||||
// Setup of various drivers that need to out-live the executor
|
||||
let m_chan = motor_controller::Controller::prepare_controller(&mut dp);
|
||||
let mut motor_control = motor_controller::Controller::new(m_chan, dp.get_cmd_channel(), motor_driver.get_endpoint());
|
||||
let m_chan = Controller::prepare_controller(&mut dp);
|
||||
let mut motor_control = Controller::new(m_chan, dp.get_cmd_channel(), motor_driver.get_endpoint());
|
||||
// Setup callback timers
|
||||
let mut button_timer = MessageTimer::<Commands, Commands>::new_on_dispatch(
|
||||
Commands::ButtonTimerRestart,
|
||||
|
||||
@ -6,8 +6,10 @@ use core::time::Duration;
|
||||
use async_channel::{Receiver, Sender};
|
||||
use futures_lite::FutureExt;
|
||||
|
||||
use crate::dispatch::Dispatch;
|
||||
use crate::commands::Commands;
|
||||
use gem_remotes_lib::{
|
||||
Commands,
|
||||
Dispatch
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum State {
|
||||
|
||||
@ -1,437 +0,0 @@
|
||||
/// State machine of the motor's current state, that takes as inputs
|
||||
/// command messages.
|
||||
|
||||
use log::*; //{trace, debug, info, warn, error}
|
||||
//use async_channel::Receiver;
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::commands::{Commands, Button};
|
||||
use crate::motor_driver::Commands as MotorCommands;
|
||||
use crate::motor_driver::SendQ as MotorSendQ;
|
||||
use crate::dispatch::{Dispatch, RecvQ, SendQ};
|
||||
|
||||
#[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,
|
||||
auto_mode: AutoMode,
|
||||
limit_state: LimitState,
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
pub fn new(recv: RecvQ, send: SendQ, motor_q: MotorSendQ) -> Self {
|
||||
Controller {
|
||||
state: ControllerStates::Stopping,
|
||||
recv: recv,
|
||||
send: send,
|
||||
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{data: Button::Released},
|
||||
Commands::PicRecvDown{data: Button::Released},
|
||||
Commands::PicRecvStop{data: Button::Released},
|
||||
Commands::BluetoothUp{data: Button::Released},
|
||||
Commands::BluetoothDown{data: Button::Released},
|
||||
Commands::BluetoothStop{data: Button::Released},
|
||||
Commands::PicRecvLimitUp{data: Button::Released},
|
||||
Commands::PicRecvLimitDown{data: Button::Released},
|
||||
Commands::PicRecvAutoMode{data: Button::Released},
|
||||
Commands::StopTimerExpired,
|
||||
Commands::ButtonTimerExpired,
|
||||
];
|
||||
dp.get_callback_channel(&cmds)
|
||||
}
|
||||
|
||||
async fn enter_state(&mut self, new_s: &ControllerStates) -> Result<()> {
|
||||
match new_s {
|
||||
ControllerStates::Stopped => {
|
||||
// Other notify commands are sent directly from the motor controller
|
||||
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?;
|
||||
}
|
||||
ControllerStates::GoingUp => {
|
||||
self.send.send(Commands::ButtonTimerRestart).await?;
|
||||
self.motor_q.send(MotorCommands::StartUp).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?;
|
||||
}
|
||||
ControllerStates::AutoDown => {
|
||||
self.motor_q.send(MotorCommands::StartDown).await?;
|
||||
self.send.send(Commands::NotifyMotorUp{data: Button::Pressed}).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn exit_state(&mut self, old_s: &ControllerStates) -> Result <()> {
|
||||
match old_s {
|
||||
ControllerStates::Stopped => {}
|
||||
ControllerStates::Stopping => {
|
||||
self.send.send(Commands::StopTimerClear).await?;
|
||||
}
|
||||
ControllerStates::GoingUp => {
|
||||
self.send.send(Commands::ButtonTimerClear).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?;
|
||||
}
|
||||
ControllerStates::AutoDown => {
|
||||
self.send.send(Commands::NotifyMotorDown{data: Button::Released}).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn change_state_if_released(&self, data: &Button, new_state: ControllerStates) -> ControllerStates {
|
||||
match data {
|
||||
Button::Released => {new_state}
|
||||
Button::Pressed => {self.state.clone()}
|
||||
}
|
||||
}
|
||||
|
||||
fn change_state_if_pressed(&self, data: &Button, new_state: ControllerStates) -> ControllerStates {
|
||||
match data {
|
||||
Button::Released => {self.state.clone()}
|
||||
Button::Pressed => {new_state}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<()> {
|
||||
if old_s != new_s {
|
||||
self.exit_state(&old_s).await?;
|
||||
self.enter_state(&new_s).await?;
|
||||
}
|
||||
self.state = new_s.clone();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> Result<()> {
|
||||
// On entry, assume initial stopping state
|
||||
debug!("Setting motor controller initial state to stopping");
|
||||
self.state = ControllerStates::Stopping;
|
||||
self.enter_state(&ControllerStates::Stopping).await?;
|
||||
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).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: &Button) {
|
||||
match data {
|
||||
Button::Released => {
|
||||
self.auto_mode = AutoMode::Disallowed;
|
||||
}
|
||||
Button::Pressed => {
|
||||
if self.limit_state == LimitState::BothHit {
|
||||
warn!("Limit switches not detected. Aborting auto mode.");
|
||||
} else {
|
||||
self.auto_mode = AutoMode::Allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn adjust_limit(&mut self, limit: LimitState, pressed: &Button) {
|
||||
match pressed {
|
||||
Button::Released => {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
Button::Pressed => {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn pressed_warning(data: &Button, warn: &str) {
|
||||
match data {
|
||||
Button::Pressed => {warn!("{}", warn);} // TODO: user warning, not intenral
|
||||
Button::Released => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn released_warning(data: &Button, warn: &str) {
|
||||
match data {
|
||||
Button::Released => {warn!("{}", warn);} // TODO: user warning, not intenral
|
||||
Button::Pressed => {}
|
||||
}
|
||||
}
|
||||
@ -2,34 +2,26 @@
|
||||
|
||||
use log::*; //{trace, debug, info, warn, error}
|
||||
use anyhow::Result;
|
||||
use async_channel::{unbounded, Receiver, Sender};
|
||||
use async_channel::unbounded;
|
||||
use gem_remotes_lib::{MotorCommands, MotorRecvQ, MotorSendQ};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Commands {
|
||||
StartUp,
|
||||
StartDown,
|
||||
Stop
|
||||
}
|
||||
|
||||
pub type SendQ = Sender<Commands>;
|
||||
pub type RecvQ = Receiver<Commands>;
|
||||
|
||||
pub struct MotorDriverDebug{
|
||||
endpoint: SendQ, // Endpoint to hand to dispatch or anyone else sending commands here.
|
||||
recv_q: RecvQ,
|
||||
endpoint: MotorSendQ, // Endpoint to hand to dispatch or anyone else sending commands here.
|
||||
recv_q: MotorRecvQ,
|
||||
}
|
||||
|
||||
/// Debug / example version of Motor Driver.
|
||||
impl MotorDriverDebug {
|
||||
pub fn new() -> Self {
|
||||
let (s,r) = unbounded();
|
||||
let (s,r) = unbounded(); // TODO: reserve a reasonable amount for all unbounded?
|
||||
MotorDriverDebug {
|
||||
endpoint: s,
|
||||
recv_q: r,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_endpoint(&self) -> SendQ {
|
||||
pub fn get_endpoint(&self) -> MotorSendQ {
|
||||
self.endpoint.clone()
|
||||
}
|
||||
|
||||
@ -40,11 +32,11 @@ impl MotorDriverDebug {
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_cmd(&self, cmd: Commands) -> Result<()> {
|
||||
async fn handle_cmd(&self, cmd: MotorCommands) -> Result<()> {
|
||||
match cmd {
|
||||
Commands::StartUp => {self.start_up().await?;}
|
||||
Commands::StartDown => {self.start_down().await?;}
|
||||
Commands::Stop => {self.stop().await?;}
|
||||
MotorCommands::StartUp => {self.start_up().await?;}
|
||||
MotorCommands::StartDown => {self.start_down().await?;}
|
||||
MotorCommands::Stop => {self.stop().await?;}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -62,3 +54,7 @@ impl MotorDriverDebug {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//TODO: we should fix panic to ensure that we shut down motors before rebooting ESP!
|
||||
// Maybe by getting another endpoint and passing it to the panic handler? Add a different command that doesn't just stop, but stops and stops processing any new commands.
|
||||
@ -4,8 +4,10 @@ use async_channel::Sender;
|
||||
use esp_idf_svc::timer::EspTaskTimerService;
|
||||
use core::time::Duration;
|
||||
|
||||
use crate::commands::Commands;
|
||||
use crate::dispatch::Dispatch;
|
||||
use gem_remotes_lib::{
|
||||
Commands,
|
||||
Dispatch,
|
||||
};
|
||||
|
||||
type SendQ = Sender<Commands>;
|
||||
|
||||
@ -25,7 +27,7 @@ impl PairButtonDriver {
|
||||
debug!("Waiting on pairing button presses");
|
||||
loop {
|
||||
//TO DO: Watch for incoming pair button presses from the PIC and/or hardware buttons
|
||||
async_timer.after(Duration::from_millis(10000)).await?; // no need to panic on test console driver timer failure
|
||||
async_timer.after(Duration::from_millis(10_000)).await?; // no need to panic on test console driver timer failure
|
||||
//When we find a press, send PicRecvPair
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,10 +24,15 @@ use ::{
|
||||
},
|
||||
core::time::Duration,
|
||||
};
|
||||
use crate::commands::{Button, Commands};
|
||||
|
||||
use async_channel::Sender;
|
||||
use log::*; //{trace, debug, info, warn, error}
|
||||
|
||||
use gem_remotes_lib::{
|
||||
Button,
|
||||
Commands
|
||||
};
|
||||
|
||||
#[derive(Command)]
|
||||
pub enum Menu{//<'a> {
|
||||
/// Simulate the PIC controller sending aus n Up character
|
||||
|
||||
Reference in New Issue
Block a user