Interface module for driving LEDs

+ start shaping up main.cpp
+ make the usage of namespaces and class names more consistent throughout the whole project
+ refactor related unit tests accordingly
pull/13/head
D.R.racer 2021-05-21 12:34:11 +02:00
parent 7611b98830
commit fce2195558
15 changed files with 772 additions and 528 deletions

View File

@ -184,7 +184,7 @@ target_include_directories(firmware PRIVATE include src)
target_compile_options(firmware PRIVATE -Wdouble-promotion) target_compile_options(firmware PRIVATE -Wdouble-promotion)
target_sources( target_sources(
firmware PRIVATE src/main.cpp src/hal/avr/cpu.cpp src/hal/avr/usart.cpp src/modules/protocol.cpp firmware PRIVATE src/main.cpp src/hal/avr/cpu.cpp src/hal/avr/usart.cpp src/modules/protocol.cpp
src/modules/buttons.cpp src/modules/buttons.cpp src/modules/leds.cpp
) )
set_property( set_property(

View File

@ -4,10 +4,10 @@
/// Hardware Abstraction Layer for the ADC's /// Hardware Abstraction Layer for the ADC's
namespace hal { namespace hal {
namespace ADC { namespace adc {
/// ADC access routines /// ADC access routines
uint16_t ReadADC(uint8_t adc); uint16_t ReadADC(uint8_t adc);
} // namespace ADC } // namespace adc
} // namespace hal } // namespace hal

View File

@ -1,7 +1,7 @@
#include "../cpu.h" #include "../cpu.h"
namespace hal { namespace hal {
namespace CPU { namespace cpu {
void Init() { void Init() {
} }

View File

@ -3,10 +3,10 @@
/// Hardware Abstraction Layer for the CPU /// Hardware Abstraction Layer for the CPU
namespace hal { namespace hal {
namespace CPU { namespace cpu {
/// CPU init routines (not really necessary for the AVR) /// CPU init routines (not really necessary for the AVR)
void Init(); void Init();
} // namespace CPU } // namespace cpu
} // namespace hal } // namespace hal

View File

@ -1,65 +1,135 @@
#include "logic/mm_control.h" #include "hal/cpu.h"
#include "hal/adc.h"
#include "hal/gpio.h" #include "hal/gpio.h"
#include "hal/spi.h" #include "hal/spi.h"
#include "hal/usart.h" #include "hal/usart.h"
#include "pins.h" #include "pins.h"
#include <avr/interrupt.h> #include <avr/interrupt.h>
#include "modules/buttons.h"
#include "modules/leds.h"
#include "modules/protocol.h"
#include "logic/mm_control.h"
static hal::UART uart;
static modules::protocol::Protocol protocol;
static modules::buttons::Buttons buttons;
static modules::leds::LEDs leds;
// examples and test code shall be located here
void TmpPlayground() {
using namespace hal;
// SPI example
gpio::Init(gpio::GPIO_pin(GPIOC, 6), gpio::GPIO_InitTypeDef(gpio::Mode::output, gpio::Level::high));
uint8_t dat[5];
gpio::WritePin(gpio::GPIO_pin(GPIOC, 6), gpio::Level::low);
spi::TxRx(SPI0, 0x01);
spi::TxRx(SPI0, 0x00);
spi::TxRx(SPI0, 0x00);
spi::TxRx(SPI0, 0x00);
spi::TxRx(SPI0, 0x00);
gpio::WritePin(gpio::GPIO_pin(GPIOC, 6), gpio::Level::high);
gpio::WritePin(gpio::GPIO_pin(GPIOC, 6), gpio::Level::low);
dat[0] = spi::TxRx(SPI0, 0x00);
dat[1] = spi::TxRx(SPI0, 0x00);
dat[2] = spi::TxRx(SPI0, 0x00);
dat[3] = spi::TxRx(SPI0, 0x00);
dat[4] = spi::TxRx(SPI0, 0x00);
gpio::WritePin(gpio::GPIO_pin(GPIOC, 6), gpio::Level::high);
(void)dat;
// using namespace hal::gpio;
// WritePin(GPIO_pin(GPIOB, 5), Level::low);
// TogglePin(GPIO_pin(GPIOB, 6));
// if (hal::gpio::ReadPin(GPIO_pin(GPIOB, 7)) == hal::gpio::Level::low)
// break;
sei();
usart1.puts("1234567890\n");
usart1.puts("1234567890\n");
usart1.puts("1234567890\n");
usart1.puts("1234567890\n");
usart1.puts("1234567890\n");
usart1.puts("1234567890\n");
usart1.puts("1234567890\n");
usart1.puts("1234567890\n");
usart1.puts("1234567890\n");
usart1.puts("1234567890\n");
usart1.puts("1234567890\n");
}
/// One-time setup of HW and SW components /// One-time setup of HW and SW components
/// Called before entering the loop() function /// Called before entering the loop() function
/// Green LEDs signalize the progress of initialization. If anything goes wrong we shall turn on a red LED
void setup() { void setup() {
using namespace hal; using namespace hal;
// spi::SPI_InitTypeDef spi_conf = { cpu::Init();
// .miso_pin = gpio::GPIO_pin(TMC2130_SPI_MISO_PIN),
// .mosi_pin = gpio::GPIO_pin(TMC2130_SPI_MOSI_PIN),
// .sck_pin = gpio::GPIO_pin(TMC2130_SPI_SCK_PIN),
// .ss_pin = gpio::GPIO_pin(TMC2130_SPI_SS_PIN),
// .prescaler = 2, //4mhz
// .cpha = 1,
// .cpol = 1,
// };
// spi::Init(SPI0, &spi_conf);
// // SPI example // shr::Init()
// gpio::Init(gpio::GPIO_pin(GPIOC, 6), gpio::GPIO_InitTypeDef(gpio::Mode::output, gpio::Level::high)); leds.SetMode(4, false, modules::leds::Mode::blink0);
// uint8_t dat[5]; // shr::Send(leds.Step(0));
// gpio::WritePin(gpio::GPIO_pin(GPIOC, 6), gpio::Level::low);
// spi::TxRx(SPI0, 0x01);
// spi::TxRx(SPI0, 0x00);
// spi::TxRx(SPI0, 0x00);
// spi::TxRx(SPI0, 0x00);
// spi::TxRx(SPI0, 0x00);
// gpio::WritePin(gpio::GPIO_pin(GPIOC, 6), gpio::Level::high);
// gpio::WritePin(gpio::GPIO_pin(GPIOC, 6), gpio::Level::low);
// dat[0] = spi::TxRx(SPI0, 0x00);
// dat[1] = spi::TxRx(SPI0, 0x00);
// dat[2] = spi::TxRx(SPI0, 0x00);
// dat[3] = spi::TxRx(SPI0, 0x00);
// dat[4] = spi::TxRx(SPI0, 0x00);
// gpio::WritePin(gpio::GPIO_pin(GPIOC, 6), gpio::Level::high);
// (void)dat;
USART::USART_InitTypeDef usart_conf = { // @@TODO if the shift register doesn't work we really can't signalize anything, only internal variables will be accessible if the UART works
USART::USART_InitTypeDef usart_conf = {
.rx_pin = gpio::GPIO_pin(GPIOD, 2), .rx_pin = gpio::GPIO_pin(GPIOD, 2),
.tx_pin = gpio::GPIO_pin(GPIOD, 3), .tx_pin = gpio::GPIO_pin(GPIOD, 3),
.baudrate = 115200, .baudrate = 115200,
};
usart1.Init(&usart_conf); usart1.Init(&usart_conf);
sei();
usart1.puts("1234567890\n"); leds.SetMode(3, false, modules::leds::Mode::on);
usart1.puts("1234567890\n"); // shr::Send(leds.Step(0));
usart1.puts("1234567890\n");
usart1.puts("1234567890\n"); // @@TODO if both shift register and the UART are dead, we are sitting ducks :(
usart1.puts("1234567890\n");
usart1.puts("1234567890\n"); spi::SPI_InitTypeDef spi_conf = {
usart1.puts("1234567890\n"); .miso_pin = gpio::GPIO_pin(TMC2130_SPI_MISO_PIN),
usart1.puts("1234567890\n"); .mosi_pin = gpio::GPIO_pin(TMC2130_SPI_MOSI_PIN),
usart1.puts("1234567890\n"); .sck_pin = gpio::GPIO_pin(TMC2130_SPI_SCK_PIN),
usart1.puts("1234567890\n"); .ss_pin = gpio::GPIO_pin(TMC2130_SPI_SS_PIN),
usart1.puts("1234567890\n"); .prescaler = 2, //4mhz
// usart1.Flush(); .cpha = 1,
.cpol = 1,
};
spi::Init(SPI0, &spi_conf);
leds.SetMode(2, false, modules::leds::Mode::on);
//shr::Send(leds.Step(0));
// tmc::Init()
leds.SetMode(1, false, modules::leds::Mode::on);
//shr::Send(leds.Step(0));
// adc::Init();
leds.SetMode(0, false, modules::leds::Mode::on);
//shr::Send(leds.Step(0));
}
void ProcessRequestMsg(const modules::protocol::RequestMsg &rq) {
}
/// @returns true if a request was successfully finished
bool CheckMsgs() {
using mpd = modules::protocol::DecodeStatus;
while (!uart.ReadEmpty()) {
switch (protocol.DecodeRequest(uart.Read())) {
case mpd::MessageCompleted:
// process the input message
return true;
break;
case mpd::NeedMoreData:
// just continue reading
break;
case mpd::Error:
// what shall we do? Start some watchdog?
break;
}
}
return false;
} }
/// Main loop of the firmware /// Main loop of the firmware
@ -77,13 +147,16 @@ void setup() {
/// The idea behind the Step* routines is to keep each automaton non-blocking allowing for some “concurrency”. /// The idea behind the Step* routines is to keep each automaton non-blocking allowing for some “concurrency”.
/// Some FW components will leverage ISR to do their stuff (UART, motor stepping?, etc.) /// Some FW components will leverage ISR to do their stuff (UART, motor stepping?, etc.)
void loop() { void loop() {
if (CheckMsgs()) {
ProcessRequestMsg(protocol.GetRequestMsg());
}
buttons.Step(hal::adc::ReadADC(0));
// shr.Send(leds.Step(0));
} }
int main() { int main() {
setup(); setup();
for (;;) { for (;;) {
if (!usart1.ReadEmpty())
usart1.Write(usart1.Read());
loop(); loop();
} }
return 0; return 0;

View File

@ -1,72 +1,71 @@
#include "buttons.h" #include "buttons.h"
#include "../hal/adc.h"
namespace modules { namespace modules {
namespace buttons {
uint16_t Buttons::tmpTiming = 0; uint16_t Buttons::tmpTiming = 0;
// original idea from: https://www.eeweb.com/debouncing-push-buttons-using-a-state-machine-approach // original idea from: https://www.eeweb.com/debouncing-push-buttons-using-a-state-machine-approach
void Button::Step(uint16_t time, bool press) { void Button::Step(uint16_t time, bool press) {
switch (f.state) { switch (f.state) {
case State::Waiting: case State::Waiting:
if (press) { if (press) {
f.state = State::Detected; f.state = State::Detected;
timeLastChange = time; timeLastChange = time;
f.tmp = press; f.tmp = press;
}
break;
case State::Detected:
if (f.tmp == press) {
if (time - timeLastChange > debounce) {
f.state = State::WaitForRelease;
} }
} else { break;
case State::Detected:
if (f.tmp == press) {
if (time - timeLastChange > debounce) {
f.state = State::WaitForRelease;
}
} else {
f.state = State::Waiting;
}
break;
case State::WaitForRelease:
if (!press) {
f.state = State::Update;
}
break;
case State::Update:
f.state = State::Waiting; f.state = State::Waiting;
timeLastChange = time;
f.tmp = false;
break;
default:
f.state = State::Waiting;
timeLastChange = time;
f.tmp = false;
} }
break; }
case State::WaitForRelease:
if (!press) { int8_t Buttons::Sample(uint16_t rawADC) {
f.state = State::Update; // decode 3 buttons' levels from one ADC
// Button 1 - 0
// Button 2 - 344
// Button 3 - 516
// Doesn't handle multiple pressed buttons at once
if (rawADC < 10)
return 0;
else if (rawADC > 320 && rawADC < 360)
return 1;
else if (rawADC > 500 && rawADC < 530)
return 2;
return -1;
}
void Buttons::Step(uint16_t rawADC) {
// @@TODO temporary timing
++tmpTiming;
int8_t currentState = Sample(rawADC);
for (uint_fast8_t b = 0; b < N; ++b) {
// this button was pressed if b == currentState, released otherwise
buttons[b].Step(tmpTiming, b == currentState);
} }
break;
case State::Update:
f.state = State::Waiting;
timeLastChange = time;
f.tmp = false;
break;
default:
f.state = State::Waiting;
timeLastChange = time;
f.tmp = false;
} }
}
int8_t Buttons::Sample() {
// decode 3 buttons' levels from one ADC
uint16_t raw = hal::ADC::ReadADC(0);
// Button 1 - 0
// Button 2 - 344
// Button 3 - 516
// Doesn't handle multiple pressed buttons at once
if (raw < 10)
return 0;
else if (raw > 320 && raw < 360)
return 1;
else if (raw > 500 && raw < 530)
return 2;
return -1;
}
void Buttons::Step() {
// @@TODO temporary timing
++tmpTiming;
int8_t currentState = Sample();
for (uint_fast8_t b = 0; b < N; ++b) {
// this button was pressed if b == currentState, released otherwise
buttons[b].Step(tmpTiming, b == currentState);
}
}
} // namespace buttons
} // namespace modules } // namespace modules

View File

@ -7,67 +7,69 @@
/// This layer should contain debouncing of buttons and their logical interpretation /// This layer should contain debouncing of buttons and their logical interpretation
namespace modules { namespace modules {
namespace buttons {
struct Button { struct Button {
inline constexpr Button() inline constexpr Button()
: timeLastChange(0) {} : timeLastChange(0) {}
/// @returns true if button is currently considered as pressed /// @returns true if button is currently considered as pressed
inline bool Pressed() const { return f.state == State::WaitForRelease; } inline bool Pressed() const { return f.state == State::WaitForRelease; }
/// State machine stepping routine /// State machine stepping routine
void Step(uint16_t time, bool press); void Step(uint16_t time, bool press);
private: private:
/// time interval for debouncing @@TODO specify units /// time interval for debouncing @@TODO specify units
constexpr static const uint16_t debounce = 100; constexpr static const uint16_t debounce = 100;
/// States of the debouncing automaton /// States of the debouncing automaton
/// Intentionally not modeled as an enum class /// Intentionally not modeled as an enum class
/// as it would impose additional casts which do not play well with the struct Flags /// as it would impose additional casts which do not play well with the struct Flags
/// and would make the code less readable /// and would make the code less readable
enum State { Waiting = 0, enum State { Waiting = 0,
Detected, Detected,
WaitForRelease, WaitForRelease,
Update }; Update };
/// The sole purpose of this data struct is to save RAM by compressing several flags into one byte on the AVR /// The sole purpose of this data struct is to save RAM by compressing several flags into one byte on the AVR
struct Flags { struct Flags {
uint8_t state : 2; ///< state of the button uint8_t state : 2; ///< state of the button
uint8_t tmp : 1; ///< temporary state of button before the debouncing state machine finishes uint8_t tmp : 1; ///< temporary state of button before the debouncing state machine finishes
inline constexpr Flags() inline constexpr Flags()
: state(State::Waiting) : state(State::Waiting)
, tmp(false) {} , tmp(false) {}
};
/// Flags and state of the debouncing automaton
Flags f;
/// Timestamp of the last change of ADC state for this button
uint16_t timeLastChange;
}; };
/// Flags and state of the debouncing automaton class Buttons {
Flags f; constexpr static const uint8_t N = 3; ///< number of buttons currently supported
constexpr static const uint8_t adc = 1; ///< ADC index - will be some define or other constant later on
static uint16_t tmpTiming; ///< subject to removal when we have timers implemented - now used for the unit tests
/// Timestamp of the last change of ADC state for this button public:
uint16_t timeLastChange; inline constexpr Buttons() = default;
};
class Buttons { /// State machine step - reads the ADC, processes debouncing, updates states of individual buttons
constexpr static const uint8_t N = 3; ///< number of buttons currently supported void Step(uint16_t rawADC);
constexpr static const uint8_t adc = 1; ///< ADC index - will be some define or other constant later on
static uint16_t tmpTiming; ///< subject to removal when we have timers implemented - now used for the unit tests
public: /// @return true if button at index is pressed
inline constexpr Buttons() = default; /// @@TODO add range checking if necessary
inline bool ButtonPressed(uint8_t index) const { return buttons[index].Pressed(); }
/// State machine step - reads the ADC, processes debouncing, updates states of individual buttons private:
void Step(); Button buttons[N];
/// @return true if button at index is pressed /// Call to the ADC and decode its output into a button index
/// @@TODO add range checking if necessary /// @returns index of the button pressed or -1 in case no button is pressed
inline bool ButtonPressed(uint8_t index) const { return buttons[index].Pressed(); } static int8_t Sample(uint16_t rawADC);
};
private:
Button buttons[N];
/// Call to the ADC and decode its output into a button index
/// @returns index of the button pressed or -1 in case no button is pressed
static int8_t Sample();
};
} // namespace buttons
} // namespace modules } // namespace modules

50
src/modules/leds.cpp Normal file
View File

@ -0,0 +1,50 @@
#include "leds.h"
namespace modules {
namespace leds {
void LED::SetMode(Mode mode) {
state.mode = mode;
// set initial state of LEDs correctly - transition from one mode to another
switch (state.mode) {
case Mode::blink1:
case Mode::off:
state.on = 0;
break;
case Mode::blink0:
case Mode::on:
state.on = 1;
break;
default:
break;
}
}
bool LED::Step(bool oddPeriod) {
switch (state.mode) {
// on and off don't change while stepping
case Mode::blink0:
state.on = oddPeriod;
break;
case Mode::blink1:
state.on = !oddPeriod;
break;
default: // do nothing
break;
}
}
uint16_t LEDs::Step(uint8_t delta_ms) {
ms += delta_ms;
bool oddPeriod = ((ms / 1000U) & 0x01U) != 0;
uint16_t result = 0;
for (uint8_t i = 0; i < ledPairs * 2; ++i) {
result <<= 1;
result |= leds[i].Step(oddPeriod);
}
return result;
}
} // namespace leds
} // namespace modules

View File

@ -1,3 +1,119 @@
#pragma once #pragma once
/// @@TODO @leptun design some nice API ;) #include <stdint.h>
/// We have 5 pairs of LEDs
/// In each pair there is a green and a red LED
///
/// A typical scenario in the past was visualization of error states.
/// The combination of colors with blinking frequency had a specific meaning.
///
/// We'd like to drive each pair? separately, which includes:
/// - blinking (none/slow/fast)
/// - what shall blink (red/green/both-at-once/both-interlaced)
///
/// The physical connection is not important on this level (i.e. how and what shall be sent into the shift registers)
namespace modules {
namespace leds {
/// Mode of LED
/// blink0 and blink1 allow for interlaced blinking of LEDs (one is on and the other off)
enum Mode {
off,
on,
blink0, ///< start blinking at even periods
blink1 ///< start blinking at odd periods
};
/// a single LED
class LED {
public:
constexpr inline LED() = default;
void SetMode(Mode mode);
/// @returns true if the LED shines
bool Step(bool oddPeriod);
inline bool On() const { return state.on; }
private:
struct State {
uint8_t on : 1;
uint8_t mode : 2;
constexpr inline State()
: on(0)
, mode(Mode::off) {}
};
State state;
};
/// main LED API
class LEDs {
public:
constexpr inline LEDs()
: ms(0) {};
/// step LED automaton
/// @returns statuses of LEDs - one bit per LED and 1 = on, 0 = off
uint16_t Step(uint8_t delta_ms);
inline constexpr uint8_t LedPairsCount() const { return ledPairs; }
inline void SetMode(uint8_t slot, bool red, Mode mode) {
SetMode(slot * 2 + red, mode);
}
inline void SetMode(uint8_t index, Mode mode) {
leds[index].SetMode(mode);
}
inline bool LedOn(uint8_t index) const {
return leds[index].On();
}
inline bool LedOn(uint8_t slot, bool red) const {
return leds[slot * 2 + red].On();
}
private:
constexpr static const uint8_t ledPairs = 5;
LED leds[ledPairs * 2];
uint16_t ms;
};
//// asi nechame moznost jednu blikat rychle a druhou pomalu
//// tohle ale nevyresi, ze jedna LED ma blikat a druha svitit
//// problem je, ze zapnuti/vypnuti led je blink none ... mozna by to chtelo blink none-on a none-off
//// jaka bude mnozina rezimu?
//// green-off green-on green-blink-slow green-blink-fast
//// red-off + + + +
//// red-on + + + +
//// red-blink-slow + + 2 x
//// red-blink-fast + + x 2
////
//// rezim 2 muze mit jeste varianty - blink-sync, blink-interlaced, coz v podstate znamena rezimy:
//// blink-slow1, blink-slow2, blink-fast1, blink-fast2, pricemz nemaji smysl kombinace:
//// blink-slow + blink-fast
//// revize
//// v aktualnim FW led jen blikaji jednou rychlosti
//// takze to spis udelam jako pole LED, pricemz cervene budou na sudych a zelene na lichych indexech
//// stav ledky bude: off, on, blink0 a blink1
//// blink0 znamena zacni pocitat blikaci interval od sude periody
//// blink1 od liche
//// tim pujde zmodelovat jak sync tak async blikani
//// Dale je otazka, jestli chceme rychle a pomale blikani... asi ne, kdyz bude report na LCD tiskarny
//// Dale castecne souvisejici
//// Start MMU by mel reportovat progress, pricemz aktualni stav je takovy, ze nabihaji LED a kazda neco znamena
//// Tiskarna by klidne mohla posilat Q a dostavat odpoved ve stylu "starting" a progress/state code
////
//// Mozna taky budeme potrebovat nastavovat ruzne vnitrni promenne (treba use slots 1-3 namisto 0-4), takze operace SetVar a k tomu obracena GetVar
//// Dava mi smysl modifikovat protokol V jako getvar (zamerne ne G, aby to nekolidovalo s gcodes) a S jako setvar
//// cislo je index variable a odpovi se A_hodnota, cili accepted a hodnota odpovedi
//void SetLEDs(uint8_t slot0, uint8_t slot1, uint8_t slot2, uint8_t slot3, uint8_t slot4);
} // namespace LEDs
} // namespace modules

View File

@ -11,198 +11,178 @@
// <OID> F : operation finished - will be repeated to "Q" messages until a new command is issued // <OID> F : operation finished - will be repeated to "Q" messages until a new command is issued
namespace modules { namespace modules {
namespace protocol {
// decoding automaton // decoding automaton
// states: input -> transition into state // states: input -> transition into state
// Code QTLMUXPSBEWK -> msgcode // Code QTLMUXPSBEWK -> msgcode
// \n ->start // \n ->start
// * ->error // * ->error
// error \n ->start // error \n ->start
// * ->error // * ->error
// msgcode 0-9 ->msgvalue // msgcode 0-9 ->msgvalue
// * ->error // * ->error
// msgvalue 0-9 ->msgvalue // msgvalue 0-9 ->msgvalue
// \n ->start successfully accepted command // \n ->start successfully accepted command
Protocol::DecodeStatus Protocol::DecodeRequest(uint8_t c) { DecodeStatus Protocol::DecodeRequest(uint8_t c) {
switch (rqState) { switch (rqState) {
case RequestStates::Code: case RequestStates::Code:
switch (c) { switch (c) {
case 'Q': case 'Q':
case 'T': case 'T':
case 'L': case 'L':
case 'M': case 'M':
case 'U': case 'U':
case 'X': case 'X':
case 'P': case 'P':
case 'S': case 'S':
case 'B': case 'B':
case 'E': case 'E':
case 'W': case 'W':
case 'K': case 'K':
requestMsg.code = (RequestMsgCodes)c; requestMsg.code = (RequestMsgCodes)c;
requestMsg.value = 0; requestMsg.value = 0;
rqState = RequestStates::Value; rqState = RequestStates::Value;
return DecodeStatus::NeedMoreData; return DecodeStatus::NeedMoreData;
default: default:
requestMsg.code = RequestMsgCodes::unknown; requestMsg.code = RequestMsgCodes::unknown;
rqState = RequestStates::Error; rqState = RequestStates::Error;
return DecodeStatus::Error; return DecodeStatus::Error;
} }
case RequestStates::Value: case RequestStates::Value:
if (c >= '0' && c <= '9') { if (c >= '0' && c <= '9') {
requestMsg.value *= 10; requestMsg.value *= 10;
requestMsg.value += c - '0'; requestMsg.value += c - '0';
return DecodeStatus::NeedMoreData; return DecodeStatus::NeedMoreData;
} else if (c == '\n') { } else if (c == '\n') {
rqState = RequestStates::Code; rqState = RequestStates::Code;
return DecodeStatus::MessageCompleted; return DecodeStatus::MessageCompleted;
} else { } else {
requestMsg.code = RequestMsgCodes::unknown; requestMsg.code = RequestMsgCodes::unknown;
rqState = RequestStates::Error; rqState = RequestStates::Error;
return DecodeStatus::Error; return DecodeStatus::Error;
} }
default: //case error: default: //case error:
if (c == '\n') { if (c == '\n') {
rqState = RequestStates::Code; rqState = RequestStates::Code;
return DecodeStatus::MessageCompleted; return DecodeStatus::MessageCompleted;
} else { } else {
requestMsg.code = RequestMsgCodes::unknown; requestMsg.code = RequestMsgCodes::unknown;
rqState = RequestStates::Error; rqState = RequestStates::Error;
return DecodeStatus::Error; return DecodeStatus::Error;
}
} }
} }
}
uint8_t Protocol::EncodeRequest(const RequestMsg &msg, uint8_t *txbuff) { uint8_t Protocol::EncodeRequest(const RequestMsg &msg, uint8_t *txbuff) {
txbuff[0] = (uint8_t)msg.code; txbuff[0] = (uint8_t)msg.code;
txbuff[1] = msg.value + '0'; txbuff[1] = msg.value + '0';
txbuff[2] = '\n'; txbuff[2] = '\n';
return 3; return 3;
} }
Protocol::DecodeStatus Protocol::DecodeResponse(uint8_t c) { DecodeStatus Protocol::DecodeResponse(uint8_t c) {
switch (rspState) { switch (rspState) {
case ResponseStates::RequestCode: case ResponseStates::RequestCode:
switch (c) { switch (c) {
case 'Q': case 'Q':
case 'T': case 'T':
case 'L': case 'L':
case 'M': case 'M':
case 'U': case 'U':
case 'X': case 'X':
case 'P': case 'P':
case 'S': case 'S':
case 'B': case 'B':
case 'E': case 'E':
case 'W': case 'W':
case 'K': case 'K':
responseMsg.request.code = (RequestMsgCodes)c; responseMsg.request.code = (RequestMsgCodes)c;
responseMsg.request.value = 0; responseMsg.request.value = 0;
rspState = ResponseStates::RequestValue; rspState = ResponseStates::RequestValue;
return DecodeStatus::NeedMoreData; return DecodeStatus::NeedMoreData;
default: default:
rspState = ResponseStates::Error; rspState = ResponseStates::Error;
return DecodeStatus::Error; return DecodeStatus::Error;
} }
case ResponseStates::RequestValue: case ResponseStates::RequestValue:
if (c >= '0' && c <= '9') { if (c >= '0' && c <= '9') {
responseMsg.request.value *= 10; responseMsg.request.value *= 10;
responseMsg.request.value += c - '0'; responseMsg.request.value += c - '0';
return DecodeStatus::NeedMoreData; return DecodeStatus::NeedMoreData;
} else if (c == ' ') { } else if (c == ' ') {
rspState = ResponseStates::ParamCode; rspState = ResponseStates::ParamCode;
return DecodeStatus::NeedMoreData; return DecodeStatus::NeedMoreData;
} else { } else {
rspState = ResponseStates::Error; rspState = ResponseStates::Error;
return DecodeStatus::Error; return DecodeStatus::Error;
} }
case ResponseStates::ParamCode: case ResponseStates::ParamCode:
switch (c) { switch (c) {
case 'P': case 'P':
case 'E': case 'E':
case 'F': case 'F':
case 'A': case 'A':
case 'R': case 'R':
rspState = ResponseStates::ParamValue; rspState = ResponseStates::ParamValue;
responseMsg.paramCode = (ResponseMsgParamCodes)c; responseMsg.paramCode = (ResponseMsgParamCodes)c;
responseMsg.paramValue = 0; responseMsg.paramValue = 0;
return DecodeStatus::NeedMoreData; return DecodeStatus::NeedMoreData;
default: default:
responseMsg.paramCode = ResponseMsgParamCodes::unknown; responseMsg.paramCode = ResponseMsgParamCodes::unknown;
rspState = ResponseStates::Error; rspState = ResponseStates::Error;
return DecodeStatus::Error; return DecodeStatus::Error;
} }
case ResponseStates::ParamValue: case ResponseStates::ParamValue:
if (c >= '0' && c <= '9') { if (c >= '0' && c <= '9') {
responseMsg.paramValue *= 10; responseMsg.paramValue *= 10;
responseMsg.paramValue += c - '0'; responseMsg.paramValue += c - '0';
return DecodeStatus::NeedMoreData; return DecodeStatus::NeedMoreData;
} else if (c == '\n') { } else if (c == '\n') {
rspState = ResponseStates::RequestCode; rspState = ResponseStates::RequestCode;
return DecodeStatus::MessageCompleted; return DecodeStatus::MessageCompleted;
} else { } else {
responseMsg.paramCode = ResponseMsgParamCodes::unknown; responseMsg.paramCode = ResponseMsgParamCodes::unknown;
rspState = ResponseStates::Error; rspState = ResponseStates::Error;
return DecodeStatus::Error; return DecodeStatus::Error;
} }
default: //case error: default: //case error:
if (c == '\n') { if (c == '\n') {
rspState = ResponseStates::RequestCode; rspState = ResponseStates::RequestCode;
return DecodeStatus::MessageCompleted; return DecodeStatus::MessageCompleted;
} else { } else {
responseMsg.paramCode = ResponseMsgParamCodes::unknown; responseMsg.paramCode = ResponseMsgParamCodes::unknown;
return DecodeStatus::Error; return DecodeStatus::Error;
}
} }
} }
}
uint8_t Protocol::EncodeResponseCmdAR(const RequestMsg &msg, ResponseMsgParamCodes ar, uint8_t *txbuff) { uint8_t Protocol::EncodeResponseCmdAR(const RequestMsg &msg, ResponseMsgParamCodes ar, uint8_t *txbuff) {
txbuff[0] = (uint8_t)msg.code; txbuff[0] = (uint8_t)msg.code;
txbuff[1] = msg.value + '0'; txbuff[1] = msg.value + '0';
txbuff[2] = ' '; txbuff[2] = ' ';
txbuff[3] = (uint8_t)ar; txbuff[3] = (uint8_t)ar;
txbuff[4] = '\n'; txbuff[4] = '\n';
return 5; return 5;
}
uint8_t Protocol::EncodeResponseReadFINDA(const RequestMsg &msg, uint8_t findaValue, uint8_t *txbuff) {
txbuff[0] = (uint8_t)msg.code;
txbuff[1] = msg.value + '0';
txbuff[2] = ' ';
txbuff[3] = (uint8_t)ResponseMsgParamCodes::Accepted;
txbuff[4] = findaValue + '0';
txbuff[5] = '\n';
return 6;
}
uint8_t Protocol::EncodeResponseVersion(const RequestMsg &msg, uint8_t value, uint8_t *txbuff) {
txbuff[0] = (uint8_t)msg.code;
txbuff[1] = msg.value + '0';
txbuff[2] = ' ';
txbuff[3] = (uint8_t)ResponseMsgParamCodes::Accepted;
uint8_t *dst = txbuff + 4;
if (value < 10) {
*dst++ = value + '0';
} else if (value < 100) {
*dst++ = value / 10 + '0';
*dst++ = value % 10 + '0';
} else {
*dst++ = value / 100 + '0';
*dst++ = (value / 10) % 10 + '0';
*dst++ = value % 10 + '0';
} }
*dst = '\n';
return dst - txbuff + 1;
}
uint8_t Protocol::EncodeResponseQueryOperation(const RequestMsg &msg, ResponseMsgParamCodes code, uint8_t value, uint8_t *txbuff) { uint8_t Protocol::EncodeResponseReadFINDA(const RequestMsg &msg, uint8_t findaValue, uint8_t *txbuff) {
txbuff[0] = (uint8_t)msg.code; txbuff[0] = (uint8_t)msg.code;
txbuff[1] = msg.value + '0'; txbuff[1] = msg.value + '0';
txbuff[2] = ' '; txbuff[2] = ' ';
txbuff[3] = (uint8_t)code; txbuff[3] = (uint8_t)ResponseMsgParamCodes::Accepted;
uint8_t *dst = txbuff + 4; txbuff[4] = findaValue + '0';
if (code != ResponseMsgParamCodes::Finished) { txbuff[5] = '\n';
return 6;
}
uint8_t Protocol::EncodeResponseVersion(const RequestMsg &msg, uint8_t value, uint8_t *txbuff) {
txbuff[0] = (uint8_t)msg.code;
txbuff[1] = msg.value + '0';
txbuff[2] = ' ';
txbuff[3] = (uint8_t)ResponseMsgParamCodes::Accepted;
uint8_t *dst = txbuff + 4;
if (value < 10) { if (value < 10) {
*dst++ = value + '0'; *dst++ = value + '0';
} else if (value < 100) { } else if (value < 100) {
@ -213,9 +193,31 @@ uint8_t Protocol::EncodeResponseQueryOperation(const RequestMsg &msg, ResponseMs
*dst++ = (value / 10) % 10 + '0'; *dst++ = (value / 10) % 10 + '0';
*dst++ = value % 10 + '0'; *dst++ = value % 10 + '0';
} }
*dst = '\n';
return dst - txbuff + 1;
} }
*dst = '\n';
return dst - txbuff + 1;
}
uint8_t Protocol::EncodeResponseQueryOperation(const RequestMsg &msg, ResponseMsgParamCodes code, uint8_t value, uint8_t *txbuff) {
txbuff[0] = (uint8_t)msg.code;
txbuff[1] = msg.value + '0';
txbuff[2] = ' ';
txbuff[3] = (uint8_t)code;
uint8_t *dst = txbuff + 4;
if (code != ResponseMsgParamCodes::Finished) {
if (value < 10) {
*dst++ = value + '0';
} else if (value < 100) {
*dst++ = value / 10 + '0';
*dst++ = value % 10 + '0';
} else {
*dst++ = value / 100 + '0';
*dst++ = (value / 10) % 10 + '0';
*dst++ = value % 10 + '0';
}
}
*dst = '\n';
return dst - txbuff + 1;
}
} // namespace protocol
} // namespace modules } // namespace modules

View File

@ -7,60 +7,56 @@
/// @@TODO possibly add some checksum to verify the correctness of messages /// @@TODO possibly add some checksum to verify the correctness of messages
namespace modules { namespace modules {
namespace protocol {
enum class RequestMsgCodes : uint8_t { enum class RequestMsgCodes : uint8_t {
unknown = 0, unknown = 0,
Query = 'Q', Query = 'Q',
Tool = 'T', Tool = 'T',
Load = 'L', Load = 'L',
Mode = 'M', Mode = 'M',
Unload = 'U', Unload = 'U',
Reset = 'X', Reset = 'X',
Finda = 'P', Finda = 'P',
Version = 'S', Version = 'S',
Button = 'B', Button = 'B',
Eject = 'E', Eject = 'E',
Wait = 'W', Wait = 'W',
Cut = 'K' Cut = 'K'
}; };
enum class ResponseMsgParamCodes : uint8_t { enum class ResponseMsgParamCodes : uint8_t {
unknown = 0, unknown = 0,
Processing = 'P', Processing = 'P',
Error = 'E', Error = 'E',
Finished = 'F', Finished = 'F',
Accepted = 'A', Accepted = 'A',
Rejected = 'R' Rejected = 'R'
}; };
/// A request message /// A request message
/// Requests are being sent by the printer into the MMU /// Requests are being sent by the printer into the MMU
/// It is the same structure as the generic Msg /// It is the same structure as the generic Msg
struct RequestMsg { struct RequestMsg {
RequestMsgCodes code; RequestMsgCodes code;
uint8_t value; uint8_t value;
inline RequestMsg(RequestMsgCodes code, uint8_t value) inline RequestMsg(RequestMsgCodes code, uint8_t value)
: code(code) : code(code)
, value(value) {} , value(value) {}
}; };
/// A response message /// A response message
/// Responses are being sent from the MMU into the printer as a response to a request message /// Responses are being sent from the MMU into the printer as a response to a request message
struct ResponseMsg { struct ResponseMsg {
RequestMsg request; ///< response is always preceeded by the request message RequestMsg request; ///< response is always preceeded by the request message
ResponseMsgParamCodes paramCode; ///< parameters of reply ResponseMsgParamCodes paramCode; ///< parameters of reply
uint8_t paramValue; ///< parameters of reply uint8_t paramValue; ///< parameters of reply
inline ResponseMsg(RequestMsg request, ResponseMsgParamCodes paramCode, uint8_t paramValue) inline ResponseMsg(RequestMsg request, ResponseMsgParamCodes paramCode, uint8_t paramValue)
: request(request) : request(request)
, paramCode(paramCode) , paramCode(paramCode)
, paramValue(paramValue) {} , paramValue(paramValue) {}
}; };
/// Protocol class is responsible for creating/decoding messages in Rx/Tx buffer
/// Beware - in the decoding more, it is meant to be a statefull instance which works through public methods
/// processing one input byte per call
class Protocol {
public:
/// Message decoding return value /// Message decoding return value
enum class DecodeStatus : uint_fast8_t { enum class DecodeStatus : uint_fast8_t {
MessageCompleted, ///< message completed and successfully lexed MessageCompleted, ///< message completed and successfully lexed
@ -68,76 +64,82 @@ public:
Error, ///< input character broke message decoding Error, ///< input character broke message decoding
}; };
inline Protocol() /// Protocol class is responsible for creating/decoding messages in Rx/Tx buffer
: rqState(RequestStates::Code) /// Beware - in the decoding more, it is meant to be a statefull instance which works through public methods
, requestMsg(RequestMsgCodes::unknown, 0) /// processing one input byte per call
, rspState(ResponseStates::RequestCode) class Protocol {
, responseMsg(RequestMsg(RequestMsgCodes::unknown, 0), ResponseMsgParamCodes::unknown, 0) { public:
} inline Protocol()
: rqState(RequestStates::Code)
, requestMsg(RequestMsgCodes::unknown, 0)
, rspState(ResponseStates::RequestCode)
, responseMsg(RequestMsg(RequestMsgCodes::unknown, 0), ResponseMsgParamCodes::unknown, 0) {
}
/// Takes the input byte c and steps one step through the state machine /// Takes the input byte c and steps one step through the state machine
/// @returns state of the message being decoded /// @returns state of the message being decoded
DecodeStatus DecodeRequest(uint8_t c); DecodeStatus DecodeRequest(uint8_t c);
/// Decodes response message in rxbuff /// Decodes response message in rxbuff
/// @returns decoded response message structure /// @returns decoded response message structure
DecodeStatus DecodeResponse(uint8_t c); DecodeStatus DecodeResponse(uint8_t c);
/// Encodes request message msg into txbuff memory /// Encodes request message msg into txbuff memory
/// It is expected the txbuff is large enough to fit the message /// It is expected the txbuff is large enough to fit the message
/// @returns number of bytes written into txbuff /// @returns number of bytes written into txbuff
static uint8_t EncodeRequest(const RequestMsg &msg, uint8_t *txbuff); static uint8_t EncodeRequest(const RequestMsg &msg, uint8_t *txbuff);
/// Encode generic response Command Accepted or Rejected /// Encode generic response Command Accepted or Rejected
/// @param msg source request message for this response /// @param msg source request message for this response
/// @returns number of bytes written into txbuff /// @returns number of bytes written into txbuff
static uint8_t EncodeResponseCmdAR(const RequestMsg &msg, ResponseMsgParamCodes ar, uint8_t *txbuff); static uint8_t EncodeResponseCmdAR(const RequestMsg &msg, ResponseMsgParamCodes ar, uint8_t *txbuff);
/// Encode response to Read FINDA query /// Encode response to Read FINDA query
/// @param msg source request message for this response /// @param msg source request message for this response
/// @param findaValue 1/0 (on/off) status of FINDA /// @param findaValue 1/0 (on/off) status of FINDA
/// @returns number of bytes written into txbuff /// @returns number of bytes written into txbuff
static uint8_t EncodeResponseReadFINDA(const RequestMsg &msg, uint8_t findaValue, uint8_t *txbuff); static uint8_t EncodeResponseReadFINDA(const RequestMsg &msg, uint8_t findaValue, uint8_t *txbuff);
/// Encode response to Version query /// Encode response to Version query
/// @param msg source request message for this response /// @param msg source request message for this response
/// @param value version number (0-255) /// @param value version number (0-255)
/// @returns number of bytes written into txbuff /// @returns number of bytes written into txbuff
static uint8_t EncodeResponseVersion(const RequestMsg &msg, uint8_t value, uint8_t *txbuff); static uint8_t EncodeResponseVersion(const RequestMsg &msg, uint8_t value, uint8_t *txbuff);
/// Encode response to Query operation status /// Encode response to Query operation status
/// @param msg source request message for this response /// @param msg source request message for this response
/// @param code status of operation (Processing, Error, Finished) /// @param code status of operation (Processing, Error, Finished)
/// @param value related to status of operation(e.g. error code or progress) /// @param value related to status of operation(e.g. error code or progress)
/// @returns number of bytes written into txbuff /// @returns number of bytes written into txbuff
static uint8_t EncodeResponseQueryOperation(const RequestMsg &msg, ResponseMsgParamCodes code, uint8_t value, uint8_t *txbuff); static uint8_t EncodeResponseQueryOperation(const RequestMsg &msg, ResponseMsgParamCodes code, uint8_t value, uint8_t *txbuff);
/// @returns the most recently lexed request message /// @returns the most recently lexed request message
inline const RequestMsg GetRequestMsg() const { return requestMsg; } inline const RequestMsg GetRequestMsg() const { return requestMsg; }
/// @returns the most recently lexed response message /// @returns the most recently lexed response message
inline const ResponseMsg GetResponseMsg() const { return responseMsg; } inline const ResponseMsg GetResponseMsg() const { return responseMsg; }
private: private:
enum class RequestStates : uint8_t { enum class RequestStates : uint8_t {
Code, ///< starting state - expects message code Code, ///< starting state - expects message code
Value, ///< expecting code value Value, ///< expecting code value
Error ///< automaton in error state Error ///< automaton in error state
};
RequestStates rqState;
RequestMsg requestMsg;
enum class ResponseStates : uint8_t {
RequestCode, ///< starting state - expects message code
RequestValue, ///< expecting code value
ParamCode, ///< expecting param code
ParamValue, ///< expecting param value
Error ///< automaton in error state
};
ResponseStates rspState;
ResponseMsg responseMsg;
}; };
RequestStates rqState; } // namespace protocol
RequestMsg requestMsg;
enum class ResponseStates : uint8_t {
RequestCode, ///< starting state - expects message code
RequestValue, ///< expecting code value
ParamCode, ///< expecting param code
ParamValue, ///< expecting param value
Error ///< automaton in error state
};
ResponseStates rspState;
ResponseMsg responseMsg;
};
} // namespace modules } // namespace modules

View File

@ -3,7 +3,7 @@
#include <vector> #include <vector>
namespace hal { namespace hal {
namespace ADC { namespace adc {
static TADCData values2Return; static TADCData values2Return;
static TADCData::const_iterator rdptr = values2Return.cbegin(); static TADCData::const_iterator rdptr = values2Return.cbegin();
@ -28,5 +28,5 @@ namespace ADC {
return rdptr != values2Return.end() ? *rdptr : 1023; return rdptr != values2Return.end() ? *rdptr : 1023;
} }
} // namespace ADC } // namespace adc
} // namespace hal } // namespace hal

View File

@ -4,11 +4,11 @@
#include <vector> #include <vector>
namespace hal { namespace hal {
namespace ADC { namespace adc {
using TADCData = std::vector<uint16_t>; using TADCData = std::vector<uint16_t>;
void ReinitADC(TADCData &&d, uint8_t ovsmpl); void ReinitADC(TADCData &&d, uint8_t ovsmpl);
} // namespace ADC } // namespace adc
} // namespace hal } // namespace hal

View File

@ -4,21 +4,21 @@
using Catch::Matchers::Equals; using Catch::Matchers::Equals;
bool Step_Basic_One_Button_Test(modules::Buttons &b, uint8_t oversampleFactor, uint8_t testedButtonIndex, uint8_t otherButton1, uint8_t otherButton2) { bool Step_Basic_One_Button_Test(modules::buttons::Buttons &b, uint8_t oversampleFactor, uint8_t testedButtonIndex, uint8_t otherButton1, uint8_t otherButton2) {
for (uint8_t i = 0; i < oversampleFactor; ++i) for (uint8_t i = 0; i < oversampleFactor; ++i)
b.Step(); // should detect the press but remain in detected state - wait for debounce b.Step(hal::adc::ReadADC(0)); // should detect the press but remain in detected state - wait for debounce
CHECK(!b.ButtonPressed(testedButtonIndex)); CHECK(!b.ButtonPressed(testedButtonIndex));
CHECK(!b.ButtonPressed(otherButton1)); CHECK(!b.ButtonPressed(otherButton1));
CHECK(!b.ButtonPressed(otherButton2)); CHECK(!b.ButtonPressed(otherButton2));
for (uint8_t i = 0; i < oversampleFactor; ++i) for (uint8_t i = 0; i < oversampleFactor; ++i)
b.Step(); // reset to waiting b.Step(hal::adc::ReadADC(0)); // reset to waiting
CHECK(b.ButtonPressed(testedButtonIndex)); CHECK(b.ButtonPressed(testedButtonIndex));
CHECK(!b.ButtonPressed(otherButton1)); CHECK(!b.ButtonPressed(otherButton1));
CHECK(!b.ButtonPressed(otherButton2)); CHECK(!b.ButtonPressed(otherButton2));
for (uint8_t i = 0; i < oversampleFactor; ++i) for (uint8_t i = 0; i < oversampleFactor; ++i)
b.Step(); // pressed again, still in debouncing state b.Step(hal::adc::ReadADC(0)); // pressed again, still in debouncing state
CHECK(!b.ButtonPressed(testedButtonIndex)); CHECK(!b.ButtonPressed(testedButtonIndex));
CHECK(!b.ButtonPressed(otherButton1)); CHECK(!b.ButtonPressed(otherButton1));
CHECK(!b.ButtonPressed(otherButton2)); CHECK(!b.ButtonPressed(otherButton2));
@ -27,14 +27,14 @@ bool Step_Basic_One_Button_Test(modules::Buttons &b, uint8_t oversampleFactor, u
} }
/// This test verifies the behaviour of a single button. The other buttons must remain intact. /// This test verifies the behaviour of a single button. The other buttons must remain intact.
bool Step_Basic_One_Button(hal::ADC::TADCData &&d, uint8_t testedButtonIndex) { bool Step_Basic_One_Button(hal::adc::TADCData &&d, uint8_t testedButtonIndex) {
using namespace modules; using namespace modules::buttons;
Buttons b; Buttons b;
// need to oversample the data as debouncing takes 100 cycles to accept a pressed button // need to oversample the data as debouncing takes 100 cycles to accept a pressed button
constexpr uint8_t oversampleFactor = 100; constexpr uint8_t oversampleFactor = 100;
hal::ADC::ReinitADC(std::move(d), oversampleFactor); hal::adc::ReinitADC(std::move(d), oversampleFactor);
uint8_t otherButton1 = 1, otherButton2 = 2; uint8_t otherButton1 = 1, otherButton2 = 2;
switch (testedButtonIndex) { switch (testedButtonIndex) {
@ -53,15 +53,15 @@ bool Step_Basic_One_Button(hal::ADC::TADCData &&d, uint8_t testedButtonIndex) {
TEST_CASE("buttons::Step-basic-button", "[buttons]") { TEST_CASE("buttons::Step-basic-button", "[buttons]") {
{ {
hal::ADC::TADCData d({ 5, 6, 1023 }); hal::adc::TADCData d({ 5, 6, 1023 });
CHECK(Step_Basic_One_Button(std::move(d), 0)); CHECK(Step_Basic_One_Button(std::move(d), 0));
} }
{ {
hal::ADC::TADCData d({ 321, 359, 1023 }); hal::adc::TADCData d({ 321, 359, 1023 });
CHECK(Step_Basic_One_Button(std::move(d), 1)); CHECK(Step_Basic_One_Button(std::move(d), 1));
} }
{ {
hal::ADC::TADCData d({ 501, 529, 1023 }); hal::adc::TADCData d({ 501, 529, 1023 });
CHECK(Step_Basic_One_Button(std::move(d), 2)); CHECK(Step_Basic_One_Button(std::move(d), 2));
} }
} }
@ -70,13 +70,13 @@ TEST_CASE("buttons::Step-basic-button", "[buttons]") {
/// and the Buttons class should press first button and release, then the second one and then the third one /// and the Buttons class should press first button and release, then the second one and then the third one
/// without being reinitialized. /// without being reinitialized.
TEST_CASE("buttons::Step-basic-button-one-after-other", "[buttons]") { TEST_CASE("buttons::Step-basic-button-one-after-other", "[buttons]") {
using namespace modules; using namespace modules::buttons;
hal::ADC::TADCData d({ 5, 6, 1023, 321, 359, 1023, 501, 529, 1023 }); hal::adc::TADCData d({ 5, 6, 1023, 321, 359, 1023, 501, 529, 1023 });
Buttons b; Buttons b;
// need to oversample the data as debouncing takes 100 cycles to accept a pressed button // need to oversample the data as debouncing takes 100 cycles to accept a pressed button
constexpr uint8_t oversampleFactor = 100; constexpr uint8_t oversampleFactor = 100;
hal::ADC::ReinitADC(std::move(d), oversampleFactor); hal::adc::ReinitADC(std::move(d), oversampleFactor);
CHECK(Step_Basic_One_Button_Test(b, oversampleFactor, 0, 1, 2)); CHECK(Step_Basic_One_Button_Test(b, oversampleFactor, 0, 1, 2));
CHECK(Step_Basic_One_Button_Test(b, oversampleFactor, 1, 0, 2)); CHECK(Step_Basic_One_Button_Test(b, oversampleFactor, 1, 0, 2));
@ -85,76 +85,76 @@ TEST_CASE("buttons::Step-basic-button-one-after-other", "[buttons]") {
/// This test tries to simulate a bouncing effect on data from ADC on the first button /// This test tries to simulate a bouncing effect on data from ADC on the first button
TEST_CASE("buttons::Step-debounce-one-button", "[buttons]") { TEST_CASE("buttons::Step-debounce-one-button", "[buttons]") {
using namespace modules; using namespace modules::buttons;
// make a bounce event on the first press // make a bounce event on the first press
hal::ADC::TADCData d({ 5, 1023, 5, 9, 6, 7, 8, 1023, 1023 }); hal::adc::TADCData d({ 5, 1023, 5, 9, 6, 7, 8, 1023, 1023 });
// need to oversample the data as debouncing takes 100 cycles to accept a pressed button // need to oversample the data as debouncing takes 100 cycles to accept a pressed button
constexpr uint8_t oversampleFactor = 25; constexpr uint8_t oversampleFactor = 25;
hal::ADC::ReinitADC(std::move(d), oversampleFactor); hal::adc::ReinitADC(std::move(d), oversampleFactor);
Buttons b; Buttons b;
// 5 // 5
for (uint8_t i = 0; i < oversampleFactor; ++i) for (uint8_t i = 0; i < oversampleFactor; ++i)
b.Step(); // should detect the press but remain in detected state - wait for debounce b.Step(hal::adc::ReadADC(0)); // should detect the press but remain in detected state - wait for debounce
CHECK(!b.ButtonPressed(0)); CHECK(!b.ButtonPressed(0));
CHECK(!b.ButtonPressed(1)); CHECK(!b.ButtonPressed(1));
CHECK(!b.ButtonPressed(2)); CHECK(!b.ButtonPressed(2));
// 1023 // 1023
for (uint8_t i = 0; i < oversampleFactor; ++i) for (uint8_t i = 0; i < oversampleFactor; ++i)
b.Step(); // reset to waiting b.Step(hal::adc::ReadADC(0)); // reset to waiting
CHECK(!b.ButtonPressed(0)); CHECK(!b.ButtonPressed(0));
CHECK(!b.ButtonPressed(1)); CHECK(!b.ButtonPressed(1));
CHECK(!b.ButtonPressed(2)); CHECK(!b.ButtonPressed(2));
// 5 // 5
for (uint8_t i = 0; i < oversampleFactor; ++i) for (uint8_t i = 0; i < oversampleFactor; ++i)
b.Step(); // pressed again, still in debouncing state b.Step(hal::adc::ReadADC(0)); // pressed again, still in debouncing state
CHECK(!b.ButtonPressed(0)); CHECK(!b.ButtonPressed(0));
CHECK(!b.ButtonPressed(1)); CHECK(!b.ButtonPressed(1));
CHECK(!b.ButtonPressed(2)); CHECK(!b.ButtonPressed(2));
// 9 // 9
for (uint8_t i = 0; i < oversampleFactor; ++i) for (uint8_t i = 0; i < oversampleFactor; ++i)
b.Step(); // no change b.Step(hal::adc::ReadADC(0)); // no change
CHECK(!b.ButtonPressed(0)); CHECK(!b.ButtonPressed(0));
CHECK(!b.ButtonPressed(1)); CHECK(!b.ButtonPressed(1));
CHECK(!b.ButtonPressed(2)); CHECK(!b.ButtonPressed(2));
// 6 // 6
for (uint8_t i = 0; i < oversampleFactor; ++i) for (uint8_t i = 0; i < oversampleFactor; ++i)
b.Step(); // no change b.Step(hal::adc::ReadADC(0)); // no change
CHECK(!b.ButtonPressed(0)); CHECK(!b.ButtonPressed(0));
CHECK(!b.ButtonPressed(1)); CHECK(!b.ButtonPressed(1));
CHECK(!b.ButtonPressed(2)); CHECK(!b.ButtonPressed(2));
// 7 // 7
for (uint8_t i = 0; i < oversampleFactor; ++i) for (uint8_t i = 0; i < oversampleFactor; ++i)
b.Step(); // one step from "pressed" b.Step(hal::adc::ReadADC(0)); // one step from "pressed"
CHECK(!b.ButtonPressed(0)); CHECK(!b.ButtonPressed(0));
CHECK(!b.ButtonPressed(1)); CHECK(!b.ButtonPressed(1));
CHECK(!b.ButtonPressed(2)); CHECK(!b.ButtonPressed(2));
// 8 // 8
for (uint8_t i = 0; i < oversampleFactor; ++i) for (uint8_t i = 0; i < oversampleFactor; ++i)
b.Step(); // fifth set of samples - should report "pressed" finally b.Step(hal::adc::ReadADC(0)); // fifth set of samples - should report "pressed" finally
CHECK(b.ButtonPressed(0)); CHECK(b.ButtonPressed(0));
CHECK(!b.ButtonPressed(1)); CHECK(!b.ButtonPressed(1));
CHECK(!b.ButtonPressed(2)); CHECK(!b.ButtonPressed(2));
// 1023 // 1023
for (uint8_t i = 0; i < oversampleFactor; ++i) for (uint8_t i = 0; i < oversampleFactor; ++i)
b.Step(); // sixth set of samples - button released (no debouncing on release) b.Step(hal::adc::ReadADC(0)); // sixth set of samples - button released (no debouncing on release)
CHECK(!b.ButtonPressed(0)); CHECK(!b.ButtonPressed(0));
CHECK(!b.ButtonPressed(1)); CHECK(!b.ButtonPressed(1));
CHECK(!b.ButtonPressed(2)); CHECK(!b.ButtonPressed(2));
// 1023 // 1023
for (uint8_t i = 0; i < oversampleFactor; ++i) for (uint8_t i = 0; i < oversampleFactor; ++i)
b.Step(); // seventh set of samples - still released b.Step(hal::adc::ReadADC(0)); // seventh set of samples - still released
CHECK(!b.ButtonPressed(0)); CHECK(!b.ButtonPressed(0));
CHECK(!b.ButtonPressed(1)); CHECK(!b.ButtonPressed(1));
CHECK(!b.ButtonPressed(2)); CHECK(!b.ButtonPressed(2));

View File

@ -4,7 +4,7 @@
using Catch::Matchers::Equals; using Catch::Matchers::Equals;
TEST_CASE("protocol::EncodeRequests", "[protocol]") { TEST_CASE("protocol::EncodeRequests", "[protocol]") {
using namespace modules; using namespace modules::protocol;
RequestMsgCodes code; RequestMsgCodes code;
uint8_t value; uint8_t value;
@ -45,7 +45,7 @@ TEST_CASE("protocol::EncodeRequests", "[protocol]") {
} }
TEST_CASE("protocol::EncodeResponseCmdAR", "[protocol]") { TEST_CASE("protocol::EncodeResponseCmdAR", "[protocol]") {
using namespace modules; using namespace modules::protocol;
auto requestMsg = GENERATE( auto requestMsg = GENERATE(
RequestMsg(RequestMsgCodes::Button, 0), RequestMsg(RequestMsgCodes::Button, 0),
@ -93,7 +93,7 @@ TEST_CASE("protocol::EncodeResponseCmdAR", "[protocol]") {
} }
TEST_CASE("protocol::EncodeResponseReadFINDA", "[protocol]") { TEST_CASE("protocol::EncodeResponseReadFINDA", "[protocol]") {
using namespace modules; using namespace modules::protocol;
auto requestMsg = RequestMsg(RequestMsgCodes::Finda, 0); auto requestMsg = RequestMsg(RequestMsgCodes::Finda, 0);
uint8_t findaStatus = GENERATE(0, 1); uint8_t findaStatus = GENERATE(0, 1);
@ -111,7 +111,7 @@ TEST_CASE("protocol::EncodeResponseReadFINDA", "[protocol]") {
} }
TEST_CASE("protocol::EncodeResponseVersion", "[protocol]") { TEST_CASE("protocol::EncodeResponseVersion", "[protocol]") {
using namespace modules; using namespace modules::protocol;
std::uint8_t versionQueryType = GENERATE(0, 1, 2, 3); std::uint8_t versionQueryType = GENERATE(0, 1, 2, 3);
auto requestMsg = RequestMsg(RequestMsgCodes::Version, versionQueryType); auto requestMsg = RequestMsg(RequestMsgCodes::Version, versionQueryType);
@ -142,7 +142,7 @@ TEST_CASE("protocol::EncodeResponseVersion", "[protocol]") {
} }
TEST_CASE("protocol::EncodeResponseQueryOperation", "[protocol]") { TEST_CASE("protocol::EncodeResponseQueryOperation", "[protocol]") {
using namespace modules; using namespace modules::protocol;
auto requestMsg = GENERATE( auto requestMsg = GENERATE(
RequestMsg(RequestMsgCodes::Cut, 0), RequestMsg(RequestMsgCodes::Cut, 0),
@ -202,7 +202,7 @@ TEST_CASE("protocol::EncodeResponseQueryOperation", "[protocol]") {
} }
TEST_CASE("protocol::DecodeRequest", "[protocol]") { TEST_CASE("protocol::DecodeRequest", "[protocol]") {
using namespace modules; using namespace modules::protocol;
Protocol p; Protocol p;
const char *rxbuff = GENERATE( const char *rxbuff = GENERATE(
"B0\n", "B1\n", "B2\n", "B0\n", "B1\n", "B2\n",
@ -226,9 +226,9 @@ TEST_CASE("protocol::DecodeRequest", "[protocol]") {
break; break;
} else if (c == '\n') { } else if (c == '\n') {
// regular end of message line // regular end of message line
CHECK(p.DecodeRequest(c) == Protocol::DecodeStatus::MessageCompleted); CHECK(p.DecodeRequest(c) == DecodeStatus::MessageCompleted);
} else { } else {
CHECK(p.DecodeRequest(c) == Protocol::DecodeStatus::NeedMoreData); CHECK(p.DecodeRequest(c) == DecodeStatus::NeedMoreData);
} }
} }
@ -239,7 +239,7 @@ TEST_CASE("protocol::DecodeRequest", "[protocol]") {
} }
TEST_CASE("protocol::DecodeResponseReadFinda", "[protocol]") { TEST_CASE("protocol::DecodeResponseReadFinda", "[protocol]") {
using namespace modules; using namespace modules::protocol;
Protocol p; Protocol p;
const char *rxbuff = GENERATE( const char *rxbuff = GENERATE(
"P0 A0\n", "P0 A0\n",
@ -253,9 +253,9 @@ TEST_CASE("protocol::DecodeResponseReadFinda", "[protocol]") {
break; break;
} else if (c == '\n') { } else if (c == '\n') {
// regular end of message line // regular end of message line
CHECK(p.DecodeResponse(c) == Protocol::DecodeStatus::MessageCompleted); CHECK(p.DecodeResponse(c) == DecodeStatus::MessageCompleted);
} else { } else {
CHECK(p.DecodeResponse(c) == Protocol::DecodeStatus::NeedMoreData); CHECK(p.DecodeResponse(c) == DecodeStatus::NeedMoreData);
} }
} }
@ -268,7 +268,7 @@ TEST_CASE("protocol::DecodeResponseReadFinda", "[protocol]") {
} }
TEST_CASE("protocol::DecodeResponseQueryOperation", "[protocol]") { TEST_CASE("protocol::DecodeResponseQueryOperation", "[protocol]") {
using namespace modules; using namespace modules::protocol;
Protocol p; Protocol p;
const char *cmdReference = GENERATE( const char *cmdReference = GENERATE(
"E0", "E1", "E2", "E3", "E4", "E0", "E1", "E2", "E3", "E4",
@ -294,9 +294,9 @@ TEST_CASE("protocol::DecodeResponseQueryOperation", "[protocol]") {
break; break;
} else if (c == '\n') { } else if (c == '\n') {
// regular end of message line // regular end of message line
CHECK(p.DecodeResponse(c) == Protocol::DecodeStatus::MessageCompleted); CHECK(p.DecodeResponse(c) == DecodeStatus::MessageCompleted);
} else { } else {
CHECK(p.DecodeResponse(c) == Protocol::DecodeStatus::NeedMoreData); CHECK(p.DecodeResponse(c) == DecodeStatus::NeedMoreData);
} }
} }
@ -311,81 +311,81 @@ TEST_CASE("protocol::DecodeResponseQueryOperation", "[protocol]") {
} }
TEST_CASE("protocol::DecodeRequestErrors", "[protocol]") { TEST_CASE("protocol::DecodeRequestErrors", "[protocol]") {
using namespace modules; using namespace modules::protocol;
Protocol p; Protocol p;
const char b0[] = "b0"; const char b0[] = "b0";
CHECK(p.DecodeRequest(b0[0]) == Protocol::DecodeStatus::Error); CHECK(p.DecodeRequest(b0[0]) == DecodeStatus::Error);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
CHECK(p.DecodeRequest(b0[1]) == Protocol::DecodeStatus::Error); CHECK(p.DecodeRequest(b0[1]) == DecodeStatus::Error);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
// reset protokol decoder // reset protokol decoder
CHECK(p.DecodeRequest('\n') == Protocol::DecodeStatus::MessageCompleted); CHECK(p.DecodeRequest('\n') == DecodeStatus::MessageCompleted);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
const char B1_[] = "B1 \n"; const char B1_[] = "B1 \n";
CHECK(p.DecodeRequest(B1_[0]) == Protocol::DecodeStatus::NeedMoreData); CHECK(p.DecodeRequest(B1_[0]) == DecodeStatus::NeedMoreData);
CHECK(p.DecodeRequest(B1_[1]) == Protocol::DecodeStatus::NeedMoreData); CHECK(p.DecodeRequest(B1_[1]) == DecodeStatus::NeedMoreData);
CHECK(p.DecodeRequest(B1_[2]) == Protocol::DecodeStatus::Error); CHECK(p.DecodeRequest(B1_[2]) == DecodeStatus::Error);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
CHECK(p.DecodeRequest(B1_[3]) == Protocol::DecodeStatus::MessageCompleted); CHECK(p.DecodeRequest(B1_[3]) == DecodeStatus::MessageCompleted);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
const char _B2[] = " B2\n"; const char _B2[] = " B2\n";
CHECK(p.DecodeRequest(_B2[0]) == Protocol::DecodeStatus::Error); CHECK(p.DecodeRequest(_B2[0]) == DecodeStatus::Error);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
CHECK(p.DecodeRequest(_B2[1]) == Protocol::DecodeStatus::Error); CHECK(p.DecodeRequest(_B2[1]) == DecodeStatus::Error);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
CHECK(p.DecodeRequest(_B2[2]) == Protocol::DecodeStatus::Error); CHECK(p.DecodeRequest(_B2[2]) == DecodeStatus::Error);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
CHECK(p.DecodeRequest(_B2[3]) == Protocol::DecodeStatus::MessageCompleted); CHECK(p.DecodeRequest(_B2[3]) == DecodeStatus::MessageCompleted);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
const char _B0_[] = " B0 "; const char _B0_[] = " B0 ";
CHECK(p.DecodeRequest(_B0_[0]) == Protocol::DecodeStatus::Error); CHECK(p.DecodeRequest(_B0_[0]) == DecodeStatus::Error);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
CHECK(p.DecodeRequest(_B0_[1]) == Protocol::DecodeStatus::Error); CHECK(p.DecodeRequest(_B0_[1]) == DecodeStatus::Error);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
CHECK(p.DecodeRequest(_B0_[2]) == Protocol::DecodeStatus::Error); CHECK(p.DecodeRequest(_B0_[2]) == DecodeStatus::Error);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
CHECK(p.DecodeRequest(_B0_[3]) == Protocol::DecodeStatus::Error); CHECK(p.DecodeRequest(_B0_[3]) == DecodeStatus::Error);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
CHECK(p.DecodeRequest('\n') == Protocol::DecodeStatus::MessageCompleted); CHECK(p.DecodeRequest('\n') == DecodeStatus::MessageCompleted);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
} }
TEST_CASE("protocol::DecodeResponseErrors", "[protocol]") { TEST_CASE("protocol::DecodeResponseErrors", "[protocol]") {
using namespace modules; using namespace modules::protocol;
Protocol p; Protocol p;
const char b0[] = "b0 A\n"; const char b0[] = "b0 A\n";
CHECK(p.DecodeRequest(b0[0]) == Protocol::DecodeStatus::Error); CHECK(p.DecodeRequest(b0[0]) == DecodeStatus::Error);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
CHECK(p.DecodeRequest(b0[1]) == Protocol::DecodeStatus::Error); CHECK(p.DecodeRequest(b0[1]) == DecodeStatus::Error);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
CHECK(p.DecodeRequest(b0[2]) == Protocol::DecodeStatus::Error); CHECK(p.DecodeRequest(b0[2]) == DecodeStatus::Error);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
CHECK(p.DecodeRequest(b0[3]) == Protocol::DecodeStatus::Error); CHECK(p.DecodeRequest(b0[3]) == DecodeStatus::Error);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
CHECK(p.DecodeRequest(b0[4]) == Protocol::DecodeStatus::MessageCompleted); CHECK(p.DecodeRequest(b0[4]) == DecodeStatus::MessageCompleted);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
const char b1[] = "b0A\n"; const char b1[] = "b0A\n";
CHECK(p.DecodeRequest(b1[0]) == Protocol::DecodeStatus::Error); CHECK(p.DecodeRequest(b1[0]) == DecodeStatus::Error);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
CHECK(p.DecodeRequest(b1[1]) == Protocol::DecodeStatus::Error); CHECK(p.DecodeRequest(b1[1]) == DecodeStatus::Error);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
CHECK(p.DecodeRequest(b1[2]) == Protocol::DecodeStatus::Error); CHECK(p.DecodeRequest(b1[2]) == DecodeStatus::Error);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
CHECK(p.DecodeRequest(b1[3]) == Protocol::DecodeStatus::MessageCompleted); CHECK(p.DecodeRequest(b1[3]) == DecodeStatus::MessageCompleted);
CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown); CHECK(p.GetRequestMsg().code == RequestMsgCodes::unknown);
} }
// Beware - this test makes 18M+ combinations, run only when changing the implementation of the codec // Beware - this test makes 18M+ combinations, run only when changing the implementation of the codec
// Therefore it is disabled [.] by default // Therefore it is disabled [.] by default
TEST_CASE("protocol::DecodeResponseErrorsCross", "[protocol][.]") { TEST_CASE("protocol::DecodeResponseErrorsCross", "[protocol][.]") {
using namespace modules; using namespace modules::protocol;
Protocol p; Protocol p;
const char *validInitialSpaces = ""; const char *validInitialSpaces = "";
@ -434,7 +434,7 @@ TEST_CASE("protocol::DecodeResponseErrorsCross", "[protocol][.]") {
bool shouldPass = viInitialSpace && viReqCode && /*viReqValue && */ viSpace && viRspCode && viTerminatingSpaces; bool shouldPass = viInitialSpace && viReqCode && /*viReqValue && */ viSpace && viRspCode && viTerminatingSpaces;
bool failed = false; bool failed = false;
std::for_each(msg.cbegin(), msg.cend(), [&](uint8_t c) { std::for_each(msg.cbegin(), msg.cend(), [&](uint8_t c) {
if (p.DecodeResponse(c) == Protocol::DecodeStatus::Error) { if (p.DecodeResponse(c) == DecodeStatus::Error) {
failed = true; failed = true;
} }
}); });