Added some tests, fixed mistakes tests found
This commit is contained in:
131
gem-remotes-esp32/analyze-binary.py
Normal file
131
gem-remotes-esp32/analyze-binary.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
#!/bin/env python3
|
||||||
|
|
||||||
|
'''
|
||||||
|
Quick and dirty script to analyze the contents of the binary.
|
||||||
|
|
||||||
|
Shows total size of a crate's added code, count of the number of sections,
|
||||||
|
and the name of the crate. Names with non-rust names are lumped into
|
||||||
|
esp-idf, as that's most likely what they are in this project.
|
||||||
|
|
||||||
|
Unfortunately, since much of the actual functionality is carried out by the
|
||||||
|
esp-idf, including newly added functionality (such as bluetooth) often mostly
|
||||||
|
just grows the amount of esp-idf that is included.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import os.path
|
||||||
|
import os.getcwd
|
||||||
|
|
||||||
|
# Shorten names to crates instead of the whole function
|
||||||
|
mangle_name = True
|
||||||
|
|
||||||
|
program_name = os.path.split(os.getcwd())[1]
|
||||||
|
print(program_name)
|
||||||
|
targets = ("./target/xtensa-esp32-espidf/release/", "./target/xtensa-esp32-espidf/debug/")
|
||||||
|
|
||||||
|
def find_file():
|
||||||
|
for t in targets:
|
||||||
|
if os.path.isfile(t + program_name):
|
||||||
|
#return t
|
||||||
|
analyze_file(t + program_name)
|
||||||
|
|
||||||
|
def analyze_file(f):
|
||||||
|
results = subprocess.run(["nm", "-S", "--demangle=rust", "--size-sort", f], capture_output=True).stdout
|
||||||
|
lines = results.splitlines()
|
||||||
|
data = {}
|
||||||
|
for line in lines:
|
||||||
|
cols = line.split()
|
||||||
|
# Cols are: 0: position, 1: size, 2: ? 3: name
|
||||||
|
if len(cols) < 4:
|
||||||
|
pass # this shouldn't happen if we sort by size; but nm lists things without a size otherwise.
|
||||||
|
else:
|
||||||
|
raw_name = cols[3].decode("utf-8")
|
||||||
|
raw_size = cols[1]
|
||||||
|
if mangle_name:
|
||||||
|
if len(raw_name):
|
||||||
|
while "<" == raw_name[0] or "&" == raw_name[0]:
|
||||||
|
raw_name = raw_name[1:]
|
||||||
|
parts = raw_name.split(':')
|
||||||
|
if len(parts[0]) == len(raw_name):
|
||||||
|
# Assume if it has no crate delimiters that it is part of esp-idf
|
||||||
|
name = "esp-idf"
|
||||||
|
else:
|
||||||
|
name = parts[0]
|
||||||
|
else:
|
||||||
|
name = "(blank)"
|
||||||
|
else:
|
||||||
|
name = raw_name
|
||||||
|
size = int(raw_size, 16)
|
||||||
|
if name in data:
|
||||||
|
(count, total) = data[name]
|
||||||
|
count += 1
|
||||||
|
total += size
|
||||||
|
data[name] = (count, total)
|
||||||
|
else:
|
||||||
|
data[name] = (1, size)
|
||||||
|
print(" total | ct | crate")
|
||||||
|
sorted_data = []
|
||||||
|
for item in data.items():
|
||||||
|
(name, (count, size)) = item
|
||||||
|
sorted_data.append((size, count, name))
|
||||||
|
sorted_data.sort(key=lambda tup: tup[0])
|
||||||
|
for i in sorted_data:
|
||||||
|
(size, count, name) = i
|
||||||
|
print(f'{size:8,}', f'{count:4}', name)
|
||||||
|
i = 0
|
||||||
|
for tup in data.values():
|
||||||
|
i += tup[1]
|
||||||
|
print("\n","Total size: ", f'{i:,}', "Actual binary size may differ due to included data and the chunks nm didn't identify")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
find_file()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
|
'''
|
||||||
|
For comparison; the 'Hello, World' app generated by 'cargo generate esp-rs/esp-idf-template cargo'
|
||||||
|
|
||||||
|
Hello, World (release) analysis
|
||||||
|
total | ct | crate
|
||||||
|
9 1 panic_abort
|
||||||
|
11 1 esp_idf_sys
|
||||||
|
84 1 hello_world
|
||||||
|
223 1 memchr
|
||||||
|
315 12 log
|
||||||
|
654 2 adler
|
||||||
|
724 8 esp_idf_svc
|
||||||
|
2,757 24 object
|
||||||
|
8,104 6 miniz_oxide
|
||||||
|
15,044 181 alloc
|
||||||
|
15,765 42 rustc_demangle
|
||||||
|
19,581 37 addr2line
|
||||||
|
26,343 200 std
|
||||||
|
33,966 291 core
|
||||||
|
34,980 104 gimli
|
||||||
|
159,412 1778 esp-idf
|
||||||
|
|
||||||
|
Total size: 317,972
|
||||||
|
|
||||||
|
Hello, World (debug) analysis
|
||||||
|
total | ct | crate
|
||||||
|
9 1 panic_abort
|
||||||
|
11 1 esp_idf_sys
|
||||||
|
84 1 hello_world
|
||||||
|
267 2 memchr
|
||||||
|
356 13 log
|
||||||
|
817 8 esp_idf_svc
|
||||||
|
1,012 5 adler
|
||||||
|
3,571 45 object
|
||||||
|
9,796 13 miniz_oxide
|
||||||
|
13,331 45 rustc_demangle
|
||||||
|
20,043 45 addr2line
|
||||||
|
28,044 281 std
|
||||||
|
35,175 629 alloc
|
||||||
|
38,761 210 gimli
|
||||||
|
60,658 863 core
|
||||||
|
186,909 2286 esp-idf
|
||||||
|
|
||||||
|
Total size: 398,844
|
||||||
|
'''
|
||||||
@ -27,8 +27,12 @@ impl MotorDriverDebug {
|
|||||||
|
|
||||||
pub async fn run(&self) -> Result<()> {
|
pub async fn run(&self) -> Result<()> {
|
||||||
loop {
|
loop {
|
||||||
let cmd = self.recv_q.recv().await.expect("Unexpected failure in motor driver command queue");
|
let cmd = self.recv_q.recv()
|
||||||
self.handle_cmd(cmd).await.expect("Unexpected failure of motor driver notification queue");
|
.await
|
||||||
|
.expect("Unexpected failure in motor driver command queue");
|
||||||
|
self.handle_cmd(cmd)
|
||||||
|
.await
|
||||||
|
.expect("Unexpected failure of motor driver notification queue");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,20 +45,26 @@ impl MotorDriverDebug {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start_up(&self) -> Result<()> {
|
async fn start_up(&self) -> Result<()> {
|
||||||
warn!("Starting motor, direction: Up");
|
warn!("Starting motor, direction: Up");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub async fn start_down(&self) -> Result<()> {
|
async fn start_down(&self) -> Result<()> {
|
||||||
warn!("Starting motor, direction: Down");
|
warn!("Starting motor, direction: Down");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub async fn stop(&self) -> Result<()> {
|
async fn stop(&self) -> Result<()> {
|
||||||
warn!("Stopping motor");
|
warn!("Stopping motor");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//TODO: we should fix panic to ensure that we shut down motors before rebooting ESP!
|
//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.
|
// 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.
|
||||||
|
|
||||||
|
//TODO: Design - are there any implications to the PIC motor driver essentially sending button
|
||||||
|
// presses instead of commanding the motor on/off? Feedback loops? No way to know without PIC
|
||||||
|
// code.
|
||||||
@ -1,3 +1,11 @@
|
|||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = "s"
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
debug = true # Symbols are nice and they don't increase the size on Flash
|
||||||
|
opt-level = "z"
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "gem-remotes-lib"
|
name = "gem-remotes-lib"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -6,6 +14,7 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
async-channel = "2.3.1"
|
async-channel = "2.3.1"
|
||||||
|
async-io = "2.3.4"
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
strum = "0.26.3"
|
strum = "0.26.3"
|
||||||
strum_macros = "0.26.4"
|
strum_macros = "0.26.4"
|
||||||
|
|||||||
@ -15,9 +15,9 @@ pub enum Commands {
|
|||||||
PicRecvUp {data: Button},
|
PicRecvUp {data: Button},
|
||||||
PicRecvDown {data: Button},
|
PicRecvDown {data: Button},
|
||||||
PicRecvStop {data: Button},
|
PicRecvStop {data: Button},
|
||||||
PicRecvLimitUp {data: Button}, // 0 for not hit, 1 for hit
|
PicRecvLimitUp {data: Button},
|
||||||
PicRecvLimitDown {data: Button}, // 0 for not hit, 1 for hit
|
PicRecvLimitDown {data: Button},
|
||||||
PicRecvAutoMode {data: Button}, // 0 for disallowed, 1 for allowed
|
PicRecvAutoMode {data: Toggle}, // 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.
|
// 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.
|
||||||
|
|
||||||
@ -44,6 +44,8 @@ pub enum Commands {
|
|||||||
NotifyMotorStop {data: Button},
|
NotifyMotorStop {data: Button},
|
||||||
|
|
||||||
EraseBleBonds,
|
EraseBleBonds,
|
||||||
|
|
||||||
|
TestingExit, // Used only in unit/integration tests. Do not subscribe for.
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
@ -52,6 +54,13 @@ pub enum Button {
|
|||||||
Pressed =1
|
Pressed =1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Distinguish toggles(like auto) which is on/off from buttons (which are pressed/released)
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum Toggle {
|
||||||
|
Inactive = 0,
|
||||||
|
Active =1
|
||||||
|
}
|
||||||
|
|
||||||
pub type CmdType = std::mem::Discriminant<Commands>;
|
pub type CmdType = std::mem::Discriminant<Commands>;
|
||||||
|
|
||||||
/// Consider commands equal if they have the same command type, but different values
|
/// Consider commands equal if they have the same command type, but different values
|
||||||
|
|||||||
@ -27,19 +27,16 @@ pub use dispatch::{
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Test Code for whole module
|
// // Test Code for whole module // //////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
pub fn add(left: u64, right: u64) -> u64 {
|
|
||||||
left + right
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
//use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_works() {
|
fn empty_test() {
|
||||||
let result = add(2, 2);
|
assert_eq!(1, 1);
|
||||||
assert_eq!(result, 4);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Check whole module for panics (unwrap, expect, panic) and ensure that it is appropriate
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
/// command messages.
|
/// command messages.
|
||||||
|
|
||||||
use log::*; //{trace, debug, info, warn, error}
|
use log::*; //{trace, debug, info, warn, error}
|
||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
use async_channel::{Receiver, Sender};
|
use async_channel::{Receiver, Sender};
|
||||||
use crate::commands::{Commands, Button};
|
use crate::commands::{Commands, Button, Toggle};
|
||||||
use crate::dispatch::{Dispatch, DispatchRecvQ, DispatchSendQ};
|
use crate::dispatch::{Dispatch, DispatchRecvQ, DispatchSendQ};
|
||||||
|
|
||||||
// The main internal state of the controller, representing the current control method of the motors.
|
// The main internal state of the controller, representing the current control method of the motors.
|
||||||
@ -18,8 +18,7 @@ enum ControllerStates {
|
|||||||
AutoDown,
|
AutoDown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum MotorCommands {
|
pub enum MotorCommands {
|
||||||
StartUp,
|
StartUp,
|
||||||
StartDown,
|
StartDown,
|
||||||
@ -76,7 +75,7 @@ impl Controller {
|
|||||||
Commands::BluetoothStop{data: Button::Released},
|
Commands::BluetoothStop{data: Button::Released},
|
||||||
Commands::PicRecvLimitUp{data: Button::Released},
|
Commands::PicRecvLimitUp{data: Button::Released},
|
||||||
Commands::PicRecvLimitDown{data: Button::Released},
|
Commands::PicRecvLimitDown{data: Button::Released},
|
||||||
Commands::PicRecvAutoMode{data: Button::Released},
|
Commands::PicRecvAutoMode{data: Toggle::Inactive},
|
||||||
Commands::StopTimerExpired,
|
Commands::StopTimerExpired,
|
||||||
Commands::ButtonTimerExpired,
|
Commands::ButtonTimerExpired,
|
||||||
];
|
];
|
||||||
@ -155,34 +154,35 @@ impl Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Determines the state the controller should be in based on the command received.
|
/// Determines the state the controller should be in based on the command received.
|
||||||
async fn handle_cmd(&mut self, cmd: &Commands) -> ControllerStates {
|
async fn handle_cmd(&mut self, cmd: &Commands) -> Result<ControllerStates> {
|
||||||
|
let mut rc = self.state.clone(); // Don't transition by default.
|
||||||
match cmd {
|
match cmd {
|
||||||
Commands::PicRecvUp { data } | Commands::BluetoothUp { data }=> {
|
Commands::PicRecvUp { data } | Commands::BluetoothUp { data }=> {
|
||||||
match self.state {
|
match self.state {
|
||||||
ControllerStates::Stopped => {return self.change_state_if_pressed(data, self.remote_up_or_auto_up())}
|
ControllerStates::Stopped => {rc = self.change_state_if_pressed(data, self.remote_up_or_auto_up())}
|
||||||
ControllerStates::Stopping => {}
|
ControllerStates::Stopping => {}
|
||||||
ControllerStates::GoingUp => {
|
ControllerStates::GoingUp => {
|
||||||
self.send.send(Commands::ButtonTimerRestart).await.expect("Failed to necessary timer");
|
self.send.send(Commands::ButtonTimerRestart).await.expect("Failed to necessary timer");
|
||||||
return self.change_state_if_released(data, ControllerStates::Stopping)
|
rc = self.change_state_if_released(data, ControllerStates::Stopping)
|
||||||
}
|
}
|
||||||
ControllerStates::AutoUp => {} // Don't stop auto on button release
|
ControllerStates::AutoUp => {} // Don't stop auto on button release
|
||||||
ControllerStates::GoingDown |
|
ControllerStates::GoingDown |
|
||||||
ControllerStates::AutoDown => {
|
ControllerStates::AutoDown => {
|
||||||
return self.change_state_if_pressed(data, ControllerStates::Stopping)
|
rc= self.change_state_if_pressed(data, ControllerStates::Stopping)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Commands::PicRecvDown { data } | Commands::BluetoothDown { data } => {
|
Commands::PicRecvDown { data } | Commands::BluetoothDown { data } => {
|
||||||
match self.state {
|
match self.state {
|
||||||
ControllerStates::Stopped => {return self.change_state_if_pressed(data, self.remote_down_or_auto_down())}
|
ControllerStates::Stopped => {rc = self.change_state_if_pressed(data, self.remote_down_or_auto_down())}
|
||||||
ControllerStates::Stopping => {}
|
ControllerStates::Stopping => {}
|
||||||
ControllerStates::GoingUp |
|
ControllerStates::GoingUp |
|
||||||
ControllerStates::AutoUp => {
|
ControllerStates::AutoUp => {
|
||||||
return self.change_state_if_pressed(data, ControllerStates::Stopping)
|
rc = self.change_state_if_pressed(data, ControllerStates::Stopping)
|
||||||
}
|
}
|
||||||
ControllerStates::GoingDown => {
|
ControllerStates::GoingDown => {
|
||||||
self.send.send(Commands::ButtonTimerRestart).await.expect("Failed to necessary timer");
|
self.send.send(Commands::ButtonTimerRestart).await.expect("Failed to necessary timer");
|
||||||
return self.change_state_if_released(data, ControllerStates::Stopping)
|
rc = self.change_state_if_released(data, ControllerStates::Stopping)
|
||||||
}
|
}
|
||||||
ControllerStates::AutoDown => {}
|
ControllerStates::AutoDown => {}
|
||||||
}
|
}
|
||||||
@ -195,7 +195,7 @@ impl Controller {
|
|||||||
ControllerStates::AutoUp |
|
ControllerStates::AutoUp |
|
||||||
ControllerStates::GoingDown |
|
ControllerStates::GoingDown |
|
||||||
ControllerStates::AutoDown => {
|
ControllerStates::AutoDown => {
|
||||||
return self.change_state_if_pressed(data, ControllerStates::Stopping)
|
rc = self.change_state_if_pressed(data, ControllerStates::Stopping)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,13 +207,13 @@ impl Controller {
|
|||||||
ControllerStates::GoingUp |
|
ControllerStates::GoingUp |
|
||||||
ControllerStates::AutoUp=> {
|
ControllerStates::AutoUp=> {
|
||||||
released_warning(data, "Limit switches may be installed incorrectly!");
|
released_warning(data, "Limit switches may be installed incorrectly!");
|
||||||
return self.change_state_if_pressed(data, ControllerStates::Stopping)
|
rc = self.change_state_if_pressed(data, ControllerStates::Stopping)
|
||||||
}
|
}
|
||||||
ControllerStates::GoingDown |
|
ControllerStates::GoingDown |
|
||||||
ControllerStates::AutoDown=> {
|
ControllerStates::AutoDown=> {
|
||||||
pressed_warning(data, "Limit switches may be installed incorrectly!");
|
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.
|
// 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)
|
rc = self.change_state_if_pressed(data, ControllerStates::Stopping)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -226,20 +226,30 @@ impl Controller {
|
|||||||
ControllerStates::AutoUp => {
|
ControllerStates::AutoUp => {
|
||||||
pressed_warning(data, "Limit switches may be installed incorrectly!");
|
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.
|
// 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)
|
rc = self.change_state_if_pressed(data, ControllerStates::Stopping)
|
||||||
}
|
}
|
||||||
ControllerStates::GoingDown |
|
ControllerStates::GoingDown |
|
||||||
ControllerStates::AutoDown => {
|
ControllerStates::AutoDown => {
|
||||||
released_warning(data, "Limit switches may be installed incorrectly!");
|
released_warning(data, "Limit switches may be installed incorrectly!");
|
||||||
return self.change_state_if_pressed(data, ControllerStates::Stopping)
|
rc = self.change_state_if_pressed(data, ControllerStates::Stopping)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Commands::PicRecvAutoMode { data } => {self.set_auto(data);}
|
Commands::PicRecvAutoMode { data } => {
|
||||||
|
self.set_auto(data);
|
||||||
|
match self.state {
|
||||||
|
ControllerStates::Stopped => {}
|
||||||
|
ControllerStates::Stopping => {}
|
||||||
|
ControllerStates::GoingUp => {}
|
||||||
|
ControllerStates::AutoUp => {rc = ControllerStates::GoingUp}
|
||||||
|
ControllerStates::GoingDown => {}
|
||||||
|
ControllerStates::AutoDown => {rc = ControllerStates::GoingDown}
|
||||||
|
}
|
||||||
|
}
|
||||||
Commands::StopTimerExpired => {
|
Commands::StopTimerExpired => {
|
||||||
match self.state {
|
match self.state {
|
||||||
ControllerStates::Stopped => {}
|
ControllerStates::Stopped => {}
|
||||||
ControllerStates::Stopping => {return ControllerStates::Stopped}
|
ControllerStates::Stopping => {rc = ControllerStates::Stopped}
|
||||||
ControllerStates::GoingUp |
|
ControllerStates::GoingUp |
|
||||||
ControllerStates::AutoUp |
|
ControllerStates::AutoUp |
|
||||||
ControllerStates::GoingDown |
|
ControllerStates::GoingDown |
|
||||||
@ -252,9 +262,9 @@ impl Controller {
|
|||||||
match self.state {
|
match self.state {
|
||||||
ControllerStates::Stopped => {}
|
ControllerStates::Stopped => {}
|
||||||
ControllerStates::Stopping => {}
|
ControllerStates::Stopping => {}
|
||||||
ControllerStates::GoingUp => {return ControllerStates::Stopping}
|
ControllerStates::GoingUp => {rc = ControllerStates::Stopping}
|
||||||
ControllerStates::AutoUp => {}
|
ControllerStates::AutoUp => {}
|
||||||
ControllerStates::GoingDown => {return ControllerStates::Stopping}
|
ControllerStates::GoingDown => {rc = ControllerStates::Stopping}
|
||||||
ControllerStates::AutoDown => {}
|
ControllerStates::AutoDown => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -277,8 +287,12 @@ impl Controller {
|
|||||||
Commands::BluetoothName { data } => {
|
Commands::BluetoothName { data } => {
|
||||||
warn!("Unexpected command received by motor controller {:?}", data) // TODO: internal "us" error.
|
warn!("Unexpected command received by motor controller {:?}", data) // TODO: internal "us" error.
|
||||||
}
|
}
|
||||||
|
Commands::TestingExit => {
|
||||||
|
return Err(anyhow!("Exiting due to testing"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.state.clone() // Don't transition by default
|
//self.state.clone() // Don't transition by default
|
||||||
|
Ok(rc)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn transition_state(&mut self, old_s: &ControllerStates, new_s: &ControllerStates) -> Result<()> {
|
async fn transition_state(&mut self, old_s: &ControllerStates, new_s: &ControllerStates) -> Result<()> {
|
||||||
@ -298,10 +312,17 @@ impl Controller {
|
|||||||
loop {
|
loop {
|
||||||
let cmd = self.recv.recv().await.expect("Motor controller command queue unexpectedly failed");
|
let cmd = self.recv.recv().await.expect("Motor controller command queue unexpectedly failed");
|
||||||
trace!("Got command {:?}",cmd);
|
trace!("Got command {:?}",cmd);
|
||||||
let new_s = self.handle_cmd(&cmd).await;
|
match self.handle_cmd(&cmd).await {
|
||||||
trace!("State current {:?} new {:?}", self.state, new_s);
|
Ok(new_s) => {
|
||||||
self.transition_state(&self.state.clone(), &new_s).await.expect("Unexpected state change failure in motor controller");
|
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");
|
||||||
|
}
|
||||||
|
Err(_) => {break;}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Err(anyhow!("Unexpectedly exited loop"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remote_up_or_auto_up(&self) -> ControllerStates {
|
fn remote_up_or_auto_up(&self) -> ControllerStates {
|
||||||
@ -356,12 +377,12 @@ impl Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_auto(&mut self, data: &Button) {
|
fn set_auto(&mut self, data: &Toggle) {
|
||||||
match data {
|
match data {
|
||||||
Button::Released => {
|
Toggle::Inactive => {
|
||||||
self.auto_mode = AutoMode::Disallowed;
|
self.auto_mode = AutoMode::Disallowed;
|
||||||
}
|
}
|
||||||
Button::Pressed => {
|
Toggle::Active => {
|
||||||
if self.limit_state == LimitState::BothHit {
|
if self.limit_state == LimitState::BothHit {
|
||||||
warn!("Limit switches not detected. Aborting auto mode.");
|
warn!("Limit switches not detected. Aborting auto mode.");
|
||||||
} else {
|
} else {
|
||||||
@ -371,6 +392,7 @@ impl Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adjusts the current limit state, based on its present state and an incoming button press or release.
|
||||||
fn adjust_limit(&mut self, limit: LimitState, pressed: &Button) {
|
fn adjust_limit(&mut self, limit: LimitState, pressed: &Button) {
|
||||||
match pressed {
|
match pressed {
|
||||||
Button::Released => {
|
Button::Released => {
|
||||||
@ -430,16 +452,315 @@ impl Controller {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Give a user warning if the given button state is pressed at the time
|
||||||
fn pressed_warning(data: &Button, warn: &str) {
|
fn pressed_warning(data: &Button, warn: &str) {
|
||||||
match data {
|
match data {
|
||||||
Button::Pressed => {warn!("{}", warn);} // TODO: user warning, not intenral
|
Button::Pressed => {warn!("{}", warn);} // TODO: user warning, not internal
|
||||||
Button::Released => {}
|
Button::Released => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Give a user warning if the given button state is released at the time
|
||||||
fn released_warning(data: &Button, warn: &str) {
|
fn released_warning(data: &Button, warn: &str) {
|
||||||
match data {
|
match data {
|
||||||
Button::Released => {warn!("{}", warn);} // TODO: user warning, not intenral
|
Button::Released => {warn!("{}", warn);} // TODO: user warning, not internal
|
||||||
Button::Pressed => {}
|
Button::Pressed => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//// Test ////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use async_channel::{unbounded, TryRecvError};
|
||||||
|
use async_io::block_on;
|
||||||
|
|
||||||
|
// Creates a new controller for use in tests. Has async-channen send, recv, and motor
|
||||||
|
// endpoints to use. Starts in AutoMode::Disallowed and LimitState::BothHit
|
||||||
|
fn create_controller() -> (Controller, DispatchSendQ, DispatchRecvQ, MotorRecvQ) {
|
||||||
|
let (ch1s, ch1r) = unbounded();
|
||||||
|
let (ch2s, ch2r) = unbounded();
|
||||||
|
let (chms, chmr) = unbounded();
|
||||||
|
let con = Controller::new(ch1r, ch2s, chms);
|
||||||
|
(con, ch1s, ch2r, chmr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a series of messages to the controller, then exit.
|
||||||
|
fn run_cmd_queue(con: &mut Controller, cs: DispatchSendQ, q: &mut Vec<Commands>) -> Result<()>{
|
||||||
|
block_on (
|
||||||
|
async {
|
||||||
|
q.push(Commands::TestingExit);
|
||||||
|
for c in q {
|
||||||
|
cs.send(c.clone()).await?
|
||||||
|
}
|
||||||
|
let _ = con.run().await; // If this doesn't error out of the loop, we'll hang here
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* States we want to test:
|
||||||
|
Controller states:
|
||||||
|
Stopped,
|
||||||
|
Stopping,
|
||||||
|
GoingUp,
|
||||||
|
AutoUp,
|
||||||
|
GoingDown,
|
||||||
|
AutoDown,
|
||||||
|
Auto states:
|
||||||
|
Disallowed
|
||||||
|
Allowed
|
||||||
|
Limit states:
|
||||||
|
NoLimitsHit,
|
||||||
|
UpperHit,
|
||||||
|
LowerHit,
|
||||||
|
BothHit,
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn motor_stops_on_initialization() -> Result<()>{
|
||||||
|
let (mut con, cs, _cr, mr) = create_controller();
|
||||||
|
let mut q = vec![];
|
||||||
|
run_cmd_queue(&mut con, cs, &mut q)?;
|
||||||
|
assert_eq!(con.state, ControllerStates::Stopping);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::Stop);
|
||||||
|
assert_eq!(mr.try_recv(), Err(TryRecvError::Empty)); // Make sure queue is empty
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn motor_stop_state_after_timeout() -> Result<()>{
|
||||||
|
let (mut con, cs, _cr, mr) = create_controller();
|
||||||
|
let mut q = vec![
|
||||||
|
Commands::StopTimerExpired,
|
||||||
|
];
|
||||||
|
run_cmd_queue(&mut con, cs, &mut q)?;
|
||||||
|
assert_eq!(con.state, ControllerStates::Stopped);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::Stop);
|
||||||
|
assert_eq!(mr.try_recv(), Err(TryRecvError::Empty)); // Make sure queue is empty
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn motor_refuses_auto_when_bothhit() -> Result<()>{
|
||||||
|
let (mut con, cs, _cr, _mr) = create_controller();
|
||||||
|
let mut q = vec![
|
||||||
|
Commands::PicRecvAutoMode{data: Toggle::Active},
|
||||||
|
];
|
||||||
|
run_cmd_queue(&mut con, cs, &mut q)?;
|
||||||
|
assert_eq!(con.auto_mode, AutoMode::Disallowed);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn motor_succeeds_auto_when_not_bothhit() -> Result<()>{
|
||||||
|
let (mut con, cs, _cr, _mr) = create_controller();
|
||||||
|
let mut q = vec![
|
||||||
|
Commands::PicRecvLimitDown { data: Button::Released },
|
||||||
|
Commands::PicRecvAutoMode{data: Toggle::Active},
|
||||||
|
];
|
||||||
|
run_cmd_queue(&mut con, cs, &mut q)?;
|
||||||
|
assert_eq!(con.auto_mode, AutoMode::Allowed);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn motor_enters_timed_up() -> Result<()>{
|
||||||
|
let (mut con, cs, _cr, mr) = create_controller();
|
||||||
|
let mut q = vec![
|
||||||
|
Commands::StopTimerExpired,
|
||||||
|
Commands::PicRecvUp { data: Button::Pressed }
|
||||||
|
];
|
||||||
|
run_cmd_queue(&mut con, cs, &mut q)?;
|
||||||
|
assert_eq!(con.state, ControllerStates::GoingUp);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::Stop);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::StartUp);
|
||||||
|
assert_eq!(mr.try_recv(), Err(TryRecvError::Empty)); // Make sure queue is empty
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn motor_stops_after_no_input_no_auto() -> Result<()>{
|
||||||
|
let (mut con, cs, _cr, mr) = create_controller();
|
||||||
|
let mut q = vec![
|
||||||
|
Commands::StopTimerExpired,
|
||||||
|
Commands::BluetoothDown { data: Button::Pressed },
|
||||||
|
Commands::ButtonTimerExpired,
|
||||||
|
];
|
||||||
|
run_cmd_queue(&mut con, cs, &mut q)?;
|
||||||
|
assert_eq!(con.state, ControllerStates::Stopping);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::Stop);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::StartDown);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::Stop);
|
||||||
|
assert_eq!(mr.try_recv(), Err(TryRecvError::Empty)); // Make sure queue is empty
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn motor_stops_on_release_timed_up() -> Result<()>{
|
||||||
|
let (mut con, cs, _cr, mr) = create_controller();
|
||||||
|
let mut q = vec![
|
||||||
|
Commands::StopTimerExpired,
|
||||||
|
Commands::PicRecvUp { data: Button::Pressed },
|
||||||
|
Commands::PicRecvUp { data: Button::Released },
|
||||||
|
];
|
||||||
|
run_cmd_queue(&mut con, cs, &mut q)?;
|
||||||
|
assert_eq!(con.state, ControllerStates::Stopping);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::Stop);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::StartUp);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::Stop);
|
||||||
|
assert_eq!(mr.try_recv(), Err(TryRecvError::Empty)); // Make sure queue is empty
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn motor_stops_on_limit_timed_up() -> Result<()>{
|
||||||
|
let (mut con, cs, _cr, mr) = create_controller();
|
||||||
|
let mut q = vec![
|
||||||
|
Commands::StopTimerExpired,
|
||||||
|
Commands::PicRecvUp { data: Button::Pressed },
|
||||||
|
Commands::PicRecvLimitUp { data: Button::Pressed },
|
||||||
|
];
|
||||||
|
run_cmd_queue(&mut con, cs, &mut q)?;
|
||||||
|
assert_eq!(con.state, ControllerStates::Stopping);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::Stop);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::StartUp);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::Stop);
|
||||||
|
assert_eq!(mr.try_recv(), Err(TryRecvError::Empty)); // Make sure queue is empty
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn motor_enters_auto_up() -> Result<()>{
|
||||||
|
let (mut con, cs, _cr, mr) = create_controller();
|
||||||
|
let mut q = vec![
|
||||||
|
Commands::StopTimerExpired,
|
||||||
|
Commands::PicRecvLimitUp{data: Button::Released},
|
||||||
|
Commands::PicRecvAutoMode{data: Toggle::Active},
|
||||||
|
Commands::PicRecvUp { data: Button::Pressed },
|
||||||
|
];
|
||||||
|
run_cmd_queue(&mut con, cs, &mut q)?;
|
||||||
|
assert_eq!(con.state, ControllerStates::AutoUp);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::Stop);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::StartUp);
|
||||||
|
assert_eq!(mr.try_recv(), Err(TryRecvError::Empty)); // Make sure queue is empty
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn motor_does_not_stop_after_no_input_in_auto() -> Result<()>{
|
||||||
|
let (mut con, cs, _cr, mr) = create_controller();
|
||||||
|
let mut q = vec![
|
||||||
|
Commands::StopTimerExpired,
|
||||||
|
Commands::PicRecvLimitUp{data: Button::Released},
|
||||||
|
Commands::PicRecvAutoMode{data: Toggle::Active},
|
||||||
|
Commands::BluetoothDown { data: Button::Pressed },
|
||||||
|
Commands::ButtonTimerExpired,
|
||||||
|
];
|
||||||
|
run_cmd_queue(&mut con, cs, &mut q)?;
|
||||||
|
assert_eq!(con.state, ControllerStates::AutoDown);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::Stop);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::StartDown);
|
||||||
|
assert_eq!(mr.try_recv(), Err(TryRecvError::Empty)); // Make sure queue is empty
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn motor_does_not_stop_auto_release() -> Result<()>{
|
||||||
|
let (mut con, cs, _cr, mr) = create_controller();
|
||||||
|
let mut q = vec![
|
||||||
|
Commands::StopTimerExpired,
|
||||||
|
Commands::PicRecvLimitUp{data: Button::Released},
|
||||||
|
Commands::PicRecvAutoMode{data: Toggle::Active},
|
||||||
|
Commands::PicRecvUp { data: Button::Pressed },
|
||||||
|
Commands::PicRecvUp { data: Button::Released },
|
||||||
|
];
|
||||||
|
run_cmd_queue(&mut con, cs, &mut q)?;
|
||||||
|
assert_eq!(con.state, ControllerStates::AutoUp);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::Stop);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::StartUp);
|
||||||
|
assert_eq!(mr.try_recv(), Err(TryRecvError::Empty)); // Make sure queue is empty
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn motor_switches_to_timed_when_auto_toggled_off() -> Result<()>{
|
||||||
|
let (mut con, cs, _cr, mr) = create_controller();
|
||||||
|
let mut q = vec![
|
||||||
|
Commands::StopTimerExpired,
|
||||||
|
Commands::PicRecvLimitUp{data: Button::Released},
|
||||||
|
Commands::PicRecvAutoMode{data: Toggle::Active},
|
||||||
|
Commands::PicRecvUp { data: Button::Pressed },
|
||||||
|
Commands::PicRecvAutoMode{data: Toggle::Inactive},
|
||||||
|
];
|
||||||
|
run_cmd_queue(&mut con, cs, &mut q)?;
|
||||||
|
assert_eq!(con.state, ControllerStates::GoingUp);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::Stop);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::StartUp);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::StartUp); // Second up commanded should be okay
|
||||||
|
assert_eq!(mr.try_recv(), Err(TryRecvError::Empty)); // Make sure queue is empty
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn motor_continues_with_additional_presses() -> Result<()>{
|
||||||
|
let (mut con, cs, _cr, mr) = create_controller();
|
||||||
|
let mut q = vec![
|
||||||
|
Commands::StopTimerExpired,
|
||||||
|
Commands::PicRecvLimitUp{data: Button::Released},
|
||||||
|
Commands::PicRecvAutoMode{data: Toggle::Active},
|
||||||
|
Commands::PicRecvUp { data: Button::Pressed },
|
||||||
|
Commands::PicRecvUp { data: Button::Released },
|
||||||
|
Commands::BluetoothUp { data: Button::Pressed },
|
||||||
|
Commands::BluetoothUp { data: Button::Released },
|
||||||
|
];
|
||||||
|
run_cmd_queue(&mut con, cs, &mut q)?;
|
||||||
|
assert_eq!(con.state, ControllerStates::AutoUp);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::Stop);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::StartUp);
|
||||||
|
assert_eq!(mr.try_recv(), Err(TryRecvError::Empty)); // Make sure queue is empty
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn motor_stops_on_opposite_button() -> Result<()>{
|
||||||
|
let (mut con, cs, _cr, mr) = create_controller();
|
||||||
|
let mut q = vec![
|
||||||
|
Commands::StopTimerExpired,
|
||||||
|
Commands::PicRecvLimitUp{data: Button::Released},
|
||||||
|
Commands::PicRecvAutoMode{data: Toggle::Active},
|
||||||
|
Commands::PicRecvUp { data: Button::Pressed },
|
||||||
|
Commands::PicRecvUp { data: Button::Released },
|
||||||
|
Commands::BluetoothDown { data: Button::Pressed },
|
||||||
|
Commands::BluetoothDown { data: Button::Released },
|
||||||
|
];
|
||||||
|
run_cmd_queue(&mut con, cs, &mut q)?;
|
||||||
|
assert_eq!(con.state, ControllerStates::Stopping);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::Stop);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::StartUp);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::Stop);
|
||||||
|
assert_eq!(mr.try_recv(), Err(TryRecvError::Empty)); // Make sure queue is empty
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn motor_stops_if_limits_installed_backwards() -> Result<()>{
|
||||||
|
let (mut con, cs, _cr, mr) = create_controller();
|
||||||
|
let mut q = vec![
|
||||||
|
Commands::StopTimerExpired,
|
||||||
|
Commands::PicRecvLimitUp{data: Button::Released},
|
||||||
|
Commands::PicRecvLimitDown{data: Button::Released},
|
||||||
|
Commands::PicRecvAutoMode{data: Toggle::Active},
|
||||||
|
Commands::PicRecvUp { data: Button::Pressed },
|
||||||
|
Commands::PicRecvUp { data: Button::Released },
|
||||||
|
Commands::PicRecvLimitDown { data: Button::Pressed },
|
||||||
|
];
|
||||||
|
run_cmd_queue(&mut con, cs, &mut q)?;
|
||||||
|
assert_eq!(con.state, ControllerStates::Stopping);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::Stop);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::StartUp);
|
||||||
|
assert_eq!(mr.try_recv().unwrap(), MotorCommands::Stop);
|
||||||
|
assert_eq!(mr.try_recv(), Err(TryRecvError::Empty)); // Make sure queue is empty
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user