#[repr(u16)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommandStatus {
Init = 1,
Idle = 10,
Executing = 20,
Done = 100,
Error = 900,
}
impl CommandStatus {
pub fn as_u16(self) -> u16 {
self as u16
}
pub fn from_u16(val: u16) -> Option<Self> {
match val {
1 => Some(Self::Init),
10 => Some(Self::Idle),
20 => Some(Self::Executing),
100 => Some(Self::Done),
900 => Some(Self::Error),
_ => None,
}
}
}
#[repr(u16)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommandRequest {
Idle = 10,
Execute = 11,
AckDone = 101,
}
impl CommandRequest {
pub fn as_u16(self) -> u16 {
self as u16
}
pub fn from_u16(val: u16) -> Option<Self> {
match val {
10 => Some(Self::Idle),
11 => Some(Self::Execute),
101 => Some(Self::AckDone),
_ => None,
}
}
}
pub struct CommandInterfaceView<'a> {
pub command: &'a mut u16,
pub command_code: &'a mut u32,
pub command_arg_1: &'a mut f64,
pub command_arg_2: &'a mut f64,
pub command_status: &'a mut u16,
pub command_result: &'a mut f64,
}
#[derive(Debug, Clone)]
pub struct CommandInterface {
initialized: bool,
active_command: Option<u32>,
}
impl CommandInterface {
pub fn new() -> Self {
Self {
initialized: false,
active_command: None,
}
}
pub fn call(&mut self, view: &mut CommandInterfaceView) -> Option<u32> {
if !self.initialized {
*view.command_status = CommandStatus::Idle.as_u16();
self.initialized = true;
return None;
}
let status = *view.command_status;
let command = *view.command;
if status == CommandStatus::Idle.as_u16() {
if command == CommandRequest::Execute.as_u16() {
self.active_command = Some(*view.command_code);
*view.command_status = CommandStatus::Executing.as_u16();
return self.active_command;
}
} else if status == CommandStatus::Done.as_u16() || status == CommandStatus::Error.as_u16() {
if command == CommandRequest::AckDone.as_u16() {
self.active_command = None;
*view.command_status = CommandStatus::Idle.as_u16();
*view.command = CommandRequest::Idle.as_u16();
}
}
None
}
pub fn is_executing(&self) -> bool {
self.active_command.is_some()
}
pub fn active_command(&self) -> Option<u32> {
self.active_command
}
pub fn set_done(&mut self, view: &mut CommandInterfaceView, result: f64) {
*view.command_result = result;
*view.command_status = CommandStatus::Done.as_u16();
}
pub fn set_error(&mut self, view: &mut CommandInterfaceView, error_result: f64) {
*view.command_result = error_result;
*view.command_status = CommandStatus::Error.as_u16();
}
}
impl Default for CommandInterface {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_view_fields() -> (u16, u32, f64, f64, u16, f64) {
(CommandRequest::Idle.as_u16(), 0u32, 0.0f64, 0.0f64, 0u16, 0.0f64)
}
macro_rules! view {
($cmd:expr, $code:expr, $a1:expr, $a2:expr, $status:expr, $result:expr) => {
CommandInterfaceView {
command: &mut $cmd,
command_code: &mut $code,
command_arg_1: &mut $a1,
command_arg_2: &mut $a2,
command_status: &mut $status,
command_result: &mut $result,
}
};
}
#[test]
fn test_initialization() {
let mut ci = CommandInterface::new();
let (mut cmd, mut code, mut a1, mut a2, mut status, mut result) = make_view_fields();
let mut v = view!(cmd, code, a1, a2, status, result);
assert_eq!(ci.call(&mut v), None);
assert_eq!(*v.command_status, CommandStatus::Idle.as_u16());
assert!(!ci.is_executing());
}
#[test]
fn test_full_handshake() {
let mut ci = CommandInterface::new();
let (mut cmd, mut code, mut a1, mut a2, mut status, mut result) = make_view_fields();
{
let mut v = view!(cmd, code, a1, a2, status, result);
ci.call(&mut v);
}
assert_eq!(status, CommandStatus::Idle.as_u16());
code = 42;
a1 = 3.14;
a2 = 2.71;
cmd = CommandRequest::Execute.as_u16();
{
let mut v = view!(cmd, code, a1, a2, status, result);
assert_eq!(ci.call(&mut v), Some(42));
}
assert_eq!(status, CommandStatus::Executing.as_u16());
assert!(ci.is_executing());
assert_eq!(ci.active_command(), Some(42));
{
let mut v = view!(cmd, code, a1, a2, status, result);
assert_eq!(ci.call(&mut v), None);
}
{
let mut v = view!(cmd, code, a1, a2, status, result);
ci.set_done(&mut v, 99.0);
}
assert_eq!(status, CommandStatus::Done.as_u16());
assert_eq!(result, 99.0);
cmd = CommandRequest::AckDone.as_u16();
{
let mut v = view!(cmd, code, a1, a2, status, result);
assert_eq!(ci.call(&mut v), None);
}
assert_eq!(status, CommandStatus::Idle.as_u16());
assert_eq!(cmd, CommandRequest::Idle.as_u16());
assert!(!ci.is_executing());
assert_eq!(ci.active_command(), None);
}
#[test]
fn test_error_handshake() {
let mut ci = CommandInterface::new();
let (mut cmd, mut code, mut a1, mut a2, mut status, mut result) = make_view_fields();
{
let mut v = view!(cmd, code, a1, a2, status, result);
ci.call(&mut v);
}
code = 7;
cmd = CommandRequest::Execute.as_u16();
{
let mut v = view!(cmd, code, a1, a2, status, result);
assert_eq!(ci.call(&mut v), Some(7));
}
{
let mut v = view!(cmd, code, a1, a2, status, result);
ci.set_error(&mut v, -1.0);
}
assert_eq!(status, CommandStatus::Error.as_u16());
assert_eq!(result, -1.0);
cmd = CommandRequest::AckDone.as_u16();
{
let mut v = view!(cmd, code, a1, a2, status, result);
ci.call(&mut v);
}
assert_eq!(status, CommandStatus::Idle.as_u16());
assert!(!ci.is_executing());
}
#[test]
fn test_ignores_exec_while_executing() {
let mut ci = CommandInterface::new();
let (mut cmd, mut code, mut a1, mut a2, mut status, mut result) = make_view_fields();
{
let mut v = view!(cmd, code, a1, a2, status, result);
ci.call(&mut v);
}
code = 1;
cmd = CommandRequest::Execute.as_u16();
{
let mut v = view!(cmd, code, a1, a2, status, result);
assert_eq!(ci.call(&mut v), Some(1));
}
code = 2;
{
let mut v = view!(cmd, code, a1, a2, status, result);
assert_eq!(ci.call(&mut v), None);
}
assert_eq!(status, CommandStatus::Executing.as_u16());
assert_eq!(ci.active_command(), Some(1));
}
#[test]
fn test_idle_command_ignored_when_idle() {
let mut ci = CommandInterface::new();
let (mut cmd, mut code, mut a1, mut a2, mut status, mut result) = make_view_fields();
{
let mut v = view!(cmd, code, a1, a2, status, result);
ci.call(&mut v);
}
{
let mut v = view!(cmd, code, a1, a2, status, result);
assert_eq!(ci.call(&mut v), None);
}
assert_eq!(status, CommandStatus::Idle.as_u16());
}
}