From f715efc5afc7860d554b821152ef25bbadd2000c Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 3 Aug 2020 21:49:29 -0400 Subject: [PATCH 01/17] Change enum values. Set default machine type to NoMachine. --- README.md | 30 ++--- src/ayab/encoders.cpp | 53 ++++---- src/ayab/encoders.h | 101 ++++++++------- src/ayab/knitter.cpp | 138 +++++++++++--------- src/ayab/knitter.h | 24 ++-- test/mocks/encoders_mock.cpp | 20 ++- test/test_encoders.cpp | 11 +- test/test_knitter.cpp | 235 +++++++++++++++++++---------------- 8 files changed, 345 insertions(+), 267 deletions(-) diff --git a/README.md b/README.md index a9aff2dd4..815465bab 100644 --- a/README.md +++ b/README.md @@ -20,41 +20,43 @@ In [ayab-desktop](https://github.com/AllYarnsAreBeautiful/ayab-desktop): go to T To set up a working development environment follow these steps: - 0. Clone the repository and update all submodules + 0. Clone the repository and update all submodules. - `git clone --recurse-submodules https://github.com/AllYarnsAreBeautiful/ayab-firmware.git ayab` + Ubuntu: + ```bash + sudo apt install -y git + git clone --recurse-submodules https://github.com/AllYarnsAreBeautiful/ayab-firmware.git ayab + ``` - 1. Install the [Arduino.mk](https://github.com/sudar/Arduino-Makefile) package and setup environment variables + 1. Install the [Arduino.mk](https://github.com/sudar/Arduino-Makefile) package and setup environment variables. Ubuntu: ```bash - sudo apt install arduino-mk + sudo apt install -y arduino-mk cmake export ARDMK_DIR=/usr/share/arduino ``` - Running ./build.sh should work now. + Running `./build.sh` should work now. - 2. Install clang-format and gcovr + + 2. Install `clang-format` and `gcovr`. Ubuntu: ```bash sudo apt install clang-format gcovr ``` - 3. Install [pre-commit](https://pre-commit.com/) via pip and use it to install git hooks - - ```bash + 3. Install [pre-commit](https://pre-commit.com/) via pip and use it to install git hooks. + ``` pip3 install --user pre-commit pre-commit install ``` - 4. Optionally create a pre-push hook + 4. Optionally create a pre-push hook. - Add the following snippet in a file called .git/hooks/pre-push - ```bash + Add the following snippet in a file called `.git/hooks/pre-push`: + ``` #!/bin/bash set -e - ./build.sh ./test/test.sh -c ``` - diff --git a/src/ayab/encoders.cpp b/src/ayab/encoders.cpp index 473187295..e09530031 100644 --- a/src/ayab/encoders.cpp +++ b/src/ayab/encoders.cpp @@ -26,18 +26,11 @@ #include "board.h" #include "encoders.h" -/*! - * \brief Set machine type. - */ -void Encoders::init(Machine_t machineType) { - m_machineType = machineType; -} - /*! * \brief Service encoder A interrupt routine. * - * Determines edge of signal and deferres to private rising/falling - * functions. + * Determines edge of signal and dispatches to private rising/falling functions. + * m_machineType assumed valid. */ void Encoders::encA_interrupt() { m_hallActive = NoDirection; @@ -62,38 +55,45 @@ uint8_t Encoders::getPosition() const { /*! * \brief Get beltshift member. */ -Beltshift_t Encoders::getBeltshift() { +Beltshift_t Encoders::getBeltshift() const { return m_beltShift; } /*! * \brief Get direction member. */ -Direction_t Encoders::getDirection() { +Direction_t Encoders::getDirection() const { return m_direction; } /*! * \brief Get hallActive member. */ -Direction_t Encoders::getHallActive() { +Direction_t Encoders::getHallActive() const { return m_hallActive; } /*! * \brief Get carriage member. */ -Carriage_t Encoders::getCarriage() { +Carriage_t Encoders::getCarriage() const { return m_carriage; } /*! * \brief Get machine type. */ -Machine_t Encoders::getMachineType() { +Machine_t Encoders::getMachineType() const { return m_machineType; } +/*! + * \brief Set machine type. + */ +void Encoders::init(Machine_t machineType) { + m_machineType = machineType; +} + /*! * \brief Read hall sensor on left and right. */ @@ -108,10 +108,14 @@ uint16_t Encoders::getHallValue(Direction_t pSensor) { } } -/* Private Methods */ +// Private Methods /*! + * \brief Interrupt service subroutine. * + * Called when encoder pin A is rising. + * Must execute as fast as possible. + * Bounds on m_machineType not checked. */ void Encoders::encA_rising() { // Direction only decided on rising edge of encoder A @@ -126,17 +130,18 @@ void Encoders::encA_rising() { // In front of Left Hall Sensor? uint16_t hallValue = analogRead(EOL_PIN_L); - if ((hallValue < FILTER_L_MIN[m_machineType]) || + if ((hallValue < FILTER_L_MIN[m_machineType]) || (hallValue > FILTER_L_MAX[m_machineType])) { m_hallActive = Left; // TODO(chris): Verify these decisions! - if ((m_machineType == Kh270) || (hallValue >= FILTER_L_MIN[m_machineType])) { - m_carriage = K; - } else if (m_carriage == K /*&& m_encoderPos == ?? */) { - m_carriage = G; + if ((m_machineType == Kh270) || + (hallValue >= FILTER_L_MIN[m_machineType])) { + m_carriage = Knit; + } else if (m_carriage == Knit /*&& m_encoderPos == ?? */) { + m_carriage = Garter; } else { - m_carriage = L; + m_carriage = Lace; } // Belt shift signal only decided in front of hall sensor @@ -148,7 +153,11 @@ void Encoders::encA_rising() { } /*! + * \brief Interrupt service subroutine. * + * Called when encoder pin A is falling. + * Must execute as fast as possible. + * Bounds on m_machineType not checked. */ void Encoders::encA_falling() { // Update carriage position @@ -171,7 +180,7 @@ void Encoders::encA_falling() { m_hallActive = Right; if (hallValueSmall) { - m_carriage = K; + m_carriage = Knit; } // Belt shift signal only decided in front of hall sensor diff --git a/src/ayab/encoders.h b/src/ayab/encoders.h index 55df51929..b26437f6f 100644 --- a/src/ayab/encoders.h +++ b/src/ayab/encoders.h @@ -30,23 +30,28 @@ // Enumerated constants -enum Direction { NoDirection, Left, Right, NUM_DIRECTIONS }; +enum Direction { NoDirection = -1, Left = 0, Right = 1, NUM_DIRECTIONS = 2 }; using Direction_t = enum Direction; -enum Carriage { NoCarriage, K, L, G, NUM_CARRIAGES }; +enum Carriage { + NoCarriage = -1, + Knit = 0, + Lace = 1, + Garter = 2, + NUM_CARRIAGES = 3 +}; using Carriage_t = enum Carriage; -enum Beltshift { - Unknown, - Regular, - Shifted, - Lace_Regular, - Lace_Shifted, - NUM_BELTSHIFTS -}; +enum Beltshift { Unknown, Regular, Shifted, Lace_Regular, Lace_Shifted }; using Beltshift_t = enum Beltshift; -enum MachineType { Kh910, Kh930, Kh270, NUM_MACHINES }; +enum MachineType { + NoMachine = -1, + Kh910 = 0, + Kh930 = 1, + Kh270 = 2, + NUM_MACHINES = 3 +}; using Machine_t = enum MachineType; // Machine constants @@ -56,39 +61,40 @@ constexpr uint8_t LINE_BUFFER_LEN[NUM_MACHINES] = {25, 25, 15}; constexpr uint8_t END_OF_LINE_OFFSET_L[NUM_MACHINES] = {12, 12, 6}; constexpr uint8_t END_OF_LINE_OFFSET_R[NUM_MACHINES] = {12, 12, 6}; -constexpr uint8_t END_LEFT[NUM_MACHINES] = { 0U, 0U, 0U}; -constexpr uint8_t END_RIGHT[NUM_MACHINES] = { 255U, 255U, 140U}; -constexpr uint8_t END_OFFSET[NUM_MACHINES] = { 28U, 28U, 14U}; +constexpr uint8_t END_LEFT[NUM_MACHINES] = {0U, 0U, 0U}; +constexpr uint8_t END_RIGHT[NUM_MACHINES] = {255U, 255U, 140U}; +constexpr uint8_t END_OFFSET[NUM_MACHINES] = {28U, 28U, 14U}; constexpr uint8_t START_OFFSET[NUM_MACHINES][NUM_DIRECTIONS][NUM_CARRIAGES] = { -// KH910 - {// NC, K, L, G - {0, 0, 0, 0}, // NoDirection - {0, 40, 40, 8}, // Left - {0, 16, 16, 32} // Right - }, -// KH930 - {// NC, K, L, G - {0, 0, 0, 0}, // NoDirection - {0, 40, 40, 8}, // Left - {0, 16, 16, 32} // Right - }, -// KH270 - {// NC, K - {0, 0, 0, 0}, // NoDirection - {0, 14, 0, 0}, // Left - {0, 2, 0, 0} // Right - } -}; + // KH910 + { + // K, L, G + {40, 40, 8}, // Left + {16, 16, 32} // Right + }, + // KH930 + { + // K, L, G + {40, 40, 8}, // Left + {16, 16, 32} // Right + }, + // KH270 + { + // K + {14, 0, 0}, // Left + {2, 0, 0} // Right + }}; // Should be calibrated to each device -// These values are for the K carriage +// Below filter minimum -> Lace carriage +// Above filter maximum -> Knit carriage // KH910 KH930 KH270 -constexpr uint16_t FILTER_L_MIN[NUM_MACHINES] = { 200U, 200U, 200U}; // below: L Carriage -constexpr uint16_t FILTER_L_MAX[NUM_MACHINES] = { 600U, 600U, 600U}; // above: K Carriage -constexpr uint16_t FILTER_R_MIN[NUM_MACHINES] = { 200U, 0U, 0U}; +constexpr uint16_t FILTER_L_MIN[NUM_MACHINES] = {200U, 200U, 200U}; +constexpr uint16_t FILTER_L_MAX[NUM_MACHINES] = {600U, 600U, 600U}; +constexpr uint16_t FILTER_R_MIN[NUM_MACHINES] = {200U, 0U, 0U}; constexpr uint16_t FILTER_R_MAX[NUM_MACHINES] = {1023U, 600U, 600U}; -constexpr uint16_t SOLENOIDS_BITMASK[NUM_MACHINES] = {0xFFFFU, 0xFFFFU, 0x7FF8}; + +constexpr uint16_t SOLENOIDS_BITMASK = 0xFFFFU; /*! * \brief Encoder interface. @@ -98,25 +104,26 @@ constexpr uint16_t SOLENOIDS_BITMASK[NUM_MACHINES] = {0xFFFFU, 0xFFFFU, 0x7FF8}; class Encoders { public: Encoders() = default; - void init(Machine_t machineType); void encA_interrupt(); - uint8_t getPosition() const; - Beltshift_t getBeltshift(); - Direction_t getDirection(); - Direction_t getHallActive(); - Carriage_t getCarriage(); - Machine_t getMachineType(); - static uint16_t getHallValue(Direction_t pSensor); + // getter/setter functions to assist mocking + uint8_t getPosition() const; + Beltshift_t getBeltshift() const; + Direction_t getDirection() const; + Direction_t getHallActive() const; + Carriage_t getCarriage() const; + Machine_t getMachineType() const; + void init(Machine_t machineType); + private: Direction_t m_direction = NoDirection; Direction_t m_hallActive = NoDirection; Beltshift_t m_beltShift = Unknown; Carriage_t m_carriage = NoCarriage; - Machine_t m_machineType = Kh910; + Machine_t m_machineType = NoMachine; uint8_t m_encoderPos = 0x00; bool m_oldState = false; diff --git a/src/ayab/knitter.cpp b/src/ayab/knitter.cpp index a8a10d645..328260051 100644 --- a/src/ayab/knitter.cpp +++ b/src/ayab/knitter.cpp @@ -32,33 +32,13 @@ constexpr uint8_t UINT8_MAX = 0xFFU; constexpr uint16_t UINT16_MAX = 0xFFFFU; #endif -#ifndef AYAB_TESTS -/*! - * \brief Wrapper for knitter's isr. - * - * This is needed since a non-static method cannot be - * passed to _attachInterrupt_. - */ -void isr_wrapper() { - extern Knitter *knitter; - knitter->isr(); -} -#endif - /*! * \brief Knitter constructor. * * Initializes the solenoids as well as pins and interrupts. */ -Knitter::Knitter() : - // incorporated by composition - m_beeper(), m_serial_encoding() { - +Knitter::Knitter() { pinMode(ENC_PIN_A, INPUT); -#ifndef AYAB_TESTS - // Attaching ENC_PIN_A(=2), Interrupt No. 0 - attachInterrupt(0, isr_wrapper, CHANGE); -#endif pinMode(ENC_PIN_B, INPUT); pinMode(ENC_PIN_C, INPUT); @@ -71,7 +51,7 @@ Knitter::Knitter() : #if DBG_NOMACHINE pinMode(DBG_BTN_PIN, INPUT); #endif - + m_solenoids.init(); } @@ -91,8 +71,14 @@ void Knitter::send(uint8_t *payload, size_t length) { m_serial_encoding.send(payload, length); } +/*! + * \brief Interrupt service routine. + * + * Update machine state data. + * Must execute as fast as possible. + * Machine type assumed valid. + */ void Knitter::isr() { - // Update machine state data m_encoders.encA_interrupt(); m_position = m_encoders.getPosition(); m_direction = m_encoders.getDirection(); @@ -101,6 +87,19 @@ void Knitter::isr() { m_carriage = m_encoders.getCarriage(); } +#ifndef AYAB_TESTS +/*! + * \brief Wrapper for knitter's isr. + * + * This is needed since a non-static method cannot be + * passed to _attachInterrupt_. + */ +void isr_wrapper() { + extern Knitter *knitter; + knitter->isr(); +} +#endif + /*! * \brief Dispatch on machine state * @@ -132,17 +131,15 @@ void Knitter::fsm() { /*! * \brief Enter operate state. - * - * \todo sl: Check that functionality is correct after removing always true - * comparison. */ -bool Knitter::startOperation(Machine_t machineType, uint8_t startNeedle, uint8_t stopNeedle, - uint8_t *pattern_start, bool continuousReportingEnabled) { - if ((pattern_start == nullptr) || - (m_opState != s_ready) || - (startNeedle >= stopNeedle) || - (stopNeedle >= NUM_NEEDLES[machineType])) { - return false; +bool Knitter::startOperation(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled) { + if ((m_opState != s_ready) || (machineType == NoMachine) || + (machineType >= NUM_MACHINES) || (pattern_start == nullptr) || + (startNeedle >= stopNeedle) || (stopNeedle >= NUM_NEEDLES[machineType])) { + // TODO(TP): error code + return false; } // Record argument values @@ -154,7 +151,7 @@ bool Knitter::startOperation(Machine_t machineType, uint8_t startNeedle, uint8_t // Reset variables to start conditions m_currentLineNumber = UINT8_MAX; // because counter will - // be increased before request + // be incremented before request m_lineRequested = false; m_lastLineFlag = false; @@ -165,8 +162,18 @@ bool Knitter::startOperation(Machine_t machineType, uint8_t startNeedle, uint8_t m_opState = s_operate; Beeper::ready(); - // TODO(sl): Not used? Can be removed? - m_lastLinesCountdown = 2; + /* m_lastLinesCountdown = 2; */ + +#ifndef AYAB_TESTS + // Attaching ENC_PIN_A, Interrupt No. 0 + // This interrupt cannot be enabled until + // the machine type has been validated. + /* + // `digitalPinToInterrupt` macro not backported until Arduino !DE v.1.0.6 + attachInterrupt(digitalPinToInterrupt(ENC_PIN_A), isr_wrapper, CHANGE); + */ + attachInterrupt(0, isr_wrapper, CHANGE); +#endif // success return true; @@ -184,13 +191,13 @@ bool Knitter::startTest() { bool Knitter::setNextLine(uint8_t lineNumber) { bool success = false; if (m_lineRequested) { - // Is there even a need for a new line? + // FIXME: Is there even a need for a new line? if (lineNumber == m_currentLineNumber) { m_lineRequested = false; Beeper::finishedLine(); success = true; } else { - // line numbers didnt match -> request again + // line numbers didn't match -> request again reqLine(m_currentLineNumber); } } @@ -202,7 +209,7 @@ void Knitter::setLastLine() { m_lastLineFlag = true; } -/* Private Methods */ +// Private Methods void Knitter::state_init() { #ifdef DBG_NOMACHINE @@ -215,7 +222,7 @@ void Knitter::state_init() { if (Right == m_direction && Left == m_hallActive) { #endif // DBG_NOMACHINE m_opState = s_ready; - m_solenoids.setSolenoids(SOLENOIDS_BITMASK[m_machineType]); + m_solenoids.setSolenoids(SOLENOIDS_BITMASK); indState(true); } @@ -225,13 +232,13 @@ void Knitter::state_init() { } void Knitter::state_ready() { - digitalWrite(LED_PIN_A, 0); + digitalWrite(LED_PIN_A, 0); // green LED off // This state is left when the startOperation() method - // is called successfully by main() + // is called successfully by fsm() } void Knitter::state_operate() { - digitalWrite(LED_PIN_A, 1); + digitalWrite(LED_PIN_A, 1); // green LED on if (m_firstRun) { m_firstRun = false; @@ -267,13 +274,14 @@ void Knitter::state_operate() { return; } + // m_machineType has been validated if ((m_pixelToSet >= m_startNeedle - END_OF_LINE_OFFSET_L[m_machineType]) && - (m_pixelToSet <= m_stopNeedle + END_OF_LINE_OFFSET_R[m_machineType])) { + (m_pixelToSet <= m_stopNeedle + END_OF_LINE_OFFSET_R[m_machineType])) { if ((m_pixelToSet >= m_startNeedle) && (m_pixelToSet <= m_stopNeedle)) { // When inside the active needles if (m_machineType == Kh270) { - digitalWrite(LED_PIN_B, 1); // yellow LED on + digitalWrite(LED_PIN_B, 1); // yellow LED on } m_workedOnLine = true; } @@ -288,7 +296,7 @@ void Knitter::state_operate() { } else { // Outside of the active needles if (m_machineType == Kh270) { - digitalWrite(LED_PIN_B, 0); // yellow LED off + digitalWrite(LED_PIN_B, 0); // yellow LED off } // Reset Solenoids when out of range @@ -302,11 +310,7 @@ void Knitter::state_operate() { // request new Line from Host reqLine(++m_currentLineNumber); } else if (m_lastLineFlag) { - Beeper::endWork(); - m_opState = s_ready; - - m_solenoids.setSolenoids(SOLENOIDS_BITMASK[m_machineType]); - Beeper::finishedLine(); + stopOperation(); } } } @@ -314,6 +318,25 @@ void Knitter::state_operate() { #endif // DBG_NOMACHINE } +void Knitter::stopOperation() { + Beeper::endWork(); + m_opState = s_ready; + + m_solenoids.setSolenoids(SOLENOIDS_BITMASK); + Beeper::finishedLine(); + +#ifndef AYAB_TESTS + // Attaching ENC_PIN_A, Interrupt No. 0 + // This interrupt cannot be enabled until + // the machine type has been validated. + /* + // `digitalPinToInterrupt` macro not backported until Arduino !DE v.1.0.6 + detachInterrupt(digitalPinToInterrupt(ENC_PIN_A)); + */ + detachInterrupt(0); +#endif +} + void Knitter::state_test() { if (m_sOldPosition != m_position) { // Only act if there is an actual change of position @@ -347,7 +370,7 @@ bool Knitter::calculatePixelAndSolenoid() { } else if (Shifted == m_beltshift) { m_solenoidToSet = (m_position - HALF_SOLENOIDS_NUM) % SOLENOIDS_NUM; } - if (L == m_carriage) { + if (Lace == m_carriage) { m_pixelToSet = m_pixelToSet + HALF_SOLENOIDS_NUM; } } @@ -370,7 +393,7 @@ bool Knitter::calculatePixelAndSolenoid() { } else if (Shifted == m_beltshift) { m_solenoidToSet = m_position % SOLENOIDS_NUM; } - if (L == m_carriage) { + if (Lace == m_carriage) { m_pixelToSet = m_pixelToSet - SOLENOIDS_NUM; } } @@ -387,16 +410,16 @@ bool Knitter::calculatePixelAndSolenoid() { } uint8_t Knitter::getStartOffset(const Direction_t direction) { - if ((direction >= NUM_DIRECTIONS) || - (m_carriage >= NUM_CARRIAGES) || - (m_machineType >= NUM_MACHINES)) { + if ((direction == NoDirection) || (direction >= NUM_DIRECTIONS) || + (m_carriage == NoCarriage) || (m_carriage >= NUM_CARRIAGES) || + (m_machineType == NoMachine) || (m_machineType >= NUM_MACHINES)) { + // TODO(TP): return error state return 0U; } return START_OFFSET[m_machineType][direction][m_carriage]; } void Knitter::reqLine(const uint8_t lineNumber) { - constexpr uint8_t REQLINE_LEN = 2U; uint8_t payload[REQLINE_LEN] = { reqLine_msgid, lineNumber, @@ -407,7 +430,6 @@ void Knitter::reqLine(const uint8_t lineNumber) { } void Knitter::indState(const bool initState) { - constexpr uint8_t INDSTATE_LEN = 9U; uint16_t leftHallValue = Encoders::getHallValue(Left); uint16_t rightHallValue = Encoders::getHallValue(Right); uint8_t payload[INDSTATE_LEN] = { diff --git a/src/ayab/knitter.h b/src/ayab/knitter.h index c4d8e28c7..ce537b0fd 100644 --- a/src/ayab/knitter.h +++ b/src/ayab/knitter.h @@ -28,6 +28,10 @@ #include "serial_encoding.h" #include "solenoids.h" +// API constants +constexpr uint8_t INDSTATE_LEN = 9U; +constexpr uint8_t REQLINE_LEN = 2U; + enum OpState { s_init, s_ready, s_operate, s_test }; using OpState_t = enum OpState; @@ -49,8 +53,9 @@ class Knitter { void isr(); void fsm(); - bool startOperation(Machine_t machineType, uint8_t startNeedle, uint8_t stopNeedle, - uint8_t *pattern_start, bool continuousReportingEnabled); + bool startOperation(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled); bool startTest(); bool setNextLine(uint8_t lineNumber); void setLastLine(); @@ -61,11 +66,8 @@ class Knitter { void setMachineType(Machine_t); private: - Machine_t m_machineType = Kh910; Solenoids m_solenoids; Encoders m_encoders; - - // these are incorporated by composition Beeper m_beeper; SerialEncoding m_serial_encoding; @@ -75,12 +77,11 @@ class Knitter { bool m_lastLineFlag = false; // job parameters + Machine_t m_machineType = NoMachine; uint8_t m_startNeedle = 0U; uint8_t m_stopNeedle = 0U; - bool m_continuousReportingEnabled = false; - bool m_lineRequested = false; - uint8_t m_currentLineNumber = 0U; uint8_t *m_lineBuffer = nullptr; + bool m_continuousReportingEnabled = false; // current machine state uint8_t m_position = 0U; @@ -89,6 +90,9 @@ class Knitter { Beltshift_t m_beltshift = Unknown; Carriage_t m_carriage = NoCarriage; + bool m_lineRequested = false; + uint8_t m_currentLineNumber = 0U; + uint8_t m_sOldPosition = 0U; bool m_firstRun = true; bool m_workedOnLine = false; @@ -111,8 +115,8 @@ class Knitter { void reqLine(uint8_t lineNumber); void indState(bool initState = false); - // TODO(sl): Not used? Can be removed? - uint8_t m_lastLinesCountdown = 0U; + void stopOperation(); + /* uint8_t m_lastLinesCountdown = 0U; */ }; #endif // KNITTER_H_ diff --git a/test/mocks/encoders_mock.cpp b/test/mocks/encoders_mock.cpp index b1b9bd6c4..76d7ce7aa 100644 --- a/test/mocks/encoders_mock.cpp +++ b/test/mocks/encoders_mock.cpp @@ -21,8 +21,8 @@ * http://ayab-knitting.com */ -#include #include +#include static EncodersMock *gEncodersMock = NULL; EncodersMock *encodersMockInstance() { @@ -43,34 +43,42 @@ void Encoders::init(Machine_t machineType) { assert(gEncodersMock != NULL); return gEncodersMock->init(machineType); } + void Encoders::encA_interrupt() { assert(gEncodersMock != NULL); gEncodersMock->encA_interrupt(); } + uint8_t Encoders::getPosition() const { assert(gEncodersMock != NULL); return gEncodersMock->getPosition(); } -Beltshift_t Encoders::getBeltshift() { + +Beltshift_t Encoders::getBeltshift() const { assert(gEncodersMock != NULL); return gEncodersMock->getBeltshift(); } -Direction_t Encoders::getDirection() { + +Direction_t Encoders::getDirection() const { assert(gEncodersMock != NULL); return gEncodersMock->getDirection(); } -Direction_t Encoders::getHallActive() { + +Direction_t Encoders::getHallActive() const { assert(gEncodersMock != NULL); return gEncodersMock->getHallActive(); } -Carriage_t Encoders::getCarriage() { + +Carriage_t Encoders::getCarriage() const { assert(gEncodersMock != NULL); return gEncodersMock->getCarriage(); } -Machine_t Encoders::getMachineType() { + +Machine_t Encoders::getMachineType() const { assert(gEncodersMock != NULL); return gEncodersMock->getMachineType(); } + uint16_t Encoders::getHallValue(Direction_t dir) { assert(gEncodersMock != NULL); return gEncodersMock->getHallValue(dir); diff --git a/test/test_encoders.cpp b/test/test_encoders.cpp index d8caee486..329e4a54b 100644 --- a/test/test_encoders.cpp +++ b/test/test_encoders.cpp @@ -92,7 +92,7 @@ TEST_F(EncodersTest, test_encA_rising_in_front_notKH270) { ASSERT_EQ(e.getDirection(), Right); ASSERT_EQ(e.getHallActive(), Left); ASSERT_EQ(e.getPosition(), END_OFFSET[e.getMachineType()]); - ASSERT_EQ(e.getCarriage(), L); + ASSERT_EQ(e.getCarriage(), Lace); ASSERT_EQ(e.getBeltshift(), Regular); } @@ -123,7 +123,7 @@ TEST_F(EncodersTest, test_encA_rising_in_front_KH270) { ASSERT_EQ(e.getDirection(), Right); ASSERT_EQ(e.getHallActive(), Left); ASSERT_EQ(e.getPosition(), END_OFFSET[e.getMachineType()]); - ASSERT_EQ(e.getCarriage(), K); + ASSERT_EQ(e.getCarriage(), Knit); ASSERT_EQ(e.getBeltshift(), Regular); } @@ -140,7 +140,7 @@ TEST_F(EncodersTest, test_encA_rising_in_front_G_carriage) { e.encA_interrupt(); - ASSERT_EQ(e.getCarriage(), K); + ASSERT_EQ(e.getCarriage(), Knit); // Create a falling edge EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_A)).WillOnce(Return(false)); @@ -160,7 +160,7 @@ TEST_F(EncodersTest, test_encA_rising_in_front_G_carriage) { e.encA_interrupt(); - ASSERT_EQ(e.getCarriage(), G); + ASSERT_EQ(e.getCarriage(), Garter); } TEST_F(EncodersTest, test_encA_falling_not_in_front) { @@ -283,10 +283,9 @@ TEST_F(EncodersTest, test_encA_falling_set_K_carriage_KH910) { EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_C)); e.encA_interrupt(); - ASSERT_EQ(e.getCarriage(), K); + ASSERT_EQ(e.getCarriage(), Knit); } - TEST_F(EncodersTest, test_encA_falling_not_at_end) { // rising, direction is left EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_A)).WillOnce(Return(true)); diff --git a/test/test_knitter.cpp b/test/test_knitter.cpp index 9f193d44a..970e2dd9e 100644 --- a/test/test_knitter.cpp +++ b/test/test_knitter.cpp @@ -23,16 +23,16 @@ #include -#include -#include #include +#include #include +#include #include #include using ::testing::_; -using ::testing::Return; using ::testing::AtLeast; +using ::testing::Return; void onPacketReceived(const uint8_t *buffer, size_t size) { (void)buffer; @@ -74,8 +74,8 @@ class KnitterTest : public ::testing::Test { EXPECT_CALL(*arduinoMock, pinMode(LED_PIN_A, OUTPUT)); EXPECT_CALL(*arduinoMock, pinMode(LED_PIN_B, OUTPUT)); - EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 1)); // green LED on - EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_B, 1)); // yellow LED on + EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 1)); // green LED on + EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_B, 1)); // yellow LED on EXPECT_CALL(*solenoidsMock, init); } @@ -97,7 +97,7 @@ class KnitterTest : public ::testing::Test { } void expect_isr(Direction_t dir, Direction_t hall) { - expect_isr(1, dir, hall, Regular, G); + expect_isr(1, dir, hall, Regular, Garter); } void expected_isr(Direction_t dir, Direction_t hall) { @@ -106,7 +106,7 @@ class KnitterTest : public ::testing::Test { } void expect_isr(uint16_t pos) { - expect_isr(pos, Right, Left, Regular, G); + expect_isr(pos, Right, Left, Regular, Garter); } void expected_isr(uint16_t pos) { @@ -141,7 +141,7 @@ class KnitterTest : public ::testing::Test { void get_to_ready() { // Machine is initialized when left hall sensor is passed in Right direction // Inside active needles - Machine_t m = k->getMachineType(); // Kh910 + Machine_t m = k->getMachineType(); expected_isr(40 + END_OF_LINE_OFFSET_L[m] + 1); // init @@ -150,29 +150,32 @@ class KnitterTest : public ::testing::Test { expected_fsm(); } - void get_to_operate() { - Machine_t m = k->getMachineType(); // Kh910 + void get_to_operate(Machine_t m) { get_to_ready(); - - EXPECT_CALL(*beeperMock, ready); - // operate uint8_t pattern[] = {1}; + EXPECT_CALL(*beeperMock, ready); + EXPECT_CALL(*encodersMock, init); k->startOperation(m, 0, NUM_NEEDLES[m] - 1, pattern, false); } void expected_operate(bool first) { if (first) { - get_to_operate(); + get_to_operate(Kh910); EXPECT_CALL(*arduinoMock, delay(2000)); EXPECT_CALL(*beeperMock, finishedLine); expect_send(); } - EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 1)); // green LED on + EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 1)); // green LED on expected_fsm(); } void expected_test(bool first) { if (first) { + // FIXME(TP): this is a kludge - + // currently there is no way to set the machineType in test mode + k->setMachineType(Kh910); + EXPECT_CALL(*encodersMock, init); + encodersMock->init(Kh910); ASSERT_EQ(k->startTest(), true); } expect_indState(); @@ -183,15 +186,15 @@ class KnitterTest : public ::testing::Test { EXPECT_CALL(*solenoidsMock, setSolenoid); expected_operate(true); // _workedOnLine is set to true - + // Position has changed since last call to operate function // m_pixelToSet is set above m_stopNeedle + END_OF_LINE_OFFSET_R - Machine_t m = k->getMachineType(); // Kh910 + Machine_t m = k->getMachineType(); // Kh910 expected_isr(NUM_NEEDLES[m] + 8 + END_OF_LINE_OFFSET_R[m] + 1); - + EXPECT_CALL(*solenoidsMock, setSolenoid); expected_operate(false); - + // No change in position, no action. EXPECT_CALL(*solenoidsMock, setSolenoid).Times(0); expected_operate(false); @@ -293,50 +296,62 @@ TEST_F(KnitterTest, test_fsm_test) { /*! * \test */ -TEST_F(KnitterTest, test_startOperation_1) { +TEST_F(KnitterTest, test_startOperation_NoMachine) { uint8_t pattern[] = {1}; - - // Not in ready state - Machine_t m = k->getMachineType(); // Kh910 - ASSERT_EQ(k->startOperation(m, 0, NUM_NEEDLES[m] - 1, pattern, false), false); - + ASSERT_EQ(k->getState(), s_init); + Machine_t m = k->getMachineType(); + ASSERT_EQ(m, NoMachine); get_to_ready(); - EXPECT_CALL(*beeperMock, ready); - ASSERT_EQ(k->startOperation(m, 0, NUM_NEEDLES[m] - 1, pattern, false), true); + ASSERT_EQ(k->startOperation(m, 0, NUM_NEEDLES[m] - 1, pattern, false), false); } /*! * \test */ -TEST_F(KnitterTest, test_startOperation_2) { +TEST_F(KnitterTest, test_startOperation_notReady) { uint8_t pattern[] = {1}; + ASSERT_EQ(k->getState(), s_init); + ASSERT_EQ(k->startOperation(Kh910, 0, NUM_NEEDLES[Kh910] - 1, pattern, false), + false); +} - // stopNeedle lower than start +/*! + * \test + */ +TEST_F(KnitterTest, test_startOperation_Kh910) { + uint8_t pattern[] = {1}; get_to_ready(); - Machine_t m = k->getMachineType(); // Kh910 - ASSERT_EQ(k->startOperation(m, 1, 0, pattern, false), false); + EXPECT_CALL(*encodersMock, init); + EXPECT_CALL(*beeperMock, ready); + ASSERT_EQ(k->startOperation(Kh910, 0, NUM_NEEDLES[Kh910] - 1, pattern, false), + true); } /*! * \test */ -TEST_F(KnitterTest, test_startOperation_3) { +TEST_F(KnitterTest, test_startOperation_Kh270) { uint8_t pattern[] = {1}; - - // stopNeedle equal to NUM_NEEDLES get_to_ready(); - Machine_t m = k->getMachineType(); // Kh910 - ASSERT_EQ(k->startOperation(m, 0, NUM_NEEDLES[m], pattern, false), false); + EXPECT_CALL(*encodersMock, init); + EXPECT_CALL(*beeperMock, ready); + ASSERT_EQ(k->startOperation(Kh270, 0, NUM_NEEDLES[Kh270] - 1, pattern, false), + true); } /*! * \test */ -TEST_F(KnitterTest, test_startOperation_4) { - // null pointer passed as line +TEST_F(KnitterTest, test_startOperation_failures) { + uint8_t pattern[] = {1}; + + // stopNeedle lower than start get_to_ready(); - Machine_t m = k->getMachineType(); // Kh910 - ASSERT_EQ(k->startOperation(m, 0, NUM_NEEDLES[m] - 1, nullptr, false), false); + ASSERT_EQ(k->startOperation(Kh910, 1, 0, pattern, false), false); + ASSERT_EQ(k->startOperation(Kh910, 0, NUM_NEEDLES[Kh910], pattern, false), + false); + ASSERT_EQ(k->startOperation(Kh910, 0, NUM_NEEDLES[Kh910] - 1, nullptr, false), + false); } /*! @@ -351,7 +366,7 @@ TEST_F(KnitterTest, test_startTest) { * \test */ TEST_F(KnitterTest, test_startTest_in_operation) { - get_to_operate(); + get_to_operate(Kh910); // Can't start test ASSERT_EQ(k->startTest(), false); ASSERT_EQ(k->getState(), s_operate); @@ -368,8 +383,7 @@ TEST_F(KnitterTest, test_setNextLine) { expected_operate(true); // Outside of the active needles - Machine_t m = k->getMachineType(); // Kh910 - expected_isr(40 + NUM_NEEDLES[m] - 1 + END_OF_LINE_OFFSET_R[m] + 1); + expected_isr(40 + NUM_NEEDLES[Kh910] - 1 + END_OF_LINE_OFFSET_R[Kh910] + 1); EXPECT_CALL(*solenoidsMock, setSolenoid).Times(1); expected_operate(false); ASSERT_EQ(k->getState(), s_operate); @@ -393,28 +407,28 @@ TEST_F(KnitterTest, test_setNextLine) { TEST_F(KnitterTest, test_operate_Kh910) { // m_pixelToSet gets set to 0 expected_isr(8); - + // init - Machine_t m = k->getMachineType(); // Kh910 - uint16_t bitmask = SOLENOIDS_BITMASK[m]; + uint16_t bitmask = SOLENOIDS_BITMASK; EXPECT_CALL(*solenoidsMock, setSolenoids(bitmask)); expect_indState(); expected_fsm(); - + // operate uint8_t pattern[] = {1}; - + // startNeedle is greater than pixelToSet EXPECT_CALL(*beeperMock, ready); - const uint8_t START_NEEDLE = NUM_NEEDLES[m] - 2; - const uint8_t STOP_NEEDLE = NUM_NEEDLES[m] - 1; - const uint8_t OFFSET = END_OF_LINE_OFFSET_R[m]; - k->startOperation(m, START_NEEDLE, STOP_NEEDLE, pattern, true); - + EXPECT_CALL(*encodersMock, init); + const uint8_t START_NEEDLE = NUM_NEEDLES[Kh910] - 2; + const uint8_t STOP_NEEDLE = NUM_NEEDLES[Kh910] - 1; + const uint8_t OFFSET = END_OF_LINE_OFFSET_R[Kh910]; + k->startOperation(Kh910, START_NEEDLE, STOP_NEEDLE, pattern, true); + // first operate EXPECT_CALL(*arduinoMock, delay(2000)); EXPECT_CALL(*beeperMock, finishedLine); - + // indState and send expect_send<2>(); EXPECT_CALL(*encodersMock, getHallValue(Left)); @@ -422,18 +436,18 @@ TEST_F(KnitterTest, test_operate_Kh910) { EXPECT_CALL(*encodersMock, getDirection); EXPECT_CALL(*solenoidsMock, setSolenoid); expected_operate(false); - + // No useful position calculated by calculatePixelAndSolenoid - expected_isr(100, NoDirection, Right, Shifted, G); + expected_isr(100, NoDirection, Right, Shifted, Garter); expect_indState(); expected_operate(false); - + // Don't set workedonline to true expected_isr(8 + STOP_NEEDLE + OFFSET); EXPECT_CALL(*solenoidsMock, setSolenoid); expect_indState(); expected_operate(false); - + expected_isr(START_NEEDLE); EXPECT_CALL(*solenoidsMock, setSolenoid); expect_indState(); @@ -444,37 +458,36 @@ TEST_F(KnitterTest, test_operate_Kh910) { * \test */ TEST_F(KnitterTest, test_operate_Kh270) { - k->setMachineType(Kh270); - encodersMock->init(Kh270); - Machine_t m = k->getMachineType(); - ASSERT_EQ(m, Kh270); - // m_pixelToSet gets set to 0 expected_isr(8); - + // init - uint16_t bitmask = SOLENOIDS_BITMASK[m]; + uint16_t bitmask = SOLENOIDS_BITMASK; EXPECT_CALL(*solenoidsMock, setSolenoids(bitmask)); expect_indState(); expected_fsm(); - + // operate uint8_t pattern[] = {1}; - + // startNeedle is greater than pixelToSet EXPECT_CALL(*beeperMock, ready); - const uint8_t START_NEEDLE = NUM_NEEDLES[m] - 2; - const uint8_t STOP_NEEDLE = NUM_NEEDLES[m] - 1; - const uint8_t OFFSET = END_OF_LINE_OFFSET_R[m]; - k->startOperation(m, START_NEEDLE, STOP_NEEDLE, pattern, true); - + EXPECT_CALL(*encodersMock, init); + const uint8_t START_NEEDLE = NUM_NEEDLES[Kh270] - 2; + const uint8_t STOP_NEEDLE = NUM_NEEDLES[Kh270] - 1; + const uint8_t OFFSET = END_OF_LINE_OFFSET_R[Kh270]; + k->startOperation(Kh270, START_NEEDLE, STOP_NEEDLE, pattern, true); + // first operate - EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_B, 0)).Times(AtLeast(0)); // yellow LED off - EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_B, 1)).Times(AtLeast(1)); // yellow LED on - EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_B, 0)).Times(AtLeast(0)); // yellow LED off + EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_B, 0)) + .Times(AtLeast(0)); // yellow LED off + EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_B, 1)) + .Times(AtLeast(1)); // yellow LED on + EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_B, 0)) + .Times(AtLeast(0)); // yellow LED off EXPECT_CALL(*arduinoMock, delay(2000)); EXPECT_CALL(*beeperMock, finishedLine); - + // indState and send expect_send<2>(); EXPECT_CALL(*encodersMock, getHallValue(Left)); @@ -482,18 +495,18 @@ TEST_F(KnitterTest, test_operate_Kh270) { EXPECT_CALL(*encodersMock, getDirection); EXPECT_CALL(*solenoidsMock, setSolenoid); expected_operate(false); - + // No useful position calculated by calculatePixelAndSolenoid - expected_isr(100, NoDirection, Right, Shifted, G); + expected_isr(100, NoDirection, Right, Shifted, Garter); expect_indState(); expected_operate(false); - + // Don't set workedonline to true expected_isr(8 + STOP_NEEDLE + OFFSET); EXPECT_CALL(*solenoidsMock, setSolenoid); expect_indState(); expected_operate(false); - + expected_isr(START_NEEDLE); EXPECT_CALL(*solenoidsMock, setSolenoid); expect_indState(); @@ -510,8 +523,7 @@ TEST_F(KnitterTest, test_operate_line_request) { // Position has changed since last call to operate function // m_pixelToSet is set above m_stopNeedle + END_OF_LINE_OFFSET_R - Machine_t m = k->getMachineType(); // Kh910 - expected_isr(NUM_NEEDLES[m] + 8 + END_OF_LINE_OFFSET_R[m] + 1); + expected_isr(NUM_NEEDLES[Kh910] + 8 + END_OF_LINE_OFFSET_R[Kh910] + 1); EXPECT_CALL(*solenoidsMock, setSolenoid); expected_operate(false); @@ -531,8 +543,7 @@ TEST_F(KnitterTest, test_operate_lastline) { // Position has changed since last call to operate function // m_pixelToSet is above m_stopNeedle + END_OF_LINE_OFFSET_R - Machine_t m = k->getMachineType(); // Kh910 - expected_isr(NUM_NEEDLES[m] + 8 + END_OF_LINE_OFFSET_R[m] + 1); + expected_isr(NUM_NEEDLES[Kh910] + 8 + END_OF_LINE_OFFSET_R[Kh910] + 1); // m_lastLineFlag is true k->setLastLine(); @@ -548,10 +559,11 @@ TEST_F(KnitterTest, test_operate_lastline) { * \test */ TEST_F(KnitterTest, test_operate_lastline_and_no_req) { + get_to_operate(Kh910); + // Note probing lots of private data and methods to get full branch coverage. k->m_stopNeedle = 100; - Machine_t m = k->getMachineType(); // Kh910 - uint8_t wanted_pixel = k->m_stopNeedle + END_OF_LINE_OFFSET_R[m] + 1; + uint8_t wanted_pixel = k->m_stopNeedle + END_OF_LINE_OFFSET_R[Kh910] + 1; k->m_firstRun = false; k->m_direction = Left; k->m_position = wanted_pixel + k->getStartOffset(Right); @@ -593,8 +605,7 @@ TEST_F(KnitterTest, test_operate_new_line) { // Position has changed since last call to operate function // m_pixelToSet is above m_stopNeedle + END_OF_LINE_OFFSET_R - Machine_t m = k->getMachineType(); // Kh910 - expected_isr(NUM_NEEDLES[m] + 8 + END_OF_LINE_OFFSET_R[m] + 1); + expected_isr(NUM_NEEDLES[Kh910] + 8 + END_OF_LINE_OFFSET_R[Kh910] + 1); // Set m_lineRequested to false EXPECT_CALL(*beeperMock, finishedLine); @@ -612,42 +623,52 @@ TEST_F(KnitterTest, test_operate_new_line) { */ TEST_F(KnitterTest, test_calculatePixelAndSolenoid) { // New Position, different beltshift and active hall - expected_isr(100, Right, Right, Shifted, L); + expected_isr(100, Right, Right, Shifted, Lace); expected_test(true); // No direction, need to change position to enter test - expected_isr(101, NoDirection, Right, Shifted, L); + expected_isr(101, NoDirection, Right, Shifted, Lace); expected_test(false); // No belt, need to change position to enter test - expected_isr(100, Right, Right, Unknown, L); + expected_isr(100, Right, Right, Unknown, Lace); expected_test(false); // No belt on left side, need to change position to enter test - expected_isr(101, Left, Right, Unknown, G); + expected_isr(101, Left, Right, Unknown, Garter); expected_test(false); - // Left L carriage - expected_isr(100, Left, Right, Unknown, L); + // Left Lace carriage + expected_isr(100, Left, Right, Unknown, Lace); expected_test(false); // Regular belt on left, need to change position to enter test - expected_isr(101, Left, Right, Regular, G); + expected_isr(101, Left, Right, Regular, Garter); expected_test(false); // Shifted belt on left, need to change position to enter test - expected_isr(100, Left, Right, Shifted, G); + expected_isr(100, Left, Right, Shifted, Garter); expected_test(false); // Off of right end, position is changed - Machine_t m = k->getMachineType(); // Kh910 - expected_isr(END_RIGHT[m], Left, Right, Unknown, L); + expected_isr(END_RIGHT[Kh910], Left, Right, Unknown, Lace); expected_test(false); - // Kh270, K carriage on left + // Direction right, have not reached offset + expected_isr(39, Right, Left, Unknown, Lace); + expected_test(false); + + // Kh270 k->setMachineType(Kh270); + EXPECT_CALL(*encodersMock, init); encodersMock->init(Kh270); - expected_isr(0, Left, Right, Regular, K); + + // K carriage direction left + expected_isr(0, Left, Right, Regular, Knit); + expected_test(false); + + // K carriage direction right + expected_isr(END_RIGHT[Kh270], Right, Left, Regular, Knit); expected_test(false); } @@ -655,13 +676,19 @@ TEST_F(KnitterTest, test_calculatePixelAndSolenoid) { * \test */ TEST_F(KnitterTest, test_getStartOffset) { - // NOTE: Probing private method to be able to cover all branches. - ASSERT_EQ(k->getStartOffset(NUM_DIRECTIONS), 0); - k->m_carriage = NUM_CARRIAGES; + // out of range values + k->m_carriage = Knit; ASSERT_EQ(k->getStartOffset(NoDirection), 0); + ASSERT_EQ(k->getStartOffset(NUM_DIRECTIONS), 0); k->m_carriage = NoCarriage; + ASSERT_EQ(k->getStartOffset(Left), 0); + k->m_carriage = NUM_CARRIAGES; + ASSERT_EQ(k->getStartOffset(Right), 0); + k->m_carriage = Lace; + k->setMachineType(NoMachine); + ASSERT_EQ(k->getStartOffset(Left), 0); k->setMachineType(NUM_MACHINES); - ASSERT_EQ(k->getStartOffset(NoDirection), 0); + ASSERT_EQ(k->getStartOffset(Right), 0); } /*! From 232c5ffcfcbb4b44e3f32e9a43797d16057c937e Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 11 Aug 2020 00:14:19 -0400 Subject: [PATCH 02/17] Add HardwareTest class. --- src/ayab/hw_test.cpp | 156 ++++++++++++++--------------- src/ayab/hw_test.h | 27 ++++- src/ayab/knitter.cpp | 184 +++++++++++++++++++++++------------ src/ayab/knitter.h | 58 +++++++---- src/ayab/main.cpp | 19 ++-- src/ayab/serial_encoding.cpp | 36 ++++--- src/ayab/serial_encoding.h | 2 +- test/CMakeLists.txt | 3 + test/mocks/beeper_mock.cpp | 2 + test/mocks/hw_test_mock.cpp | 49 ++++++++++ test/mocks/hw_test_mock.h | 39 ++++++++ test/mocks/knitter_mock.cpp | 72 +++++++++++++- test/mocks/knitter_mock.h | 21 +++- test/test_knitter.cpp | 39 +++++--- 14 files changed, 486 insertions(+), 221 deletions(-) create mode 100644 test/mocks/hw_test_mock.cpp create mode 100644 test/mocks/hw_test_mock.h diff --git a/src/ayab/hw_test.cpp b/src/ayab/hw_test.cpp index 72e83bd0b..a2a280048 100644 --- a/src/ayab/hw_test.cpp +++ b/src/ayab/hw_test.cpp @@ -21,24 +21,14 @@ */ #include -#include +#include "hw_test.h" #include "knitter.h" -extern Knitter *knitter; - -static SerialCommand SCmd; ///< The SerialCommand object -static Beeper beeper; -static Solenoids solenoids; - -static bool autoReadOn = false; -static bool autoTestOn = false; - static void prompt() { - Serial.print("$ "); } -static void help() { +static void helpCmd() { Serial.println("The following commands are available:"); Serial.println("setSingle [0..15] [1/0]"); Serial.println("setAll [0..255] [0..255]"); @@ -48,23 +38,25 @@ static void help() { Serial.println("autoRead"); Serial.println("autoTest"); Serial.println("send"); + Serial.println("stop"); + Serial.println("quit"); Serial.println("help"); prompt(); } -static void send() { +static void sendCmd() { + extern Knitter *knitter; Serial.println("Called send"); uint8_t p[] = {1, 2, 3}; knitter->send(p, 3); Serial.print("\n"); - prompt(); } static void beep() { - beeper.ready(); + hwTest->m_beeper.ready(); } /*! @@ -74,7 +66,6 @@ static void beepCmd() { Serial.println("Called beep"); beep(); - prompt(); } @@ -88,10 +79,10 @@ static void encoderAChange() { /*! * \brief Set single solenoid command handler. */ -static void setSingle() { +static void setSingleCmd() { Serial.println("Called setSingle"); - char *arg = SCmd.next(); + char *arg = hwTest->m_SCmd.next(); if (arg == nullptr) { return; } @@ -101,51 +92,44 @@ static void setSingle() { Serial.println(solenoidNumber); return; } - if (arg == nullptr) { return; } - arg = SCmd.next(); + arg = hwTest->m_SCmd.next(); uint8_t solenoidState = atoi(arg); if (solenoidState > 1) { Serial.print("Invalid argument: "); Serial.println(solenoidState); return; } - - solenoids.setSolenoid(solenoidNumber, solenoidState); - + hwTest->m_solenoids.setSolenoid(solenoidNumber, solenoidState); prompt(); } /*! * \brief Set all solenoids command handler. */ -static void setAll() { +static void setAllCmd() { Serial.println("Called setAll"); - char *arg = SCmd.next(); + char *arg = hwTest->m_SCmd.next(); if (arg == nullptr) { return; } uint8_t highByte = atoi(arg); - - arg = SCmd.next(); + arg = hwTest->m_SCmd.next(); if (arg == nullptr) { return; } uint8_t lowByte = atoi(arg); - uint16_t solenoidState = (highByte << 8) + lowByte; - solenoids.setSolenoids(solenoidState); - + hwTest->m_solenoids.setSolenoids(solenoidState); prompt(); } static void readEOLsensors() { Serial.print(" EOL_L: "); Serial.print(analogRead(EOL_PIN_L)); - Serial.print(" EOL_R: "); Serial.print(analogRead(EOL_PIN_R)); } @@ -158,7 +142,6 @@ static void readEOLsensorsCmd() { readEOLsensors(); Serial.print("\n"); - prompt(); } @@ -166,11 +149,9 @@ static void readEncoders() { Serial.print(" ENC_A: "); bool state = digitalRead(ENC_PIN_A); Serial.print(state ? "HIGH" : "LOW"); - Serial.print(" ENC_B: "); state = digitalRead(ENC_PIN_B); Serial.print(state ? "HIGH" : "LOW"); - Serial.print(" ENC_C: "); state = digitalRead(ENC_PIN_C); Serial.print(state ? "HIGH" : "LOW"); @@ -184,7 +165,6 @@ static void readEncodersCmd() { readEncoders(); Serial.print("\n"); - prompt(); } @@ -193,9 +173,8 @@ static void autoRead() { readEOLsensors(); readEncoders(); Serial.print("\n"); - prompt(); - delay(1000); + // delay(1000); } /*! @@ -203,26 +182,25 @@ static void autoRead() { */ static void autoReadCmd() { Serial.println("Called autoRead, send stop to quit"); - autoReadOn = true; + hwTest->m_autoReadOn = true; } -static void autoTest() { +static void autoTestEven() { Serial.print("\n"); - Serial.println("Set even solenoids"); digitalWrite(LED_PIN_A, 1); digitalWrite(LED_PIN_B, 1); - solenoids.setSolenoids(0xAAAA); - - delay(500); + hwTest->m_solenoids.setSolenoids(0xAAAA); + // delay(500); +} +static void autoTestOdd() { Serial.println("Set odd solenoids"); digitalWrite(LED_PIN_A, 0); digitalWrite(LED_PIN_B, 0); - solenoids.setSolenoids(0x5555); - + hwTest->m_solenoids.setSolenoids(0x5555); prompt(); - delay(500); + // delay(500); } /*! @@ -230,16 +208,21 @@ static void autoTest() { */ static void autoTestCmd() { Serial.println("Called autoTest, send stop to quit"); - autoTestOn = true; + hwTest->m_autoTestOn = true; } -static void stop() { - autoReadOn = false; - autoTestOn = false; - +static void stopCmd() { + hwTest->m_autoReadOn = false; + hwTest->m_autoTestOn = false; prompt(); } +static void quitCmd() { + extern Knitter *knitter; + hwTest->m_quit = true; + knitter->setUpInterrupt(); +} + /*! * \brief Unrecognized command handler. * @@ -248,57 +231,62 @@ static void stop() { * This gets set as the default handler, and gets called when no other command * matches. */ -static void unrecognized(const char *buffer) { +static void unrecognizedCmd(const char *buffer) { Serial.println("Unrecognized Command"); - (void)(buffer); - - help(); + helpCmd(); } +// Member functions + /*! * \brief Setup for hw tests. */ -void hw_test_setup() { - pinMode(LED_PIN_A, OUTPUT); - pinMode(LED_PIN_B, OUTPUT); - - // Setup callbacks for SerialCommand commands - SCmd.addCommand("setSingle", setSingle); - SCmd.addCommand("setAll", setAll); - SCmd.addCommand("readEOLsensors", readEOLsensorsCmd); - SCmd.addCommand("readEncoders", readEncodersCmd); - SCmd.addCommand("beep", beepCmd); - SCmd.addCommand("autoRead", autoReadCmd); - SCmd.addCommand("autoTest", autoTestCmd); - SCmd.addCommand("send", send); - SCmd.addCommand("stop", stop); - SCmd.addCommand("help", help); - SCmd.setDefaultHandler( - unrecognized); // Handler for command that isn't matched - - attachInterrupt(0, encoderAChange, RISING); // Attaching ENC_PIN_A(=2) - - Serial.print("AYAB HW Test Firmware v"); +void HardwareTest::setUp() { + // attach interrupt for ENC_PIN_A(=2), interrupt #0 + detachInterrupt(0); + attachInterrupt(0, encoderAChange, RISING); + + // set up callbacks for SerialCommand commands + m_SCmd.addCommand("setSingle", setSingleCmd); + m_SCmd.addCommand("setAll", setAllCmd); + m_SCmd.addCommand("readEOLsensors", readEOLsensorsCmd); + m_SCmd.addCommand("readEncoders", readEncodersCmd); + m_SCmd.addCommand("beep", beepCmd); + m_SCmd.addCommand("autoRead", autoReadCmd); + m_SCmd.addCommand("autoTest", autoTestCmd); + m_SCmd.addCommand("send", sendCmd); + m_SCmd.addCommand("stop", stopCmd); + m_SCmd.addCommand("quit", quitCmd); + m_SCmd.addCommand("help", helpCmd); + m_SCmd.setDefaultHandler(unrecognizedCmd); + + // Print welcome message + Serial.print("AYAB Hardware Test, Firmware v"); Serial.print(FW_VERSION_MAJ); Serial.print("."); Serial.print(FW_VERSION_MIN); Serial.print(" API v"); Serial.print(API_VERSION); Serial.print("\n\n"); - - help(); + helpCmd(); } /*! - * \brief Main loop for hw tests. + * \brief Main loop for hardware tests. */ -void hw_test_loop() { - if (autoReadOn) { +void HardwareTest::loop() { + if (m_autoReadOn and m_timerEvent and m_timerEventOdd) { autoRead(); } - if (autoTestOn) { - autoTest(); + if (m_autoTestOn) { + if (m_timerEventOdd) { + autoTestOdd(); + } else { + autoTestEven(); + } } - SCmd.readSerial(); + m_timerEvent = false; + m_timerEventOdd = not m_timerEventOdd; + m_SCmd.readSerial(); } diff --git a/src/ayab/hw_test.h b/src/ayab/hw_test.h index fabad637f..f041cca77 100644 --- a/src/ayab/hw_test.h +++ b/src/ayab/hw_test.h @@ -23,7 +23,28 @@ #ifndef HW_TEST_H_ #define HW_TEST_H_ -void hw_test_setup(); -void hw_test_loop(); +#include +#include -#endif // HW_TEST_H_ +#include "beeper.h" +#include "solenoids.h" + +class HardwareTest { +public: + void setUp(); + void loop(); + + SerialCommand m_SCmd; + Beeper m_beeper; + Solenoids m_solenoids; + + volatile bool m_timerEvent = false; + bool m_timerEventOdd = false; + bool m_autoReadOn = false; + bool m_autoTestOn = false; + bool m_quit = false; +}; + +extern HardwareTest *hwTest; + +#endif // HW_TEST_H_ diff --git a/src/ayab/knitter.cpp b/src/ayab/knitter.cpp index 328260051..da5a9eeca 100644 --- a/src/ayab/knitter.cpp +++ b/src/ayab/knitter.cpp @@ -20,9 +20,11 @@ * Modified Work Copyright 2020 Sturla Lange, Tom Price * http://ayab-knitting.com */ + #include #include "board.h" +#include "hw_test.h" #include "knitter.h" #ifdef CLANG_TIDY @@ -32,17 +34,33 @@ constexpr uint8_t UINT8_MAX = 0xFFU; constexpr uint16_t UINT16_MAX = 0xFFFFU; #endif +extern Knitter *knitter; +extern HardwareTest *hwTest; + +#ifndef AYAB_TESTS +/*! + * \brief Wrapper for knitter's isr. + * + * This is needed since a non-static method cannot be + * passed to _attachInterrupt_. + */ +static void isr_wrapper() { + knitter->isr(); +} +#endif // AYAB_TESTS + /*! * \brief Knitter constructor. * * Initializes the solenoids as well as pins and interrupts. */ -Knitter::Knitter() { - pinMode(ENC_PIN_A, INPUT); +Knitter::Knitter() + : // incorporated by composition + m_beeper(), m_serial_encoding() { + pinMode(ENC_PIN_A, INPUT); pinMode(ENC_PIN_B, INPUT); pinMode(ENC_PIN_C, INPUT); - pinMode(LED_PIN_A, OUTPUT); pinMode(LED_PIN_B, OUTPUT); digitalWrite(LED_PIN_A, 1); @@ -53,18 +71,15 @@ Knitter::Knitter() { #endif m_solenoids.init(); + setUpInterrupt(); } -OpState_t Knitter::getState() { - return m_opState; -} - -Machine_t Knitter::getMachineType() { - return m_machineType; -} - -void Knitter::setMachineType(Machine_t machineType) { - m_machineType = machineType; +void Knitter::setUpInterrupt() { + // attach ENC_PIN_A(=2), interrupt #0 + detachInterrupt(0); +#ifndef AYAB_TESTS + attachInterrupt(0, isr_wrapper, CHANGE); +#endif // AYAB_TESTS } void Knitter::send(uint8_t *payload, size_t length) { @@ -79,6 +94,7 @@ void Knitter::send(uint8_t *payload, size_t length) { * Machine type assumed valid. */ void Knitter::isr() { + // update machine state data m_encoders.encA_interrupt(); m_position = m_encoders.getPosition(); m_direction = m_encoders.getDirection(); @@ -87,23 +103,10 @@ void Knitter::isr() { m_carriage = m_encoders.getCarriage(); } -#ifndef AYAB_TESTS -/*! - * \brief Wrapper for knitter's isr. - * - * This is needed since a non-static method cannot be - * passed to _attachInterrupt_. - */ -void isr_wrapper() { - extern Knitter *knitter; - knitter->isr(); -} -#endif - /*! * \brief Dispatch on machine state * - * \todo TP: Add error state(s) + * \todo TP: add error state(s) */ void Knitter::fsm() { switch (m_opState) { @@ -131,6 +134,9 @@ void Knitter::fsm() { /*! * \brief Enter operate state. + * + * \todo sl: check that functionality is correct after removing always true + * comparison. */ bool Knitter::startOperation(Machine_t machineType, uint8_t startNeedle, uint8_t stopNeedle, uint8_t *pattern_start, @@ -142,38 +148,36 @@ bool Knitter::startOperation(Machine_t machineType, uint8_t startNeedle, return false; } - // Record argument values + // record argument values m_machineType = machineType; m_startNeedle = startNeedle; m_stopNeedle = stopNeedle; m_lineBuffer = pattern_start; m_continuousReportingEnabled = continuousReportingEnabled; - // Reset variables to start conditions + // reset variables to start conditions m_currentLineNumber = UINT8_MAX; // because counter will // be incremented before request m_lineRequested = false; m_lastLineFlag = false; - // Initialize encoders + // initialize encoders m_encoders.init(machineType); - // Proceed to next state + // proceed to next state m_opState = s_operate; Beeper::ready(); /* m_lastLinesCountdown = 2; */ -#ifndef AYAB_TESTS - // Attaching ENC_PIN_A, Interrupt No. 0 + // Attaching ENC_PIN_A, Interrupt #0 // This interrupt cannot be enabled until // the machine type has been validated. /* // `digitalPinToInterrupt` macro not backported until Arduino !DE v.1.0.6 attachInterrupt(digitalPinToInterrupt(ENC_PIN_A), isr_wrapper, CHANGE); */ - attachInterrupt(0, isr_wrapper, CHANGE); -#endif + setUpInterrupt(); // success return true; @@ -215,10 +219,10 @@ void Knitter::state_init() { #ifdef DBG_NOMACHINE bool state = digitalRead(DBG_BTN_PIN); - // TODO(Who?): Check if debounce is needed + // TODO(who?): check if debounce is needed if (m_prevState && !state) { #else - // Machine is initialized when left hall sensor is passed in Right direction + // machine is initialized when left hall sensor is passed in Right direction if (Right == m_direction && Left == m_hallActive) { #endif // DBG_NOMACHINE m_opState = s_ready; @@ -242,7 +246,7 @@ void Knitter::state_operate() { if (m_firstRun) { m_firstRun = false; - // TODO(Who?): Optimize Delay for various Arduino Models + // TODO(who?): optimize delay for various Arduino models delay(START_OPERATION_DELAY); Beeper::finishedLine(); reqLine(++m_currentLineNumber); @@ -251,7 +255,7 @@ void Knitter::state_operate() { #ifdef DBG_NOMACHINE bool state = digitalRead(DBG_BTN_PIN); - // TODO(Who?): Check if debounce is needed + // TODO(who?): check if debounce is needed if (m_prevState && !state) { if (!m_lineRequested) { reqLine(++m_currentLineNumber); @@ -260,17 +264,17 @@ void Knitter::state_operate() { m_prevState = state; #else if (m_sOldPosition != m_position) { - // Only act if there is an actual change of position - // Store current Encoder position for next call of this function + // only act if there is an actual change of position + // store current Encoder position for next call of this function m_sOldPosition = m_position; if (m_continuousReportingEnabled) { - // Send current position to GUI + // send current position to GUI indState(true); } if (!calculatePixelAndSolenoid()) { - // No valid/useful position calculated + // no valid/useful position calculated return; } @@ -279,27 +283,27 @@ void Knitter::state_operate() { (m_pixelToSet <= m_stopNeedle + END_OF_LINE_OFFSET_R[m_machineType])) { if ((m_pixelToSet >= m_startNeedle) && (m_pixelToSet <= m_stopNeedle)) { - // When inside the active needles + // when inside the active needles if (m_machineType == Kh270) { digitalWrite(LED_PIN_B, 1); // yellow LED on } m_workedOnLine = true; } - // Find the right byte from the currentLine array, + // find the right byte from the currentLine array, // then read the appropriate Pixel(/Bit) for the current needle to set uint8_t currentByte = m_pixelToSet / 8U; bool pixelValue = bitRead(m_lineBuffer[currentByte], m_pixelToSet - (8U * currentByte)); - // Write Pixel state to the appropriate needle + // write Pixel state to the appropriate needle m_solenoids.setSolenoid(m_solenoidToSet, pixelValue); } else { - // Outside of the active needles + // outside of the active needles if (m_machineType == Kh270) { digitalWrite(LED_PIN_B, 0); // yellow LED off } - // Reset Solenoids when out of range + // reset solenoids when out of range m_solenoids.setSolenoid(m_solenoidToSet, true); if (m_workedOnLine) { @@ -307,7 +311,7 @@ void Knitter::state_operate() { m_workedOnLine = false; if (!m_lineRequested && !m_lastLineFlag) { - // request new Line from Host + // request new line from host reqLine(++m_currentLineNumber); } else if (m_lastLineFlag) { stopOperation(); @@ -325,27 +329,23 @@ void Knitter::stopOperation() { m_solenoids.setSolenoids(SOLENOIDS_BITMASK); Beeper::finishedLine(); -#ifndef AYAB_TESTS - // Attaching ENC_PIN_A, Interrupt No. 0 - // This interrupt cannot be enabled until - // the machine type has been validated. + // detaching ENC_PIN_A, Interrupt #0 /* // `digitalPinToInterrupt` macro not backported until Arduino !DE v.1.0.6 detachInterrupt(digitalPinToInterrupt(ENC_PIN_A)); */ detachInterrupt(0); -#endif } void Knitter::state_test() { if (m_sOldPosition != m_position) { - // Only act if there is an actual change of position - // Store current Encoder position for next call of this function + // only act if there is an actual change of position + // store current encoder position for next call of this function m_sOldPosition = m_position; - calculatePixelAndSolenoid(); indState(); } + hwTest->loop(); } bool Knitter::calculatePixelAndSolenoid() { @@ -353,16 +353,16 @@ bool Knitter::calculatePixelAndSolenoid() { bool success = true; switch (m_direction) { - // Calculate the solenoid and pixel to be set - // Implemented according to machine manual - // Magic numbers result from machine manual + // calculate the solenoid and pixel to be set + // implemented according to machine manual + // magic numbers from machine manual case Right: startOffset = getStartOffset(Left); if (m_position >= startOffset) { m_pixelToSet = m_position - startOffset; if (m_machineType == Kh270) { - // TODO(Who?): check + // TODO(who?): check m_solenoidToSet = (m_position % 12) + 3; } else { if (Regular == m_beltshift) { @@ -385,7 +385,7 @@ bool Knitter::calculatePixelAndSolenoid() { m_pixelToSet = m_position - startOffset; if (m_machineType == Kh270) { - // TODO(Who?): check + // TODO(who?): check m_solenoidToSet = ((m_position + 6) % 12) + 3; } else { if (Regular == m_beltshift) { @@ -409,7 +409,7 @@ bool Knitter::calculatePixelAndSolenoid() { return success; } -uint8_t Knitter::getStartOffset(const Direction_t direction) { +uint8_t Knitter::getStartOffset(const Direction_t direction) const { if ((direction == NoDirection) || (direction >= NUM_DIRECTIONS) || (m_carriage == NoCarriage) || (m_carriage >= NUM_CARRIAGES) || (m_machineType == NoMachine) || (m_machineType >= NUM_MACHINES)) { @@ -449,3 +449,61 @@ void Knitter::indState(const bool initState) { void Knitter::onPacketReceived(const uint8_t *buffer, size_t size) { m_serial_encoding.onPacketReceived(buffer, size); } + +OpState_t Knitter::getState() const { + return m_opState; +} + +Machine_t Knitter::getMachineType() const { + return m_machineType; +} + +void Knitter::setMachineType(Machine_t machineType) { + m_machineType = machineType; +} + +// for testing purposes only + +void Knitter::setState(OpState_t state) { + m_opState = state; +} + +uint8_t Knitter::getStartNeedle() const { + return m_startNeedle; +} + +uint8_t Knitter::getStopNeedle() const { + return m_stopNeedle; +} + +void Knitter::setStopNeedle(uint8_t stopNeedle) { + m_stopNeedle = stopNeedle; +} + +void Knitter::setPosition(uint8_t position) { + m_position = position; +} + +void Knitter::setDirection(Direction_t direction) { + m_direction = direction; +} + +void Knitter::setCarriage(Carriage_t carriage) { + m_carriage = carriage; +} + +void Knitter::setFirstRun(bool firstRun) { + m_firstRun = firstRun; +} + +void Knitter::setWorkedOnLine(bool workedOnLine) { + m_workedOnLine = workedOnLine; +} + +void Knitter::setLineRequested(bool lineRequested) { + m_lineRequested = lineRequested; +} + +void Knitter::setLastLineFlag(bool lastLineFlag) { + m_lastLineFlag = lastLineFlag; +} diff --git a/src/ayab/knitter.h b/src/ayab/knitter.h index ce537b0fd..d9cc64cb6 100644 --- a/src/ayab/knitter.h +++ b/src/ayab/knitter.h @@ -20,11 +20,13 @@ * Modified Work Copyright 2020 Sturla Lange, Tom Price * http://ayab-knitting.com */ + #ifndef KNITTER_H_ #define KNITTER_H_ #include "beeper.h" #include "encoders.h" +#include "hw_test.h" #include "serial_encoding.h" #include "solenoids.h" @@ -42,28 +44,54 @@ using OpState_t = enum OpState; * encoders, and serial communication. */ class Knitter { -#if AYAB_TESTS - FRIEND_TEST(KnitterTest, test_constructor); - FRIEND_TEST(KnitterTest, test_fsm_default_case); - FRIEND_TEST(KnitterTest, test_getStartOffset); - FRIEND_TEST(KnitterTest, test_operate_lastline_and_no_req); -#endif + /* + #if AYAB_TESTS + FRIEND_TEST(KnitterTest, test_constructor); + FRIEND_TEST(KnitterTest, test_fsm_default_case); + FRIEND_TEST(KnitterTest, test_getStartOffset); + FRIEND_TEST(KnitterTest, test_operate_lastline_and_no_req); + #endif + */ public: Knitter(); void isr(); void fsm(); + void setUpInterrupt(); bool startOperation(Machine_t machineType, uint8_t startNeedle, uint8_t stopNeedle, uint8_t *pattern_start, bool continuousReportingEnabled); - bool startTest(); - bool setNextLine(uint8_t lineNumber); - void setLastLine(); - OpState_t getState(); + void send(uint8_t *payload, size_t length); void onPacketReceived(const uint8_t *buffer, size_t size); - Machine_t getMachineType(); + + OpState_t getState() const; + void state_init(); + static void state_ready(); + void state_operate(); + void state_test(); + + bool startTest(); + + Machine_t getMachineType() const; void setMachineType(Machine_t); + uint8_t getStartOffset(const Direction_t direction) const; + + bool setNextLine(uint8_t lineNumber); + void setLastLine(); + + // for testing purposes only + void setState(OpState_t state); + uint8_t getStartNeedle() const; + uint8_t getStopNeedle() const; + void setStopNeedle(uint8_t stopNeedle); + void setPosition(uint8_t position); + void setDirection(Direction_t direction); + void setCarriage(Carriage_t carriage); + void setFirstRun(bool firstRun); + void setWorkedOnLine(bool workedOnLine); + void setLineRequested(bool lineRequested); + void setLastLineFlag(bool lastLineFlag); private: Solenoids m_solenoids; @@ -104,13 +132,7 @@ class Knitter { uint8_t m_solenoidToSet = 0U; uint8_t m_pixelToSet = 0U; - void state_init(); - static void state_ready(); - void state_operate(); - void state_test(); - bool calculatePixelAndSolenoid(); - uint8_t getStartOffset(Direction_t direction); void reqLine(uint8_t lineNumber); void indState(bool initState = false); @@ -119,4 +141,6 @@ class Knitter { /* uint8_t m_lastLinesCountdown = 0U; */ }; +extern Knitter *knitter; + #endif // KNITTER_H_ diff --git a/src/ayab/main.cpp b/src/ayab/main.cpp index 2c03c7568..ff0125be3 100644 --- a/src/ayab/main.cpp +++ b/src/ayab/main.cpp @@ -24,23 +24,20 @@ #include -#ifdef AYAB_HW_TEST -#include -#endif - +#include "hw_test.h" #include "knitter.h" -/* Global Declarations */ -Knitter *knitter; ///< A pointer to the global instance of the knitter object. +// global definitions +// references everywhere else must use `extern` +Knitter *knitter; +HardwareTest *hwTest; /*! * Setup - steps to take before going to the main loop. */ void setup() { knitter = new Knitter(); -#ifdef AYAB_HW_TEST - hw_test_setup(); -#endif + hwTest = new HardwareTest(); } /*! @@ -48,9 +45,5 @@ void setup() { * or someone cuts power to us. */ void loop() { -#ifdef AYAB_HW_TEST - hw_test_loop(); -#else knitter->fsm(); -#endif } diff --git a/src/ayab/serial_encoding.cpp b/src/ayab/serial_encoding.cpp index fe854ab74..ff0abbd28 100644 --- a/src/ayab/serial_encoding.cpp +++ b/src/ayab/serial_encoding.cpp @@ -21,8 +21,8 @@ * http://ayab-knitting.com */ -#include "knitter.h" #include "serial_encoding.h" +#include "knitter.h" #ifndef AYAB_TESTS /*! @@ -32,11 +32,11 @@ * passed to _setPacketHandler_, and the only global variable * is knitter. */ -void gOnPacketReceived(const uint8_t *buffer, size_t size) { +static void gOnPacketReceived(const uint8_t *buffer, size_t size) { extern Knitter *knitter; knitter->onPacketReceived(buffer, size); } -#endif +#endif // AYAB_TESTS #ifdef AYAB_ENABLE_CRC /*! @@ -112,8 +112,9 @@ void SerialEncoding::h_reqStart(const uint8_t *buffer, size_t size) { } extern Knitter *knitter; - bool success = knitter->startOperation(machineType, startNeedle, stopNeedle, - lineBuffer, continuousReportingEnabled); + bool success = + knitter->startOperation(machineType, startNeedle, stopNeedle, lineBuffer, + continuousReportingEnabled); uint8_t payload[2]; payload[0] = cnfStart_msgid; @@ -131,11 +132,13 @@ void SerialEncoding::h_cnfLine(const uint8_t *buffer, size_t size) { extern Knitter *knitter; uint8_t lenLineBuffer = LINE_BUFFER_LEN[knitter->getMachineType()]; if (size < lenLineBuffer + 5U) { + // message is too short + // TODO(sl): handle error? return; } uint8_t lineNumber = buffer[1]; - /* uint8_t color = buffer[2]; // unused */ + /* uint8_t color = buffer[2]; // currently unused */ uint8_t flags = buffer[3]; for (uint8_t i = 0U; i < lenLineBuffer; i++) { @@ -147,6 +150,7 @@ void SerialEncoding::h_cnfLine(const uint8_t *buffer, size_t size) { uint8_t crc8 = buffer[lenLineBuffer + 4]; // Calculate checksum of buffer contents if (crc8 != CRC8(buffer, lenLineBuffer + 4)) { + // TODO(sl): handle checksum error? return; } #endif @@ -213,7 +217,7 @@ SerialEncoding::SerialEncoding() { m_packetSerial.begin(SERIAL_BAUDRATE); #ifndef AYAB_TESTS m_packetSerial.setPacketHandler(gOnPacketReceived); -#endif +#endif // AYAB_TESTS } void SerialEncoding::update() { @@ -221,14 +225,14 @@ void SerialEncoding::update() { } void SerialEncoding::send(uint8_t *payload, size_t length) { -/* -#ifdef AYAB_HW_TEST - Serial.print("Sent: "); - for (uint8_t i = 0; i < length; ++i) { - Serial.print(payload[i]); - } - Serial.print(", Encoded as: "); -#endif -*/ + /* + #ifdef AYAB_HW_TEST + Serial.print("Sent: "); + for (uint8_t i = 0; i < length; ++i) { + Serial.print(payload[i]); + } + Serial.print(", Encoded as: "); + #endif + */ m_packetSerial.send(payload, length); } diff --git a/src/ayab/serial_encoding.h b/src/ayab/serial_encoding.h index ed3640311..4bc1d014f 100644 --- a/src/ayab/serial_encoding.h +++ b/src/ayab/serial_encoding.h @@ -68,4 +68,4 @@ class SerialEncoding { void h_reqTest(); }; -#endif // SERIAL_ENCODING_H_ +#endif // SERIAL_ENCODING_H_ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bfa1b7911..61e97449c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -30,6 +30,7 @@ set(COMMON_INCLUDES ${ARDUINO_MOCK_LIBS_DIR}/lib/gtest/gtest/src/gtest/googlemock/include ${PROJECT_SOURCE_DIR}/mocks ${SOURCE_DIRECTORY} + ${LIBRARY_DIRECTORY}/SerialCommand ${LIBRARY_DIRECTORY}/PacketSerial/src ) set(EXTERNAL_LIB_INCLUDES @@ -132,6 +133,8 @@ add_executable(${PROJECT_NAME}_knitter ${PROJECT_SOURCE_DIR}/mocks/encoders_mock.cpp ${PROJECT_SOURCE_DIR}/mocks/beeper_mock.cpp ${PROJECT_SOURCE_DIR}/mocks/serial_encoding_mock.cpp + ${PROJECT_SOURCE_DIR}/mocks/hw_test_mock.cpp + ${LIBRARY_DIRECTORY}/SerialCommand/SerialCommand.cpp ${PROJECT_SOURCE_DIR}/test_knitter.cpp ${SOFT_I2C_LIB} ) diff --git a/test/mocks/beeper_mock.cpp b/test/mocks/beeper_mock.cpp index f2535174d..14fa47fe9 100644 --- a/test/mocks/beeper_mock.cpp +++ b/test/mocks/beeper_mock.cpp @@ -42,10 +42,12 @@ void Beeper::ready() { assert(gBeeperMock != NULL); gBeeperMock->ready(); } + void Beeper::finishedLine() { assert(gBeeperMock != NULL); gBeeperMock->finishedLine(); } + void Beeper::endWork() { assert(gBeeperMock != NULL); gBeeperMock->endWork(); diff --git a/test/mocks/hw_test_mock.cpp b/test/mocks/hw_test_mock.cpp new file mode 100644 index 000000000..a4ce05c4f --- /dev/null +++ b/test/mocks/hw_test_mock.cpp @@ -0,0 +1,49 @@ +/*!` + * \file hw_test_mock.cpp + * + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#include + +static HardwareTestMock *gHardwareTestMock = NULL; +HardwareTestMock *hardwareTestMockInstance() { + if (!gHardwareTestMock) { + gHardwareTestMock = new HardwareTestMock(); + } + return gHardwareTestMock; +} + +void releaseHardwareTestMock() { + if (gHardwareTestMock) { + delete gHardwareTestMock; + gHardwareTestMock = NULL; + } +} + +void HardwareTest::setUp() { + assert(gHardwareTestMock != NULL); + gHardwareTestMock->setUp(); +} + +void HardwareTest::loop() { + assert(gHardwareTestMock != NULL); + gHardwareTestMock->loop(); +} diff --git a/test/mocks/hw_test_mock.h b/test/mocks/hw_test_mock.h new file mode 100644 index 000000000..18949f965 --- /dev/null +++ b/test/mocks/hw_test_mock.h @@ -0,0 +1,39 @@ +/*!` + * \file hw_test_mock.h + * + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#ifndef HW_TEST_MOCK_H_ +#define HW_TEST_MOCK_H_ + +#include +#include + +class HardwareTestMock { +public: + MOCK_METHOD0(setUp, void()); + MOCK_METHOD0(loop, void()); +}; + +HardwareTestMock *hardwareTestMockInstance(); +void releaseHardwareTestMock(); + +#endif // HW_TEST_MOCK_H_ diff --git a/test/mocks/knitter_mock.cpp b/test/mocks/knitter_mock.cpp index b0772c670..cb56e7c0d 100644 --- a/test/mocks/knitter_mock.cpp +++ b/test/mocks/knitter_mock.cpp @@ -21,8 +21,8 @@ * http://ayab-knitting.com */ -#include #include +#include static KnitterMock *gKnitterMock = NULL; KnitterMock *knitterMockInstance() { @@ -39,7 +39,7 @@ void releaseKnitterMock() { } } -Machine_t Knitter::getMachineType() { +Machine_t Knitter::getMachineType() const { assert(gKnitterMock != NULL); return gKnitterMock->getMachineType(); } @@ -49,16 +49,23 @@ void Knitter::setMachineType(Machine_t machineType) { return gKnitterMock->setMachineType(machineType); } +uint8_t Knitter::getStartOffset(const Direction_t direction) const { + assert(gKnitterMock != NULL); + return gKnitterMock->getStartOffset(direction); +} + bool Knitter::startTest() { assert(gKnitterMock != NULL); return gKnitterMock->startTest(); } -bool Knitter::startOperation(Machine_t machineType, uint8_t startNeedle, uint8_t stopNeedle, - uint8_t *pattern_start, bool continuousReportingEnabled) { +bool Knitter::startOperation(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled) { assert(gKnitterMock != NULL); return gKnitterMock->startOperation(machineType, startNeedle, stopNeedle, - pattern_start, continuousReportingEnabled); + pattern_start, + continuousReportingEnabled); } bool Knitter::setNextLine(uint8_t lineNumber) { @@ -81,4 +88,59 @@ void Knitter::onPacketReceived(const uint8_t *buffer, size_t size) { gKnitterMock->onPacketReceived(buffer, size); } +void Knitter::setState(OpState_t state) { + assert(gKnitterMock != NULL); + gKnitterMock->setState(state); +} + +uint8_t Knitter::getStartNeedle() const { + assert(gKnitterMock != NULL); + return gKnitterMock->getStartNeedle(); +} + +uint8_t Knitter::getStopNeedle() const { + assert(gKnitterMock != NULL); + return gKnitterMock->getStopNeedle(); +} + +void Knitter::setStopNeedle(uint8_t stopNeedle) { + assert(gKnitterMock != NULL); + gKnitterMock->setStopNeedle(stopNeedle); +} + +void Knitter::setPosition(uint8_t position) { + assert(gKnitterMock != NULL); + gKnitterMock->setPosition(position); +} + +void Knitter::setDirection(Direction_t direction) { + assert(gKnitterMock != NULL); + gKnitterMock->setDirection(direction); +} + +void Knitter::setCarriage(Carriage_t carriage) { + assert(gKnitterMock != NULL); + gKnitterMock->setCarriage(carriage); +} + +void Knitter::setFirstRun(bool firstRun) { + assert(gKnitterMock != NULL); + gKnitterMock->setFirstRun(firstRun); +} + +void Knitter::setWorkedOnLine(bool workedOnLine) { + assert(gKnitterMock != NULL); + gKnitterMock->setWorkedOnLine(workedOnLine); +} + +void Knitter::setLineRequested(bool lineRequested) { + assert(gKnitterMock != NULL); + gKnitterMock->setLineRequested(lineRequested); +} + +void Knitter::setLastLineFlag(bool lastLineFlag) { + assert(gKnitterMock != NULL); + gKnitterMock->setLastLineFlag(lastLineFlag); +} + Knitter *knitter; diff --git a/test/mocks/knitter_mock.h b/test/mocks/knitter_mock.h index 9d7167441..25c1e191e 100644 --- a/test/mocks/knitter_mock.h +++ b/test/mocks/knitter_mock.h @@ -24,23 +24,36 @@ #ifndef KNITTER_MOCK_H_ #define KNITTER_MOCK_H_ -#include #include +#include class KnitterMock { public: MOCK_METHOD5(startOperation, bool(Machine_t machineType, uint8_t startNeedle, - uint8_t stopNeedle, uint8_t *pattern_start, bool continuousReportingEnabled)); + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled)); MOCK_METHOD0(startTest, bool()); MOCK_METHOD1(setNextLine, bool(uint8_t lineNumber)); MOCK_METHOD0(setLastLine, void()); MOCK_METHOD2(send, void(uint8_t *payload, size_t length)); MOCK_METHOD2(onPacketReceived, void(const uint8_t *buffer, size_t size)); - MOCK_METHOD0(getMachineType, Machine_t()); + MOCK_CONST_METHOD0(getMachineType, Machine_t()); MOCK_METHOD1(setMachineType, void(Machine_t)); + MOCK_CONST_METHOD1(getStartOffset, uint8_t(const Direction_t direction)); + MOCK_METHOD1(setState, void(OpState_t)); + MOCK_CONST_METHOD0(getStartNeedle, uint8_t()); + MOCK_CONST_METHOD0(getStopNeedle, uint8_t()); + MOCK_METHOD1(setStopNeedle, void(uint8_t)); + MOCK_METHOD1(setPosition, void(uint8_t)); + MOCK_METHOD1(setDirection, void(Direction_t)); + MOCK_METHOD1(setCarriage, void(Carriage_t)); + MOCK_METHOD1(setFirstRun, void(bool)); + MOCK_METHOD1(setWorkedOnLine, void(bool)); + MOCK_METHOD1(setLineRequested, void(bool)); + MOCK_METHOD1(setLastLineFlag, void(bool)); }; KnitterMock *knitterMockInstance(); void releaseKnitterMock(); -#endif // KNITTER_MOCK_H_ +#endif // KNITTER_MOCK_H_ diff --git a/test/test_knitter.cpp b/test/test_knitter.cpp index 970e2dd9e..d109992da 100644 --- a/test/test_knitter.cpp +++ b/test/test_knitter.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,11 @@ using ::testing::_; using ::testing::AtLeast; using ::testing::Return; +// global definitions +// references everywhere else must use `extern` +Knitter *knitter; +HardwareTest *hwTest; + void onPacketReceived(const uint8_t *buffer, size_t size) { (void)buffer; (void)size; @@ -47,6 +53,7 @@ class KnitterTest : public ::testing::Test { solenoidsMock = solenoidsMockInstance(); encodersMock = encodersMockInstance(); serialEncodingMock = serialEncodingMockInstance(); + hwTestMock = hardwareTestMockInstance(); expect_constructor(); k = new Knitter(); } @@ -57,6 +64,7 @@ class KnitterTest : public ::testing::Test { releaseSolenoidsMock(); releaseEncodersMock(); releaseSerialEncodingMock(); + releaseHardwareTestMock(); } ArduinoMock *arduinoMock; @@ -64,6 +72,7 @@ class KnitterTest : public ::testing::Test { SolenoidsMock *solenoidsMock; EncodersMock *encodersMock; SerialEncodingMock *serialEncodingMock; + HardwareTestMock *hwTestMock; Knitter *k; void expect_constructor() { @@ -207,7 +216,7 @@ class KnitterTest : public ::testing::Test { TEST_F(KnitterTest, test_constructor) { ASSERT_EQ(k->getState(), s_init); // NOTE: Probing private data! - ASSERT_EQ(k->m_startNeedle, 0); + ASSERT_EQ(k->getStartNeedle(), 0); } /*! @@ -233,7 +242,7 @@ TEST_F(KnitterTest, test_isr) { */ TEST_F(KnitterTest, test_fsm_default_case) { // NOTE: Probing private data to be able to cover all branches. - k->m_opState = static_cast(4); + k->setState(static_cast(4)); expected_fsm(); } @@ -562,14 +571,14 @@ TEST_F(KnitterTest, test_operate_lastline_and_no_req) { get_to_operate(Kh910); // Note probing lots of private data and methods to get full branch coverage. - k->m_stopNeedle = 100; - uint8_t wanted_pixel = k->m_stopNeedle + END_OF_LINE_OFFSET_R[Kh910] + 1; - k->m_firstRun = false; - k->m_direction = Left; - k->m_position = wanted_pixel + k->getStartOffset(Right); - k->m_workedOnLine = true; - k->m_lineRequested = false; - k->m_lastLineFlag = true; + k->setStopNeedle(100); + uint8_t wanted_pixel = k->getStopNeedle() + END_OF_LINE_OFFSET_R[Kh910] + 1; + k->setFirstRun(false); + k->setDirection(Left); + k->setPosition(wanted_pixel + k->getStartOffset(Right)); + k->setWorkedOnLine(true); + k->setLineRequested(false); + k->setLastLineFlag(true); EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 1)); EXPECT_CALL(*solenoidsMock, setSolenoid); @@ -579,7 +588,7 @@ TEST_F(KnitterTest, test_operate_lastline_and_no_req) { k->state_operate(); ASSERT_EQ(k->getStartOffset(NUM_DIRECTIONS), 0); - k->m_carriage = NUM_CARRIAGES; + k->setCarriage(NUM_CARRIAGES); ASSERT_EQ(k->getStartOffset(Right), 0); } @@ -677,14 +686,14 @@ TEST_F(KnitterTest, test_calculatePixelAndSolenoid) { */ TEST_F(KnitterTest, test_getStartOffset) { // out of range values - k->m_carriage = Knit; + k->setCarriage(Knit); ASSERT_EQ(k->getStartOffset(NoDirection), 0); ASSERT_EQ(k->getStartOffset(NUM_DIRECTIONS), 0); - k->m_carriage = NoCarriage; + k->setCarriage(NoCarriage); ASSERT_EQ(k->getStartOffset(Left), 0); - k->m_carriage = NUM_CARRIAGES; + k->setCarriage(NUM_CARRIAGES); ASSERT_EQ(k->getStartOffset(Right), 0); - k->m_carriage = Lace; + k->setCarriage(Lace); k->setMachineType(NoMachine); ASSERT_EQ(k->getStartOffset(Left), 0); k->setMachineType(NUM_MACHINES); From eaad74f1bbb210e8ed3e98fc0ca8f65db2ce36ae Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 12 Aug 2020 18:19:54 -0400 Subject: [PATCH 03/17] Add test script for HardwareTest class. Add SerialCommandMock class. --- src/ayab/encoders.h | 12 +- src/ayab/hw_test.cpp | 185 ++++++++++++++------------- src/ayab/hw_test.h | 60 +++++++-- src/ayab/knitter.cpp | 70 ++++------ src/ayab/knitter.h | 41 +++--- src/ayab/serial_encoding.cpp | 30 +++-- src/ayab/serial_encoding.h | 2 +- test/CMakeLists.txt | 6 +- test/mocks/SerialCommand_mock.cpp | 63 +++++++++ test/mocks/SerialCommand_mock.h | 40 ++++++ test/mocks/hw_test_mock.cpp | 60 +++++++++ test/mocks/hw_test_mock.h | 12 ++ test/mocks/knitter_mock.cpp | 56 ++------ test/mocks/knitter_mock.h | 16 +-- test/test_hw_test.cpp | 204 ++++++++++++++++++++++++++++++ test/test_knitter.cpp | 118 +++++++++++------ test/test_serial_encoding.cpp | 48 +++++-- 17 files changed, 727 insertions(+), 296 deletions(-) create mode 100644 test/mocks/SerialCommand_mock.cpp create mode 100644 test/mocks/SerialCommand_mock.h create mode 100644 test/test_hw_test.cpp diff --git a/src/ayab/encoders.h b/src/ayab/encoders.h index b26437f6f..af5d1ee14 100644 --- a/src/ayab/encoders.h +++ b/src/ayab/encoders.h @@ -119,14 +119,14 @@ class Encoders { void init(Machine_t machineType); private: - Direction_t m_direction = NoDirection; - Direction_t m_hallActive = NoDirection; - Beltshift_t m_beltShift = Unknown; - Carriage_t m_carriage = NoCarriage; Machine_t m_machineType = NoMachine; - uint8_t m_encoderPos = 0x00; - bool m_oldState = false; + volatile Direction_t m_direction = NoDirection; + volatile Direction_t m_hallActive = NoDirection; + volatile Beltshift_t m_beltShift = Unknown; + volatile Carriage_t m_carriage = NoCarriage; + volatile uint8_t m_encoderPos = 0x00; + volatile bool m_oldState = false; void encA_rising(); void encA_falling(); diff --git a/src/ayab/hw_test.cpp b/src/ayab/hw_test.cpp index a2a280048..6d1738883 100644 --- a/src/ayab/hw_test.cpp +++ b/src/ayab/hw_test.cpp @@ -25,10 +25,17 @@ #include "hw_test.h" #include "knitter.h" -static void prompt() { -} +extern Knitter *knitter; + +// initialise static members +SerialCommand HardwareTest::m_sCmd = SerialCommand(); +bool HardwareTest::m_autoReadOn = false; +bool HardwareTest::m_autoTestOn = false; +bool HardwareTest::m_timerEvent = false; +bool HardwareTest::m_timerEventOdd = false; +unsigned long HardwareTest::m_lastTime = 0U; -static void helpCmd() { +void HardwareTest::helpCmd() { Serial.println("The following commands are available:"); Serial.println("setSingle [0..15] [1/0]"); Serial.println("setAll [0..255] [0..255]"); @@ -41,93 +48,76 @@ static void helpCmd() { Serial.println("stop"); Serial.println("quit"); Serial.println("help"); - - prompt(); } -static void sendCmd() { - extern Knitter *knitter; +void HardwareTest::sendCmd() { Serial.println("Called send"); - uint8_t p[] = {1, 2, 3}; knitter->send(p, 3); Serial.print("\n"); - prompt(); } -static void beep() { - hwTest->m_beeper.ready(); +void HardwareTest::beep() { + knitter->m_beeper.ready(); } /*! * \brief Beep command handler. */ -static void beepCmd() { +void HardwareTest::beepCmd() { Serial.println("Called beep"); - - beep(); - prompt(); -} - -/*! - * \brief Interrupt service routine for encoder A. - */ -static void encoderAChange() { beep(); } /*! * \brief Set single solenoid command handler. */ -static void setSingleCmd() { +void HardwareTest::setSingleCmd() { Serial.println("Called setSingle"); - - char *arg = hwTest->m_SCmd.next(); + char *arg = m_sCmd.next(); if (arg == nullptr) { return; } - uint8_t solenoidNumber = atoi(arg); - if (solenoidNumber > 15) { + int solenoidNumber = atoi(arg); + if (solenoidNumber < 0 or solenoidNumber > 15) { Serial.print("Invalid argument: "); Serial.println(solenoidNumber); return; } + arg = m_sCmd.next(); if (arg == nullptr) { return; } - arg = hwTest->m_SCmd.next(); - uint8_t solenoidState = atoi(arg); - if (solenoidState > 1) { + int solenoidState = atoi(arg); + if (solenoidState < 0 or solenoidState > 1) { Serial.print("Invalid argument: "); Serial.println(solenoidState); return; } - hwTest->m_solenoids.setSolenoid(solenoidNumber, solenoidState); - prompt(); + knitter->setSolenoid(static_cast(solenoidNumber), + static_cast(solenoidState)); } /*! * \brief Set all solenoids command handler. */ -static void setAllCmd() { +void HardwareTest::setAllCmd() { Serial.println("Called setAll"); - - char *arg = hwTest->m_SCmd.next(); + char *arg = m_sCmd.next(); if (arg == nullptr) { return; } uint8_t highByte = atoi(arg); - arg = hwTest->m_SCmd.next(); + arg = m_sCmd.next(); if (arg == nullptr) { return; } uint8_t lowByte = atoi(arg); uint16_t solenoidState = (highByte << 8) + lowByte; - hwTest->m_solenoids.setSolenoids(solenoidState); - prompt(); + knitter->setSolenoids(solenoidState); } -static void readEOLsensors() { +void HardwareTest::readEOLsensors() { Serial.print(" EOL_L: "); Serial.print(analogRead(EOL_PIN_L)); Serial.print(" EOL_R: "); @@ -137,15 +127,13 @@ static void readEOLsensors() { /*! * \brief Read EOL sensors command handler. */ -static void readEOLsensorsCmd() { +void HardwareTest::readEOLsensorsCmd() { Serial.println("Called readEOLsensors"); - readEOLsensors(); Serial.print("\n"); - prompt(); } -static void readEncoders() { +void HardwareTest::readEncoders() { Serial.print(" ENC_A: "); bool state = digitalRead(ENC_PIN_A); Serial.print(state ? "HIGH" : "LOW"); @@ -160,80 +148,70 @@ static void readEncoders() { /*! * \brief Read encoders command handler. */ -static void readEncodersCmd() { +void HardwareTest::readEncodersCmd() { Serial.println("Called readEncoders"); - readEncoders(); Serial.print("\n"); - prompt(); } -static void autoRead() { +void HardwareTest::autoRead() { Serial.print("\n"); readEOLsensors(); readEncoders(); Serial.print("\n"); - prompt(); - // delay(1000); } /*! * \brief Auto read command handler. */ -static void autoReadCmd() { +void HardwareTest::autoReadCmd() { Serial.println("Called autoRead, send stop to quit"); - hwTest->m_autoReadOn = true; + m_autoReadOn = true; } -static void autoTestEven() { - Serial.print("\n"); +void HardwareTest::autoTestEven() { Serial.println("Set even solenoids"); digitalWrite(LED_PIN_A, 1); digitalWrite(LED_PIN_B, 1); - hwTest->m_solenoids.setSolenoids(0xAAAA); - // delay(500); + knitter->setSolenoids(0xAAAA); } -static void autoTestOdd() { +void HardwareTest::autoTestOdd() { Serial.println("Set odd solenoids"); digitalWrite(LED_PIN_A, 0); digitalWrite(LED_PIN_B, 0); - hwTest->m_solenoids.setSolenoids(0x5555); - prompt(); - // delay(500); + knitter->setSolenoids(0x5555); } /*! * \brief Auto test command handler. */ -static void autoTestCmd() { +void HardwareTest::autoTestCmd() { Serial.println("Called autoTest, send stop to quit"); - hwTest->m_autoTestOn = true; + m_autoTestOn = true; } -static void stopCmd() { - hwTest->m_autoReadOn = false; - hwTest->m_autoTestOn = false; - prompt(); +void HardwareTest::stopCmd() { + m_autoReadOn = false; + m_autoTestOn = false; } -static void quitCmd() { - extern Knitter *knitter; - hwTest->m_quit = true; +void HardwareTest::quitCmd() { + knitter->setQuitFlag(true); knitter->setUpInterrupt(); } /*! * \brief Unrecognized command handler. * - * \param buffer Unused buffer parameter. + * \param buffer: pointer to string containing unrecognized command * * This gets set as the default handler, and gets called when no other command * matches. */ -static void unrecognizedCmd(const char *buffer) { - Serial.println("Unrecognized Command"); - (void)(buffer); +void HardwareTest::unrecognizedCmd(const char *buffer) { + Serial.println("Unrecognized command"); + (void)(buffer); // does nothing but prevents 'unused variable' compile error helpCmd(); } @@ -243,23 +221,19 @@ static void unrecognizedCmd(const char *buffer) { * \brief Setup for hw tests. */ void HardwareTest::setUp() { - // attach interrupt for ENC_PIN_A(=2), interrupt #0 - detachInterrupt(0); - attachInterrupt(0, encoderAChange, RISING); - // set up callbacks for SerialCommand commands - m_SCmd.addCommand("setSingle", setSingleCmd); - m_SCmd.addCommand("setAll", setAllCmd); - m_SCmd.addCommand("readEOLsensors", readEOLsensorsCmd); - m_SCmd.addCommand("readEncoders", readEncodersCmd); - m_SCmd.addCommand("beep", beepCmd); - m_SCmd.addCommand("autoRead", autoReadCmd); - m_SCmd.addCommand("autoTest", autoTestCmd); - m_SCmd.addCommand("send", sendCmd); - m_SCmd.addCommand("stop", stopCmd); - m_SCmd.addCommand("quit", quitCmd); - m_SCmd.addCommand("help", helpCmd); - m_SCmd.setDefaultHandler(unrecognizedCmd); + m_sCmd.addCommand("setSingle", HardwareTest::setSingleCmd); + m_sCmd.addCommand("setAll", HardwareTest::setAllCmd); + m_sCmd.addCommand("readEOLsensors", HardwareTest::readEOLsensorsCmd); + m_sCmd.addCommand("readEncoders", HardwareTest::readEncodersCmd); + m_sCmd.addCommand("beep", HardwareTest::beepCmd); + m_sCmd.addCommand("autoRead", HardwareTest::autoReadCmd); + m_sCmd.addCommand("autoTest", HardwareTest::autoTestCmd); + m_sCmd.addCommand("send", HardwareTest::sendCmd); + m_sCmd.addCommand("stop", HardwareTest::stopCmd); + m_sCmd.addCommand("quit", HardwareTest::quitCmd); + m_sCmd.addCommand("help", HardwareTest::helpCmd); + m_sCmd.setDefaultHandler(HardwareTest::unrecognizedCmd); // Print welcome message Serial.print("AYAB Hardware Test, Firmware v"); @@ -270,13 +244,45 @@ void HardwareTest::setUp() { Serial.print(API_VERSION); Serial.print("\n\n"); helpCmd(); + + // attach interrupt for ENC_PIN_A(=2), interrupt #0 + detachInterrupt(0); +#ifndef AYAB_TESTS + attachInterrupt(0, encoderAChange, RISING); + + m_lastTime = millis(); +#endif // AYAB_TESTS + m_autoReadOn = false; + m_autoTestOn = false; + m_timerEventOdd = false; + knitter->setQuitFlag(false); +} + +#ifndef AYAB_TESTS +/*! + * \brief Interrupt service routine for encoder A. + */ +void HardwareTest::encoderAChange() { + beep(); } +#endif // AYAB_TESTS /*! * \brief Main loop for hardware tests. */ void HardwareTest::loop() { - if (m_autoReadOn and m_timerEvent and m_timerEventOdd) { + unsigned long now = millis(); + if (now - m_lastTime >= 500) { + m_lastTime = now; + handleTimerEvent(); + } +} + +/*! + * \brief Timer event every 500ms to handle auto functions. + */ +void HardwareTest::handleTimerEvent() { + if (m_autoReadOn and m_timerEventOdd) { autoRead(); } if (m_autoTestOn) { @@ -286,7 +292,6 @@ void HardwareTest::loop() { autoTestEven(); } } - m_timerEvent = false; m_timerEventOdd = not m_timerEventOdd; - m_SCmd.readSerial(); + m_sCmd.readSerial(); } diff --git a/src/ayab/hw_test.h b/src/ayab/hw_test.h index f041cca77..9183c741a 100644 --- a/src/ayab/hw_test.h +++ b/src/ayab/hw_test.h @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with AYAB. If not, see . * - * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller * Modified Work Copyright 2020 Sturla Lange, Tom Price * http://ayab-knitting.com */ @@ -27,22 +27,54 @@ #include #include "beeper.h" -#include "solenoids.h" class HardwareTest { +#if AYAB_TESTS + FRIEND_TEST(HardwareTestTest, test_setUp); + FRIEND_TEST(HardwareTestTest, test_loop_default); + FRIEND_TEST(HardwareTestTest, test_loop_autoRead); + FRIEND_TEST(HardwareTestTest, test_loop_autoTestEven); + FRIEND_TEST(HardwareTestTest, test_loop_autoTestOdd); + friend class HardwareTestTest; +#endif public: - void setUp(); - void loop(); - - SerialCommand m_SCmd; - Beeper m_beeper; - Solenoids m_solenoids; - - volatile bool m_timerEvent = false; - bool m_timerEventOdd = false; - bool m_autoReadOn = false; - bool m_autoTestOn = false; - bool m_quit = false; + static void helpCmd(); + static void sendCmd(); + static void beepCmd(); + static void setSingleCmd(); + static void setAllCmd(); + static void readEOLsensorsCmd(); + static void readEncodersCmd(); + static void autoReadCmd(); + static void autoTestCmd(); + static void stopCmd(); + static void quitCmd(); + static void unrecognizedCmd(const char *buffer); + + static void setUp(); + static void loop(); + + static SerialCommand m_sCmd; + /* static bool m_quitFlag; */ + +private: + static void beep(); + static void readEOLsensors(); + static void readEncoders(); + static void autoRead(); + static void autoTestEven(); + static void autoTestOdd(); + static void handleTimerEvent(); + +#ifndef AYAB_TESTS + static void encoderAChange(); +#endif + + static bool m_autoReadOn; + static bool m_autoTestOn; + + static unsigned long m_lastTime; + static bool m_timerEventOdd; }; extern HardwareTest *hwTest; diff --git a/src/ayab/knitter.cpp b/src/ayab/knitter.cpp index da5a9eeca..fa30fa2e8 100644 --- a/src/ayab/knitter.cpp +++ b/src/ayab/knitter.cpp @@ -54,10 +54,7 @@ static void isr_wrapper() { * * Initializes the solenoids as well as pins and interrupts. */ -Knitter::Knitter() - : // incorporated by composition - m_beeper(), m_serial_encoding() { - +Knitter::Knitter() : m_beeper(), m_serial_encoding() { pinMode(ENC_PIN_A, INPUT); pinMode(ENC_PIN_B, INPUT); pinMode(ENC_PIN_C, INPUT); @@ -75,7 +72,7 @@ Knitter::Knitter() } void Knitter::setUpInterrupt() { - // attach ENC_PIN_A(=2), interrupt #0 + // (re-)attach ENC_PIN_A(=2), interrupt #0 detachInterrupt(0); #ifndef AYAB_TESTS attachInterrupt(0, isr_wrapper, CHANGE); @@ -183,10 +180,12 @@ bool Knitter::startOperation(Machine_t machineType, uint8_t startNeedle, return true; } -bool Knitter::startTest() { +bool Knitter::startTest(Machine_t machineType) { bool success = false; if (s_init == m_opState || s_ready == m_opState) { m_opState = s_test; + m_machineType = machineType; + HardwareTest::setUp(); success = true; } return success; @@ -213,7 +212,7 @@ void Knitter::setLastLine() { m_lastLineFlag = true; } -// Private Methods +// private methods void Knitter::state_init() { #ifdef DBG_NOMACHINE @@ -345,7 +344,14 @@ void Knitter::state_test() { calculatePixelAndSolenoid(); indState(); } - hwTest->loop(); + HardwareTest::loop(); + if (m_quitFlag) { + m_opState = s_ready; + } +} + +void Knitter::setQuitFlag(bool flag) { + m_quitFlag = flag; } bool Knitter::calculatePixelAndSolenoid() { @@ -409,6 +415,14 @@ bool Knitter::calculatePixelAndSolenoid() { return success; } +void Knitter::setSolenoids(uint16_t state) { + m_solenoids.setSolenoids(state); +} + +void Knitter::setSolenoid(uint8_t solenoid, uint8_t state) { + m_solenoids.setSolenoid(solenoid, state); +} + uint8_t Knitter::getStartOffset(const Direction_t direction) const { if ((direction == NoDirection) || (direction >= NUM_DIRECTIONS) || (m_carriage == NoCarriage) || (m_carriage >= NUM_CARRIAGES) || @@ -467,43 +481,3 @@ void Knitter::setMachineType(Machine_t machineType) { void Knitter::setState(OpState_t state) { m_opState = state; } - -uint8_t Knitter::getStartNeedle() const { - return m_startNeedle; -} - -uint8_t Knitter::getStopNeedle() const { - return m_stopNeedle; -} - -void Knitter::setStopNeedle(uint8_t stopNeedle) { - m_stopNeedle = stopNeedle; -} - -void Knitter::setPosition(uint8_t position) { - m_position = position; -} - -void Knitter::setDirection(Direction_t direction) { - m_direction = direction; -} - -void Knitter::setCarriage(Carriage_t carriage) { - m_carriage = carriage; -} - -void Knitter::setFirstRun(bool firstRun) { - m_firstRun = firstRun; -} - -void Knitter::setWorkedOnLine(bool workedOnLine) { - m_workedOnLine = workedOnLine; -} - -void Knitter::setLineRequested(bool lineRequested) { - m_lineRequested = lineRequested; -} - -void Knitter::setLastLineFlag(bool lastLineFlag) { - m_lastLineFlag = lastLineFlag; -} diff --git a/src/ayab/knitter.h b/src/ayab/knitter.h index d9cc64cb6..c0dd6a030 100644 --- a/src/ayab/knitter.h +++ b/src/ayab/knitter.h @@ -44,14 +44,16 @@ using OpState_t = enum OpState; * encoders, and serial communication. */ class Knitter { - /* - #if AYAB_TESTS - FRIEND_TEST(KnitterTest, test_constructor); - FRIEND_TEST(KnitterTest, test_fsm_default_case); - FRIEND_TEST(KnitterTest, test_getStartOffset); - FRIEND_TEST(KnitterTest, test_operate_lastline_and_no_req); - #endif - */ +#if AYAB_TESTS + FRIEND_TEST(KnitterTest, test_constructor); + FRIEND_TEST(KnitterTest, test_fsm_default_case); + FRIEND_TEST(KnitterTest, test_getStartOffset); + FRIEND_TEST(KnitterTest, test_operate_lastline); + FRIEND_TEST(KnitterTest, test_operate_lastline_and_no_req); + FRIEND_TEST(KnitterTest, test_quit_hw_test); +#endif + friend class HardwareTest; + public: Knitter(); @@ -70,28 +72,21 @@ class Knitter { static void state_ready(); void state_operate(); void state_test(); + void setQuitFlag(bool flag); - bool startTest(); + bool startTest(Machine_t machineType); - Machine_t getMachineType() const; - void setMachineType(Machine_t); uint8_t getStartOffset(const Direction_t direction) const; + Machine_t getMachineType() const; bool setNextLine(uint8_t lineNumber); void setLastLine(); // for testing purposes only + void setMachineType(Machine_t); void setState(OpState_t state); - uint8_t getStartNeedle() const; - uint8_t getStopNeedle() const; - void setStopNeedle(uint8_t stopNeedle); - void setPosition(uint8_t position); - void setDirection(Direction_t direction); - void setCarriage(Carriage_t carriage); - void setFirstRun(bool firstRun); - void setWorkedOnLine(bool workedOnLine); - void setLineRequested(bool lineRequested); - void setLastLineFlag(bool lastLineFlag); + void setSolenoids(uint16_t state); + void setSolenoid(uint8_t solenoid, uint8_t state); private: Solenoids m_solenoids; @@ -101,8 +96,7 @@ class Knitter { // machine state OpState_t m_opState = s_init; - - bool m_lastLineFlag = false; + bool m_quitFlag = false; // job parameters Machine_t m_machineType = NoMachine; @@ -120,6 +114,7 @@ class Knitter { bool m_lineRequested = false; uint8_t m_currentLineNumber = 0U; + bool m_lastLineFlag = false; uint8_t m_sOldPosition = 0U; bool m_firstRun = true; diff --git a/src/ayab/serial_encoding.cpp b/src/ayab/serial_encoding.cpp index ff0abbd28..08f0b5efa 100644 --- a/src/ayab/serial_encoding.cpp +++ b/src/ayab/serial_encoding.cpp @@ -24,6 +24,8 @@ #include "serial_encoding.h" #include "knitter.h" +extern Knitter *knitter; + #ifndef AYAB_TESTS /*! * \brief Wrapper for knitter's onPacketReceived. @@ -33,7 +35,6 @@ * is knitter. */ static void gOnPacketReceived(const uint8_t *buffer, size_t size) { - extern Knitter *knitter; knitter->onPacketReceived(buffer, size); } #endif // AYAB_TESTS @@ -111,7 +112,6 @@ void SerialEncoding::h_reqStart(const uint8_t *buffer, size_t size) { lineBuffer[i] = 0xFFU; } - extern Knitter *knitter; bool success = knitter->startOperation(machineType, startNeedle, stopNeedle, lineBuffer, continuousReportingEnabled); @@ -129,7 +129,6 @@ void SerialEncoding::h_reqStart(const uint8_t *buffer, size_t size) { * \todo sl: Assert size? Handle error? */ void SerialEncoding::h_cnfLine(const uint8_t *buffer, size_t size) { - extern Knitter *knitter; uint8_t lenLineBuffer = LINE_BUFFER_LEN[knitter->getMachineType()]; if (size < lenLineBuffer + 5U) { // message is too short @@ -173,9 +172,20 @@ void SerialEncoding::h_reqInfo() { send(payload, 4); } -void SerialEncoding::h_reqTest() { - extern Knitter *knitter; - bool success = knitter->startTest(); +/*! + * \brief Handle request hardware test command. + * + * \todo TP: Assert size? Handle error? + */ +void SerialEncoding::h_reqTest(const uint8_t *buffer, size_t size) { + if (size < 1U) { + // message is too short + // TODO(TP): handle error? + return; + } + + Machine_t machineType = static_cast(buffer[0]); + bool success = knitter->startTest(machineType); uint8_t payload[2]; payload[0] = cnfTest_msgid; @@ -184,10 +194,11 @@ void SerialEncoding::h_reqTest() { } static void h_unrecognized() { + // do nothing } -/*! Callback for PacketSerial - * +/*! + * \brief Callback for PacketSerial. */ void SerialEncoding::onPacketReceived(const uint8_t *buffer, size_t size) { switch (buffer[0]) { @@ -204,7 +215,7 @@ void SerialEncoding::onPacketReceived(const uint8_t *buffer, size_t size) { break; case reqTest_msgid: - h_reqTest(); + h_reqTest(buffer, size); break; default: @@ -225,6 +236,7 @@ void SerialEncoding::update() { } void SerialEncoding::send(uint8_t *payload, size_t length) { + // TODO(TP): insert a workaround for hardware test code /* #ifdef AYAB_HW_TEST Serial.print("Sent: "); diff --git a/src/ayab/serial_encoding.h b/src/ayab/serial_encoding.h index 4bc1d014f..197451eba 100644 --- a/src/ayab/serial_encoding.h +++ b/src/ayab/serial_encoding.h @@ -65,7 +65,7 @@ class SerialEncoding { void h_reqStart(const uint8_t *buffer, size_t size); void h_cnfLine(const uint8_t *buffer, size_t size); void h_reqInfo(); - void h_reqTest(); + void h_reqTest(const uint8_t *buffer, size_t size); }; #endif // SERIAL_ENCODING_H_ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 61e97449c..2ac2ce66e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -52,6 +52,10 @@ set(COMMON_SOURCES ${SOURCE_DIRECTORY}/serial_encoding.cpp ${PROJECT_SOURCE_DIR}/test_serial_encoding.cpp + ${SOURCE_DIRECTORY}/hw_test.cpp + ${PROJECT_SOURCE_DIR}/test_hw_test.cpp + ${PROJECT_SOURCE_DIR}/mocks/SerialCommand_mock.cpp + ${PROJECT_SOURCE_DIR}/mocks/knitter_mock.cpp ) set(COMMON_DEFINES @@ -134,8 +138,8 @@ add_executable(${PROJECT_NAME}_knitter ${PROJECT_SOURCE_DIR}/mocks/beeper_mock.cpp ${PROJECT_SOURCE_DIR}/mocks/serial_encoding_mock.cpp ${PROJECT_SOURCE_DIR}/mocks/hw_test_mock.cpp - ${LIBRARY_DIRECTORY}/SerialCommand/SerialCommand.cpp ${PROJECT_SOURCE_DIR}/test_knitter.cpp + ${LIBRARY_DIRECTORY}/SerialCommand/SerialCommand.cpp ${SOFT_I2C_LIB} ) target_include_directories(${PROJECT_NAME}_knitter diff --git a/test/mocks/SerialCommand_mock.cpp b/test/mocks/SerialCommand_mock.cpp new file mode 100644 index 000000000..278698817 --- /dev/null +++ b/test/mocks/SerialCommand_mock.cpp @@ -0,0 +1,63 @@ +/*!` + * \file SerialCommand_mock.cpp + * + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#include +#include + +static SerialCommandMock *gSerialCommandMock = NULL; +SerialCommandMock *serialCommandMockInstance() { + if (!gSerialCommandMock) { + gSerialCommandMock = new SerialCommandMock(); + } + return gSerialCommandMock; +} + +void releaseSerialCommandMock() { + if (gSerialCommandMock) { + delete gSerialCommandMock; + gSerialCommandMock = NULL; + } +} + +SerialCommand::SerialCommand() { +} + +char *SerialCommand::next() { + assert(gSerialCommandMock != nullptr); + return gSerialCommandMock->next(); +} + +void SerialCommand::addCommand(const char *command, void (*function)()) { + assert(gSerialCommandMock != nullptr); + gSerialCommandMock->addCommand(command, function); +} + +void SerialCommand::setDefaultHandler(void (*function)(const char *command)) { + assert(gSerialCommandMock != nullptr); + gSerialCommandMock->setDefaultHandler(function); +} + +void SerialCommand::readSerial() { + assert(gSerialCommandMock != nullptr); + gSerialCommandMock->readSerial(); +} diff --git a/test/mocks/SerialCommand_mock.h b/test/mocks/SerialCommand_mock.h new file mode 100644 index 000000000..b4c52ba8e --- /dev/null +++ b/test/mocks/SerialCommand_mock.h @@ -0,0 +1,40 @@ +/*!` + * \file SerialCommand_mock.h + * + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#ifndef SERIAL_COMMAND_MOCK_H_ +#define SERIAL_COMMAND_MOCK_H_ + +#include + +class SerialCommandMock { +public: + MOCK_METHOD0(next, char *()); + MOCK_METHOD2(addCommand, void(const char *command, void (*function)())); + MOCK_METHOD1(setDefaultHandler, void(void (*function)(const char *command))); + MOCK_METHOD0(readSerial, void()); +}; + +SerialCommandMock *serialCommandMockInstance(); +void releaseSerialCommandMock(); + +#endif // SERIAL_COMMAND_MOCK_H_ diff --git a/test/mocks/hw_test_mock.cpp b/test/mocks/hw_test_mock.cpp index a4ce05c4f..e25cfb5a6 100644 --- a/test/mocks/hw_test_mock.cpp +++ b/test/mocks/hw_test_mock.cpp @@ -38,6 +38,66 @@ void releaseHardwareTestMock() { } } +void HardwareTest::helpCmd() { + assert(gHardwareTestMock != NULL); + gHardwareTestMock->helpCmd(); +} + +void HardwareTest::sendCmd() { + assert(gHardwareTestMock != NULL); + gHardwareTestMock->sendCmd(); +} + +void HardwareTest::beepCmd() { + assert(gHardwareTestMock != NULL); + gHardwareTestMock->beepCmd(); +} + +void HardwareTest::setSingleCmd() { + assert(gHardwareTestMock != NULL); + gHardwareTestMock->setSingleCmd(); +} + +void HardwareTest::setAllCmd() { + assert(gHardwareTestMock != NULL); + gHardwareTestMock->setAllCmd(); +} + +void HardwareTest::readEOLsensorsCmd() { + assert(gHardwareTestMock != NULL); + gHardwareTestMock->readEOLsensorsCmd(); +} + +void HardwareTest::readEncodersCmd() { + assert(gHardwareTestMock != NULL); + gHardwareTestMock->readEncodersCmd(); +} + +void HardwareTest::autoReadCmd() { + assert(gHardwareTestMock != NULL); + gHardwareTestMock->autoReadCmd(); +} + +void HardwareTest::autoTestCmd() { + assert(gHardwareTestMock != NULL); + gHardwareTestMock->autoTestCmd(); +} + +void HardwareTest::stopCmd() { + assert(gHardwareTestMock != NULL); + gHardwareTestMock->stopCmd(); +} + +void HardwareTest::quitCmd() { + assert(gHardwareTestMock != NULL); + gHardwareTestMock->quitCmd(); +} + +void HardwareTest::unrecognizedCmd(const char *buffer) { + assert(gHardwareTestMock != NULL); + gHardwareTestMock->unrecognizedCmd(buffer); +} + void HardwareTest::setUp() { assert(gHardwareTestMock != NULL); gHardwareTestMock->setUp(); diff --git a/test/mocks/hw_test_mock.h b/test/mocks/hw_test_mock.h index 18949f965..f317c991d 100644 --- a/test/mocks/hw_test_mock.h +++ b/test/mocks/hw_test_mock.h @@ -29,6 +29,18 @@ class HardwareTestMock { public: + MOCK_METHOD0(helpCmd, void()); + MOCK_METHOD0(sendCmd, void()); + MOCK_METHOD0(beepCmd, void()); + MOCK_METHOD0(setSingleCmd, void()); + MOCK_METHOD0(setAllCmd, void()); + MOCK_METHOD0(readEOLsensorsCmd, void()); + MOCK_METHOD0(readEncodersCmd, void()); + MOCK_METHOD0(autoReadCmd, void()); + MOCK_METHOD0(autoTestCmd, void()); + MOCK_METHOD0(stopCmd, void()); + MOCK_METHOD0(quitCmd, void()); + MOCK_METHOD1(unrecognizedCmd, void(const char *)); MOCK_METHOD0(setUp, void()); MOCK_METHOD0(loop, void()); }; diff --git a/test/mocks/knitter_mock.cpp b/test/mocks/knitter_mock.cpp index cb56e7c0d..35f26aaa8 100644 --- a/test/mocks/knitter_mock.cpp +++ b/test/mocks/knitter_mock.cpp @@ -39,6 +39,11 @@ void releaseKnitterMock() { } } +bool Knitter::startTest(Machine_t machineType) { + assert(gKnitterMock != NULL); + return gKnitterMock->startTest(machineType); +} + Machine_t Knitter::getMachineType() const { assert(gKnitterMock != NULL); return gKnitterMock->getMachineType(); @@ -54,11 +59,6 @@ uint8_t Knitter::getStartOffset(const Direction_t direction) const { return gKnitterMock->getStartOffset(direction); } -bool Knitter::startTest() { - assert(gKnitterMock != NULL); - return gKnitterMock->startTest(); -} - bool Knitter::startOperation(Machine_t machineType, uint8_t startNeedle, uint8_t stopNeedle, uint8_t *pattern_start, bool continuousReportingEnabled) { @@ -93,54 +93,24 @@ void Knitter::setState(OpState_t state) { gKnitterMock->setState(state); } -uint8_t Knitter::getStartNeedle() const { - assert(gKnitterMock != NULL); - return gKnitterMock->getStartNeedle(); -} - -uint8_t Knitter::getStopNeedle() const { - assert(gKnitterMock != NULL); - return gKnitterMock->getStopNeedle(); -} - -void Knitter::setStopNeedle(uint8_t stopNeedle) { - assert(gKnitterMock != NULL); - gKnitterMock->setStopNeedle(stopNeedle); -} - -void Knitter::setPosition(uint8_t position) { - assert(gKnitterMock != NULL); - gKnitterMock->setPosition(position); -} - -void Knitter::setDirection(Direction_t direction) { - assert(gKnitterMock != NULL); - gKnitterMock->setDirection(direction); -} - -void Knitter::setCarriage(Carriage_t carriage) { - assert(gKnitterMock != NULL); - gKnitterMock->setCarriage(carriage); -} - -void Knitter::setFirstRun(bool firstRun) { +void Knitter::setSolenoids(uint16_t state) { assert(gKnitterMock != NULL); - gKnitterMock->setFirstRun(firstRun); + gKnitterMock->setSolenoids(state); } -void Knitter::setWorkedOnLine(bool workedOnLine) { +void Knitter::setSolenoid(uint8_t solenoid, uint8_t state) { assert(gKnitterMock != NULL); - gKnitterMock->setWorkedOnLine(workedOnLine); + gKnitterMock->setSolenoid(solenoid, state); } -void Knitter::setLineRequested(bool lineRequested) { +void Knitter::setQuitFlag(bool flag) { assert(gKnitterMock != NULL); - gKnitterMock->setLineRequested(lineRequested); + gKnitterMock->setQuitFlag(flag); } -void Knitter::setLastLineFlag(bool lastLineFlag) { +void Knitter::setUpInterrupt() { assert(gKnitterMock != NULL); - gKnitterMock->setLastLineFlag(lastLineFlag); + gKnitterMock->setUpInterrupt(); } Knitter *knitter; diff --git a/test/mocks/knitter_mock.h b/test/mocks/knitter_mock.h index 25c1e191e..8ad56bb7f 100644 --- a/test/mocks/knitter_mock.h +++ b/test/mocks/knitter_mock.h @@ -32,7 +32,7 @@ class KnitterMock { MOCK_METHOD5(startOperation, bool(Machine_t machineType, uint8_t startNeedle, uint8_t stopNeedle, uint8_t *pattern_start, bool continuousReportingEnabled)); - MOCK_METHOD0(startTest, bool()); + MOCK_METHOD1(startTest, bool(Machine_t)); MOCK_METHOD1(setNextLine, bool(uint8_t lineNumber)); MOCK_METHOD0(setLastLine, void()); MOCK_METHOD2(send, void(uint8_t *payload, size_t length)); @@ -41,16 +41,10 @@ class KnitterMock { MOCK_METHOD1(setMachineType, void(Machine_t)); MOCK_CONST_METHOD1(getStartOffset, uint8_t(const Direction_t direction)); MOCK_METHOD1(setState, void(OpState_t)); - MOCK_CONST_METHOD0(getStartNeedle, uint8_t()); - MOCK_CONST_METHOD0(getStopNeedle, uint8_t()); - MOCK_METHOD1(setStopNeedle, void(uint8_t)); - MOCK_METHOD1(setPosition, void(uint8_t)); - MOCK_METHOD1(setDirection, void(Direction_t)); - MOCK_METHOD1(setCarriage, void(Carriage_t)); - MOCK_METHOD1(setFirstRun, void(bool)); - MOCK_METHOD1(setWorkedOnLine, void(bool)); - MOCK_METHOD1(setLineRequested, void(bool)); - MOCK_METHOD1(setLastLineFlag, void(bool)); + MOCK_METHOD1(setSolenoids, void(uint16_t state)); + MOCK_METHOD2(setSolenoid, void(uint8_t solenoid, uint8_t state)); + MOCK_METHOD1(setQuitFlag, void(bool flag)); + MOCK_METHOD0(setUpInterrupt, void()); }; KnitterMock *knitterMockInstance(); diff --git a/test/test_hw_test.cpp b/test/test_hw_test.cpp new file mode 100644 index 000000000..026c2800a --- /dev/null +++ b/test/test_hw_test.cpp @@ -0,0 +1,204 @@ +/*!` + * \file test_hw_test.cpp + * + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#include + +#include +#include +#include + +using ::testing::AtLeast; +using ::testing::Return; + +static char zero[2] = {48, 0}; // "0" +static char two[2] = {50, 0}; // "2" +static char twenty[3] = {50, 48, 0}; // "20" + +class HardwareTestTest : public ::testing::Test { +protected: + void SetUp() override { + arduinoMock = arduinoMockInstance(); + serialMock = serialMockInstance(); + serialCommandMock = serialCommandMockInstance(); + knitterMock = knitterMockInstance(); + } + + void TearDown() override { + releaseArduinoMock(); + releaseSerialMock(); + releaseSerialCommandMock(); + releaseKnitterMock(); + } + + ArduinoMock *arduinoMock; + SerialMock *serialMock; + SerialCommandMock *serialCommandMock; + KnitterMock *knitterMock; + + void expect_test() { + } +}; + +TEST_F(HardwareTestTest, test_setUp) { + EXPECT_CALL(*knitterMock, setQuitFlag); + ASSERT_FALSE(HardwareTest::m_autoReadOn); + ASSERT_FALSE(HardwareTest::m_autoTestOn); + ASSERT_FALSE(HardwareTest::m_timerEvent); + ASSERT_FALSE(HardwareTest::m_timerEventOdd); + HardwareTest::setUp(); +} + +TEST_F(HardwareTestTest, test_helpCmd) { + HardwareTest::helpCmd(); +} + +TEST_F(HardwareTestTest, test_sendCmd) { + EXPECT_CALL(*knitterMock, send); + HardwareTest::sendCmd(); +} + +TEST_F(HardwareTestTest, test_beepCmd) { + EXPECT_CALL(*arduinoMock, analogWrite).Times(AtLeast(1)); + EXPECT_CALL(*arduinoMock, delay).Times(AtLeast(1)); + HardwareTest::beepCmd(); +} + +TEST_F(HardwareTestTest, test_setSingleCmd_fail1) { + EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(nullptr)); + HardwareTest::setSingleCmd(); +} + +TEST_F(HardwareTestTest, test_setSingleCmd_fail2) { + EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(twenty)); + HardwareTest::setSingleCmd(); +} + +TEST_F(HardwareTestTest, test_setSingleCmd_fail3) { + EXPECT_CALL(*serialCommandMock, next) + .WillOnce(Return(zero)) + .WillOnce(Return(nullptr)); + HardwareTest::setSingleCmd(); +} + +TEST_F(HardwareTestTest, test_setSingleCmd_fail4) { + EXPECT_CALL(*serialCommandMock, next) + .WillOnce(Return(zero)) + .WillOnce(Return(two)); + HardwareTest::setSingleCmd(); +} + +TEST_F(HardwareTestTest, test_setSingleCmd_success) { + EXPECT_CALL(*serialCommandMock, next).WillRepeatedly(Return(zero)); + EXPECT_CALL(*knitterMock, setSolenoid); + HardwareTest::setSingleCmd(); +} + +TEST_F(HardwareTestTest, test_setAllCmd_fail1) { + EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(nullptr)); + HardwareTest::setAllCmd(); +} + +TEST_F(HardwareTestTest, test_setAllCmd_fail2) { + EXPECT_CALL(*serialCommandMock, next) + .WillOnce(Return(zero)) + .WillOnce(Return(nullptr)); + HardwareTest::setAllCmd(); +} + +TEST_F(HardwareTestTest, test_setAllCmd_success) { + EXPECT_CALL(*serialCommandMock, next).WillRepeatedly(Return(zero)); + EXPECT_CALL(*knitterMock, setSolenoids); + HardwareTest::setAllCmd(); +} + +TEST_F(HardwareTestTest, test_readEOLsensorsCmd) { + HardwareTest::readEOLsensorsCmd(); +} + +TEST_F(HardwareTestTest, test_readEncodersCmd) { + // low + EXPECT_CALL(*arduinoMock, digitalRead).WillRepeatedly(Return(0)); + HardwareTest::readEncodersCmd(); + + // high + EXPECT_CALL(*arduinoMock, digitalRead).WillRepeatedly(Return(1)); + HardwareTest::readEncodersCmd(); +} + +TEST_F(HardwareTestTest, test_autoReadCmd) { + HardwareTest::autoReadCmd(); +} + +TEST_F(HardwareTestTest, test_autoTestCmd) { + HardwareTest::autoTestCmd(); +} + +TEST_F(HardwareTestTest, test_stopCmd) { + HardwareTest::stopCmd(); +} + +TEST_F(HardwareTestTest, test_quitCmd) { + EXPECT_CALL(*knitterMock, setQuitFlag); + HardwareTest::quitCmd(); +} + +TEST_F(HardwareTestTest, test_unrecognizedCmd) { + const char buffer[1] = {1}; + HardwareTest::unrecognizedCmd(buffer); +} + +TEST_F(HardwareTestTest, test_loop_default) { + HardwareTest::m_lastTime = 0; + EXPECT_CALL(*arduinoMock, millis).WillOnce(Return(499)); + HardwareTest::loop(); +} + +TEST_F(HardwareTestTest, test_loop_autoRead) { + HardwareTest::m_lastTime = 0; + EXPECT_CALL(*arduinoMock, millis).WillOnce(Return(500)); + HardwareTest::m_timerEventOdd = true; + HardwareTest::m_autoReadOn = true; + EXPECT_CALL(*arduinoMock, analogRead).Times(2); + EXPECT_CALL(*arduinoMock, digitalRead).Times(3); + HardwareTest::loop(); +} + +TEST_F(HardwareTestTest, test_loop_autoTestEven) { + HardwareTest::m_lastTime = 0; + EXPECT_CALL(*arduinoMock, millis).WillOnce(Return(500)); + HardwareTest::m_timerEventOdd = false; + HardwareTest::m_autoTestOn = true; + EXPECT_CALL(*arduinoMock, digitalWrite).Times(2); + EXPECT_CALL(*knitterMock, setSolenoids); + HardwareTest::loop(); +} + +TEST_F(HardwareTestTest, test_loop_autoTestOdd) { + HardwareTest::m_lastTime = 0; + EXPECT_CALL(*arduinoMock, millis).WillOnce(Return(500)); + HardwareTest::m_timerEventOdd = true; + HardwareTest::m_autoTestOn = true; + EXPECT_CALL(*arduinoMock, digitalWrite).Times(2); + EXPECT_CALL(*knitterMock, setSolenoids); + HardwareTest::loop(); +} diff --git a/test/test_knitter.cpp b/test/test_knitter.cpp index d109992da..5e6dfae54 100644 --- a/test/test_knitter.cpp +++ b/test/test_knitter.cpp @@ -73,7 +73,7 @@ class KnitterTest : public ::testing::Test { EncodersMock *encodersMock; SerialEncodingMock *serialEncodingMock; HardwareTestMock *hwTestMock; - Knitter *k; + Knitter *&k = knitter; // `k` is alias of global `knitter` void expect_constructor() { EXPECT_CALL(*arduinoMock, pinMode(ENC_PIN_A, INPUT)); @@ -178,19 +178,19 @@ class KnitterTest : public ::testing::Test { expected_fsm(); } - void expected_test(bool first) { - if (first) { - // FIXME(TP): this is a kludge - - // currently there is no way to set the machineType in test mode - k->setMachineType(Kh910); - EXPECT_CALL(*encodersMock, init); - encodersMock->init(Kh910); - ASSERT_EQ(k->startTest(), true); - } + void expected_test() { expect_indState(); + EXPECT_CALL(*hwTestMock, loop); expected_fsm(); } + void expected_set_machine(Machine_t machineType) { + k->setMachineType(machineType); + EXPECT_CALL(*encodersMock, init); + encodersMock->init(machineType); + ASSERT_EQ(k->startTest(machineType), true); + } + void test_operate_line_request() { EXPECT_CALL(*solenoidsMock, setSolenoid); expected_operate(true); @@ -216,7 +216,7 @@ class KnitterTest : public ::testing::Test { TEST_F(KnitterTest, test_constructor) { ASSERT_EQ(k->getState(), s_init); // NOTE: Probing private data! - ASSERT_EQ(k->getStartNeedle(), 0); + ASSERT_EQ(k->m_startNeedle, 0); } /*! @@ -290,15 +290,18 @@ TEST_F(KnitterTest, test_fsm_ready) { */ TEST_F(KnitterTest, test_fsm_test) { // Enter test state - ASSERT_EQ(k->startTest(), true); + EXPECT_CALL(*hwTestMock, setUp); + ASSERT_EQ(k->startTest(Kh910), true); expected_isr(); expect_indState(); + EXPECT_CALL(*hwTestMock, loop); expected_fsm(); // Again with same position, no indState this time. expected_isr(); expect_indState<0>(); + EXPECT_CALL(*hwTestMock, loop); expected_fsm(); } @@ -367,7 +370,8 @@ TEST_F(KnitterTest, test_startOperation_failures) { * \test */ TEST_F(KnitterTest, test_startTest) { - ASSERT_EQ(k->startTest(), true); + EXPECT_CALL(*hwTestMock, setUp); + ASSERT_EQ(k->startTest(Kh910), true); ASSERT_EQ(k->getState(), s_test); } @@ -377,7 +381,7 @@ TEST_F(KnitterTest, test_startTest) { TEST_F(KnitterTest, test_startTest_in_operation) { get_to_operate(Kh910); // Can't start test - ASSERT_EQ(k->startTest(), false); + ASSERT_EQ(k->startTest(Kh910), false); ASSERT_EQ(k->getState(), s_operate); } @@ -571,14 +575,14 @@ TEST_F(KnitterTest, test_operate_lastline_and_no_req) { get_to_operate(Kh910); // Note probing lots of private data and methods to get full branch coverage. - k->setStopNeedle(100); - uint8_t wanted_pixel = k->getStopNeedle() + END_OF_LINE_OFFSET_R[Kh910] + 1; - k->setFirstRun(false); - k->setDirection(Left); - k->setPosition(wanted_pixel + k->getStartOffset(Right)); - k->setWorkedOnLine(true); - k->setLineRequested(false); - k->setLastLineFlag(true); + k->m_stopNeedle = 100; + uint8_t wanted_pixel = k->m_stopNeedle + END_OF_LINE_OFFSET_R[Kh910] + 1; + k->m_firstRun = false; + k->m_direction = Left; + k->m_position = wanted_pixel + k->getStartOffset(Right); + k->m_workedOnLine = true; + k->m_lineRequested = false; + k->m_lastLineFlag = true; EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 1)); EXPECT_CALL(*solenoidsMock, setSolenoid); @@ -588,7 +592,7 @@ TEST_F(KnitterTest, test_operate_lastline_and_no_req) { k->state_operate(); ASSERT_EQ(k->getStartOffset(NUM_DIRECTIONS), 0); - k->setCarriage(NUM_CARRIAGES); + k->m_carriage = NUM_CARRIAGES; ASSERT_EQ(k->getStartOffset(Right), 0); } @@ -631,54 +635,55 @@ TEST_F(KnitterTest, test_operate_new_line) { * \test */ TEST_F(KnitterTest, test_calculatePixelAndSolenoid) { + EXPECT_CALL(*hwTestMock, setUp); + expected_set_machine(Kh910); + // New Position, different beltshift and active hall expected_isr(100, Right, Right, Shifted, Lace); - expected_test(true); + expected_test(); // No direction, need to change position to enter test expected_isr(101, NoDirection, Right, Shifted, Lace); - expected_test(false); + expected_test(); // No belt, need to change position to enter test expected_isr(100, Right, Right, Unknown, Lace); - expected_test(false); + expected_test(); // No belt on left side, need to change position to enter test expected_isr(101, Left, Right, Unknown, Garter); - expected_test(false); + expected_test(); // Left Lace carriage expected_isr(100, Left, Right, Unknown, Lace); - expected_test(false); + expected_test(); // Regular belt on left, need to change position to enter test expected_isr(101, Left, Right, Regular, Garter); - expected_test(false); + expected_test(); // Shifted belt on left, need to change position to enter test expected_isr(100, Left, Right, Shifted, Garter); - expected_test(false); + expected_test(); // Off of right end, position is changed expected_isr(END_RIGHT[Kh910], Left, Right, Unknown, Lace); - expected_test(false); + expected_test(); // Direction right, have not reached offset expected_isr(39, Right, Left, Unknown, Lace); - expected_test(false); + expected_test(); // Kh270 k->setMachineType(Kh270); - EXPECT_CALL(*encodersMock, init); - encodersMock->init(Kh270); // K carriage direction left expected_isr(0, Left, Right, Regular, Knit); - expected_test(false); + expected_test(); // K carriage direction right expected_isr(END_RIGHT[Kh270], Right, Left, Regular, Knit); - expected_test(false); + expected_test(); } /*! @@ -686,17 +691,17 @@ TEST_F(KnitterTest, test_calculatePixelAndSolenoid) { */ TEST_F(KnitterTest, test_getStartOffset) { // out of range values - k->setCarriage(Knit); + k->m_carriage = Knit; ASSERT_EQ(k->getStartOffset(NoDirection), 0); ASSERT_EQ(k->getStartOffset(NUM_DIRECTIONS), 0); - k->setCarriage(NoCarriage); + k->m_carriage = NoCarriage; ASSERT_EQ(k->getStartOffset(Left), 0); - k->setCarriage(NUM_CARRIAGES); + k->m_carriage = NUM_CARRIAGES; ASSERT_EQ(k->getStartOffset(Right), 0); - k->setCarriage(Lace); - k->setMachineType(NoMachine); + k->m_carriage = Lace; + k->m_machineType = NoMachine; ASSERT_EQ(k->getStartOffset(Left), 0); - k->setMachineType(NUM_MACHINES); + k->m_machineType = NUM_MACHINES; ASSERT_EQ(k->getStartOffset(Right), 0); } @@ -707,3 +712,32 @@ TEST_F(KnitterTest, test_onPacketReceived) { EXPECT_CALL(*serialEncodingMock, onPacketReceived); k->onPacketReceived(nullptr, 0); } + +/*! + * \test + */ +TEST_F(KnitterTest, test_quit_hw_test) { + EXPECT_CALL(*hwTestMock, setUp); + ASSERT_EQ(k->startTest(Kh910), true); + ASSERT_EQ(k->getState(), s_test); + k->setQuitFlag(true); + EXPECT_CALL(*hwTestMock, loop); + k->state_test(); + ASSERT_EQ(k->getState(), s_ready); +} + +/*! + * \test + */ +TEST_F(KnitterTest, test_setSolenoids) { + EXPECT_CALL(*solenoidsMock, setSolenoids); + k->setSolenoids(0x1234); +} + +/*! + * \test + */ +TEST_F(KnitterTest, test_setSolenoid) { + EXPECT_CALL(*solenoidsMock, setSolenoid); + k->setSolenoid(0x12, 0x34); +} diff --git a/test/test_serial_encoding.cpp b/test/test_serial_encoding.cpp index 3f38fe326..f60bdc657 100644 --- a/test/test_serial_encoding.cpp +++ b/test/test_serial_encoding.cpp @@ -23,8 +23,8 @@ #include -#include #include +#include using ::testing::_; using ::testing::Return; @@ -54,19 +54,28 @@ TEST_F(SerialEncodingTest, test_API) { */ TEST_F(SerialEncodingTest, test_testmsg) { uint8_t buffer[] = {reqTest_msgid}; + EXPECT_CALL(*knitterMock, startTest).WillOnce(Return(false)); s->onPacketReceived(buffer, sizeof(buffer)); + + // no machineType + EXPECT_CALL(*knitterMock, startTest).Times(0); + s->onPacketReceived(buffer, sizeof(buffer) - 1); } TEST_F(SerialEncodingTest, test_startmsg) { uint8_t buffer[] = {reqStart_msgid, 0, 0, 10, 1, 0x74}; + EXPECT_CALL(*knitterMock, startOperation); s->onPacketReceived(buffer, sizeof(buffer)); // checksum wrong buffer[5] = 0x73; + EXPECT_CALL(*knitterMock, startOperation).Times(0); s->onPacketReceived(buffer, sizeof(buffer)); // kh270 buffer[1] = 2; + EXPECT_CALL(*knitterMock, startOperation); s->onPacketReceived(buffer, sizeof(buffer)); // Not enough bytes + EXPECT_CALL(*knitterMock, startOperation).Times(0); s->onPacketReceived(buffer, sizeof(buffer) - 1); } @@ -80,13 +89,36 @@ TEST_F(SerialEncodingTest, test_cnfmsg_kh910) { uint8_t pattern[] = {1}; // message for machine with 200 needles - uint8_t buffer[30] = {cnfLine_msgid /* 0x42 */, 0, 0, 1, - 0xde, 0xad, 0xbe, 0xef, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0xa7}; // CRC8 + uint8_t buffer[30] = {cnfLine_msgid /* 0x42 */, + 0, + 0, + 1, + 0xde, + 0xad, + 0xbe, + 0xef, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xa7}; // CRC8 // start KH910 job knitterMock->startOperation(Kh910, 0, 199, pattern, false); From 0b5fdb3132c948ef7b23f5cf9107d4ba83ac8370 Mon Sep 17 00:00:00 2001 From: Tom Date: Sat, 15 Aug 2020 18:14:55 -0400 Subject: [PATCH 04/17] Add HardwareTestInterface class, GlobalHardwareTest class. --- .travis.yml | 8 +- src/ayab/global_hw_test.cpp | 87 +++++++++++++++++++ src/ayab/hw_test.cpp | 162 ++++++++++++++++++------------------ src/ayab/hw_test.h | 89 +++++++++++++++----- src/ayab/knitter.cpp | 5 +- src/ayab/main.cpp | 7 +- test/CMakeLists.txt | 9 +- test/mocks/hw_test_mock.cpp | 3 +- test/mocks/hw_test_mock.h | 4 +- test/test_hw_test.cpp | 90 ++++++++++---------- test/test_knitter.cpp | 19 ++++- 11 files changed, 321 insertions(+), 162 deletions(-) create mode 100644 src/ayab/global_hw_test.cpp diff --git a/.travis.yml b/.travis.yml index 099073b46..967b5d14d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,13 @@ matrix: include: - language: cpp - dist: bionic + dist: focal addons: apt: sources: - ubuntu-toolchain-r-test - packages: - - g++ env: - - MATRIX_EVAL="CC=gcc-7 && CXX=g++-7" + - MATRIX_EVAL="CC=gcc-9 && CXX=g++-9" before_install: - eval "${MATRIX_EVAL}" - pip install gcovr @@ -17,7 +15,7 @@ matrix: script: - "./test/test.sh" - language: c - dist: bionic + dist: focal addons: apt: packages: diff --git a/src/ayab/global_hw_test.cpp b/src/ayab/global_hw_test.cpp new file mode 100644 index 000000000..dfdc6be90 --- /dev/null +++ b/src/ayab/global_hw_test.cpp @@ -0,0 +1,87 @@ +/*! + * \file global_hw_test.cpp + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#include "hw_test.h" + +// static member functions + +void GlobalHardwareTest::helpCmd() { + m_instance->helpCmd(); +} + +void GlobalHardwareTest::sendCmd() { + m_instance->sendCmd(); +} + +void GlobalHardwareTest::beepCmd() { + m_instance->beepCmd(); +} + +void GlobalHardwareTest::setSingleCmd() { + m_instance->setSingleCmd(); +} + +void GlobalHardwareTest::setAllCmd() { + m_instance->setAllCmd(); +} + +void GlobalHardwareTest::readEOLsensorsCmd() { + m_instance->readEOLsensorsCmd(); +} + +void GlobalHardwareTest::readEncodersCmd() { + m_instance->readEncodersCmd(); +} + +void GlobalHardwareTest::autoReadCmd() { + m_instance->autoReadCmd(); +} + +void GlobalHardwareTest::autoTestCmd() { + m_instance->autoTestCmd(); +} + +void GlobalHardwareTest::stopCmd() { + m_instance->stopCmd(); +} + +void GlobalHardwareTest::quitCmd() { + m_instance->quitCmd(); +} + +void GlobalHardwareTest::unrecognizedCmd(const char *buffer) { + m_instance->unrecognizedCmd(buffer); +} + +void GlobalHardwareTest::setUp() { + m_instance->setUp(); +} + +void GlobalHardwareTest::loop() { + m_instance->loop(); +} + +#ifndef AYAB_TESTS +void GlobalHardwareTest::encoderAChange() { + m_instance->encoderAChange(); +} +#endif // AYAB_TESTS diff --git a/src/ayab/hw_test.cpp b/src/ayab/hw_test.cpp index 6d1738883..6aab23995 100644 --- a/src/ayab/hw_test.cpp +++ b/src/ayab/hw_test.cpp @@ -25,16 +25,11 @@ #include "hw_test.h" #include "knitter.h" -extern Knitter *knitter; - -// initialise static members -SerialCommand HardwareTest::m_sCmd = SerialCommand(); -bool HardwareTest::m_autoReadOn = false; -bool HardwareTest::m_autoTestOn = false; -bool HardwareTest::m_timerEvent = false; -bool HardwareTest::m_timerEventOdd = false; -unsigned long HardwareTest::m_lastTime = 0U; +// public interface +/*! + * \brief Help command handler. + */ void HardwareTest::helpCmd() { Serial.println("The following commands are available:"); Serial.println("setSingle [0..15] [1/0]"); @@ -50,6 +45,9 @@ void HardwareTest::helpCmd() { Serial.println("help"); } +/*! + * \brief Send command handler. + */ void HardwareTest::sendCmd() { Serial.println("Called send"); uint8_t p[] = {1, 2, 3}; @@ -57,10 +55,6 @@ void HardwareTest::sendCmd() { Serial.print("\n"); } -void HardwareTest::beep() { - knitter->m_beeper.ready(); -} - /*! * \brief Beep command handler. */ @@ -117,13 +111,6 @@ void HardwareTest::setAllCmd() { knitter->setSolenoids(solenoidState); } -void HardwareTest::readEOLsensors() { - Serial.print(" EOL_L: "); - Serial.print(analogRead(EOL_PIN_L)); - Serial.print(" EOL_R: "); - Serial.print(analogRead(EOL_PIN_R)); -} - /*! * \brief Read EOL sensors command handler. */ @@ -133,18 +120,6 @@ void HardwareTest::readEOLsensorsCmd() { Serial.print("\n"); } -void HardwareTest::readEncoders() { - Serial.print(" ENC_A: "); - bool state = digitalRead(ENC_PIN_A); - Serial.print(state ? "HIGH" : "LOW"); - Serial.print(" ENC_B: "); - state = digitalRead(ENC_PIN_B); - Serial.print(state ? "HIGH" : "LOW"); - Serial.print(" ENC_C: "); - state = digitalRead(ENC_PIN_C); - Serial.print(state ? "HIGH" : "LOW"); -} - /*! * \brief Read encoders command handler. */ @@ -154,13 +129,6 @@ void HardwareTest::readEncodersCmd() { Serial.print("\n"); } -void HardwareTest::autoRead() { - Serial.print("\n"); - readEOLsensors(); - readEncoders(); - Serial.print("\n"); -} - /*! * \brief Auto read command handler. */ @@ -169,20 +137,6 @@ void HardwareTest::autoReadCmd() { m_autoReadOn = true; } -void HardwareTest::autoTestEven() { - Serial.println("Set even solenoids"); - digitalWrite(LED_PIN_A, 1); - digitalWrite(LED_PIN_B, 1); - knitter->setSolenoids(0xAAAA); -} - -void HardwareTest::autoTestOdd() { - Serial.println("Set odd solenoids"); - digitalWrite(LED_PIN_A, 0); - digitalWrite(LED_PIN_B, 0); - knitter->setSolenoids(0x5555); -} - /*! * \brief Auto test command handler. */ @@ -191,11 +145,17 @@ void HardwareTest::autoTestCmd() { m_autoTestOn = true; } +/*! + * \brief Stop command handler. + */ void HardwareTest::stopCmd() { m_autoReadOn = false; m_autoTestOn = false; } +/*! + * \brief Quit command handler. + */ void HardwareTest::quitCmd() { knitter->setQuitFlag(true); knitter->setUpInterrupt(); @@ -215,25 +175,23 @@ void HardwareTest::unrecognizedCmd(const char *buffer) { helpCmd(); } -// Member functions - /*! - * \brief Setup for hw tests. + * \brief Setup for hardware tests. */ void HardwareTest::setUp() { // set up callbacks for SerialCommand commands - m_sCmd.addCommand("setSingle", HardwareTest::setSingleCmd); - m_sCmd.addCommand("setAll", HardwareTest::setAllCmd); - m_sCmd.addCommand("readEOLsensors", HardwareTest::readEOLsensorsCmd); - m_sCmd.addCommand("readEncoders", HardwareTest::readEncodersCmd); - m_sCmd.addCommand("beep", HardwareTest::beepCmd); - m_sCmd.addCommand("autoRead", HardwareTest::autoReadCmd); - m_sCmd.addCommand("autoTest", HardwareTest::autoTestCmd); - m_sCmd.addCommand("send", HardwareTest::sendCmd); - m_sCmd.addCommand("stop", HardwareTest::stopCmd); - m_sCmd.addCommand("quit", HardwareTest::quitCmd); - m_sCmd.addCommand("help", HardwareTest::helpCmd); - m_sCmd.setDefaultHandler(HardwareTest::unrecognizedCmd); + m_sCmd.addCommand("setSingle", GlobalHardwareTest::setSingleCmd); + m_sCmd.addCommand("setAll", GlobalHardwareTest::setAllCmd); + m_sCmd.addCommand("readEOLsensors", GlobalHardwareTest::readEOLsensorsCmd); + m_sCmd.addCommand("readEncoders", GlobalHardwareTest::readEncodersCmd); + m_sCmd.addCommand("beep", GlobalHardwareTest::beepCmd); + m_sCmd.addCommand("autoRead", GlobalHardwareTest::autoReadCmd); + m_sCmd.addCommand("autoTest", GlobalHardwareTest::autoTestCmd); + m_sCmd.addCommand("send", GlobalHardwareTest::sendCmd); + m_sCmd.addCommand("stop", GlobalHardwareTest::stopCmd); + m_sCmd.addCommand("quit", GlobalHardwareTest::quitCmd); + m_sCmd.addCommand("help", GlobalHardwareTest::helpCmd); + m_sCmd.setDefaultHandler(GlobalHardwareTest::unrecognizedCmd); // Print welcome message Serial.print("AYAB Hardware Test, Firmware v"); @@ -248,25 +206,16 @@ void HardwareTest::setUp() { // attach interrupt for ENC_PIN_A(=2), interrupt #0 detachInterrupt(0); #ifndef AYAB_TESTS - attachInterrupt(0, encoderAChange, RISING); + attachInterrupt(0, GlobalHardwareTest::encoderAChange, RISING); +#endif // AYAB_TESTS m_lastTime = millis(); -#endif // AYAB_TESTS m_autoReadOn = false; m_autoTestOn = false; m_timerEventOdd = false; knitter->setQuitFlag(false); } -#ifndef AYAB_TESTS -/*! - * \brief Interrupt service routine for encoder A. - */ -void HardwareTest::encoderAChange() { - beep(); -} -#endif // AYAB_TESTS - /*! * \brief Main loop for hardware tests. */ @@ -278,6 +227,61 @@ void HardwareTest::loop() { } } +// Private member functions + +void HardwareTest::beep() { + knitter->m_beeper.ready(); +} + +void HardwareTest::readEncoders() { + Serial.print(" ENC_A: "); + bool state = digitalRead(ENC_PIN_A); + Serial.print(state ? "HIGH" : "LOW"); + Serial.print(" ENC_B: "); + state = digitalRead(ENC_PIN_B); + Serial.print(state ? "HIGH" : "LOW"); + Serial.print(" ENC_C: "); + state = digitalRead(ENC_PIN_C); + Serial.print(state ? "HIGH" : "LOW"); +} + +void HardwareTest::readEOLsensors() { + Serial.print(" EOL_L: "); + Serial.print(analogRead(EOL_PIN_L)); + Serial.print(" EOL_R: "); + Serial.print(analogRead(EOL_PIN_R)); +} + +void HardwareTest::autoRead() { + Serial.print("\n"); + readEOLsensors(); + readEncoders(); + Serial.print("\n"); +} + +void HardwareTest::autoTestEven() { + Serial.println("Set even solenoids"); + digitalWrite(LED_PIN_A, 1); + digitalWrite(LED_PIN_B, 1); + knitter->setSolenoids(0xAAAA); +} + +void HardwareTest::autoTestOdd() { + Serial.println("Set odd solenoids"); + digitalWrite(LED_PIN_A, 0); + digitalWrite(LED_PIN_B, 0); + knitter->setSolenoids(0x5555); +} + +/*! + * \brief Interrupt service routine for encoder A. + */ +#ifndef AYAB_TESTS +void HardwareTest::encoderAChange() { + beep(); +} +#endif // AYAB_TESTS + /*! * \brief Timer event every 500ms to handle auto functions. */ diff --git a/src/ayab/hw_test.h b/src/ayab/hw_test.h index 9183c741a..36eab1bef 100644 --- a/src/ayab/hw_test.h +++ b/src/ayab/hw_test.h @@ -28,15 +28,79 @@ #include "beeper.h" -class HardwareTest { +class HardwareTestInterface { +public: + virtual ~HardwareTestInterface(){}; + + virtual void helpCmd() = 0; + virtual void sendCmd() = 0; + virtual void beepCmd() = 0; + virtual void setSingleCmd() = 0; + virtual void setAllCmd() = 0; + virtual void readEOLsensorsCmd() = 0; + virtual void readEncodersCmd() = 0; + virtual void autoReadCmd() = 0; + virtual void autoTestCmd() = 0; + virtual void stopCmd() = 0; + virtual void quitCmd() = 0; + virtual void unrecognizedCmd(const char *buffer) = 0; + + virtual void setUp() = 0; + virtual void loop() = 0; +#ifndef AYAB_TESTS + virtual void encoderAChange() = 0; +#endif +}; + +class HardwareTest : public HardwareTestInterface { #if AYAB_TESTS FRIEND_TEST(HardwareTestTest, test_setUp); FRIEND_TEST(HardwareTestTest, test_loop_default); - FRIEND_TEST(HardwareTestTest, test_loop_autoRead); + FRIEND_TEST(HardwareTestTest, test_loop_null); FRIEND_TEST(HardwareTestTest, test_loop_autoTestEven); FRIEND_TEST(HardwareTestTest, test_loop_autoTestOdd); friend class HardwareTestTest; #endif + +public: + void helpCmd(); + void sendCmd(); + void beepCmd(); + void setSingleCmd(); + void setAllCmd(); + void readEOLsensorsCmd(); + void readEncodersCmd(); + void autoReadCmd(); + void autoTestCmd(); + void stopCmd(); + void quitCmd(); + void unrecognizedCmd(const char *buffer); + + void setUp(); + void loop(); +#ifndef AYAB_TESTS + void encoderAChange(); +#endif + +private: + void beep(); + void readEOLsensors(); + void readEncoders(); + void autoRead(); + void autoTestEven(); + void autoTestOdd(); + void handleTimerEvent(); + + SerialCommand m_sCmd = SerialCommand(); + + bool m_autoReadOn = false; + bool m_autoTestOn = false; + + unsigned long m_lastTime = 0U; + bool m_timerEventOdd = false; +}; + +class GlobalHardwareTest { public: static void helpCmd(); static void sendCmd(); @@ -53,30 +117,13 @@ class HardwareTest { static void setUp(); static void loop(); - - static SerialCommand m_sCmd; - /* static bool m_quitFlag; */ - -private: - static void beep(); - static void readEOLsensors(); - static void readEncoders(); - static void autoRead(); - static void autoTestEven(); - static void autoTestOdd(); - static void handleTimerEvent(); - #ifndef AYAB_TESTS static void encoderAChange(); #endif - static bool m_autoReadOn; - static bool m_autoTestOn; - - static unsigned long m_lastTime; - static bool m_timerEventOdd; + static HardwareTestInterface *m_instance; }; -extern HardwareTest *hwTest; +extern GlobalHardwareTest *hwTest; #endif // HW_TEST_H_ diff --git a/src/ayab/knitter.cpp b/src/ayab/knitter.cpp index fa30fa2e8..35eec65a5 100644 --- a/src/ayab/knitter.cpp +++ b/src/ayab/knitter.cpp @@ -35,7 +35,6 @@ constexpr uint16_t UINT16_MAX = 0xFFFFU; #endif extern Knitter *knitter; -extern HardwareTest *hwTest; #ifndef AYAB_TESTS /*! @@ -185,7 +184,7 @@ bool Knitter::startTest(Machine_t machineType) { if (s_init == m_opState || s_ready == m_opState) { m_opState = s_test; m_machineType = machineType; - HardwareTest::setUp(); + GlobalHardwareTest::setUp(); success = true; } return success; @@ -344,7 +343,7 @@ void Knitter::state_test() { calculatePixelAndSolenoid(); indState(); } - HardwareTest::loop(); + GlobalHardwareTest::loop(); if (m_quitFlag) { m_opState = s_ready; } diff --git a/src/ayab/main.cpp b/src/ayab/main.cpp index ff0125be3..3fb1cccfb 100644 --- a/src/ayab/main.cpp +++ b/src/ayab/main.cpp @@ -27,17 +27,18 @@ #include "hw_test.h" #include "knitter.h" -// global definitions +// global definition // references everywhere else must use `extern` Knitter *knitter; -HardwareTest *hwTest; + +// initialize static member +HardwareTestInterface *GlobalHardwareTest::m_instance = new HardwareTest(); /*! * Setup - steps to take before going to the main loop. */ void setup() { knitter = new Knitter(); - hwTest = new HardwareTest(); } /*! diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2ac2ce66e..0ac32270b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -53,6 +53,7 @@ set(COMMON_SOURCES ${PROJECT_SOURCE_DIR}/test_serial_encoding.cpp ${SOURCE_DIRECTORY}/hw_test.cpp + ${SOURCE_DIRECTORY}/global_hw_test.cpp ${PROJECT_SOURCE_DIR}/test_hw_test.cpp ${PROJECT_SOURCE_DIR}/mocks/SerialCommand_mock.cpp @@ -68,7 +69,12 @@ set(COMMON_FLAGS -Wall -Wextra -Wpedantic -Wno-vla -Werror - -fprofile-arcs -ftest-coverage + -fprofile-arcs -ftest-coverage + + # exclude global_hw_test.cpp from profile analysis + # to avoid tedious mocking of these simple functions + -fprofile-exclude-files=global_hw_test.cpp + -g -Og ) set(COMMON_LINKER_FLAGS @@ -133,6 +139,7 @@ add_board(Mega) add_executable(${PROJECT_NAME}_knitter ${PROJECT_SOURCE_DIR}/test_all.cpp ${SOURCE_DIRECTORY}/knitter.cpp + ${SOURCE_DIRECTORY}/global_hw_test.cpp ${PROJECT_SOURCE_DIR}/mocks/solenoids_mock.cpp ${PROJECT_SOURCE_DIR}/mocks/encoders_mock.cpp ${PROJECT_SOURCE_DIR}/mocks/beeper_mock.cpp diff --git a/test/mocks/hw_test_mock.cpp b/test/mocks/hw_test_mock.cpp index e25cfb5a6..c82c498ab 100644 --- a/test/mocks/hw_test_mock.cpp +++ b/test/mocks/hw_test_mock.cpp @@ -24,7 +24,8 @@ #include static HardwareTestMock *gHardwareTestMock = NULL; -HardwareTestMock *hardwareTestMockInstance() { + +HardwareTestMock *hwTestMockInstance() { if (!gHardwareTestMock) { gHardwareTestMock = new HardwareTestMock(); } diff --git a/test/mocks/hw_test_mock.h b/test/mocks/hw_test_mock.h index f317c991d..d8e60f372 100644 --- a/test/mocks/hw_test_mock.h +++ b/test/mocks/hw_test_mock.h @@ -27,7 +27,7 @@ #include #include -class HardwareTestMock { +class HardwareTestMock : public HardwareTestInterface { public: MOCK_METHOD0(helpCmd, void()); MOCK_METHOD0(sendCmd, void()); @@ -45,7 +45,7 @@ class HardwareTestMock { MOCK_METHOD0(loop, void()); }; -HardwareTestMock *hardwareTestMockInstance(); +HardwareTestMock *hwTestMockInstance(); void releaseHardwareTestMock(); #endif // HW_TEST_MOCK_H_ diff --git a/test/test_hw_test.cpp b/test/test_hw_test.cpp index 026c2800a..d1f011169 100644 --- a/test/test_hw_test.cpp +++ b/test/test_hw_test.cpp @@ -34,6 +34,9 @@ static char zero[2] = {48, 0}; // "0" static char two[2] = {50, 0}; // "2" static char twenty[3] = {50, 48, 0}; // "20" +// initialize static member +HardwareTestInterface *GlobalHardwareTest::m_instance = new HardwareTest(); + class HardwareTestTest : public ::testing::Test { protected: void SetUp() override { @@ -41,6 +44,7 @@ class HardwareTestTest : public ::testing::Test { serialMock = serialMockInstance(); serialCommandMock = serialCommandMockInstance(); knitterMock = knitterMockInstance(); + h = new HardwareTest(); } void TearDown() override { @@ -54,151 +58,149 @@ class HardwareTestTest : public ::testing::Test { SerialMock *serialMock; SerialCommandMock *serialCommandMock; KnitterMock *knitterMock; - - void expect_test() { - } + HardwareTest *h; }; TEST_F(HardwareTestTest, test_setUp) { EXPECT_CALL(*knitterMock, setQuitFlag); - ASSERT_FALSE(HardwareTest::m_autoReadOn); - ASSERT_FALSE(HardwareTest::m_autoTestOn); - ASSERT_FALSE(HardwareTest::m_timerEvent); - ASSERT_FALSE(HardwareTest::m_timerEventOdd); - HardwareTest::setUp(); + EXPECT_CALL(*arduinoMock, millis); + h->setUp(); + ASSERT_FALSE(h->m_autoReadOn); + ASSERT_FALSE(h->m_autoTestOn); + ASSERT_FALSE(h->m_timerEventOdd); } TEST_F(HardwareTestTest, test_helpCmd) { - HardwareTest::helpCmd(); + h->helpCmd(); } TEST_F(HardwareTestTest, test_sendCmd) { EXPECT_CALL(*knitterMock, send); - HardwareTest::sendCmd(); + h->sendCmd(); } TEST_F(HardwareTestTest, test_beepCmd) { EXPECT_CALL(*arduinoMock, analogWrite).Times(AtLeast(1)); EXPECT_CALL(*arduinoMock, delay).Times(AtLeast(1)); - HardwareTest::beepCmd(); + h->beepCmd(); } TEST_F(HardwareTestTest, test_setSingleCmd_fail1) { EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(nullptr)); - HardwareTest::setSingleCmd(); + h->setSingleCmd(); } TEST_F(HardwareTestTest, test_setSingleCmd_fail2) { EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(twenty)); - HardwareTest::setSingleCmd(); + h->setSingleCmd(); } TEST_F(HardwareTestTest, test_setSingleCmd_fail3) { EXPECT_CALL(*serialCommandMock, next) .WillOnce(Return(zero)) .WillOnce(Return(nullptr)); - HardwareTest::setSingleCmd(); + h->setSingleCmd(); } TEST_F(HardwareTestTest, test_setSingleCmd_fail4) { EXPECT_CALL(*serialCommandMock, next) .WillOnce(Return(zero)) .WillOnce(Return(two)); - HardwareTest::setSingleCmd(); + h->setSingleCmd(); } TEST_F(HardwareTestTest, test_setSingleCmd_success) { EXPECT_CALL(*serialCommandMock, next).WillRepeatedly(Return(zero)); EXPECT_CALL(*knitterMock, setSolenoid); - HardwareTest::setSingleCmd(); + h->setSingleCmd(); } TEST_F(HardwareTestTest, test_setAllCmd_fail1) { EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(nullptr)); - HardwareTest::setAllCmd(); + h->setAllCmd(); } TEST_F(HardwareTestTest, test_setAllCmd_fail2) { EXPECT_CALL(*serialCommandMock, next) .WillOnce(Return(zero)) .WillOnce(Return(nullptr)); - HardwareTest::setAllCmd(); + h->setAllCmd(); } TEST_F(HardwareTestTest, test_setAllCmd_success) { EXPECT_CALL(*serialCommandMock, next).WillRepeatedly(Return(zero)); EXPECT_CALL(*knitterMock, setSolenoids); - HardwareTest::setAllCmd(); + h->setAllCmd(); } TEST_F(HardwareTestTest, test_readEOLsensorsCmd) { - HardwareTest::readEOLsensorsCmd(); + h->readEOLsensorsCmd(); } TEST_F(HardwareTestTest, test_readEncodersCmd) { // low EXPECT_CALL(*arduinoMock, digitalRead).WillRepeatedly(Return(0)); - HardwareTest::readEncodersCmd(); + h->readEncodersCmd(); // high EXPECT_CALL(*arduinoMock, digitalRead).WillRepeatedly(Return(1)); - HardwareTest::readEncodersCmd(); + h->readEncodersCmd(); } TEST_F(HardwareTestTest, test_autoReadCmd) { - HardwareTest::autoReadCmd(); + h->autoReadCmd(); } TEST_F(HardwareTestTest, test_autoTestCmd) { - HardwareTest::autoTestCmd(); + h->autoTestCmd(); } TEST_F(HardwareTestTest, test_stopCmd) { - HardwareTest::stopCmd(); + h->stopCmd(); } TEST_F(HardwareTestTest, test_quitCmd) { EXPECT_CALL(*knitterMock, setQuitFlag); - HardwareTest::quitCmd(); + h->quitCmd(); } TEST_F(HardwareTestTest, test_unrecognizedCmd) { const char buffer[1] = {1}; - HardwareTest::unrecognizedCmd(buffer); + h->unrecognizedCmd(buffer); } TEST_F(HardwareTestTest, test_loop_default) { - HardwareTest::m_lastTime = 0; + h->m_lastTime = 0; EXPECT_CALL(*arduinoMock, millis).WillOnce(Return(499)); - HardwareTest::loop(); + h->loop(); } -TEST_F(HardwareTestTest, test_loop_autoRead) { - HardwareTest::m_lastTime = 0; +TEST_F(HardwareTestTest, test_loop_null) { + h->m_lastTime = 0; EXPECT_CALL(*arduinoMock, millis).WillOnce(Return(500)); - HardwareTest::m_timerEventOdd = true; - HardwareTest::m_autoReadOn = true; - EXPECT_CALL(*arduinoMock, analogRead).Times(2); - EXPECT_CALL(*arduinoMock, digitalRead).Times(3); - HardwareTest::loop(); + h->m_autoReadOn = false; + h->m_autoTestOn = false; + h->loop(); } TEST_F(HardwareTestTest, test_loop_autoTestEven) { - HardwareTest::m_lastTime = 0; + h->m_lastTime = 0; EXPECT_CALL(*arduinoMock, millis).WillOnce(Return(500)); - HardwareTest::m_timerEventOdd = false; - HardwareTest::m_autoTestOn = true; + h->m_timerEventOdd = false; + h->m_autoReadOn = true; + h->m_autoTestOn = true; EXPECT_CALL(*arduinoMock, digitalWrite).Times(2); EXPECT_CALL(*knitterMock, setSolenoids); - HardwareTest::loop(); + h->loop(); } TEST_F(HardwareTestTest, test_loop_autoTestOdd) { - HardwareTest::m_lastTime = 0; + h->m_lastTime = 0; EXPECT_CALL(*arduinoMock, millis).WillOnce(Return(500)); - HardwareTest::m_timerEventOdd = true; - HardwareTest::m_autoTestOn = true; + h->m_timerEventOdd = true; + h->m_autoReadOn = true; + h->m_autoTestOn = true; EXPECT_CALL(*arduinoMock, digitalWrite).Times(2); EXPECT_CALL(*knitterMock, setSolenoids); - HardwareTest::loop(); + h->loop(); } diff --git a/test/test_knitter.cpp b/test/test_knitter.cpp index 5e6dfae54..644fad2b1 100644 --- a/test/test_knitter.cpp +++ b/test/test_knitter.cpp @@ -33,12 +33,15 @@ using ::testing::_; using ::testing::AtLeast; +using ::testing::Mock; using ::testing::Return; // global definitions // references everywhere else must use `extern` Knitter *knitter; -HardwareTest *hwTest; + +// initialize static member +HardwareTestInterface *GlobalHardwareTest::m_instance = new HardwareTestMock(); void onPacketReceived(const uint8_t *buffer, size_t size) { (void)buffer; @@ -53,7 +56,9 @@ class KnitterTest : public ::testing::Test { solenoidsMock = solenoidsMockInstance(); encodersMock = encodersMockInstance(); serialEncodingMock = serialEncodingMockInstance(); - hwTestMock = hardwareTestMockInstance(); + hwTestMock = + static_cast(GlobalHardwareTest::m_instance); + Mock::AllowLeak(hwTestMock); // because it does not get destructed expect_constructor(); k = new Knitter(); } @@ -73,7 +78,8 @@ class KnitterTest : public ::testing::Test { EncodersMock *encodersMock; SerialEncodingMock *serialEncodingMock; HardwareTestMock *hwTestMock; - Knitter *&k = knitter; // `k` is alias of global `knitter` + HardwareTestMock *dummy; + Knitter *&k = knitter; // alias of global `knitter` void expect_constructor() { EXPECT_CALL(*arduinoMock, pinMode(ENC_PIN_A, INPUT)); @@ -182,6 +188,7 @@ class KnitterTest : public ::testing::Test { expect_indState(); EXPECT_CALL(*hwTestMock, loop); expected_fsm(); + ASSERT_TRUE(Mock::VerifyAndClear(hwTestMock)); } void expected_set_machine(Machine_t machineType) { @@ -303,6 +310,8 @@ TEST_F(KnitterTest, test_fsm_test) { expect_indState<0>(); EXPECT_CALL(*hwTestMock, loop); expected_fsm(); + + ASSERT_TRUE(Mock::VerifyAndClear(hwTestMock)); } /*! @@ -373,6 +382,7 @@ TEST_F(KnitterTest, test_startTest) { EXPECT_CALL(*hwTestMock, setUp); ASSERT_EQ(k->startTest(Kh910), true); ASSERT_EQ(k->getState(), s_test); + ASSERT_TRUE(Mock::VerifyAndClear(hwTestMock)); } /*! @@ -684,6 +694,8 @@ TEST_F(KnitterTest, test_calculatePixelAndSolenoid) { // K carriage direction right expected_isr(END_RIGHT[Kh270], Right, Left, Regular, Knit); expected_test(); + + ASSERT_TRUE(Mock::VerifyAndClear(hwTestMock)); } /*! @@ -724,6 +736,7 @@ TEST_F(KnitterTest, test_quit_hw_test) { EXPECT_CALL(*hwTestMock, loop); k->state_test(); ASSERT_EQ(k->getState(), s_ready); + ASSERT_TRUE(Mock::VerifyAndClear(hwTestMock)); } /*! From 9203f8b36049e5744b5e607efcb5fd8c41145513 Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 18 Aug 2020 01:32:18 -0400 Subject: [PATCH 05/17] Fix up serial communication for hardware tests. --- README.md | 6 +- src/ayab/global_hw_test.cpp | 2 +- src/ayab/global_hw_test.h | 61 ++++++++ src/ayab/hw_test.cpp | 210 ++++++++++++++++------------ src/ayab/hw_test.h | 32 +---- src/ayab/knitter.cpp | 10 +- src/ayab/knitter.h | 2 + src/ayab/main.cpp | 5 +- src/ayab/serial_encoding.cpp | 16 ++- src/ayab/serial_encoding.h | 8 +- test/CMakeLists.txt | 6 +- test/mocks/knitter_mock.cpp | 10 ++ test/mocks/knitter_mock.h | 2 + test/mocks/serial_encoding_mock.cpp | 12 +- test/mocks/serial_encoding_mock.h | 4 +- test/test.sh | 7 +- test/test_hw_test.cpp | 94 +++++++++++-- test/test_knitter.cpp | 188 ++++++++++++++++--------- test/test_serial_encoding.cpp | 13 ++ 19 files changed, 473 insertions(+), 215 deletions(-) create mode 100644 src/ayab/global_hw_test.h diff --git a/README.md b/README.md index 815465bab..b4764ce34 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,13 @@ To set up a working development environment follow these steps: Running `./build.sh` should work now. - 2. Install `clang-format` and `gcovr`. + 2. Install `clang-format`, `gcovr`, and update `gcc` to version 9. Ubuntu: ```bash - sudo apt install clang-format gcovr + sudo apt install -y clang-format gcovr \ + gcc-9 g++-9 cpp-9 gcc-9-base gcc-10-base \ + libgcc-9-dev libstdc++-9-dev ``` 3. Install [pre-commit](https://pre-commit.com/) via pip and use it to install git hooks. diff --git a/src/ayab/global_hw_test.cpp b/src/ayab/global_hw_test.cpp index dfdc6be90..c56587ec5 100644 --- a/src/ayab/global_hw_test.cpp +++ b/src/ayab/global_hw_test.cpp @@ -20,7 +20,7 @@ * http://ayab-knitting.com */ -#include "hw_test.h" +#include "global_hw_test.h" // static member functions diff --git a/src/ayab/global_hw_test.h b/src/ayab/global_hw_test.h new file mode 100644 index 000000000..6d45db864 --- /dev/null +++ b/src/ayab/global_hw_test.h @@ -0,0 +1,61 @@ +/*! + * \file global_hw_test.h + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#ifndef GLOBAL_HW_TEST_H_ +#define GLOBAL_HW_TEST_H_ + +#include "hw_test.h" + +// This is a container for the static methods called back by SerialCommand. +// Dependency injection is enabled using a pointer to a global instance of +// either HardwareTest or HardwareTestMock, both of which classes implement +// the pure virtual methods of HardwareTestInterface. + +class GlobalHardwareTest { +public: + static void helpCmd(); + static void sendCmd(); + static void beepCmd(); + static void setSingleCmd(); + static void setAllCmd(); + static void readEOLsensorsCmd(); + static void readEncodersCmd(); + static void autoReadCmd(); + static void autoTestCmd(); + static void stopCmd(); + static void quitCmd(); + static void unrecognizedCmd(const char *buffer); + + static void setUp(); + static void loop(); +#ifndef AYAB_TESTS + static void encoderAChange(); +#endif + + // pointer to global instance whose methods are implemented + // in the static methods belonging to this class + static HardwareTestInterface *m_instance; +}; + +extern GlobalHardwareTest *hwTest; + +#endif // GLOBAL_HW_TEST_H_ diff --git a/src/ayab/hw_test.cpp b/src/ayab/hw_test.cpp index 6aab23995..16fd3c859 100644 --- a/src/ayab/hw_test.cpp +++ b/src/ayab/hw_test.cpp @@ -22,7 +22,7 @@ #include -#include "hw_test.h" +#include "global_hw_test.h" #include "knitter.h" // public interface @@ -31,35 +31,35 @@ * \brief Help command handler. */ void HardwareTest::helpCmd() { - Serial.println("The following commands are available:"); - Serial.println("setSingle [0..15] [1/0]"); - Serial.println("setAll [0..255] [0..255]"); - Serial.println("readEOLsensors"); - Serial.println("readEncoders"); - Serial.println("beep"); - Serial.println("autoRead"); - Serial.println("autoTest"); - Serial.println("send"); - Serial.println("stop"); - Serial.println("quit"); - Serial.println("help"); + knitter->sendMsg(test_msgid, "The following commands are available:\n"); + knitter->sendMsg(test_msgid, "setSingle [0..15] [1/0]\n"); + knitter->sendMsg(test_msgid, "setAll [0..FFFF]\n"); + knitter->sendMsg(test_msgid, "readEOLsensors\n"); + knitter->sendMsg(test_msgid, "readEncoders\n"); + knitter->sendMsg(test_msgid, "beep\n"); + knitter->sendMsg(test_msgid, "autoRead\n"); + knitter->sendMsg(test_msgid, "autoTest\n"); + knitter->sendMsg(test_msgid, "send\n"); + knitter->sendMsg(test_msgid, "stop\n"); + knitter->sendMsg(test_msgid, "quit\n"); + knitter->sendMsg(test_msgid, "help\n"); } /*! * \brief Send command handler. */ void HardwareTest::sendCmd() { - Serial.println("Called send"); + knitter->sendMsg(test_msgid, "Called send\n"); uint8_t p[] = {1, 2, 3}; knitter->send(p, 3); - Serial.print("\n"); + knitter->sendMsg(test_msgid, "\n"); } /*! * \brief Beep command handler. */ void HardwareTest::beepCmd() { - Serial.println("Called beep"); + knitter->sendMsg(test_msgid, "Called beep\n"); beep(); } @@ -67,15 +67,15 @@ void HardwareTest::beepCmd() { * \brief Set single solenoid command handler. */ void HardwareTest::setSingleCmd() { - Serial.println("Called setSingle"); + knitter->sendMsg(test_msgid, "Called setSingle\n"); char *arg = m_sCmd.next(); if (arg == nullptr) { return; } int solenoidNumber = atoi(arg); if (solenoidNumber < 0 or solenoidNumber > 15) { - Serial.print("Invalid argument: "); - Serial.println(solenoidNumber); + sprintf(buf, "Invalid argument: %i\n", solenoidNumber); + knitter->sendMsg(test_msgid, buf); return; } arg = m_sCmd.next(); @@ -84,8 +84,8 @@ void HardwareTest::setSingleCmd() { } int solenoidState = atoi(arg); if (solenoidState < 0 or solenoidState > 1) { - Serial.print("Invalid argument: "); - Serial.println(solenoidState); + sprintf(buf, "Invalid argument: %i\n", solenoidState); + knitter->sendMsg(test_msgid, buf); return; } knitter->setSolenoid(static_cast(solenoidNumber), @@ -96,118 +96,118 @@ void HardwareTest::setSingleCmd() { * \brief Set all solenoids command handler. */ void HardwareTest::setAllCmd() { - Serial.println("Called setAll"); + knitter->sendMsg(test_msgid, "Called setAll\n"); char *arg = m_sCmd.next(); if (arg == nullptr) { return; } - uint8_t highByte = atoi(arg); - arg = m_sCmd.next(); - if (arg == nullptr) { - return; + short unsigned int solenoidState; + // if (scanHex(arg, 4, &solenoidState)) { + if (sscanf(arg, "%hx", &solenoidState)) { + knitter->setSolenoids(solenoidState); + } else { + knitter->sendMsg(test_msgid, "Invalid argument. Please enter a hexadecimal " + "number between 0 and FFFF.\n"); } - uint8_t lowByte = atoi(arg); - uint16_t solenoidState = (highByte << 8) + lowByte; - knitter->setSolenoids(solenoidState); } -/*! +/*! // GCOVR_EXCL_LINE * \brief Read EOL sensors command handler. */ void HardwareTest::readEOLsensorsCmd() { - Serial.println("Called readEOLsensors"); + knitter->sendMsg(test_msgid, "Called readEOLsensors\n"); readEOLsensors(); - Serial.print("\n"); + knitter->sendMsg(test_msgid, "\n"); } -/*! +/*! // GCOVR_EXCL_START * \brief Read encoders command handler. - */ + */ // GCOVR_EXCL_STOP void HardwareTest::readEncodersCmd() { - Serial.println("Called readEncoders"); + knitter->sendMsg(test_msgid, "Called readEncoders\n"); readEncoders(); - Serial.print("\n"); + knitter->sendMsg(test_msgid, "\n"); } -/*! +/*! // GCOVR_EXCL_START * \brief Auto read command handler. - */ + */ // GCOVR_EXCL_STOP void HardwareTest::autoReadCmd() { - Serial.println("Called autoRead, send stop to quit"); + knitter->sendMsg(test_msgid, "Called autoRead, send stop to quit\n"); m_autoReadOn = true; } -/*! +/*! // GCOVR_EXCL_START * \brief Auto test command handler. - */ + */ // GCOVR_EXCL_STOP void HardwareTest::autoTestCmd() { - Serial.println("Called autoTest, send stop to quit"); + knitter->sendMsg(test_msgid, "Called autoTest, send stop to quit\n"); m_autoTestOn = true; } -/*! +/*! // GCOVR_EXCL_START * \brief Stop command handler. - */ + */ // GCOVR_EXCL_STOP void HardwareTest::stopCmd() { m_autoReadOn = false; m_autoTestOn = false; } -/*! +/*! // GCOVR_EXCL_START * \brief Quit command handler. - */ + */ // GCOVR_EXCL_STOP void HardwareTest::quitCmd() { knitter->setQuitFlag(true); knitter->setUpInterrupt(); } -/*! +/*! // GCOVR_EXCL_START * \brief Unrecognized command handler. * * \param buffer: pointer to string containing unrecognized command * * This gets set as the default handler, and gets called when no other command * matches. - */ + */ // GCOVR_EXCL_STOP void HardwareTest::unrecognizedCmd(const char *buffer) { - Serial.println("Unrecognized command"); + knitter->sendMsg(test_msgid, "Unrecognized command\n"); (void)(buffer); // does nothing but prevents 'unused variable' compile error helpCmd(); } -/*! +/*! // GCOVR_EXCL_START * \brief Setup for hardware tests. - */ + */ // GCOVR_EXCL_STOP void HardwareTest::setUp() { // set up callbacks for SerialCommand commands - m_sCmd.addCommand("setSingle", GlobalHardwareTest::setSingleCmd); - m_sCmd.addCommand("setAll", GlobalHardwareTest::setAllCmd); - m_sCmd.addCommand("readEOLsensors", GlobalHardwareTest::readEOLsensorsCmd); - m_sCmd.addCommand("readEncoders", GlobalHardwareTest::readEncodersCmd); - m_sCmd.addCommand("beep", GlobalHardwareTest::beepCmd); - m_sCmd.addCommand("autoRead", GlobalHardwareTest::autoReadCmd); - m_sCmd.addCommand("autoTest", GlobalHardwareTest::autoTestCmd); - m_sCmd.addCommand("send", GlobalHardwareTest::sendCmd); - m_sCmd.addCommand("stop", GlobalHardwareTest::stopCmd); - m_sCmd.addCommand("quit", GlobalHardwareTest::quitCmd); - m_sCmd.addCommand("help", GlobalHardwareTest::helpCmd); + m_sCmd.addCommand("%setSingle", GlobalHardwareTest::setSingleCmd); + m_sCmd.addCommand("%setAll", GlobalHardwareTest::setAllCmd); + m_sCmd.addCommand("%readEOLsensors", GlobalHardwareTest::readEOLsensorsCmd); + m_sCmd.addCommand("%readEncoders", GlobalHardwareTest::readEncodersCmd); + m_sCmd.addCommand("%beep", GlobalHardwareTest::beepCmd); + m_sCmd.addCommand("%autoRead", GlobalHardwareTest::autoReadCmd); + m_sCmd.addCommand("%autoTest", GlobalHardwareTest::autoTestCmd); + m_sCmd.addCommand("%send", GlobalHardwareTest::sendCmd); + m_sCmd.addCommand("%stop", GlobalHardwareTest::stopCmd); + m_sCmd.addCommand("%quit", GlobalHardwareTest::quitCmd); + m_sCmd.addCommand("%help", GlobalHardwareTest::helpCmd); m_sCmd.setDefaultHandler(GlobalHardwareTest::unrecognizedCmd); // Print welcome message - Serial.print("AYAB Hardware Test, Firmware v"); - Serial.print(FW_VERSION_MAJ); - Serial.print("."); - Serial.print(FW_VERSION_MIN); - Serial.print(" API v"); - Serial.print(API_VERSION); - Serial.print("\n\n"); + knitter->sendMsg(test_msgid, "AYAB Hardware Test, "); + sprintf(buf, "Firmware v%hhu", FW_VERSION_MAJ); + knitter->sendMsg(test_msgid, buf); + sprintf(buf, ".%hhu", FW_VERSION_MIN); + knitter->sendMsg(test_msgid, buf); + sprintf(buf, " API v%hhu\n\n", API_VERSION); + knitter->sendMsg(test_msgid, buf); helpCmd(); // attach interrupt for ENC_PIN_A(=2), interrupt #0 detachInterrupt(0); #ifndef AYAB_TESTS attachInterrupt(0, GlobalHardwareTest::encoderAChange, RISING); -#endif // AYAB_TESTS +#endif // AYAB_TESTS // GCOVR_EXCL_LINE m_lastTime = millis(); m_autoReadOn = false; @@ -216,9 +216,9 @@ void HardwareTest::setUp() { knitter->setQuitFlag(false); } -/*! +/*! // GCOVR_EXCL_START * \brief Main loop for hardware tests. - */ + */ // GCOVR_EXCL_STOP void HardwareTest::loop() { unsigned long now = millis(); if (now - m_lastTime >= 500) { @@ -234,42 +234,44 @@ void HardwareTest::beep() { } void HardwareTest::readEncoders() { - Serial.print(" ENC_A: "); + knitter->sendMsg(test_msgid, " ENC_A: "); bool state = digitalRead(ENC_PIN_A); - Serial.print(state ? "HIGH" : "LOW"); - Serial.print(" ENC_B: "); + knitter->sendMsg(test_msgid, state ? "HIGH" : "LOW"); + knitter->sendMsg(test_msgid, " ENC_B: "); state = digitalRead(ENC_PIN_B); - Serial.print(state ? "HIGH" : "LOW"); - Serial.print(" ENC_C: "); + knitter->sendMsg(test_msgid, state ? "HIGH" : "LOW"); + knitter->sendMsg(test_msgid, " ENC_C: "); state = digitalRead(ENC_PIN_C); - Serial.print(state ? "HIGH" : "LOW"); + knitter->sendMsg(test_msgid, state ? "HIGH" : "LOW"); } -void HardwareTest::readEOLsensors() { - Serial.print(" EOL_L: "); - Serial.print(analogRead(EOL_PIN_L)); - Serial.print(" EOL_R: "); - Serial.print(analogRead(EOL_PIN_R)); +void HardwareTest::readEOLsensors() { // GCOVR_EXCL_LINE (?) + uint16_t hallSensor = static_cast(analogRead(EOL_PIN_L)); + sprintf(buf, " EOL_L: %hu", hallSensor); + knitter->sendMsg(test_msgid, buf); + hallSensor = static_cast(analogRead(EOL_PIN_R)); + sprintf(buf, " EOL_R: %hu", hallSensor); + knitter->sendMsg(test_msgid, buf); } void HardwareTest::autoRead() { - Serial.print("\n"); + knitter->sendMsg(test_msgid, "\n"); readEOLsensors(); readEncoders(); - Serial.print("\n"); + knitter->sendMsg(test_msgid, "\n"); } void HardwareTest::autoTestEven() { - Serial.println("Set even solenoids"); - digitalWrite(LED_PIN_A, 1); - digitalWrite(LED_PIN_B, 1); + knitter->sendMsg(test_msgid, "Set even solenoids\n"); + digitalWrite(LED_PIN_A, HIGH); + digitalWrite(LED_PIN_B, HIGH); knitter->setSolenoids(0xAAAA); } void HardwareTest::autoTestOdd() { - Serial.println("Set odd solenoids"); - digitalWrite(LED_PIN_A, 0); - digitalWrite(LED_PIN_B, 0); + knitter->sendMsg(test_msgid, "Set odd solenoids\n"); + digitalWrite(LED_PIN_A, LOW); + digitalWrite(LED_PIN_B, LOW); knitter->setSolenoids(0x5555); } @@ -299,3 +301,29 @@ void HardwareTest::handleTimerEvent() { m_timerEventOdd = not m_timerEventOdd; m_sCmd.readSerial(); } + +/* +// homebrew `sscanf(str, "%hx", &result);` +// does not trim white space +bool HardwareTest::scanHex(char *str, uint8_t maxDigits, uint16_t *result) { + if (maxDigits == 0 or *str == 0) { + return false; + } + uint16_t s = 0; + char c; + while (maxDigits-- > 0 and (c = *str++) != 0) { + s <<= 4; + if ('0' <= c and c <= '9') { + s += c - '0'; + } else if ('a' <= c and c <= 'f') { + s += c + 10 - 'a'; + } else if ('A' <= c and c <= 'F') { + s += c + 10 - 'A'; + } else { + return false; + } + } + *result = s; + return true; +} +*/ diff --git a/src/ayab/hw_test.h b/src/ayab/hw_test.h index 36eab1bef..9d92aaa18 100644 --- a/src/ayab/hw_test.h +++ b/src/ayab/hw_test.h @@ -28,6 +28,8 @@ #include "beeper.h" +constexpr uint8_t BUFFER_LEN = 20; + class HardwareTestInterface { public: virtual ~HardwareTestInterface(){}; @@ -54,11 +56,13 @@ class HardwareTestInterface { class HardwareTest : public HardwareTestInterface { #if AYAB_TESTS + FRIEND_TEST(HardwareTestTest, test_stopCmd); FRIEND_TEST(HardwareTestTest, test_setUp); FRIEND_TEST(HardwareTestTest, test_loop_default); FRIEND_TEST(HardwareTestTest, test_loop_null); FRIEND_TEST(HardwareTestTest, test_loop_autoTestEven); FRIEND_TEST(HardwareTestTest, test_loop_autoTestOdd); + // FRIEND_TEST(HardwareTestTest, test_scanHex); friend class HardwareTestTest; #endif @@ -91,6 +95,8 @@ class HardwareTest : public HardwareTestInterface { void autoTestOdd(); void handleTimerEvent(); + // static bool scanHex(char *str, uint8_t maxDigits, uint16_t *result); + SerialCommand m_sCmd = SerialCommand(); bool m_autoReadOn = false; @@ -98,32 +104,8 @@ class HardwareTest : public HardwareTestInterface { unsigned long m_lastTime = 0U; bool m_timerEventOdd = false; -}; - -class GlobalHardwareTest { -public: - static void helpCmd(); - static void sendCmd(); - static void beepCmd(); - static void setSingleCmd(); - static void setAllCmd(); - static void readEOLsensorsCmd(); - static void readEncodersCmd(); - static void autoReadCmd(); - static void autoTestCmd(); - static void stopCmd(); - static void quitCmd(); - static void unrecognizedCmd(const char *buffer); - - static void setUp(); - static void loop(); -#ifndef AYAB_TESTS - static void encoderAChange(); -#endif - static HardwareTestInterface *m_instance; + char buf[BUFFER_LEN] = {0}; }; -extern GlobalHardwareTest *hwTest; - #endif // HW_TEST_H_ diff --git a/src/ayab/knitter.cpp b/src/ayab/knitter.cpp index 35eec65a5..dceec93f0 100644 --- a/src/ayab/knitter.cpp +++ b/src/ayab/knitter.cpp @@ -24,7 +24,7 @@ #include #include "board.h" -#include "hw_test.h" +#include "global_hw_test.h" #include "knitter.h" #ifdef CLANG_TIDY @@ -82,6 +82,14 @@ void Knitter::send(uint8_t *payload, size_t length) { m_serial_encoding.send(payload, length); } +void Knitter::sendMsg(AYAB_API_t id, const char *msg) { + m_serial_encoding.sendMsg(id, msg); +} + +void Knitter::sendMsg(AYAB_API_t id, char *msg) { + m_serial_encoding.sendMsg(id, msg); +} + /*! * \brief Interrupt service routine. * diff --git a/src/ayab/knitter.h b/src/ayab/knitter.h index c0dd6a030..d81a4e8c3 100644 --- a/src/ayab/knitter.h +++ b/src/ayab/knitter.h @@ -65,6 +65,8 @@ class Knitter { bool continuousReportingEnabled); void send(uint8_t *payload, size_t length); + void sendMsg(AYAB_API_t id, const char *msg); + void sendMsg(AYAB_API_t id, char *msg); void onPacketReceived(const uint8_t *buffer, size_t size); OpState_t getState() const; diff --git a/src/ayab/main.cpp b/src/ayab/main.cpp index 3fb1cccfb..fc3fdf4a1 100644 --- a/src/ayab/main.cpp +++ b/src/ayab/main.cpp @@ -24,12 +24,13 @@ #include -#include "hw_test.h" +#include "global_hw_test.h" #include "knitter.h" -// global definition +// global definitions // references everywhere else must use `extern` Knitter *knitter; +GlobalHardwareTest *hwTest; // initialize static member HardwareTestInterface *GlobalHardwareTest::m_instance = new HardwareTest(); diff --git a/src/ayab/serial_encoding.cpp b/src/ayab/serial_encoding.cpp index 08f0b5efa..eb30730a1 100644 --- a/src/ayab/serial_encoding.cpp +++ b/src/ayab/serial_encoding.cpp @@ -70,7 +70,7 @@ static uint8_t CRC8(const uint8_t *buffer, size_t len) { } #endif -/* Serial Command handling */ +// Serial command handling /*! * \brief Handle start request command. @@ -248,3 +248,17 @@ void SerialEncoding::send(uint8_t *payload, size_t length) { */ m_packetSerial.send(payload, length); } + +// send initial msgid followed by null-terminated string +void SerialEncoding::sendMsg(AYAB_API_t id, const char *msg) { + uint8_t length = 0; + msgBuffer[length++] = static_cast(id); + while (*msg) { + msgBuffer[length++] = static_cast(*msg++); + } + m_packetSerial.send(msgBuffer, length); +} + +void SerialEncoding::sendMsg(AYAB_API_t id, char *msg) { + sendMsg(id, static_cast(msg)); +} diff --git a/src/ayab/serial_encoding.h b/src/ayab/serial_encoding.h index 197451eba..811c2caba 100644 --- a/src/ayab/serial_encoding.h +++ b/src/ayab/serial_encoding.h @@ -36,6 +36,7 @@ constexpr uint8_t API_VERSION = 6U; constexpr uint32_t SERIAL_BAUDRATE = 115200U; constexpr uint8_t MAX_LINE_BUFFER_LEN = 25U; +constexpr uint8_t MAX_MSG_BUFFER_LEN = 255U; enum AYAB_API { reqStart_msgid = 0x01, @@ -47,7 +48,9 @@ enum AYAB_API { reqTest_msgid = 0x04, cnfTest_msgid = 0xC4, indState_msgid = 0x84, - debug_msgid = 0xFF, + cmd_msgid = 0x25, + test_msgid = 0xA5, + debug_msgid = 0xBF, }; using AYAB_API_t = enum AYAB_API; @@ -56,11 +59,14 @@ class SerialEncoding { SerialEncoding(); void update(); void send(uint8_t *payload, size_t length); + void sendMsg(AYAB_API_t id, const char *msg); + void sendMsg(AYAB_API_t id, char *msg); void onPacketReceived(const uint8_t *buffer, size_t size); private: SLIPPacketSerial m_packetSerial; uint8_t lineBuffer[MAX_LINE_BUFFER_LEN] = {0}; + uint8_t msgBuffer[MAX_MSG_BUFFER_LEN] = {0}; void h_reqStart(const uint8_t *buffer, size_t size); void h_cnfLine(const uint8_t *buffer, size_t size); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0ac32270b..585f2420d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -69,10 +69,10 @@ set(COMMON_FLAGS -Wall -Wextra -Wpedantic -Wno-vla -Werror - -fprofile-arcs -ftest-coverage + --coverage # exclude global_hw_test.cpp from profile analysis - # to avoid tedious mocking of these simple functions + # to avoid tedious testing of these simple functions -fprofile-exclude-files=global_hw_test.cpp -g -Og @@ -139,7 +139,7 @@ add_board(Mega) add_executable(${PROJECT_NAME}_knitter ${PROJECT_SOURCE_DIR}/test_all.cpp ${SOURCE_DIRECTORY}/knitter.cpp - ${SOURCE_DIRECTORY}/global_hw_test.cpp + ${SOURCE_DIRECTORY}/global_hw_test.cpp # not mockable ${PROJECT_SOURCE_DIR}/mocks/solenoids_mock.cpp ${PROJECT_SOURCE_DIR}/mocks/encoders_mock.cpp ${PROJECT_SOURCE_DIR}/mocks/beeper_mock.cpp diff --git a/test/mocks/knitter_mock.cpp b/test/mocks/knitter_mock.cpp index 35f26aaa8..4d4b9e35c 100644 --- a/test/mocks/knitter_mock.cpp +++ b/test/mocks/knitter_mock.cpp @@ -83,6 +83,16 @@ void Knitter::send(uint8_t payload[], size_t length) { gKnitterMock->send(payload, length); } +void Knitter::sendMsg(AYAB_API_t id, const char *msg) { + assert(gKnitterMock != NULL); + gKnitterMock->sendMsg(id, msg); +} + +void Knitter::sendMsg(AYAB_API_t id, char *msg) { + assert(gKnitterMock != NULL); + gKnitterMock->sendMsg(id, msg); +} + void Knitter::onPacketReceived(const uint8_t *buffer, size_t size) { assert(gKnitterMock != NULL); gKnitterMock->onPacketReceived(buffer, size); diff --git a/test/mocks/knitter_mock.h b/test/mocks/knitter_mock.h index 8ad56bb7f..f957b9917 100644 --- a/test/mocks/knitter_mock.h +++ b/test/mocks/knitter_mock.h @@ -36,6 +36,8 @@ class KnitterMock { MOCK_METHOD1(setNextLine, bool(uint8_t lineNumber)); MOCK_METHOD0(setLastLine, void()); MOCK_METHOD2(send, void(uint8_t *payload, size_t length)); + MOCK_METHOD2(sendMsg, void(AYAB_API_t id, const char *msg)); + MOCK_METHOD2(sendMsg, void(AYAB_API_t id, char *msg)); MOCK_METHOD2(onPacketReceived, void(const uint8_t *buffer, size_t size)); MOCK_CONST_METHOD0(getMachineType, Machine_t()); MOCK_METHOD1(setMachineType, void(Machine_t)); diff --git a/test/mocks/serial_encoding_mock.cpp b/test/mocks/serial_encoding_mock.cpp index 0cf5d7096..365381ca6 100644 --- a/test/mocks/serial_encoding_mock.cpp +++ b/test/mocks/serial_encoding_mock.cpp @@ -21,8 +21,8 @@ * http://ayab-knitting.com */ -#include #include +#include static SerialEncodingMock *gSerialEncodingMock = NULL; SerialEncodingMock *serialEncodingMockInstance() { @@ -52,6 +52,16 @@ void SerialEncoding::send(uint8_t *payload, size_t length) { gSerialEncodingMock->send(payload, length); } +void SerialEncoding::sendMsg(AYAB_API_t id, const char *msg) { + assert(gSerialEncodingMock != nullptr); + gSerialEncodingMock->sendMsg(id, msg); +} + +void SerialEncoding::sendMsg(AYAB_API_t id, char *msg) { + assert(gSerialEncodingMock != nullptr); + gSerialEncodingMock->sendMsg(id, msg); +} + void SerialEncoding::onPacketReceived(const uint8_t *buffer, size_t size) { assert(gSerialEncodingMock != nullptr); gSerialEncodingMock->onPacketReceived(buffer, size); diff --git a/test/mocks/serial_encoding_mock.h b/test/mocks/serial_encoding_mock.h index 94d4d1036..67ff2f2fd 100644 --- a/test/mocks/serial_encoding_mock.h +++ b/test/mocks/serial_encoding_mock.h @@ -30,10 +30,12 @@ class SerialEncodingMock { public: MOCK_METHOD0(update, void()); MOCK_METHOD2(send, void(uint8_t *payload, size_t length)); + MOCK_METHOD2(sendMsg, void(AYAB_API_t id, const char *msg)); + MOCK_METHOD2(sendMsg, void(AYAB_API_t id, char *msg)); MOCK_METHOD2(onPacketReceived, void(const uint8_t *buffer, size_t size)); }; SerialEncodingMock *serialEncodingMockInstance(); void releaseSerialEncodingMock(); -#endif // SERIAL_ENCODING_MOCK_H_ +#endif // SERIAL_ENCODING_MOCK_H_ diff --git a/test/test.sh b/test/test.sh index c039742d5..4d18662fa 100755 --- a/test/test.sh +++ b/test/test.sh @@ -43,8 +43,9 @@ GTEST_COLOR=1 ctest $ctest_verbose --output-on-failure . cd ../.. GCOVR_ARGS="--exclude-unreachable-branches --exclude-throw-branches \ - --exclude-directories 'test/build/arduino_mock$' -e test_* -e libraries*" + --exclude-directories 'test/build/arduino_mock$' -e test_* -e libraries*" # -e src/ayab/global_hw_test.cpp -gcovr -r . $GCOVR_ARGS --html-details -o ./test/build/coverage.html -gcovr -r . $GCOVR_ARGS --branches +gcovr -k -r . $GCOVR_ARGS --html-details -o ./test/build/coverage.html +# gcovr -r . $GCOVR_ARGS --json-pretty -o ./test/build/coverage-report.json +gcovr -k -r . $GCOVR_ARGS --branches # --json-pretty -o ./test/build/branch-coverage-report.json gcovr -d -j $(nproc) -r . $GCOVR_ARGS --fail-under-line 100 --fail-under-branch 100 diff --git a/test/test_hw_test.cpp b/test/test_hw_test.cpp index d1f011169..0be762648 100644 --- a/test/test_hw_test.cpp +++ b/test/test_hw_test.cpp @@ -24,15 +24,18 @@ #include #include -#include +#include #include +using ::testing::An; using ::testing::AtLeast; using ::testing::Return; -static char zero[2] = {48, 0}; // "0" -static char two[2] = {50, 0}; // "2" -static char twenty[3] = {50, 48, 0}; // "20" +static char zero[2] = {'0', 0}; +static char two[2] = {'2', 0}; +static char g[2] = {'g', 0}; +static char fAdE[5] = {'f', 'A', 'd', 'E', 0}; +static char sixteen[3] = {'1', '6', 0}; // initialize static member HardwareTestInterface *GlobalHardwareTest::m_instance = new HardwareTest(); @@ -71,92 +74,132 @@ TEST_F(HardwareTestTest, test_setUp) { } TEST_F(HardwareTestTest, test_helpCmd) { + EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + .Times(AtLeast(1)); h->helpCmd(); } TEST_F(HardwareTestTest, test_sendCmd) { + EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + .Times(AtLeast(1)); EXPECT_CALL(*knitterMock, send); h->sendCmd(); } TEST_F(HardwareTestTest, test_beepCmd) { + EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + .Times(AtLeast(1)); EXPECT_CALL(*arduinoMock, analogWrite).Times(AtLeast(1)); EXPECT_CALL(*arduinoMock, delay).Times(AtLeast(1)); h->beepCmd(); } TEST_F(HardwareTestTest, test_setSingleCmd_fail1) { + EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + .Times(AtLeast(1)); EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(nullptr)); + EXPECT_CALL(*knitterMock, setSolenoid).Times(0); h->setSingleCmd(); } TEST_F(HardwareTestTest, test_setSingleCmd_fail2) { - EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(twenty)); + EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + .Times(AtLeast(1)); + EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(sixteen)); + EXPECT_CALL(*knitterMock, setSolenoid).Times(0); h->setSingleCmd(); } TEST_F(HardwareTestTest, test_setSingleCmd_fail3) { + EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + .Times(AtLeast(1)); EXPECT_CALL(*serialCommandMock, next) .WillOnce(Return(zero)) .WillOnce(Return(nullptr)); + EXPECT_CALL(*knitterMock, setSolenoid).Times(0); h->setSingleCmd(); } TEST_F(HardwareTestTest, test_setSingleCmd_fail4) { + EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + .Times(AtLeast(1)); EXPECT_CALL(*serialCommandMock, next) .WillOnce(Return(zero)) .WillOnce(Return(two)); + EXPECT_CALL(*knitterMock, setSolenoid).Times(0); h->setSingleCmd(); } TEST_F(HardwareTestTest, test_setSingleCmd_success) { + EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + .Times(AtLeast(1)); EXPECT_CALL(*serialCommandMock, next).WillRepeatedly(Return(zero)); EXPECT_CALL(*knitterMock, setSolenoid); h->setSingleCmd(); } TEST_F(HardwareTestTest, test_setAllCmd_fail1) { + EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + .Times(AtLeast(1)); EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(nullptr)); + EXPECT_CALL(*knitterMock, setSolenoids).Times(0); h->setAllCmd(); } TEST_F(HardwareTestTest, test_setAllCmd_fail2) { - EXPECT_CALL(*serialCommandMock, next) - .WillOnce(Return(zero)) - .WillOnce(Return(nullptr)); + EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + .Times(AtLeast(1)); + EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(g)); + EXPECT_CALL(*knitterMock, setSolenoids).Times(0); h->setAllCmd(); } TEST_F(HardwareTestTest, test_setAllCmd_success) { - EXPECT_CALL(*serialCommandMock, next).WillRepeatedly(Return(zero)); + EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + .Times(AtLeast(1)); + EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(fAdE)); EXPECT_CALL(*knitterMock, setSolenoids); h->setAllCmd(); } TEST_F(HardwareTestTest, test_readEOLsensorsCmd) { + EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + .Times(AtLeast(1)); h->readEOLsensorsCmd(); } -TEST_F(HardwareTestTest, test_readEncodersCmd) { - // low - EXPECT_CALL(*arduinoMock, digitalRead).WillRepeatedly(Return(0)); +TEST_F(HardwareTestTest, test_readEncodersCmd_low) { + EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + .Times(AtLeast(1)); + EXPECT_CALL(*arduinoMock, digitalRead).WillRepeatedly(Return(LOW)); h->readEncodersCmd(); +} - // high - EXPECT_CALL(*arduinoMock, digitalRead).WillRepeatedly(Return(1)); +TEST_F(HardwareTestTest, test_readEncodersCmd_high) { + EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + .Times(AtLeast(1)); + EXPECT_CALL(*arduinoMock, digitalRead).WillRepeatedly(Return(HIGH)); h->readEncodersCmd(); } TEST_F(HardwareTestTest, test_autoReadCmd) { + EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + .Times(AtLeast(1)); h->autoReadCmd(); } TEST_F(HardwareTestTest, test_autoTestCmd) { + EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + .Times(AtLeast(1)); h->autoTestCmd(); } TEST_F(HardwareTestTest, test_stopCmd) { + h->m_autoReadOn = true; + h->m_autoTestOn = true; h->stopCmd(); + ASSERT_FALSE(h->m_autoReadOn); + ASSERT_FALSE(h->m_autoTestOn); } TEST_F(HardwareTestTest, test_quitCmd) { @@ -165,6 +208,8 @@ TEST_F(HardwareTestTest, test_quitCmd) { } TEST_F(HardwareTestTest, test_unrecognizedCmd) { + EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + .Times(AtLeast(1)); const char buffer[1] = {1}; h->unrecognizedCmd(buffer); } @@ -189,6 +234,7 @@ TEST_F(HardwareTestTest, test_loop_autoTestEven) { h->m_timerEventOdd = false; h->m_autoReadOn = true; h->m_autoTestOn = true; + EXPECT_CALL(*arduinoMock, digitalRead).Times(0); EXPECT_CALL(*arduinoMock, digitalWrite).Times(2); EXPECT_CALL(*knitterMock, setSolenoids); h->loop(); @@ -200,7 +246,27 @@ TEST_F(HardwareTestTest, test_loop_autoTestOdd) { h->m_timerEventOdd = true; h->m_autoReadOn = true; h->m_autoTestOn = true; + EXPECT_CALL(*arduinoMock, digitalRead).Times(3); EXPECT_CALL(*arduinoMock, digitalWrite).Times(2); EXPECT_CALL(*knitterMock, setSolenoids); h->loop(); } + +/* +TEST_F(HardwareTestTest, test_scanHex) { + uint16_t result; + ASSERT_FALSE(h->scanHex(zero, 0, &result)); + ASSERT_FALSE(h->scanHex(g, 4, &result)); + ASSERT_FALSE(h->scanHex(g + 1, 1, &result)); + ASSERT_TRUE(h->scanHex(zero, 1, &result)); + ASSERT_TRUE(result == 0); + ASSERT_TRUE(h->scanHex(zero, 4, &result)); + ASSERT_TRUE(result == 0); + ASSERT_TRUE(h->scanHex(a, 1, &result)); + ASSERT_TRUE(result == 0xa); + ASSERT_TRUE(h->scanHex(A, 4, &result)); + ASSERT_TRUE(result == 0xA); + ASSERT_TRUE(h->scanHex(fAdE, 4, &result)); + ASSERT_TRUE(result == 0xfAdE); +} +*/ diff --git a/test/test_knitter.cpp b/test/test_knitter.cpp index 644fad2b1..c892293ab 100644 --- a/test/test_knitter.cpp +++ b/test/test_knitter.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -35,12 +36,17 @@ using ::testing::_; using ::testing::AtLeast; using ::testing::Mock; using ::testing::Return; +using ::testing::TypedEq; -// global definitions -// references everywhere else must use `extern` +// Global definition. +// References everywhere else must use `extern` Knitter *knitter; -// initialize static member +// GlobalHardwareTest has static methods which cannot be mocked directly. +// These static methods delegate to non-static methods of a global instance +// implementing the HardwareTestInterface class (i.e. either HardwareTest or +// HardwareTestMock). Here we initialize the member of GlobalHardwareTest +// that points to this instance. This is a form of dependency injection. HardwareTestInterface *GlobalHardwareTest::m_instance = new HardwareTestMock(); void onPacketReceived(const uint8_t *buffer, size_t size) { @@ -56,9 +62,16 @@ class KnitterTest : public ::testing::Test { solenoidsMock = solenoidsMockInstance(); encodersMock = encodersMockInstance(); serialEncodingMock = serialEncodingMockInstance(); + + // Pointer to global HardwareTestMock instance hwTestMock = static_cast(GlobalHardwareTest::m_instance); - Mock::AllowLeak(hwTestMock); // because it does not get destructed + + // The global instance does not get destroyed at the end of each test. + // Ordinarily the mock instance would be local and such behaviour would + // cause a memory leak. We must notify the test that this is not the case. + Mock::AllowLeak(hwTestMock); + expect_constructor(); k = new Knitter(); } @@ -77,9 +90,8 @@ class KnitterTest : public ::testing::Test { SolenoidsMock *solenoidsMock; EncodersMock *encodersMock; SerialEncodingMock *serialEncodingMock; - HardwareTestMock *hwTestMock; - HardwareTestMock *dummy; - Knitter *&k = knitter; // alias of global `knitter` + HardwareTestMock *hwTestMock; // pointer to global instance + Knitter *&k = knitter; // alias of global `knitter` void expect_constructor() { EXPECT_CALL(*arduinoMock, pinMode(ENC_PIN_A, INPUT)); @@ -154,12 +166,12 @@ class KnitterTest : public ::testing::Test { } void get_to_ready() { - // Machine is initialized when left hall sensor is passed in Right direction - // Inside active needles + // machine is initialized when left hall sensor + // is passed in Right direction inside active needles Machine_t m = k->getMachineType(); expected_isr(40 + END_OF_LINE_OFFSET_L[m] + 1); - // init + // initialize EXPECT_CALL(*solenoidsMock, setSolenoids(0xFFFF)); expect_indState(); expected_fsm(); @@ -201,17 +213,17 @@ class KnitterTest : public ::testing::Test { void test_operate_line_request() { EXPECT_CALL(*solenoidsMock, setSolenoid); expected_operate(true); - // _workedOnLine is set to true + // `m_workedOnLine` is set to `true` // Position has changed since last call to operate function - // m_pixelToSet is set above m_stopNeedle + END_OF_LINE_OFFSET_R + // `m_pixelToSet` is set above `m_stopNeedle` + END_OF_LINE_OFFSET_R Machine_t m = k->getMachineType(); // Kh910 expected_isr(NUM_NEEDLES[m] + 8 + END_OF_LINE_OFFSET_R[m] + 1); EXPECT_CALL(*solenoidsMock, setSolenoid); expected_operate(false); - // No change in position, no action. + // no change in position, no action. EXPECT_CALL(*solenoidsMock, setSolenoid).Times(0); expected_operate(false); } @@ -222,7 +234,7 @@ class KnitterTest : public ::testing::Test { */ TEST_F(KnitterTest, test_constructor) { ASSERT_EQ(k->getState(), s_init); - // NOTE: Probing private data! + // NOTE: probing private data! ASSERT_EQ(k->m_startNeedle, 0); } @@ -248,7 +260,6 @@ TEST_F(KnitterTest, test_isr) { * \test */ TEST_F(KnitterTest, test_fsm_default_case) { - // NOTE: Probing private data to be able to cover all branches. k->setState(static_cast(4)); expected_fsm(); } @@ -257,17 +268,17 @@ TEST_F(KnitterTest, test_fsm_default_case) { * \test */ TEST_F(KnitterTest, test_fsm_init) { - // Not ready + // not ready expected_isr(Left, Left); expected_fsm(); ASSERT_EQ(k->getState(), s_init); - // Not now either + // still not ready expected_isr(Right, Right); expected_fsm(); ASSERT_EQ(k->getState(), s_init); - // Ready + // ready expected_isr(Right, Left); EXPECT_CALL(*solenoidsMock, setSolenoids(0xFFFF)); expect_indState(); @@ -282,21 +293,23 @@ TEST_F(KnitterTest, test_fsm_ready) { get_to_ready(); EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 0)); expected_fsm(); - // Still in ready + + // still in ready state ASSERT_EQ(k->getState(), s_ready); - // Again + // again EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 0)); expected_fsm(); - // Still in ready + + // still in ready state ASSERT_EQ(k->getState(), s_ready); } /*! * \test */ -TEST_F(KnitterTest, test_fsm_test) { - // Enter test state +TEST_F(KnitterTest, test_fsm_hwtest) { + // enter test state EXPECT_CALL(*hwTestMock, setUp); ASSERT_EQ(k->startTest(Kh910), true); @@ -305,7 +318,7 @@ TEST_F(KnitterTest, test_fsm_test) { EXPECT_CALL(*hwTestMock, loop); expected_fsm(); - // Again with same position, no indState this time. + // again with same position, no `indState` this time. expected_isr(); expect_indState<0>(); EXPECT_CALL(*hwTestMock, loop); @@ -365,12 +378,16 @@ TEST_F(KnitterTest, test_startOperation_Kh270) { */ TEST_F(KnitterTest, test_startOperation_failures) { uint8_t pattern[] = {1}; - - // stopNeedle lower than start get_to_ready(); + + // `m_stopNeedle` lower than `m_startNeedle` ASSERT_EQ(k->startOperation(Kh910, 1, 0, pattern, false), false); + + // `m_stopNeedle` out of range ASSERT_EQ(k->startOperation(Kh910, 0, NUM_NEEDLES[Kh910], pattern, false), false); + + // null pattern ASSERT_EQ(k->startOperation(Kh910, 0, NUM_NEEDLES[Kh910] - 1, nullptr, false), false); } @@ -390,9 +407,10 @@ TEST_F(KnitterTest, test_startTest) { */ TEST_F(KnitterTest, test_startTest_in_operation) { get_to_operate(Kh910); - // Can't start test - ASSERT_EQ(k->startTest(Kh910), false); ASSERT_EQ(k->getState(), s_operate); + + // can't start test + ASSERT_EQ(k->startTest(Kh910), false); } /*! @@ -401,26 +419,26 @@ TEST_F(KnitterTest, test_startTest_in_operation) { TEST_F(KnitterTest, test_setNextLine) { ASSERT_EQ(k->setNextLine(1), false); - // Set m_lineRequested + // set `m_lineRequested` EXPECT_CALL(*solenoidsMock, setSolenoid).Times(1); expected_operate(true); - // Outside of the active needles + // outside of the active needles expected_isr(40 + NUM_NEEDLES[Kh910] - 1 + END_OF_LINE_OFFSET_R[Kh910] + 1); EXPECT_CALL(*solenoidsMock, setSolenoid).Times(1); expected_operate(false); ASSERT_EQ(k->getState(), s_operate); - // Wrong line number + // wrong line number EXPECT_CALL(*beeperMock, finishedLine).Times(0); expect_send(); ASSERT_EQ(k->setNextLine(1), false); - // Correct line number + // correct line number EXPECT_CALL(*beeperMock, finishedLine).Times(1); ASSERT_EQ(k->setNextLine(0), true); - // m_lineRequested has been set to false + // `m_lineRequested` has been set to `false` ASSERT_EQ(k->setNextLine(0), false); } @@ -428,10 +446,10 @@ TEST_F(KnitterTest, test_setNextLine) { * \test */ TEST_F(KnitterTest, test_operate_Kh910) { - // m_pixelToSet gets set to 0 + // `m_pixelToSet` gets set to 0 expected_isr(8); - // init + // initialize uint16_t bitmask = SOLENOIDS_BITMASK; EXPECT_CALL(*solenoidsMock, setSolenoids(bitmask)); expect_indState(); @@ -440,7 +458,7 @@ TEST_F(KnitterTest, test_operate_Kh910) { // operate uint8_t pattern[] = {1}; - // startNeedle is greater than pixelToSet + // `m_startNeedle` is greater than `m_pixelToSet` EXPECT_CALL(*beeperMock, ready); EXPECT_CALL(*encodersMock, init); const uint8_t START_NEEDLE = NUM_NEEDLES[Kh910] - 2; @@ -452,7 +470,7 @@ TEST_F(KnitterTest, test_operate_Kh910) { EXPECT_CALL(*arduinoMock, delay(2000)); EXPECT_CALL(*beeperMock, finishedLine); - // indState and send + // `indState` and send expect_send<2>(); EXPECT_CALL(*encodersMock, getHallValue(Left)); EXPECT_CALL(*encodersMock, getHallValue(Right)); @@ -460,12 +478,12 @@ TEST_F(KnitterTest, test_operate_Kh910) { EXPECT_CALL(*solenoidsMock, setSolenoid); expected_operate(false); - // No useful position calculated by calculatePixelAndSolenoid + // no useful position calculated by `calculatePixelAndSolenoid()` expected_isr(100, NoDirection, Right, Shifted, Garter); expect_indState(); expected_operate(false); - // Don't set workedonline to true + // don't set `m_workedonline` to `true` expected_isr(8 + STOP_NEEDLE + OFFSET); EXPECT_CALL(*solenoidsMock, setSolenoid); expect_indState(); @@ -481,10 +499,10 @@ TEST_F(KnitterTest, test_operate_Kh910) { * \test */ TEST_F(KnitterTest, test_operate_Kh270) { - // m_pixelToSet gets set to 0 + // `m_pixelToSet` gets set to 0 expected_isr(8); - // init + // initialize uint16_t bitmask = SOLENOIDS_BITMASK; EXPECT_CALL(*solenoidsMock, setSolenoids(bitmask)); expect_indState(); @@ -493,7 +511,7 @@ TEST_F(KnitterTest, test_operate_Kh270) { // operate uint8_t pattern[] = {1}; - // startNeedle is greater than pixelToSet + // `m_startNeedle` is greater than `m_pixelToSet` EXPECT_CALL(*beeperMock, ready); EXPECT_CALL(*encodersMock, init); const uint8_t START_NEEDLE = NUM_NEEDLES[Kh270] - 2; @@ -511,7 +529,7 @@ TEST_F(KnitterTest, test_operate_Kh270) { EXPECT_CALL(*arduinoMock, delay(2000)); EXPECT_CALL(*beeperMock, finishedLine); - // indState and send + // `indState` and send expect_send<2>(); EXPECT_CALL(*encodersMock, getHallValue(Left)); EXPECT_CALL(*encodersMock, getHallValue(Right)); @@ -519,12 +537,12 @@ TEST_F(KnitterTest, test_operate_Kh270) { EXPECT_CALL(*solenoidsMock, setSolenoid); expected_operate(false); - // No useful position calculated by calculatePixelAndSolenoid + // no useful position calculated by `calculatePixelAndSolenoid()` expected_isr(100, NoDirection, Right, Shifted, Garter); expect_indState(); expected_operate(false); - // Don't set workedonline to true + // don't set `m_workedonline` to `true` expected_isr(8 + STOP_NEEDLE + OFFSET); EXPECT_CALL(*solenoidsMock, setSolenoid); expect_indState(); @@ -541,17 +559,18 @@ TEST_F(KnitterTest, test_operate_Kh270) { */ TEST_F(KnitterTest, test_operate_line_request) { EXPECT_CALL(*solenoidsMock, setSolenoid); + + // `m_workedOnLine` is set to `true` expected_operate(true); - // _workedOnLine is set to true // Position has changed since last call to operate function - // m_pixelToSet is set above m_stopNeedle + END_OF_LINE_OFFSET_R + // `m_pixelToSet` is set above `m_stopNeedle` + END_OF_LINE_OFFSET_R expected_isr(NUM_NEEDLES[Kh910] + 8 + END_OF_LINE_OFFSET_R[Kh910] + 1); EXPECT_CALL(*solenoidsMock, setSolenoid); expected_operate(false); - // No change in position, no action. + // no change in position, no action. EXPECT_CALL(*solenoidsMock, setSolenoid).Times(0); expected_operate(false); } @@ -561,14 +580,15 @@ TEST_F(KnitterTest, test_operate_line_request) { */ TEST_F(KnitterTest, test_operate_lastline) { EXPECT_CALL(*solenoidsMock, setSolenoid); + + // `m_workedOnLine` is set to true expected_operate(true); - // _workedOnLine is set to true // Position has changed since last call to operate function - // m_pixelToSet is above m_stopNeedle + END_OF_LINE_OFFSET_R + // `m_pixelToSet` is above `m_stopNeedle` + END_OF_LINE_OFFSET_R expected_isr(NUM_NEEDLES[Kh910] + 8 + END_OF_LINE_OFFSET_R[Kh910] + 1); - // m_lastLineFlag is true + // `m_lastLineFlag` is `true` k->setLastLine(); EXPECT_CALL(*solenoidsMock, setSolenoid); @@ -584,7 +604,7 @@ TEST_F(KnitterTest, test_operate_lastline) { TEST_F(KnitterTest, test_operate_lastline_and_no_req) { get_to_operate(Kh910); - // Note probing lots of private data and methods to get full branch coverage. + // Note: probing private data and methods to get full branch coverage. k->m_stopNeedle = 100; uint8_t wanted_pixel = k->m_stopNeedle + END_OF_LINE_OFFSET_R[Kh910] + 1; k->m_firstRun = false; @@ -613,7 +633,7 @@ TEST_F(KnitterTest, test_operate_same_position) { EXPECT_CALL(*solenoidsMock, setSolenoid); expected_operate(true); - // No call to setSolenoid since position was the same + // no call to `setSolenoid()` since position was the same EXPECT_CALL(*solenoidsMock, setSolenoid).Times(0); expected_operate(false); } @@ -623,20 +643,21 @@ TEST_F(KnitterTest, test_operate_same_position) { */ TEST_F(KnitterTest, test_operate_new_line) { EXPECT_CALL(*solenoidsMock, setSolenoid); - expected_operate(true); + // _workedOnLine is set to true + expected_operate(true); // Position has changed since last call to operate function - // m_pixelToSet is above m_stopNeedle + END_OF_LINE_OFFSET_R + // `m_pixelToSet` is above `m_stopNeedle` + END_OF_LINE_OFFSET_R expected_isr(NUM_NEEDLES[Kh910] + 8 + END_OF_LINE_OFFSET_R[Kh910] + 1); - // Set m_lineRequested to false + // set `m_lineRequested` to `false` EXPECT_CALL(*beeperMock, finishedLine); k->setNextLine(0); EXPECT_CALL(*solenoidsMock, setSolenoid); - // reqLine is called which calls send + // `reqLine()` is called which calls `send()` expect_send(); expected_operate(false); } @@ -648,43 +669,43 @@ TEST_F(KnitterTest, test_calculatePixelAndSolenoid) { EXPECT_CALL(*hwTestMock, setUp); expected_set_machine(Kh910); - // New Position, different beltshift and active hall + // new position, different beltshift and active hall expected_isr(100, Right, Right, Shifted, Lace); expected_test(); - // No direction, need to change position to enter test + // no direction, need to change position to enter test expected_isr(101, NoDirection, Right, Shifted, Lace); expected_test(); - // No belt, need to change position to enter test + // no belt, need to change position to enter test expected_isr(100, Right, Right, Unknown, Lace); expected_test(); - // No belt on left side, need to change position to enter test + // no belt on left side, need to change position to enter test expected_isr(101, Left, Right, Unknown, Garter); expected_test(); - // Left Lace carriage + // left Lace carriage expected_isr(100, Left, Right, Unknown, Lace); expected_test(); - // Regular belt on left, need to change position to enter test + // regular belt on left, need to change position to enter test expected_isr(101, Left, Right, Regular, Garter); expected_test(); - // Shifted belt on left, need to change position to enter test + // shifted belt on left, need to change position to enter test expected_isr(100, Left, Right, Shifted, Garter); expected_test(); - // Off of right end, position is changed + // off of right end, position is changed expected_isr(END_RIGHT[Kh910], Left, Right, Unknown, Lace); expected_test(); - // Direction right, have not reached offset + // direction right, have not reached offset expected_isr(39, Right, Left, Unknown, Lace); expected_test(); - // Kh270 + // KH270 k->setMachineType(Kh270); // K carriage direction left @@ -695,6 +716,7 @@ TEST_F(KnitterTest, test_calculatePixelAndSolenoid) { expected_isr(END_RIGHT[Kh270], Right, Left, Regular, Knit); expected_test(); + // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(hwTestMock)); } @@ -705,14 +727,19 @@ TEST_F(KnitterTest, test_getStartOffset) { // out of range values k->m_carriage = Knit; ASSERT_EQ(k->getStartOffset(NoDirection), 0); + ASSERT_EQ(k->getStartOffset(NUM_DIRECTIONS), 0); + k->m_carriage = NoCarriage; ASSERT_EQ(k->getStartOffset(Left), 0); + k->m_carriage = NUM_CARRIAGES; ASSERT_EQ(k->getStartOffset(Right), 0); + k->m_carriage = Lace; k->m_machineType = NoMachine; ASSERT_EQ(k->getStartOffset(Left), 0); + k->m_machineType = NUM_MACHINES; ASSERT_EQ(k->getStartOffset(Right), 0); } @@ -729,13 +756,18 @@ TEST_F(KnitterTest, test_onPacketReceived) { * \test */ TEST_F(KnitterTest, test_quit_hw_test) { + // get to test state EXPECT_CALL(*hwTestMock, setUp); ASSERT_EQ(k->startTest(Kh910), true); ASSERT_EQ(k->getState(), s_test); + + // quit k->setQuitFlag(true); EXPECT_CALL(*hwTestMock, loop); k->state_test(); ASSERT_EQ(k->getState(), s_ready); + + // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(hwTestMock)); } @@ -744,7 +776,7 @@ TEST_F(KnitterTest, test_quit_hw_test) { */ TEST_F(KnitterTest, test_setSolenoids) { EXPECT_CALL(*solenoidsMock, setSolenoids); - k->setSolenoids(0x1234); + k->setSolenoids(0xFADE); } /*! @@ -752,5 +784,23 @@ TEST_F(KnitterTest, test_setSolenoids) { */ TEST_F(KnitterTest, test_setSolenoid) { EXPECT_CALL(*solenoidsMock, setSolenoid); - k->setSolenoid(0x12, 0x34); + k->setSolenoid(15, 1); +} + +/*! + * \test + */ +TEST_F(KnitterTest, test_sendMsg1) { + EXPECT_CALL(*serialEncodingMock, + sendMsg(test_msgid, TypedEq("abc"))); + k->sendMsg(test_msgid, "abc"); +} + +/*! + * \test + */ +TEST_F(KnitterTest, test_sendMsg2) { + char buf[] = "abc\0"; + EXPECT_CALL(*serialEncodingMock, sendMsg(test_msgid, TypedEq(buf))); + k->sendMsg(test_msgid, buf); } diff --git a/test/test_serial_encoding.cpp b/test/test_serial_encoding.cpp index f60bdc657..45efffaaa 100644 --- a/test/test_serial_encoding.cpp +++ b/test/test_serial_encoding.cpp @@ -186,3 +186,16 @@ TEST_F(SerialEncodingTest, test_send) { uint8_t p[] = {1, 2, 3}; s->send(p, 3); } + +TEST_F(SerialEncodingTest, test_sendMsg1) { + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); + s->sendMsg(test_msgid, "abc"); +} + +TEST_F(SerialEncodingTest, test_sendMsg2) { + char buf[] = "abc\0"; + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); + s->sendMsg(test_msgid, buf); +} From 940b1cce41c6f668580172e669988b25cba2c644 Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 19 Aug 2020 10:16:36 -0400 Subject: [PATCH 06/17] Remove SerialCommand and make hardware test command methods non-static. --- src/ayab/global_hw_test.cpp | 30 ++++++++++--------- src/ayab/global_hw_test.h | 22 +++++++------- src/ayab/hw_test.cpp | 8 +++++ src/ayab/hw_test.h | 30 ++++++++++--------- test/CMakeLists.txt | 6 ++-- test/mocks/hw_test_mock.cpp | 10 +++---- test/mocks/hw_test_mock.h | 4 +-- test/test_hw_test.cpp | 58 ++++++++++++++++++------------------- 8 files changed, 91 insertions(+), 77 deletions(-) diff --git a/src/ayab/global_hw_test.cpp b/src/ayab/global_hw_test.cpp index c56587ec5..60dafa75f 100644 --- a/src/ayab/global_hw_test.cpp +++ b/src/ayab/global_hw_test.cpp @@ -24,6 +24,21 @@ // static member functions +void GlobalHardwareTest::setUp() { + m_instance->setUp(); +} + +void GlobalHardwareTest::loop() { + m_instance->loop(); +} + +#ifndef AYAB_TESTS +void GlobalHardwareTest::encoderAChange() { + m_instance->encoderAChange(); +} +#endif // AYAB_TESTS + +/* void GlobalHardwareTest::helpCmd() { m_instance->helpCmd(); } @@ -71,17 +86,4 @@ void GlobalHardwareTest::quitCmd() { void GlobalHardwareTest::unrecognizedCmd(const char *buffer) { m_instance->unrecognizedCmd(buffer); } - -void GlobalHardwareTest::setUp() { - m_instance->setUp(); -} - -void GlobalHardwareTest::loop() { - m_instance->loop(); -} - -#ifndef AYAB_TESTS -void GlobalHardwareTest::encoderAChange() { - m_instance->encoderAChange(); -} -#endif // AYAB_TESTS +*/ diff --git a/src/ayab/global_hw_test.h b/src/ayab/global_hw_test.h index 6d45db864..d89434379 100644 --- a/src/ayab/global_hw_test.h +++ b/src/ayab/global_hw_test.h @@ -32,6 +32,17 @@ class GlobalHardwareTest { public: + static void setUp(); + static void loop(); +#ifndef AYAB_TESTS + static void encoderAChange(); +#endif + + // pointer to global instance whose methods are implemented + // in the static methods belonging to this class + static HardwareTestInterface *m_instance; + + /* static void helpCmd(); static void sendCmd(); static void beepCmd(); @@ -44,16 +55,7 @@ class GlobalHardwareTest { static void stopCmd(); static void quitCmd(); static void unrecognizedCmd(const char *buffer); - - static void setUp(); - static void loop(); -#ifndef AYAB_TESTS - static void encoderAChange(); -#endif - - // pointer to global instance whose methods are implemented - // in the static methods belonging to this class - static HardwareTestInterface *m_instance; + */ }; extern GlobalHardwareTest *hwTest; diff --git a/src/ayab/hw_test.cpp b/src/ayab/hw_test.cpp index 16fd3c859..7f47b24aa 100644 --- a/src/ayab/hw_test.cpp +++ b/src/ayab/hw_test.cpp @@ -68,6 +68,7 @@ void HardwareTest::beepCmd() { */ void HardwareTest::setSingleCmd() { knitter->sendMsg(test_msgid, "Called setSingle\n"); + /* char *arg = m_sCmd.next(); if (arg == nullptr) { return; @@ -90,6 +91,7 @@ void HardwareTest::setSingleCmd() { } knitter->setSolenoid(static_cast(solenoidNumber), static_cast(solenoidState)); + */ } /*! @@ -97,6 +99,7 @@ void HardwareTest::setSingleCmd() { */ void HardwareTest::setAllCmd() { knitter->sendMsg(test_msgid, "Called setAll\n"); + /* char *arg = m_sCmd.next(); if (arg == nullptr) { return; @@ -109,6 +112,7 @@ void HardwareTest::setAllCmd() { knitter->sendMsg(test_msgid, "Invalid argument. Please enter a hexadecimal " "number between 0 and FFFF.\n"); } + */ } /*! // GCOVR_EXCL_LINE @@ -180,6 +184,7 @@ void HardwareTest::unrecognizedCmd(const char *buffer) { */ // GCOVR_EXCL_STOP void HardwareTest::setUp() { // set up callbacks for SerialCommand commands + /* m_sCmd.addCommand("%setSingle", GlobalHardwareTest::setSingleCmd); m_sCmd.addCommand("%setAll", GlobalHardwareTest::setAllCmd); m_sCmd.addCommand("%readEOLsensors", GlobalHardwareTest::readEOLsensorsCmd); @@ -192,6 +197,7 @@ void HardwareTest::setUp() { m_sCmd.addCommand("%quit", GlobalHardwareTest::quitCmd); m_sCmd.addCommand("%help", GlobalHardwareTest::helpCmd); m_sCmd.setDefaultHandler(GlobalHardwareTest::unrecognizedCmd); + */ // Print welcome message knitter->sendMsg(test_msgid, "AYAB Hardware Test, "); @@ -299,7 +305,9 @@ void HardwareTest::handleTimerEvent() { } } m_timerEventOdd = not m_timerEventOdd; + /* m_sCmd.readSerial(); + */ } /* diff --git a/src/ayab/hw_test.h b/src/ayab/hw_test.h index 9d92aaa18..877078a71 100644 --- a/src/ayab/hw_test.h +++ b/src/ayab/hw_test.h @@ -24,7 +24,7 @@ #define HW_TEST_H_ #include -#include +//#include #include "beeper.h" @@ -33,7 +33,14 @@ constexpr uint8_t BUFFER_LEN = 20; class HardwareTestInterface { public: virtual ~HardwareTestInterface(){}; + virtual void setUp() = 0; + virtual void loop() = 0; + +#ifndef AYAB_TESTS + virtual void encoderAChange() = 0; +#endif + /* virtual void helpCmd() = 0; virtual void sendCmd() = 0; virtual void beepCmd() = 0; @@ -46,12 +53,7 @@ class HardwareTestInterface { virtual void stopCmd() = 0; virtual void quitCmd() = 0; virtual void unrecognizedCmd(const char *buffer) = 0; - - virtual void setUp() = 0; - virtual void loop() = 0; -#ifndef AYAB_TESTS - virtual void encoderAChange() = 0; -#endif + */ }; class HardwareTest : public HardwareTestInterface { @@ -67,6 +69,12 @@ class HardwareTest : public HardwareTestInterface { #endif public: + void setUp(); + void loop(); +#ifndef AYAB_TESTS + void encoderAChange(); +#endif + void helpCmd(); void sendCmd(); void beepCmd(); @@ -80,12 +88,6 @@ class HardwareTest : public HardwareTestInterface { void quitCmd(); void unrecognizedCmd(const char *buffer); - void setUp(); - void loop(); -#ifndef AYAB_TESTS - void encoderAChange(); -#endif - private: void beep(); void readEOLsensors(); @@ -97,7 +99,7 @@ class HardwareTest : public HardwareTestInterface { // static bool scanHex(char *str, uint8_t maxDigits, uint16_t *result); - SerialCommand m_sCmd = SerialCommand(); + // SerialCommand m_sCmd = SerialCommand(); bool m_autoReadOn = false; bool m_autoTestOn = false; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 585f2420d..f5c821c25 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -30,7 +30,7 @@ set(COMMON_INCLUDES ${ARDUINO_MOCK_LIBS_DIR}/lib/gtest/gtest/src/gtest/googlemock/include ${PROJECT_SOURCE_DIR}/mocks ${SOURCE_DIRECTORY} - ${LIBRARY_DIRECTORY}/SerialCommand + #${LIBRARY_DIRECTORY}/SerialCommand ${LIBRARY_DIRECTORY}/PacketSerial/src ) set(EXTERNAL_LIB_INCLUDES @@ -55,7 +55,7 @@ set(COMMON_SOURCES ${SOURCE_DIRECTORY}/hw_test.cpp ${SOURCE_DIRECTORY}/global_hw_test.cpp ${PROJECT_SOURCE_DIR}/test_hw_test.cpp - ${PROJECT_SOURCE_DIR}/mocks/SerialCommand_mock.cpp + #${PROJECT_SOURCE_DIR}/mocks/SerialCommand_mock.cpp ${PROJECT_SOURCE_DIR}/mocks/knitter_mock.cpp ) @@ -146,7 +146,7 @@ add_executable(${PROJECT_NAME}_knitter ${PROJECT_SOURCE_DIR}/mocks/serial_encoding_mock.cpp ${PROJECT_SOURCE_DIR}/mocks/hw_test_mock.cpp ${PROJECT_SOURCE_DIR}/test_knitter.cpp - ${LIBRARY_DIRECTORY}/SerialCommand/SerialCommand.cpp + #${LIBRARY_DIRECTORY}/SerialCommand/SerialCommand.cpp ${SOFT_I2C_LIB} ) target_include_directories(${PROJECT_NAME}_knitter diff --git a/test/mocks/hw_test_mock.cpp b/test/mocks/hw_test_mock.cpp index c82c498ab..0efd0762f 100644 --- a/test/mocks/hw_test_mock.cpp +++ b/test/mocks/hw_test_mock.cpp @@ -39,6 +39,11 @@ void releaseHardwareTestMock() { } } +void HardwareTest::setUp() { + assert(gHardwareTestMock != NULL); + gHardwareTestMock->setUp(); +} + void HardwareTest::helpCmd() { assert(gHardwareTestMock != NULL); gHardwareTestMock->helpCmd(); @@ -99,11 +104,6 @@ void HardwareTest::unrecognizedCmd(const char *buffer) { gHardwareTestMock->unrecognizedCmd(buffer); } -void HardwareTest::setUp() { - assert(gHardwareTestMock != NULL); - gHardwareTestMock->setUp(); -} - void HardwareTest::loop() { assert(gHardwareTestMock != NULL); gHardwareTestMock->loop(); diff --git a/test/mocks/hw_test_mock.h b/test/mocks/hw_test_mock.h index d8e60f372..049d78fb0 100644 --- a/test/mocks/hw_test_mock.h +++ b/test/mocks/hw_test_mock.h @@ -29,6 +29,8 @@ class HardwareTestMock : public HardwareTestInterface { public: + MOCK_METHOD0(setUp, void()); + MOCK_METHOD0(loop, void()); MOCK_METHOD0(helpCmd, void()); MOCK_METHOD0(sendCmd, void()); MOCK_METHOD0(beepCmd, void()); @@ -41,8 +43,6 @@ class HardwareTestMock : public HardwareTestInterface { MOCK_METHOD0(stopCmd, void()); MOCK_METHOD0(quitCmd, void()); MOCK_METHOD1(unrecognizedCmd, void(const char *)); - MOCK_METHOD0(setUp, void()); - MOCK_METHOD0(loop, void()); }; HardwareTestMock *hwTestMockInstance(); diff --git a/test/test_hw_test.cpp b/test/test_hw_test.cpp index 0be762648..cff83502d 100644 --- a/test/test_hw_test.cpp +++ b/test/test_hw_test.cpp @@ -23,7 +23,7 @@ #include -#include +//#include #include #include @@ -31,11 +31,11 @@ using ::testing::An; using ::testing::AtLeast; using ::testing::Return; -static char zero[2] = {'0', 0}; -static char two[2] = {'2', 0}; -static char g[2] = {'g', 0}; -static char fAdE[5] = {'f', 'A', 'd', 'E', 0}; -static char sixteen[3] = {'1', '6', 0}; +// static char zero[2] = {'0', 0}; +// static char two[2] = {'2', 0}; +// static char g[2] = {'g', 0}; +// static char fAdE[5] = {'f', 'A', 'd', 'E', 0}; +// static char sixteen[3] = {'1', '6', 0}; // initialize static member HardwareTestInterface *GlobalHardwareTest::m_instance = new HardwareTest(); @@ -45,7 +45,7 @@ class HardwareTestTest : public ::testing::Test { void SetUp() override { arduinoMock = arduinoMockInstance(); serialMock = serialMockInstance(); - serialCommandMock = serialCommandMockInstance(); + // serialCommandMock = serialCommandMockInstance(); knitterMock = knitterMockInstance(); h = new HardwareTest(); } @@ -53,13 +53,13 @@ class HardwareTestTest : public ::testing::Test { void TearDown() override { releaseArduinoMock(); releaseSerialMock(); - releaseSerialCommandMock(); + // releaseSerialCommandMock(); releaseKnitterMock(); } ArduinoMock *arduinoMock; SerialMock *serialMock; - SerialCommandMock *serialCommandMock; + // SerialCommandMock *serialCommandMock; KnitterMock *knitterMock; HardwareTest *h; }; @@ -97,68 +97,68 @@ TEST_F(HardwareTestTest, test_beepCmd) { TEST_F(HardwareTestTest, test_setSingleCmd_fail1) { EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) .Times(AtLeast(1)); - EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(nullptr)); - EXPECT_CALL(*knitterMock, setSolenoid).Times(0); + // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(nullptr)); + // EXPECT_CALL(*knitterMock, setSolenoid).Times(0); h->setSingleCmd(); } TEST_F(HardwareTestTest, test_setSingleCmd_fail2) { EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) .Times(AtLeast(1)); - EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(sixteen)); - EXPECT_CALL(*knitterMock, setSolenoid).Times(0); + // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(sixteen)); + // EXPECT_CALL(*knitterMock, setSolenoid).Times(0); h->setSingleCmd(); } TEST_F(HardwareTestTest, test_setSingleCmd_fail3) { EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) .Times(AtLeast(1)); - EXPECT_CALL(*serialCommandMock, next) - .WillOnce(Return(zero)) - .WillOnce(Return(nullptr)); - EXPECT_CALL(*knitterMock, setSolenoid).Times(0); + // EXPECT_CALL(*serialCommandMock, next) + // .WillOnce(Return(zero)) + // .WillOnce(Return(nullptr)); + // EXPECT_CALL(*knitterMock, setSolenoid).Times(0); h->setSingleCmd(); } TEST_F(HardwareTestTest, test_setSingleCmd_fail4) { EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) .Times(AtLeast(1)); - EXPECT_CALL(*serialCommandMock, next) - .WillOnce(Return(zero)) - .WillOnce(Return(two)); - EXPECT_CALL(*knitterMock, setSolenoid).Times(0); + // EXPECT_CALL(*serialCommandMock, next) + // .WillOnce(Return(zero)) + // .WillOnce(Return(two)); + // EXPECT_CALL(*knitterMock, setSolenoid).Times(0); h->setSingleCmd(); } TEST_F(HardwareTestTest, test_setSingleCmd_success) { EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) .Times(AtLeast(1)); - EXPECT_CALL(*serialCommandMock, next).WillRepeatedly(Return(zero)); - EXPECT_CALL(*knitterMock, setSolenoid); + // EXPECT_CALL(*serialCommandMock, next).WillRepeatedly(Return(zero)); + // EXPECT_CALL(*knitterMock, setSolenoid); h->setSingleCmd(); } TEST_F(HardwareTestTest, test_setAllCmd_fail1) { EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) .Times(AtLeast(1)); - EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(nullptr)); - EXPECT_CALL(*knitterMock, setSolenoids).Times(0); + // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(nullptr)); + // EXPECT_CALL(*knitterMock, setSolenoids).Times(0); h->setAllCmd(); } TEST_F(HardwareTestTest, test_setAllCmd_fail2) { EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) .Times(AtLeast(1)); - EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(g)); - EXPECT_CALL(*knitterMock, setSolenoids).Times(0); + // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(g)); + // EXPECT_CALL(*knitterMock, setSolenoids).Times(0); h->setAllCmd(); } TEST_F(HardwareTestTest, test_setAllCmd_success) { EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) .Times(AtLeast(1)); - EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(fAdE)); - EXPECT_CALL(*knitterMock, setSolenoids); + // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(fAdE)); + // EXPECT_CALL(*knitterMock, setSolenoids); h->setAllCmd(); } From 445dd284b95aa78e11bfccf6739145d9f41f58bb Mon Sep 17 00:00:00 2001 From: Tom Date: Thu, 20 Aug 2020 21:51:22 -0400 Subject: [PATCH 07/17] Add Singleton GlobalKnitter, GlobalCom, and GlobalBeeper classes. --- src/ayab/beeper.cpp | 1 + src/ayab/beeper.h | 41 +- src/ayab/{serial_encoding.cpp => com.cpp} | 179 ++++---- src/ayab/{serial_encoding.h => com.h} | 51 ++- src/ayab/encoders.cpp | 7 +- src/ayab/global_beeper.cpp | 37 ++ src/ayab/global_com.cpp | 53 +++ src/ayab/global_hw_test.h | 63 --- src/ayab/global_knitter.cpp | 82 ++++ .../{global_hw_test.cpp => global_tester.cpp} | 46 +- src/ayab/knitter.cpp | 171 ++++--- src/ayab/knitter.h | 143 +++--- src/ayab/main.cpp | 27 +- src/ayab/solenoids.cpp | 21 +- src/ayab/solenoids.h | 7 +- src/ayab/{hw_test.cpp => tester.cpp} | 210 +++++---- src/ayab/{hw_test.h => tester.h} | 89 ++-- test/CMakeLists.txt | 35 +- test/mocks/beeper_mock.h | 7 +- test/mocks/com_mock.cpp | 70 +++ .../{serial_encoding_mock.h => com_mock.h} | 17 +- test/mocks/hw_test_mock.cpp | 110 ----- test/mocks/knitter_mock.cpp | 59 +-- test/mocks/knitter_mock.h | 19 +- test/mocks/serial_encoding_mock.cpp | 68 --- test/mocks/tester_mock.cpp | 115 +++++ test/mocks/{hw_test_mock.h => tester_mock.h} | 17 +- test/test.sh | 4 +- test/test_all.cpp | 19 + test/test_beeper.cpp | 10 +- test/test_boards.cpp | 48 ++ ...{test_serial_encoding.cpp => test_com.cpp} | 148 +++--- test/test_hw_test.cpp | 272 ----------- test/test_knitter.cpp | 431 +++++++----------- test/test_solenoids.cpp | 25 +- test/test_tester.cpp | 280 ++++++++++++ 36 files changed, 1635 insertions(+), 1347 deletions(-) rename src/ayab/{serial_encoding.cpp => com.cpp} (77%) rename src/ayab/{serial_encoding.h => com.h} (60%) create mode 100644 src/ayab/global_beeper.cpp create mode 100644 src/ayab/global_com.cpp delete mode 100644 src/ayab/global_hw_test.h create mode 100644 src/ayab/global_knitter.cpp rename src/ayab/{global_hw_test.cpp => global_tester.cpp} (67%) rename src/ayab/{hw_test.cpp => tester.cpp} (54%) rename src/ayab/{hw_test.h => tester.h} (56%) create mode 100644 test/mocks/com_mock.cpp rename test/mocks/{serial_encoding_mock.h => com_mock.h} (83%) delete mode 100644 test/mocks/hw_test_mock.cpp delete mode 100644 test/mocks/serial_encoding_mock.cpp create mode 100644 test/mocks/tester_mock.cpp rename test/mocks/{hw_test_mock.h => tester_mock.h} (84%) create mode 100644 test/test_boards.cpp rename test/{test_serial_encoding.cpp => test_com.cpp} (62%) delete mode 100644 test/test_hw_test.cpp create mode 100644 test/test_tester.cpp diff --git a/src/ayab/beeper.cpp b/src/ayab/beeper.cpp index f2cc02dad..680179b64 100644 --- a/src/ayab/beeper.cpp +++ b/src/ayab/beeper.cpp @@ -1,5 +1,6 @@ /*! * \file beeper.cpp + * \brief Singleton class containing methods for beeper. * * This file is part of AYAB. * diff --git a/src/ayab/beeper.h b/src/ayab/beeper.h index 27115ba08..f23ed3d7a 100644 --- a/src/ayab/beeper.h +++ b/src/ayab/beeper.h @@ -36,19 +36,46 @@ constexpr uint8_t BEEP_ON_DUTY = 0U; constexpr uint8_t BEEP_OFF_DUTY = 20U; constexpr uint8_t BEEP_NO_DUTY = 255U; -/*! - * Class to actuate a beeper connected to PIEZO_PIN - */ -class Beeper { +class BeeperInterface { +public: + virtual ~BeeperInterface(){}; + + // any methods that need to be mocked should go here + virtual void ready() = 0; + virtual void finishedLine() = 0; + virtual void endWork() = 0; +}; + +// Container class for the static methods that implement the serial API. +// Dependency injection is enabled using a pointer to a global instance of +// either `Beeper` or `BeeperMock`, both of which classes implement the +// pure virtual methods of `BeeperInterface`. + +class GlobalBeeper final { +private: + // singleton class so private constructor is appropriate + GlobalBeeper() = default; + public: - Beeper() = default; + // pointer to global instance whose methods are implemented + static BeeperInterface *m_instance; static void ready(); static void finishedLine(); static void endWork(); +}; + +/*! + * Class to actuate a beeper connected to PIEZO_PIN + */ +class Beeper : public BeeperInterface { +public: + void ready(); + void finishedLine(); + void endWork(); private: - static void beep(uint8_t length); + void beep(uint8_t length); }; -#endif // BEEPER_H_ +#endif // BEEPER_H_ diff --git a/src/ayab/serial_encoding.cpp b/src/ayab/com.cpp similarity index 77% rename from src/ayab/serial_encoding.cpp rename to src/ayab/com.cpp index eb30730a1..87d4e6055 100644 --- a/src/ayab/serial_encoding.cpp +++ b/src/ayab/com.cpp @@ -1,5 +1,6 @@ /*! - * \file serial_encoding.cpp + * \file com.cpp + * \brief Singleton class containing methods for serial communication. * * This file is part of AYAB. * @@ -21,24 +22,9 @@ * http://ayab-knitting.com */ -#include "serial_encoding.h" +#include "com.h" #include "knitter.h" -extern Knitter *knitter; - -#ifndef AYAB_TESTS -/*! - * \brief Wrapper for knitter's onPacketReceived. - * - * This is needed since a non-static method cannot be - * passed to _setPacketHandler_, and the only global variable - * is knitter. - */ -static void gOnPacketReceived(const uint8_t *buffer, size_t size) { - knitter->onPacketReceived(buffer, size); -} -#endif // AYAB_TESTS - #ifdef AYAB_ENABLE_CRC /*! * \brief Calculate CRC8 of a buffer @@ -70,6 +56,72 @@ static uint8_t CRC8(const uint8_t *buffer, size_t len) { } #endif +void Com::init() { + m_packetSerial.begin(SERIAL_BAUDRATE); +#ifndef AYAB_TESTS + m_packetSerial.setPacketHandler(GlobalCom::onPacketReceived); +#endif // AYAB_TESTS +} + +/*! + * \brief Callback for PacketSerial. + */ +void Com::onPacketReceived(const uint8_t *buffer, size_t size) { + switch (buffer[0]) { + case reqStart_msgid: + h_reqStart(buffer, size); + break; + + case cnfLine_msgid: + h_cnfLine(buffer, size); + break; + + case reqInfo_msgid: + h_reqInfo(); + break; + + case reqTest_msgid: + h_reqTest(buffer, size); + break; + + default: + h_unrecognized(); + break; + } +} + +void Com::update() { + m_packetSerial.update(); +} + +void Com::send(uint8_t *payload, size_t length) { + // TODO(TP): insert a workaround for hardware test code + /* + #ifdef AYAB_HW_TEST + Serial.print("Sent: "); + for (uint8_t i = 0; i < length; ++i) { + Serial.print(payload[i]); + } + Serial.print(", Encoded as: "); + #endif + */ + m_packetSerial.send(payload, length); +} + +// send initial msgid followed by null-terminated string +void Com::sendMsg(AYAB_API_t id, const char *msg) { + uint8_t length = 0; + msgBuffer[length++] = static_cast(id); + while (*msg) { + msgBuffer[length++] = static_cast(*msg++); + } + m_packetSerial.send(msgBuffer, length); +} + +void Com::sendMsg(AYAB_API_t id, char *msg) { + sendMsg(id, static_cast(msg)); +} + // Serial command handling /*! @@ -78,7 +130,7 @@ static uint8_t CRC8(const uint8_t *buffer, size_t len) { * \todo sl: Assert size? Handle error? * \todo TP: Handle CRC-8 error? */ -void SerialEncoding::h_reqStart(const uint8_t *buffer, size_t size) { +void Com::h_reqStart(const uint8_t *buffer, size_t size) { #ifdef AYAB_ENABLE_CRC if (size < 6U) { @@ -113,8 +165,8 @@ void SerialEncoding::h_reqStart(const uint8_t *buffer, size_t size) { } bool success = - knitter->startOperation(machineType, startNeedle, stopNeedle, lineBuffer, - continuousReportingEnabled); + GlobalKnitter::startOperation(machineType, startNeedle, stopNeedle, + lineBuffer, continuousReportingEnabled); uint8_t payload[2]; payload[0] = cnfStart_msgid; @@ -128,8 +180,9 @@ void SerialEncoding::h_reqStart(const uint8_t *buffer, size_t size) { * \todo sl: Handle CRC-8 error? * \todo sl: Assert size? Handle error? */ -void SerialEncoding::h_cnfLine(const uint8_t *buffer, size_t size) { - uint8_t lenLineBuffer = LINE_BUFFER_LEN[knitter->getMachineType()]; +void Com::h_cnfLine(const uint8_t *buffer, size_t size) { + uint8_t m = static_cast(GlobalKnitter::getMachineType()); + uint8_t lenLineBuffer = LINE_BUFFER_LEN[m]; if (size < lenLineBuffer + 5U) { // message is too short // TODO(sl): handle error? @@ -154,16 +207,16 @@ void SerialEncoding::h_cnfLine(const uint8_t *buffer, size_t size) { } #endif - if (knitter->setNextLine(lineNumber)) { + if (GlobalKnitter::setNextLine(lineNumber)) { // Line was accepted bool flagLastLine = bitRead(flags, 0U); if (flagLastLine) { - knitter->setLastLine(); + GlobalKnitter::setLastLine(); } } } -void SerialEncoding::h_reqInfo() { +void Com::h_reqInfo() { uint8_t payload[4]; payload[0] = cnfInfo_msgid; payload[1] = API_VERSION; @@ -177,15 +230,15 @@ void SerialEncoding::h_reqInfo() { * * \todo TP: Assert size? Handle error? */ -void SerialEncoding::h_reqTest(const uint8_t *buffer, size_t size) { - if (size < 1U) { +void Com::h_reqTest(const uint8_t *buffer, size_t size) { + if (size < 2U) { // message is too short // TODO(TP): handle error? return; } Machine_t machineType = static_cast(buffer[0]); - bool success = knitter->startTest(machineType); + bool success = GlobalKnitter::startTest(machineType); uint8_t payload[2]; payload[0] = cnfTest_msgid; @@ -193,72 +246,8 @@ void SerialEncoding::h_reqTest(const uint8_t *buffer, size_t size) { send(payload, 2); } -static void h_unrecognized() { +// GCOVR_EXCL_START +void Com::h_unrecognized() { // do nothing } - -/*! - * \brief Callback for PacketSerial. - */ -void SerialEncoding::onPacketReceived(const uint8_t *buffer, size_t size) { - switch (buffer[0]) { - case reqStart_msgid: - h_reqStart(buffer, size); - break; - - case cnfLine_msgid: - h_cnfLine(buffer, size); - break; - - case reqInfo_msgid: - h_reqInfo(); - break; - - case reqTest_msgid: - h_reqTest(buffer, size); - break; - - default: - h_unrecognized(); - break; - } -} - -SerialEncoding::SerialEncoding() { - m_packetSerial.begin(SERIAL_BAUDRATE); -#ifndef AYAB_TESTS - m_packetSerial.setPacketHandler(gOnPacketReceived); -#endif // AYAB_TESTS -} - -void SerialEncoding::update() { - m_packetSerial.update(); -} - -void SerialEncoding::send(uint8_t *payload, size_t length) { - // TODO(TP): insert a workaround for hardware test code - /* - #ifdef AYAB_HW_TEST - Serial.print("Sent: "); - for (uint8_t i = 0; i < length; ++i) { - Serial.print(payload[i]); - } - Serial.print(", Encoded as: "); - #endif - */ - m_packetSerial.send(payload, length); -} - -// send initial msgid followed by null-terminated string -void SerialEncoding::sendMsg(AYAB_API_t id, const char *msg) { - uint8_t length = 0; - msgBuffer[length++] = static_cast(id); - while (*msg) { - msgBuffer[length++] = static_cast(*msg++); - } - m_packetSerial.send(msgBuffer, length); -} - -void SerialEncoding::sendMsg(AYAB_API_t id, char *msg) { - sendMsg(id, static_cast(msg)); -} +// GCOVR_EXCL_STOP diff --git a/src/ayab/serial_encoding.h b/src/ayab/com.h similarity index 60% rename from src/ayab/serial_encoding.h rename to src/ayab/com.h index 811c2caba..269b651dc 100644 --- a/src/ayab/serial_encoding.h +++ b/src/ayab/com.h @@ -1,5 +1,5 @@ /*! - * \file serial_encoding.h + * \file com.h * * This file is part of AYAB. * @@ -21,8 +21,8 @@ * http://ayab-knitting.com */ -#ifndef SERIAL_ENCODING_H_ -#define SERIAL_ENCODING_H_ +#ifndef COM_H_ +#define COM_H_ #include #include @@ -54,9 +54,47 @@ enum AYAB_API { }; using AYAB_API_t = enum AYAB_API; -class SerialEncoding { +class ComInterface { public: - SerialEncoding(); + virtual ~ComInterface(){}; + + // any methods that need to be mocked should go here + virtual void init() = 0; + virtual void update() = 0; + virtual void send(uint8_t *payload, size_t length) = 0; + virtual void sendMsg(AYAB_API_t id, const char *msg) = 0; + virtual void sendMsg(AYAB_API_t id, char *msg) = 0; + virtual void onPacketReceived(const uint8_t *buffer, size_t size) = 0; +}; + +// Container class for the static methods that implement the serial API. +// Dependency injection is enabled using a pointer to a global instance of +// either `Com` or `ComMock`, both of which classes implement the +// pure virtual methods of `ComInterface`. + +class GlobalCom final { +private: + // singleton class so private constructor is appropriate + GlobalCom() = default; + +public: + // pointer to global instance whose methods are implemented + static ComInterface *m_instance; + + static void init(); + static void update(); + static void send(uint8_t *payload, size_t length); + static void sendMsg(AYAB_API_t id, const char *msg); + static void sendMsg(AYAB_API_t id, char *msg); + static void onPacketReceived(const uint8_t *buffer, size_t size); + +private: + static SLIPPacketSerial m_packetSerial; +}; + +class Com : public ComInterface { +public: + void init(); void update(); void send(uint8_t *payload, size_t length); void sendMsg(AYAB_API_t id, const char *msg); @@ -72,6 +110,7 @@ class SerialEncoding { void h_cnfLine(const uint8_t *buffer, size_t size); void h_reqInfo(); void h_reqTest(const uint8_t *buffer, size_t size); + void h_unrecognized(); }; -#endif // SERIAL_ENCODING_H_ +#endif // COM_H_ diff --git a/src/ayab/encoders.cpp b/src/ayab/encoders.cpp index e09530031..f2d3c211f 100644 --- a/src/ayab/encoders.cpp +++ b/src/ayab/encoders.cpp @@ -1,5 +1,6 @@ /*! * \file encoders.cpp + * \brief Class containing method governing encoders. * * This file is part of AYAB. * @@ -30,7 +31,7 @@ * \brief Service encoder A interrupt routine. * * Determines edge of signal and dispatches to private rising/falling functions. - * m_machineType assumed valid. + * `m_machineType` assumed valid. */ void Encoders::encA_interrupt() { m_hallActive = NoDirection; @@ -115,7 +116,7 @@ uint16_t Encoders::getHallValue(Direction_t pSensor) { * * Called when encoder pin A is rising. * Must execute as fast as possible. - * Bounds on m_machineType not checked. + * Bounds on `m_machineType` not checked. */ void Encoders::encA_rising() { // Direction only decided on rising edge of encoder A @@ -157,7 +158,7 @@ void Encoders::encA_rising() { * * Called when encoder pin A is falling. * Must execute as fast as possible. - * Bounds on m_machineType not checked. + * Bounds on `m_machineType` not checked. */ void Encoders::encA_falling() { // Update carriage position diff --git a/src/ayab/global_beeper.cpp b/src/ayab/global_beeper.cpp new file mode 100644 index 000000000..4a1f2f0a3 --- /dev/null +++ b/src/ayab/global_beeper.cpp @@ -0,0 +1,37 @@ +/*! + * \file global_beeper.cpp + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#include "beeper.h" + +// static member functions + +void GlobalBeeper::ready() { + m_instance->ready(); +} + +void GlobalBeeper::finishedLine() { + m_instance->finishedLine(); +} + +void GlobalBeeper::endWork() { + m_instance->endWork(); +} diff --git a/src/ayab/global_com.cpp b/src/ayab/global_com.cpp new file mode 100644 index 000000000..5354d0032 --- /dev/null +++ b/src/ayab/global_com.cpp @@ -0,0 +1,53 @@ +/*! + * \file global_com.cpp + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#include "com.h" + +// static member functions + +// GCOVR_EXCL_START +void GlobalCom::init() { + m_instance->init(); +} +// GCOVR_EXCL_STOP + +void GlobalCom::update() { + m_instance->update(); +} + +void GlobalCom::send(uint8_t *payload, size_t length) { + m_instance->send(payload, length); +} + +void GlobalCom::sendMsg(AYAB_API_t id, const char *msg) { + m_instance->sendMsg(id, msg); +} + +void GlobalCom::sendMsg(AYAB_API_t id, char *msg) { + m_instance->sendMsg(id, msg); +} + +// GCOVR_EXCL_START +void GlobalCom::onPacketReceived(const uint8_t *buffer, size_t size) { + m_instance->onPacketReceived(buffer, size); +} +// GCOVR_EXCL_STOP diff --git a/src/ayab/global_hw_test.h b/src/ayab/global_hw_test.h deleted file mode 100644 index d89434379..000000000 --- a/src/ayab/global_hw_test.h +++ /dev/null @@ -1,63 +0,0 @@ -/*! - * \file global_hw_test.h - * This file is part of AYAB. - * - * AYAB is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * AYAB is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with AYAB. If not, see . - * - * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller - * Modified Work Copyright 2020 Sturla Lange, Tom Price - * http://ayab-knitting.com - */ - -#ifndef GLOBAL_HW_TEST_H_ -#define GLOBAL_HW_TEST_H_ - -#include "hw_test.h" - -// This is a container for the static methods called back by SerialCommand. -// Dependency injection is enabled using a pointer to a global instance of -// either HardwareTest or HardwareTestMock, both of which classes implement -// the pure virtual methods of HardwareTestInterface. - -class GlobalHardwareTest { -public: - static void setUp(); - static void loop(); -#ifndef AYAB_TESTS - static void encoderAChange(); -#endif - - // pointer to global instance whose methods are implemented - // in the static methods belonging to this class - static HardwareTestInterface *m_instance; - - /* - static void helpCmd(); - static void sendCmd(); - static void beepCmd(); - static void setSingleCmd(); - static void setAllCmd(); - static void readEOLsensorsCmd(); - static void readEncodersCmd(); - static void autoReadCmd(); - static void autoTestCmd(); - static void stopCmd(); - static void quitCmd(); - static void unrecognizedCmd(const char *buffer); - */ -}; - -extern GlobalHardwareTest *hwTest; - -#endif // GLOBAL_HW_TEST_H_ diff --git a/src/ayab/global_knitter.cpp b/src/ayab/global_knitter.cpp new file mode 100644 index 000000000..62c262e67 --- /dev/null +++ b/src/ayab/global_knitter.cpp @@ -0,0 +1,82 @@ +/*! + * \file global_knitter.cpp + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#include "knitter.h" + +// static member functions + +void GlobalKnitter::init() { + m_instance->init(); +} + +void GlobalKnitter::fsm() { + m_instance->fsm(); +} + +void GlobalKnitter::setUpInterrupt() { + m_instance->setUpInterrupt(); +} + +#ifndef AYAB_TESTS +void GlobalKnitter::isr() { + m_instance->isr(); +} +#endif + +bool GlobalKnitter::startOperation(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled) { + return m_instance->startOperation(machineType, startNeedle, stopNeedle, + pattern_start, continuousReportingEnabled); +} + +bool GlobalKnitter::startTest(Machine_t machineType) { + return m_instance->startTest(machineType); +} + +uint8_t GlobalKnitter::getStartOffset(const Direction_t direction) { + return m_instance->getStartOffset(direction); +} + +Machine_t GlobalKnitter::getMachineType() { + return m_instance->getMachineType(); +} + +void GlobalKnitter::setSolenoids(uint16_t state) { + m_instance->setSolenoids(state); +} + +bool GlobalKnitter::setNextLine(uint8_t lineNumber) { + return m_instance->setNextLine(lineNumber); +} + +void GlobalKnitter::setLastLine() { + m_instance->setLastLine(); +} + +void GlobalKnitter::setMachineType(Machine_t machineType) { + m_instance->setMachineType(machineType); +} + +void GlobalKnitter::setState(OpState_t state) { + m_instance->setState(state); +} diff --git a/src/ayab/global_hw_test.cpp b/src/ayab/global_tester.cpp similarity index 67% rename from src/ayab/global_hw_test.cpp rename to src/ayab/global_tester.cpp index 60dafa75f..4e0fa4958 100644 --- a/src/ayab/global_hw_test.cpp +++ b/src/ayab/global_tester.cpp @@ -1,5 +1,5 @@ /*! - * \file global_hw_test.cpp + * \file global_tester.cpp * This file is part of AYAB. * * AYAB is free software: you can redistribute it and/or modify @@ -20,70 +20,72 @@ * http://ayab-knitting.com */ -#include "global_hw_test.h" +#include "tester.h" // static member functions -void GlobalHardwareTest::setUp() { +void GlobalTester::setUp() { m_instance->setUp(); } -void GlobalHardwareTest::loop() { +void GlobalTester::loop() { m_instance->loop(); } -#ifndef AYAB_TESTS -void GlobalHardwareTest::encoderAChange() { - m_instance->encoderAChange(); +bool GlobalTester::getQuitFlag() { + return m_instance->getQuitFlag(); } -#endif // AYAB_TESTS -/* -void GlobalHardwareTest::helpCmd() { +void GlobalTester::helpCmd() { m_instance->helpCmd(); } -void GlobalHardwareTest::sendCmd() { +void GlobalTester::sendCmd() { m_instance->sendCmd(); } -void GlobalHardwareTest::beepCmd() { +void GlobalTester::beepCmd() { m_instance->beepCmd(); } -void GlobalHardwareTest::setSingleCmd() { +void GlobalTester::setSingleCmd() { m_instance->setSingleCmd(); } -void GlobalHardwareTest::setAllCmd() { +void GlobalTester::setAllCmd() { m_instance->setAllCmd(); } -void GlobalHardwareTest::readEOLsensorsCmd() { +void GlobalTester::readEOLsensorsCmd() { m_instance->readEOLsensorsCmd(); } -void GlobalHardwareTest::readEncodersCmd() { +void GlobalTester::readEncodersCmd() { m_instance->readEncodersCmd(); } -void GlobalHardwareTest::autoReadCmd() { +void GlobalTester::autoReadCmd() { m_instance->autoReadCmd(); } -void GlobalHardwareTest::autoTestCmd() { +void GlobalTester::autoTestCmd() { m_instance->autoTestCmd(); } -void GlobalHardwareTest::stopCmd() { +void GlobalTester::stopCmd() { m_instance->stopCmd(); } -void GlobalHardwareTest::quitCmd() { +void GlobalTester::quitCmd() { m_instance->quitCmd(); } -void GlobalHardwareTest::unrecognizedCmd(const char *buffer) { +void GlobalTester::unrecognizedCmd(const char *buffer) { m_instance->unrecognizedCmd(buffer); } -*/ + +#ifndef AYAB_TESTS +void GlobalTester::encoderAChange() { + m_instance->encoderAChange(); +} +#endif // AYAB_TESTS diff --git a/src/ayab/knitter.cpp b/src/ayab/knitter.cpp index dceec93f0..5c05dd4b3 100644 --- a/src/ayab/knitter.cpp +++ b/src/ayab/knitter.cpp @@ -1,5 +1,7 @@ /*! * \file knitter.cpp + * \brief Singleton class containing methods for the finite state machine + * that co-ordinates the AYAB firmware. * * This file is part of AYAB. * @@ -23,9 +25,11 @@ #include +#include "beeper.h" #include "board.h" -#include "global_hw_test.h" +#include "com.h" #include "knitter.h" +#include "tester.h" #ifdef CLANG_TIDY // clang-tidy doesn't find these macros for some reason, @@ -34,26 +38,12 @@ constexpr uint8_t UINT8_MAX = 0xFFU; constexpr uint16_t UINT16_MAX = 0xFFFFU; #endif -extern Knitter *knitter; - -#ifndef AYAB_TESTS -/*! - * \brief Wrapper for knitter's isr. - * - * This is needed since a non-static method cannot be - * passed to _attachInterrupt_. - */ -static void isr_wrapper() { - knitter->isr(); -} -#endif // AYAB_TESTS - /*! * \brief Knitter constructor. * * Initializes the solenoids as well as pins and interrupts. */ -Knitter::Knitter() : m_beeper(), m_serial_encoding() { +void Knitter::init() { pinMode(ENC_PIN_A, INPUT); pinMode(ENC_PIN_B, INPUT); pinMode(ENC_PIN_C, INPUT); @@ -68,28 +58,37 @@ Knitter::Knitter() : m_beeper(), m_serial_encoding() { m_solenoids.init(); setUpInterrupt(); + + // explicitly initialize members + m_opState = s_init; + m_machineType = NoMachine; + m_startNeedle = 0U; + m_stopNeedle = 0U; + m_lineBuffer = nullptr; + m_continuousReportingEnabled = false; + m_position = 0U; + m_direction = NoDirection; + m_hallActive = NoDirection; + m_beltshift = Unknown; + m_carriage = NoCarriage; + m_lineRequested = false; + m_currentLineNumber = 0U; + m_lastLineFlag = false; + m_sOldPosition = 0U; + m_firstRun = true; + m_workedOnLine = false; + m_solenoidToSet = 0U; + m_pixelToSet = 0U; } void Knitter::setUpInterrupt() { // (re-)attach ENC_PIN_A(=2), interrupt #0 detachInterrupt(0); #ifndef AYAB_TESTS - attachInterrupt(0, isr_wrapper, CHANGE); + attachInterrupt(0, GlobalKnitter::isr, CHANGE); #endif // AYAB_TESTS } -void Knitter::send(uint8_t *payload, size_t length) { - m_serial_encoding.send(payload, length); -} - -void Knitter::sendMsg(AYAB_API_t id, const char *msg) { - m_serial_encoding.sendMsg(id, msg); -} - -void Knitter::sendMsg(AYAB_API_t id, char *msg) { - m_serial_encoding.sendMsg(id, msg); -} - /*! * \brief Interrupt service routine. * @@ -133,7 +132,7 @@ void Knitter::fsm() { default: break; } - m_serial_encoding.update(); + GlobalCom::update(); } /*! @@ -170,15 +169,13 @@ bool Knitter::startOperation(Machine_t machineType, uint8_t startNeedle, // proceed to next state m_opState = s_operate; - Beeper::ready(); - - /* m_lastLinesCountdown = 2; */ + GlobalBeeper::ready(); // Attaching ENC_PIN_A, Interrupt #0 // This interrupt cannot be enabled until // the machine type has been validated. /* - // `digitalPinToInterrupt` macro not backported until Arduino !DE v.1.0.6 + // `digitalPinToInterrupt` macro not backported until Arduino IDE v.1.0.6 attachInterrupt(digitalPinToInterrupt(ENC_PIN_A), isr_wrapper, CHANGE); */ setUpInterrupt(); @@ -192,19 +189,37 @@ bool Knitter::startTest(Machine_t machineType) { if (s_init == m_opState || s_ready == m_opState) { m_opState = s_test; m_machineType = machineType; - GlobalHardwareTest::setUp(); + GlobalTester::setUp(); success = true; } return success; } +uint8_t Knitter::getStartOffset(const Direction_t direction) { + if ((direction == NoDirection) || (direction >= NUM_DIRECTIONS) || + (m_carriage == NoCarriage) || (m_carriage >= NUM_CARRIAGES) || + (m_machineType == NoMachine) || (m_machineType >= NUM_MACHINES)) { + // TODO(TP): return error state + return 0U; + } + return START_OFFSET[m_machineType][direction][m_carriage]; +} + +Machine_t Knitter::getMachineType() { + return m_machineType; +} + +void Knitter::setSolenoids(uint16_t state) { + m_solenoids.setSolenoids(state); +} + bool Knitter::setNextLine(uint8_t lineNumber) { bool success = false; if (m_lineRequested) { // FIXME: Is there even a need for a new line? if (lineNumber == m_currentLineNumber) { m_lineRequested = false; - Beeper::finishedLine(); + GlobalBeeper::finishedLine(); success = true; } else { // line numbers didn't match -> request again @@ -219,6 +234,14 @@ void Knitter::setLastLine() { m_lastLineFlag = true; } +void Knitter::setMachineType(Machine_t machineType) { + m_machineType = machineType; +} + +void Knitter::setState(OpState_t state) { + m_opState = state; +} + // private methods void Knitter::state_init() { @@ -254,7 +277,7 @@ void Knitter::state_operate() { m_firstRun = false; // TODO(who?): optimize delay for various Arduino models delay(START_OPERATION_DELAY); - Beeper::finishedLine(); + GlobalBeeper::finishedLine(); reqLine(++m_currentLineNumber); } @@ -328,21 +351,6 @@ void Knitter::state_operate() { #endif // DBG_NOMACHINE } -void Knitter::stopOperation() { - Beeper::endWork(); - m_opState = s_ready; - - m_solenoids.setSolenoids(SOLENOIDS_BITMASK); - Beeper::finishedLine(); - - // detaching ENC_PIN_A, Interrupt #0 - /* - // `digitalPinToInterrupt` macro not backported until Arduino !DE v.1.0.6 - detachInterrupt(digitalPinToInterrupt(ENC_PIN_A)); - */ - detachInterrupt(0); -} - void Knitter::state_test() { if (m_sOldPosition != m_position) { // only act if there is an actual change of position @@ -351,16 +359,12 @@ void Knitter::state_test() { calculatePixelAndSolenoid(); indState(); } - GlobalHardwareTest::loop(); - if (m_quitFlag) { + GlobalTester::loop(); + if (GlobalTester::getQuitFlag()) { m_opState = s_ready; } } -void Knitter::setQuitFlag(bool flag) { - m_quitFlag = flag; -} - bool Knitter::calculatePixelAndSolenoid() { uint8_t startOffset = 0; bool success = true; @@ -422,30 +426,12 @@ bool Knitter::calculatePixelAndSolenoid() { return success; } -void Knitter::setSolenoids(uint16_t state) { - m_solenoids.setSolenoids(state); -} - -void Knitter::setSolenoid(uint8_t solenoid, uint8_t state) { - m_solenoids.setSolenoid(solenoid, state); -} - -uint8_t Knitter::getStartOffset(const Direction_t direction) const { - if ((direction == NoDirection) || (direction >= NUM_DIRECTIONS) || - (m_carriage == NoCarriage) || (m_carriage >= NUM_CARRIAGES) || - (m_machineType == NoMachine) || (m_machineType >= NUM_MACHINES)) { - // TODO(TP): return error state - return 0U; - } - return START_OFFSET[m_machineType][direction][m_carriage]; -} - void Knitter::reqLine(const uint8_t lineNumber) { uint8_t payload[REQLINE_LEN] = { reqLine_msgid, lineNumber, }; - send(static_cast(payload), REQLINE_LEN); + GlobalCom::send(static_cast(payload), REQLINE_LEN); m_lineRequested = true; } @@ -464,27 +450,24 @@ void Knitter::indState(const bool initState) { static_cast(m_position), static_cast(m_encoders.getDirection()), }; - send(static_cast(payload), INDSTATE_LEN); -} - -void Knitter::onPacketReceived(const uint8_t *buffer, size_t size) { - m_serial_encoding.onPacketReceived(buffer, size); + GlobalCom::send(static_cast(payload), INDSTATE_LEN); } -OpState_t Knitter::getState() const { - return m_opState; -} +void Knitter::stopOperation() { + GlobalBeeper::endWork(); + m_opState = s_ready; -Machine_t Knitter::getMachineType() const { - return m_machineType; -} + m_solenoids.setSolenoids(SOLENOIDS_BITMASK); + GlobalBeeper::finishedLine(); -void Knitter::setMachineType(Machine_t machineType) { - m_machineType = machineType; + // detaching ENC_PIN_A, Interrupt #0 + /* + // `digitalPinToInterrupt` macro not backported until Arduino !DE v.1.0.6 + detachInterrupt(digitalPinToInterrupt(ENC_PIN_A)); + */ + detachInterrupt(0); } -// for testing purposes only - -void Knitter::setState(OpState_t state) { - m_opState = state; +OpState_t Knitter::getState() const { + return m_opState; } diff --git a/src/ayab/knitter.h b/src/ayab/knitter.h index d81a4e8c3..0242b0756 100644 --- a/src/ayab/knitter.h +++ b/src/ayab/knitter.h @@ -25,10 +25,10 @@ #define KNITTER_H_ #include "beeper.h" +#include "com.h" #include "encoders.h" -#include "hw_test.h" -#include "serial_encoding.h" #include "solenoids.h" +#include "tester.h" // API constants constexpr uint8_t INDSTATE_LEN = 9U; @@ -37,68 +37,117 @@ constexpr uint8_t REQLINE_LEN = 2U; enum OpState { s_init, s_ready, s_operate, s_test }; using OpState_t = enum OpState; -/*! - * \brief The knitting finite state machine. - * - * Orchestrates the beeper, hall sensors, - * encoders, and serial communication. - */ -class Knitter { +class KnitterInterface { +public: + virtual ~KnitterInterface(){}; + + // any methods that need to be mocked should go here + virtual void init() = 0; + virtual void fsm() = 0; + virtual void setUpInterrupt() = 0; + virtual void isr() = 0; + virtual bool startOperation(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled) = 0; + virtual bool startTest(Machine_t machineType) = 0; + virtual uint8_t getStartOffset(const Direction_t direction) = 0; + virtual Machine_t getMachineType() = 0; + virtual void setSolenoids(uint16_t state) = 0; + virtual bool setNextLine(uint8_t lineNumber) = 0; + virtual void setLastLine() = 0; + virtual void setMachineType(Machine_t) = 0; + virtual void setState(OpState_t state) = 0; +}; + +// Singleton container class for static methods. +// Dependency injection is enabled using a pointer +// to a global instance of either `Knitter` or `KnitterMock` +// both of which classes implement the pure virtual methods +// of the `KnitterInterface` class. + +class GlobalKnitter final { +private: + // singleton class so private constructor is appropriate + GlobalKnitter() = default; + +public: + // pointer to global instance whose methods are implemented + static KnitterInterface *m_instance; + + static void init(); + static void fsm(); + static void setUpInterrupt(); +#ifndef AYAB_TESTS + static void isr(); +#endif + static bool startOperation(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled); + static bool startTest(Machine_t machineType); + static uint8_t getStartOffset(const Direction_t direction); + static Machine_t getMachineType(); + static void setSolenoids(uint16_t state); + static bool setNextLine(uint8_t lineNumber); + static void setLastLine(); + static void setMachineType(Machine_t); + static void setState(OpState_t state); +}; + +class Knitter : public KnitterInterface { #if AYAB_TESTS - FRIEND_TEST(KnitterTest, test_constructor); - FRIEND_TEST(KnitterTest, test_fsm_default_case); + FRIEND_TEST(KnitterTest, test_init); FRIEND_TEST(KnitterTest, test_getStartOffset); FRIEND_TEST(KnitterTest, test_operate_lastline); FRIEND_TEST(KnitterTest, test_operate_lastline_and_no_req); - FRIEND_TEST(KnitterTest, test_quit_hw_test); + FRIEND_TEST(KnitterTest, test_fsm_default_case); + FRIEND_TEST(KnitterTest, test_fsm_init_LL); + FRIEND_TEST(KnitterTest, test_fsm_init_RR); + FRIEND_TEST(KnitterTest, test_fsm_init_RL); + FRIEND_TEST(KnitterTest, test_fsm_ready); + FRIEND_TEST(KnitterTest, test_fsm_test); + FRIEND_TEST(KnitterTest, test_fsm_test_quit); + FRIEND_TEST(KnitterTest, test_startOperation_NoMachine); + FRIEND_TEST(KnitterTest, test_startOperation_notReady); + FRIEND_TEST(KnitterTest, test_startTest); + FRIEND_TEST(KnitterTest, test_startTest_in_operation); + FRIEND_TEST(KnitterTest, test_setNextLine); #endif - friend class HardwareTest; + friend class Tester; public: - Knitter(); - - void isr(); + void init(); void fsm(); void setUpInterrupt(); + void isr(); bool startOperation(Machine_t machineType, uint8_t startNeedle, uint8_t stopNeedle, uint8_t *pattern_start, bool continuousReportingEnabled); - - void send(uint8_t *payload, size_t length); - void sendMsg(AYAB_API_t id, const char *msg); - void sendMsg(AYAB_API_t id, char *msg); - void onPacketReceived(const uint8_t *buffer, size_t size); - - OpState_t getState() const; - void state_init(); - static void state_ready(); - void state_operate(); - void state_test(); - void setQuitFlag(bool flag); - bool startTest(Machine_t machineType); - - uint8_t getStartOffset(const Direction_t direction) const; - Machine_t getMachineType() const; - + uint8_t getStartOffset(const Direction_t direction); + Machine_t getMachineType(); + void setSolenoids(uint16_t state); bool setNextLine(uint8_t lineNumber); void setLastLine(); - - // for testing purposes only void setMachineType(Machine_t); void setState(OpState_t state); - void setSolenoids(uint16_t state); - void setSolenoid(uint8_t solenoid, uint8_t state); private: - Solenoids m_solenoids; + void state_init(); + static void state_ready(); + void state_operate(); + void state_test(); + + bool calculatePixelAndSolenoid(); + void reqLine(uint8_t lineNumber); + void indState(bool initState = false); + void stopOperation(); + OpState_t getState() const; + Encoders m_encoders; - Beeper m_beeper; - SerialEncoding m_serial_encoding; + Solenoids m_solenoids; // machine state - OpState_t m_opState = s_init; - bool m_quitFlag = false; + OpState_t m_opState; // job parameters Machine_t m_machineType = NoMachine; @@ -128,16 +177,6 @@ class Knitter { // resulting needle data uint8_t m_solenoidToSet = 0U; uint8_t m_pixelToSet = 0U; - - bool calculatePixelAndSolenoid(); - - void reqLine(uint8_t lineNumber); - void indState(bool initState = false); - - void stopOperation(); - /* uint8_t m_lastLinesCountdown = 0U; */ }; -extern Knitter *knitter; - #endif // KNITTER_H_ diff --git a/src/ayab/main.cpp b/src/ayab/main.cpp index fc3fdf4a1..cf7669e2c 100644 --- a/src/ayab/main.cpp +++ b/src/ayab/main.cpp @@ -1,6 +1,6 @@ /*! * \file main.cpp - * \brief Main entry point of ayab-firmware. + * \brief Main entry point for AYAB firmware. * * This file is part of AYAB. * @@ -24,28 +24,33 @@ #include -#include "global_hw_test.h" #include "knitter.h" +#include "tester.h" // global definitions // references everywhere else must use `extern` -Knitter *knitter; -GlobalHardwareTest *hwTest; +GlobalKnitter *knitter; +GlobalBeeper *beeper; +GlobalCom *com; +GlobalTester *tester; -// initialize static member -HardwareTestInterface *GlobalHardwareTest::m_instance = new HardwareTest(); +// initialize static members +KnitterInterface *GlobalKnitter::m_instance = new Knitter(); +BeeperInterface *GlobalBeeper::m_instance = new Beeper(); +ComInterface *GlobalCom::m_instance = new Com(); +TesterInterface *GlobalTester::m_instance = new Tester(); /*! - * Setup - steps to take before going to the main loop. + * Setup - do once before going to the main loop. */ void setup() { - knitter = new Knitter(); + GlobalKnitter::init(); + GlobalCom::init(); } /*! - * Main Loop - repeated until the heat death of the universe, - * or someone cuts power to us. + * Main Loop - repeat forever. */ void loop() { - knitter->fsm(); + GlobalKnitter::fsm(); } diff --git a/src/ayab/solenoids.cpp b/src/ayab/solenoids.cpp index e284cc31e..b60aba5ad 100644 --- a/src/ayab/solenoids.cpp +++ b/src/ayab/solenoids.cpp @@ -1,5 +1,6 @@ /*! * \file solenoids.cpp + * \brief Class containing methods governing solenoids. * * This file is part of AYAB. * @@ -51,14 +52,17 @@ void Solenoids::setSolenoid(uint8_t solenoid, bool state) { return; } + uint16_t oldState = solenoidState; if (state) { bitSet(solenoidState, solenoid); } else { bitClear(solenoidState, solenoid); } - - // TODO(Who?): Optimize to act only when there is an actual change of state - write(solenoidState); + if (oldState != solenoidState) { +#ifndef AYAB_TESTS + write(solenoidState); +#endif + } } /*! @@ -68,8 +72,12 @@ void Solenoids::setSolenoid(uint8_t solenoid, bool state) { * one bit per solenoid. */ void Solenoids::setSolenoids(uint16_t state) { - solenoidState = state; - write(state); + if (state != solenoidState) { + solenoidState = state; +#ifndef AYAB_TESTS + write(state); +#endif + } } /* @@ -85,7 +93,9 @@ void Solenoids::setSolenoids(uint16_t state) { * \param newState Two bytes describing the state of the solenoids, * one bit per solenoid. */ +// GCOVR_EXCL_START void Solenoids::write(uint16_t newState) { + (void)newState; #ifdef HARD_I2C mcp_0.writeGPIO(lowByte(newState)); mcp_1.writeGPIO(highByte(newState)); @@ -98,3 +108,4 @@ void Solenoids::write(uint16_t newState) { SoftI2C.endTransmission(); #endif } +// GCOVR_EXCL_STOP diff --git a/src/ayab/solenoids.h b/src/ayab/solenoids.h index 41d3b8bd7..146718953 100644 --- a/src/ayab/solenoids.h +++ b/src/ayab/solenoids.h @@ -43,6 +43,11 @@ constexpr uint8_t SOLENOIDS_I2C_ADDRESS_MASK = 0x20U; * \brief Control of the needles via solenoids connected to IO expanders. */ class Solenoids { +#ifdef AYAB_TESTS + FRIEND_TEST(SolenoidsTest, test_setSolenoid1); + FRIEND_TEST(SolenoidsTest, test_setSolenoid2); + FRIEND_TEST(SolenoidsTest, test_setSolenoid3); +#endif public: Solenoids() #if defined(HARD_I2C) @@ -69,4 +74,4 @@ class Solenoids { #endif }; -#endif // SOLENOIDS_H_ +#endif // SOLENOIDS_H_ diff --git a/src/ayab/hw_test.cpp b/src/ayab/tester.cpp similarity index 54% rename from src/ayab/hw_test.cpp rename to src/ayab/tester.cpp index 7f47b24aa..6466c9a7f 100644 --- a/src/ayab/hw_test.cpp +++ b/src/ayab/tester.cpp @@ -1,5 +1,7 @@ /*! - * \file hw_test.cpp + * \file tester.cpp + * \brief Singleton class containing methods for hardware testing. + * * This file is part of AYAB. * * AYAB is free software: you can redistribute it and/or modify @@ -22,52 +24,54 @@ #include -#include "global_hw_test.h" +#include "beeper.h" +#include "com.h" #include "knitter.h" +#include "tester.h" -// public interface +// public methods /*! * \brief Help command handler. */ -void HardwareTest::helpCmd() { - knitter->sendMsg(test_msgid, "The following commands are available:\n"); - knitter->sendMsg(test_msgid, "setSingle [0..15] [1/0]\n"); - knitter->sendMsg(test_msgid, "setAll [0..FFFF]\n"); - knitter->sendMsg(test_msgid, "readEOLsensors\n"); - knitter->sendMsg(test_msgid, "readEncoders\n"); - knitter->sendMsg(test_msgid, "beep\n"); - knitter->sendMsg(test_msgid, "autoRead\n"); - knitter->sendMsg(test_msgid, "autoTest\n"); - knitter->sendMsg(test_msgid, "send\n"); - knitter->sendMsg(test_msgid, "stop\n"); - knitter->sendMsg(test_msgid, "quit\n"); - knitter->sendMsg(test_msgid, "help\n"); +void Tester::helpCmd() { + GlobalCom::sendMsg(test_msgid, "The following commands are available:\n"); + GlobalCom::sendMsg(test_msgid, "setSingle [0..15] [1/0]\n"); + GlobalCom::sendMsg(test_msgid, "setAll [0..FFFF]\n"); + GlobalCom::sendMsg(test_msgid, "readEOLsensors\n"); + GlobalCom::sendMsg(test_msgid, "readEncoders\n"); + GlobalCom::sendMsg(test_msgid, "beep\n"); + GlobalCom::sendMsg(test_msgid, "autoRead\n"); + GlobalCom::sendMsg(test_msgid, "autoTest\n"); + GlobalCom::sendMsg(test_msgid, "send\n"); + GlobalCom::sendMsg(test_msgid, "stop\n"); + GlobalCom::sendMsg(test_msgid, "quit\n"); + GlobalCom::sendMsg(test_msgid, "help\n"); } /*! * \brief Send command handler. */ -void HardwareTest::sendCmd() { - knitter->sendMsg(test_msgid, "Called send\n"); +void Tester::sendCmd() { + GlobalCom::sendMsg(test_msgid, "Called send\n"); uint8_t p[] = {1, 2, 3}; - knitter->send(p, 3); - knitter->sendMsg(test_msgid, "\n"); + GlobalCom::send(p, 3); + GlobalCom::sendMsg(test_msgid, "\n"); } /*! * \brief Beep command handler. */ -void HardwareTest::beepCmd() { - knitter->sendMsg(test_msgid, "Called beep\n"); +void Tester::beepCmd() { + GlobalCom::sendMsg(test_msgid, "Called beep\n"); beep(); } /*! * \brief Set single solenoid command handler. */ -void HardwareTest::setSingleCmd() { - knitter->sendMsg(test_msgid, "Called setSingle\n"); +void Tester::setSingleCmd() { + GlobalCom::sendMsg(test_msgid, "Called setSingle\n"); /* char *arg = m_sCmd.next(); if (arg == nullptr) { @@ -76,7 +80,7 @@ void HardwareTest::setSingleCmd() { int solenoidNumber = atoi(arg); if (solenoidNumber < 0 or solenoidNumber > 15) { sprintf(buf, "Invalid argument: %i\n", solenoidNumber); - knitter->sendMsg(test_msgid, buf); + GlobalCom::sendMsg(test_msgid, buf); return; } arg = m_sCmd.next(); @@ -86,10 +90,10 @@ void HardwareTest::setSingleCmd() { int solenoidState = atoi(arg); if (solenoidState < 0 or solenoidState > 1) { sprintf(buf, "Invalid argument: %i\n", solenoidState); - knitter->sendMsg(test_msgid, buf); + GlobalCom::sendMsg(test_msgid, buf); return; } - knitter->setSolenoid(static_cast(solenoidNumber), + GlobalKnitter::setSolenoid(static_cast(solenoidNumber), static_cast(solenoidState)); */ } @@ -97,8 +101,8 @@ void HardwareTest::setSingleCmd() { /*! * \brief Set all solenoids command handler. */ -void HardwareTest::setAllCmd() { - knitter->sendMsg(test_msgid, "Called setAll\n"); +void Tester::setAllCmd() { + GlobalCom::sendMsg(test_msgid, "Called setAll\n"); /* char *arg = m_sCmd.next(); if (arg == nullptr) { @@ -107,10 +111,10 @@ void HardwareTest::setAllCmd() { short unsigned int solenoidState; // if (scanHex(arg, 4, &solenoidState)) { if (sscanf(arg, "%hx", &solenoidState)) { - knitter->setSolenoids(solenoidState); + GlobalKnitter::setSolenoids(solenoidState); } else { - knitter->sendMsg(test_msgid, "Invalid argument. Please enter a hexadecimal " - "number between 0 and FFFF.\n"); + GlobalCom::sendMsg(test_msgid, "Invalid argument. Please enter a hexadecimal + " "number between 0 and FFFF.\n"); } */ } @@ -118,41 +122,41 @@ void HardwareTest::setAllCmd() { /*! // GCOVR_EXCL_LINE * \brief Read EOL sensors command handler. */ -void HardwareTest::readEOLsensorsCmd() { - knitter->sendMsg(test_msgid, "Called readEOLsensors\n"); +void Tester::readEOLsensorsCmd() { + GlobalCom::sendMsg(test_msgid, "Called readEOLsensors\n"); readEOLsensors(); - knitter->sendMsg(test_msgid, "\n"); + GlobalCom::sendMsg(test_msgid, "\n"); } /*! // GCOVR_EXCL_START * \brief Read encoders command handler. */ // GCOVR_EXCL_STOP -void HardwareTest::readEncodersCmd() { - knitter->sendMsg(test_msgid, "Called readEncoders\n"); +void Tester::readEncodersCmd() { + GlobalCom::sendMsg(test_msgid, "Called readEncoders\n"); readEncoders(); - knitter->sendMsg(test_msgid, "\n"); + GlobalCom::sendMsg(test_msgid, "\n"); } /*! // GCOVR_EXCL_START * \brief Auto read command handler. */ // GCOVR_EXCL_STOP -void HardwareTest::autoReadCmd() { - knitter->sendMsg(test_msgid, "Called autoRead, send stop to quit\n"); +void Tester::autoReadCmd() { + GlobalCom::sendMsg(test_msgid, "Called autoRead, send stop to quit\n"); m_autoReadOn = true; } /*! // GCOVR_EXCL_START * \brief Auto test command handler. */ // GCOVR_EXCL_STOP -void HardwareTest::autoTestCmd() { - knitter->sendMsg(test_msgid, "Called autoTest, send stop to quit\n"); +void Tester::autoTestCmd() { + GlobalCom::sendMsg(test_msgid, "Called autoTest, send stop to quit\n"); m_autoTestOn = true; } /*! // GCOVR_EXCL_START * \brief Stop command handler. */ // GCOVR_EXCL_STOP -void HardwareTest::stopCmd() { +void Tester::stopCmd() { m_autoReadOn = false; m_autoTestOn = false; } @@ -160,9 +164,9 @@ void HardwareTest::stopCmd() { /*! // GCOVR_EXCL_START * \brief Quit command handler. */ // GCOVR_EXCL_STOP -void HardwareTest::quitCmd() { - knitter->setQuitFlag(true); - knitter->setUpInterrupt(); +void Tester::quitCmd() { + m_quit = true; + GlobalKnitter::setUpInterrupt(); } /*! // GCOVR_EXCL_START @@ -173,8 +177,8 @@ void HardwareTest::quitCmd() { * This gets set as the default handler, and gets called when no other command * matches. */ // GCOVR_EXCL_STOP -void HardwareTest::unrecognizedCmd(const char *buffer) { - knitter->sendMsg(test_msgid, "Unrecognized command\n"); +void Tester::unrecognizedCmd(const char *buffer) { + GlobalCom::sendMsg(test_msgid, "Unrecognized command\n"); (void)(buffer); // does nothing but prevents 'unused variable' compile error helpCmd(); } @@ -182,50 +186,50 @@ void HardwareTest::unrecognizedCmd(const char *buffer) { /*! // GCOVR_EXCL_START * \brief Setup for hardware tests. */ // GCOVR_EXCL_STOP -void HardwareTest::setUp() { +void Tester::setUp() { // set up callbacks for SerialCommand commands /* - m_sCmd.addCommand("%setSingle", GlobalHardwareTest::setSingleCmd); - m_sCmd.addCommand("%setAll", GlobalHardwareTest::setAllCmd); - m_sCmd.addCommand("%readEOLsensors", GlobalHardwareTest::readEOLsensorsCmd); - m_sCmd.addCommand("%readEncoders", GlobalHardwareTest::readEncodersCmd); - m_sCmd.addCommand("%beep", GlobalHardwareTest::beepCmd); - m_sCmd.addCommand("%autoRead", GlobalHardwareTest::autoReadCmd); - m_sCmd.addCommand("%autoTest", GlobalHardwareTest::autoTestCmd); - m_sCmd.addCommand("%send", GlobalHardwareTest::sendCmd); - m_sCmd.addCommand("%stop", GlobalHardwareTest::stopCmd); - m_sCmd.addCommand("%quit", GlobalHardwareTest::quitCmd); - m_sCmd.addCommand("%help", GlobalHardwareTest::helpCmd); - m_sCmd.setDefaultHandler(GlobalHardwareTest::unrecognizedCmd); + m_sCmd.addCommand("%setSingle", GlobalTester::setSingleCmd); + m_sCmd.addCommand("%setAll", GlobalTester::setAllCmd); + m_sCmd.addCommand("%readEOLsensors", GlobalTester::readEOLsensorsCmd); + m_sCmd.addCommand("%readEncoders", GlobalTester::readEncodersCmd); + m_sCmd.addCommand("%beep", GlobalTester::beepCmd); + m_sCmd.addCommand("%autoRead", GlobalTester::autoReadCmd); + m_sCmd.addCommand("%autoTest", GlobalTester::autoTestCmd); + m_sCmd.addCommand("%send", GlobalTester::sendCmd); + m_sCmd.addCommand("%stop", GlobalTester::stopCmd); + m_sCmd.addCommand("%quit", GlobalTester::quitCmd); + m_sCmd.addCommand("%help", GlobalTester::helpCmd); + m_sCmd.setDefaultHandler(GlobalTester::unrecognizedCmd); */ // Print welcome message - knitter->sendMsg(test_msgid, "AYAB Hardware Test, "); + GlobalCom::sendMsg(test_msgid, "AYAB Hardware Test, "); sprintf(buf, "Firmware v%hhu", FW_VERSION_MAJ); - knitter->sendMsg(test_msgid, buf); + GlobalCom::sendMsg(test_msgid, buf); sprintf(buf, ".%hhu", FW_VERSION_MIN); - knitter->sendMsg(test_msgid, buf); + GlobalCom::sendMsg(test_msgid, buf); sprintf(buf, " API v%hhu\n\n", API_VERSION); - knitter->sendMsg(test_msgid, buf); + GlobalCom::sendMsg(test_msgid, buf); helpCmd(); // attach interrupt for ENC_PIN_A(=2), interrupt #0 detachInterrupt(0); #ifndef AYAB_TESTS - attachInterrupt(0, GlobalHardwareTest::encoderAChange, RISING); + attachInterrupt(0, GlobalTester::encoderAChange, RISING); #endif // AYAB_TESTS // GCOVR_EXCL_LINE - m_lastTime = millis(); + m_quit = false; m_autoReadOn = false; m_autoTestOn = false; + m_lastTime = millis(); m_timerEventOdd = false; - knitter->setQuitFlag(false); } /*! // GCOVR_EXCL_START * \brief Main loop for hardware tests. */ // GCOVR_EXCL_STOP -void HardwareTest::loop() { +void Tester::loop() { unsigned long now = millis(); if (now - m_lastTime >= 500) { m_lastTime = now; @@ -233,67 +237,71 @@ void HardwareTest::loop() { } } +#ifndef AYAB_TESTS +/*! + * \brief Interrupt service routine for encoder A. + */ +void Tester::encoderAChange() { + beep(); +} +#endif // AYAB_TESTS + +bool Tester::getQuitFlag() { + return m_quit; +} + // Private member functions -void HardwareTest::beep() { - knitter->m_beeper.ready(); +void Tester::beep() { + GlobalBeeper::ready(); } -void HardwareTest::readEncoders() { - knitter->sendMsg(test_msgid, " ENC_A: "); +void Tester::readEncoders() { + GlobalCom::sendMsg(test_msgid, " ENC_A: "); bool state = digitalRead(ENC_PIN_A); - knitter->sendMsg(test_msgid, state ? "HIGH" : "LOW"); - knitter->sendMsg(test_msgid, " ENC_B: "); + GlobalCom::sendMsg(test_msgid, state ? "HIGH" : "LOW"); + GlobalCom::sendMsg(test_msgid, " ENC_B: "); state = digitalRead(ENC_PIN_B); - knitter->sendMsg(test_msgid, state ? "HIGH" : "LOW"); - knitter->sendMsg(test_msgid, " ENC_C: "); + GlobalCom::sendMsg(test_msgid, state ? "HIGH" : "LOW"); + GlobalCom::sendMsg(test_msgid, " ENC_C: "); state = digitalRead(ENC_PIN_C); - knitter->sendMsg(test_msgid, state ? "HIGH" : "LOW"); + GlobalCom::sendMsg(test_msgid, state ? "HIGH" : "LOW"); } -void HardwareTest::readEOLsensors() { // GCOVR_EXCL_LINE (?) +void Tester::readEOLsensors() { // GCOVR_EXCL_LINE (?) uint16_t hallSensor = static_cast(analogRead(EOL_PIN_L)); sprintf(buf, " EOL_L: %hu", hallSensor); - knitter->sendMsg(test_msgid, buf); + GlobalCom::sendMsg(test_msgid, buf); hallSensor = static_cast(analogRead(EOL_PIN_R)); sprintf(buf, " EOL_R: %hu", hallSensor); - knitter->sendMsg(test_msgid, buf); + GlobalCom::sendMsg(test_msgid, buf); } -void HardwareTest::autoRead() { - knitter->sendMsg(test_msgid, "\n"); +void Tester::autoRead() { + GlobalCom::sendMsg(test_msgid, "\n"); readEOLsensors(); readEncoders(); - knitter->sendMsg(test_msgid, "\n"); + GlobalCom::sendMsg(test_msgid, "\n"); } -void HardwareTest::autoTestEven() { - knitter->sendMsg(test_msgid, "Set even solenoids\n"); +void Tester::autoTestEven() { + GlobalCom::sendMsg(test_msgid, "Set even solenoids\n"); digitalWrite(LED_PIN_A, HIGH); digitalWrite(LED_PIN_B, HIGH); - knitter->setSolenoids(0xAAAA); + GlobalKnitter::setSolenoids(0xAAAA); } -void HardwareTest::autoTestOdd() { - knitter->sendMsg(test_msgid, "Set odd solenoids\n"); +void Tester::autoTestOdd() { + GlobalCom::sendMsg(test_msgid, "Set odd solenoids\n"); digitalWrite(LED_PIN_A, LOW); digitalWrite(LED_PIN_B, LOW); - knitter->setSolenoids(0x5555); -} - -/*! - * \brief Interrupt service routine for encoder A. - */ -#ifndef AYAB_TESTS -void HardwareTest::encoderAChange() { - beep(); + GlobalKnitter::setSolenoids(0x5555); } -#endif // AYAB_TESTS /*! * \brief Timer event every 500ms to handle auto functions. */ -void HardwareTest::handleTimerEvent() { +void Tester::handleTimerEvent() { if (m_autoReadOn and m_timerEventOdd) { autoRead(); } @@ -313,7 +321,7 @@ void HardwareTest::handleTimerEvent() { /* // homebrew `sscanf(str, "%hx", &result);` // does not trim white space -bool HardwareTest::scanHex(char *str, uint8_t maxDigits, uint16_t *result) { +bool Tester::scanHex(char *str, uint8_t maxDigits, uint16_t *result) { if (maxDigits == 0 or *str == 0) { return false; } diff --git a/src/ayab/hw_test.h b/src/ayab/tester.h similarity index 56% rename from src/ayab/hw_test.h rename to src/ayab/tester.h index 877078a71..9ba0aa444 100644 --- a/src/ayab/hw_test.h +++ b/src/ayab/tester.h @@ -1,5 +1,5 @@ /*! - * \file hw_test.h + * \file tester.h * This file is part of AYAB. * * AYAB is free software: you can redistribute it and/or modify @@ -20,8 +20,8 @@ * http://ayab-knitting.com */ -#ifndef HW_TEST_H_ -#define HW_TEST_H_ +#ifndef TESTER_H_ +#define TESTER_H_ #include //#include @@ -30,17 +30,14 @@ constexpr uint8_t BUFFER_LEN = 20; -class HardwareTestInterface { +class TesterInterface { public: - virtual ~HardwareTestInterface(){}; + virtual ~TesterInterface(){}; + + // any methods that need to be mocked should go here virtual void setUp() = 0; virtual void loop() = 0; - -#ifndef AYAB_TESTS - virtual void encoderAChange() = 0; -#endif - - /* + virtual bool getQuitFlag() = 0; virtual void helpCmd() = 0; virtual void sendCmd() = 0; virtual void beepCmd() = 0; @@ -53,28 +50,63 @@ class HardwareTestInterface { virtual void stopCmd() = 0; virtual void quitCmd() = 0; virtual void unrecognizedCmd(const char *buffer) = 0; - */ +#ifndef AYAB_TESTS + virtual void encoderAChange(); +#endif }; -class HardwareTest : public HardwareTestInterface { +// Container class for the static methods that implement the hardware test +// commands. Originally these methods were called back by `SerialCommand`. +// Dependency injection is enabled using a pointer to a global instance of +// either `Tester` or `TesterMock`, both of which classes implement the +// pure virtual methods of `TesterInterface`. + +class GlobalTester final { +private: + // singleton class so private constructor is appropriate + GlobalTester() = default; + +public: + // pointer to global instance whose methods are implemented + static TesterInterface *m_instance; + + static void setUp(); + static void loop(); + static bool getQuitFlag(); + static void helpCmd(); + static void sendCmd(); + static void beepCmd(); + static void setSingleCmd(); + static void setAllCmd(); + static void readEOLsensorsCmd(); + static void readEncodersCmd(); + static void autoReadCmd(); + static void autoTestCmd(); + static void stopCmd(); + static void quitCmd(); + static void unrecognizedCmd(const char *buffer); +#ifndef AYAB_TESTS + static void encoderAChange(); +#endif +}; + +class Tester : public TesterInterface { #if AYAB_TESTS - FRIEND_TEST(HardwareTestTest, test_stopCmd); - FRIEND_TEST(HardwareTestTest, test_setUp); - FRIEND_TEST(HardwareTestTest, test_loop_default); - FRIEND_TEST(HardwareTestTest, test_loop_null); - FRIEND_TEST(HardwareTestTest, test_loop_autoTestEven); - FRIEND_TEST(HardwareTestTest, test_loop_autoTestOdd); - // FRIEND_TEST(HardwareTestTest, test_scanHex); - friend class HardwareTestTest; + FRIEND_TEST(TesterTest, test_stopCmd); + FRIEND_TEST(TesterTest, test_quitCmd); + FRIEND_TEST(TesterTest, test_setUp); + FRIEND_TEST(TesterTest, test_loop_default); + FRIEND_TEST(TesterTest, test_loop_null); + FRIEND_TEST(TesterTest, test_loop_autoTestEven); + FRIEND_TEST(TesterTest, test_loop_autoTestOdd); + // FRIEND_TEST(TesterTest, test_scanHex); + friend class TesterTest; #endif public: void setUp(); void loop(); -#ifndef AYAB_TESTS - void encoderAChange(); -#endif - + bool getQuitFlag(); void helpCmd(); void sendCmd(); void beepCmd(); @@ -87,6 +119,9 @@ class HardwareTest : public HardwareTestInterface { void stopCmd(); void quitCmd(); void unrecognizedCmd(const char *buffer); +#ifndef AYAB_TESTS + void encoderAChange(); +#endif private: void beep(); @@ -101,13 +136,13 @@ class HardwareTest : public HardwareTestInterface { // SerialCommand m_sCmd = SerialCommand(); + bool m_quit = false; bool m_autoReadOn = false; bool m_autoTestOn = false; - unsigned long m_lastTime = 0U; bool m_timerEventOdd = false; char buf[BUFFER_LEN] = {0}; }; -#endif // HW_TEST_H_ +#endif // TESTER_H_ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f5c821c25..3461b5715 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -38,25 +38,28 @@ set(EXTERNAL_LIB_INCLUDES ${LIBRARY_DIRECTORY}/SoftI2CMaster ) set(COMMON_SOURCES - ${PROJECT_SOURCE_DIR}/test_all.cpp + ${PROJECT_SOURCE_DIR}/test_boards.cpp ${SOURCE_DIRECTORY}/encoders.cpp ${PROJECT_SOURCE_DIR}/test_encoders.cpp - ${SOURCE_DIRECTORY}/beeper.cpp - ${PROJECT_SOURCE_DIR}/test_beeper.cpp - ${SOURCE_DIRECTORY}/solenoids.cpp ${PROJECT_SOURCE_DIR}/test_solenoids.cpp - ${SOURCE_DIRECTORY}/serial_encoding.cpp - ${PROJECT_SOURCE_DIR}/test_serial_encoding.cpp + ${SOURCE_DIRECTORY}/beeper.cpp + ${SOURCE_DIRECTORY}/global_beeper.cpp + ${PROJECT_SOURCE_DIR}/test_beeper.cpp + + ${SOURCE_DIRECTORY}/com.cpp + ${SOURCE_DIRECTORY}/global_com.cpp + ${PROJECT_SOURCE_DIR}/test_com.cpp - ${SOURCE_DIRECTORY}/hw_test.cpp - ${SOURCE_DIRECTORY}/global_hw_test.cpp - ${PROJECT_SOURCE_DIR}/test_hw_test.cpp + ${SOURCE_DIRECTORY}/tester.cpp + ${SOURCE_DIRECTORY}/global_tester.cpp + ${PROJECT_SOURCE_DIR}/test_tester.cpp #${PROJECT_SOURCE_DIR}/mocks/SerialCommand_mock.cpp + ${SOURCE_DIRECTORY}/global_knitter.cpp ${PROJECT_SOURCE_DIR}/mocks/knitter_mock.cpp ) set(COMMON_DEFINES @@ -70,11 +73,6 @@ set(COMMON_FLAGS -Wno-vla -Werror --coverage - - # exclude global_hw_test.cpp from profile analysis - # to avoid tedious testing of these simple functions - -fprofile-exclude-files=global_hw_test.cpp - -g -Og ) set(COMMON_LINKER_FLAGS @@ -139,12 +137,15 @@ add_board(Mega) add_executable(${PROJECT_NAME}_knitter ${PROJECT_SOURCE_DIR}/test_all.cpp ${SOURCE_DIRECTORY}/knitter.cpp - ${SOURCE_DIRECTORY}/global_hw_test.cpp # not mockable + ${SOURCE_DIRECTORY}/global_knitter.cpp + ${SOURCE_DIRECTORY}/global_com.cpp + ${SOURCE_DIRECTORY}/global_beeper.cpp + ${SOURCE_DIRECTORY}/global_tester.cpp ${PROJECT_SOURCE_DIR}/mocks/solenoids_mock.cpp ${PROJECT_SOURCE_DIR}/mocks/encoders_mock.cpp ${PROJECT_SOURCE_DIR}/mocks/beeper_mock.cpp - ${PROJECT_SOURCE_DIR}/mocks/serial_encoding_mock.cpp - ${PROJECT_SOURCE_DIR}/mocks/hw_test_mock.cpp + ${PROJECT_SOURCE_DIR}/mocks/com_mock.cpp + ${PROJECT_SOURCE_DIR}/mocks/tester_mock.cpp ${PROJECT_SOURCE_DIR}/test_knitter.cpp #${LIBRARY_DIRECTORY}/SerialCommand/SerialCommand.cpp ${SOFT_I2C_LIB} diff --git a/test/mocks/beeper_mock.h b/test/mocks/beeper_mock.h index cd5afc890..e7ac97a43 100644 --- a/test/mocks/beeper_mock.h +++ b/test/mocks/beeper_mock.h @@ -24,10 +24,11 @@ #ifndef BEEPER_MOCK_H_ #define BEEPER_MOCK_H_ -#include #include -class BeeperMock { +#include + +class BeeperMock : public BeeperInterface { public: MOCK_METHOD0(ready, void()); MOCK_METHOD0(finishedLine, void()); @@ -37,4 +38,4 @@ class BeeperMock { BeeperMock *beeperMockInstance(); void releaseBeeperMock(); -#endif // BEEPER_MOCK_H_ +#endif // BEEPER_MOCK_H_ diff --git a/test/mocks/com_mock.cpp b/test/mocks/com_mock.cpp new file mode 100644 index 000000000..0ce36812c --- /dev/null +++ b/test/mocks/com_mock.cpp @@ -0,0 +1,70 @@ +/*!` + * \file com_mock.cpp + * + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#include +#include + +static ComMock *gComMock = NULL; +ComMock *comMockInstance() { + if (!gComMock) { + gComMock = new ComMock(); + } + return gComMock; +} + +void releaseComMock() { + if (gComMock) { + delete gComMock; + gComMock = NULL; + } +} + +void Com::init() { + assert(gComMock != nullptr); + gComMock->init(); +} + +void Com::update() { + assert(gComMock != nullptr); + gComMock->update(); +} + +void Com::send(uint8_t *payload, size_t length) { + assert(gComMock != nullptr); + gComMock->send(payload, length); +} + +void Com::sendMsg(AYAB_API_t id, const char *msg) { + assert(gComMock != nullptr); + gComMock->sendMsg(id, msg); +} + +void Com::sendMsg(AYAB_API_t id, char *msg) { + assert(gComMock != nullptr); + gComMock->sendMsg(id, msg); +} + +void Com::onPacketReceived(const uint8_t *buffer, size_t size) { + assert(gComMock != nullptr); + gComMock->onPacketReceived(buffer, size); +} diff --git a/test/mocks/serial_encoding_mock.h b/test/mocks/com_mock.h similarity index 83% rename from test/mocks/serial_encoding_mock.h rename to test/mocks/com_mock.h index 67ff2f2fd..57aae277c 100644 --- a/test/mocks/serial_encoding_mock.h +++ b/test/mocks/com_mock.h @@ -1,5 +1,5 @@ /*!` - * \file serial_encoding_mock.h + * \file com_mock.h * * This file is part of AYAB. * @@ -21,13 +21,16 @@ * http://ayab-knitting.com */ -#ifndef SERIAL_ENCODING_MOCK_H_ -#define SERIAL_ENCODING_MOCK_H_ +#ifndef COM_MOCK_H_ +#define COM_MOCK_H_ #include -class SerialEncodingMock { +#include + +class ComMock : public ComInterface { public: + MOCK_METHOD0(init, void()); MOCK_METHOD0(update, void()); MOCK_METHOD2(send, void(uint8_t *payload, size_t length)); MOCK_METHOD2(sendMsg, void(AYAB_API_t id, const char *msg)); @@ -35,7 +38,7 @@ class SerialEncodingMock { MOCK_METHOD2(onPacketReceived, void(const uint8_t *buffer, size_t size)); }; -SerialEncodingMock *serialEncodingMockInstance(); -void releaseSerialEncodingMock(); +ComMock *comMockInstance(); +void releaseComMock(); -#endif // SERIAL_ENCODING_MOCK_H_ +#endif // COM_MOCK_H_ diff --git a/test/mocks/hw_test_mock.cpp b/test/mocks/hw_test_mock.cpp deleted file mode 100644 index 0efd0762f..000000000 --- a/test/mocks/hw_test_mock.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/*!` - * \file hw_test_mock.cpp - * - * This file is part of AYAB. - * - * AYAB is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * AYAB is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with AYAB. If not, see . - * - * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller - * Modified Work Copyright 2020 Sturla Lange, Tom Price - * http://ayab-knitting.com - */ - -#include - -static HardwareTestMock *gHardwareTestMock = NULL; - -HardwareTestMock *hwTestMockInstance() { - if (!gHardwareTestMock) { - gHardwareTestMock = new HardwareTestMock(); - } - return gHardwareTestMock; -} - -void releaseHardwareTestMock() { - if (gHardwareTestMock) { - delete gHardwareTestMock; - gHardwareTestMock = NULL; - } -} - -void HardwareTest::setUp() { - assert(gHardwareTestMock != NULL); - gHardwareTestMock->setUp(); -} - -void HardwareTest::helpCmd() { - assert(gHardwareTestMock != NULL); - gHardwareTestMock->helpCmd(); -} - -void HardwareTest::sendCmd() { - assert(gHardwareTestMock != NULL); - gHardwareTestMock->sendCmd(); -} - -void HardwareTest::beepCmd() { - assert(gHardwareTestMock != NULL); - gHardwareTestMock->beepCmd(); -} - -void HardwareTest::setSingleCmd() { - assert(gHardwareTestMock != NULL); - gHardwareTestMock->setSingleCmd(); -} - -void HardwareTest::setAllCmd() { - assert(gHardwareTestMock != NULL); - gHardwareTestMock->setAllCmd(); -} - -void HardwareTest::readEOLsensorsCmd() { - assert(gHardwareTestMock != NULL); - gHardwareTestMock->readEOLsensorsCmd(); -} - -void HardwareTest::readEncodersCmd() { - assert(gHardwareTestMock != NULL); - gHardwareTestMock->readEncodersCmd(); -} - -void HardwareTest::autoReadCmd() { - assert(gHardwareTestMock != NULL); - gHardwareTestMock->autoReadCmd(); -} - -void HardwareTest::autoTestCmd() { - assert(gHardwareTestMock != NULL); - gHardwareTestMock->autoTestCmd(); -} - -void HardwareTest::stopCmd() { - assert(gHardwareTestMock != NULL); - gHardwareTestMock->stopCmd(); -} - -void HardwareTest::quitCmd() { - assert(gHardwareTestMock != NULL); - gHardwareTestMock->quitCmd(); -} - -void HardwareTest::unrecognizedCmd(const char *buffer) { - assert(gHardwareTestMock != NULL); - gHardwareTestMock->unrecognizedCmd(buffer); -} - -void HardwareTest::loop() { - assert(gHardwareTestMock != NULL); - gHardwareTestMock->loop(); -} diff --git a/test/mocks/knitter_mock.cpp b/test/mocks/knitter_mock.cpp index 4d4b9e35c..e9da46fdb 100644 --- a/test/mocks/knitter_mock.cpp +++ b/test/mocks/knitter_mock.cpp @@ -39,24 +39,24 @@ void releaseKnitterMock() { } } -bool Knitter::startTest(Machine_t machineType) { +void Knitter::init() { assert(gKnitterMock != NULL); - return gKnitterMock->startTest(machineType); + gKnitterMock->init(); } -Machine_t Knitter::getMachineType() const { +void Knitter::fsm() { assert(gKnitterMock != NULL); - return gKnitterMock->getMachineType(); + gKnitterMock->fsm(); } -void Knitter::setMachineType(Machine_t machineType) { +void Knitter::isr() { assert(gKnitterMock != NULL); - return gKnitterMock->setMachineType(machineType); + gKnitterMock->isr(); } -uint8_t Knitter::getStartOffset(const Direction_t direction) const { +void Knitter::setUpInterrupt() { assert(gKnitterMock != NULL); - return gKnitterMock->getStartOffset(direction); + gKnitterMock->setUpInterrupt(); } bool Knitter::startOperation(Machine_t machineType, uint8_t startNeedle, @@ -68,6 +68,11 @@ bool Knitter::startOperation(Machine_t machineType, uint8_t startNeedle, continuousReportingEnabled); } +bool Knitter::startTest(Machine_t machineType) { + assert(gKnitterMock != NULL); + return gKnitterMock->startTest(machineType); +} + bool Knitter::setNextLine(uint8_t lineNumber) { assert(gKnitterMock != NULL); return gKnitterMock->setNextLine(lineNumber); @@ -78,49 +83,27 @@ void Knitter::setLastLine() { gKnitterMock->setLastLine(); } -void Knitter::send(uint8_t payload[], size_t length) { +Machine_t Knitter::getMachineType() { assert(gKnitterMock != NULL); - gKnitterMock->send(payload, length); + return gKnitterMock->getMachineType(); } -void Knitter::sendMsg(AYAB_API_t id, const char *msg) { +void Knitter::setSolenoids(uint16_t state) { assert(gKnitterMock != NULL); - gKnitterMock->sendMsg(id, msg); + return gKnitterMock->setSolenoids(state); } -void Knitter::sendMsg(AYAB_API_t id, char *msg) { +void Knitter::setMachineType(Machine_t machineType) { assert(gKnitterMock != NULL); - gKnitterMock->sendMsg(id, msg); + return gKnitterMock->setMachineType(machineType); } -void Knitter::onPacketReceived(const uint8_t *buffer, size_t size) { +uint8_t Knitter::getStartOffset(const Direction_t direction) { assert(gKnitterMock != NULL); - gKnitterMock->onPacketReceived(buffer, size); + return gKnitterMock->getStartOffset(direction); } void Knitter::setState(OpState_t state) { assert(gKnitterMock != NULL); gKnitterMock->setState(state); } - -void Knitter::setSolenoids(uint16_t state) { - assert(gKnitterMock != NULL); - gKnitterMock->setSolenoids(state); -} - -void Knitter::setSolenoid(uint8_t solenoid, uint8_t state) { - assert(gKnitterMock != NULL); - gKnitterMock->setSolenoid(solenoid, state); -} - -void Knitter::setQuitFlag(bool flag) { - assert(gKnitterMock != NULL); - gKnitterMock->setQuitFlag(flag); -} - -void Knitter::setUpInterrupt() { - assert(gKnitterMock != NULL); - gKnitterMock->setUpInterrupt(); -} - -Knitter *knitter; diff --git a/test/mocks/knitter_mock.h b/test/mocks/knitter_mock.h index f957b9917..e52530a72 100644 --- a/test/mocks/knitter_mock.h +++ b/test/mocks/knitter_mock.h @@ -27,26 +27,23 @@ #include #include -class KnitterMock { +class KnitterMock : public KnitterInterface { public: + MOCK_METHOD0(init, void()); + MOCK_METHOD0(fsm, void()); + MOCK_METHOD0(isr, void()); + MOCK_METHOD0(setUpInterrupt, void()); MOCK_METHOD5(startOperation, bool(Machine_t machineType, uint8_t startNeedle, uint8_t stopNeedle, uint8_t *pattern_start, bool continuousReportingEnabled)); MOCK_METHOD1(startTest, bool(Machine_t)); MOCK_METHOD1(setNextLine, bool(uint8_t lineNumber)); MOCK_METHOD0(setLastLine, void()); - MOCK_METHOD2(send, void(uint8_t *payload, size_t length)); - MOCK_METHOD2(sendMsg, void(AYAB_API_t id, const char *msg)); - MOCK_METHOD2(sendMsg, void(AYAB_API_t id, char *msg)); - MOCK_METHOD2(onPacketReceived, void(const uint8_t *buffer, size_t size)); - MOCK_CONST_METHOD0(getMachineType, Machine_t()); + MOCK_METHOD0(getMachineType, Machine_t()); + MOCK_METHOD1(setSolenoids, void(uint16_t)); MOCK_METHOD1(setMachineType, void(Machine_t)); - MOCK_CONST_METHOD1(getStartOffset, uint8_t(const Direction_t direction)); + MOCK_METHOD1(getStartOffset, uint8_t(const Direction_t direction)); MOCK_METHOD1(setState, void(OpState_t)); - MOCK_METHOD1(setSolenoids, void(uint16_t state)); - MOCK_METHOD2(setSolenoid, void(uint8_t solenoid, uint8_t state)); - MOCK_METHOD1(setQuitFlag, void(bool flag)); - MOCK_METHOD0(setUpInterrupt, void()); }; KnitterMock *knitterMockInstance(); diff --git a/test/mocks/serial_encoding_mock.cpp b/test/mocks/serial_encoding_mock.cpp deleted file mode 100644 index 365381ca6..000000000 --- a/test/mocks/serial_encoding_mock.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/*!` - * \file serial_encoding_mock.cpp - * - * This file is part of AYAB. - * - * AYAB is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * AYAB is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with AYAB. If not, see . - * - * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller - * Modified Work Copyright 2020 Sturla Lange, Tom Price - * http://ayab-knitting.com - */ - -#include -#include - -static SerialEncodingMock *gSerialEncodingMock = NULL; -SerialEncodingMock *serialEncodingMockInstance() { - if (!gSerialEncodingMock) { - gSerialEncodingMock = new SerialEncodingMock(); - } - return gSerialEncodingMock; -} - -void releaseSerialEncodingMock() { - if (gSerialEncodingMock) { - delete gSerialEncodingMock; - gSerialEncodingMock = NULL; - } -} - -SerialEncoding::SerialEncoding() { -} - -void SerialEncoding::update() { - assert(gSerialEncodingMock != nullptr); - gSerialEncodingMock->update(); -} - -void SerialEncoding::send(uint8_t *payload, size_t length) { - assert(gSerialEncodingMock != nullptr); - gSerialEncodingMock->send(payload, length); -} - -void SerialEncoding::sendMsg(AYAB_API_t id, const char *msg) { - assert(gSerialEncodingMock != nullptr); - gSerialEncodingMock->sendMsg(id, msg); -} - -void SerialEncoding::sendMsg(AYAB_API_t id, char *msg) { - assert(gSerialEncodingMock != nullptr); - gSerialEncodingMock->sendMsg(id, msg); -} - -void SerialEncoding::onPacketReceived(const uint8_t *buffer, size_t size) { - assert(gSerialEncodingMock != nullptr); - gSerialEncodingMock->onPacketReceived(buffer, size); -} diff --git a/test/mocks/tester_mock.cpp b/test/mocks/tester_mock.cpp new file mode 100644 index 000000000..c317926e7 --- /dev/null +++ b/test/mocks/tester_mock.cpp @@ -0,0 +1,115 @@ +/*!` + * \file tester_mock.cpp + * + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#include + +static TesterMock *gTesterMock = NULL; + +TesterMock *testerMockInstance() { + if (!gTesterMock) { + gTesterMock = new TesterMock(); + } + return gTesterMock; +} + +void releaseTesterMock() { + if (gTesterMock) { + delete gTesterMock; + gTesterMock = NULL; + } +} + +void Tester::setUp() { + assert(gTesterMock != NULL); + gTesterMock->setUp(); +} + +void Tester::loop() { + assert(gTesterMock != NULL); + gTesterMock->loop(); +} + +bool Tester::getQuitFlag() { + assert(gTesterMock != NULL); + return gTesterMock->getQuitFlag(); +} + +void Tester::helpCmd() { + assert(gTesterMock != NULL); + gTesterMock->helpCmd(); +} + +void Tester::sendCmd() { + assert(gTesterMock != NULL); + gTesterMock->sendCmd(); +} + +void Tester::beepCmd() { + assert(gTesterMock != NULL); + gTesterMock->beepCmd(); +} + +void Tester::setSingleCmd() { + assert(gTesterMock != NULL); + gTesterMock->setSingleCmd(); +} + +void Tester::setAllCmd() { + assert(gTesterMock != NULL); + gTesterMock->setAllCmd(); +} + +void Tester::readEOLsensorsCmd() { + assert(gTesterMock != NULL); + gTesterMock->readEOLsensorsCmd(); +} + +void Tester::readEncodersCmd() { + assert(gTesterMock != NULL); + gTesterMock->readEncodersCmd(); +} + +void Tester::autoReadCmd() { + assert(gTesterMock != NULL); + gTesterMock->autoReadCmd(); +} + +void Tester::autoTestCmd() { + assert(gTesterMock != NULL); + gTesterMock->autoTestCmd(); +} + +void Tester::stopCmd() { + assert(gTesterMock != NULL); + gTesterMock->stopCmd(); +} + +void Tester::quitCmd() { + assert(gTesterMock != NULL); + gTesterMock->quitCmd(); +} + +void Tester::unrecognizedCmd(const char *buffer) { + assert(gTesterMock != NULL); + gTesterMock->unrecognizedCmd(buffer); +} diff --git a/test/mocks/hw_test_mock.h b/test/mocks/tester_mock.h similarity index 84% rename from test/mocks/hw_test_mock.h rename to test/mocks/tester_mock.h index 049d78fb0..7d1640cc1 100644 --- a/test/mocks/hw_test_mock.h +++ b/test/mocks/tester_mock.h @@ -1,5 +1,5 @@ /*!` - * \file hw_test_mock.h + * \file tester_mock.h * * This file is part of AYAB. * @@ -21,16 +21,17 @@ * http://ayab-knitting.com */ -#ifndef HW_TEST_MOCK_H_ -#define HW_TEST_MOCK_H_ +#ifndef TESTER_MOCK_H_ +#define TESTER_MOCK_H_ #include -#include +#include -class HardwareTestMock : public HardwareTestInterface { +class TesterMock : public TesterInterface { public: MOCK_METHOD0(setUp, void()); MOCK_METHOD0(loop, void()); + MOCK_METHOD0(getQuitFlag, bool()); MOCK_METHOD0(helpCmd, void()); MOCK_METHOD0(sendCmd, void()); MOCK_METHOD0(beepCmd, void()); @@ -45,7 +46,7 @@ class HardwareTestMock : public HardwareTestInterface { MOCK_METHOD1(unrecognizedCmd, void(const char *)); }; -HardwareTestMock *hwTestMockInstance(); -void releaseHardwareTestMock(); +TesterMock *testerMockInstance(); +void releaseTesterMock(); -#endif // HW_TEST_MOCK_H_ +#endif // TESTER_MOCK_H_ diff --git a/test/test.sh b/test/test.sh index 4d18662fa..871ecac68 100755 --- a/test/test.sh +++ b/test/test.sh @@ -43,7 +43,9 @@ GTEST_COLOR=1 ctest $ctest_verbose --output-on-failure . cd ../.. GCOVR_ARGS="--exclude-unreachable-branches --exclude-throw-branches \ - --exclude-directories 'test/build/arduino_mock$' -e test_* -e libraries*" # -e src/ayab/global_hw_test.cpp + --exclude-directories 'test/build/arduino_mock$' \ + -e test_* -e libraries* -e src/ayab/global_knitter.cpp \ + -e src/ayab/global_tester.cpp" gcovr -k -r . $GCOVR_ARGS --html-details -o ./test/build/coverage.html # gcovr -r . $GCOVR_ARGS --json-pretty -o ./test/build/coverage-report.json diff --git a/test/test_all.cpp b/test/test_all.cpp index 1da024352..285af9195 100644 --- a/test/test_all.cpp +++ b/test/test_all.cpp @@ -23,6 +23,25 @@ #include "gtest/gtest.h" +#include + +#include +#include +#include + +// global definitions +// references everywhere else must use `extern` +Knitter *knitter = new Knitter(); +BeeperMock *beeper = new BeeperMock(); +ComMock *com = new ComMock(); +TesterMock *tester = new TesterMock(); + +// instantiate singleton classes with mock objects +KnitterInterface *GlobalKnitter::m_instance = knitter; +BeeperInterface *GlobalBeeper::m_instance = beeper; +ComInterface *GlobalCom::m_instance = com; +TesterInterface *GlobalTester::m_instance = tester; + int main(int argc, char *argv[]) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/test/test_beeper.cpp b/test/test_beeper.cpp index 7994ab0a5..6b447bb8a 100644 --- a/test/test_beeper.cpp +++ b/test/test_beeper.cpp @@ -28,11 +28,12 @@ using ::testing::Return; +extern Beeper *beeper; + class BeeperTest : public ::testing::Test { protected: void SetUp() override { arduinoMock = arduinoMockInstance(); - b = Beeper(); } void TearDown() override { @@ -47,20 +48,19 @@ class BeeperTest : public ::testing::Test { } ArduinoMock *arduinoMock; - Beeper b; }; TEST_F(BeeperTest, test_ready) { checkBeepTime(5); - b.ready(); + beeper->ready(); } TEST_F(BeeperTest, test_finishedLine) { checkBeepTime(3); - b.finishedLine(); + beeper->finishedLine(); } TEST_F(BeeperTest, test_endWork) { checkBeepTime(10); - b.endWork(); + beeper->endWork(); } diff --git a/test/test_boards.cpp b/test/test_boards.cpp new file mode 100644 index 000000000..cad5f5bb0 --- /dev/null +++ b/test/test_boards.cpp @@ -0,0 +1,48 @@ +/*!` + * \file test_all.cpp + * + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#include "gtest/gtest.h" + +#include +#include +#include + +#include + +// global definitions +// references everywhere else must use `extern` +KnitterMock *knitter = new KnitterMock(); +Beeper *beeper = new Beeper(); +Com *com = new Com(); +Tester *tester = new Tester(); + +// initialize static members +KnitterInterface *GlobalKnitter::m_instance = knitter; +BeeperInterface *GlobalBeeper::m_instance = beeper; +ComInterface *GlobalCom::m_instance = com; +TesterInterface *GlobalTester::m_instance = tester; + +int main(int argc, char *argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/test_serial_encoding.cpp b/test/test_com.cpp similarity index 62% rename from test/test_serial_encoding.cpp rename to test/test_com.cpp index 45efffaaa..a8e4b9f1b 100644 --- a/test/test_serial_encoding.cpp +++ b/test/test_com.cpp @@ -23,19 +23,32 @@ #include +#include + #include -#include using ::testing::_; +using ::testing::Mock; using ::testing::Return; -class SerialEncodingTest : public ::testing::Test { +extern Com *com; +extern KnitterMock *knitter; + +class ComTest : public ::testing::Test { protected: void SetUp() override { - knitterMock = knitterMockInstance(); serialMock = serialMockInstance(); - EXPECT_CALL(*serialMock, begin); - s = new SerialEncoding(); + + // pointer to global instance + knitterMock = knitter; + + // The global instance does not get destroyed at the end of each test. + // Ordinarily the mock instance would be local and such behaviour would + // cause a memory leak. We must notify the test that this is not the case. + Mock::AllowLeak(knitterMock); + + expect_init(); + com->init(); } void TearDown() override { @@ -45,46 +58,74 @@ class SerialEncodingTest : public ::testing::Test { KnitterMock *knitterMock; SerialMock *serialMock; - SerialEncoding *s; + + void expect_init() { + EXPECT_CALL(*serialMock, begin); + } }; + /* -TEST_F(SerialEncodingTest, test_API) { +TEST_F(ComTest, test_API) { ASSERT_EQ(API_VERSION, 6); } */ -TEST_F(SerialEncodingTest, test_testmsg) { - uint8_t buffer[] = {reqTest_msgid}; - EXPECT_CALL(*knitterMock, startTest).WillOnce(Return(false)); - s->onPacketReceived(buffer, sizeof(buffer)); +TEST_F(ComTest, test_reqtest_fail) { // no machineType + uint8_t buffer[] = {reqTest_msgid}; EXPECT_CALL(*knitterMock, startTest).Times(0); - s->onPacketReceived(buffer, sizeof(buffer) - 1); + com->onPacketReceived(buffer, sizeof(buffer)); + ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); } -TEST_F(SerialEncodingTest, test_startmsg) { - uint8_t buffer[] = {reqStart_msgid, 0, 0, 10, 1, 0x74}; - EXPECT_CALL(*knitterMock, startOperation); - s->onPacketReceived(buffer, sizeof(buffer)); +TEST_F(ComTest, test_reqtest_success) { + uint8_t buffer[] = {reqTest_msgid, Kh270}; + EXPECT_CALL(*knitterMock, startTest).WillOnce(Return(false)); + com->onPacketReceived(buffer, sizeof(buffer)); + ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); +} + +TEST_F(ComTest, test_reqstart_fail1) { // checksum wrong - buffer[5] = 0x73; + uint8_t buffer[] = {reqStart_msgid, 0, 0, 10, 1, 0x73}; EXPECT_CALL(*knitterMock, startOperation).Times(0); - s->onPacketReceived(buffer, sizeof(buffer)); - // kh270 - buffer[1] = 2; - EXPECT_CALL(*knitterMock, startOperation); - s->onPacketReceived(buffer, sizeof(buffer)); - // Not enough bytes + com->onPacketReceived(buffer, sizeof(buffer)); + ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); +} + +TEST_F(ComTest, test_reqstart_fail2) { + // not enough bytes + uint8_t buffer[] = {reqStart_msgid, 0, 0, 10, 1, 0x74}; EXPECT_CALL(*knitterMock, startOperation).Times(0); - s->onPacketReceived(buffer, sizeof(buffer) - 1); + com->onPacketReceived(buffer, sizeof(buffer) - 1); + ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); } -TEST_F(SerialEncodingTest, test_infomsg) { +TEST_F(ComTest, test_reqstart_success_KH910) { + uint8_t buffer[] = {reqStart_msgid, 0, 0, 10, 1, 0x74}; + EXPECT_CALL(*knitterMock, startOperation); + com->onPacketReceived(buffer, sizeof(buffer)); + ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); +} + +TEST_F(ComTest, test_reqstart_success_KH270) { + uint8_t buffer[] = {reqStart_msgid, 2, 0, 10, 1, 0x73}; + EXPECT_CALL(*knitterMock, startOperation); + com->onPacketReceived(buffer, sizeof(buffer)); + ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); +} + +TEST_F(ComTest, test_reqinfo) { uint8_t buffer[] = {reqInfo_msgid}; - s->onPacketReceived(buffer, sizeof(buffer)); + com->onPacketReceived(buffer, sizeof(buffer)); +} + +TEST_F(ComTest, test_unrecognized) { + uint8_t buffer[] = {0xFF}; + com->onPacketReceived(buffer, sizeof(buffer)); } -TEST_F(SerialEncodingTest, test_cnfmsg_kh910) { +TEST_F(ComTest, test_cnfline_kh910) { // dummy pattern uint8_t pattern[] = {1}; @@ -124,33 +165,36 @@ TEST_F(SerialEncodingTest, test_cnfmsg_kh910) { knitterMock->startOperation(Kh910, 0, 199, pattern, false); // first call increments line number to zero, not accepted - EXPECT_CALL(*knitterMock, setLastLine).Times(0); EXPECT_CALL(*knitterMock, setNextLine).WillOnce(Return(false)); - s->onPacketReceived(buffer, sizeof(buffer)); + EXPECT_CALL(*knitterMock, setLastLine).Times(0); + com->onPacketReceived(buffer, sizeof(buffer)); // second call Line accepted, last line - EXPECT_CALL(*knitterMock, setLastLine).Times(1); EXPECT_CALL(*knitterMock, setNextLine).WillOnce(Return(true)); - s->onPacketReceived(buffer, sizeof(buffer)); + EXPECT_CALL(*knitterMock, setLastLine).Times(1); + com->onPacketReceived(buffer, sizeof(buffer)); - // Not last line + // not last line buffer[3] = 0x00; buffer[29] = 0xc0; - EXPECT_CALL(*knitterMock, setLastLine).Times(0); EXPECT_CALL(*knitterMock, setNextLine).WillOnce(Return(true)); - s->onPacketReceived(buffer, sizeof(buffer)); + EXPECT_CALL(*knitterMock, setLastLine).Times(0); + com->onPacketReceived(buffer, sizeof(buffer)); - // crc wrong + // checksum wrong EXPECT_CALL(*knitterMock, setNextLine).Times(0); buffer[29]--; - s->onPacketReceived(buffer, sizeof(buffer)); + com->onPacketReceived(buffer, sizeof(buffer)); - // Not enough bytes in buffer + // not enough bytes in buffer EXPECT_CALL(*knitterMock, setNextLine).Times(0); - s->onPacketReceived(buffer, sizeof(buffer) - 1); + com->onPacketReceived(buffer, sizeof(buffer) - 1); + + ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); } + /* -TEST_F(SerialEncodingTest, test_cnfmsg_kh270) { +TEST_F(ComTest, test_cnfline_kh270) { // dummy pattern uint8_t pattern[] = {1}; @@ -164,38 +208,36 @@ TEST_F(SerialEncodingTest, test_cnfmsg_kh270) { 0xab}; // CRC8 // start KH270 job knitterMock->startOperation(Kh270, 0, 113, pattern, false); - s->onPacketReceived(buffer, sizeof(buffer)); + com->onPacketReceived(buffer, sizeof(buffer)); } */ -TEST_F(SerialEncodingTest, test_debug) { - uint8_t buffer[] = {debug_msgid}; - s->onPacketReceived(buffer, sizeof(buffer)); -} -TEST_F(SerialEncodingTest, test_constructor) { +TEST_F(ComTest, test_debug) { + uint8_t buffer[] = {debug_msgid}; + com->onPacketReceived(buffer, sizeof(buffer)); } -TEST_F(SerialEncodingTest, test_update) { +TEST_F(ComTest, test_update) { EXPECT_CALL(*serialMock, available); - s->update(); + com->update(); } -TEST_F(SerialEncodingTest, test_send) { +TEST_F(ComTest, test_send) { EXPECT_CALL(*serialMock, write(_, _)); EXPECT_CALL(*serialMock, write(SLIP::END)); uint8_t p[] = {1, 2, 3}; - s->send(p, 3); + com->send(p, 3); } -TEST_F(SerialEncodingTest, test_sendMsg1) { +TEST_F(ComTest, test_sendMsg1) { EXPECT_CALL(*serialMock, write(_, _)); EXPECT_CALL(*serialMock, write(SLIP::END)); - s->sendMsg(test_msgid, "abc"); + com->sendMsg(test_msgid, "abc"); } -TEST_F(SerialEncodingTest, test_sendMsg2) { +TEST_F(ComTest, test_sendMsg2) { char buf[] = "abc\0"; EXPECT_CALL(*serialMock, write(_, _)); EXPECT_CALL(*serialMock, write(SLIP::END)); - s->sendMsg(test_msgid, buf); + com->sendMsg(test_msgid, buf); } diff --git a/test/test_hw_test.cpp b/test/test_hw_test.cpp deleted file mode 100644 index cff83502d..000000000 --- a/test/test_hw_test.cpp +++ /dev/null @@ -1,272 +0,0 @@ -/*!` - * \file test_hw_test.cpp - * - * This file is part of AYAB. - * - * AYAB is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * AYAB is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with AYAB. If not, see . - * - * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller - * Modified Work Copyright 2020 Sturla Lange, Tom Price - * http://ayab-knitting.com - */ - -#include - -//#include -#include -#include - -using ::testing::An; -using ::testing::AtLeast; -using ::testing::Return; - -// static char zero[2] = {'0', 0}; -// static char two[2] = {'2', 0}; -// static char g[2] = {'g', 0}; -// static char fAdE[5] = {'f', 'A', 'd', 'E', 0}; -// static char sixteen[3] = {'1', '6', 0}; - -// initialize static member -HardwareTestInterface *GlobalHardwareTest::m_instance = new HardwareTest(); - -class HardwareTestTest : public ::testing::Test { -protected: - void SetUp() override { - arduinoMock = arduinoMockInstance(); - serialMock = serialMockInstance(); - // serialCommandMock = serialCommandMockInstance(); - knitterMock = knitterMockInstance(); - h = new HardwareTest(); - } - - void TearDown() override { - releaseArduinoMock(); - releaseSerialMock(); - // releaseSerialCommandMock(); - releaseKnitterMock(); - } - - ArduinoMock *arduinoMock; - SerialMock *serialMock; - // SerialCommandMock *serialCommandMock; - KnitterMock *knitterMock; - HardwareTest *h; -}; - -TEST_F(HardwareTestTest, test_setUp) { - EXPECT_CALL(*knitterMock, setQuitFlag); - EXPECT_CALL(*arduinoMock, millis); - h->setUp(); - ASSERT_FALSE(h->m_autoReadOn); - ASSERT_FALSE(h->m_autoTestOn); - ASSERT_FALSE(h->m_timerEventOdd); -} - -TEST_F(HardwareTestTest, test_helpCmd) { - EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - .Times(AtLeast(1)); - h->helpCmd(); -} - -TEST_F(HardwareTestTest, test_sendCmd) { - EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - .Times(AtLeast(1)); - EXPECT_CALL(*knitterMock, send); - h->sendCmd(); -} - -TEST_F(HardwareTestTest, test_beepCmd) { - EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - .Times(AtLeast(1)); - EXPECT_CALL(*arduinoMock, analogWrite).Times(AtLeast(1)); - EXPECT_CALL(*arduinoMock, delay).Times(AtLeast(1)); - h->beepCmd(); -} - -TEST_F(HardwareTestTest, test_setSingleCmd_fail1) { - EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - .Times(AtLeast(1)); - // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(nullptr)); - // EXPECT_CALL(*knitterMock, setSolenoid).Times(0); - h->setSingleCmd(); -} - -TEST_F(HardwareTestTest, test_setSingleCmd_fail2) { - EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - .Times(AtLeast(1)); - // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(sixteen)); - // EXPECT_CALL(*knitterMock, setSolenoid).Times(0); - h->setSingleCmd(); -} - -TEST_F(HardwareTestTest, test_setSingleCmd_fail3) { - EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - .Times(AtLeast(1)); - // EXPECT_CALL(*serialCommandMock, next) - // .WillOnce(Return(zero)) - // .WillOnce(Return(nullptr)); - // EXPECT_CALL(*knitterMock, setSolenoid).Times(0); - h->setSingleCmd(); -} - -TEST_F(HardwareTestTest, test_setSingleCmd_fail4) { - EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - .Times(AtLeast(1)); - // EXPECT_CALL(*serialCommandMock, next) - // .WillOnce(Return(zero)) - // .WillOnce(Return(two)); - // EXPECT_CALL(*knitterMock, setSolenoid).Times(0); - h->setSingleCmd(); -} - -TEST_F(HardwareTestTest, test_setSingleCmd_success) { - EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - .Times(AtLeast(1)); - // EXPECT_CALL(*serialCommandMock, next).WillRepeatedly(Return(zero)); - // EXPECT_CALL(*knitterMock, setSolenoid); - h->setSingleCmd(); -} - -TEST_F(HardwareTestTest, test_setAllCmd_fail1) { - EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - .Times(AtLeast(1)); - // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(nullptr)); - // EXPECT_CALL(*knitterMock, setSolenoids).Times(0); - h->setAllCmd(); -} - -TEST_F(HardwareTestTest, test_setAllCmd_fail2) { - EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - .Times(AtLeast(1)); - // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(g)); - // EXPECT_CALL(*knitterMock, setSolenoids).Times(0); - h->setAllCmd(); -} - -TEST_F(HardwareTestTest, test_setAllCmd_success) { - EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - .Times(AtLeast(1)); - // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(fAdE)); - // EXPECT_CALL(*knitterMock, setSolenoids); - h->setAllCmd(); -} - -TEST_F(HardwareTestTest, test_readEOLsensorsCmd) { - EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - .Times(AtLeast(1)); - h->readEOLsensorsCmd(); -} - -TEST_F(HardwareTestTest, test_readEncodersCmd_low) { - EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - .Times(AtLeast(1)); - EXPECT_CALL(*arduinoMock, digitalRead).WillRepeatedly(Return(LOW)); - h->readEncodersCmd(); -} - -TEST_F(HardwareTestTest, test_readEncodersCmd_high) { - EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - .Times(AtLeast(1)); - EXPECT_CALL(*arduinoMock, digitalRead).WillRepeatedly(Return(HIGH)); - h->readEncodersCmd(); -} - -TEST_F(HardwareTestTest, test_autoReadCmd) { - EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - .Times(AtLeast(1)); - h->autoReadCmd(); -} - -TEST_F(HardwareTestTest, test_autoTestCmd) { - EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - .Times(AtLeast(1)); - h->autoTestCmd(); -} - -TEST_F(HardwareTestTest, test_stopCmd) { - h->m_autoReadOn = true; - h->m_autoTestOn = true; - h->stopCmd(); - ASSERT_FALSE(h->m_autoReadOn); - ASSERT_FALSE(h->m_autoTestOn); -} - -TEST_F(HardwareTestTest, test_quitCmd) { - EXPECT_CALL(*knitterMock, setQuitFlag); - h->quitCmd(); -} - -TEST_F(HardwareTestTest, test_unrecognizedCmd) { - EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - .Times(AtLeast(1)); - const char buffer[1] = {1}; - h->unrecognizedCmd(buffer); -} - -TEST_F(HardwareTestTest, test_loop_default) { - h->m_lastTime = 0; - EXPECT_CALL(*arduinoMock, millis).WillOnce(Return(499)); - h->loop(); -} - -TEST_F(HardwareTestTest, test_loop_null) { - h->m_lastTime = 0; - EXPECT_CALL(*arduinoMock, millis).WillOnce(Return(500)); - h->m_autoReadOn = false; - h->m_autoTestOn = false; - h->loop(); -} - -TEST_F(HardwareTestTest, test_loop_autoTestEven) { - h->m_lastTime = 0; - EXPECT_CALL(*arduinoMock, millis).WillOnce(Return(500)); - h->m_timerEventOdd = false; - h->m_autoReadOn = true; - h->m_autoTestOn = true; - EXPECT_CALL(*arduinoMock, digitalRead).Times(0); - EXPECT_CALL(*arduinoMock, digitalWrite).Times(2); - EXPECT_CALL(*knitterMock, setSolenoids); - h->loop(); -} - -TEST_F(HardwareTestTest, test_loop_autoTestOdd) { - h->m_lastTime = 0; - EXPECT_CALL(*arduinoMock, millis).WillOnce(Return(500)); - h->m_timerEventOdd = true; - h->m_autoReadOn = true; - h->m_autoTestOn = true; - EXPECT_CALL(*arduinoMock, digitalRead).Times(3); - EXPECT_CALL(*arduinoMock, digitalWrite).Times(2); - EXPECT_CALL(*knitterMock, setSolenoids); - h->loop(); -} - -/* -TEST_F(HardwareTestTest, test_scanHex) { - uint16_t result; - ASSERT_FALSE(h->scanHex(zero, 0, &result)); - ASSERT_FALSE(h->scanHex(g, 4, &result)); - ASSERT_FALSE(h->scanHex(g + 1, 1, &result)); - ASSERT_TRUE(h->scanHex(zero, 1, &result)); - ASSERT_TRUE(result == 0); - ASSERT_TRUE(h->scanHex(zero, 4, &result)); - ASSERT_TRUE(result == 0); - ASSERT_TRUE(h->scanHex(a, 1, &result)); - ASSERT_TRUE(result == 0xa); - ASSERT_TRUE(h->scanHex(A, 4, &result)); - ASSERT_TRUE(result == 0xA); - ASSERT_TRUE(h->scanHex(fAdE, 4, &result)); - ASSERT_TRUE(result == 0xfAdE); -} -*/ diff --git a/test/test_knitter.cpp b/test/test_knitter.cpp index c892293ab..8857c44c5 100644 --- a/test/test_knitter.cpp +++ b/test/test_knitter.cpp @@ -23,14 +23,15 @@ #include -#include #include -#include -#include -#include #include -#include +#include + +#include +#include +#include #include +#include using ::testing::_; using ::testing::AtLeast; @@ -38,62 +39,48 @@ using ::testing::Mock; using ::testing::Return; using ::testing::TypedEq; -// Global definition. -// References everywhere else must use `extern` -Knitter *knitter; - -// GlobalHardwareTest has static methods which cannot be mocked directly. -// These static methods delegate to non-static methods of a global instance -// implementing the HardwareTestInterface class (i.e. either HardwareTest or -// HardwareTestMock). Here we initialize the member of GlobalHardwareTest -// that points to this instance. This is a form of dependency injection. -HardwareTestInterface *GlobalHardwareTest::m_instance = new HardwareTestMock(); - -void onPacketReceived(const uint8_t *buffer, size_t size) { - (void)buffer; - (void)size; -} +extern Knitter *knitter; +extern BeeperMock *beeper; +extern ComMock *com; +extern TesterMock *tester; class KnitterTest : public ::testing::Test { protected: void SetUp() override { arduinoMock = arduinoMockInstance(); - beeperMock = beeperMockInstance(); solenoidsMock = solenoidsMockInstance(); encodersMock = encodersMockInstance(); - serialEncodingMock = serialEncodingMockInstance(); - // Pointer to global HardwareTestMock instance - hwTestMock = - static_cast(GlobalHardwareTest::m_instance); + // pointers to global instances + beeperMock = beeper; + comMock = com; + testerMock = tester; - // The global instance does not get destroyed at the end of each test. - // Ordinarily the mock instance would be local and such behaviour would + // The global instances do not get destroyed at the end of each test. + // Ordinarily the mock instances would be local and such behaviour would // cause a memory leak. We must notify the test that this is not the case. - Mock::AllowLeak(hwTestMock); + Mock::AllowLeak(beeperMock); + Mock::AllowLeak(comMock); + Mock::AllowLeak(testerMock); - expect_constructor(); - k = new Knitter(); + expect_init(); + knitter->init(); } void TearDown() override { releaseArduinoMock(); - releaseBeeperMock(); releaseSolenoidsMock(); releaseEncodersMock(); - releaseSerialEncodingMock(); - releaseHardwareTestMock(); } ArduinoMock *arduinoMock; BeeperMock *beeperMock; - SolenoidsMock *solenoidsMock; + ComMock *comMock; EncodersMock *encodersMock; - SerialEncodingMock *serialEncodingMock; - HardwareTestMock *hwTestMock; // pointer to global instance - Knitter *&k = knitter; // alias of global `knitter` + SolenoidsMock *solenoidsMock; + TesterMock *testerMock; - void expect_constructor() { + void expect_init() { EXPECT_CALL(*arduinoMock, pinMode(ENC_PIN_A, INPUT)); EXPECT_CALL(*arduinoMock, pinMode(ENC_PIN_B, INPUT)); EXPECT_CALL(*arduinoMock, pinMode(ENC_PIN_C, INPUT)); @@ -120,7 +107,7 @@ class KnitterTest : public ::testing::Test { void expected_isr(uint16_t pos, Direction_t dir, Direction_t hall, Beltshift_t belt, Carriage_t carriage) { expect_isr(pos, dir, hall, belt, carriage); - k->isr(); + knitter->isr(); } void expect_isr(Direction_t dir, Direction_t hall) { @@ -129,7 +116,7 @@ class KnitterTest : public ::testing::Test { void expected_isr(Direction_t dir, Direction_t hall) { expect_isr(dir, hall); - k->isr(); + knitter->isr(); } void expect_isr(uint16_t pos) { @@ -138,7 +125,7 @@ class KnitterTest : public ::testing::Test { void expected_isr(uint16_t pos) { expect_isr(pos); - k->isr(); + knitter->isr(); } void expected_isr() { @@ -146,7 +133,7 @@ class KnitterTest : public ::testing::Test { } template void expect_send() { - EXPECT_CALL(*serialEncodingMock, send).Times(times); + EXPECT_CALL(*comMock, send).Times(times); } template void expect_indState() { @@ -157,18 +144,18 @@ class KnitterTest : public ::testing::Test { } void expect_fsm() { - EXPECT_CALL(*serialEncodingMock, update); + EXPECT_CALL(*comMock, update); } void expected_fsm() { expect_fsm(); - k->fsm(); + knitter->fsm(); } void get_to_ready() { // machine is initialized when left hall sensor // is passed in Right direction inside active needles - Machine_t m = k->getMachineType(); + Machine_t m = knitter->getMachineType(); expected_isr(40 + END_OF_LINE_OFFSET_L[m] + 1); // initialize @@ -182,7 +169,7 @@ class KnitterTest : public ::testing::Test { uint8_t pattern[] = {1}; EXPECT_CALL(*beeperMock, ready); EXPECT_CALL(*encodersMock, init); - k->startOperation(m, 0, NUM_NEEDLES[m] - 1, pattern, false); + knitter->startOperation(m, 0, NUM_NEEDLES[m] - 1, pattern, false); } void expected_operate(bool first) { @@ -198,16 +185,18 @@ class KnitterTest : public ::testing::Test { void expected_test() { expect_indState(); - EXPECT_CALL(*hwTestMock, loop); + EXPECT_CALL(*testerMock, loop); expected_fsm(); - ASSERT_TRUE(Mock::VerifyAndClear(hwTestMock)); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(testerMock)); } void expected_set_machine(Machine_t machineType) { - k->setMachineType(machineType); + knitter->setMachineType(machineType); EXPECT_CALL(*encodersMock, init); encodersMock->init(machineType); - ASSERT_EQ(k->startTest(machineType), true); + ASSERT_EQ(knitter->startTest(machineType), true); } void test_operate_line_request() { @@ -217,7 +206,7 @@ class KnitterTest : public ::testing::Test { // Position has changed since last call to operate function // `m_pixelToSet` is set above `m_stopNeedle` + END_OF_LINE_OFFSET_R - Machine_t m = k->getMachineType(); // Kh910 + Machine_t m = knitter->getMachineType(); // Kh910 expected_isr(NUM_NEEDLES[m] + 8 + END_OF_LINE_OFFSET_R[m] + 1); EXPECT_CALL(*solenoidsMock, setSolenoid); @@ -229,195 +218,184 @@ class KnitterTest : public ::testing::Test { } }; -/*! - * \test - */ -TEST_F(KnitterTest, test_constructor) { - ASSERT_EQ(k->getState(), s_init); - // NOTE: probing private data! - ASSERT_EQ(k->m_startNeedle, 0); +TEST_F(KnitterTest, test_init) { + ASSERT_EQ(knitter->getState(), s_init); + ASSERT_EQ(knitter->m_startNeedle, 0); } -/*! - * \test - * - * \todo sl: Maybe mock packet serial? - */ TEST_F(KnitterTest, test_send) { uint8_t p[] = {1, 2, 3, 4, 5}; expect_send(); - k->send(p, 5); + comMock->send(p, 5); } -/*! - * \test - */ TEST_F(KnitterTest, test_isr) { expected_isr(1); } -/*! - * \test - */ +TEST_F(KnitterTest, test_setSolenoids) { + EXPECT_CALL(*solenoidsMock, setSolenoids); + knitter->setSolenoids(0xFADE); +} + TEST_F(KnitterTest, test_fsm_default_case) { - k->setState(static_cast(4)); + knitter->setState(static_cast(4)); expected_fsm(); } -/*! - * \test - */ -TEST_F(KnitterTest, test_fsm_init) { +TEST_F(KnitterTest, test_fsm_init_LL) { // not ready expected_isr(Left, Left); expected_fsm(); - ASSERT_EQ(k->getState(), s_init); + ASSERT_EQ(knitter->getState(), s_init); +} +TEST_F(KnitterTest, test_fsm_init_RR) { // still not ready expected_isr(Right, Right); expected_fsm(); - ASSERT_EQ(k->getState(), s_init); + ASSERT_EQ(knitter->getState(), s_init); +} +TEST_F(KnitterTest, test_fsm_init_RL) { // ready expected_isr(Right, Left); EXPECT_CALL(*solenoidsMock, setSolenoids(0xFFFF)); expect_indState(); expected_fsm(); - ASSERT_EQ(k->getState(), s_ready); + ASSERT_EQ(knitter->getState(), s_ready); } -/*! - * \test - */ TEST_F(KnitterTest, test_fsm_ready) { get_to_ready(); EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 0)); expected_fsm(); // still in ready state - ASSERT_EQ(k->getState(), s_ready); + ASSERT_EQ(knitter->getState(), s_ready); // again EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 0)); expected_fsm(); // still in ready state - ASSERT_EQ(k->getState(), s_ready); + ASSERT_EQ(knitter->getState(), s_ready); } -/*! - * \test - */ -TEST_F(KnitterTest, test_fsm_hwtest) { +TEST_F(KnitterTest, test_fsm_test) { // enter test state - EXPECT_CALL(*hwTestMock, setUp); - ASSERT_EQ(k->startTest(Kh910), true); + EXPECT_CALL(*testerMock, setUp); + ASSERT_EQ(knitter->startTest(Kh910), true); expected_isr(); expect_indState(); - EXPECT_CALL(*hwTestMock, loop); + EXPECT_CALL(*testerMock, loop); expected_fsm(); // again with same position, no `indState` this time. expected_isr(); expect_indState<0>(); - EXPECT_CALL(*hwTestMock, loop); + EXPECT_CALL(*testerMock, loop); expected_fsm(); - ASSERT_TRUE(Mock::VerifyAndClear(hwTestMock)); + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(testerMock)); +} + +TEST_F(KnitterTest, test_fsm_test_quit) { + // enter test state + EXPECT_CALL(*testerMock, setUp); + ASSERT_EQ(knitter->startTest(Kh910), true); + + // quit + EXPECT_CALL(*testerMock, getQuitFlag).WillOnce(Return(true)); + expected_fsm(); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(testerMock)); } -/*! - * \test - */ TEST_F(KnitterTest, test_startOperation_NoMachine) { uint8_t pattern[] = {1}; - ASSERT_EQ(k->getState(), s_init); - Machine_t m = k->getMachineType(); + ASSERT_EQ(knitter->getState(), s_init); + Machine_t m = knitter->getMachineType(); ASSERT_EQ(m, NoMachine); get_to_ready(); - ASSERT_EQ(k->startOperation(m, 0, NUM_NEEDLES[m] - 1, pattern, false), false); + ASSERT_EQ(knitter->startOperation(m, 0, NUM_NEEDLES[m] - 1, pattern, false), + false); } -/*! - * \test - */ TEST_F(KnitterTest, test_startOperation_notReady) { uint8_t pattern[] = {1}; - ASSERT_EQ(k->getState(), s_init); - ASSERT_EQ(k->startOperation(Kh910, 0, NUM_NEEDLES[Kh910] - 1, pattern, false), - false); + ASSERT_EQ(knitter->getState(), s_init); + ASSERT_EQ( + knitter->startOperation(Kh910, 0, NUM_NEEDLES[Kh910] - 1, pattern, false), + false); } -/*! - * \test - */ TEST_F(KnitterTest, test_startOperation_Kh910) { uint8_t pattern[] = {1}; get_to_ready(); EXPECT_CALL(*encodersMock, init); EXPECT_CALL(*beeperMock, ready); - ASSERT_EQ(k->startOperation(Kh910, 0, NUM_NEEDLES[Kh910] - 1, pattern, false), - true); + ASSERT_EQ( + knitter->startOperation(Kh910, 0, NUM_NEEDLES[Kh910] - 1, pattern, false), + true); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); } -/*! - * \test - */ TEST_F(KnitterTest, test_startOperation_Kh270) { uint8_t pattern[] = {1}; get_to_ready(); EXPECT_CALL(*encodersMock, init); EXPECT_CALL(*beeperMock, ready); - ASSERT_EQ(k->startOperation(Kh270, 0, NUM_NEEDLES[Kh270] - 1, pattern, false), - true); + ASSERT_EQ( + knitter->startOperation(Kh270, 0, NUM_NEEDLES[Kh270] - 1, pattern, false), + true); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); } -/*! - * \test - */ TEST_F(KnitterTest, test_startOperation_failures) { uint8_t pattern[] = {1}; get_to_ready(); // `m_stopNeedle` lower than `m_startNeedle` - ASSERT_EQ(k->startOperation(Kh910, 1, 0, pattern, false), false); + ASSERT_EQ(knitter->startOperation(Kh910, 1, 0, pattern, false), false); // `m_stopNeedle` out of range - ASSERT_EQ(k->startOperation(Kh910, 0, NUM_NEEDLES[Kh910], pattern, false), - false); + ASSERT_EQ( + knitter->startOperation(Kh910, 0, NUM_NEEDLES[Kh910], pattern, false), + false); // null pattern - ASSERT_EQ(k->startOperation(Kh910, 0, NUM_NEEDLES[Kh910] - 1, nullptr, false), - false); + ASSERT_EQ( + knitter->startOperation(Kh910, 0, NUM_NEEDLES[Kh910] - 1, nullptr, false), + false); } -/*! - * \test - */ TEST_F(KnitterTest, test_startTest) { - EXPECT_CALL(*hwTestMock, setUp); - ASSERT_EQ(k->startTest(Kh910), true); - ASSERT_EQ(k->getState(), s_test); - ASSERT_TRUE(Mock::VerifyAndClear(hwTestMock)); + EXPECT_CALL(*testerMock, setUp); + ASSERT_EQ(knitter->startTest(Kh910), true); + ASSERT_EQ(knitter->getState(), s_test); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(testerMock)); } -/*! - * \test - */ TEST_F(KnitterTest, test_startTest_in_operation) { get_to_operate(Kh910); - ASSERT_EQ(k->getState(), s_operate); + ASSERT_EQ(knitter->getState(), s_operate); // can't start test - ASSERT_EQ(k->startTest(Kh910), false); + ASSERT_EQ(knitter->startTest(Kh910), false); } -/*! - * \test - */ TEST_F(KnitterTest, test_setNextLine) { - ASSERT_EQ(k->setNextLine(1), false); + ASSERT_EQ(knitter->setNextLine(1), false); // set `m_lineRequested` EXPECT_CALL(*solenoidsMock, setSolenoid).Times(1); @@ -427,24 +405,24 @@ TEST_F(KnitterTest, test_setNextLine) { expected_isr(40 + NUM_NEEDLES[Kh910] - 1 + END_OF_LINE_OFFSET_R[Kh910] + 1); EXPECT_CALL(*solenoidsMock, setSolenoid).Times(1); expected_operate(false); - ASSERT_EQ(k->getState(), s_operate); + ASSERT_EQ(knitter->getState(), s_operate); // wrong line number EXPECT_CALL(*beeperMock, finishedLine).Times(0); expect_send(); - ASSERT_EQ(k->setNextLine(1), false); + ASSERT_EQ(knitter->setNextLine(1), false); // correct line number EXPECT_CALL(*beeperMock, finishedLine).Times(1); - ASSERT_EQ(k->setNextLine(0), true); + ASSERT_EQ(knitter->setNextLine(0), true); // `m_lineRequested` has been set to `false` - ASSERT_EQ(k->setNextLine(0), false); + ASSERT_EQ(knitter->setNextLine(0), false); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); } -/*! - * \test - */ TEST_F(KnitterTest, test_operate_Kh910) { // `m_pixelToSet` gets set to 0 expected_isr(8); @@ -464,7 +442,7 @@ TEST_F(KnitterTest, test_operate_Kh910) { const uint8_t START_NEEDLE = NUM_NEEDLES[Kh910] - 2; const uint8_t STOP_NEEDLE = NUM_NEEDLES[Kh910] - 1; const uint8_t OFFSET = END_OF_LINE_OFFSET_R[Kh910]; - k->startOperation(Kh910, START_NEEDLE, STOP_NEEDLE, pattern, true); + knitter->startOperation(Kh910, START_NEEDLE, STOP_NEEDLE, pattern, true); // first operate EXPECT_CALL(*arduinoMock, delay(2000)); @@ -493,11 +471,11 @@ TEST_F(KnitterTest, test_operate_Kh910) { EXPECT_CALL(*solenoidsMock, setSolenoid); expect_indState(); expected_operate(false); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); } -/*! - * \test - */ TEST_F(KnitterTest, test_operate_Kh270) { // `m_pixelToSet` gets set to 0 expected_isr(8); @@ -517,7 +495,7 @@ TEST_F(KnitterTest, test_operate_Kh270) { const uint8_t START_NEEDLE = NUM_NEEDLES[Kh270] - 2; const uint8_t STOP_NEEDLE = NUM_NEEDLES[Kh270] - 1; const uint8_t OFFSET = END_OF_LINE_OFFSET_R[Kh270]; - k->startOperation(Kh270, START_NEEDLE, STOP_NEEDLE, pattern, true); + knitter->startOperation(Kh270, START_NEEDLE, STOP_NEEDLE, pattern, true); // first operate EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_B, 0)) @@ -552,11 +530,11 @@ TEST_F(KnitterTest, test_operate_Kh270) { EXPECT_CALL(*solenoidsMock, setSolenoid); expect_indState(); expected_operate(false); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); } -/*! - * \test - */ TEST_F(KnitterTest, test_operate_line_request) { EXPECT_CALL(*solenoidsMock, setSolenoid); @@ -575,9 +553,6 @@ TEST_F(KnitterTest, test_operate_line_request) { expected_operate(false); } -/*! - * \test - */ TEST_F(KnitterTest, test_operate_lastline) { EXPECT_CALL(*solenoidsMock, setSolenoid); @@ -589,46 +564,47 @@ TEST_F(KnitterTest, test_operate_lastline) { expected_isr(NUM_NEEDLES[Kh910] + 8 + END_OF_LINE_OFFSET_R[Kh910] + 1); // `m_lastLineFlag` is `true` - k->setLastLine(); + knitter->setLastLine(); EXPECT_CALL(*solenoidsMock, setSolenoid); EXPECT_CALL(*beeperMock, endWork); EXPECT_CALL(*solenoidsMock, setSolenoids(0xFFFF)); EXPECT_CALL(*beeperMock, finishedLine); expected_operate(false); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); } -/*! - * \test - */ TEST_F(KnitterTest, test_operate_lastline_and_no_req) { get_to_operate(Kh910); // Note: probing private data and methods to get full branch coverage. - k->m_stopNeedle = 100; - uint8_t wanted_pixel = k->m_stopNeedle + END_OF_LINE_OFFSET_R[Kh910] + 1; - k->m_firstRun = false; - k->m_direction = Left; - k->m_position = wanted_pixel + k->getStartOffset(Right); - k->m_workedOnLine = true; - k->m_lineRequested = false; - k->m_lastLineFlag = true; + knitter->m_stopNeedle = 100; + uint8_t wanted_pixel = + knitter->m_stopNeedle + END_OF_LINE_OFFSET_R[Kh910] + 1; + knitter->m_firstRun = false; + knitter->m_direction = Left; + knitter->m_position = wanted_pixel + knitter->getStartOffset(Right); + knitter->m_workedOnLine = true; + knitter->m_lineRequested = false; + knitter->m_lastLineFlag = true; EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 1)); EXPECT_CALL(*solenoidsMock, setSolenoid); EXPECT_CALL(*beeperMock, endWork); EXPECT_CALL(*solenoidsMock, setSolenoids(0xFFFF)); EXPECT_CALL(*beeperMock, finishedLine); - k->state_operate(); + knitter->state_operate(); - ASSERT_EQ(k->getStartOffset(NUM_DIRECTIONS), 0); - k->m_carriage = NUM_CARRIAGES; - ASSERT_EQ(k->getStartOffset(Right), 0); + ASSERT_EQ(knitter->getStartOffset(NUM_DIRECTIONS), 0); + knitter->m_carriage = NUM_CARRIAGES; + ASSERT_EQ(knitter->getStartOffset(Right), 0); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); } -/*! - * \test - */ TEST_F(KnitterTest, test_operate_same_position) { EXPECT_CALL(*solenoidsMock, setSolenoid); expected_operate(true); @@ -638,9 +614,6 @@ TEST_F(KnitterTest, test_operate_same_position) { expected_operate(false); } -/*! - * \test - */ TEST_F(KnitterTest, test_operate_new_line) { EXPECT_CALL(*solenoidsMock, setSolenoid); @@ -653,20 +626,20 @@ TEST_F(KnitterTest, test_operate_new_line) { // set `m_lineRequested` to `false` EXPECT_CALL(*beeperMock, finishedLine); - k->setNextLine(0); + knitter->setNextLine(0); EXPECT_CALL(*solenoidsMock, setSolenoid); // `reqLine()` is called which calls `send()` expect_send(); expected_operate(false); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); } -/*! - * \test - */ TEST_F(KnitterTest, test_calculatePixelAndSolenoid) { - EXPECT_CALL(*hwTestMock, setUp); + EXPECT_CALL(*testerMock, setUp); expected_set_machine(Kh910); // new position, different beltshift and active hall @@ -706,7 +679,7 @@ TEST_F(KnitterTest, test_calculatePixelAndSolenoid) { expected_test(); // KH270 - k->setMachineType(Kh270); + knitter->setMachineType(Kh270); // K carriage direction left expected_isr(0, Left, Right, Regular, Knit); @@ -717,90 +690,26 @@ TEST_F(KnitterTest, test_calculatePixelAndSolenoid) { expected_test(); // test expectations without destroying instance - ASSERT_TRUE(Mock::VerifyAndClear(hwTestMock)); + ASSERT_TRUE(Mock::VerifyAndClear(testerMock)); } -/*! - * \test - */ TEST_F(KnitterTest, test_getStartOffset) { // out of range values - k->m_carriage = Knit; - ASSERT_EQ(k->getStartOffset(NoDirection), 0); + knitter->m_carriage = Knit; + ASSERT_EQ(knitter->getStartOffset(NoDirection), 0); - ASSERT_EQ(k->getStartOffset(NUM_DIRECTIONS), 0); + ASSERT_EQ(knitter->getStartOffset(NUM_DIRECTIONS), 0); - k->m_carriage = NoCarriage; - ASSERT_EQ(k->getStartOffset(Left), 0); + knitter->m_carriage = NoCarriage; + ASSERT_EQ(knitter->getStartOffset(Left), 0); - k->m_carriage = NUM_CARRIAGES; - ASSERT_EQ(k->getStartOffset(Right), 0); + knitter->m_carriage = NUM_CARRIAGES; + ASSERT_EQ(knitter->getStartOffset(Right), 0); - k->m_carriage = Lace; - k->m_machineType = NoMachine; - ASSERT_EQ(k->getStartOffset(Left), 0); + knitter->m_carriage = Lace; + knitter->m_machineType = NoMachine; + ASSERT_EQ(knitter->getStartOffset(Left), 0); - k->m_machineType = NUM_MACHINES; - ASSERT_EQ(k->getStartOffset(Right), 0); -} - -/*! - * \test - */ -TEST_F(KnitterTest, test_onPacketReceived) { - EXPECT_CALL(*serialEncodingMock, onPacketReceived); - k->onPacketReceived(nullptr, 0); -} - -/*! - * \test - */ -TEST_F(KnitterTest, test_quit_hw_test) { - // get to test state - EXPECT_CALL(*hwTestMock, setUp); - ASSERT_EQ(k->startTest(Kh910), true); - ASSERT_EQ(k->getState(), s_test); - - // quit - k->setQuitFlag(true); - EXPECT_CALL(*hwTestMock, loop); - k->state_test(); - ASSERT_EQ(k->getState(), s_ready); - - // test expectations without destroying instance - ASSERT_TRUE(Mock::VerifyAndClear(hwTestMock)); -} - -/*! - * \test - */ -TEST_F(KnitterTest, test_setSolenoids) { - EXPECT_CALL(*solenoidsMock, setSolenoids); - k->setSolenoids(0xFADE); -} - -/*! - * \test - */ -TEST_F(KnitterTest, test_setSolenoid) { - EXPECT_CALL(*solenoidsMock, setSolenoid); - k->setSolenoid(15, 1); -} - -/*! - * \test - */ -TEST_F(KnitterTest, test_sendMsg1) { - EXPECT_CALL(*serialEncodingMock, - sendMsg(test_msgid, TypedEq("abc"))); - k->sendMsg(test_msgid, "abc"); -} - -/*! - * \test - */ -TEST_F(KnitterTest, test_sendMsg2) { - char buf[] = "abc\0"; - EXPECT_CALL(*serialEncodingMock, sendMsg(test_msgid, TypedEq(buf))); - k->sendMsg(test_msgid, buf); + knitter->m_machineType = NUM_MACHINES; + ASSERT_EQ(knitter->getStartOffset(Right), 0); } diff --git a/test/test_solenoids.cpp b/test/test_solenoids.cpp index f8d73421c..6f9136a35 100644 --- a/test/test_solenoids.cpp +++ b/test/test_solenoids.cpp @@ -58,12 +58,25 @@ TEST_F(SolenoidsTest, test_init) { s.init(); } -TEST_F(SolenoidsTest, test_setSolenoid) { - s.setSolenoid(1, true); - s.setSolenoid(1, false); - s.setSolenoid(16, false); +TEST_F(SolenoidsTest, test_setSolenoid1) { + s.setSolenoids(0); + ASSERT_EQ(s.solenoidState, 0U); + s.setSolenoid(0, true); + ASSERT_EQ(s.solenoidState, 1U); +} + +TEST_F(SolenoidsTest, test_setSolenoid2) { + s.setSolenoids(0); + ASSERT_EQ(s.solenoidState, 0U); + s.setSolenoids(0); + ASSERT_EQ(s.solenoidState, 0U); + s.setSolenoid(0, false); + ASSERT_EQ(s.solenoidState, 0U); } -TEST_F(SolenoidsTest, test_setSolenoids) { - s.setSolenoids(0xFFFF); +TEST_F(SolenoidsTest, test_setSolenoid3) { + s.setSolenoids(0x8000); + ASSERT_EQ(s.solenoidState, 0x8000U); + s.setSolenoid(16, false); + ASSERT_EQ(s.solenoidState, 0x8000U); } diff --git a/test/test_tester.cpp b/test/test_tester.cpp new file mode 100644 index 000000000..7fab8058e --- /dev/null +++ b/test/test_tester.cpp @@ -0,0 +1,280 @@ +/*!` + * \file test_tester.cpp + * + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#include + +//#include +#include +#include + +#include + +using ::testing::An; +using ::testing::AtLeast; +using ::testing::Mock; +using ::testing::Return; + +extern Tester *tester; +extern KnitterMock *knitter; + +// static char zero[2] = {'0', 0}; +// static char two[2] = {'2', 0}; +// static char g[2] = {'g', 0}; +// static char fAdE[5] = {'f', 'A', 'd', 'E', 0}; +// static char sixteen[3] = {'1', '6', 0}; + +class TesterTest : public ::testing::Test { +protected: + void SetUp() override { + arduinoMock = arduinoMockInstance(); + serialMock = serialMockInstance(); + // serialCommandMock = serialCommandMockInstance(); + + // pointer to global instance + knitterMock = knitter; + + // The global instance does not get destroyed at the end of each test. + // Ordinarily the mock instance would be local and such behaviour would + // cause a memory leak. We must notify the test that this is not the case. + Mock::AllowLeak(knitterMock); + } + + void TearDown() override { + releaseArduinoMock(); + releaseSerialMock(); + // releaseSerialCommandMock(); + releaseKnitterMock(); + } + + ArduinoMock *arduinoMock; + SerialMock *serialMock; + // SerialCommandMock *serialCommandMock; + KnitterMock *knitterMock; +}; + +TEST_F(TesterTest, test_setUp) { + EXPECT_CALL(*arduinoMock, millis); + tester->setUp(); + ASSERT_FALSE(tester->m_autoReadOn); + ASSERT_FALSE(tester->m_autoTestOn); + ASSERT_FALSE(tester->m_timerEventOdd); + ASSERT_FALSE(tester->getQuitFlag()); +} + +TEST_F(TesterTest, test_helpCmd) { + // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + // .Times(AtLeast(1)); + tester->helpCmd(); +} + +TEST_F(TesterTest, test_sendCmd) { + // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + // .Times(AtLeast(1)); + // EXPECT_CALL(*knitterMock, send); + tester->sendCmd(); +} + +TEST_F(TesterTest, test_beepCmd) { + // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + // .Times(AtLeast(1)); + EXPECT_CALL(*arduinoMock, analogWrite).Times(AtLeast(1)); + EXPECT_CALL(*arduinoMock, delay).Times(AtLeast(1)); + tester->beepCmd(); +} + +TEST_F(TesterTest, test_setSingleCmd_fail1) { + // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + // .Times(AtLeast(1)); + // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(nullptr)); + EXPECT_CALL(*knitterMock, setSolenoids).Times(0); + tester->setSingleCmd(); +} + +TEST_F(TesterTest, test_setSingleCmd_fail2) { + // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + // .Times(AtLeast(1)); + // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(sixteen)); + EXPECT_CALL(*knitterMock, setSolenoids).Times(0); + tester->setSingleCmd(); +} + +TEST_F(TesterTest, test_setSingleCmd_fail3) { + // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + // .Times(AtLeast(1)); + // EXPECT_CALL(*serialCommandMock, next) + // .WillOnce(Return(zero)) + // .WillOnce(Return(nullptr)); + EXPECT_CALL(*knitterMock, setSolenoids).Times(0); + tester->setSingleCmd(); +} + +TEST_F(TesterTest, test_setSingleCmd_fail4) { + // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + // .Times(AtLeast(1)); + // EXPECT_CALL(*serialCommandMock, next) + // .WillOnce(Return(zero)) + // .WillOnce(Return(two)); + EXPECT_CALL(*knitterMock, setSolenoids).Times(0); + tester->setSingleCmd(); +} + +TEST_F(TesterTest, test_setSingleCmd_success) { + // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + // .Times(AtLeast(1)); + // EXPECT_CALL(*serialCommandMock, next).WillRepeatedly(Return(zero)); + EXPECT_CALL(*knitterMock, setSolenoids); + tester->setSingleCmd(); +} + +TEST_F(TesterTest, test_setAllCmd_fail1) { + // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + // .Times(AtLeast(1)); + // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(nullptr)); + EXPECT_CALL(*knitterMock, setSolenoids).Times(0); + tester->setAllCmd(); +} + +TEST_F(TesterTest, test_setAllCmd_fail2) { + // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + // .Times(AtLeast(1)); + // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(g)); + EXPECT_CALL(*knitterMock, setSolenoids).Times(0); + tester->setAllCmd(); +} + +TEST_F(TesterTest, test_setAllCmd_success) { + // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + // .Times(AtLeast(1)); + // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(fAdE)); + EXPECT_CALL(*knitterMock, setSolenoids); + tester->setAllCmd(); +} + +TEST_F(TesterTest, test_readEOLsensorsCmd) { + // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + // .Times(AtLeast(1)); + tester->readEOLsensorsCmd(); +} + +TEST_F(TesterTest, test_readEncodersCmd_low) { + // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + // .Times(AtLeast(1)); + EXPECT_CALL(*arduinoMock, digitalRead).WillRepeatedly(Return(LOW)); + tester->readEncodersCmd(); +} + +TEST_F(TesterTest, test_readEncodersCmd_high) { + // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + // .Times(AtLeast(1)); + EXPECT_CALL(*arduinoMock, digitalRead).WillRepeatedly(Return(HIGH)); + tester->readEncodersCmd(); +} + +TEST_F(TesterTest, test_autoReadCmd) { + // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + // .Times(AtLeast(1)); + tester->autoReadCmd(); +} + +TEST_F(TesterTest, test_autoTestCmd) { + // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + // .Times(AtLeast(1)); + tester->autoTestCmd(); +} + +TEST_F(TesterTest, test_stopCmd) { + tester->m_autoReadOn = true; + tester->m_autoTestOn = true; + tester->stopCmd(); + ASSERT_FALSE(tester->m_autoReadOn); + ASSERT_FALSE(tester->m_autoTestOn); +} + +TEST_F(TesterTest, test_quitCmd) { + tester->quitCmd(); + ASSERT_TRUE(tester->m_quit); +} + +TEST_F(TesterTest, test_unrecognizedCmd) { + // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) + // .Times(AtLeast(1)); + const char buffer[1] = {1}; + tester->unrecognizedCmd(buffer); +} + +TEST_F(TesterTest, test_loop_default) { + tester->m_lastTime = 0; + EXPECT_CALL(*arduinoMock, millis).WillOnce(Return(499)); + tester->loop(); +} + +TEST_F(TesterTest, test_loop_null) { + tester->m_lastTime = 0; + EXPECT_CALL(*arduinoMock, millis).WillOnce(Return(500)); + tester->m_autoReadOn = false; + tester->m_autoTestOn = false; + tester->loop(); +} + +TEST_F(TesterTest, test_loop_autoTestEven) { + tester->m_lastTime = 0; + EXPECT_CALL(*arduinoMock, millis).WillOnce(Return(500)); + tester->m_timerEventOdd = false; + tester->m_autoReadOn = true; + tester->m_autoTestOn = true; + EXPECT_CALL(*arduinoMock, digitalRead).Times(0); + EXPECT_CALL(*arduinoMock, digitalWrite).Times(2); + EXPECT_CALL(*knitterMock, setSolenoids); + tester->loop(); +} + +TEST_F(TesterTest, test_loop_autoTestOdd) { + tester->m_lastTime = 0; + EXPECT_CALL(*arduinoMock, millis).WillOnce(Return(500)); + tester->m_timerEventOdd = true; + tester->m_autoReadOn = true; + tester->m_autoTestOn = true; + EXPECT_CALL(*arduinoMock, digitalRead).Times(3); + EXPECT_CALL(*arduinoMock, digitalWrite).Times(2); + EXPECT_CALL(*knitterMock, setSolenoids); + tester->loop(); +} + +/* +TEST_F(TesterTest, test_scanHex) { + uint16_t result; + ASSERT_FALSE(tester->scanHex(zero, 0, &result)); + ASSERT_FALSE(tester->scanHex(g, 4, &result)); + ASSERT_FALSE(tester->scanHex(g + 1, 1, &result)); + ASSERT_TRUE(tester->scanHex(zero, 1, &result)); + ASSERT_TRUE(result == 0); + ASSERT_TRUE(tester->scanHex(zero, 4, &result)); + ASSERT_TRUE(result == 0); + ASSERT_TRUE(tester->scanHex(a, 1, &result)); + ASSERT_TRUE(result == 0xa); + ASSERT_TRUE(tester->scanHex(A, 4, &result)); + ASSERT_TRUE(result == 0xA); + ASSERT_TRUE(tester->scanHex(fAdE, 4, &result)); + ASSERT_TRUE(result == 0xfAdE); +} +*/ From a7987f94f4d9c4977aca2aa195fe1cfee27bc644 Mon Sep 17 00:00:00 2001 From: Tom Date: Sat, 22 Aug 2020 02:16:28 -0400 Subject: [PATCH 08/17] Add GlobalSolenoids class. Change API for hardware test commands. --- src/ayab/beeper.cpp | 3 +- src/ayab/beeper.h | 4 +- src/ayab/board.h | 4 +- src/ayab/com.cpp | 49 +++++++- src/ayab/com.h | 22 +++- src/ayab/global_knitter.cpp | 14 +-- src/ayab/global_solenoids.cpp | 37 ++++++ src/ayab/global_tester.cpp | 8 +- src/ayab/knitter.cpp | 35 +++--- src/ayab/knitter.h | 32 +++-- src/ayab/main.cpp | 9 +- src/ayab/solenoids.cpp | 4 +- src/ayab/solenoids.h | 37 +++++- src/ayab/tester.cpp | 216 ++++++++++++---------------------- src/ayab/tester.h | 21 ++-- test/CMakeLists.txt | 5 +- test/mocks/knitter_mock.cpp | 16 +-- test/mocks/knitter_mock.h | 7 +- test/mocks/solenoids_mock.h | 6 +- test/mocks/tester_mock.cpp | 8 +- test/mocks/tester_mock.h | 4 +- test/test_all.cpp | 3 + test/test_boards.cpp | 2 + test/test_com.cpp | 76 ++++++++++-- test/test_knitter.cpp | 177 ++++++++++++++++------------ test/test_tester.cpp | 54 +++------ 26 files changed, 481 insertions(+), 372 deletions(-) create mode 100644 src/ayab/global_solenoids.cpp diff --git a/src/ayab/beeper.cpp b/src/ayab/beeper.cpp index 680179b64..eb112b36f 100644 --- a/src/ayab/beeper.cpp +++ b/src/ayab/beeper.cpp @@ -1,6 +1,7 @@ /*! * \file beeper.cpp - * \brief Singleton class containing methods for beeper. + * \brief Singleton class containing methods to actuate a beeper + * connected to PIEZO_PIN. * * This file is part of AYAB. * diff --git a/src/ayab/beeper.h b/src/ayab/beeper.h index f23ed3d7a..4439a052f 100644 --- a/src/ayab/beeper.h +++ b/src/ayab/beeper.h @@ -46,7 +46,7 @@ class BeeperInterface { virtual void endWork() = 0; }; -// Container class for the static methods that implement the serial API. +// Container class for the static methods that control the beeper. // Dependency injection is enabled using a pointer to a global instance of // either `Beeper` or `BeeperMock`, both of which classes implement the // pure virtual methods of `BeeperInterface`. @@ -66,7 +66,7 @@ class GlobalBeeper final { }; /*! - * Class to actuate a beeper connected to PIEZO_PIN + * \brief Class to actuate a beeper connected to PIEZO_PIN */ class Beeper : public BeeperInterface { public: diff --git a/src/ayab/board.h b/src/ayab/board.h index 9ff6aafcf..7c20f5de0 100644 --- a/src/ayab/board.h +++ b/src/ayab/board.h @@ -48,7 +48,7 @@ constexpr uint8_t I2Caddr_sol1_8 = 0x0U; ///< I2C Address of solenoids 1 - 8 constexpr uint8_t I2Caddr_sol9_16 = 0x1U; ///< I2C Address of solenoids 9 - 16 // TODO(Who?): Optimize Delay for various Arduino Models -constexpr uint16_t START_OPERATION_DELAY = 2000U; +constexpr uint16_t START_KNITTING_DELAY = 2000U; // Determine board type #if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) @@ -63,4 +63,4 @@ constexpr uint16_t START_OPERATION_DELAY = 2000U; #error "untested board - please check your I2C ports" #endif -#endif // BOARD_H_ +#endif // BOARD_H_ diff --git a/src/ayab/com.cpp b/src/ayab/com.cpp index 87d4e6055..41100513d 100644 --- a/src/ayab/com.cpp +++ b/src/ayab/com.cpp @@ -24,6 +24,7 @@ #include "com.h" #include "knitter.h" +#include "tester.h" #ifdef AYAB_ENABLE_CRC /*! @@ -84,6 +85,50 @@ void Com::onPacketReceived(const uint8_t *buffer, size_t size) { h_reqTest(buffer, size); break; + case helpCmd_msgid: + GlobalTester::helpCmd(); + break; + + case sendCmd_msgid: + GlobalTester::sendCmd(); + break; + + case beepCmd_msgid: + GlobalTester::beepCmd(); + break; + + case setSingleCmd_msgid: + GlobalTester::setSingleCmd(buffer, size); + break; + + case setAllCmd_msgid: + GlobalTester::setAllCmd(buffer, size); + break; + + case readEOLsensorsCmd_msgid: + GlobalTester::readEOLsensorsCmd(); + break; + + case readEncodersCmd_msgid: + GlobalTester::readEncodersCmd(); + break; + + case autoReadCmd_msgid: + GlobalTester::autoReadCmd(); + break; + + case autoTestCmd_msgid: + GlobalTester::autoTestCmd(); + break; + + case stopCmd_msgid: + GlobalTester::stopCmd(); + break; + + case quitCmd_msgid: + GlobalTester::quitCmd(); + break; + default: h_unrecognized(); break; @@ -165,8 +210,8 @@ void Com::h_reqStart(const uint8_t *buffer, size_t size) { } bool success = - GlobalKnitter::startOperation(machineType, startNeedle, stopNeedle, - lineBuffer, continuousReportingEnabled); + GlobalKnitter::startKnitting(machineType, startNeedle, stopNeedle, + lineBuffer, continuousReportingEnabled); uint8_t payload[2]; payload[0] = cnfStart_msgid; diff --git a/src/ayab/com.h b/src/ayab/com.h index 269b651dc..b99824dd5 100644 --- a/src/ayab/com.h +++ b/src/ayab/com.h @@ -40,17 +40,27 @@ constexpr uint8_t MAX_MSG_BUFFER_LEN = 255U; enum AYAB_API { reqStart_msgid = 0x01, - cnfStart_msgid = 0xC1, + cnfStart_msgid = 0xc1, reqLine_msgid = 0x82, cnfLine_msgid = 0x42, reqInfo_msgid = 0x03, - cnfInfo_msgid = 0xC3, + cnfInfo_msgid = 0xc3, reqTest_msgid = 0x04, - cnfTest_msgid = 0xC4, + cnfTest_msgid = 0xc4, indState_msgid = 0x84, - cmd_msgid = 0x25, - test_msgid = 0xA5, - debug_msgid = 0xBF, + helpCmd_msgid = 0x25, + sendCmd_msgid = 0x26, + beepCmd_msgid = 0x27, + setSingleCmd_msgid = 0x28, + setAllCmd_msgid = 0x29, + readEOLsensorsCmd_msgid = 0x2a, + readEncodersCmd_msgid = 0x2b, + autoReadCmd_msgid = 0x2c, + autoTestCmd_msgid = 0x2d, + stopCmd_msgid = 0x2e, + quitCmd_msgid = 0x2f, + testRes_msgid = 0xe0, + debug_msgid = 0x99 }; using AYAB_API_t = enum AYAB_API; diff --git a/src/ayab/global_knitter.cpp b/src/ayab/global_knitter.cpp index 62c262e67..a58801aa9 100644 --- a/src/ayab/global_knitter.cpp +++ b/src/ayab/global_knitter.cpp @@ -42,11 +42,11 @@ void GlobalKnitter::isr() { } #endif -bool GlobalKnitter::startOperation(Machine_t machineType, uint8_t startNeedle, - uint8_t stopNeedle, uint8_t *pattern_start, - bool continuousReportingEnabled) { - return m_instance->startOperation(machineType, startNeedle, stopNeedle, - pattern_start, continuousReportingEnabled); +bool GlobalKnitter::startKnitting(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled) { + return m_instance->startKnitting(machineType, startNeedle, stopNeedle, + pattern_start, continuousReportingEnabled); } bool GlobalKnitter::startTest(Machine_t machineType) { @@ -61,10 +61,6 @@ Machine_t GlobalKnitter::getMachineType() { return m_instance->getMachineType(); } -void GlobalKnitter::setSolenoids(uint16_t state) { - m_instance->setSolenoids(state); -} - bool GlobalKnitter::setNextLine(uint8_t lineNumber) { return m_instance->setNextLine(lineNumber); } diff --git a/src/ayab/global_solenoids.cpp b/src/ayab/global_solenoids.cpp new file mode 100644 index 000000000..f5976d510 --- /dev/null +++ b/src/ayab/global_solenoids.cpp @@ -0,0 +1,37 @@ +/*! + * \file global_solenoids.cpp + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#include "solenoids.h" + +// static member functions + +void GlobalSolenoids::init() { + m_instance->init(); +} + +void GlobalSolenoids::setSolenoid(uint8_t solenoid, bool state) { + m_instance->setSolenoid(solenoid, state); +} + +void GlobalSolenoids::setSolenoids(uint16_t state) { + m_instance->setSolenoids(state); +} diff --git a/src/ayab/global_tester.cpp b/src/ayab/global_tester.cpp index 4e0fa4958..1aebae945 100644 --- a/src/ayab/global_tester.cpp +++ b/src/ayab/global_tester.cpp @@ -48,12 +48,12 @@ void GlobalTester::beepCmd() { m_instance->beepCmd(); } -void GlobalTester::setSingleCmd() { - m_instance->setSingleCmd(); +void GlobalTester::setSingleCmd(const uint8_t *buffer, size_t size) { + m_instance->setSingleCmd(buffer, size); } -void GlobalTester::setAllCmd() { - m_instance->setAllCmd(); +void GlobalTester::setAllCmd(const uint8_t *buffer, size_t size) { + m_instance->setAllCmd(buffer, size); } void GlobalTester::readEOLsensorsCmd() { diff --git a/src/ayab/knitter.cpp b/src/ayab/knitter.cpp index 5c05dd4b3..f83db1438 100644 --- a/src/ayab/knitter.cpp +++ b/src/ayab/knitter.cpp @@ -56,7 +56,6 @@ void Knitter::init() { pinMode(DBG_BTN_PIN, INPUT); #endif - m_solenoids.init(); setUpInterrupt(); // explicitly initialize members @@ -79,6 +78,8 @@ void Knitter::init() { m_workedOnLine = false; m_solenoidToSet = 0U; m_pixelToSet = 0U; + + GlobalSolenoids::init(); } void Knitter::setUpInterrupt() { @@ -121,8 +122,8 @@ void Knitter::fsm() { state_ready(); break; - case s_operate: - state_operate(); + case s_knit: + state_knit(); break; case s_test: @@ -141,9 +142,9 @@ void Knitter::fsm() { * \todo sl: check that functionality is correct after removing always true * comparison. */ -bool Knitter::startOperation(Machine_t machineType, uint8_t startNeedle, - uint8_t stopNeedle, uint8_t *pattern_start, - bool continuousReportingEnabled) { +bool Knitter::startKnitting(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled) { if ((m_opState != s_ready) || (machineType == NoMachine) || (machineType >= NUM_MACHINES) || (pattern_start == nullptr) || (startNeedle >= stopNeedle) || (stopNeedle >= NUM_NEEDLES[machineType])) { @@ -168,7 +169,7 @@ bool Knitter::startOperation(Machine_t machineType, uint8_t startNeedle, m_encoders.init(machineType); // proceed to next state - m_opState = s_operate; + m_opState = s_knit; GlobalBeeper::ready(); // Attaching ENC_PIN_A, Interrupt #0 @@ -209,10 +210,6 @@ Machine_t Knitter::getMachineType() { return m_machineType; } -void Knitter::setSolenoids(uint16_t state) { - m_solenoids.setSolenoids(state); -} - bool Knitter::setNextLine(uint8_t lineNumber) { bool success = false; if (m_lineRequested) { @@ -255,7 +252,7 @@ void Knitter::state_init() { if (Right == m_direction && Left == m_hallActive) { #endif // DBG_NOMACHINE m_opState = s_ready; - m_solenoids.setSolenoids(SOLENOIDS_BITMASK); + GlobalSolenoids::setSolenoids(SOLENOIDS_BITMASK); indState(true); } @@ -270,13 +267,13 @@ void Knitter::state_ready() { // is called successfully by fsm() } -void Knitter::state_operate() { +void Knitter::state_knit() { digitalWrite(LED_PIN_A, 1); // green LED on if (m_firstRun) { m_firstRun = false; // TODO(who?): optimize delay for various Arduino models - delay(START_OPERATION_DELAY); + delay(START_KNITTING_DELAY); GlobalBeeper::finishedLine(); reqLine(++m_currentLineNumber); } @@ -325,7 +322,7 @@ void Knitter::state_operate() { bool pixelValue = bitRead(m_lineBuffer[currentByte], m_pixelToSet - (8U * currentByte)); // write Pixel state to the appropriate needle - m_solenoids.setSolenoid(m_solenoidToSet, pixelValue); + GlobalSolenoids::setSolenoid(m_solenoidToSet, pixelValue); } else { // outside of the active needles if (m_machineType == Kh270) { @@ -333,7 +330,7 @@ void Knitter::state_operate() { } // reset solenoids when out of range - m_solenoids.setSolenoid(m_solenoidToSet, true); + GlobalSolenoids::setSolenoid(m_solenoidToSet, true); if (m_workedOnLine) { // already worked on the current line -> finished the line @@ -343,7 +340,7 @@ void Knitter::state_operate() { // request new line from host reqLine(++m_currentLineNumber); } else if (m_lastLineFlag) { - stopOperation(); + stopKnitting(); } } } @@ -453,11 +450,11 @@ void Knitter::indState(const bool initState) { GlobalCom::send(static_cast(payload), INDSTATE_LEN); } -void Knitter::stopOperation() { +void Knitter::stopKnitting() { GlobalBeeper::endWork(); m_opState = s_ready; - m_solenoids.setSolenoids(SOLENOIDS_BITMASK); + GlobalSolenoids::setSolenoids(SOLENOIDS_BITMASK); GlobalBeeper::finishedLine(); // detaching ENC_PIN_A, Interrupt #0 diff --git a/src/ayab/knitter.h b/src/ayab/knitter.h index 0242b0756..5c87d38f3 100644 --- a/src/ayab/knitter.h +++ b/src/ayab/knitter.h @@ -34,7 +34,7 @@ constexpr uint8_t INDSTATE_LEN = 9U; constexpr uint8_t REQLINE_LEN = 2U; -enum OpState { s_init, s_ready, s_operate, s_test }; +enum OpState { s_init, s_ready, s_knit, s_test }; using OpState_t = enum OpState; class KnitterInterface { @@ -46,13 +46,12 @@ class KnitterInterface { virtual void fsm() = 0; virtual void setUpInterrupt() = 0; virtual void isr() = 0; - virtual bool startOperation(Machine_t machineType, uint8_t startNeedle, - uint8_t stopNeedle, uint8_t *pattern_start, - bool continuousReportingEnabled) = 0; + virtual bool startKnitting(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled) = 0; virtual bool startTest(Machine_t machineType) = 0; virtual uint8_t getStartOffset(const Direction_t direction) = 0; virtual Machine_t getMachineType() = 0; - virtual void setSolenoids(uint16_t state) = 0; virtual bool setNextLine(uint8_t lineNumber) = 0; virtual void setLastLine() = 0; virtual void setMachineType(Machine_t) = 0; @@ -80,13 +79,12 @@ class GlobalKnitter final { #ifndef AYAB_TESTS static void isr(); #endif - static bool startOperation(Machine_t machineType, uint8_t startNeedle, - uint8_t stopNeedle, uint8_t *pattern_start, - bool continuousReportingEnabled); + static bool startKnitting(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled); static bool startTest(Machine_t machineType); static uint8_t getStartOffset(const Direction_t direction); static Machine_t getMachineType(); - static void setSolenoids(uint16_t state); static bool setNextLine(uint8_t lineNumber); static void setLastLine(); static void setMachineType(Machine_t); @@ -106,8 +104,8 @@ class Knitter : public KnitterInterface { FRIEND_TEST(KnitterTest, test_fsm_ready); FRIEND_TEST(KnitterTest, test_fsm_test); FRIEND_TEST(KnitterTest, test_fsm_test_quit); - FRIEND_TEST(KnitterTest, test_startOperation_NoMachine); - FRIEND_TEST(KnitterTest, test_startOperation_notReady); + FRIEND_TEST(KnitterTest, test_startKnitting_NoMachine); + FRIEND_TEST(KnitterTest, test_startKnitting_notReady); FRIEND_TEST(KnitterTest, test_startTest); FRIEND_TEST(KnitterTest, test_startTest_in_operation); FRIEND_TEST(KnitterTest, test_setNextLine); @@ -119,13 +117,12 @@ class Knitter : public KnitterInterface { void fsm(); void setUpInterrupt(); void isr(); - bool startOperation(Machine_t machineType, uint8_t startNeedle, - uint8_t stopNeedle, uint8_t *pattern_start, - bool continuousReportingEnabled); + bool startKnitting(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled); bool startTest(Machine_t machineType); uint8_t getStartOffset(const Direction_t direction); Machine_t getMachineType(); - void setSolenoids(uint16_t state); bool setNextLine(uint8_t lineNumber); void setLastLine(); void setMachineType(Machine_t); @@ -134,17 +131,16 @@ class Knitter : public KnitterInterface { private: void state_init(); static void state_ready(); - void state_operate(); + void state_knit(); void state_test(); bool calculatePixelAndSolenoid(); void reqLine(uint8_t lineNumber); void indState(bool initState = false); - void stopOperation(); + void stopKnitting(); OpState_t getState() const; Encoders m_encoders; - Solenoids m_solenoids; // machine state OpState_t m_opState; diff --git a/src/ayab/main.cpp b/src/ayab/main.cpp index cf7669e2c..345087f68 100644 --- a/src/ayab/main.cpp +++ b/src/ayab/main.cpp @@ -29,23 +29,26 @@ // global definitions // references everywhere else must use `extern` -GlobalKnitter *knitter; GlobalBeeper *beeper; GlobalCom *com; +GlobalKnitter *knitter; +GlobalSolenoids *solenoids; GlobalTester *tester; // initialize static members -KnitterInterface *GlobalKnitter::m_instance = new Knitter(); BeeperInterface *GlobalBeeper::m_instance = new Beeper(); ComInterface *GlobalCom::m_instance = new Com(); +KnitterInterface *GlobalKnitter::m_instance = new Knitter(); +SolenoidsInterface *GlobalSolenoids::m_instance = new Solenoids(); TesterInterface *GlobalTester::m_instance = new Tester(); /*! * Setup - do once before going to the main loop. */ void setup() { - GlobalKnitter::init(); GlobalCom::init(); + GlobalKnitter::init(); + GlobalSolenoids::init(); } /*! diff --git a/src/ayab/solenoids.cpp b/src/ayab/solenoids.cpp index b60aba5ad..3ee2a2f5d 100644 --- a/src/ayab/solenoids.cpp +++ b/src/ayab/solenoids.cpp @@ -1,6 +1,7 @@ /*! * \file solenoids.cpp - * \brief Class containing methods governing solenoids. + * \brief Class containing methods that control the needles + * via solenoids connected to IO expanders on the device. * * This file is part of AYAB. * @@ -38,6 +39,7 @@ void Solenoids::init() { } #endif // No Action needed for SOFT_I2C + solenoidState = 0x0000U; } /*! diff --git a/src/ayab/solenoids.h b/src/ayab/solenoids.h index 146718953..5115d9753 100644 --- a/src/ayab/solenoids.h +++ b/src/ayab/solenoids.h @@ -24,9 +24,8 @@ #ifndef SOLENOIDS_H_ #define SOLENOIDS_H_ -#include - #include "board.h" +#include #if defined(HARD_I2C) #include @@ -39,10 +38,36 @@ constexpr uint8_t SOLENOIDS_NUM = 16U; constexpr uint8_t HALF_SOLENOIDS_NUM = 8U; constexpr uint8_t SOLENOIDS_I2C_ADDRESS_MASK = 0x20U; -/*! - * \brief Control of the needles via solenoids connected to IO expanders. - */ -class Solenoids { +class SolenoidsInterface { +public: + virtual ~SolenoidsInterface(){}; + + // any methods that need to be mocked should go here + virtual void init() = 0; + virtual void setSolenoid(uint8_t solenoid, bool state) = 0; + virtual void setSolenoids(uint16_t state) = 0; +}; + +// Container class for the static methods that control the solenoids. +// Dependency injection is enabled using a pointer to a global instance of +// either `Solenoids` or `SolenoidsMock`, both of which classes implement +// the pure virtual methods of `SolenoidsInterface`. + +class GlobalSolenoids final { +private: + // singleton class so private constructor is appropriate + GlobalSolenoids() = default; + +public: + // pointer to global instance whose methods are implemented + static SolenoidsInterface *m_instance; + + static void init(); + static void setSolenoid(uint8_t solenoid, bool state); + static void setSolenoids(uint16_t state); +}; + +class Solenoids : public SolenoidsInterface { #ifdef AYAB_TESTS FRIEND_TEST(SolenoidsTest, test_setSolenoid1); FRIEND_TEST(SolenoidsTest, test_setSolenoid2); diff --git a/src/ayab/tester.cpp b/src/ayab/tester.cpp index 6466c9a7f..698cc7b84 100644 --- a/src/ayab/tester.cpp +++ b/src/ayab/tester.cpp @@ -35,189 +35,158 @@ * \brief Help command handler. */ void Tester::helpCmd() { - GlobalCom::sendMsg(test_msgid, "The following commands are available:\n"); - GlobalCom::sendMsg(test_msgid, "setSingle [0..15] [1/0]\n"); - GlobalCom::sendMsg(test_msgid, "setAll [0..FFFF]\n"); - GlobalCom::sendMsg(test_msgid, "readEOLsensors\n"); - GlobalCom::sendMsg(test_msgid, "readEncoders\n"); - GlobalCom::sendMsg(test_msgid, "beep\n"); - GlobalCom::sendMsg(test_msgid, "autoRead\n"); - GlobalCom::sendMsg(test_msgid, "autoTest\n"); - GlobalCom::sendMsg(test_msgid, "send\n"); - GlobalCom::sendMsg(test_msgid, "stop\n"); - GlobalCom::sendMsg(test_msgid, "quit\n"); - GlobalCom::sendMsg(test_msgid, "help\n"); + GlobalCom::sendMsg(testRes_msgid, "The following commands are available:\n"); + GlobalCom::sendMsg(testRes_msgid, "setSingle [0..15] [1/0]\n"); + GlobalCom::sendMsg(testRes_msgid, "setAll [0..FFFF]\n"); + GlobalCom::sendMsg(testRes_msgid, "readEOLsensors\n"); + GlobalCom::sendMsg(testRes_msgid, "readEncoders\n"); + GlobalCom::sendMsg(testRes_msgid, "beep\n"); + GlobalCom::sendMsg(testRes_msgid, "autoRead\n"); + GlobalCom::sendMsg(testRes_msgid, "autoTest\n"); + GlobalCom::sendMsg(testRes_msgid, "send\n"); + GlobalCom::sendMsg(testRes_msgid, "stop\n"); + GlobalCom::sendMsg(testRes_msgid, "quit\n"); + GlobalCom::sendMsg(testRes_msgid, "help\n"); } /*! * \brief Send command handler. */ void Tester::sendCmd() { - GlobalCom::sendMsg(test_msgid, "Called send\n"); + GlobalCom::sendMsg(testRes_msgid, "Called send\n"); uint8_t p[] = {1, 2, 3}; GlobalCom::send(p, 3); - GlobalCom::sendMsg(test_msgid, "\n"); + GlobalCom::sendMsg(testRes_msgid, "\n"); } /*! * \brief Beep command handler. */ void Tester::beepCmd() { - GlobalCom::sendMsg(test_msgid, "Called beep\n"); + GlobalCom::sendMsg(testRes_msgid, "Called beep\n"); beep(); } /*! * \brief Set single solenoid command handler. */ -void Tester::setSingleCmd() { - GlobalCom::sendMsg(test_msgid, "Called setSingle\n"); - /* - char *arg = m_sCmd.next(); - if (arg == nullptr) { +void Tester::setSingleCmd(const uint8_t *buffer, size_t size) { + GlobalCom::sendMsg(testRes_msgid, "Called setSingle\n"); + if (size < 3U) { + GlobalCom::sendMsg(testRes_msgid, "Error: invalid arguments\n"); return; } - int solenoidNumber = atoi(arg); - if (solenoidNumber < 0 or solenoidNumber > 15) { - sprintf(buf, "Invalid argument: %i\n", solenoidNumber); - GlobalCom::sendMsg(test_msgid, buf); + uint8_t solenoidNumber = buffer[1]; + if (solenoidNumber > 15) { + sprintf(buf, "Error: invalid solenoid index %i\n", solenoidNumber); + GlobalCom::sendMsg(testRes_msgid, buf); return; } - arg = m_sCmd.next(); - if (arg == nullptr) { + uint8_t solenoidState = buffer[2]; + if (solenoidState > 1) { + sprintf(buf, "Error: invalid solenoid value %i\n", solenoidState); + GlobalCom::sendMsg(testRes_msgid, buf); return; } - int solenoidState = atoi(arg); - if (solenoidState < 0 or solenoidState > 1) { - sprintf(buf, "Invalid argument: %i\n", solenoidState); - GlobalCom::sendMsg(test_msgid, buf); - return; - } - GlobalKnitter::setSolenoid(static_cast(solenoidNumber), - static_cast(solenoidState)); - */ + GlobalSolenoids::setSolenoid(solenoidNumber, solenoidState); } /*! * \brief Set all solenoids command handler. */ -void Tester::setAllCmd() { - GlobalCom::sendMsg(test_msgid, "Called setAll\n"); - /* - char *arg = m_sCmd.next(); - if (arg == nullptr) { +void Tester::setAllCmd(const uint8_t *buffer, size_t size) { + GlobalCom::sendMsg(testRes_msgid, "Called setAll\n"); + if (size < 3U) { + GlobalCom::sendMsg(testRes_msgid, "Error: invalid arguments\n"); return; } - short unsigned int solenoidState; - // if (scanHex(arg, 4, &solenoidState)) { - if (sscanf(arg, "%hx", &solenoidState)) { - GlobalKnitter::setSolenoids(solenoidState); - } else { - GlobalCom::sendMsg(test_msgid, "Invalid argument. Please enter a hexadecimal - " "number between 0 and FFFF.\n"); - } - */ + uint16_t solenoidState = (buffer[1] << 8) + buffer[2]; + GlobalSolenoids::setSolenoids(solenoidState); } -/*! // GCOVR_EXCL_LINE +/*! // G COVR_EXCL_LINE * \brief Read EOL sensors command handler. */ void Tester::readEOLsensorsCmd() { - GlobalCom::sendMsg(test_msgid, "Called readEOLsensors\n"); + GlobalCom::sendMsg(testRes_msgid, "Called readEOLsensors\n"); readEOLsensors(); - GlobalCom::sendMsg(test_msgid, "\n"); + GlobalCom::sendMsg(testRes_msgid, "\n"); } -/*! // GCOVR_EXCL_START +/*! * \brief Read encoders command handler. - */ // GCOVR_EXCL_STOP + */ void Tester::readEncodersCmd() { - GlobalCom::sendMsg(test_msgid, "Called readEncoders\n"); + GlobalCom::sendMsg(testRes_msgid, "Called readEncoders\n"); readEncoders(); - GlobalCom::sendMsg(test_msgid, "\n"); + GlobalCom::sendMsg(testRes_msgid, "\n"); } -/*! // GCOVR_EXCL_START +/*! * \brief Auto read command handler. - */ // GCOVR_EXCL_STOP + */ void Tester::autoReadCmd() { - GlobalCom::sendMsg(test_msgid, "Called autoRead, send stop to quit\n"); + GlobalCom::sendMsg(testRes_msgid, "Called autoRead, send stop to quit\n"); m_autoReadOn = true; } -/*! // GCOVR_EXCL_START +/*! * \brief Auto test command handler. - */ // GCOVR_EXCL_STOP + */ void Tester::autoTestCmd() { - GlobalCom::sendMsg(test_msgid, "Called autoTest, send stop to quit\n"); + GlobalCom::sendMsg(testRes_msgid, "Called autoTest, send stop to quit\n"); m_autoTestOn = true; } -/*! // GCOVR_EXCL_START +/*! * \brief Stop command handler. - */ // GCOVR_EXCL_STOP + */ void Tester::stopCmd() { m_autoReadOn = false; m_autoTestOn = false; } -/*! // GCOVR_EXCL_START +/*! * \brief Quit command handler. - */ // GCOVR_EXCL_STOP + */ void Tester::quitCmd() { m_quit = true; GlobalKnitter::setUpInterrupt(); } -/*! // GCOVR_EXCL_START +/*! * \brief Unrecognized command handler. * * \param buffer: pointer to string containing unrecognized command * * This gets set as the default handler, and gets called when no other command * matches. - */ // GCOVR_EXCL_STOP + */ void Tester::unrecognizedCmd(const char *buffer) { - GlobalCom::sendMsg(test_msgid, "Unrecognized command\n"); + GlobalCom::sendMsg(testRes_msgid, "Unrecognized command\n"); (void)(buffer); // does nothing but prevents 'unused variable' compile error helpCmd(); } -/*! // GCOVR_EXCL_START +/*! * \brief Setup for hardware tests. - */ // GCOVR_EXCL_STOP + */ void Tester::setUp() { - // set up callbacks for SerialCommand commands - /* - m_sCmd.addCommand("%setSingle", GlobalTester::setSingleCmd); - m_sCmd.addCommand("%setAll", GlobalTester::setAllCmd); - m_sCmd.addCommand("%readEOLsensors", GlobalTester::readEOLsensorsCmd); - m_sCmd.addCommand("%readEncoders", GlobalTester::readEncodersCmd); - m_sCmd.addCommand("%beep", GlobalTester::beepCmd); - m_sCmd.addCommand("%autoRead", GlobalTester::autoReadCmd); - m_sCmd.addCommand("%autoTest", GlobalTester::autoTestCmd); - m_sCmd.addCommand("%send", GlobalTester::sendCmd); - m_sCmd.addCommand("%stop", GlobalTester::stopCmd); - m_sCmd.addCommand("%quit", GlobalTester::quitCmd); - m_sCmd.addCommand("%help", GlobalTester::helpCmd); - m_sCmd.setDefaultHandler(GlobalTester::unrecognizedCmd); - */ - // Print welcome message - GlobalCom::sendMsg(test_msgid, "AYAB Hardware Test, "); + GlobalCom::sendMsg(testRes_msgid, "AYAB Hardware Test, "); sprintf(buf, "Firmware v%hhu", FW_VERSION_MAJ); - GlobalCom::sendMsg(test_msgid, buf); + GlobalCom::sendMsg(testRes_msgid, buf); sprintf(buf, ".%hhu", FW_VERSION_MIN); - GlobalCom::sendMsg(test_msgid, buf); + GlobalCom::sendMsg(testRes_msgid, buf); sprintf(buf, " API v%hhu\n\n", API_VERSION); - GlobalCom::sendMsg(test_msgid, buf); + GlobalCom::sendMsg(testRes_msgid, buf); helpCmd(); // attach interrupt for ENC_PIN_A(=2), interrupt #0 detachInterrupt(0); #ifndef AYAB_TESTS attachInterrupt(0, GlobalTester::encoderAChange, RISING); -#endif // AYAB_TESTS // GCOVR_EXCL_LINE +#endif // AYAB_TESTS m_quit = false; m_autoReadOn = false; @@ -226,9 +195,9 @@ void Tester::setUp() { m_timerEventOdd = false; } -/*! // GCOVR_EXCL_START +/*! * \brief Main loop for hardware tests. - */ // GCOVR_EXCL_STOP + */ void Tester::loop() { unsigned long now = millis(); if (now - m_lastTime >= 500) { @@ -257,45 +226,45 @@ void Tester::beep() { } void Tester::readEncoders() { - GlobalCom::sendMsg(test_msgid, " ENC_A: "); + GlobalCom::sendMsg(testRes_msgid, " ENC_A: "); bool state = digitalRead(ENC_PIN_A); - GlobalCom::sendMsg(test_msgid, state ? "HIGH" : "LOW"); - GlobalCom::sendMsg(test_msgid, " ENC_B: "); + GlobalCom::sendMsg(testRes_msgid, state ? "HIGH" : "LOW"); + GlobalCom::sendMsg(testRes_msgid, " ENC_B: "); state = digitalRead(ENC_PIN_B); - GlobalCom::sendMsg(test_msgid, state ? "HIGH" : "LOW"); - GlobalCom::sendMsg(test_msgid, " ENC_C: "); + GlobalCom::sendMsg(testRes_msgid, state ? "HIGH" : "LOW"); + GlobalCom::sendMsg(testRes_msgid, " ENC_C: "); state = digitalRead(ENC_PIN_C); - GlobalCom::sendMsg(test_msgid, state ? "HIGH" : "LOW"); + GlobalCom::sendMsg(testRes_msgid, state ? "HIGH" : "LOW"); } -void Tester::readEOLsensors() { // GCOVR_EXCL_LINE (?) +void Tester::readEOLsensors() { uint16_t hallSensor = static_cast(analogRead(EOL_PIN_L)); sprintf(buf, " EOL_L: %hu", hallSensor); - GlobalCom::sendMsg(test_msgid, buf); + GlobalCom::sendMsg(testRes_msgid, buf); hallSensor = static_cast(analogRead(EOL_PIN_R)); sprintf(buf, " EOL_R: %hu", hallSensor); - GlobalCom::sendMsg(test_msgid, buf); + GlobalCom::sendMsg(testRes_msgid, buf); } void Tester::autoRead() { - GlobalCom::sendMsg(test_msgid, "\n"); + GlobalCom::sendMsg(testRes_msgid, "\n"); readEOLsensors(); readEncoders(); - GlobalCom::sendMsg(test_msgid, "\n"); + GlobalCom::sendMsg(testRes_msgid, "\n"); } void Tester::autoTestEven() { - GlobalCom::sendMsg(test_msgid, "Set even solenoids\n"); + GlobalCom::sendMsg(testRes_msgid, "Set even solenoids\n"); digitalWrite(LED_PIN_A, HIGH); digitalWrite(LED_PIN_B, HIGH); - GlobalKnitter::setSolenoids(0xAAAA); + GlobalSolenoids::setSolenoids(0xAAAA); } void Tester::autoTestOdd() { - GlobalCom::sendMsg(test_msgid, "Set odd solenoids\n"); + GlobalCom::sendMsg(testRes_msgid, "Set odd solenoids\n"); digitalWrite(LED_PIN_A, LOW); digitalWrite(LED_PIN_B, LOW); - GlobalKnitter::setSolenoids(0x5555); + GlobalSolenoids::setSolenoids(0x5555); } /*! @@ -313,33 +282,4 @@ void Tester::handleTimerEvent() { } } m_timerEventOdd = not m_timerEventOdd; - /* - m_sCmd.readSerial(); - */ -} - -/* -// homebrew `sscanf(str, "%hx", &result);` -// does not trim white space -bool Tester::scanHex(char *str, uint8_t maxDigits, uint16_t *result) { - if (maxDigits == 0 or *str == 0) { - return false; - } - uint16_t s = 0; - char c; - while (maxDigits-- > 0 and (c = *str++) != 0) { - s <<= 4; - if ('0' <= c and c <= '9') { - s += c - '0'; - } else if ('a' <= c and c <= 'f') { - s += c + 10 - 'a'; - } else if ('A' <= c and c <= 'F') { - s += c + 10 - 'A'; - } else { - return false; - } - } - *result = s; - return true; } -*/ diff --git a/src/ayab/tester.h b/src/ayab/tester.h index 9ba0aa444..091e5742f 100644 --- a/src/ayab/tester.h +++ b/src/ayab/tester.h @@ -28,7 +28,7 @@ #include "beeper.h" -constexpr uint8_t BUFFER_LEN = 20; +constexpr uint8_t BUFFER_LEN = 40; class TesterInterface { public: @@ -41,8 +41,8 @@ class TesterInterface { virtual void helpCmd() = 0; virtual void sendCmd() = 0; virtual void beepCmd() = 0; - virtual void setSingleCmd() = 0; - virtual void setAllCmd() = 0; + virtual void setSingleCmd(const uint8_t *buffer, size_t size) = 0; + virtual void setAllCmd(const uint8_t *buffer, size_t size) = 0; virtual void readEOLsensorsCmd() = 0; virtual void readEncodersCmd() = 0; virtual void autoReadCmd() = 0; @@ -76,8 +76,8 @@ class GlobalTester final { static void helpCmd(); static void sendCmd(); static void beepCmd(); - static void setSingleCmd(); - static void setAllCmd(); + static void setSingleCmd(const uint8_t *buffer, size_t size); + static void setAllCmd(const uint8_t *buffer, size_t size); static void readEOLsensorsCmd(); static void readEncodersCmd(); static void autoReadCmd(); @@ -99,8 +99,7 @@ class Tester : public TesterInterface { FRIEND_TEST(TesterTest, test_loop_null); FRIEND_TEST(TesterTest, test_loop_autoTestEven); FRIEND_TEST(TesterTest, test_loop_autoTestOdd); - // FRIEND_TEST(TesterTest, test_scanHex); - friend class TesterTest; + // friend class TesterTest; #endif public: @@ -110,8 +109,8 @@ class Tester : public TesterInterface { void helpCmd(); void sendCmd(); void beepCmd(); - void setSingleCmd(); - void setAllCmd(); + void setSingleCmd(const uint8_t *buffer, size_t size); + void setAllCmd(const uint8_t *buffer, size_t size); void readEOLsensorsCmd(); void readEncodersCmd(); void autoReadCmd(); @@ -132,10 +131,6 @@ class Tester : public TesterInterface { void autoTestOdd(); void handleTimerEvent(); - // static bool scanHex(char *str, uint8_t maxDigits, uint16_t *result); - - // SerialCommand m_sCmd = SerialCommand(); - bool m_quit = false; bool m_autoReadOn = false; bool m_autoTestOn = false; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3461b5715..04bbf4d29 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -30,7 +30,6 @@ set(COMMON_INCLUDES ${ARDUINO_MOCK_LIBS_DIR}/lib/gtest/gtest/src/gtest/googlemock/include ${PROJECT_SOURCE_DIR}/mocks ${SOURCE_DIRECTORY} - #${LIBRARY_DIRECTORY}/SerialCommand ${LIBRARY_DIRECTORY}/PacketSerial/src ) set(EXTERNAL_LIB_INCLUDES @@ -44,6 +43,7 @@ set(COMMON_SOURCES ${PROJECT_SOURCE_DIR}/test_encoders.cpp ${SOURCE_DIRECTORY}/solenoids.cpp + ${SOURCE_DIRECTORY}/global_solenoids.cpp ${PROJECT_SOURCE_DIR}/test_solenoids.cpp ${SOURCE_DIRECTORY}/beeper.cpp @@ -57,7 +57,6 @@ set(COMMON_SOURCES ${SOURCE_DIRECTORY}/tester.cpp ${SOURCE_DIRECTORY}/global_tester.cpp ${PROJECT_SOURCE_DIR}/test_tester.cpp - #${PROJECT_SOURCE_DIR}/mocks/SerialCommand_mock.cpp ${SOURCE_DIRECTORY}/global_knitter.cpp ${PROJECT_SOURCE_DIR}/mocks/knitter_mock.cpp @@ -140,6 +139,7 @@ add_executable(${PROJECT_NAME}_knitter ${SOURCE_DIRECTORY}/global_knitter.cpp ${SOURCE_DIRECTORY}/global_com.cpp ${SOURCE_DIRECTORY}/global_beeper.cpp + ${SOURCE_DIRECTORY}/global_solenoids.cpp ${SOURCE_DIRECTORY}/global_tester.cpp ${PROJECT_SOURCE_DIR}/mocks/solenoids_mock.cpp ${PROJECT_SOURCE_DIR}/mocks/encoders_mock.cpp @@ -147,7 +147,6 @@ add_executable(${PROJECT_NAME}_knitter ${PROJECT_SOURCE_DIR}/mocks/com_mock.cpp ${PROJECT_SOURCE_DIR}/mocks/tester_mock.cpp ${PROJECT_SOURCE_DIR}/test_knitter.cpp - #${LIBRARY_DIRECTORY}/SerialCommand/SerialCommand.cpp ${SOFT_I2C_LIB} ) target_include_directories(${PROJECT_NAME}_knitter diff --git a/test/mocks/knitter_mock.cpp b/test/mocks/knitter_mock.cpp index e9da46fdb..2bfe5d690 100644 --- a/test/mocks/knitter_mock.cpp +++ b/test/mocks/knitter_mock.cpp @@ -59,13 +59,12 @@ void Knitter::setUpInterrupt() { gKnitterMock->setUpInterrupt(); } -bool Knitter::startOperation(Machine_t machineType, uint8_t startNeedle, - uint8_t stopNeedle, uint8_t *pattern_start, - bool continuousReportingEnabled) { +bool Knitter::startKnitting(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled) { assert(gKnitterMock != NULL); - return gKnitterMock->startOperation(machineType, startNeedle, stopNeedle, - pattern_start, - continuousReportingEnabled); + return gKnitterMock->startKnitting(machineType, startNeedle, stopNeedle, + pattern_start, continuousReportingEnabled); } bool Knitter::startTest(Machine_t machineType) { @@ -88,11 +87,6 @@ Machine_t Knitter::getMachineType() { return gKnitterMock->getMachineType(); } -void Knitter::setSolenoids(uint16_t state) { - assert(gKnitterMock != NULL); - return gKnitterMock->setSolenoids(state); -} - void Knitter::setMachineType(Machine_t machineType) { assert(gKnitterMock != NULL); return gKnitterMock->setMachineType(machineType); diff --git a/test/mocks/knitter_mock.h b/test/mocks/knitter_mock.h index e52530a72..97ea0d316 100644 --- a/test/mocks/knitter_mock.h +++ b/test/mocks/knitter_mock.h @@ -33,14 +33,13 @@ class KnitterMock : public KnitterInterface { MOCK_METHOD0(fsm, void()); MOCK_METHOD0(isr, void()); MOCK_METHOD0(setUpInterrupt, void()); - MOCK_METHOD5(startOperation, bool(Machine_t machineType, uint8_t startNeedle, - uint8_t stopNeedle, uint8_t *pattern_start, - bool continuousReportingEnabled)); + MOCK_METHOD5(startKnitting, bool(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled)); MOCK_METHOD1(startTest, bool(Machine_t)); MOCK_METHOD1(setNextLine, bool(uint8_t lineNumber)); MOCK_METHOD0(setLastLine, void()); MOCK_METHOD0(getMachineType, Machine_t()); - MOCK_METHOD1(setSolenoids, void(uint16_t)); MOCK_METHOD1(setMachineType, void(Machine_t)); MOCK_METHOD1(getStartOffset, uint8_t(const Direction_t direction)); MOCK_METHOD1(setState, void(OpState_t)); diff --git a/test/mocks/solenoids_mock.h b/test/mocks/solenoids_mock.h index 5776f551f..437c3965a 100644 --- a/test/mocks/solenoids_mock.h +++ b/test/mocks/solenoids_mock.h @@ -24,10 +24,10 @@ #ifndef SOLENOIDS_MOCK_H_ #define SOLENOIDS_MOCK_H_ -#include #include +#include -class SolenoidsMock : public Solenoids { +class SolenoidsMock : public SolenoidsInterface { public: MOCK_METHOD0(init, void()); MOCK_METHOD2(setSolenoid, void(uint8_t, bool)); @@ -37,4 +37,4 @@ class SolenoidsMock : public Solenoids { SolenoidsMock *solenoidsMockInstance(); void releaseSolenoidsMock(); -#endif // SOLENOIDS_MOCK_H_ +#endif // SOLENOIDS_MOCK_H_ diff --git a/test/mocks/tester_mock.cpp b/test/mocks/tester_mock.cpp index c317926e7..e96c0ca73 100644 --- a/test/mocks/tester_mock.cpp +++ b/test/mocks/tester_mock.cpp @@ -69,14 +69,14 @@ void Tester::beepCmd() { gTesterMock->beepCmd(); } -void Tester::setSingleCmd() { +void Tester::setSingleCmd(const uint8_t *buffer, size_t size) { assert(gTesterMock != NULL); - gTesterMock->setSingleCmd(); + gTesterMock->setSingleCmd(buffer, size); } -void Tester::setAllCmd() { +void Tester::setAllCmd(const uint8_t *buffer, size_t size) { assert(gTesterMock != NULL); - gTesterMock->setAllCmd(); + gTesterMock->setAllCmd(buffer, size); } void Tester::readEOLsensorsCmd() { diff --git a/test/mocks/tester_mock.h b/test/mocks/tester_mock.h index 7d1640cc1..b99917ffc 100644 --- a/test/mocks/tester_mock.h +++ b/test/mocks/tester_mock.h @@ -35,8 +35,8 @@ class TesterMock : public TesterInterface { MOCK_METHOD0(helpCmd, void()); MOCK_METHOD0(sendCmd, void()); MOCK_METHOD0(beepCmd, void()); - MOCK_METHOD0(setSingleCmd, void()); - MOCK_METHOD0(setAllCmd, void()); + MOCK_METHOD2(setSingleCmd, void(const uint8_t *, size_t)); + MOCK_METHOD2(setAllCmd, void(const uint8_t *, size_t)); MOCK_METHOD0(readEOLsensorsCmd, void()); MOCK_METHOD0(readEncodersCmd, void()); MOCK_METHOD0(autoReadCmd, void()); diff --git a/test/test_all.cpp b/test/test_all.cpp index 285af9195..b42f03b8b 100644 --- a/test/test_all.cpp +++ b/test/test_all.cpp @@ -27,6 +27,7 @@ #include #include +#include #include // global definitions @@ -34,12 +35,14 @@ Knitter *knitter = new Knitter(); BeeperMock *beeper = new BeeperMock(); ComMock *com = new ComMock(); +SolenoidsMock *solenoids = new SolenoidsMock(); TesterMock *tester = new TesterMock(); // instantiate singleton classes with mock objects KnitterInterface *GlobalKnitter::m_instance = knitter; BeeperInterface *GlobalBeeper::m_instance = beeper; ComInterface *GlobalCom::m_instance = com; +SolenoidsInterface *GlobalSolenoids::m_instance = solenoids; TesterInterface *GlobalTester::m_instance = tester; int main(int argc, char *argv[]) { diff --git a/test/test_boards.cpp b/test/test_boards.cpp index cad5f5bb0..b0807acd8 100644 --- a/test/test_boards.cpp +++ b/test/test_boards.cpp @@ -34,12 +34,14 @@ KnitterMock *knitter = new KnitterMock(); Beeper *beeper = new Beeper(); Com *com = new Com(); +Solenoids *solenoids = new Solenoids(); Tester *tester = new Tester(); // initialize static members KnitterInterface *GlobalKnitter::m_instance = knitter; BeeperInterface *GlobalBeeper::m_instance = beeper; ComInterface *GlobalCom::m_instance = com; +SolenoidsInterface *GlobalSolenoids::m_instance = solenoids; TesterInterface *GlobalTester::m_instance = tester; int main(int argc, char *argv[]) { diff --git a/test/test_com.cpp b/test/test_com.cpp index a8e4b9f1b..8073465fd 100644 --- a/test/test_com.cpp +++ b/test/test_com.cpp @@ -37,6 +37,7 @@ extern KnitterMock *knitter; class ComTest : public ::testing::Test { protected: void SetUp() override { + arduinoMock = arduinoMockInstance(); serialMock = serialMockInstance(); // pointer to global instance @@ -52,12 +53,14 @@ class ComTest : public ::testing::Test { } void TearDown() override { - releaseKnitterMock(); + releaseArduinoMock(); releaseSerialMock(); + releaseKnitterMock(); } - KnitterMock *knitterMock; + ArduinoMock *arduinoMock; SerialMock *serialMock; + KnitterMock *knitterMock; void expect_init() { EXPECT_CALL(*serialMock, begin); @@ -88,7 +91,7 @@ TEST_F(ComTest, test_reqtest_success) { TEST_F(ComTest, test_reqstart_fail1) { // checksum wrong uint8_t buffer[] = {reqStart_msgid, 0, 0, 10, 1, 0x73}; - EXPECT_CALL(*knitterMock, startOperation).Times(0); + EXPECT_CALL(*knitterMock, startKnitting).Times(0); com->onPacketReceived(buffer, sizeof(buffer)); ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); } @@ -96,21 +99,21 @@ TEST_F(ComTest, test_reqstart_fail1) { TEST_F(ComTest, test_reqstart_fail2) { // not enough bytes uint8_t buffer[] = {reqStart_msgid, 0, 0, 10, 1, 0x74}; - EXPECT_CALL(*knitterMock, startOperation).Times(0); + EXPECT_CALL(*knitterMock, startKnitting).Times(0); com->onPacketReceived(buffer, sizeof(buffer) - 1); ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); } TEST_F(ComTest, test_reqstart_success_KH910) { uint8_t buffer[] = {reqStart_msgid, 0, 0, 10, 1, 0x74}; - EXPECT_CALL(*knitterMock, startOperation); + EXPECT_CALL(*knitterMock, startKnitting); com->onPacketReceived(buffer, sizeof(buffer)); ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); } TEST_F(ComTest, test_reqstart_success_KH270) { uint8_t buffer[] = {reqStart_msgid, 2, 0, 10, 1, 0x73}; - EXPECT_CALL(*knitterMock, startOperation); + EXPECT_CALL(*knitterMock, startKnitting); com->onPacketReceived(buffer, sizeof(buffer)); ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); } @@ -120,6 +123,61 @@ TEST_F(ComTest, test_reqinfo) { com->onPacketReceived(buffer, sizeof(buffer)); } +TEST_F(ComTest, test_helpCmd) { + uint8_t buffer[] = {helpCmd_msgid}; + com->onPacketReceived(buffer, sizeof(buffer)); +} + +TEST_F(ComTest, test_sendCmd) { + uint8_t buffer[] = {sendCmd_msgid}; + com->onPacketReceived(buffer, sizeof(buffer)); +} + +TEST_F(ComTest, test_beepCmd) { + uint8_t buffer[] = {beepCmd_msgid}; + com->onPacketReceived(buffer, sizeof(buffer)); +} + +TEST_F(ComTest, test_setSingleCmd) { + uint8_t buffer[] = {setSingleCmd_msgid, 0, 0}; + com->onPacketReceived(buffer, sizeof(buffer)); +} + +TEST_F(ComTest, test_setAllCmd) { + uint8_t buffer[] = {setAllCmd_msgid, 0, 0}; + com->onPacketReceived(buffer, sizeof(buffer)); +} + +TEST_F(ComTest, test_readEOLsensorsCmd) { + uint8_t buffer[] = {readEOLsensorsCmd_msgid}; + com->onPacketReceived(buffer, sizeof(buffer)); +} + +TEST_F(ComTest, test_readEncodersCmd) { + uint8_t buffer[] = {readEncodersCmd_msgid}; + com->onPacketReceived(buffer, sizeof(buffer)); +} + +TEST_F(ComTest, test_autoReadCmd) { + uint8_t buffer[] = {autoReadCmd_msgid}; + com->onPacketReceived(buffer, sizeof(buffer)); +} + +TEST_F(ComTest, test_autoTestCmd) { + uint8_t buffer[] = {autoTestCmd_msgid}; + com->onPacketReceived(buffer, sizeof(buffer)); +} + +TEST_F(ComTest, test_stopCmd) { + uint8_t buffer[] = {stopCmd_msgid}; + com->onPacketReceived(buffer, sizeof(buffer)); +} + +TEST_F(ComTest, test_quitCmd) { + uint8_t buffer[] = {quitCmd_msgid}; + com->onPacketReceived(buffer, sizeof(buffer)); +} + TEST_F(ComTest, test_unrecognized) { uint8_t buffer[] = {0xFF}; com->onPacketReceived(buffer, sizeof(buffer)); @@ -162,7 +220,7 @@ TEST_F(ComTest, test_cnfline_kh910) { 0xa7}; // CRC8 // start KH910 job - knitterMock->startOperation(Kh910, 0, 199, pattern, false); + knitterMock->startKnitting(Kh910, 0, 199, pattern, false); // first call increments line number to zero, not accepted EXPECT_CALL(*knitterMock, setNextLine).WillOnce(Return(false)); @@ -232,12 +290,12 @@ TEST_F(ComTest, test_send) { TEST_F(ComTest, test_sendMsg1) { EXPECT_CALL(*serialMock, write(_, _)); EXPECT_CALL(*serialMock, write(SLIP::END)); - com->sendMsg(test_msgid, "abc"); + com->sendMsg(testRes_msgid, "abc"); } TEST_F(ComTest, test_sendMsg2) { char buf[] = "abc\0"; EXPECT_CALL(*serialMock, write(_, _)); EXPECT_CALL(*serialMock, write(SLIP::END)); - com->sendMsg(test_msgid, buf); + com->sendMsg(testRes_msgid, buf); } diff --git a/test/test_knitter.cpp b/test/test_knitter.cpp index 8857c44c5..235857dea 100644 --- a/test/test_knitter.cpp +++ b/test/test_knitter.cpp @@ -42,18 +42,19 @@ using ::testing::TypedEq; extern Knitter *knitter; extern BeeperMock *beeper; extern ComMock *com; +extern SolenoidsMock *solenoids; extern TesterMock *tester; class KnitterTest : public ::testing::Test { protected: void SetUp() override { arduinoMock = arduinoMockInstance(); - solenoidsMock = solenoidsMockInstance(); encodersMock = encodersMockInstance(); // pointers to global instances beeperMock = beeper; comMock = com; + solenoidsMock = solenoids; testerMock = tester; // The global instances do not get destroyed at the end of each test. @@ -61,6 +62,7 @@ class KnitterTest : public ::testing::Test { // cause a memory leak. We must notify the test that this is not the case. Mock::AllowLeak(beeperMock); Mock::AllowLeak(comMock); + Mock::AllowLeak(solenoidsMock); Mock::AllowLeak(testerMock); expect_init(); @@ -69,7 +71,6 @@ class KnitterTest : public ::testing::Test { void TearDown() override { releaseArduinoMock(); - releaseSolenoidsMock(); releaseEncodersMock(); } @@ -90,8 +91,6 @@ class KnitterTest : public ::testing::Test { EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 1)); // green LED on EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_B, 1)); // yellow LED on - - EXPECT_CALL(*solenoidsMock, init); } void expect_isr(uint16_t pos, Direction_t dir, Direction_t hall, @@ -164,17 +163,17 @@ class KnitterTest : public ::testing::Test { expected_fsm(); } - void get_to_operate(Machine_t m) { + void get_to_knit(Machine_t m) { get_to_ready(); uint8_t pattern[] = {1}; EXPECT_CALL(*beeperMock, ready); EXPECT_CALL(*encodersMock, init); - knitter->startOperation(m, 0, NUM_NEEDLES[m] - 1, pattern, false); + knitter->startKnitting(m, 0, NUM_NEEDLES[m] - 1, pattern, false); } - void expected_operate(bool first) { + void expected_knit(bool first) { if (first) { - get_to_operate(Kh910); + get_to_knit(Kh910); EXPECT_CALL(*arduinoMock, delay(2000)); EXPECT_CALL(*beeperMock, finishedLine); expect_send(); @@ -187,9 +186,6 @@ class KnitterTest : public ::testing::Test { expect_indState(); EXPECT_CALL(*testerMock, loop); expected_fsm(); - - // test expectations without destroying instance - ASSERT_TRUE(Mock::VerifyAndClear(testerMock)); } void expected_set_machine(Machine_t machineType) { @@ -198,24 +194,6 @@ class KnitterTest : public ::testing::Test { encodersMock->init(machineType); ASSERT_EQ(knitter->startTest(machineType), true); } - - void test_operate_line_request() { - EXPECT_CALL(*solenoidsMock, setSolenoid); - expected_operate(true); - // `m_workedOnLine` is set to `true` - - // Position has changed since last call to operate function - // `m_pixelToSet` is set above `m_stopNeedle` + END_OF_LINE_OFFSET_R - Machine_t m = knitter->getMachineType(); // Kh910 - expected_isr(NUM_NEEDLES[m] + 8 + END_OF_LINE_OFFSET_R[m] + 1); - - EXPECT_CALL(*solenoidsMock, setSolenoid); - expected_operate(false); - - // no change in position, no action. - EXPECT_CALL(*solenoidsMock, setSolenoid).Times(0); - expected_operate(false); - } }; TEST_F(KnitterTest, test_init) { @@ -233,14 +211,12 @@ TEST_F(KnitterTest, test_isr) { expected_isr(1); } -TEST_F(KnitterTest, test_setSolenoids) { - EXPECT_CALL(*solenoidsMock, setSolenoids); - knitter->setSolenoids(0xFADE); -} - TEST_F(KnitterTest, test_fsm_default_case) { knitter->setState(static_cast(4)); expected_fsm(); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_fsm_init_LL) { @@ -248,6 +224,9 @@ TEST_F(KnitterTest, test_fsm_init_LL) { expected_isr(Left, Left); expected_fsm(); ASSERT_EQ(knitter->getState(), s_init); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_fsm_init_RR) { @@ -255,6 +234,9 @@ TEST_F(KnitterTest, test_fsm_init_RR) { expected_isr(Right, Right); expected_fsm(); ASSERT_EQ(knitter->getState(), s_init); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_fsm_init_RL) { @@ -264,6 +246,10 @@ TEST_F(KnitterTest, test_fsm_init_RL) { expect_indState(); expected_fsm(); ASSERT_EQ(knitter->getState(), s_ready); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_fsm_ready) { @@ -280,6 +266,10 @@ TEST_F(KnitterTest, test_fsm_ready) { // still in ready state ASSERT_EQ(knitter->getState(), s_ready); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_fsm_test) { @@ -300,6 +290,7 @@ TEST_F(KnitterTest, test_fsm_test) { // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(testerMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_fsm_test_quit) { @@ -313,68 +304,81 @@ TEST_F(KnitterTest, test_fsm_test_quit) { // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(testerMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } -TEST_F(KnitterTest, test_startOperation_NoMachine) { +TEST_F(KnitterTest, test_startKnitting_NoMachine) { uint8_t pattern[] = {1}; ASSERT_EQ(knitter->getState(), s_init); Machine_t m = knitter->getMachineType(); ASSERT_EQ(m, NoMachine); get_to_ready(); - ASSERT_EQ(knitter->startOperation(m, 0, NUM_NEEDLES[m] - 1, pattern, false), + ASSERT_EQ(knitter->startKnitting(m, 0, NUM_NEEDLES[m] - 1, pattern, false), false); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } -TEST_F(KnitterTest, test_startOperation_notReady) { +TEST_F(KnitterTest, test_startKnitting_notReady) { uint8_t pattern[] = {1}; ASSERT_EQ(knitter->getState(), s_init); ASSERT_EQ( - knitter->startOperation(Kh910, 0, NUM_NEEDLES[Kh910] - 1, pattern, false), + knitter->startKnitting(Kh910, 0, NUM_NEEDLES[Kh910] - 1, pattern, false), false); } -TEST_F(KnitterTest, test_startOperation_Kh910) { +TEST_F(KnitterTest, test_startKnitting_Kh910) { uint8_t pattern[] = {1}; get_to_ready(); EXPECT_CALL(*encodersMock, init); EXPECT_CALL(*beeperMock, ready); ASSERT_EQ( - knitter->startOperation(Kh910, 0, NUM_NEEDLES[Kh910] - 1, pattern, false), + knitter->startKnitting(Kh910, 0, NUM_NEEDLES[Kh910] - 1, pattern, false), true); // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } -TEST_F(KnitterTest, test_startOperation_Kh270) { +TEST_F(KnitterTest, test_startKnitting_Kh270) { uint8_t pattern[] = {1}; get_to_ready(); EXPECT_CALL(*encodersMock, init); EXPECT_CALL(*beeperMock, ready); ASSERT_EQ( - knitter->startOperation(Kh270, 0, NUM_NEEDLES[Kh270] - 1, pattern, false), + knitter->startKnitting(Kh270, 0, NUM_NEEDLES[Kh270] - 1, pattern, false), true); // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } -TEST_F(KnitterTest, test_startOperation_failures) { +TEST_F(KnitterTest, test_startKnitting_failures) { uint8_t pattern[] = {1}; get_to_ready(); // `m_stopNeedle` lower than `m_startNeedle` - ASSERT_EQ(knitter->startOperation(Kh910, 1, 0, pattern, false), false); + ASSERT_EQ(knitter->startKnitting(Kh910, 1, 0, pattern, false), false); // `m_stopNeedle` out of range ASSERT_EQ( - knitter->startOperation(Kh910, 0, NUM_NEEDLES[Kh910], pattern, false), + knitter->startKnitting(Kh910, 0, NUM_NEEDLES[Kh910], pattern, false), false); // null pattern ASSERT_EQ( - knitter->startOperation(Kh910, 0, NUM_NEEDLES[Kh910] - 1, nullptr, false), + knitter->startKnitting(Kh910, 0, NUM_NEEDLES[Kh910] - 1, nullptr, false), false); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_startTest) { @@ -387,11 +391,16 @@ TEST_F(KnitterTest, test_startTest) { } TEST_F(KnitterTest, test_startTest_in_operation) { - get_to_operate(Kh910); - ASSERT_EQ(knitter->getState(), s_operate); + get_to_knit(Kh910); + ASSERT_EQ(knitter->getState(), s_knit); // can't start test ASSERT_EQ(knitter->startTest(Kh910), false); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_setNextLine) { @@ -399,13 +408,13 @@ TEST_F(KnitterTest, test_setNextLine) { // set `m_lineRequested` EXPECT_CALL(*solenoidsMock, setSolenoid).Times(1); - expected_operate(true); + expected_knit(true); // outside of the active needles expected_isr(40 + NUM_NEEDLES[Kh910] - 1 + END_OF_LINE_OFFSET_R[Kh910] + 1); EXPECT_CALL(*solenoidsMock, setSolenoid).Times(1); - expected_operate(false); - ASSERT_EQ(knitter->getState(), s_operate); + expected_knit(false); + ASSERT_EQ(knitter->getState(), s_knit); // wrong line number EXPECT_CALL(*beeperMock, finishedLine).Times(0); @@ -421,6 +430,8 @@ TEST_F(KnitterTest, test_setNextLine) { // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_operate_Kh910) { @@ -442,7 +453,7 @@ TEST_F(KnitterTest, test_operate_Kh910) { const uint8_t START_NEEDLE = NUM_NEEDLES[Kh910] - 2; const uint8_t STOP_NEEDLE = NUM_NEEDLES[Kh910] - 1; const uint8_t OFFSET = END_OF_LINE_OFFSET_R[Kh910]; - knitter->startOperation(Kh910, START_NEEDLE, STOP_NEEDLE, pattern, true); + knitter->startKnitting(Kh910, START_NEEDLE, STOP_NEEDLE, pattern, true); // first operate EXPECT_CALL(*arduinoMock, delay(2000)); @@ -454,26 +465,28 @@ TEST_F(KnitterTest, test_operate_Kh910) { EXPECT_CALL(*encodersMock, getHallValue(Right)); EXPECT_CALL(*encodersMock, getDirection); EXPECT_CALL(*solenoidsMock, setSolenoid); - expected_operate(false); + expected_knit(false); // no useful position calculated by `calculatePixelAndSolenoid()` expected_isr(100, NoDirection, Right, Shifted, Garter); expect_indState(); - expected_operate(false); + expected_knit(false); // don't set `m_workedonline` to `true` expected_isr(8 + STOP_NEEDLE + OFFSET); EXPECT_CALL(*solenoidsMock, setSolenoid); expect_indState(); - expected_operate(false); + expected_knit(false); expected_isr(START_NEEDLE); EXPECT_CALL(*solenoidsMock, setSolenoid); expect_indState(); - expected_operate(false); + expected_knit(false); // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); } TEST_F(KnitterTest, test_operate_Kh270) { @@ -495,7 +508,7 @@ TEST_F(KnitterTest, test_operate_Kh270) { const uint8_t START_NEEDLE = NUM_NEEDLES[Kh270] - 2; const uint8_t STOP_NEEDLE = NUM_NEEDLES[Kh270] - 1; const uint8_t OFFSET = END_OF_LINE_OFFSET_R[Kh270]; - knitter->startOperation(Kh270, START_NEEDLE, STOP_NEEDLE, pattern, true); + knitter->startKnitting(Kh270, START_NEEDLE, STOP_NEEDLE, pattern, true); // first operate EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_B, 0)) @@ -513,51 +526,58 @@ TEST_F(KnitterTest, test_operate_Kh270) { EXPECT_CALL(*encodersMock, getHallValue(Right)); EXPECT_CALL(*encodersMock, getDirection); EXPECT_CALL(*solenoidsMock, setSolenoid); - expected_operate(false); + expected_knit(false); // no useful position calculated by `calculatePixelAndSolenoid()` expected_isr(100, NoDirection, Right, Shifted, Garter); expect_indState(); - expected_operate(false); + expected_knit(false); // don't set `m_workedonline` to `true` expected_isr(8 + STOP_NEEDLE + OFFSET); EXPECT_CALL(*solenoidsMock, setSolenoid); expect_indState(); - expected_operate(false); + expected_knit(false); expected_isr(START_NEEDLE); EXPECT_CALL(*solenoidsMock, setSolenoid); expect_indState(); - expected_operate(false); + expected_knit(false); // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_operate_line_request) { EXPECT_CALL(*solenoidsMock, setSolenoid); // `m_workedOnLine` is set to `true` - expected_operate(true); + expected_knit(true); // Position has changed since last call to operate function // `m_pixelToSet` is set above `m_stopNeedle` + END_OF_LINE_OFFSET_R expected_isr(NUM_NEEDLES[Kh910] + 8 + END_OF_LINE_OFFSET_R[Kh910] + 1); EXPECT_CALL(*solenoidsMock, setSolenoid); - expected_operate(false); + expected_knit(false); // no change in position, no action. EXPECT_CALL(*solenoidsMock, setSolenoid).Times(0); - expected_operate(false); + expected_knit(false); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_operate_lastline) { EXPECT_CALL(*solenoidsMock, setSolenoid); // `m_workedOnLine` is set to true - expected_operate(true); + expected_knit(true); // Position has changed since last call to operate function // `m_pixelToSet` is above `m_stopNeedle` + END_OF_LINE_OFFSET_R @@ -570,14 +590,16 @@ TEST_F(KnitterTest, test_operate_lastline) { EXPECT_CALL(*beeperMock, endWork); EXPECT_CALL(*solenoidsMock, setSolenoids(0xFFFF)); EXPECT_CALL(*beeperMock, finishedLine); - expected_operate(false); + expected_knit(false); // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_operate_lastline_and_no_req) { - get_to_operate(Kh910); + get_to_knit(Kh910); // Note: probing private data and methods to get full branch coverage. knitter->m_stopNeedle = 100; @@ -595,7 +617,7 @@ TEST_F(KnitterTest, test_operate_lastline_and_no_req) { EXPECT_CALL(*beeperMock, endWork); EXPECT_CALL(*solenoidsMock, setSolenoids(0xFFFF)); EXPECT_CALL(*beeperMock, finishedLine); - knitter->state_operate(); + knitter->state_knit(); ASSERT_EQ(knitter->getStartOffset(NUM_DIRECTIONS), 0); knitter->m_carriage = NUM_CARRIAGES; @@ -603,22 +625,29 @@ TEST_F(KnitterTest, test_operate_lastline_and_no_req) { // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_operate_same_position) { EXPECT_CALL(*solenoidsMock, setSolenoid); - expected_operate(true); + expected_knit(true); // no call to `setSolenoid()` since position was the same EXPECT_CALL(*solenoidsMock, setSolenoid).Times(0); - expected_operate(false); + expected_knit(false); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_operate_new_line) { EXPECT_CALL(*solenoidsMock, setSolenoid); // _workedOnLine is set to true - expected_operate(true); + expected_knit(true); // Position has changed since last call to operate function // `m_pixelToSet` is above `m_stopNeedle` + END_OF_LINE_OFFSET_R @@ -632,10 +661,12 @@ TEST_F(KnitterTest, test_operate_new_line) { // `reqLine()` is called which calls `send()` expect_send(); - expected_operate(false); + expected_knit(false); // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_calculatePixelAndSolenoid) { @@ -691,6 +722,8 @@ TEST_F(KnitterTest, test_calculatePixelAndSolenoid) { // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(testerMock)); + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_getStartOffset) { diff --git a/test/test_tester.cpp b/test/test_tester.cpp index 7fab8058e..4f1421c63 100644 --- a/test/test_tester.cpp +++ b/test/test_tester.cpp @@ -105,69 +105,43 @@ TEST_F(TesterTest, test_beepCmd) { TEST_F(TesterTest, test_setSingleCmd_fail1) { // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) // .Times(AtLeast(1)); - // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(nullptr)); - EXPECT_CALL(*knitterMock, setSolenoids).Times(0); - tester->setSingleCmd(); + const uint8_t buf[] = {setSingleCmd_msgid, 0}; + tester->setSingleCmd(buf, 2); } TEST_F(TesterTest, test_setSingleCmd_fail2) { // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) // .Times(AtLeast(1)); - // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(sixteen)); - EXPECT_CALL(*knitterMock, setSolenoids).Times(0); - tester->setSingleCmd(); + const uint8_t buf[] = {setSingleCmd_msgid, 16, 0}; + tester->setSingleCmd(buf, 3); } TEST_F(TesterTest, test_setSingleCmd_fail3) { // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) // .Times(AtLeast(1)); - // EXPECT_CALL(*serialCommandMock, next) - // .WillOnce(Return(zero)) - // .WillOnce(Return(nullptr)); - EXPECT_CALL(*knitterMock, setSolenoids).Times(0); - tester->setSingleCmd(); -} - -TEST_F(TesterTest, test_setSingleCmd_fail4) { - // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - // .Times(AtLeast(1)); - // EXPECT_CALL(*serialCommandMock, next) - // .WillOnce(Return(zero)) - // .WillOnce(Return(two)); - EXPECT_CALL(*knitterMock, setSolenoids).Times(0); - tester->setSingleCmd(); + const uint8_t buf[] = {setSingleCmd_msgid, 15, 2}; + tester->setSingleCmd(buf, 3); } TEST_F(TesterTest, test_setSingleCmd_success) { // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) // .Times(AtLeast(1)); - // EXPECT_CALL(*serialCommandMock, next).WillRepeatedly(Return(zero)); - EXPECT_CALL(*knitterMock, setSolenoids); - tester->setSingleCmd(); + const uint8_t buf[] = {setSingleCmd_msgid, 15, 1}; + tester->setSingleCmd(buf, 3); } TEST_F(TesterTest, test_setAllCmd_fail1) { // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) // .Times(AtLeast(1)); - // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(nullptr)); - EXPECT_CALL(*knitterMock, setSolenoids).Times(0); - tester->setAllCmd(); -} - -TEST_F(TesterTest, test_setAllCmd_fail2) { - // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - // .Times(AtLeast(1)); - // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(g)); - EXPECT_CALL(*knitterMock, setSolenoids).Times(0); - tester->setAllCmd(); + const uint8_t buf[] = {setAllCmd_msgid, 0}; + tester->setAllCmd(buf, 2); } TEST_F(TesterTest, test_setAllCmd_success) { // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) // .Times(AtLeast(1)); - // EXPECT_CALL(*serialCommandMock, next).WillOnce(Return(fAdE)); - EXPECT_CALL(*knitterMock, setSolenoids); - tester->setAllCmd(); + const uint8_t buf[] = {setAllCmd_msgid, 0xff, 0xff}; + tester->setAllCmd(buf, 3); } TEST_F(TesterTest, test_readEOLsensorsCmd) { @@ -244,7 +218,7 @@ TEST_F(TesterTest, test_loop_autoTestEven) { tester->m_autoTestOn = true; EXPECT_CALL(*arduinoMock, digitalRead).Times(0); EXPECT_CALL(*arduinoMock, digitalWrite).Times(2); - EXPECT_CALL(*knitterMock, setSolenoids); + // EXPECT_CALL(*knitterMock, setSolenoids); tester->loop(); } @@ -256,7 +230,7 @@ TEST_F(TesterTest, test_loop_autoTestOdd) { tester->m_autoTestOn = true; EXPECT_CALL(*arduinoMock, digitalRead).Times(3); EXPECT_CALL(*arduinoMock, digitalWrite).Times(2); - EXPECT_CALL(*knitterMock, setSolenoids); + // EXPECT_CALL(*knitterMock, setSolenoids); tester->loop(); } From 72fef244010b4c88c8b220441ffad5116054f4f8 Mon Sep 17 00:00:00 2001 From: Tom Date: Sat, 22 Aug 2020 17:40:53 -0400 Subject: [PATCH 09/17] Add GlobalEncoders class. --- src/ayab/beeper.cpp | 4 +- src/ayab/com.cpp | 2 +- src/ayab/encoders.cpp | 83 +++++++------- src/ayab/encoders.h | 115 ++++++++++++++++---- src/ayab/global_beeper.cpp | 3 + src/ayab/global_com.cpp | 2 + src/ayab/global_encoders.cpp | 92 ++++++++++++++++ src/ayab/global_knitter.cpp | 3 + src/ayab/global_solenoids.cpp | 3 + src/ayab/global_tester.cpp | 2 + src/ayab/knitter.cpp | 62 ++++++----- src/ayab/knitter.h | 49 +++++---- src/ayab/main.cpp | 6 ++ src/ayab/tester.cpp | 2 +- test/CMakeLists.txt | 10 +- test/mocks/encoders_mock.cpp | 14 +-- test/mocks/encoders_mock.h | 6 +- test/test_all.cpp | 3 + test/test_boards.cpp | 4 + test/test_encoders.cpp | 196 +++++++++++++++++----------------- test/test_knitter.cpp | 60 +++++++++-- 21 files changed, 481 insertions(+), 240 deletions(-) create mode 100644 src/ayab/global_encoders.cpp diff --git a/src/ayab/beeper.cpp b/src/ayab/beeper.cpp index eb112b36f..779390cb8 100644 --- a/src/ayab/beeper.cpp +++ b/src/ayab/beeper.cpp @@ -1,7 +1,7 @@ /*! * \file beeper.cpp - * \brief Singleton class containing methods to actuate a beeper - * connected to PIEZO_PIN. + * \brief Class containing methods to actuate a beeper connected + * to PIEZO_PIN. * * This file is part of AYAB. * diff --git a/src/ayab/com.cpp b/src/ayab/com.cpp index 41100513d..bb3d61b56 100644 --- a/src/ayab/com.cpp +++ b/src/ayab/com.cpp @@ -1,6 +1,6 @@ /*! * \file com.cpp - * \brief Singleton class containing methods for serial communication. + * \brief Class containing methods for serial communication. * * This file is part of AYAB. * diff --git a/src/ayab/encoders.cpp b/src/ayab/encoders.cpp index f2d3c211f..c62daeff3 100644 --- a/src/ayab/encoders.cpp +++ b/src/ayab/encoders.cpp @@ -1,6 +1,7 @@ /*! * \file encoders.cpp - * \brief Class containing method governing encoders. + * \brief Class containing methods governing encoders + * for BeltShift, Direction, Active Hall sensor and Carriage Type. * * This file is part of AYAB. * @@ -22,9 +23,9 @@ * http://ayab-knitting.com */ +#include "board.h" #include -#include "board.h" #include "encoders.h" /*! @@ -46,69 +47,75 @@ void Encoders::encA_interrupt() { m_oldState = currentState; } +/*! + * \brief Read hall sensor on left and right. + */ +uint16_t Encoders::getHallValue(Direction_t pSensor) { + switch (pSensor) { + case Left: + return analogRead(EOL_PIN_L); + case Right: + return analogRead(EOL_PIN_R); + default: + return 0; + } +} + +/*! + * \brief Initialize machine type. + */ +void Encoders::init(Machine_t machineType) { + m_machineType = machineType; + m_position = 0U; + m_direction = NoDirection; + m_hallActive = NoDirection; + m_beltShift = Unknown; + m_carriage = NoCarriage; + m_oldState = false; +} + /*! * \brief Get position member. */ -uint8_t Encoders::getPosition() const { - return m_encoderPos; +uint8_t Encoders::getPosition() { + return m_position; } /*! - * \brief Get beltshift member. + * \brief Get beltShift member. */ -Beltshift_t Encoders::getBeltshift() const { +BeltShift_t Encoders::getBeltShift() { return m_beltShift; } /*! * \brief Get direction member. */ -Direction_t Encoders::getDirection() const { +Direction_t Encoders::getDirection() { return m_direction; } /*! * \brief Get hallActive member. */ -Direction_t Encoders::getHallActive() const { +Direction_t Encoders::getHallActive() { return m_hallActive; } /*! * \brief Get carriage member. */ -Carriage_t Encoders::getCarriage() const { +Carriage_t Encoders::getCarriage() { return m_carriage; } /*! * \brief Get machine type. */ -Machine_t Encoders::getMachineType() const { +Machine_t Encoders::getMachineType() { return m_machineType; } -/*! - * \brief Set machine type. - */ -void Encoders::init(Machine_t machineType) { - m_machineType = machineType; -} - -/*! - * \brief Read hall sensor on left and right. - */ -uint16_t Encoders::getHallValue(Direction_t pSensor) { - switch (pSensor) { - case Left: - return analogRead(EOL_PIN_L); - case Right: - return analogRead(EOL_PIN_R); - default: - return 0; - } -} - // Private Methods /*! @@ -124,8 +131,8 @@ void Encoders::encA_rising() { // Update carriage position if (Right == m_direction) { - if (m_encoderPos < END_RIGHT[m_machineType]) { - m_encoderPos++; + if (m_position < END_RIGHT[m_machineType]) { + m_position++; } } @@ -139,7 +146,7 @@ void Encoders::encA_rising() { if ((m_machineType == Kh270) || (hallValue >= FILTER_L_MIN[m_machineType])) { m_carriage = Knit; - } else if (m_carriage == Knit /*&& m_encoderPos == ?? */) { + } else if (m_carriage == Knit /*&& m_position == ?? */) { m_carriage = Garter; } else { m_carriage = Lace; @@ -149,7 +156,7 @@ void Encoders::encA_rising() { m_beltShift = digitalRead(ENC_PIN_C) != 0 ? Regular : Shifted; // Known position of the carriage -> overwrite position - m_encoderPos = END_LEFT[m_machineType] + END_OFFSET[m_machineType]; + m_position = END_LEFT[m_machineType] + END_OFFSET[m_machineType]; } } @@ -163,8 +170,8 @@ void Encoders::encA_rising() { void Encoders::encA_falling() { // Update carriage position if (Left == m_direction) { - if (m_encoderPos > END_LEFT[m_machineType]) { - m_encoderPos--; + if (m_position > END_LEFT[m_machineType]) { + m_position--; } } @@ -188,6 +195,6 @@ void Encoders::encA_falling() { m_beltShift = digitalRead(ENC_PIN_C) != 0 ? Shifted : Regular; // Known position of the carriage -> overwrite position - m_encoderPos = END_RIGHT[m_machineType] - END_OFFSET[m_machineType]; + m_position = END_RIGHT[m_machineType] - END_OFFSET[m_machineType]; } } diff --git a/src/ayab/encoders.h b/src/ayab/encoders.h index af5d1ee14..56967b79d 100644 --- a/src/ayab/encoders.h +++ b/src/ayab/encoders.h @@ -21,8 +21,6 @@ * http://ayab-knitting.com */ -// TODO(TP): incorporate into machine instead of knitter - #ifndef ENCODERS_H_ #define ENCODERS_H_ @@ -42,8 +40,8 @@ enum Carriage { }; using Carriage_t = enum Carriage; -enum Beltshift { Unknown, Regular, Shifted, Lace_Regular, Lace_Shifted }; -using Beltshift_t = enum Beltshift; +enum BeltShift { Unknown, Regular, Shifted, Lace_Regular, Lace_Shifted }; +using BeltShift_t = enum BeltShift; enum MachineType { NoMachine = -1, @@ -99,34 +97,105 @@ constexpr uint16_t SOLENOIDS_BITMASK = 0xFFFFU; /*! * \brief Encoder interface. * - * Encoders for Beltshift, Direction, Active Hall sensor and Carriage Type. + * Encoders for BeltShift, Direction, Active Hall sensor and Carriage Type. */ -class Encoders { +class EncodersInterface { public: - Encoders() = default; + virtual ~EncodersInterface(){}; - void encA_interrupt(); + // any methods that need to be mocked should go here + virtual void encA_interrupt() = 0; + virtual uint16_t getHallValue(Direction_t pSensor) = 0; + + virtual void init(Machine_t machineType) = 0; + virtual Machine_t getMachineType() = 0; + + // virtual void setBeltShift(BeltShift_t beltShift) = 0; + virtual BeltShift_t getBeltShift() = 0; + + // virtual void setCarriage(Carriage_t carriage) = 0; + virtual Carriage_t getCarriage() = 0; + + // virtual void setDirection(Direction_t direction) = 0; + virtual Direction_t getDirection() = 0; + + // virtual void setHallActive(Direction_t hallActive) = 0; + virtual Direction_t getHallActive() = 0; + // virtual void setPosition(uint8_t position) = 0; + virtual uint8_t getPosition() = 0; +}; + +// Container class for the static methods for the encoders. +// Dependency injection is enabled using a pointer to a global instance of +// either `Encoders` or `EncodersMock`, both of which classes implement the +// pure virtual methods of `EncodersInterface`. + +class GlobalEncoders final { +private: + // singleton class so private constructor is appropriate + GlobalEncoders() = default; + +public: + // pointer to global instance whose methods are implemented + static EncodersInterface *m_instance; + + static void encA_interrupt(); static uint16_t getHallValue(Direction_t pSensor); - // getter/setter functions to assist mocking - uint8_t getPosition() const; - Beltshift_t getBeltshift() const; - Direction_t getDirection() const; - Direction_t getHallActive() const; - Carriage_t getCarriage() const; - Machine_t getMachineType() const; + static void init(Machine_t machineType); + static Machine_t getMachineType(); + + // static void setBeltShift(BeltShift_t beltShift); + static BeltShift_t getBeltShift(); + + // static void setCarriage(Carriage_t carriage); + static Carriage_t getCarriage(); + + // static void setDirection(Direction_t direction); + static Direction_t getDirection(); + + // static void setHallActive(Direction_t hallActive); + static Direction_t getHallActive(); + + // static void setPosition(uint8_t position); + static uint8_t getPosition(); +}; + +class Encoders : public EncodersInterface { +public: + Encoders() = default; + + void encA_interrupt(); + uint16_t getHallValue(Direction_t pSensor); + void init(Machine_t machineType); + Machine_t getMachineType(); + + // void setBeltShift(BeltShift_t beltShift); + BeltShift_t getBeltShift(); + + // void setCarriage(Carriage_t carriage); + Carriage_t getCarriage(); + + // void setDirection(Direction_t direction); + Direction_t getDirection(); + + // void setHallActive(Direction_t hallActive); + Direction_t getHallActive(); + + // void setPosition(uint8_t position); + uint8_t getPosition(); private: - Machine_t m_machineType = NoMachine; - - volatile Direction_t m_direction = NoDirection; - volatile Direction_t m_hallActive = NoDirection; - volatile Beltshift_t m_beltShift = Unknown; - volatile Carriage_t m_carriage = NoCarriage; - volatile uint8_t m_encoderPos = 0x00; - volatile bool m_oldState = false; + Machine_t m_machineType; + + volatile BeltShift_t m_beltShift; + volatile Carriage_t m_carriage; + volatile Direction_t m_direction; + volatile Direction_t m_hallActive; + volatile uint8_t m_position; + volatile bool m_oldState; void encA_rising(); void encA_falling(); diff --git a/src/ayab/global_beeper.cpp b/src/ayab/global_beeper.cpp index 4a1f2f0a3..36497bf58 100644 --- a/src/ayab/global_beeper.cpp +++ b/src/ayab/global_beeper.cpp @@ -1,5 +1,8 @@ /*! * \file global_beeper.cpp + * \brief Singleton class containing methods to actuate a beeper + * connected to PIEZO_PIN. + * * This file is part of AYAB. * * AYAB is free software: you can redistribute it and/or modify diff --git a/src/ayab/global_com.cpp b/src/ayab/global_com.cpp index 5354d0032..345e76826 100644 --- a/src/ayab/global_com.cpp +++ b/src/ayab/global_com.cpp @@ -1,5 +1,7 @@ /*! * \file global_com.cpp + * \brief Singleton class containing methods for serial communication. + * * This file is part of AYAB. * * AYAB is free software: you can redistribute it and/or modify diff --git a/src/ayab/global_encoders.cpp b/src/ayab/global_encoders.cpp new file mode 100644 index 000000000..30cefff2d --- /dev/null +++ b/src/ayab/global_encoders.cpp @@ -0,0 +1,92 @@ +/*! + * \file global_encoders.cpp + * \brief Singleton class containing methods governing encoders + * for BeltShift, Direction, Active Hall sensor and Carriage Type. + * + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#include "encoders.h" + +void GlobalEncoders::encA_interrupt() { + m_instance->encA_interrupt(); +} + +uint16_t GlobalEncoders::getHallValue(Direction_t pSensor) { + return m_instance->getHallValue(pSensor); +} + +void GlobalEncoders::init(Machine_t machineType) { + m_instance->init(machineType); +} + +/* +Machine_t GlobalEncoders::getMachineType() { + return m_instance->getMachineType(); +} + +void GlobalEncoders::setBeltShift(BeltShift_t beltShift) { + m_instance->setBeltShift(beltShift); +} +*/ + +BeltShift_t GlobalEncoders::getBeltShift() { + return m_instance->getBeltShift(); +} + +/* +void GlobalEncoders::setCarriage(Carriage_t carriage) { + m_instance->setCarriage(carriage); +} +*/ + +Carriage_t GlobalEncoders::getCarriage() { + return m_instance->getCarriage(); +} + +/* +void GlobalEncoders::setDirection(Direction_t direction) { + m_instance->setDirection(direction); +} +*/ + +Direction_t GlobalEncoders::getDirection() { + return m_instance->getDirection(); +} + +/* +void GlobalEncoders::setHallActive(Direction_t hallActive) { + m_instance->setHallActive(hallActive); +} +*/ + +Direction_t GlobalEncoders::getHallActive() { + return m_instance->getHallActive(); +} + +/* +void GlobalEncoders::setPosition(uint8_t position) { + m_instance->setPosition(position); +} +*/ + +uint8_t GlobalEncoders::getPosition() { + return m_instance->getPosition(); +} diff --git a/src/ayab/global_knitter.cpp b/src/ayab/global_knitter.cpp index a58801aa9..da689caa5 100644 --- a/src/ayab/global_knitter.cpp +++ b/src/ayab/global_knitter.cpp @@ -1,5 +1,8 @@ /*! * \file global_knitter.cpp + * \brief Singleton class containing methods for the finite state machine + * that co-ordinates the AYAB firmware. + * * This file is part of AYAB. * * AYAB is free software: you can redistribute it and/or modify diff --git a/src/ayab/global_solenoids.cpp b/src/ayab/global_solenoids.cpp index f5976d510..e77232c4c 100644 --- a/src/ayab/global_solenoids.cpp +++ b/src/ayab/global_solenoids.cpp @@ -1,5 +1,8 @@ /*! * \file global_solenoids.cpp + * \brief Singleton class containing methods that control the needles + * via solenoids connected to IO expanders on the device. + * * This file is part of AYAB. * * AYAB is free software: you can redistribute it and/or modify diff --git a/src/ayab/global_tester.cpp b/src/ayab/global_tester.cpp index 1aebae945..732b0f5a5 100644 --- a/src/ayab/global_tester.cpp +++ b/src/ayab/global_tester.cpp @@ -1,5 +1,7 @@ /*! * \file global_tester.cpp + * \brief Singleton class containing methods for hardware testing. + * * This file is part of AYAB. * * AYAB is free software: you can redistribute it and/or modify diff --git a/src/ayab/knitter.cpp b/src/ayab/knitter.cpp index f83db1438..157421905 100644 --- a/src/ayab/knitter.cpp +++ b/src/ayab/knitter.cpp @@ -1,6 +1,6 @@ /*! * \file knitter.cpp - * \brief Singleton class containing methods for the finite state machine + * \brief Class containing methods for the finite state machine * that co-ordinates the AYAB firmware. * * This file is part of AYAB. @@ -28,6 +28,7 @@ #include "beeper.h" #include "board.h" #include "com.h" +#include "encoders.h" #include "knitter.h" #include "tester.h" @@ -58,28 +59,31 @@ void Knitter::init() { setUpInterrupt(); + GlobalSolenoids::init(); + // explicitly initialize members + + // machine state m_opState = s_init; + + // job parameters m_machineType = NoMachine; m_startNeedle = 0U; m_stopNeedle = 0U; m_lineBuffer = nullptr; m_continuousReportingEnabled = false; - m_position = 0U; - m_direction = NoDirection; - m_hallActive = NoDirection; - m_beltshift = Unknown; - m_carriage = NoCarriage; + m_lineRequested = false; m_currentLineNumber = 0U; m_lastLineFlag = false; m_sOldPosition = 0U; m_firstRun = true; m_workedOnLine = false; +#ifdef DBG_NOMACHINE + m_prevState = false; +#endif m_solenoidToSet = 0U; m_pixelToSet = 0U; - - GlobalSolenoids::init(); } void Knitter::setUpInterrupt() { @@ -99,12 +103,12 @@ void Knitter::setUpInterrupt() { */ void Knitter::isr() { // update machine state data - m_encoders.encA_interrupt(); - m_position = m_encoders.getPosition(); - m_direction = m_encoders.getDirection(); - m_hallActive = m_encoders.getHallActive(); - m_beltshift = m_encoders.getBeltshift(); - m_carriage = m_encoders.getCarriage(); + GlobalEncoders::encA_interrupt(); + m_position = GlobalEncoders::getPosition(); + m_direction = GlobalEncoders::getDirection(); + m_hallActive = GlobalEncoders::getHallActive(); + m_beltShift = GlobalEncoders::getBeltShift(); + m_carriage = GlobalEncoders::getCarriage(); } /*! @@ -145,7 +149,7 @@ void Knitter::fsm() { bool Knitter::startKnitting(Machine_t machineType, uint8_t startNeedle, uint8_t stopNeedle, uint8_t *pattern_start, bool continuousReportingEnabled) { - if ((m_opState != s_ready) || (machineType == NoMachine) || + if ((getState() != s_ready) || (machineType == NoMachine) || (machineType >= NUM_MACHINES) || (pattern_start == nullptr) || (startNeedle >= stopNeedle) || (stopNeedle >= NUM_NEEDLES[machineType])) { // TODO(TP): error code @@ -166,10 +170,10 @@ bool Knitter::startKnitting(Machine_t machineType, uint8_t startNeedle, m_lastLineFlag = false; // initialize encoders - m_encoders.init(machineType); + GlobalEncoders::init(machineType); // proceed to next state - m_opState = s_knit; + setState(s_knit); GlobalBeeper::ready(); // Attaching ENC_PIN_A, Interrupt #0 @@ -187,8 +191,8 @@ bool Knitter::startKnitting(Machine_t machineType, uint8_t startNeedle, bool Knitter::startTest(Machine_t machineType) { bool success = false; - if (s_init == m_opState || s_ready == m_opState) { - m_opState = s_test; + if (s_init == getState() || s_ready == getState()) { + setState(s_test); m_machineType = machineType; GlobalTester::setUp(); success = true; @@ -251,7 +255,7 @@ void Knitter::state_init() { // machine is initialized when left hall sensor is passed in Right direction if (Right == m_direction && Left == m_hallActive) { #endif // DBG_NOMACHINE - m_opState = s_ready; + setState(s_ready); GlobalSolenoids::setSolenoids(SOLENOIDS_BITMASK); indState(true); } @@ -358,7 +362,7 @@ void Knitter::state_test() { } GlobalTester::loop(); if (GlobalTester::getQuitFlag()) { - m_opState = s_ready; + setState(s_ready); } } @@ -379,9 +383,9 @@ bool Knitter::calculatePixelAndSolenoid() { // TODO(who?): check m_solenoidToSet = (m_position % 12) + 3; } else { - if (Regular == m_beltshift) { + if (Regular == m_beltShift) { m_solenoidToSet = m_position % SOLENOIDS_NUM; - } else if (Shifted == m_beltshift) { + } else if (Shifted == m_beltShift) { m_solenoidToSet = (m_position - HALF_SOLENOIDS_NUM) % SOLENOIDS_NUM; } if (Lace == m_carriage) { @@ -402,9 +406,9 @@ bool Knitter::calculatePixelAndSolenoid() { // TODO(who?): check m_solenoidToSet = ((m_position + 6) % 12) + 3; } else { - if (Regular == m_beltshift) { + if (Regular == m_beltShift) { m_solenoidToSet = (m_position + HALF_SOLENOIDS_NUM) % SOLENOIDS_NUM; - } else if (Shifted == m_beltshift) { + } else if (Shifted == m_beltShift) { m_solenoidToSet = m_position % SOLENOIDS_NUM; } if (Lace == m_carriage) { @@ -434,8 +438,8 @@ void Knitter::reqLine(const uint8_t lineNumber) { } void Knitter::indState(const bool initState) { - uint16_t leftHallValue = Encoders::getHallValue(Left); - uint16_t rightHallValue = Encoders::getHallValue(Right); + uint16_t leftHallValue = GlobalEncoders::getHallValue(Left); + uint16_t rightHallValue = GlobalEncoders::getHallValue(Right); uint8_t payload[INDSTATE_LEN] = { indState_msgid, static_cast(initState), @@ -445,14 +449,14 @@ void Knitter::indState(const bool initState) { lowByte(rightHallValue), static_cast(m_carriage), static_cast(m_position), - static_cast(m_encoders.getDirection()), + static_cast(GlobalEncoders::getDirection()), }; GlobalCom::send(static_cast(payload), INDSTATE_LEN); } void Knitter::stopKnitting() { GlobalBeeper::endWork(); - m_opState = s_ready; + setState(s_ready); GlobalSolenoids::setSolenoids(SOLENOIDS_BITMASK); GlobalBeeper::finishedLine(); diff --git a/src/ayab/knitter.h b/src/ayab/knitter.h index 5c87d38f3..8d10c0542 100644 --- a/src/ayab/knitter.h +++ b/src/ayab/knitter.h @@ -106,8 +106,9 @@ class Knitter : public KnitterInterface { FRIEND_TEST(KnitterTest, test_fsm_test_quit); FRIEND_TEST(KnitterTest, test_startKnitting_NoMachine); FRIEND_TEST(KnitterTest, test_startKnitting_notReady); - FRIEND_TEST(KnitterTest, test_startTest); - FRIEND_TEST(KnitterTest, test_startTest_in_operation); + FRIEND_TEST(KnitterTest, test_startTest_in_init); + FRIEND_TEST(KnitterTest, test_startTest_in_ready); + FRIEND_TEST(KnitterTest, test_startTest_in_knit); FRIEND_TEST(KnitterTest, test_setNextLine); #endif friend class Tester; @@ -140,39 +141,37 @@ class Knitter : public KnitterInterface { void stopKnitting(); OpState_t getState() const; - Encoders m_encoders; - // machine state OpState_t m_opState; // job parameters - Machine_t m_machineType = NoMachine; - uint8_t m_startNeedle = 0U; - uint8_t m_stopNeedle = 0U; - uint8_t *m_lineBuffer = nullptr; - bool m_continuousReportingEnabled = false; + Machine_t m_machineType; + uint8_t m_startNeedle; + uint8_t m_stopNeedle; + uint8_t *m_lineBuffer; + bool m_continuousReportingEnabled; // current machine state - uint8_t m_position = 0U; - Direction_t m_direction = NoDirection; - Direction_t m_hallActive = NoDirection; - Beltshift_t m_beltshift = Unknown; - Carriage_t m_carriage = NoCarriage; - - bool m_lineRequested = false; - uint8_t m_currentLineNumber = 0U; - bool m_lastLineFlag = false; - - uint8_t m_sOldPosition = 0U; - bool m_firstRun = true; - bool m_workedOnLine = false; + uint8_t m_position; + Direction_t m_direction; + Direction_t m_hallActive; + BeltShift_t m_beltShift; + Carriage_t m_carriage; + + bool m_lineRequested; + uint8_t m_currentLineNumber; + bool m_lastLineFlag; + + uint8_t m_sOldPosition; + bool m_firstRun; + bool m_workedOnLine; #ifdef DBG_NOMACHINE - bool m_prevState = false; + bool m_prevState; #endif // resulting needle data - uint8_t m_solenoidToSet = 0U; - uint8_t m_pixelToSet = 0U; + uint8_t m_solenoidToSet; + uint8_t m_pixelToSet; }; #endif // KNITTER_H_ diff --git a/src/ayab/main.cpp b/src/ayab/main.cpp index 345087f68..32ed32954 100644 --- a/src/ayab/main.cpp +++ b/src/ayab/main.cpp @@ -24,13 +24,18 @@ #include +#include "beeper.h" +#include "com.h" +#include "encoders.h" #include "knitter.h" +#include "solenoids.h" #include "tester.h" // global definitions // references everywhere else must use `extern` GlobalBeeper *beeper; GlobalCom *com; +GlobalEncoders *encoders; GlobalKnitter *knitter; GlobalSolenoids *solenoids; GlobalTester *tester; @@ -38,6 +43,7 @@ GlobalTester *tester; // initialize static members BeeperInterface *GlobalBeeper::m_instance = new Beeper(); ComInterface *GlobalCom::m_instance = new Com(); +EncodersInterface *GlobalEncoders::m_instance = new Encoders(); KnitterInterface *GlobalKnitter::m_instance = new Knitter(); SolenoidsInterface *GlobalSolenoids::m_instance = new Solenoids(); TesterInterface *GlobalTester::m_instance = new Tester(); diff --git a/src/ayab/tester.cpp b/src/ayab/tester.cpp index 698cc7b84..607cb523d 100644 --- a/src/ayab/tester.cpp +++ b/src/ayab/tester.cpp @@ -1,6 +1,6 @@ /*! * \file tester.cpp - * \brief Singleton class containing methods for hardware testing. + * \brief Class containing methods for hardware testing. * * This file is part of AYAB. * diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 04bbf4d29..2f4a3b4ba 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -40,6 +40,7 @@ set(COMMON_SOURCES ${PROJECT_SOURCE_DIR}/test_boards.cpp ${SOURCE_DIRECTORY}/encoders.cpp + ${SOURCE_DIRECTORY}/global_encoders.cpp ${PROJECT_SOURCE_DIR}/test_encoders.cpp ${SOURCE_DIRECTORY}/solenoids.cpp @@ -136,15 +137,16 @@ add_board(Mega) add_executable(${PROJECT_NAME}_knitter ${PROJECT_SOURCE_DIR}/test_all.cpp ${SOURCE_DIRECTORY}/knitter.cpp - ${SOURCE_DIRECTORY}/global_knitter.cpp - ${SOURCE_DIRECTORY}/global_com.cpp ${SOURCE_DIRECTORY}/global_beeper.cpp + ${SOURCE_DIRECTORY}/global_com.cpp + ${SOURCE_DIRECTORY}/global_encoders.cpp + ${SOURCE_DIRECTORY}/global_knitter.cpp ${SOURCE_DIRECTORY}/global_solenoids.cpp ${SOURCE_DIRECTORY}/global_tester.cpp - ${PROJECT_SOURCE_DIR}/mocks/solenoids_mock.cpp - ${PROJECT_SOURCE_DIR}/mocks/encoders_mock.cpp ${PROJECT_SOURCE_DIR}/mocks/beeper_mock.cpp ${PROJECT_SOURCE_DIR}/mocks/com_mock.cpp + ${PROJECT_SOURCE_DIR}/mocks/encoders_mock.cpp + ${PROJECT_SOURCE_DIR}/mocks/solenoids_mock.cpp ${PROJECT_SOURCE_DIR}/mocks/tester_mock.cpp ${PROJECT_SOURCE_DIR}/test_knitter.cpp ${SOFT_I2C_LIB} diff --git a/test/mocks/encoders_mock.cpp b/test/mocks/encoders_mock.cpp index 76d7ce7aa..1e138b81c 100644 --- a/test/mocks/encoders_mock.cpp +++ b/test/mocks/encoders_mock.cpp @@ -49,32 +49,32 @@ void Encoders::encA_interrupt() { gEncodersMock->encA_interrupt(); } -uint8_t Encoders::getPosition() const { +uint8_t Encoders::getPosition() { assert(gEncodersMock != NULL); return gEncodersMock->getPosition(); } -Beltshift_t Encoders::getBeltshift() const { +BeltShift_t Encoders::getBeltShift() { assert(gEncodersMock != NULL); - return gEncodersMock->getBeltshift(); + return gEncodersMock->getBeltShift(); } -Direction_t Encoders::getDirection() const { +Direction_t Encoders::getDirection() { assert(gEncodersMock != NULL); return gEncodersMock->getDirection(); } -Direction_t Encoders::getHallActive() const { +Direction_t Encoders::getHallActive() { assert(gEncodersMock != NULL); return gEncodersMock->getHallActive(); } -Carriage_t Encoders::getCarriage() const { +Carriage_t Encoders::getCarriage() { assert(gEncodersMock != NULL); return gEncodersMock->getCarriage(); } -Machine_t Encoders::getMachineType() const { +Machine_t Encoders::getMachineType() { assert(gEncodersMock != NULL); return gEncodersMock->getMachineType(); } diff --git a/test/mocks/encoders_mock.h b/test/mocks/encoders_mock.h index 0ea2b6283..d46573442 100644 --- a/test/mocks/encoders_mock.h +++ b/test/mocks/encoders_mock.h @@ -27,10 +27,10 @@ #include #include -class EncodersMock { +class EncodersMock : public EncodersInterface { public: MOCK_METHOD1(init, void(Machine_t)); - MOCK_METHOD0(getBeltshift, Beltshift_t()); + MOCK_METHOD0(getBeltShift, BeltShift_t()); MOCK_METHOD0(getDirection, Direction_t()); MOCK_METHOD0(getCarriage, Carriage_t()); MOCK_METHOD0(getMachineType, Machine_t()); @@ -43,4 +43,4 @@ class EncodersMock { EncodersMock *encodersMockInstance(); void releaseEncodersMock(); -#endif // ENCODERS_MOCK_H_ +#endif // ENCODERS_MOCK_H_ diff --git a/test/test_all.cpp b/test/test_all.cpp index b42f03b8b..e0a149936 100644 --- a/test/test_all.cpp +++ b/test/test_all.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -35,6 +36,7 @@ Knitter *knitter = new Knitter(); BeeperMock *beeper = new BeeperMock(); ComMock *com = new ComMock(); +EncodersMock *encoders = new EncodersMock(); SolenoidsMock *solenoids = new SolenoidsMock(); TesterMock *tester = new TesterMock(); @@ -42,6 +44,7 @@ TesterMock *tester = new TesterMock(); KnitterInterface *GlobalKnitter::m_instance = knitter; BeeperInterface *GlobalBeeper::m_instance = beeper; ComInterface *GlobalCom::m_instance = com; +EncodersInterface *GlobalEncoders::m_instance = encoders; SolenoidsInterface *GlobalSolenoids::m_instance = solenoids; TesterInterface *GlobalTester::m_instance = tester; diff --git a/test/test_boards.cpp b/test/test_boards.cpp index b0807acd8..ff4f95943 100644 --- a/test/test_boards.cpp +++ b/test/test_boards.cpp @@ -25,6 +25,8 @@ #include #include +#include +#include #include #include @@ -34,6 +36,7 @@ KnitterMock *knitter = new KnitterMock(); Beeper *beeper = new Beeper(); Com *com = new Com(); +Encoders *encoders = new Encoders(); Solenoids *solenoids = new Solenoids(); Tester *tester = new Tester(); @@ -41,6 +44,7 @@ Tester *tester = new Tester(); KnitterInterface *GlobalKnitter::m_instance = knitter; BeeperInterface *GlobalBeeper::m_instance = beeper; ComInterface *GlobalCom::m_instance = com; +EncodersInterface *GlobalEncoders::m_instance = encoders; SolenoidsInterface *GlobalSolenoids::m_instance = solenoids; TesterInterface *GlobalTester::m_instance = tester; diff --git a/test/test_encoders.cpp b/test/test_encoders.cpp index 329e4a54b..431582291 100644 --- a/test/test_encoders.cpp +++ b/test/test_encoders.cpp @@ -1,4 +1,4 @@ -/*!` +/*!`s * \file test_encoders.cpp * * This file is part of AYAB. @@ -28,12 +28,13 @@ using ::testing::Return; +extern Encoders *encoders; + class EncodersTest : public ::testing::Test { protected: void SetUp() override { arduinoMock = arduinoMockInstance(); - e = Encoders(); - e.init(Kh910); + encoders->init(Kh910); } void TearDown() override { @@ -41,7 +42,6 @@ class EncodersTest : public ::testing::Test { } ArduinoMock *arduinoMock; - Encoders e; }; TEST_F(EncodersTest, test_encA_rising_not_in_front) { @@ -53,21 +53,21 @@ TEST_F(EncodersTest, test_encA_rising_not_in_front) { .WillOnce(Return(true)); // We have not entered the rising function yet EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)).Times(0); - e.encA_interrupt(); + encoders->encA_interrupt(); // Enter rising function, direction is right EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_B)).WillOnce(Return(true)); // Not in front of Left Hall Sensor EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)) - .WillOnce(Return(FILTER_L_MIN[e.getMachineType()])); - e.encA_interrupt(); - ASSERT_EQ(e.getDirection(), Right); - ASSERT_EQ(e.getPosition(), 0x01); - ASSERT_EQ(e.getCarriage(), NoCarriage); + .WillOnce(Return(FILTER_L_MIN[encoders->getMachineType()])); + encoders->encA_interrupt(); + ASSERT_EQ(encoders->getDirection(), Right); + ASSERT_EQ(encoders->getPosition(), 0x01); + ASSERT_EQ(encoders->getCarriage(), NoCarriage); } TEST_F(EncodersTest, test_encA_rising_in_front_notKH270) { - ASSERT_FALSE(e.getMachineType() == Kh270); - ASSERT_EQ(e.getCarriage(), NoCarriage); + ASSERT_FALSE(encoders->getMachineType() == Kh270); + ASSERT_EQ(encoders->getCarriage(), NoCarriage); // We should not enter the falling function EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_R)).Times(0); // Create a rising edge @@ -75,7 +75,7 @@ TEST_F(EncodersTest, test_encA_rising_in_front_notKH270) { // We have not entered the rising function yet EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)).Times(0); - e.encA_interrupt(); + encoders->encA_interrupt(); // Create a rising edge EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_A)).WillOnce(Return(true)); @@ -83,22 +83,22 @@ TEST_F(EncodersTest, test_encA_rising_in_front_notKH270) { EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_B)).WillOnce(Return(true)); // In front of Left Hall Sensor EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)) - .WillOnce(Return(FILTER_L_MIN[e.getMachineType()] - 1)); - // Beltshift is regular + .WillOnce(Return(FILTER_L_MIN[encoders->getMachineType()] - 1)); + // BeltShift is regular EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_C)).WillOnce(Return(true)); - e.encA_interrupt(); + encoders->encA_interrupt(); - ASSERT_EQ(e.getDirection(), Right); - ASSERT_EQ(e.getHallActive(), Left); - ASSERT_EQ(e.getPosition(), END_OFFSET[e.getMachineType()]); - ASSERT_EQ(e.getCarriage(), Lace); - ASSERT_EQ(e.getBeltshift(), Regular); + ASSERT_EQ(encoders->getDirection(), Right); + ASSERT_EQ(encoders->getHallActive(), Left); + ASSERT_EQ(encoders->getPosition(), END_OFFSET[encoders->getMachineType()]); + ASSERT_EQ(encoders->getCarriage(), Lace); + ASSERT_EQ(encoders->getBeltShift(), Regular); } TEST_F(EncodersTest, test_encA_rising_in_front_KH270) { - e.init(Kh270); - ASSERT_TRUE(e.getMachineType() == Kh270); + encoders->init(Kh270); + ASSERT_TRUE(encoders->getMachineType() == Kh270); // We should not enter the falling function EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_R)).Times(0); // Create a rising edge @@ -106,7 +106,7 @@ TEST_F(EncodersTest, test_encA_rising_in_front_KH270) { // We have not entered the rising function yet EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)).Times(0); - e.encA_interrupt(); + encoders->encA_interrupt(); // Create a rising edge EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_A)).WillOnce(Return(true)); @@ -114,17 +114,17 @@ TEST_F(EncodersTest, test_encA_rising_in_front_KH270) { EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_B)).WillOnce(Return(true)); // In front of Left Hall Sensor EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)) - .WillOnce(Return(FILTER_L_MIN[e.getMachineType()] - 1)); - // Beltshift is regular + .WillOnce(Return(FILTER_L_MIN[encoders->getMachineType()] - 1)); + // BeltShift is regular EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_C)).WillOnce(Return(true)); - e.encA_interrupt(); + encoders->encA_interrupt(); - ASSERT_EQ(e.getDirection(), Right); - ASSERT_EQ(e.getHallActive(), Left); - ASSERT_EQ(e.getPosition(), END_OFFSET[e.getMachineType()]); - ASSERT_EQ(e.getCarriage(), Knit); - ASSERT_EQ(e.getBeltshift(), Regular); + ASSERT_EQ(encoders->getDirection(), Right); + ASSERT_EQ(encoders->getHallActive(), Left); + ASSERT_EQ(encoders->getPosition(), END_OFFSET[encoders->getMachineType()]); + ASSERT_EQ(encoders->getCarriage(), Knit); + ASSERT_EQ(encoders->getBeltShift(), Regular); } TEST_F(EncodersTest, test_encA_rising_in_front_G_carriage) { @@ -134,33 +134,33 @@ TEST_F(EncodersTest, test_encA_rising_in_front_G_carriage) { EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_B)).WillOnce(Return(true)); // In front of Left Hall Sensor EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)) - .WillOnce(Return(FILTER_L_MAX[e.getMachineType()] + 1)); - // Beltshift is regular + .WillOnce(Return(FILTER_L_MAX[encoders->getMachineType()] + 1)); + // BeltShift is regular EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_C)).WillOnce(Return(true)); - e.encA_interrupt(); + encoders->encA_interrupt(); - ASSERT_EQ(e.getCarriage(), Knit); + ASSERT_EQ(encoders->getCarriage(), Knit); // Create a falling edge EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_A)).WillOnce(Return(false)); EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_R)) - .WillOnce(Return(FILTER_R_MAX[e.getMachineType()] + 1)); + .WillOnce(Return(FILTER_R_MAX[encoders->getMachineType()] + 1)); EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_C)); - e.encA_interrupt(); + encoders->encA_interrupt(); // Create a rising edge EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_A)).WillOnce(Return(true)); // Enter rising function, direction is right EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_B)).WillOnce(Return(true)); // In front of Left Hall Sensor EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)) - .WillOnce(Return(FILTER_L_MIN[e.getMachineType()] - 1)); - // Beltshift is regular + .WillOnce(Return(FILTER_L_MIN[encoders->getMachineType()] - 1)); + // BeltShift is regular EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_C)).WillOnce(Return(true)); - e.encA_interrupt(); + encoders->encA_interrupt(); - ASSERT_EQ(e.getCarriage(), Garter); + ASSERT_EQ(encoders->getCarriage(), Garter); } TEST_F(EncodersTest, test_encA_falling_not_in_front) { @@ -175,15 +175,15 @@ TEST_F(EncodersTest, test_encA_falling_not_in_front) { EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_B)).WillOnce(Return(true)); // Not in front of Left Hall Sensor EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)) - .WillOnce(Return(FILTER_L_MIN[e.getMachineType()])); - e.encA_interrupt(); - e.encA_interrupt(); + .WillOnce(Return(FILTER_L_MIN[encoders->getMachineType()])); + encoders->encA_interrupt(); + encoders->encA_interrupt(); EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_A)).WillOnce(Return(false)); EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_R)) - .WillOnce(Return(FILTER_R_MIN[e.getMachineType()])); + .WillOnce(Return(FILTER_R_MIN[encoders->getMachineType()])); - e.encA_interrupt(); + encoders->encA_interrupt(); } TEST_F(EncodersTest, test_encA_falling_in_front) { @@ -198,23 +198,23 @@ TEST_F(EncodersTest, test_encA_falling_in_front) { EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_B)).WillOnce(Return(false)); // In front of Left Hall Sensor EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)) - .WillOnce(Return(FILTER_L_MIN[e.getMachineType()])); - e.encA_interrupt(); - e.encA_interrupt(); + .WillOnce(Return(FILTER_L_MIN[encoders->getMachineType()])); + encoders->encA_interrupt(); + encoders->encA_interrupt(); EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_A)).WillOnce(Return(false)); EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_R)) - .WillOnce(Return(FILTER_R_MAX[e.getMachineType()] + 1)); - // Beltshift is shifted + .WillOnce(Return(FILTER_R_MAX[encoders->getMachineType()] + 1)); + // BeltShift is shifted EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_C)).WillOnce(Return(true)); - e.encA_interrupt(); + encoders->encA_interrupt(); - ASSERT_EQ(e.getDirection(), Left); - ASSERT_EQ(e.getHallActive(), Right); - ASSERT_EQ(e.getPosition(), 227); - ASSERT_EQ(e.getCarriage(), NoCarriage); - ASSERT_EQ(e.getBeltshift(), Shifted); + ASSERT_EQ(encoders->getDirection(), Left); + ASSERT_EQ(encoders->getHallActive(), Right); + ASSERT_EQ(encoders->getPosition(), 227); + ASSERT_EQ(encoders->getCarriage(), NoCarriage); + ASSERT_EQ(encoders->getBeltShift(), Shifted); } TEST_F(EncodersTest, test_encA_falling_at_end) { @@ -225,65 +225,65 @@ TEST_F(EncodersTest, test_encA_falling_at_end) { EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_B)).WillOnce(Return(false)); // In front of Left Hall Sensor EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)) - .WillOnce(Return(FILTER_L_MAX[e.getMachineType()])); + .WillOnce(Return(FILTER_L_MAX[encoders->getMachineType()])); EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_C)).Times(0); - e.encA_interrupt(); + encoders->encA_interrupt(); EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_A)).WillOnce(Return(false)); EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_R)) - .WillOnce(Return(FILTER_R_MAX[e.getMachineType()] + 1)); + .WillOnce(Return(FILTER_R_MAX[encoders->getMachineType()] + 1)); EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_C)); - e.encA_interrupt(); - ASSERT_EQ(e.getPosition(), 227); + encoders->encA_interrupt(); + ASSERT_EQ(encoders->getPosition(), 227); uint16_t pos = 227; - while (pos < END_RIGHT[e.getMachineType()]) { + while (pos < END_RIGHT[encoders->getMachineType()]) { // Rising EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_A)).WillOnce(Return(true)); EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_B)).WillOnce(Return(true)); EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)) - .WillOnce(Return(FILTER_L_MAX[e.getMachineType()])); - e.encA_interrupt(); - ASSERT_EQ(e.getPosition(), ++pos); + .WillOnce(Return(FILTER_L_MAX[encoders->getMachineType()])); + encoders->encA_interrupt(); + ASSERT_EQ(encoders->getPosition(), ++pos); // Falling EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_A)).WillOnce(Return(false)); EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_R)) - .WillOnce(Return(FILTER_R_MAX[e.getMachineType()])); - e.encA_interrupt(); - ASSERT_EQ(e.getPosition(), pos); + .WillOnce(Return(FILTER_R_MAX[encoders->getMachineType()])); + encoders->encA_interrupt(); + ASSERT_EQ(encoders->getPosition(), pos); } - ASSERT_EQ(e.getPosition(), pos); + ASSERT_EQ(encoders->getPosition(), pos); // Rising EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_A)).WillOnce(Return(true)); EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_B)).WillOnce(Return(true)); EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)) - .WillOnce(Return(FILTER_L_MAX[e.getMachineType()])); - e.encA_interrupt(); - ASSERT_EQ(e.getPosition(), pos); + .WillOnce(Return(FILTER_L_MAX[encoders->getMachineType()])); + encoders->encA_interrupt(); + ASSERT_EQ(encoders->getPosition(), pos); } // requires FILTER_R_MIN != 0 TEST_F(EncodersTest, test_encA_falling_set_K_carriage_KH910) { - ASSERT_TRUE(e.getMachineType() == Kh910); + ASSERT_TRUE(encoders->getMachineType() == Kh910); // Create a rising edge EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_A)).WillOnce(Return(true)); EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_B)); EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_C)); EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)); - e.encA_interrupt(); + encoders->encA_interrupt(); // falling edge EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_A)).WillOnce(Return(false)); EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_R)) - .WillOnce(Return(FILTER_R_MIN[e.getMachineType()] - 1)); + .WillOnce(Return(FILTER_R_MIN[encoders->getMachineType()] - 1)); EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_C)); - e.encA_interrupt(); - ASSERT_EQ(e.getCarriage(), Knit); + encoders->encA_interrupt(); + ASSERT_EQ(encoders->getCarriage(), Knit); } TEST_F(EncodersTest, test_encA_falling_not_at_end) { @@ -292,64 +292,64 @@ TEST_F(EncodersTest, test_encA_falling_not_at_end) { EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_B)).WillOnce(Return(false)); EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_C)); EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)) - .WillOnce(Return(FILTER_R_MAX[e.getMachineType()] + 1)); - e.encA_interrupt(); - ASSERT_EQ(e.getPosition(), 28); + .WillOnce(Return(FILTER_R_MAX[encoders->getMachineType()] + 1)); + encoders->encA_interrupt(); + ASSERT_EQ(encoders->getPosition(), 28); // falling, direction is left and pos is > 0 EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_A)).WillOnce(Return(false)); EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_R)) - .WillOnce(Return(FILTER_R_MAX[e.getMachineType()])); - e.encA_interrupt(); - ASSERT_EQ(e.getPosition(), 27); + .WillOnce(Return(FILTER_R_MAX[encoders->getMachineType()])); + encoders->encA_interrupt(); + ASSERT_EQ(encoders->getPosition(), 27); } TEST_F(EncodersTest, test_getPosition) { - uint8_t p = e.getPosition(); + uint8_t p = encoders->getPosition(); ASSERT_EQ(p, 0x00); } -TEST_F(EncodersTest, test_getBeltshift) { - Beltshift_t b = e.getBeltshift(); +TEST_F(EncodersTest, test_getBeltShift) { + BeltShift_t b = encoders->getBeltShift(); ASSERT_EQ(b, Unknown); } TEST_F(EncodersTest, test_getDirection) { - Direction_t d = e.getDirection(); + Direction_t d = encoders->getDirection(); ASSERT_EQ(d, NoDirection); } TEST_F(EncodersTest, test_getHallActive) { - Direction_t d = e.getHallActive(); + Direction_t d = encoders->getHallActive(); ASSERT_EQ(d, NoDirection); } TEST_F(EncodersTest, test_getCarriage) { - Carriage_t c = e.getCarriage(); + Carriage_t c = encoders->getCarriage(); ASSERT_EQ(c, NoCarriage); } TEST_F(EncodersTest, test_getMachineType) { - Machine_t m = e.getMachineType(); + Machine_t m = encoders->getMachineType(); ASSERT_EQ(m, Kh910); } TEST_F(EncodersTest, test_init) { - e.init(Kh270); - Machine_t m = e.getMachineType(); + encoders->init(Kh270); + Machine_t m = encoders->getMachineType(); ASSERT_EQ(m, Kh270); } TEST_F(EncodersTest, test_getHallValue) { - uint16_t v = e.getHallValue(NoDirection); + uint16_t v = encoders->getHallValue(NoDirection); ASSERT_EQ(v, 0u); EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)); - v = e.getHallValue(Left); + v = encoders->getHallValue(Left); ASSERT_EQ(v, 0u); EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_R)); - v = e.getHallValue(Right); + v = encoders->getHallValue(Right); ASSERT_EQ(v, 0u); EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_R)).WillOnce(Return(0xbeefu)); - v = e.getHallValue(Right); + v = encoders->getHallValue(Right); ASSERT_EQ(v, 0xbeefu); } diff --git a/test/test_knitter.cpp b/test/test_knitter.cpp index 235857dea..4053d2f2b 100644 --- a/test/test_knitter.cpp +++ b/test/test_knitter.cpp @@ -25,7 +25,6 @@ #include #include -#include #include #include @@ -42,6 +41,7 @@ using ::testing::TypedEq; extern Knitter *knitter; extern BeeperMock *beeper; extern ComMock *com; +extern EncodersMock *encoders; extern SolenoidsMock *solenoids; extern TesterMock *tester; @@ -49,11 +49,11 @@ class KnitterTest : public ::testing::Test { protected: void SetUp() override { arduinoMock = arduinoMockInstance(); - encodersMock = encodersMockInstance(); // pointers to global instances beeperMock = beeper; comMock = com; + encodersMock = encoders; solenoidsMock = solenoids; testerMock = tester; @@ -62,6 +62,7 @@ class KnitterTest : public ::testing::Test { // cause a memory leak. We must notify the test that this is not the case. Mock::AllowLeak(beeperMock); Mock::AllowLeak(comMock); + Mock::AllowLeak(encodersMock); Mock::AllowLeak(solenoidsMock); Mock::AllowLeak(testerMock); @@ -71,7 +72,6 @@ class KnitterTest : public ::testing::Test { void TearDown() override { releaseArduinoMock(); - releaseEncodersMock(); } ArduinoMock *arduinoMock; @@ -94,17 +94,17 @@ class KnitterTest : public ::testing::Test { } void expect_isr(uint16_t pos, Direction_t dir, Direction_t hall, - Beltshift_t belt, Carriage_t carriage) { + BeltShift_t belt, Carriage_t carriage) { EXPECT_CALL(*encodersMock, encA_interrupt); EXPECT_CALL(*encodersMock, getPosition).WillRepeatedly(Return(pos)); EXPECT_CALL(*encodersMock, getDirection).WillRepeatedly(Return(dir)); EXPECT_CALL(*encodersMock, getHallActive).WillRepeatedly(Return(hall)); - EXPECT_CALL(*encodersMock, getBeltshift).WillRepeatedly(Return(belt)); + EXPECT_CALL(*encodersMock, getBeltShift).WillRepeatedly(Return(belt)); EXPECT_CALL(*encodersMock, getCarriage).WillRepeatedly(Return(carriage)); } void expected_isr(uint16_t pos, Direction_t dir, Direction_t hall, - Beltshift_t belt, Carriage_t carriage) { + BeltShift_t belt, Carriage_t carriage) { expect_isr(pos, dir, hall, belt, carriage); knitter->isr(); } @@ -205,10 +205,16 @@ TEST_F(KnitterTest, test_send) { uint8_t p[] = {1, 2, 3, 4, 5}; expect_send(); comMock->send(p, 5); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_isr) { expected_isr(1); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); } TEST_F(KnitterTest, test_fsm_default_case) { @@ -226,6 +232,7 @@ TEST_F(KnitterTest, test_fsm_init_LL) { ASSERT_EQ(knitter->getState(), s_init); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } @@ -236,6 +243,7 @@ TEST_F(KnitterTest, test_fsm_init_RR) { ASSERT_EQ(knitter->getState(), s_init); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } @@ -248,6 +256,7 @@ TEST_F(KnitterTest, test_fsm_init_RL) { ASSERT_EQ(knitter->getState(), s_ready); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } @@ -268,6 +277,7 @@ TEST_F(KnitterTest, test_fsm_ready) { ASSERT_EQ(knitter->getState(), s_ready); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } @@ -289,6 +299,7 @@ TEST_F(KnitterTest, test_fsm_test) { expected_fsm(); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(testerMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } @@ -317,6 +328,7 @@ TEST_F(KnitterTest, test_startKnitting_NoMachine) { false); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } @@ -339,6 +351,7 @@ TEST_F(KnitterTest, test_startKnitting_Kh910) { true); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); @@ -354,6 +367,7 @@ TEST_F(KnitterTest, test_startKnitting_Kh270) { true); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); @@ -377,20 +391,38 @@ TEST_F(KnitterTest, test_startKnitting_failures) { false); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } -TEST_F(KnitterTest, test_startTest) { +TEST_F(KnitterTest, test_startTest_in_init) { + // from state `s_init` EXPECT_CALL(*testerMock, setUp); + ASSERT_EQ(knitter->getState(), s_init); ASSERT_EQ(knitter->startTest(Kh910), true); + + // fail if call `startTest()` while already in state `s_test` ASSERT_EQ(knitter->getState(), s_test); + ASSERT_EQ(knitter->startTest(Kh270), false); // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(testerMock)); + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); +} + +TEST_F(KnitterTest, test_startTest_in_ready) { + get_to_ready(); + ASSERT_EQ(knitter->getState(), s_ready); + ASSERT_EQ(knitter->startTest(Kh930), true); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } -TEST_F(KnitterTest, test_startTest_in_operation) { +TEST_F(KnitterTest, test_startTest_in_knit) { get_to_knit(Kh910); ASSERT_EQ(knitter->getState(), s_knit); @@ -398,6 +430,7 @@ TEST_F(KnitterTest, test_startTest_in_operation) { ASSERT_EQ(knitter->startTest(Kh910), false); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); @@ -429,6 +462,7 @@ TEST_F(KnitterTest, test_setNextLine) { ASSERT_EQ(knitter->setNextLine(0), false); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); @@ -484,6 +518,7 @@ TEST_F(KnitterTest, test_operate_Kh910) { expected_knit(false); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); @@ -545,6 +580,7 @@ TEST_F(KnitterTest, test_operate_Kh270) { expected_knit(false); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); @@ -568,6 +604,7 @@ TEST_F(KnitterTest, test_operate_line_request) { expected_knit(false); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); @@ -593,6 +630,7 @@ TEST_F(KnitterTest, test_operate_lastline) { expected_knit(false); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); @@ -624,6 +662,7 @@ TEST_F(KnitterTest, test_operate_lastline_and_no_req) { ASSERT_EQ(knitter->getStartOffset(Right), 0); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); @@ -638,6 +677,7 @@ TEST_F(KnitterTest, test_operate_same_position) { expected_knit(false); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); @@ -664,6 +704,7 @@ TEST_F(KnitterTest, test_operate_new_line) { expected_knit(false); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); @@ -673,7 +714,7 @@ TEST_F(KnitterTest, test_calculatePixelAndSolenoid) { EXPECT_CALL(*testerMock, setUp); expected_set_machine(Kh910); - // new position, different beltshift and active hall + // new position, different beltShift and active hall expected_isr(100, Right, Right, Shifted, Lace); expected_test(); @@ -721,6 +762,7 @@ TEST_F(KnitterTest, test_calculatePixelAndSolenoid) { expected_test(); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(testerMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); From a1ce5d2bf0fe33035f6b0329fb4169afb86ffbad Mon Sep 17 00:00:00 2001 From: Tom Date: Sat, 22 Aug 2020 20:44:42 -0400 Subject: [PATCH 10/17] Added Fsm and GlobalFsm classes. --- src/ayab/com.cpp | 93 ++++++---- src/ayab/com.h | 15 ++ src/ayab/fsm.cpp | 99 +++++++++++ src/ayab/fsm.h | 82 +++++++++ src/ayab/global_com.cpp | 9 + src/ayab/global_fsm.cpp | 41 +++++ src/ayab/global_knitter.cpp | 20 +-- src/ayab/global_tester.cpp | 8 +- src/ayab/knitter.cpp | 327 ++++++++++++++---------------------- src/ayab/knitter.h | 43 ++--- src/ayab/main.cpp | 6 +- src/ayab/tester.cpp | 78 +++++---- src/ayab/tester.h | 11 +- test/CMakeLists.txt | 26 ++- test/mocks/com_mock.cpp | 12 ++ test/mocks/com_mock.h | 3 + test/mocks/fsm_mock.cpp | 61 +++++++ test/mocks/fsm_mock.h | 41 +++++ test/mocks/knitter_mock.cpp | 38 ++--- test/mocks/knitter_mock.h | 12 +- test/mocks/tester_mock.cpp | 9 +- test/mocks/tester_mock.h | 3 +- test/test.sh | 2 +- test/test_all.cpp | 5 + test/test_boards.cpp | 9 +- test/test_com.cpp | 35 +++- test/test_fsm.cpp | 150 +++++++++++++++++ test/test_knitter.cpp | 308 +++++++++++---------------------- test/test_tester.cpp | 98 ++++------- 29 files changed, 996 insertions(+), 648 deletions(-) create mode 100644 src/ayab/fsm.cpp create mode 100644 src/ayab/fsm.h create mode 100644 src/ayab/global_fsm.cpp create mode 100644 test/mocks/fsm_mock.cpp create mode 100644 test/mocks/fsm_mock.h create mode 100644 test/test_fsm.cpp diff --git a/src/ayab/com.cpp b/src/ayab/com.cpp index bb3d61b56..440577dae 100644 --- a/src/ayab/com.cpp +++ b/src/ayab/com.cpp @@ -64,6 +64,64 @@ void Com::init() { #endif // AYAB_TESTS } +void Com::update() { + m_packetSerial.update(); +} + +void Com::send(uint8_t *payload, size_t length) { + // TODO(TP): insert a workaround for hardware test code + /* + #ifdef AYAB_HW_TEST + Serial.print("Sent: "); + for (uint8_t i = 0; i < length; ++i) { + Serial.print(payload[i]); + } + Serial.print(", Encoded as: "); + #endif + */ + m_packetSerial.send(payload, length); +} + +// send initial msgid followed by null-terminated string +void Com::sendMsg(AYAB_API_t id, const char *msg) { + uint8_t length = 0; + msgBuffer[length++] = static_cast(id); + while (*msg) { + msgBuffer[length++] = static_cast(*msg++); + } + m_packetSerial.send(msgBuffer, length); +} + +void Com::sendMsg(AYAB_API_t id, char *msg) { + sendMsg(id, static_cast(msg)); +} + +void Com::send_reqLine(const uint8_t lineNumber) { + uint8_t payload[REQLINE_LEN] = { + reqLine_msgid, + lineNumber, + }; + send(static_cast(payload), REQLINE_LEN); +} + +void Com::send_indState(Carriage_t carriage, uint8_t position, + const bool initState) { + uint16_t leftHallValue = GlobalEncoders::getHallValue(Left); + uint16_t rightHallValue = GlobalEncoders::getHallValue(Right); + uint8_t payload[INDSTATE_LEN] = { + indState_msgid, + static_cast(initState), + highByte(leftHallValue), + lowByte(leftHallValue), + highByte(rightHallValue), + lowByte(rightHallValue), + static_cast(carriage), + static_cast(position), + static_cast(GlobalEncoders::getDirection()), + }; + GlobalCom::send(static_cast(payload), INDSTATE_LEN); +} + /*! * \brief Callback for PacketSerial. */ @@ -135,38 +193,6 @@ void Com::onPacketReceived(const uint8_t *buffer, size_t size) { } } -void Com::update() { - m_packetSerial.update(); -} - -void Com::send(uint8_t *payload, size_t length) { - // TODO(TP): insert a workaround for hardware test code - /* - #ifdef AYAB_HW_TEST - Serial.print("Sent: "); - for (uint8_t i = 0; i < length; ++i) { - Serial.print(payload[i]); - } - Serial.print(", Encoded as: "); - #endif - */ - m_packetSerial.send(payload, length); -} - -// send initial msgid followed by null-terminated string -void Com::sendMsg(AYAB_API_t id, const char *msg) { - uint8_t length = 0; - msgBuffer[length++] = static_cast(id); - while (*msg) { - msgBuffer[length++] = static_cast(*msg++); - } - m_packetSerial.send(msgBuffer, length); -} - -void Com::sendMsg(AYAB_API_t id, char *msg) { - sendMsg(id, static_cast(msg)); -} - // Serial command handling /*! @@ -283,7 +309,7 @@ void Com::h_reqTest(const uint8_t *buffer, size_t size) { } Machine_t machineType = static_cast(buffer[0]); - bool success = GlobalKnitter::startTest(machineType); + bool success = GlobalTester::startTest(machineType); uint8_t payload[2]; payload[0] = cnfTest_msgid; @@ -296,3 +322,4 @@ void Com::h_unrecognized() { // do nothing } // GCOVR_EXCL_STOP +// diff --git a/src/ayab/com.h b/src/ayab/com.h index b99824dd5..cbe779a12 100644 --- a/src/ayab/com.h +++ b/src/ayab/com.h @@ -27,6 +27,8 @@ #include #include +#include "encoders.h" + constexpr uint8_t FW_VERSION_MAJ = 1U; constexpr uint8_t FW_VERSION_MIN = 0U; constexpr uint8_t FW_VERSION_PATCH = 0U; @@ -64,6 +66,10 @@ enum AYAB_API { }; using AYAB_API_t = enum AYAB_API; +// API constants +constexpr uint8_t INDSTATE_LEN = 9U; +constexpr uint8_t REQLINE_LEN = 2U; + class ComInterface { public: virtual ~ComInterface(){}; @@ -74,6 +80,9 @@ class ComInterface { virtual void send(uint8_t *payload, size_t length) = 0; virtual void sendMsg(AYAB_API_t id, const char *msg) = 0; virtual void sendMsg(AYAB_API_t id, char *msg) = 0; + virtual void send_reqLine(const uint8_t lineNumber) = 0; + virtual void send_indState(Carriage_t carriage, uint8_t position, + const bool initState = false) = 0; virtual void onPacketReceived(const uint8_t *buffer, size_t size) = 0; }; @@ -96,6 +105,9 @@ class GlobalCom final { static void send(uint8_t *payload, size_t length); static void sendMsg(AYAB_API_t id, const char *msg); static void sendMsg(AYAB_API_t id, char *msg); + static void send_reqLine(const uint8_t lineNumber); + static void send_indState(Carriage_t carriage, uint8_t position, + const bool initState = false); static void onPacketReceived(const uint8_t *buffer, size_t size); private: @@ -109,6 +121,9 @@ class Com : public ComInterface { void send(uint8_t *payload, size_t length); void sendMsg(AYAB_API_t id, const char *msg); void sendMsg(AYAB_API_t id, char *msg); + void send_reqLine(const uint8_t lineNumber); + void send_indState(Carriage_t carriage, uint8_t position, + const bool initState = false); void onPacketReceived(const uint8_t *buffer, size_t size); private: diff --git a/src/ayab/fsm.cpp b/src/ayab/fsm.cpp new file mode 100644 index 000000000..8ddf45ae8 --- /dev/null +++ b/src/ayab/fsm.cpp @@ -0,0 +1,99 @@ +/*! + * \file fsm.cpp + * \brief Singleton class containing methods for the finite state machine + * that co-ordinates the AYAB firmware. + * + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013-2015 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#include "board.h" +#include + +#include "com.h" +#include "fsm.h" +#include "knitter.h" + +// Public methods + +void Fsm::init() { + m_opState = s_init; +} + +OpState_t Fsm::getState() { + return m_opState; +} + +void Fsm::setState(OpState_t state) { + m_opState = state; +} + +/*! + * \brief Dispatch on machine state + * + * \todo TP: add error state(s) + */ +void Fsm::dispatch() { + switch (m_opState) { + case s_init: + state_init(); + break; + + case s_ready: + state_ready(); + break; + + case s_knit: + state_knit(); + break; + + case s_test: + state_test(); + break; + + default: + break; + } + GlobalCom::update(); +} + +// Private methods + +void Fsm::state_init() { + if (GlobalKnitter::isReady()) { + setState(s_ready); + } +} + +void Fsm::state_ready() { + digitalWrite(LED_PIN_A, 0); // green LED off +} + +void Fsm::state_knit() { + digitalWrite(LED_PIN_A, 1); // green LED on + GlobalKnitter::knit(); +} + +void Fsm::state_test() { + GlobalKnitter::encodePosition(); + GlobalTester::loop(); + if (GlobalTester::getQuitFlag()) { + setState(s_ready); + } +} diff --git a/src/ayab/fsm.h b/src/ayab/fsm.h new file mode 100644 index 000000000..d3e47f99d --- /dev/null +++ b/src/ayab/fsm.h @@ -0,0 +1,82 @@ +/*!` + * \file fsm.h + * + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#ifndef FSM_H_ +#define FSM_H_ + +enum OpState { s_init, s_ready, s_knit, s_test }; +using OpState_t = enum OpState; + +class FsmInterface { +public: + virtual ~FsmInterface(){}; + + // any methods that need to be mocked should go here + virtual void init() = 0; + virtual OpState_t getState() = 0; + virtual void setState(OpState_t state) = 0; + virtual void dispatch() = 0; +}; + +// Singleton container class for static methods. +// Dependency injection is enabled using a pointer +// to a global instance of either `Knitter` or `KnitterMock` +// both of which classes implement the pure virtual methods +// of the `KnitterInterface` class. + +class GlobalFsm final { +private: + // singleton class so private constructor is appropriate + GlobalFsm() = default; + +public: + // pointer to global instance whose methods are implemented + static FsmInterface *m_instance; + + static void init(); + static OpState_t getState(); + static void setState(OpState_t state); + static void dispatch(); +}; + +class Fsm : public FsmInterface { +#if AYAB_TESTS +#endif + +public: + void init(); + OpState_t getState(); + void setState(OpState_t state); + void dispatch(); + +private: + void state_init(); + void state_ready(); + void state_knit(); + void state_test(); + + // machine state + OpState_t m_opState; +}; + +#endif // FSM_H_ diff --git a/src/ayab/global_com.cpp b/src/ayab/global_com.cpp index 345e76826..997b9a69d 100644 --- a/src/ayab/global_com.cpp +++ b/src/ayab/global_com.cpp @@ -53,3 +53,12 @@ void GlobalCom::onPacketReceived(const uint8_t *buffer, size_t size) { m_instance->onPacketReceived(buffer, size); } // GCOVR_EXCL_STOP + +void GlobalCom::send_reqLine(const uint8_t lineNumber) { + m_instance->send_reqLine(lineNumber); +} + +void GlobalCom::send_indState(Carriage_t carriage, uint8_t position, + const bool initState) { + m_instance->send_indState(carriage, position, initState); +} diff --git a/src/ayab/global_fsm.cpp b/src/ayab/global_fsm.cpp new file mode 100644 index 000000000..5b5543039 --- /dev/null +++ b/src/ayab/global_fsm.cpp @@ -0,0 +1,41 @@ +/*! + * \file global_fsm.cpp + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#include "fsm.h" + +// static member functions + +void GlobalFsm::init() { + m_instance->init(); +} + +OpState_t GlobalFsm::getState() { + return m_instance->getState(); +} + +void GlobalFsm::setState(OpState_t state) { + m_instance->setState(state); +} + +void GlobalFsm::dispatch() { + m_instance->dispatch(); +} diff --git a/src/ayab/global_knitter.cpp b/src/ayab/global_knitter.cpp index da689caa5..64c3c4944 100644 --- a/src/ayab/global_knitter.cpp +++ b/src/ayab/global_knitter.cpp @@ -31,10 +31,6 @@ void GlobalKnitter::init() { m_instance->init(); } -void GlobalKnitter::fsm() { - m_instance->fsm(); -} - void GlobalKnitter::setUpInterrupt() { m_instance->setUpInterrupt(); } @@ -52,8 +48,16 @@ bool GlobalKnitter::startKnitting(Machine_t machineType, uint8_t startNeedle, pattern_start, continuousReportingEnabled); } -bool GlobalKnitter::startTest(Machine_t machineType) { - return m_instance->startTest(machineType); +void GlobalKnitter::encodePosition() { + m_instance->encodePosition(); +} + +bool GlobalKnitter::isReady() { + return m_instance->isReady(); +} + +void GlobalKnitter::knit() { + m_instance->knit(); } uint8_t GlobalKnitter::getStartOffset(const Direction_t direction) { @@ -75,7 +79,3 @@ void GlobalKnitter::setLastLine() { void GlobalKnitter::setMachineType(Machine_t machineType) { m_instance->setMachineType(machineType); } - -void GlobalKnitter::setState(OpState_t state) { - m_instance->setState(state); -} diff --git a/src/ayab/global_tester.cpp b/src/ayab/global_tester.cpp index 732b0f5a5..3baa5b4f8 100644 --- a/src/ayab/global_tester.cpp +++ b/src/ayab/global_tester.cpp @@ -26,8 +26,8 @@ // static member functions -void GlobalTester::setUp() { - m_instance->setUp(); +bool GlobalTester::startTest(Machine_t machineType) { + return m_instance->startTest(machineType); } void GlobalTester::loop() { @@ -82,10 +82,6 @@ void GlobalTester::quitCmd() { m_instance->quitCmd(); } -void GlobalTester::unrecognizedCmd(const char *buffer) { - m_instance->unrecognizedCmd(buffer); -} - #ifndef AYAB_TESTS void GlobalTester::encoderAChange() { m_instance->encoderAChange(); diff --git a/src/ayab/knitter.cpp b/src/ayab/knitter.cpp index 157421905..4539bc1ef 100644 --- a/src/ayab/knitter.cpp +++ b/src/ayab/knitter.cpp @@ -23,12 +23,13 @@ * http://ayab-knitting.com */ +#include "board.h" #include #include "beeper.h" -#include "board.h" #include "com.h" #include "encoders.h" +#include "fsm.h" #include "knitter.h" #include "tester.h" @@ -40,9 +41,9 @@ constexpr uint16_t UINT16_MAX = 0xFFFFU; #endif /*! - * \brief Knitter constructor. + * \brief Initialize Knitter object. * - * Initializes the solenoids as well as pins and interrupts. + * Initialize the solenoids as well as pins and interrupts. */ void Knitter::init() { pinMode(ENC_PIN_A, INPUT); @@ -57,16 +58,12 @@ void Knitter::init() { pinMode(DBG_BTN_PIN, INPUT); #endif - setUpInterrupt(); - + // FIXME(TP): should this go in `main()`? GlobalSolenoids::init(); - // explicitly initialize members - - // machine state - m_opState = s_init; + setUpInterrupt(); - // job parameters + // explicitly initialize members m_machineType = NoMachine; m_startNeedle = 0U; m_stopNeedle = 0U; @@ -90,6 +87,13 @@ void Knitter::setUpInterrupt() { // (re-)attach ENC_PIN_A(=2), interrupt #0 detachInterrupt(0); #ifndef AYAB_TESTS + // Attaching ENC_PIN_A, Interrupt #0 + // This interrupt cannot be enabled until + // the machine type has been validated. + /* + // `digitalPinToInterrupt` macro not backported until Arduino IDE v.1.0.6 + attachInterrupt(digitalPinToInterrupt(ENC_PIN_A), isr_wrapper, CHANGE); + */ attachInterrupt(0, GlobalKnitter::isr, CHANGE); #endif // AYAB_TESTS } @@ -112,44 +116,12 @@ void Knitter::isr() { } /*! - * \brief Dispatch on machine state - * - * \todo TP: add error state(s) - */ -void Knitter::fsm() { - switch (m_opState) { - case s_init: - state_init(); - break; - - case s_ready: - state_ready(); - break; - - case s_knit: - state_knit(); - break; - - case s_test: - state_test(); - break; - - default: - break; - } - GlobalCom::update(); -} - -/*! - * \brief Enter operate state. - * - * \todo sl: check that functionality is correct after removing always true - * comparison. + * \brief Enter knit state. */ bool Knitter::startKnitting(Machine_t machineType, uint8_t startNeedle, uint8_t stopNeedle, uint8_t *pattern_start, bool continuousReportingEnabled) { - if ((getState() != s_ready) || (machineType == NoMachine) || + if ((GlobalFsm::getState() != s_ready) || (machineType == NoMachine) || (machineType >= NUM_MACHINES) || (pattern_start == nullptr) || (startNeedle >= stopNeedle) || (stopNeedle >= NUM_NEEDLES[machineType])) { // TODO(TP): error code @@ -173,79 +145,26 @@ bool Knitter::startKnitting(Machine_t machineType, uint8_t startNeedle, GlobalEncoders::init(machineType); // proceed to next state - setState(s_knit); + GlobalFsm::setState(s_knit); GlobalBeeper::ready(); - // Attaching ENC_PIN_A, Interrupt #0 - // This interrupt cannot be enabled until - // the machine type has been validated. - /* - // `digitalPinToInterrupt` macro not backported until Arduino IDE v.1.0.6 - attachInterrupt(digitalPinToInterrupt(ENC_PIN_A), isr_wrapper, CHANGE); - */ - setUpInterrupt(); - // success return true; } -bool Knitter::startTest(Machine_t machineType) { - bool success = false; - if (s_init == getState() || s_ready == getState()) { - setState(s_test); - m_machineType = machineType; - GlobalTester::setUp(); - success = true; - } - return success; -} - -uint8_t Knitter::getStartOffset(const Direction_t direction) { - if ((direction == NoDirection) || (direction >= NUM_DIRECTIONS) || - (m_carriage == NoCarriage) || (m_carriage >= NUM_CARRIAGES) || - (m_machineType == NoMachine) || (m_machineType >= NUM_MACHINES)) { - // TODO(TP): return error state - return 0U; - } - return START_OFFSET[m_machineType][direction][m_carriage]; -} - -Machine_t Knitter::getMachineType() { - return m_machineType; -} - -bool Knitter::setNextLine(uint8_t lineNumber) { - bool success = false; - if (m_lineRequested) { - // FIXME: Is there even a need for a new line? - if (lineNumber == m_currentLineNumber) { - m_lineRequested = false; - GlobalBeeper::finishedLine(); - success = true; - } else { - // line numbers didn't match -> request again - reqLine(m_currentLineNumber); - } +// used in hardware test loop +void Knitter::encodePosition() { + if (m_sOldPosition != m_position) { + // only act if there is an actual change of position + // store current encoder position for next call of this function + m_sOldPosition = m_position; + calculatePixelAndSolenoid(); + indState(false); } - return success; -} - -void Knitter::setLastLine() { - // lastLineFlag is evaluated in s_operate - m_lastLineFlag = true; -} - -void Knitter::setMachineType(Machine_t machineType) { - m_machineType = machineType; } -void Knitter::setState(OpState_t state) { - m_opState = state; -} - -// private methods - -void Knitter::state_init() { +// return true -> move from state `s_init` to `s_ready` +bool Knitter::isReady() { #ifdef DBG_NOMACHINE bool state = digitalRead(DBG_BTN_PIN); @@ -255,25 +174,18 @@ void Knitter::state_init() { // machine is initialized when left hall sensor is passed in Right direction if (Right == m_direction && Left == m_hallActive) { #endif // DBG_NOMACHINE - setState(s_ready); GlobalSolenoids::setSolenoids(SOLENOIDS_BITMASK); indState(true); + return true; // move to `s_ready` } #ifdef DBG_NOMACHINE m_prevState = state; #endif + return false; // stay in `s_init` } -void Knitter::state_ready() { - digitalWrite(LED_PIN_A, 0); // green LED off - // This state is left when the startOperation() method - // is called successfully by fsm() -} - -void Knitter::state_knit() { - digitalWrite(LED_PIN_A, 1); // green LED on - +void Knitter::knit() { if (m_firstRun) { m_firstRun = false; // TODO(who?): optimize delay for various Arduino models @@ -293,77 +205,115 @@ void Knitter::state_knit() { } m_prevState = state; #else - if (m_sOldPosition != m_position) { - // only act if there is an actual change of position - // store current Encoder position for next call of this function - m_sOldPosition = m_position; + // only act if there is an actual change of position + if (m_sOldPosition == m_position) { + return; + } - if (m_continuousReportingEnabled) { - // send current position to GUI - indState(true); - } + // store current carriage position for next call of this function + m_sOldPosition = m_position; - if (!calculatePixelAndSolenoid()) { - // no valid/useful position calculated - return; - } + if (m_continuousReportingEnabled) { + // send current position to GUI + indState(true); + } - // m_machineType has been validated - if ((m_pixelToSet >= m_startNeedle - END_OF_LINE_OFFSET_L[m_machineType]) && - (m_pixelToSet <= m_stopNeedle + END_OF_LINE_OFFSET_R[m_machineType])) { + if (!calculatePixelAndSolenoid()) { + // no valid/useful position calculated + return; + } - if ((m_pixelToSet >= m_startNeedle) && (m_pixelToSet <= m_stopNeedle)) { - // when inside the active needles - if (m_machineType == Kh270) { - digitalWrite(LED_PIN_B, 1); // yellow LED on - } - m_workedOnLine = true; - } + // `m_machineType` has been validated - no need to check + if ((m_pixelToSet >= m_startNeedle - END_OF_LINE_OFFSET_L[m_machineType]) && + (m_pixelToSet <= m_stopNeedle + END_OF_LINE_OFFSET_R[m_machineType])) { - // find the right byte from the currentLine array, - // then read the appropriate Pixel(/Bit) for the current needle to set - uint8_t currentByte = m_pixelToSet / 8U; - bool pixelValue = - bitRead(m_lineBuffer[currentByte], m_pixelToSet - (8U * currentByte)); - // write Pixel state to the appropriate needle - GlobalSolenoids::setSolenoid(m_solenoidToSet, pixelValue); - } else { - // outside of the active needles + if ((m_pixelToSet >= m_startNeedle) && (m_pixelToSet <= m_stopNeedle)) { + // when inside the active needles if (m_machineType == Kh270) { - digitalWrite(LED_PIN_B, 0); // yellow LED off + digitalWrite(LED_PIN_B, 1); // yellow LED on } + m_workedOnLine = true; + } + + // find the right byte from the currentLine array, + // then read the appropriate Pixel(/Bit) for the current needle to set + uint8_t currentByte = m_pixelToSet / 8U; + bool pixelValue = + bitRead(m_lineBuffer[currentByte], m_pixelToSet - (8U * currentByte)); + // write Pixel state to the appropriate needle + GlobalSolenoids::setSolenoid(m_solenoidToSet, pixelValue); + } else { + // outside of the active needles + if (m_machineType == Kh270) { + digitalWrite(LED_PIN_B, 0); // yellow LED off + } - // reset solenoids when out of range - GlobalSolenoids::setSolenoid(m_solenoidToSet, true); + // reset solenoids when out of range + GlobalSolenoids::setSolenoid(m_solenoidToSet, true); - if (m_workedOnLine) { - // already worked on the current line -> finished the line - m_workedOnLine = false; + if (m_workedOnLine) { + // already worked on the current line -> finished the line + m_workedOnLine = false; - if (!m_lineRequested && !m_lastLineFlag) { - // request new line from host - reqLine(++m_currentLineNumber); - } else if (m_lastLineFlag) { - stopKnitting(); - } + if (!m_lineRequested && !m_lastLineFlag) { + // request new line from host + reqLine(++m_currentLineNumber); + } else if (m_lastLineFlag) { + stopKnitting(); } } } #endif // DBG_NOMACHINE } -void Knitter::state_test() { - if (m_sOldPosition != m_position) { - // only act if there is an actual change of position - // store current encoder position for next call of this function - m_sOldPosition = m_position; - calculatePixelAndSolenoid(); - indState(); +Machine_t Knitter::getMachineType() { + return m_machineType; +} + +uint8_t Knitter::getStartOffset(const Direction_t direction) { + if ((direction == NoDirection) || (direction >= NUM_DIRECTIONS) || + (m_carriage == NoCarriage) || (m_carriage >= NUM_CARRIAGES) || + (m_machineType == NoMachine) || (m_machineType >= NUM_MACHINES)) { + // TODO(TP): return error state + return 0U; } - GlobalTester::loop(); - if (GlobalTester::getQuitFlag()) { - setState(s_ready); + return START_OFFSET[m_machineType][direction][m_carriage]; +} + +bool Knitter::setNextLine(uint8_t lineNumber) { + bool success = false; + if (m_lineRequested) { + // FIXME: Is there even a need for a new line? + if (lineNumber == m_currentLineNumber) { + m_lineRequested = false; + GlobalBeeper::finishedLine(); + success = true; + } else { + // line numbers didn't match -> request again + reqLine(m_currentLineNumber); + } } + return success; +} + +void Knitter::setLastLine() { + // lastLineFlag is evaluated in s_operate + m_lastLineFlag = true; +} + +void Knitter::setMachineType(Machine_t machineType) { + m_machineType = machineType; +} + +// private methods + +void Knitter::reqLine(uint8_t lineNumber) { + GlobalCom::send_reqLine(lineNumber); + m_lineRequested = true; +} + +void Knitter::indState(const bool initState) { + GlobalCom::send_indState(m_carriage, m_position, initState); } bool Knitter::calculatePixelAndSolenoid() { @@ -427,36 +377,9 @@ bool Knitter::calculatePixelAndSolenoid() { return success; } -void Knitter::reqLine(const uint8_t lineNumber) { - uint8_t payload[REQLINE_LEN] = { - reqLine_msgid, - lineNumber, - }; - GlobalCom::send(static_cast(payload), REQLINE_LEN); - - m_lineRequested = true; -} - -void Knitter::indState(const bool initState) { - uint16_t leftHallValue = GlobalEncoders::getHallValue(Left); - uint16_t rightHallValue = GlobalEncoders::getHallValue(Right); - uint8_t payload[INDSTATE_LEN] = { - indState_msgid, - static_cast(initState), - highByte(leftHallValue), - lowByte(leftHallValue), - highByte(rightHallValue), - lowByte(rightHallValue), - static_cast(m_carriage), - static_cast(m_position), - static_cast(GlobalEncoders::getDirection()), - }; - GlobalCom::send(static_cast(payload), INDSTATE_LEN); -} - void Knitter::stopKnitting() { GlobalBeeper::endWork(); - setState(s_ready); + GlobalFsm::setState(s_ready); GlobalSolenoids::setSolenoids(SOLENOIDS_BITMASK); GlobalBeeper::finishedLine(); @@ -466,9 +389,5 @@ void Knitter::stopKnitting() { // `digitalPinToInterrupt` macro not backported until Arduino !DE v.1.0.6 detachInterrupt(digitalPinToInterrupt(ENC_PIN_A)); */ - detachInterrupt(0); -} - -OpState_t Knitter::getState() const { - return m_opState; + // detachInterrupt(0); } diff --git a/src/ayab/knitter.h b/src/ayab/knitter.h index 8d10c0542..8e4f26d63 100644 --- a/src/ayab/knitter.h +++ b/src/ayab/knitter.h @@ -30,32 +30,25 @@ #include "solenoids.h" #include "tester.h" -// API constants -constexpr uint8_t INDSTATE_LEN = 9U; -constexpr uint8_t REQLINE_LEN = 2U; - -enum OpState { s_init, s_ready, s_knit, s_test }; -using OpState_t = enum OpState; - class KnitterInterface { public: virtual ~KnitterInterface(){}; // any methods that need to be mocked should go here virtual void init() = 0; - virtual void fsm() = 0; virtual void setUpInterrupt() = 0; virtual void isr() = 0; virtual bool startKnitting(Machine_t machineType, uint8_t startNeedle, uint8_t stopNeedle, uint8_t *pattern_start, bool continuousReportingEnabled) = 0; - virtual bool startTest(Machine_t machineType) = 0; + virtual void encodePosition() = 0; + virtual bool isReady() = 0; + virtual void knit() = 0; virtual uint8_t getStartOffset(const Direction_t direction) = 0; virtual Machine_t getMachineType() = 0; virtual bool setNextLine(uint8_t lineNumber) = 0; virtual void setLastLine() = 0; virtual void setMachineType(Machine_t) = 0; - virtual void setState(OpState_t state) = 0; }; // Singleton container class for static methods. @@ -74,7 +67,6 @@ class GlobalKnitter final { static KnitterInterface *m_instance; static void init(); - static void fsm(); static void setUpInterrupt(); #ifndef AYAB_TESTS static void isr(); @@ -82,21 +74,22 @@ class GlobalKnitter final { static bool startKnitting(Machine_t machineType, uint8_t startNeedle, uint8_t stopNeedle, uint8_t *pattern_start, bool continuousReportingEnabled); - static bool startTest(Machine_t machineType); + static void encodePosition(); + static bool isReady(); + static void knit(); static uint8_t getStartOffset(const Direction_t direction); static Machine_t getMachineType(); static bool setNextLine(uint8_t lineNumber); static void setLastLine(); static void setMachineType(Machine_t); - static void setState(OpState_t state); }; class Knitter : public KnitterInterface { #if AYAB_TESTS FRIEND_TEST(KnitterTest, test_init); FRIEND_TEST(KnitterTest, test_getStartOffset); - FRIEND_TEST(KnitterTest, test_operate_lastline); - FRIEND_TEST(KnitterTest, test_operate_lastline_and_no_req); + FRIEND_TEST(KnitterTest, test_knit_lastline); + FRIEND_TEST(KnitterTest, test_knit_lastline_and_no_req); FRIEND_TEST(KnitterTest, test_fsm_default_case); FRIEND_TEST(KnitterTest, test_fsm_init_LL); FRIEND_TEST(KnitterTest, test_fsm_init_RR); @@ -110,39 +103,31 @@ class Knitter : public KnitterInterface { FRIEND_TEST(KnitterTest, test_startTest_in_ready); FRIEND_TEST(KnitterTest, test_startTest_in_knit); FRIEND_TEST(KnitterTest, test_setNextLine); + FRIEND_TEST(FsmTest, test_dispatch_init); #endif friend class Tester; public: void init(); - void fsm(); void setUpInterrupt(); void isr(); bool startKnitting(Machine_t machineType, uint8_t startNeedle, uint8_t stopNeedle, uint8_t *pattern_start, bool continuousReportingEnabled); - bool startTest(Machine_t machineType); + void encodePosition(); + bool isReady(); + void knit(); uint8_t getStartOffset(const Direction_t direction); Machine_t getMachineType(); bool setNextLine(uint8_t lineNumber); void setLastLine(); void setMachineType(Machine_t); - void setState(OpState_t state); private: - void state_init(); - static void state_ready(); - void state_knit(); - void state_test(); - - bool calculatePixelAndSolenoid(); void reqLine(uint8_t lineNumber); - void indState(bool initState = false); + void indState(const bool initState = true); + bool calculatePixelAndSolenoid(); void stopKnitting(); - OpState_t getState() const; - - // machine state - OpState_t m_opState; // job parameters Machine_t m_machineType; diff --git a/src/ayab/main.cpp b/src/ayab/main.cpp index 32ed32954..b7745589e 100644 --- a/src/ayab/main.cpp +++ b/src/ayab/main.cpp @@ -27,6 +27,7 @@ #include "beeper.h" #include "com.h" #include "encoders.h" +#include "fsm.h" #include "knitter.h" #include "solenoids.h" #include "tester.h" @@ -36,6 +37,7 @@ GlobalBeeper *beeper; GlobalCom *com; GlobalEncoders *encoders; +GlobalFsm *fsm; GlobalKnitter *knitter; GlobalSolenoids *solenoids; GlobalTester *tester; @@ -44,6 +46,7 @@ GlobalTester *tester; BeeperInterface *GlobalBeeper::m_instance = new Beeper(); ComInterface *GlobalCom::m_instance = new Com(); EncodersInterface *GlobalEncoders::m_instance = new Encoders(); +FsmInterface *GlobalFsm::m_instance = new Fsm(); KnitterInterface *GlobalKnitter::m_instance = new Knitter(); SolenoidsInterface *GlobalSolenoids::m_instance = new Solenoids(); TesterInterface *GlobalTester::m_instance = new Tester(); @@ -53,6 +56,7 @@ TesterInterface *GlobalTester::m_instance = new Tester(); */ void setup() { GlobalCom::init(); + GlobalFsm::init(); GlobalKnitter::init(); GlobalSolenoids::init(); } @@ -61,5 +65,5 @@ void setup() { * Main Loop - repeat forever. */ void loop() { - GlobalKnitter::fsm(); + GlobalFsm::dispatch(); } diff --git a/src/ayab/tester.cpp b/src/ayab/tester.cpp index 607cb523d..29f334188 100644 --- a/src/ayab/tester.cpp +++ b/src/ayab/tester.cpp @@ -26,6 +26,7 @@ #include "beeper.h" #include "com.h" +#include "fsm.h" #include "knitter.h" #include "tester.h" @@ -154,20 +155,44 @@ void Tester::quitCmd() { GlobalKnitter::setUpInterrupt(); } +bool Tester::startTest(Machine_t machineType) { + bool success = false; + OpState_t currentState = GlobalFsm::getState(); + if (s_init == currentState || s_ready == currentState) { + GlobalFsm::setState(s_test); + GlobalKnitter::setMachineType(machineType); + setUp(); + success = true; + } + return success; +} + /*! - * \brief Unrecognized command handler. - * - * \param buffer: pointer to string containing unrecognized command - * - * This gets set as the default handler, and gets called when no other command - * matches. + * \brief Main loop for hardware tests. */ -void Tester::unrecognizedCmd(const char *buffer) { - GlobalCom::sendMsg(testRes_msgid, "Unrecognized command\n"); - (void)(buffer); // does nothing but prevents 'unused variable' compile error - helpCmd(); +void Tester::loop() { + unsigned long now = millis(); + if (now - m_lastTime >= 500) { + m_lastTime = now; + handleTimerEvent(); + } } +#ifndef AYAB_TESTS +/*! + * \brief Interrupt service routine for encoder A. + */ +void Tester::encoderAChange() { + beep(); +} +#endif // AYAB_TESTS + +bool Tester::getQuitFlag() { + return m_quit; +} + +// Private member functions + /*! * \brief Setup for hardware tests. */ @@ -185,6 +210,13 @@ void Tester::setUp() { // attach interrupt for ENC_PIN_A(=2), interrupt #0 detachInterrupt(0); #ifndef AYAB_TESTS + // Attaching ENC_PIN_A, Interrupt #0 + // This interrupt cannot be enabled until + // the machine type has been validated. + /* + // `digitalPinToInterrupt` macro not backported until Arduino IDE v.1.0.6 + attachInterrupt(digitalPinToInterrupt(ENC_PIN_A), isr_wrapper, CHANGE); + */ attachInterrupt(0, GlobalTester::encoderAChange, RISING); #endif // AYAB_TESTS @@ -195,32 +227,6 @@ void Tester::setUp() { m_timerEventOdd = false; } -/*! - * \brief Main loop for hardware tests. - */ -void Tester::loop() { - unsigned long now = millis(); - if (now - m_lastTime >= 500) { - m_lastTime = now; - handleTimerEvent(); - } -} - -#ifndef AYAB_TESTS -/*! - * \brief Interrupt service routine for encoder A. - */ -void Tester::encoderAChange() { - beep(); -} -#endif // AYAB_TESTS - -bool Tester::getQuitFlag() { - return m_quit; -} - -// Private member functions - void Tester::beep() { GlobalBeeper::ready(); } diff --git a/src/ayab/tester.h b/src/ayab/tester.h index 091e5742f..4a9990f72 100644 --- a/src/ayab/tester.h +++ b/src/ayab/tester.h @@ -27,6 +27,7 @@ //#include #include "beeper.h" +#include "encoders.h" constexpr uint8_t BUFFER_LEN = 40; @@ -35,7 +36,7 @@ class TesterInterface { virtual ~TesterInterface(){}; // any methods that need to be mocked should go here - virtual void setUp() = 0; + virtual bool startTest(Machine_t machineType) = 0; virtual void loop() = 0; virtual bool getQuitFlag() = 0; virtual void helpCmd() = 0; @@ -49,7 +50,6 @@ class TesterInterface { virtual void autoTestCmd() = 0; virtual void stopCmd() = 0; virtual void quitCmd() = 0; - virtual void unrecognizedCmd(const char *buffer) = 0; #ifndef AYAB_TESTS virtual void encoderAChange(); #endif @@ -70,7 +70,7 @@ class GlobalTester final { // pointer to global instance whose methods are implemented static TesterInterface *m_instance; - static void setUp(); + static bool startTest(Machine_t machineType); static void loop(); static bool getQuitFlag(); static void helpCmd(); @@ -84,7 +84,6 @@ class GlobalTester final { static void autoTestCmd(); static void stopCmd(); static void quitCmd(); - static void unrecognizedCmd(const char *buffer); #ifndef AYAB_TESTS static void encoderAChange(); #endif @@ -103,7 +102,7 @@ class Tester : public TesterInterface { #endif public: - void setUp(); + bool startTest(Machine_t machineType); void loop(); bool getQuitFlag(); void helpCmd(); @@ -117,12 +116,12 @@ class Tester : public TesterInterface { void autoTestCmd(); void stopCmd(); void quitCmd(); - void unrecognizedCmd(const char *buffer); #ifndef AYAB_TESTS void encoderAChange(); #endif private: + void setUp(); void beep(); void readEOLsensors(); void readEncoders(); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2f4a3b4ba..7a06108b2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -61,6 +61,9 @@ set(COMMON_SOURCES ${SOURCE_DIRECTORY}/global_knitter.cpp ${PROJECT_SOURCE_DIR}/mocks/knitter_mock.cpp + + ${SOURCE_DIRECTORY}/global_fsm.cpp + ${PROJECT_SOURCE_DIR}/mocks/fsm_mock.cpp ) set(COMMON_DEFINES ARDUINO=105 @@ -136,19 +139,30 @@ add_board(Mega) # Knitter (device independent) add_executable(${PROJECT_NAME}_knitter ${PROJECT_SOURCE_DIR}/test_all.cpp - ${SOURCE_DIRECTORY}/knitter.cpp + ${SOURCE_DIRECTORY}/global_beeper.cpp - ${SOURCE_DIRECTORY}/global_com.cpp - ${SOURCE_DIRECTORY}/global_encoders.cpp - ${SOURCE_DIRECTORY}/global_knitter.cpp - ${SOURCE_DIRECTORY}/global_solenoids.cpp - ${SOURCE_DIRECTORY}/global_tester.cpp ${PROJECT_SOURCE_DIR}/mocks/beeper_mock.cpp + + ${SOURCE_DIRECTORY}/global_com.cpp ${PROJECT_SOURCE_DIR}/mocks/com_mock.cpp + + ${SOURCE_DIRECTORY}/global_encoders.cpp ${PROJECT_SOURCE_DIR}/mocks/encoders_mock.cpp + + ${SOURCE_DIRECTORY}/global_solenoids.cpp ${PROJECT_SOURCE_DIR}/mocks/solenoids_mock.cpp + + ${SOURCE_DIRECTORY}/global_tester.cpp ${PROJECT_SOURCE_DIR}/mocks/tester_mock.cpp + + ${SOURCE_DIRECTORY}/fsm.cpp + ${SOURCE_DIRECTORY}/global_fsm.cpp + ${PROJECT_SOURCE_DIR}/test_fsm.cpp + + ${SOURCE_DIRECTORY}/knitter.cpp + ${SOURCE_DIRECTORY}/global_knitter.cpp ${PROJECT_SOURCE_DIR}/test_knitter.cpp + ${SOFT_I2C_LIB} ) target_include_directories(${PROJECT_NAME}_knitter diff --git a/test/mocks/com_mock.cpp b/test/mocks/com_mock.cpp index 0ce36812c..614e81478 100644 --- a/test/mocks/com_mock.cpp +++ b/test/mocks/com_mock.cpp @@ -25,6 +25,7 @@ #include static ComMock *gComMock = NULL; + ComMock *comMockInstance() { if (!gComMock) { gComMock = new ComMock(); @@ -64,6 +65,17 @@ void Com::sendMsg(AYAB_API_t id, char *msg) { gComMock->sendMsg(id, msg); } +void Com::send_reqLine(const uint8_t lineNumber) { + assert(gComMock != nullptr); + gComMock->send_reqLine(lineNumber); +} + +void Com::send_indState(Carriage_t carriage, uint8_t position, + const bool initState) { + assert(gComMock != nullptr); + gComMock->send_indState(carriage, position, initState); +} + void Com::onPacketReceived(const uint8_t *buffer, size_t size) { assert(gComMock != nullptr); gComMock->onPacketReceived(buffer, size); diff --git a/test/mocks/com_mock.h b/test/mocks/com_mock.h index 57aae277c..1d0405aab 100644 --- a/test/mocks/com_mock.h +++ b/test/mocks/com_mock.h @@ -35,6 +35,9 @@ class ComMock : public ComInterface { MOCK_METHOD2(send, void(uint8_t *payload, size_t length)); MOCK_METHOD2(sendMsg, void(AYAB_API_t id, const char *msg)); MOCK_METHOD2(sendMsg, void(AYAB_API_t id, char *msg)); + MOCK_METHOD1(send_reqLine, void(const uint8_t lineNumber)); + MOCK_METHOD3(send_indState, void(Carriage_t carriage, uint8_t position, + const bool initState)); MOCK_METHOD2(onPacketReceived, void(const uint8_t *buffer, size_t size)); }; diff --git a/test/mocks/fsm_mock.cpp b/test/mocks/fsm_mock.cpp new file mode 100644 index 000000000..c1eaa19c9 --- /dev/null +++ b/test/mocks/fsm_mock.cpp @@ -0,0 +1,61 @@ +/*!` + * \file fsm_mock.cpp + * + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#include +#include + +static FsmMock *gFsmMock = NULL; + +FsmMock *fsmMockInstance() { + if (!gFsmMock) { + gFsmMock = new FsmMock(); + } + return gFsmMock; +} + +void releaseFsmMock() { + if (gFsmMock) { + delete gFsmMock; + gFsmMock = NULL; + } +} + +void Fsm::init() { + assert(gFsmMock != NULL); + gFsmMock->init(); +} + +OpState_t Fsm::getState() { + assert(gFsmMock != NULL); + return gFsmMock->getState(); +} + +void Fsm::setState(OpState_t state) { + assert(gFsmMock != NULL); + gFsmMock->setState(state); +} + +void Fsm::dispatch() { + assert(gFsmMock != NULL); + gFsmMock->dispatch(); +} diff --git a/test/mocks/fsm_mock.h b/test/mocks/fsm_mock.h new file mode 100644 index 000000000..01492c013 --- /dev/null +++ b/test/mocks/fsm_mock.h @@ -0,0 +1,41 @@ +/*!` + * \file fsm_mock.h + * + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#ifndef FSM_MOCK_H_ +#define FSM_MOCK_H_ + +#include +#include + +class FsmMock : public FsmInterface { +public: + MOCK_METHOD0(init, void()); + MOCK_METHOD0(getState, OpState_t()); + MOCK_METHOD1(setState, void(OpState_t state)); + MOCK_METHOD0(dispatch, void()); +}; + +FsmMock *fsmMockInstance(); +void releaseFsmMock(); + +#endif // FSM_MOCK_H_ diff --git a/test/mocks/knitter_mock.cpp b/test/mocks/knitter_mock.cpp index 2bfe5d690..eb05351bc 100644 --- a/test/mocks/knitter_mock.cpp +++ b/test/mocks/knitter_mock.cpp @@ -44,9 +44,9 @@ void Knitter::init() { gKnitterMock->init(); } -void Knitter::fsm() { +void Knitter::setUpInterrupt() { assert(gKnitterMock != NULL); - gKnitterMock->fsm(); + gKnitterMock->setUpInterrupt(); } void Knitter::isr() { @@ -54,11 +54,6 @@ void Knitter::isr() { gKnitterMock->isr(); } -void Knitter::setUpInterrupt() { - assert(gKnitterMock != NULL); - gKnitterMock->setUpInterrupt(); -} - bool Knitter::startKnitting(Machine_t machineType, uint8_t startNeedle, uint8_t stopNeedle, uint8_t *pattern_start, bool continuousReportingEnabled) { @@ -67,19 +62,24 @@ bool Knitter::startKnitting(Machine_t machineType, uint8_t startNeedle, pattern_start, continuousReportingEnabled); } -bool Knitter::startTest(Machine_t machineType) { +void Knitter::encodePosition() { assert(gKnitterMock != NULL); - return gKnitterMock->startTest(machineType); + gKnitterMock->encodePosition(); } -bool Knitter::setNextLine(uint8_t lineNumber) { +bool Knitter::isReady() { assert(gKnitterMock != NULL); - return gKnitterMock->setNextLine(lineNumber); + return gKnitterMock->isReady(); } -void Knitter::setLastLine() { +void Knitter::knit() { assert(gKnitterMock != NULL); - gKnitterMock->setLastLine(); + gKnitterMock->knit(); +} + +uint8_t Knitter::getStartOffset(const Direction_t direction) { + assert(gKnitterMock != NULL); + return gKnitterMock->getStartOffset(direction); } Machine_t Knitter::getMachineType() { @@ -87,17 +87,17 @@ Machine_t Knitter::getMachineType() { return gKnitterMock->getMachineType(); } -void Knitter::setMachineType(Machine_t machineType) { +bool Knitter::setNextLine(uint8_t lineNumber) { assert(gKnitterMock != NULL); - return gKnitterMock->setMachineType(machineType); + return gKnitterMock->setNextLine(lineNumber); } -uint8_t Knitter::getStartOffset(const Direction_t direction) { +void Knitter::setLastLine() { assert(gKnitterMock != NULL); - return gKnitterMock->getStartOffset(direction); + gKnitterMock->setLastLine(); } -void Knitter::setState(OpState_t state) { +void Knitter::setMachineType(Machine_t machineType) { assert(gKnitterMock != NULL); - gKnitterMock->setState(state); + return gKnitterMock->setMachineType(machineType); } diff --git a/test/mocks/knitter_mock.h b/test/mocks/knitter_mock.h index 97ea0d316..115b7524e 100644 --- a/test/mocks/knitter_mock.h +++ b/test/mocks/knitter_mock.h @@ -30,19 +30,19 @@ class KnitterMock : public KnitterInterface { public: MOCK_METHOD0(init, void()); - MOCK_METHOD0(fsm, void()); - MOCK_METHOD0(isr, void()); MOCK_METHOD0(setUpInterrupt, void()); + MOCK_METHOD0(isr, void()); MOCK_METHOD5(startKnitting, bool(Machine_t machineType, uint8_t startNeedle, uint8_t stopNeedle, uint8_t *pattern_start, bool continuousReportingEnabled)); - MOCK_METHOD1(startTest, bool(Machine_t)); + MOCK_METHOD0(encodePosition, void()); + MOCK_METHOD0(isReady, bool()); + MOCK_METHOD0(knit, void()); + MOCK_METHOD1(getStartOffset, uint8_t(const Direction_t direction)); + MOCK_METHOD0(getMachineType, Machine_t()); MOCK_METHOD1(setNextLine, bool(uint8_t lineNumber)); MOCK_METHOD0(setLastLine, void()); - MOCK_METHOD0(getMachineType, Machine_t()); MOCK_METHOD1(setMachineType, void(Machine_t)); - MOCK_METHOD1(getStartOffset, uint8_t(const Direction_t direction)); - MOCK_METHOD1(setState, void(OpState_t)); }; KnitterMock *knitterMockInstance(); diff --git a/test/mocks/tester_mock.cpp b/test/mocks/tester_mock.cpp index e96c0ca73..448329f84 100644 --- a/test/mocks/tester_mock.cpp +++ b/test/mocks/tester_mock.cpp @@ -39,9 +39,9 @@ void releaseTesterMock() { } } -void Tester::setUp() { +bool Tester::startTest(Machine_t machineType) { assert(gTesterMock != NULL); - gTesterMock->setUp(); + return gTesterMock->startTest(machineType); } void Tester::loop() { @@ -108,8 +108,3 @@ void Tester::quitCmd() { assert(gTesterMock != NULL); gTesterMock->quitCmd(); } - -void Tester::unrecognizedCmd(const char *buffer) { - assert(gTesterMock != NULL); - gTesterMock->unrecognizedCmd(buffer); -} diff --git a/test/mocks/tester_mock.h b/test/mocks/tester_mock.h index b99917ffc..8cc5114cb 100644 --- a/test/mocks/tester_mock.h +++ b/test/mocks/tester_mock.h @@ -29,7 +29,7 @@ class TesterMock : public TesterInterface { public: - MOCK_METHOD0(setUp, void()); + MOCK_METHOD1(startTest, bool(Machine_t machineType)); MOCK_METHOD0(loop, void()); MOCK_METHOD0(getQuitFlag, bool()); MOCK_METHOD0(helpCmd, void()); @@ -43,7 +43,6 @@ class TesterMock : public TesterInterface { MOCK_METHOD0(autoTestCmd, void()); MOCK_METHOD0(stopCmd, void()); MOCK_METHOD0(quitCmd, void()); - MOCK_METHOD1(unrecognizedCmd, void(const char *)); }; TesterMock *testerMockInstance(); diff --git a/test/test.sh b/test/test.sh index 871ecac68..a942f5b7b 100755 --- a/test/test.sh +++ b/test/test.sh @@ -45,7 +45,7 @@ cd ../.. GCOVR_ARGS="--exclude-unreachable-branches --exclude-throw-branches \ --exclude-directories 'test/build/arduino_mock$' \ -e test_* -e libraries* -e src/ayab/global_knitter.cpp \ - -e src/ayab/global_tester.cpp" + -e src/ayab/global_fsm.cpp" gcovr -k -r . $GCOVR_ARGS --html-details -o ./test/build/coverage.html # gcovr -r . $GCOVR_ARGS --json-pretty -o ./test/build/coverage-report.json diff --git a/test/test_all.cpp b/test/test_all.cpp index e0a149936..3c2aa55b4 100644 --- a/test/test_all.cpp +++ b/test/test_all.cpp @@ -23,6 +23,7 @@ #include "gtest/gtest.h" +#include #include #include @@ -33,7 +34,9 @@ // global definitions // references everywhere else must use `extern` +Fsm *fsm = new Fsm(); Knitter *knitter = new Knitter(); + BeeperMock *beeper = new BeeperMock(); ComMock *com = new ComMock(); EncodersMock *encoders = new EncodersMock(); @@ -41,7 +44,9 @@ SolenoidsMock *solenoids = new SolenoidsMock(); TesterMock *tester = new TesterMock(); // instantiate singleton classes with mock objects +FsmInterface *GlobalFsm::m_instance = fsm; KnitterInterface *GlobalKnitter::m_instance = knitter; + BeeperInterface *GlobalBeeper::m_instance = beeper; ComInterface *GlobalCom::m_instance = com; EncodersInterface *GlobalEncoders::m_instance = encoders; diff --git a/test/test_boards.cpp b/test/test_boards.cpp index ff4f95943..a0089329f 100644 --- a/test/test_boards.cpp +++ b/test/test_boards.cpp @@ -29,25 +29,30 @@ #include #include +#include #include // global definitions // references everywhere else must use `extern` -KnitterMock *knitter = new KnitterMock(); Beeper *beeper = new Beeper(); Com *com = new Com(); Encoders *encoders = new Encoders(); Solenoids *solenoids = new Solenoids(); Tester *tester = new Tester(); +FsmMock *fsm = new FsmMock(); +KnitterMock *knitter = new KnitterMock(); + // initialize static members -KnitterInterface *GlobalKnitter::m_instance = knitter; BeeperInterface *GlobalBeeper::m_instance = beeper; ComInterface *GlobalCom::m_instance = com; EncodersInterface *GlobalEncoders::m_instance = encoders; SolenoidsInterface *GlobalSolenoids::m_instance = solenoids; TesterInterface *GlobalTester::m_instance = tester; +FsmInterface *GlobalFsm::m_instance = fsm; +KnitterInterface *GlobalKnitter::m_instance = knitter; + int main(int argc, char *argv[]) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/test/test_com.cpp b/test/test_com.cpp index 8073465fd..394dfab73 100644 --- a/test/test_com.cpp +++ b/test/test_com.cpp @@ -24,7 +24,9 @@ #include #include +#include +#include #include using ::testing::_; @@ -32,6 +34,8 @@ using ::testing::Mock; using ::testing::Return; extern Com *com; + +extern FsmMock *fsm; extern KnitterMock *knitter; class ComTest : public ::testing::Test { @@ -41,11 +45,13 @@ class ComTest : public ::testing::Test { serialMock = serialMockInstance(); // pointer to global instance + fsmMock = fsm; knitterMock = knitter; // The global instance does not get destroyed at the end of each test. // Ordinarily the mock instance would be local and such behaviour would // cause a memory leak. We must notify the test that this is not the case. + Mock::AllowLeak(fsmMock); Mock::AllowLeak(knitterMock); expect_init(); @@ -55,12 +61,12 @@ class ComTest : public ::testing::Test { void TearDown() override { releaseArduinoMock(); releaseSerialMock(); - releaseKnitterMock(); } ArduinoMock *arduinoMock; - SerialMock *serialMock; + FsmMock *fsmMock; KnitterMock *knitterMock; + SerialMock *serialMock; void expect_init() { EXPECT_CALL(*serialMock, begin); @@ -76,16 +82,16 @@ TEST_F(ComTest, test_API) { TEST_F(ComTest, test_reqtest_fail) { // no machineType uint8_t buffer[] = {reqTest_msgid}; - EXPECT_CALL(*knitterMock, startTest).Times(0); + EXPECT_CALL(*fsmMock, setState(s_test)).Times(0); com->onPacketReceived(buffer, sizeof(buffer)); - ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); + ASSERT_TRUE(Mock::VerifyAndClear(fsmMock)); } TEST_F(ComTest, test_reqtest_success) { uint8_t buffer[] = {reqTest_msgid, Kh270}; - EXPECT_CALL(*knitterMock, startTest).WillOnce(Return(false)); + EXPECT_CALL(*fsmMock, setState(s_test)); com->onPacketReceived(buffer, sizeof(buffer)); - ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); + ASSERT_TRUE(Mock::VerifyAndClear(fsmMock)); } TEST_F(ComTest, test_reqstart_fail1) { @@ -299,3 +305,20 @@ TEST_F(ComTest, test_sendMsg2) { EXPECT_CALL(*serialMock, write(SLIP::END)); com->sendMsg(testRes_msgid, buf); } + +TEST_F(ComTest, test_send_reqLine) { + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); + com->send_reqLine(0); +} + +TEST_F(ComTest, test_send_indState) { + EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)); + EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_R)); + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); + com->send_indState(Knit, 0, true); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(fsmMock)); +} diff --git a/test/test_fsm.cpp b/test/test_fsm.cpp new file mode 100644 index 000000000..d6f29ec06 --- /dev/null +++ b/test/test_fsm.cpp @@ -0,0 +1,150 @@ +/*!` + * \file test_fsm.cpp + * + * This file is part of AYAB. + * + * AYAB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AYAB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AYAB. If not, see . + * + * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller + * Modified Work Copyright 2020 Sturla Lange, Tom Price + * http://ayab-knitting.com + */ + +#include + +#include +#include + +#include +#include +#include + +using ::testing::Mock; +using ::testing::Return; +using ::testing::Test; + +extern Fsm *fsm; +extern Knitter *knitter; + +extern ComMock *com; +extern SolenoidsMock *solenoids; +extern TesterMock *tester; + +class FsmTest : public ::testing::Test { +protected: + void SetUp() override { + arduinoMock = arduinoMockInstance(); + + // pointers to global instances + comMock = com; + solenoidsMock = solenoids; + testerMock = tester; + + // The global instance does not get destroyed at the end of each test. + // Ordinarily the mock instance would be local and such behaviour would + // cause a memory leak. We must notify the test that this is not the case. + Mock::AllowLeak(comMock); + Mock::AllowLeak(solenoidsMock); + Mock::AllowLeak(testerMock); + } + + void TearDown() override { + releaseArduinoMock(); + } + + ArduinoMock *arduinoMock; + ComMock *comMock; + SolenoidsMock *solenoidsMock; + TesterMock *testerMock; +}; + +TEST_F(FsmTest, test_init) { + fsm->init(); + ASSERT_EQ(fsm->getState(), s_init); +} + +TEST_F(FsmTest, test_setState) { + fsm->setState(s_ready); + ASSERT_EQ(fsm->getState(), s_ready); +} + +TEST_F(FsmTest, test_dispatch_init) { + // Knitter::isReady() == false + fsm->init(); + EXPECT_CALL(*comMock, update); + fsm->dispatch(); + ASSERT_EQ(fsm->getState(), s_init); + + // Knitter::isReady() == true + knitter->m_direction = Right; + knitter->m_hallActive = Left; + EXPECT_CALL(*solenoidsMock, setSolenoids(0xFFFF)); + EXPECT_CALL(*comMock, send_indState); + EXPECT_CALL(*comMock, update); + fsm->dispatch(); + ASSERT_EQ(fsm->getState(), s_ready); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); +} + +TEST_F(FsmTest, test_dispatch_ready) { + fsm->setState(s_ready); + EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 0)); + EXPECT_CALL(*comMock, update); + fsm->dispatch(); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); +} + +TEST_F(FsmTest, test_dispatch_test) { + fsm->setState(s_test); + + // tester->getQuitFlag() == false + EXPECT_CALL(*testerMock, loop); + EXPECT_CALL(*testerMock, getQuitFlag).WillOnce(Return(false)); + EXPECT_CALL(*comMock, update); + fsm->dispatch(); + + // tester->getQuitFlag() == true + EXPECT_CALL(*testerMock, loop); + EXPECT_CALL(*testerMock, getQuitFlag).WillOnce(Return(true)); + EXPECT_CALL(*comMock, update); + fsm->dispatch(); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(testerMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); +} + +TEST_F(FsmTest, test_dispatch_knit) { + fsm->setState(s_knit); + EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 1)); + EXPECT_CALL(*comMock, update); + fsm->dispatch(); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); +} + +TEST_F(FsmTest, test_dispatch_default) { + fsm->setState(static_cast(4)); + EXPECT_CALL(*comMock, update); + fsm->dispatch(); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); +} diff --git a/test/test_knitter.cpp b/test/test_knitter.cpp index 4053d2f2b..f0b394086 100644 --- a/test/test_knitter.cpp +++ b/test/test_knitter.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -39,6 +40,8 @@ using ::testing::Return; using ::testing::TypedEq; extern Knitter *knitter; +extern Fsm *fsm; + extern BeeperMock *beeper; extern ComMock *com; extern EncodersMock *encoders; @@ -67,6 +70,7 @@ class KnitterTest : public ::testing::Test { Mock::AllowLeak(testerMock); expect_init(); + fsm->init(); knitter->init(); } @@ -91,6 +95,8 @@ class KnitterTest : public ::testing::Test { EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 1)); // green LED on EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_B, 1)); // yellow LED on + + EXPECT_CALL(*solenoidsMock, init); } void expect_isr(uint16_t pos, Direction_t dir, Direction_t hall, @@ -131,24 +137,17 @@ class KnitterTest : public ::testing::Test { expected_isr(1); } - template void expect_send() { - EXPECT_CALL(*comMock, send).Times(times); - } - - template void expect_indState() { - expect_send(); - EXPECT_CALL(*encodersMock, getHallValue(Left)).Times(times); - EXPECT_CALL(*encodersMock, getHallValue(Right)).Times(times); - EXPECT_CALL(*encodersMock, getDirection).Times(times); + void expect_reqLine() { + EXPECT_CALL(*comMock, send_reqLine); } - void expect_fsm() { - EXPECT_CALL(*comMock, update); + void expect_indState() { + EXPECT_CALL(*comMock, send_indState); } void expected_fsm() { - expect_fsm(); - knitter->fsm(); + EXPECT_CALL(*comMock, update); + fsm->dispatch(); } void get_to_ready() { @@ -176,7 +175,7 @@ class KnitterTest : public ::testing::Test { get_to_knit(Kh910); EXPECT_CALL(*arduinoMock, delay(2000)); EXPECT_CALL(*beeperMock, finishedLine); - expect_send(); + expect_reqLine(); } EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 1)); // green LED on expected_fsm(); @@ -185,28 +184,25 @@ class KnitterTest : public ::testing::Test { void expected_test() { expect_indState(); EXPECT_CALL(*testerMock, loop); + EXPECT_CALL(*testerMock, getQuitFlag); expected_fsm(); } - - void expected_set_machine(Machine_t machineType) { - knitter->setMachineType(machineType); - EXPECT_CALL(*encodersMock, init); - encodersMock->init(machineType); - ASSERT_EQ(knitter->startTest(machineType), true); - } }; TEST_F(KnitterTest, test_init) { - ASSERT_EQ(knitter->getState(), s_init); ASSERT_EQ(knitter->m_startNeedle, 0); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); } TEST_F(KnitterTest, test_send) { uint8_t p[] = {1, 2, 3, 4, 5}; - expect_send(); + EXPECT_CALL(*comMock, send); comMock->send(p, 5); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } @@ -214,131 +210,31 @@ TEST_F(KnitterTest, test_isr) { expected_isr(1); // test expectations without destroying instance - ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); -} - -TEST_F(KnitterTest, test_fsm_default_case) { - knitter->setState(static_cast(4)); - expected_fsm(); - - // test expectations without destroying instance - ASSERT_TRUE(Mock::VerifyAndClear(comMock)); -} - -TEST_F(KnitterTest, test_fsm_init_LL) { - // not ready - expected_isr(Left, Left); - expected_fsm(); - ASSERT_EQ(knitter->getState(), s_init); - - // test expectations without destroying instance - ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); - ASSERT_TRUE(Mock::VerifyAndClear(comMock)); -} - -TEST_F(KnitterTest, test_fsm_init_RR) { - // still not ready - expected_isr(Right, Right); - expected_fsm(); - ASSERT_EQ(knitter->getState(), s_init); - - // test expectations without destroying instance - ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); - ASSERT_TRUE(Mock::VerifyAndClear(comMock)); -} - -TEST_F(KnitterTest, test_fsm_init_RL) { - // ready - expected_isr(Right, Left); - EXPECT_CALL(*solenoidsMock, setSolenoids(0xFFFF)); - expect_indState(); - expected_fsm(); - ASSERT_EQ(knitter->getState(), s_ready); - - // test expectations without destroying instance - ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); - ASSERT_TRUE(Mock::VerifyAndClear(comMock)); -} - -TEST_F(KnitterTest, test_fsm_ready) { - get_to_ready(); - EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 0)); - expected_fsm(); - - // still in ready state - ASSERT_EQ(knitter->getState(), s_ready); - - // again - EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 0)); - expected_fsm(); - - // still in ready state - ASSERT_EQ(knitter->getState(), s_ready); - - // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); - ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); - ASSERT_TRUE(Mock::VerifyAndClear(comMock)); -} - -TEST_F(KnitterTest, test_fsm_test) { - // enter test state - EXPECT_CALL(*testerMock, setUp); - ASSERT_EQ(knitter->startTest(Kh910), true); - - expected_isr(); - expect_indState(); - EXPECT_CALL(*testerMock, loop); - expected_fsm(); - - // again with same position, no `indState` this time. - expected_isr(); - expect_indState<0>(); - EXPECT_CALL(*testerMock, loop); - expected_fsm(); - - // test expectations without destroying instance - ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); - ASSERT_TRUE(Mock::VerifyAndClear(testerMock)); - ASSERT_TRUE(Mock::VerifyAndClear(comMock)); -} - -TEST_F(KnitterTest, test_fsm_test_quit) { - // enter test state - EXPECT_CALL(*testerMock, setUp); - ASSERT_EQ(knitter->startTest(Kh910), true); - - // quit - EXPECT_CALL(*testerMock, getQuitFlag).WillOnce(Return(true)); - expected_fsm(); - - // test expectations without destroying instance - ASSERT_TRUE(Mock::VerifyAndClear(testerMock)); - ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_startKnitting_NoMachine) { uint8_t pattern[] = {1}; - ASSERT_EQ(knitter->getState(), s_init); Machine_t m = knitter->getMachineType(); ASSERT_EQ(m, NoMachine); - get_to_ready(); + fsm->setState(s_ready); ASSERT_EQ(knitter->startKnitting(m, 0, NUM_NEEDLES[m] - 1, pattern, false), false); // test expectations without destroying instance - ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); - ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_startKnitting_notReady) { uint8_t pattern[] = {1}; - ASSERT_EQ(knitter->getState(), s_init); + // fsm->setState(s_init); ASSERT_EQ( knitter->startKnitting(Kh910, 0, NUM_NEEDLES[Kh910] - 1, pattern, false), false); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); } TEST_F(KnitterTest, test_startKnitting_Kh910) { @@ -351,9 +247,9 @@ TEST_F(KnitterTest, test_startKnitting_Kh910) { true); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); - ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } @@ -367,9 +263,9 @@ TEST_F(KnitterTest, test_startKnitting_Kh270) { true); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); - ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } @@ -391,48 +287,8 @@ TEST_F(KnitterTest, test_startKnitting_failures) { false); // test expectations without destroying instance - ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); - ASSERT_TRUE(Mock::VerifyAndClear(comMock)); -} - -TEST_F(KnitterTest, test_startTest_in_init) { - // from state `s_init` - EXPECT_CALL(*testerMock, setUp); - ASSERT_EQ(knitter->getState(), s_init); - ASSERT_EQ(knitter->startTest(Kh910), true); - - // fail if call `startTest()` while already in state `s_test` - ASSERT_EQ(knitter->getState(), s_test); - ASSERT_EQ(knitter->startTest(Kh270), false); - - // test expectations without destroying instance - ASSERT_TRUE(Mock::VerifyAndClear(testerMock)); - ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); - ASSERT_TRUE(Mock::VerifyAndClear(comMock)); -} - -TEST_F(KnitterTest, test_startTest_in_ready) { - get_to_ready(); - ASSERT_EQ(knitter->getState(), s_ready); - ASSERT_EQ(knitter->startTest(Kh930), true); - - // test expectations without destroying instance - ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); - ASSERT_TRUE(Mock::VerifyAndClear(comMock)); -} - -TEST_F(KnitterTest, test_startTest_in_knit) { - get_to_knit(Kh910); - ASSERT_EQ(knitter->getState(), s_knit); - - // can't start test - ASSERT_EQ(knitter->startTest(Kh910), false); - - // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); - ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); - ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } @@ -447,11 +303,10 @@ TEST_F(KnitterTest, test_setNextLine) { expected_isr(40 + NUM_NEEDLES[Kh910] - 1 + END_OF_LINE_OFFSET_R[Kh910] + 1); EXPECT_CALL(*solenoidsMock, setSolenoid).Times(1); expected_knit(false); - ASSERT_EQ(knitter->getState(), s_knit); // wrong line number EXPECT_CALL(*beeperMock, finishedLine).Times(0); - expect_send(); + expect_reqLine(); ASSERT_EQ(knitter->setNextLine(1), false); // correct line number @@ -462,13 +317,13 @@ TEST_F(KnitterTest, test_setNextLine) { ASSERT_EQ(knitter->setNextLine(0), false); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); - ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } -TEST_F(KnitterTest, test_operate_Kh910) { +TEST_F(KnitterTest, test_knit_Kh910) { // `m_pixelToSet` gets set to 0 expected_isr(8); @@ -478,7 +333,7 @@ TEST_F(KnitterTest, test_operate_Kh910) { expect_indState(); expected_fsm(); - // operate + // knit uint8_t pattern[] = {1}; // `m_startNeedle` is greater than `m_pixelToSet` @@ -494,15 +349,14 @@ TEST_F(KnitterTest, test_operate_Kh910) { EXPECT_CALL(*beeperMock, finishedLine); // `indState` and send - expect_send<2>(); - EXPECT_CALL(*encodersMock, getHallValue(Left)); - EXPECT_CALL(*encodersMock, getHallValue(Right)); - EXPECT_CALL(*encodersMock, getDirection); + expect_reqLine(); + expect_indState(); EXPECT_CALL(*solenoidsMock, setSolenoid); expected_knit(false); // no useful position calculated by `calculatePixelAndSolenoid()` expected_isr(100, NoDirection, Right, Shifted, Garter); + EXPECT_CALL(*solenoidsMock, setSolenoid).Times(0); expect_indState(); expected_knit(false); @@ -518,13 +372,13 @@ TEST_F(KnitterTest, test_operate_Kh910) { expected_knit(false); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); - ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); } -TEST_F(KnitterTest, test_operate_Kh270) { +TEST_F(KnitterTest, test_knit_Kh270) { // `m_pixelToSet` gets set to 0 expected_isr(8); @@ -534,7 +388,7 @@ TEST_F(KnitterTest, test_operate_Kh270) { expect_indState(); expected_fsm(); - // operate + // knit uint8_t pattern[] = {1}; // `m_startNeedle` is greater than `m_pixelToSet` @@ -556,37 +410,36 @@ TEST_F(KnitterTest, test_operate_Kh270) { EXPECT_CALL(*beeperMock, finishedLine); // `indState` and send - expect_send<2>(); - EXPECT_CALL(*encodersMock, getHallValue(Left)); - EXPECT_CALL(*encodersMock, getHallValue(Right)); - EXPECT_CALL(*encodersMock, getDirection); + expect_reqLine(); + expect_indState(); EXPECT_CALL(*solenoidsMock, setSolenoid); expected_knit(false); // no useful position calculated by `calculatePixelAndSolenoid()` - expected_isr(100, NoDirection, Right, Shifted, Garter); + expected_isr(60, NoDirection, Right, Shifted, Knit); + EXPECT_CALL(*solenoidsMock, setSolenoid).Times(0); expect_indState(); expected_knit(false); // don't set `m_workedonline` to `true` - expected_isr(8 + STOP_NEEDLE + OFFSET); + expected_isr(8 + STOP_NEEDLE + OFFSET, Right, Left, Regular, Knit); EXPECT_CALL(*solenoidsMock, setSolenoid); expect_indState(); expected_knit(false); - expected_isr(START_NEEDLE); + expected_isr(START_NEEDLE, Right, Left, Regular, Knit); EXPECT_CALL(*solenoidsMock, setSolenoid); expect_indState(); expected_knit(false); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); - ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } -TEST_F(KnitterTest, test_operate_line_request) { +TEST_F(KnitterTest, test_knit_line_request) { EXPECT_CALL(*solenoidsMock, setSolenoid); // `m_workedOnLine` is set to `true` @@ -604,13 +457,13 @@ TEST_F(KnitterTest, test_operate_line_request) { expected_knit(false); // test expectations without destroying instance - ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } -TEST_F(KnitterTest, test_operate_lastline) { +TEST_F(KnitterTest, test_knit_lastline) { EXPECT_CALL(*solenoidsMock, setSolenoid); // `m_workedOnLine` is set to true @@ -630,13 +483,13 @@ TEST_F(KnitterTest, test_operate_lastline) { expected_knit(false); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); - ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } -TEST_F(KnitterTest, test_operate_lastline_and_no_req) { +TEST_F(KnitterTest, test_knit_lastline_and_no_req) { get_to_knit(Kh910); // Note: probing private data and methods to get full branch coverage. @@ -650,25 +503,25 @@ TEST_F(KnitterTest, test_operate_lastline_and_no_req) { knitter->m_lineRequested = false; knitter->m_lastLineFlag = true; - EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 1)); + // EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 1)); EXPECT_CALL(*solenoidsMock, setSolenoid); EXPECT_CALL(*beeperMock, endWork); EXPECT_CALL(*solenoidsMock, setSolenoids(0xFFFF)); EXPECT_CALL(*beeperMock, finishedLine); - knitter->state_knit(); + knitter->knit(); ASSERT_EQ(knitter->getStartOffset(NUM_DIRECTIONS), 0); knitter->m_carriage = NUM_CARRIAGES; ASSERT_EQ(knitter->getStartOffset(Right), 0); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); - ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } -TEST_F(KnitterTest, test_operate_same_position) { +TEST_F(KnitterTest, test_knit_same_position) { EXPECT_CALL(*solenoidsMock, setSolenoid); expected_knit(true); @@ -677,13 +530,13 @@ TEST_F(KnitterTest, test_operate_same_position) { expected_knit(false); // test expectations without destroying instance - ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } -TEST_F(KnitterTest, test_operate_new_line) { +TEST_F(KnitterTest, test_knit_new_line) { EXPECT_CALL(*solenoidsMock, setSolenoid); // _workedOnLine is set to true @@ -699,20 +552,20 @@ TEST_F(KnitterTest, test_operate_new_line) { EXPECT_CALL(*solenoidsMock, setSolenoid); - // `reqLine()` is called which calls `send()` - expect_send(); + // `reqLine()` is called which calls `send_reqLine()` + expect_reqLine(); expected_knit(false); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); - ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } TEST_F(KnitterTest, test_calculatePixelAndSolenoid) { - EXPECT_CALL(*testerMock, setUp); - expected_set_machine(Kh910); + fsm->setState(s_test); + knitter->setMachineType(Kh910); // new position, different beltShift and active hall expected_isr(100, Right, Right, Shifted, Lace); @@ -762,9 +615,9 @@ TEST_F(KnitterTest, test_calculatePixelAndSolenoid) { expected_test(); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); ASSERT_TRUE(Mock::VerifyAndClear(testerMock)); - ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } @@ -787,4 +640,45 @@ TEST_F(KnitterTest, test_getStartOffset) { knitter->m_machineType = NUM_MACHINES; ASSERT_EQ(knitter->getStartOffset(Right), 0); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); +} + +TEST_F(KnitterTest, test_fsm_init_LL) { + // not ready + expected_isr(Left, Left); + expected_fsm(); + ASSERT_EQ(fsm->getState(), s_init); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); +} + +TEST_F(KnitterTest, test_fsm_init_RR) { + // still not ready + expected_isr(Right, Right); + expected_fsm(); + ASSERT_EQ(fsm->getState(), s_init); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); +} + +TEST_F(KnitterTest, test_fsm_init_RL) { + // ready + expected_isr(Right, Left); + EXPECT_CALL(*solenoidsMock, setSolenoids(0xFFFF)); + expect_indState(); + expected_fsm(); + ASSERT_EQ(fsm->getState(), s_ready); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); } diff --git a/test/test_tester.cpp b/test/test_tester.cpp index 4f1421c63..361505d4d 100644 --- a/test/test_tester.cpp +++ b/test/test_tester.cpp @@ -23,10 +23,10 @@ #include -//#include #include #include +#include #include using ::testing::An; @@ -35,13 +35,9 @@ using ::testing::Mock; using ::testing::Return; extern Tester *tester; -extern KnitterMock *knitter; -// static char zero[2] = {'0', 0}; -// static char two[2] = {'2', 0}; -// static char g[2] = {'g', 0}; -// static char fAdE[5] = {'f', 'A', 'd', 'E', 0}; -// static char sixteen[3] = {'1', '6', 0}; +extern FsmMock *fsm; +extern KnitterMock *knitter; class TesterTest : public ::testing::Test { protected: @@ -50,25 +46,25 @@ class TesterTest : public ::testing::Test { serialMock = serialMockInstance(); // serialCommandMock = serialCommandMockInstance(); - // pointer to global instance + // pointers to global instances + fsmMock = fsm; knitterMock = knitter; - // The global instance does not get destroyed at the end of each test. + // The global instances do not get destroyed at the end of each test. // Ordinarily the mock instance would be local and such behaviour would // cause a memory leak. We must notify the test that this is not the case. + Mock::AllowLeak(fsmMock); Mock::AllowLeak(knitterMock); } void TearDown() override { releaseArduinoMock(); releaseSerialMock(); - // releaseSerialCommandMock(); - releaseKnitterMock(); } ArduinoMock *arduinoMock; SerialMock *serialMock; - // SerialCommandMock *serialCommandMock; + FsmMock *fsmMock; KnitterMock *knitterMock; }; @@ -82,97 +78,70 @@ TEST_F(TesterTest, test_setUp) { } TEST_F(TesterTest, test_helpCmd) { - // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - // .Times(AtLeast(1)); tester->helpCmd(); } TEST_F(TesterTest, test_sendCmd) { - // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - // .Times(AtLeast(1)); - // EXPECT_CALL(*knitterMock, send); tester->sendCmd(); } TEST_F(TesterTest, test_beepCmd) { - // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - // .Times(AtLeast(1)); EXPECT_CALL(*arduinoMock, analogWrite).Times(AtLeast(1)); EXPECT_CALL(*arduinoMock, delay).Times(AtLeast(1)); tester->beepCmd(); } TEST_F(TesterTest, test_setSingleCmd_fail1) { - // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - // .Times(AtLeast(1)); const uint8_t buf[] = {setSingleCmd_msgid, 0}; tester->setSingleCmd(buf, 2); } TEST_F(TesterTest, test_setSingleCmd_fail2) { - // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - // .Times(AtLeast(1)); const uint8_t buf[] = {setSingleCmd_msgid, 16, 0}; tester->setSingleCmd(buf, 3); } TEST_F(TesterTest, test_setSingleCmd_fail3) { - // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - // .Times(AtLeast(1)); const uint8_t buf[] = {setSingleCmd_msgid, 15, 2}; tester->setSingleCmd(buf, 3); } TEST_F(TesterTest, test_setSingleCmd_success) { - // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - // .Times(AtLeast(1)); const uint8_t buf[] = {setSingleCmd_msgid, 15, 1}; tester->setSingleCmd(buf, 3); } TEST_F(TesterTest, test_setAllCmd_fail1) { - // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - // .Times(AtLeast(1)); const uint8_t buf[] = {setAllCmd_msgid, 0}; tester->setAllCmd(buf, 2); } TEST_F(TesterTest, test_setAllCmd_success) { - // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - // .Times(AtLeast(1)); const uint8_t buf[] = {setAllCmd_msgid, 0xff, 0xff}; tester->setAllCmd(buf, 3); } TEST_F(TesterTest, test_readEOLsensorsCmd) { - // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - // .Times(AtLeast(1)); + EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)); + EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_R)); tester->readEOLsensorsCmd(); } TEST_F(TesterTest, test_readEncodersCmd_low) { - // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - // .Times(AtLeast(1)); EXPECT_CALL(*arduinoMock, digitalRead).WillRepeatedly(Return(LOW)); tester->readEncodersCmd(); } TEST_F(TesterTest, test_readEncodersCmd_high) { - // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - // .Times(AtLeast(1)); EXPECT_CALL(*arduinoMock, digitalRead).WillRepeatedly(Return(HIGH)); tester->readEncodersCmd(); } TEST_F(TesterTest, test_autoReadCmd) { - // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - // .Times(AtLeast(1)); tester->autoReadCmd(); } TEST_F(TesterTest, test_autoTestCmd) { - // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - // .Times(AtLeast(1)); tester->autoTestCmd(); } @@ -185,15 +154,12 @@ TEST_F(TesterTest, test_stopCmd) { } TEST_F(TesterTest, test_quitCmd) { + EXPECT_CALL(*knitterMock, setUpInterrupt); tester->quitCmd(); ASSERT_TRUE(tester->m_quit); -} -TEST_F(TesterTest, test_unrecognizedCmd) { - // EXPECT_CALL(*knitterMock, sendMsg(test_msgid, An())) - // .Times(AtLeast(1)); - const char buffer[1] = {1}; - tester->unrecognizedCmd(buffer); + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); } TEST_F(TesterTest, test_loop_default) { @@ -218,7 +184,6 @@ TEST_F(TesterTest, test_loop_autoTestEven) { tester->m_autoTestOn = true; EXPECT_CALL(*arduinoMock, digitalRead).Times(0); EXPECT_CALL(*arduinoMock, digitalWrite).Times(2); - // EXPECT_CALL(*knitterMock, setSolenoids); tester->loop(); } @@ -228,27 +193,26 @@ TEST_F(TesterTest, test_loop_autoTestOdd) { tester->m_timerEventOdd = true; tester->m_autoReadOn = true; tester->m_autoTestOn = true; + EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)); + EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_R)); EXPECT_CALL(*arduinoMock, digitalRead).Times(3); EXPECT_CALL(*arduinoMock, digitalWrite).Times(2); - // EXPECT_CALL(*knitterMock, setSolenoids); tester->loop(); } -/* -TEST_F(TesterTest, test_scanHex) { - uint16_t result; - ASSERT_FALSE(tester->scanHex(zero, 0, &result)); - ASSERT_FALSE(tester->scanHex(g, 4, &result)); - ASSERT_FALSE(tester->scanHex(g + 1, 1, &result)); - ASSERT_TRUE(tester->scanHex(zero, 1, &result)); - ASSERT_TRUE(result == 0); - ASSERT_TRUE(tester->scanHex(zero, 4, &result)); - ASSERT_TRUE(result == 0); - ASSERT_TRUE(tester->scanHex(a, 1, &result)); - ASSERT_TRUE(result == 0xa); - ASSERT_TRUE(tester->scanHex(A, 4, &result)); - ASSERT_TRUE(result == 0xA); - ASSERT_TRUE(tester->scanHex(fAdE, 4, &result)); - ASSERT_TRUE(result == 0xfAdE); -} -*/ +TEST_F(TesterTest, test_startTest_fail) { + // can't start test from state `s_knit` + EXPECT_CALL(*fsmMock, getState).WillOnce(Return(s_knit)); + ASSERT_EQ(tester->startTest(Kh910), false); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(fsmMock)); +} + +TEST_F(TesterTest, test_startTest_success) { + EXPECT_CALL(*fsmMock, getState).WillOnce(Return(s_ready)); + ASSERT_EQ(tester->startTest(Kh930), true); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(fsmMock)); +} From 35bb0d1d8e857d1d12b510aa8dec587a291d44f7 Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 23 Aug 2020 13:42:19 -0400 Subject: [PATCH 11/17] Carriage can start on the Right hand side moving Left. --- src/ayab/knitter.cpp | 15 ++++++++++----- test/test_knitter.cpp | 28 ++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/ayab/knitter.cpp b/src/ayab/knitter.cpp index 4539bc1ef..2a58e40d5 100644 --- a/src/ayab/knitter.cpp +++ b/src/ayab/knitter.cpp @@ -64,6 +64,8 @@ void Knitter::init() { setUpInterrupt(); // explicitly initialize members + + // job parameters m_machineType = NoMachine; m_startNeedle = 0U; m_stopNeedle = 0U; @@ -79,8 +81,6 @@ void Knitter::init() { #ifdef DBG_NOMACHINE m_prevState = false; #endif - m_solenoidToSet = 0U; - m_pixelToSet = 0U; } void Knitter::setUpInterrupt() { @@ -163,7 +163,8 @@ void Knitter::encodePosition() { } } -// return true -> move from state `s_init` to `s_ready` +// if this function returns true then +// the FSM will move from state `s_init` to `s_ready` bool Knitter::isReady() { #ifdef DBG_NOMACHINE bool state = digitalRead(DBG_BTN_PIN); @@ -171,8 +172,12 @@ bool Knitter::isReady() { // TODO(who?): check if debounce is needed if (m_prevState && !state) { #else - // machine is initialized when left hall sensor is passed in Right direction - if (Right == m_direction && Left == m_hallActive) { + // Machine is initialized when left Hall sensor is passed in Right direction + // New feature (August 2020): the machine is also initialized + // when the right Hall sensor is passed in Left direction. + if ((Right == m_direction and Left == m_hallActive) or + (Left == m_direction and Right == m_hallActive)) { + #endif // DBG_NOMACHINE GlobalSolenoids::setSolenoids(SOLENOIDS_BITMASK); indState(true); diff --git a/test/test_knitter.cpp b/test/test_knitter.cpp index f0b394086..74cab462a 100644 --- a/test/test_knitter.cpp +++ b/test/test_knitter.cpp @@ -116,7 +116,7 @@ class KnitterTest : public ::testing::Test { } void expect_isr(Direction_t dir, Direction_t hall) { - expect_isr(1, dir, hall, Regular, Garter); + expect_isr(1, dir, hall, Regular, Knit); } void expected_isr(Direction_t dir, Direction_t hall) { @@ -151,10 +151,10 @@ class KnitterTest : public ::testing::Test { } void get_to_ready() { - // machine is initialized when left hall sensor - // is passed in Right direction inside active needles + // Machine is initialized when Left hall sensor + // is passed in Right direction inside active needles. Machine_t m = knitter->getMachineType(); - expected_isr(40 + END_OF_LINE_OFFSET_L[m] + 1); + expected_isr(40 + END_OF_LINE_OFFSET_L[m] + 1, Right, Left, Regular, Knit); // initialize EXPECT_CALL(*solenoidsMock, setSolenoids(0xFFFF)); @@ -355,7 +355,7 @@ TEST_F(KnitterTest, test_knit_Kh910) { expected_knit(false); // no useful position calculated by `calculatePixelAndSolenoid()` - expected_isr(100, NoDirection, Right, Shifted, Garter); + expected_isr(100, NoDirection, Right, Shifted, Knit); EXPECT_CALL(*solenoidsMock, setSolenoid).Times(0); expect_indState(); expected_knit(false); @@ -670,7 +670,8 @@ TEST_F(KnitterTest, test_fsm_init_RR) { } TEST_F(KnitterTest, test_fsm_init_RL) { - // ready + // Machine is initialized when Left hall sensor + // is passed in Right direction inside active needles. expected_isr(Right, Left); EXPECT_CALL(*solenoidsMock, setSolenoids(0xFFFF)); expect_indState(); @@ -682,3 +683,18 @@ TEST_F(KnitterTest, test_fsm_init_RL) { ASSERT_TRUE(Mock::VerifyAndClear(comMock)); ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); } + +TEST_F(KnitterTest, test_fsm_init_LR) { + // New feature (August 2020): the machine is also initialized + // when the right Hall sensor is passed in the Left direction. + expected_isr(Left, Right); + EXPECT_CALL(*solenoidsMock, setSolenoids(0xFFFF)); + expect_indState(); + expected_fsm(); + ASSERT_EQ(fsm->getState(), s_ready); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); + ASSERT_TRUE(Mock::VerifyAndClear(comMock)); + ASSERT_TRUE(Mock::VerifyAndClear(encodersMock)); +} From ed104f4f9d7f82704f6939c583066f8db21ea64b Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 23 Aug 2020 14:13:31 -0400 Subject: [PATCH 12/17] Re-initialize after quitting hardware test. --- src/ayab/fsm.cpp | 4 +++- test/test_fsm.cpp | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/ayab/fsm.cpp b/src/ayab/fsm.cpp index 8ddf45ae8..4f8ff2903 100644 --- a/src/ayab/fsm.cpp +++ b/src/ayab/fsm.cpp @@ -94,6 +94,8 @@ void Fsm::state_test() { GlobalKnitter::encodePosition(); GlobalTester::loop(); if (GlobalTester::getQuitFlag()) { - setState(s_ready); + // return to state `s_init after quitting test + GlobalKnitter::init(); + setState(s_init); } } diff --git a/test/test_fsm.cpp b/test/test_fsm.cpp index d6f29ec06..c0d403278 100644 --- a/test/test_fsm.cpp +++ b/test/test_fsm.cpp @@ -67,6 +67,20 @@ class FsmTest : public ::testing::Test { ComMock *comMock; SolenoidsMock *solenoidsMock; TesterMock *testerMock; + + void expect_init() { + EXPECT_CALL(*arduinoMock, pinMode(ENC_PIN_A, INPUT)); + EXPECT_CALL(*arduinoMock, pinMode(ENC_PIN_B, INPUT)); + EXPECT_CALL(*arduinoMock, pinMode(ENC_PIN_C, INPUT)); + + EXPECT_CALL(*arduinoMock, pinMode(LED_PIN_A, OUTPUT)); + EXPECT_CALL(*arduinoMock, pinMode(LED_PIN_B, OUTPUT)); + + EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 1)); // green LED on + EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_B, 1)); // yellow LED on + + EXPECT_CALL(*solenoidsMock, init); + } }; TEST_F(FsmTest, test_init) { @@ -123,11 +137,13 @@ TEST_F(FsmTest, test_dispatch_test) { EXPECT_CALL(*testerMock, loop); EXPECT_CALL(*testerMock, getQuitFlag).WillOnce(Return(true)); EXPECT_CALL(*comMock, update); + expect_init(); fsm->dispatch(); // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(testerMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); } TEST_F(FsmTest, test_dispatch_knit) { From a51d51698c400ded8ede39974e5e4a6acb7f297e Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 23 Aug 2020 14:28:40 -0400 Subject: [PATCH 13/17] Make CRC function a private method of class Com. --- src/ayab/com.cpp | 63 ++++++++++++++++++++++++------------------------ src/ayab/com.h | 3 +++ 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/ayab/com.cpp b/src/ayab/com.cpp index 440577dae..00db69cf4 100644 --- a/src/ayab/com.cpp +++ b/src/ayab/com.cpp @@ -26,37 +26,6 @@ #include "knitter.h" #include "tester.h" -#ifdef AYAB_ENABLE_CRC -/*! - * \brief Calculate CRC8 of a buffer - * - * Based on - * https://www.leonardomiliani.com/en/2013/un-semplice-crc8-per-arduino/ - * - * CRC-8 - based on the CRC8 formulas by Dallas/Maxim - * code released under the therms of the GNU GPL 3.0 license - */ -static uint8_t CRC8(const uint8_t *buffer, size_t len) { - uint8_t crc = 0x00U; - - while (len--) { - uint8_t extract = *buffer; - buffer++; - - for (uint8_t tempI = 8U; tempI; tempI--) { - uint8_t sum = (crc ^ extract) & 0x01U; - crc >>= 1U; - - if (sum) { - crc ^= 0x8CU; - } - extract >>= 1U; - } - } - return crc; -} -#endif - void Com::init() { m_packetSerial.begin(SERIAL_BAUDRATE); #ifndef AYAB_TESTS @@ -322,4 +291,34 @@ void Com::h_unrecognized() { // do nothing } // GCOVR_EXCL_STOP -// + +#ifdef AYAB_ENABLE_CRC +/*! + * \brief Calculate CRC8 of a buffer + * + * Based on + * https://www.leonardomiliani.com/en/2013/un-semplice-crc8-per-arduino/ + * + * CRC-8 - based on the CRC8 formulas by Dallas/Maxim + * code released under the therms of the GNU GPL 3.0 license + */ +uint8_t Com::CRC8(const uint8_t *buffer, size_t len) { + uint8_t crc = 0x00U; + + while (len--) { + uint8_t extract = *buffer; + buffer++; + + for (uint8_t tempI = 8U; tempI; tempI--) { + uint8_t sum = (crc ^ extract) & 0x01U; + crc >>= 1U; + + if (sum) { + crc ^= 0x8CU; + } + extract >>= 1U; + } + } + return crc; +} +#endif diff --git a/src/ayab/com.h b/src/ayab/com.h index cbe779a12..d394e9d0f 100644 --- a/src/ayab/com.h +++ b/src/ayab/com.h @@ -136,6 +136,9 @@ class Com : public ComInterface { void h_reqInfo(); void h_reqTest(const uint8_t *buffer, size_t size); void h_unrecognized(); +#ifdef AYAB_ENABLE_CRC + uint8_t CRC8(const uint8_t *buffer, size_t len); +#endif }; #endif // COM_H_ From bae130b9f3119c8b18804095e36a7884c45206ba Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 23 Aug 2020 19:55:42 -0400 Subject: [PATCH 14/17] Add error codes. --- src/ayab/com.cpp | 101 ++++++++++++++++++++++++++---------- src/ayab/com.h | 57 ++++++++++++++++++-- src/ayab/global_com.cpp | 4 +- src/ayab/global_knitter.cpp | 6 +-- src/ayab/global_tester.cpp | 2 +- src/ayab/knitter.cpp | 35 +++++++++---- src/ayab/knitter.h | 18 +++---- src/ayab/tester.cpp | 15 ++++-- src/ayab/tester.h | 7 +-- test/mocks/com_mock.cpp | 4 +- test/mocks/com_mock.h | 2 +- test/mocks/knitter_mock.cpp | 6 +-- test/mocks/knitter_mock.h | 6 +-- test/mocks/tester_mock.cpp | 2 +- test/mocks/tester_mock.h | 2 +- test/test_com.cpp | 60 ++++++++++++++++++--- test/test_fsm.cpp | 8 +++ test/test_knitter.cpp | 40 +++++++------- test/test_tester.cpp | 48 +++++++++++++++-- 19 files changed, 317 insertions(+), 106 deletions(-) diff --git a/src/ayab/com.cpp b/src/ayab/com.cpp index 00db69cf4..8c689f686 100644 --- a/src/ayab/com.cpp +++ b/src/ayab/com.cpp @@ -60,19 +60,24 @@ void Com::sendMsg(AYAB_API_t id, const char *msg) { } m_packetSerial.send(msgBuffer, length); } - void Com::sendMsg(AYAB_API_t id, char *msg) { sendMsg(id, static_cast(msg)); } -void Com::send_reqLine(const uint8_t lineNumber) { - uint8_t payload[REQLINE_LEN] = { - reqLine_msgid, - lineNumber, - }; +/*! + * \brief Send `reqLine` message. + * + * if the second argument takes any value other than `0` + * this is a request for information to be re-sent + */ +void Com::send_reqLine(const uint8_t lineNumber, Err_t error) { + uint8_t payload[REQLINE_LEN] = {reqLine_msgid, lineNumber, error}; send(static_cast(payload), REQLINE_LEN); } +/*! + * \brief Send `indState` message. + */ void Com::send_indState(Carriage_t carriage, uint8_t position, const bool initState) { uint16_t leftHallValue = GlobalEncoders::getHallValue(Left); @@ -165,7 +170,7 @@ void Com::onPacketReceived(const uint8_t *buffer, size_t size) { // Serial command handling /*! - * \brief Handle start request command. + * \brief Handle `reqStart` (start request) command. * * \todo sl: Assert size? Handle error? * \todo TP: Handle CRC-8 error? @@ -175,11 +180,13 @@ void Com::h_reqStart(const uint8_t *buffer, size_t size) { #ifdef AYAB_ENABLE_CRC if (size < 6U) { // Need 6 bytes from buffer below. + send_cnfStart(EXPECTED_LONGER_MESSAGE); return; } #else if (size < 5U) { // Need 5 bytes from buffer below. + send_cnfStart(EXPECTED_LONGER_MESSAGE); return; } #endif @@ -193,29 +200,32 @@ void Com::h_reqStart(const uint8_t *buffer, size_t size) { uint8_t crc8 = buffer[5]; // Check crc on bytes 0-4 of buffer. if (crc8 != CRC8(buffer, 5)) { + send_cnfStart(CHECKSUM_ERROR); return; } #endif // TODO(who?): verify operation // memset(lineBuffer,0,sizeof(lineBuffer)); + /* // temporary solution: for (uint8_t i = 0U; i < MAX_LINE_BUFFER_LEN; i++) { lineBuffer[i] = 0xFFU; } + */ + memset(lineBuffer, 0xFF, MAX_LINE_BUFFER_LEN); - bool success = + // Note (August 2020): the return value of this function has changed. + // Previously, it returned `true` for success and `false` for failure. + // Now, it returns `0` for success and an informative error code otherwise. + Err_t error = GlobalKnitter::startKnitting(machineType, startNeedle, stopNeedle, lineBuffer, continuousReportingEnabled); - - uint8_t payload[2]; - payload[0] = cnfStart_msgid; - payload[1] = static_cast(success); - send(payload, 2); + send_cnfStart(error); } /*! - * \brief Handle configure line command. + * \brief Handle `cnfLine` (configure line) command. * * \todo sl: Handle CRC-8 error? * \todo sl: Assert size? Handle error? @@ -226,6 +236,7 @@ void Com::h_cnfLine(const uint8_t *buffer, size_t size) { if (size < lenLineBuffer + 5U) { // message is too short // TODO(sl): handle error? + // TODO(TP): send repeat request with error code? return; } @@ -243,6 +254,7 @@ void Com::h_cnfLine(const uint8_t *buffer, size_t size) { // Calculate checksum of buffer contents if (crc8 != CRC8(buffer, lenLineBuffer + 4)) { // TODO(sl): handle checksum error? + // TODO(TP): send repeat request with error code? return; } #endif @@ -256,42 +268,75 @@ void Com::h_cnfLine(const uint8_t *buffer, size_t size) { } } +/*! + * \brief Handle `reqInfo` (request information) command. + */ void Com::h_reqInfo() { - uint8_t payload[4]; - payload[0] = cnfInfo_msgid; - payload[1] = API_VERSION; - payload[2] = FW_VERSION_MAJ; - payload[3] = FW_VERSION_MIN; - send(payload, 4); + send_cnfInfo(); } /*! - * \brief Handle request hardware test command. + * \brief Handle `reqTest` (request hardware test) command. * * \todo TP: Assert size? Handle error? */ void Com::h_reqTest(const uint8_t *buffer, size_t size) { if (size < 2U) { // message is too short - // TODO(TP): handle error? + send_cnfTest(EXPECTED_LONGER_MESSAGE); return; } - Machine_t machineType = static_cast(buffer[0]); - bool success = GlobalTester::startTest(machineType); + Machine_t machineType = static_cast(buffer[1]); - uint8_t payload[2]; - payload[0] = cnfTest_msgid; - payload[1] = static_cast(success); - send(payload, 2); + // Note (August 2020): the return value of this function has changed. + // Previously, it returned `true` for success and `false` for failure. + // Now, it returns `0` for success and an informative error code otherwise. + Err_t error = GlobalTester::startTest(machineType); + send_cnfTest(error); } // GCOVR_EXCL_START +/*! + * \brief Handle unrecognized command. + */ void Com::h_unrecognized() { // do nothing } // GCOVR_EXCL_STOP +/*! + * \brief Send `cnfInfo` message. + */ +void Com::send_cnfInfo() { + uint8_t payload[4]; + payload[0] = cnfInfo_msgid; + payload[1] = API_VERSION; + payload[2] = FW_VERSION_MAJ; + payload[3] = FW_VERSION_MIN; + send(payload, 4); +} + +/*! + * \brief Send `cnfStart` message. + */ +void Com::send_cnfStart(Err_t error) { + uint8_t payload[2]; + payload[0] = cnfStart_msgid; + payload[1] = static_cast(error); + send(payload, 2); +} + +/*! + * \brief Send `cnfTest` message. + */ +void Com::send_cnfTest(Err_t error) { + uint8_t payload[2]; + payload[0] = cnfTest_msgid; + payload[1] = static_cast(error); + send(payload, 2); +} + #ifdef AYAB_ENABLE_CRC /*! * \brief Calculate CRC8 of a buffer diff --git a/src/ayab/com.h b/src/ayab/com.h index d394e9d0f..6abe60769 100644 --- a/src/ayab/com.h +++ b/src/ayab/com.h @@ -66,9 +66,53 @@ enum AYAB_API { }; using AYAB_API_t = enum AYAB_API; +// As of APIv6, the only important distinction +// is between `SUCCESS` (0) and any other value. +// Informative error codes are provided for +// diagnostic purposes (that is, for debugging). +// Non-zero error codes are subject to change. +// Such changes will be considered non-breaking. +enum ErrorCode { + SUCCESS = 0x00, + + // message not understood + EXPECTED_LONGER_MESSAGE = 0x01, + UNRECOGNIZED_MSGID = 0x02, + UNEXPECTED_MSGID = 0x03, + CHECKSUM_ERROR = 0x04, + + // invalid arguments + MACHINE_TYPE_INVALID = 0x10, + NEEDLE_VALUE_INVALID = 0x11, + NULL_POINTER_ARGUMENT = 0x12, + ARGUMENT_INVALID = 0x13, + ARGUMENTS_INCOMPATIBLE = 0x13, + + // device not initialized + NO_MACHINE_TYPE = 0x20, + NO_CARRIAGE = 0x21, + NO_DIRECTION = 0x22, + NO_BELTSHIFT = 0x23, + + // machine in wrong FSM state + MACHINE_STATE_INIT = 0xE0, + MACHINE_STATE_READY = 0xE1, + MACHINE_STATE_KNIT = 0xE2, + MACHINE_STATE_TEST = 0xE3, + WRONG_MACHINE_STATE = 0xEF, + + // generic error codes + WARNING = 0xF0, // ignorable error + RECOVERABLE_ERROR = 0xF1, + CRITICAL_ERROR = 0xF2, + FATAL_ERROR = 0xF3, + UNSPECIFIED_FAILURE = 0xFF +}; +using Err_t = enum ErrorCode; + // API constants constexpr uint8_t INDSTATE_LEN = 9U; -constexpr uint8_t REQLINE_LEN = 2U; +constexpr uint8_t REQLINE_LEN = 3U; class ComInterface { public: @@ -80,7 +124,8 @@ class ComInterface { virtual void send(uint8_t *payload, size_t length) = 0; virtual void sendMsg(AYAB_API_t id, const char *msg) = 0; virtual void sendMsg(AYAB_API_t id, char *msg) = 0; - virtual void send_reqLine(const uint8_t lineNumber) = 0; + virtual void send_reqLine(const uint8_t lineNumber, + Err_t error = SUCCESS) = 0; virtual void send_indState(Carriage_t carriage, uint8_t position, const bool initState = false) = 0; virtual void onPacketReceived(const uint8_t *buffer, size_t size) = 0; @@ -105,7 +150,7 @@ class GlobalCom final { static void send(uint8_t *payload, size_t length); static void sendMsg(AYAB_API_t id, const char *msg); static void sendMsg(AYAB_API_t id, char *msg); - static void send_reqLine(const uint8_t lineNumber); + static void send_reqLine(const uint8_t lineNumber, Err_t error = SUCCESS); static void send_indState(Carriage_t carriage, uint8_t position, const bool initState = false); static void onPacketReceived(const uint8_t *buffer, size_t size); @@ -121,7 +166,7 @@ class Com : public ComInterface { void send(uint8_t *payload, size_t length); void sendMsg(AYAB_API_t id, const char *msg); void sendMsg(AYAB_API_t id, char *msg); - void send_reqLine(const uint8_t lineNumber); + void send_reqLine(const uint8_t lineNumber, Err_t error = SUCCESS); void send_indState(Carriage_t carriage, uint8_t position, const bool initState = false); void onPacketReceived(const uint8_t *buffer, size_t size); @@ -136,6 +181,10 @@ class Com : public ComInterface { void h_reqInfo(); void h_reqTest(const uint8_t *buffer, size_t size); void h_unrecognized(); + + void send_cnfInfo(); + void send_cnfStart(Err_t error); + void send_cnfTest(Err_t error); #ifdef AYAB_ENABLE_CRC uint8_t CRC8(const uint8_t *buffer, size_t len); #endif diff --git a/src/ayab/global_com.cpp b/src/ayab/global_com.cpp index 997b9a69d..6265b9c4d 100644 --- a/src/ayab/global_com.cpp +++ b/src/ayab/global_com.cpp @@ -54,8 +54,8 @@ void GlobalCom::onPacketReceived(const uint8_t *buffer, size_t size) { } // GCOVR_EXCL_STOP -void GlobalCom::send_reqLine(const uint8_t lineNumber) { - m_instance->send_reqLine(lineNumber); +void GlobalCom::send_reqLine(const uint8_t lineNumber, Err_t error) { + m_instance->send_reqLine(lineNumber, error); } void GlobalCom::send_indState(Carriage_t carriage, uint8_t position, diff --git a/src/ayab/global_knitter.cpp b/src/ayab/global_knitter.cpp index 64c3c4944..6f45c3d8c 100644 --- a/src/ayab/global_knitter.cpp +++ b/src/ayab/global_knitter.cpp @@ -41,9 +41,9 @@ void GlobalKnitter::isr() { } #endif -bool GlobalKnitter::startKnitting(Machine_t machineType, uint8_t startNeedle, - uint8_t stopNeedle, uint8_t *pattern_start, - bool continuousReportingEnabled) { +Err_t GlobalKnitter::startKnitting(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled) { return m_instance->startKnitting(machineType, startNeedle, stopNeedle, pattern_start, continuousReportingEnabled); } diff --git a/src/ayab/global_tester.cpp b/src/ayab/global_tester.cpp index 3baa5b4f8..87d3a1d76 100644 --- a/src/ayab/global_tester.cpp +++ b/src/ayab/global_tester.cpp @@ -26,7 +26,7 @@ // static member functions -bool GlobalTester::startTest(Machine_t machineType) { +Err_t GlobalTester::startTest(Machine_t machineType) { return m_instance->startTest(machineType); } diff --git a/src/ayab/knitter.cpp b/src/ayab/knitter.cpp index 2a58e40d5..599d67700 100644 --- a/src/ayab/knitter.cpp +++ b/src/ayab/knitter.cpp @@ -117,15 +117,28 @@ void Knitter::isr() { /*! * \brief Enter knit state. + * + * Note (August 2020): the return value of this function has changed. + * Previously, it returned `true` for success and `false` for failure. + * Now, it returns `0` for success and an informative error code otherwise. */ -bool Knitter::startKnitting(Machine_t machineType, uint8_t startNeedle, - uint8_t stopNeedle, uint8_t *pattern_start, - bool continuousReportingEnabled) { - if ((GlobalFsm::getState() != s_ready) || (machineType == NoMachine) || - (machineType >= NUM_MACHINES) || (pattern_start == nullptr) || - (startNeedle >= stopNeedle) || (stopNeedle >= NUM_NEEDLES[machineType])) { - // TODO(TP): error code - return false; +Err_t Knitter::startKnitting(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled) { + if (GlobalFsm::getState() != s_ready) { + return WRONG_MACHINE_STATE; + } + if (machineType == NoMachine) { + return NO_MACHINE_TYPE; + } + if (machineType >= NUM_MACHINES) { + return MACHINE_TYPE_INVALID; + } + if (pattern_start == nullptr) { + return NULL_POINTER_ARGUMENT; + } + if (startNeedle >= stopNeedle || stopNeedle >= NUM_NEEDLES[machineType]) { + return NEEDLE_VALUE_INVALID; } // record argument values @@ -149,7 +162,7 @@ bool Knitter::startKnitting(Machine_t machineType, uint8_t startNeedle, GlobalBeeper::ready(); // success - return true; + return SUCCESS; } // used in hardware test loop @@ -279,7 +292,7 @@ uint8_t Knitter::getStartOffset(const Direction_t direction) { if ((direction == NoDirection) || (direction >= NUM_DIRECTIONS) || (m_carriage == NoCarriage) || (m_carriage >= NUM_CARRIAGES) || (m_machineType == NoMachine) || (m_machineType >= NUM_MACHINES)) { - // TODO(TP): return error state + // TODO(TP): return error state? return 0U; } return START_OFFSET[m_machineType][direction][m_carriage]; @@ -313,7 +326,7 @@ void Knitter::setMachineType(Machine_t machineType) { // private methods void Knitter::reqLine(uint8_t lineNumber) { - GlobalCom::send_reqLine(lineNumber); + GlobalCom::send_reqLine(lineNumber, SUCCESS); m_lineRequested = true; } diff --git a/src/ayab/knitter.h b/src/ayab/knitter.h index 8e4f26d63..f578bf700 100644 --- a/src/ayab/knitter.h +++ b/src/ayab/knitter.h @@ -38,9 +38,9 @@ class KnitterInterface { virtual void init() = 0; virtual void setUpInterrupt() = 0; virtual void isr() = 0; - virtual bool startKnitting(Machine_t machineType, uint8_t startNeedle, - uint8_t stopNeedle, uint8_t *pattern_start, - bool continuousReportingEnabled) = 0; + virtual Err_t startKnitting(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled) = 0; virtual void encodePosition() = 0; virtual bool isReady() = 0; virtual void knit() = 0; @@ -71,9 +71,9 @@ class GlobalKnitter final { #ifndef AYAB_TESTS static void isr(); #endif - static bool startKnitting(Machine_t machineType, uint8_t startNeedle, - uint8_t stopNeedle, uint8_t *pattern_start, - bool continuousReportingEnabled); + static Err_t startKnitting(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled); static void encodePosition(); static bool isReady(); static void knit(); @@ -111,9 +111,9 @@ class Knitter : public KnitterInterface { void init(); void setUpInterrupt(); void isr(); - bool startKnitting(Machine_t machineType, uint8_t startNeedle, - uint8_t stopNeedle, uint8_t *pattern_start, - bool continuousReportingEnabled); + Err_t startKnitting(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled); void encodePosition(); bool isReady(); void knit(); diff --git a/src/ayab/tester.cpp b/src/ayab/tester.cpp index 29f334188..1cc239c3d 100644 --- a/src/ayab/tester.cpp +++ b/src/ayab/tester.cpp @@ -155,16 +155,23 @@ void Tester::quitCmd() { GlobalKnitter::setUpInterrupt(); } -bool Tester::startTest(Machine_t machineType) { - bool success = false; +/*! + * \brief Start hardware test. + * + * Note (August 2020): the return value of this function has changed. + * Previously, it returned `true` for success and `false` for failure. + * Now, it returns `0` for success and an informative error code otherwise. + */ +Err_t Tester::startTest(Machine_t machineType) { OpState_t currentState = GlobalFsm::getState(); if (s_init == currentState || s_ready == currentState) { GlobalFsm::setState(s_test); GlobalKnitter::setMachineType(machineType); setUp(); - success = true; + return SUCCESS; } - return success; + // TODO(TP): return informative error code. + return WRONG_MACHINE_STATE; } /*! diff --git a/src/ayab/tester.h b/src/ayab/tester.h index 4a9990f72..3edc5a873 100644 --- a/src/ayab/tester.h +++ b/src/ayab/tester.h @@ -27,6 +27,7 @@ //#include #include "beeper.h" +#include "com.h" #include "encoders.h" constexpr uint8_t BUFFER_LEN = 40; @@ -36,7 +37,7 @@ class TesterInterface { virtual ~TesterInterface(){}; // any methods that need to be mocked should go here - virtual bool startTest(Machine_t machineType) = 0; + virtual Err_t startTest(Machine_t machineType) = 0; virtual void loop() = 0; virtual bool getQuitFlag() = 0; virtual void helpCmd() = 0; @@ -70,7 +71,7 @@ class GlobalTester final { // pointer to global instance whose methods are implemented static TesterInterface *m_instance; - static bool startTest(Machine_t machineType); + static Err_t startTest(Machine_t machineType); static void loop(); static bool getQuitFlag(); static void helpCmd(); @@ -102,7 +103,7 @@ class Tester : public TesterInterface { #endif public: - bool startTest(Machine_t machineType); + Err_t startTest(Machine_t machineType); void loop(); bool getQuitFlag(); void helpCmd(); diff --git a/test/mocks/com_mock.cpp b/test/mocks/com_mock.cpp index 614e81478..cd976332e 100644 --- a/test/mocks/com_mock.cpp +++ b/test/mocks/com_mock.cpp @@ -65,9 +65,9 @@ void Com::sendMsg(AYAB_API_t id, char *msg) { gComMock->sendMsg(id, msg); } -void Com::send_reqLine(const uint8_t lineNumber) { +void Com::send_reqLine(const uint8_t lineNumber, Err_t error) { assert(gComMock != nullptr); - gComMock->send_reqLine(lineNumber); + gComMock->send_reqLine(lineNumber, error); } void Com::send_indState(Carriage_t carriage, uint8_t position, diff --git a/test/mocks/com_mock.h b/test/mocks/com_mock.h index 1d0405aab..51fa65c5c 100644 --- a/test/mocks/com_mock.h +++ b/test/mocks/com_mock.h @@ -35,7 +35,7 @@ class ComMock : public ComInterface { MOCK_METHOD2(send, void(uint8_t *payload, size_t length)); MOCK_METHOD2(sendMsg, void(AYAB_API_t id, const char *msg)); MOCK_METHOD2(sendMsg, void(AYAB_API_t id, char *msg)); - MOCK_METHOD1(send_reqLine, void(const uint8_t lineNumber)); + MOCK_METHOD2(send_reqLine, void(const uint8_t lineNumber, Err_t error)); MOCK_METHOD3(send_indState, void(Carriage_t carriage, uint8_t position, const bool initState)); MOCK_METHOD2(onPacketReceived, void(const uint8_t *buffer, size_t size)); diff --git a/test/mocks/knitter_mock.cpp b/test/mocks/knitter_mock.cpp index eb05351bc..8372883e2 100644 --- a/test/mocks/knitter_mock.cpp +++ b/test/mocks/knitter_mock.cpp @@ -54,9 +54,9 @@ void Knitter::isr() { gKnitterMock->isr(); } -bool Knitter::startKnitting(Machine_t machineType, uint8_t startNeedle, - uint8_t stopNeedle, uint8_t *pattern_start, - bool continuousReportingEnabled) { +Err_t Knitter::startKnitting(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled) { assert(gKnitterMock != NULL); return gKnitterMock->startKnitting(machineType, startNeedle, stopNeedle, pattern_start, continuousReportingEnabled); diff --git a/test/mocks/knitter_mock.h b/test/mocks/knitter_mock.h index 115b7524e..fc6b6d34b 100644 --- a/test/mocks/knitter_mock.h +++ b/test/mocks/knitter_mock.h @@ -32,9 +32,9 @@ class KnitterMock : public KnitterInterface { MOCK_METHOD0(init, void()); MOCK_METHOD0(setUpInterrupt, void()); MOCK_METHOD0(isr, void()); - MOCK_METHOD5(startKnitting, bool(Machine_t machineType, uint8_t startNeedle, - uint8_t stopNeedle, uint8_t *pattern_start, - bool continuousReportingEnabled)); + MOCK_METHOD5(startKnitting, Err_t(Machine_t machineType, uint8_t startNeedle, + uint8_t stopNeedle, uint8_t *pattern_start, + bool continuousReportingEnabled)); MOCK_METHOD0(encodePosition, void()); MOCK_METHOD0(isReady, bool()); MOCK_METHOD0(knit, void()); diff --git a/test/mocks/tester_mock.cpp b/test/mocks/tester_mock.cpp index 448329f84..4bd6acd9a 100644 --- a/test/mocks/tester_mock.cpp +++ b/test/mocks/tester_mock.cpp @@ -39,7 +39,7 @@ void releaseTesterMock() { } } -bool Tester::startTest(Machine_t machineType) { +Err_t Tester::startTest(Machine_t machineType) { assert(gTesterMock != NULL); return gTesterMock->startTest(machineType); } diff --git a/test/mocks/tester_mock.h b/test/mocks/tester_mock.h index 8cc5114cb..3e937a6ae 100644 --- a/test/mocks/tester_mock.h +++ b/test/mocks/tester_mock.h @@ -29,7 +29,7 @@ class TesterMock : public TesterInterface { public: - MOCK_METHOD1(startTest, bool(Machine_t machineType)); + MOCK_METHOD1(startTest, Err_t(Machine_t machineType)); MOCK_METHOD0(loop, void()); MOCK_METHOD0(getQuitFlag, bool()); MOCK_METHOD0(helpCmd, void()); diff --git a/test/test_com.cpp b/test/test_com.cpp index 394dfab73..7f3e3315d 100644 --- a/test/test_com.cpp +++ b/test/test_com.cpp @@ -30,6 +30,7 @@ #include using ::testing::_; +using ::testing::AtLeast; using ::testing::Mock; using ::testing::Return; @@ -82,21 +83,30 @@ TEST_F(ComTest, test_API) { TEST_F(ComTest, test_reqtest_fail) { // no machineType uint8_t buffer[] = {reqTest_msgid}; + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); EXPECT_CALL(*fsmMock, setState(s_test)).Times(0); com->onPacketReceived(buffer, sizeof(buffer)); ASSERT_TRUE(Mock::VerifyAndClear(fsmMock)); } -TEST_F(ComTest, test_reqtest_success) { +TEST_F(ComTest, test_reqtest_success_KH270) { uint8_t buffer[] = {reqTest_msgid, Kh270}; + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); EXPECT_CALL(*fsmMock, setState(s_test)); + EXPECT_CALL(*knitterMock, setMachineType(Kh270)); + EXPECT_CALL(*arduinoMock, millis); com->onPacketReceived(buffer, sizeof(buffer)); ASSERT_TRUE(Mock::VerifyAndClear(fsmMock)); + ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); } TEST_F(ComTest, test_reqstart_fail1) { // checksum wrong uint8_t buffer[] = {reqStart_msgid, 0, 0, 10, 1, 0x73}; + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); EXPECT_CALL(*knitterMock, startKnitting).Times(0); com->onPacketReceived(buffer, sizeof(buffer)); ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); @@ -105,6 +115,8 @@ TEST_F(ComTest, test_reqstart_fail1) { TEST_F(ComTest, test_reqstart_fail2) { // not enough bytes uint8_t buffer[] = {reqStart_msgid, 0, 0, 10, 1, 0x74}; + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); EXPECT_CALL(*knitterMock, startKnitting).Times(0); com->onPacketReceived(buffer, sizeof(buffer) - 1); ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); @@ -112,6 +124,8 @@ TEST_F(ComTest, test_reqstart_fail2) { TEST_F(ComTest, test_reqstart_success_KH910) { uint8_t buffer[] = {reqStart_msgid, 0, 0, 10, 1, 0x74}; + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); EXPECT_CALL(*knitterMock, startKnitting); com->onPacketReceived(buffer, sizeof(buffer)); ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); @@ -119,6 +133,8 @@ TEST_F(ComTest, test_reqstart_success_KH910) { TEST_F(ComTest, test_reqstart_success_KH270) { uint8_t buffer[] = {reqStart_msgid, 2, 0, 10, 1, 0x73}; + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); EXPECT_CALL(*knitterMock, startKnitting); com->onPacketReceived(buffer, sizeof(buffer)); ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); @@ -126,51 +142,77 @@ TEST_F(ComTest, test_reqstart_success_KH270) { TEST_F(ComTest, test_reqinfo) { uint8_t buffer[] = {reqInfo_msgid}; + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); com->onPacketReceived(buffer, sizeof(buffer)); } TEST_F(ComTest, test_helpCmd) { uint8_t buffer[] = {helpCmd_msgid}; + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); com->onPacketReceived(buffer, sizeof(buffer)); } TEST_F(ComTest, test_sendCmd) { uint8_t buffer[] = {sendCmd_msgid}; + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); com->onPacketReceived(buffer, sizeof(buffer)); } TEST_F(ComTest, test_beepCmd) { uint8_t buffer[] = {beepCmd_msgid}; + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); + EXPECT_CALL(*arduinoMock, analogWrite(PIEZO_PIN, _)).Times(AtLeast(1)); com->onPacketReceived(buffer, sizeof(buffer)); } TEST_F(ComTest, test_setSingleCmd) { uint8_t buffer[] = {setSingleCmd_msgid, 0, 0}; + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); com->onPacketReceived(buffer, sizeof(buffer)); } TEST_F(ComTest, test_setAllCmd) { uint8_t buffer[] = {setAllCmd_msgid, 0, 0}; + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); com->onPacketReceived(buffer, sizeof(buffer)); } TEST_F(ComTest, test_readEOLsensorsCmd) { uint8_t buffer[] = {readEOLsensorsCmd_msgid}; + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); + EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)); + EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_R)); com->onPacketReceived(buffer, sizeof(buffer)); } TEST_F(ComTest, test_readEncodersCmd) { uint8_t buffer[] = {readEncodersCmd_msgid}; + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); + EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_A)); + EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_B)); + EXPECT_CALL(*arduinoMock, digitalRead(ENC_PIN_C)); com->onPacketReceived(buffer, sizeof(buffer)); } TEST_F(ComTest, test_autoReadCmd) { uint8_t buffer[] = {autoReadCmd_msgid}; + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); com->onPacketReceived(buffer, sizeof(buffer)); } TEST_F(ComTest, test_autoTestCmd) { uint8_t buffer[] = {autoTestCmd_msgid}; + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); com->onPacketReceived(buffer, sizeof(buffer)); } @@ -181,7 +223,9 @@ TEST_F(ComTest, test_stopCmd) { TEST_F(ComTest, test_quitCmd) { uint8_t buffer[] = {quitCmd_msgid}; + EXPECT_CALL(*knitterMock, setUpInterrupt); com->onPacketReceived(buffer, sizeof(buffer)); + ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); } TEST_F(ComTest, test_unrecognized) { @@ -198,10 +242,10 @@ TEST_F(ComTest, test_cnfline_kh910) { 0, 0, 1, - 0xde, - 0xad, - 0xbe, - 0xef, + 0xDE, + 0xAD, + 0xBE, + 0xEF, 0x00, 0x00, 0x00, @@ -223,7 +267,7 @@ TEST_F(ComTest, test_cnfline_kh910) { 0x00, 0x00, 0x00, - 0xa7}; // CRC8 + 0xA7}; // CRC8 // start KH910 job knitterMock->startKnitting(Kh910, 0, 199, pattern, false); @@ -240,7 +284,7 @@ TEST_F(ComTest, test_cnfline_kh910) { // not last line buffer[3] = 0x00; - buffer[29] = 0xc0; + buffer[29] = 0xC0; EXPECT_CALL(*knitterMock, setNextLine).WillOnce(Return(true)); EXPECT_CALL(*knitterMock, setLastLine).Times(0); com->onPacketReceived(buffer, sizeof(buffer)); @@ -266,7 +310,7 @@ TEST_F(ComTest, test_cnfline_kh270) { // CRC8 calculated with // http://tomeko.net/online_tools/crc8.php?lang=en uint8_t buffer[20] = {cnfLine_msgid, 0, 0, 1, - 0xde, 0xad, 0xbe, 0xef, 0x00, + 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab}; // CRC8 diff --git a/test/test_fsm.cpp b/test/test_fsm.cpp index c0d403278..5a719fcb3 100644 --- a/test/test_fsm.cpp +++ b/test/test_fsm.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -37,6 +38,7 @@ using ::testing::Test; extern Fsm *fsm; extern Knitter *knitter; +extern BeeperMock *beeper; extern ComMock *com; extern SolenoidsMock *solenoids; extern TesterMock *tester; @@ -47,6 +49,7 @@ class FsmTest : public ::testing::Test { arduinoMock = arduinoMockInstance(); // pointers to global instances + beeperMock = beeper; comMock = com; solenoidsMock = solenoids; testerMock = tester; @@ -54,6 +57,7 @@ class FsmTest : public ::testing::Test { // The global instance does not get destroyed at the end of each test. // Ordinarily the mock instance would be local and such behaviour would // cause a memory leak. We must notify the test that this is not the case. + Mock::AllowLeak(beeperMock); Mock::AllowLeak(comMock); Mock::AllowLeak(solenoidsMock); Mock::AllowLeak(testerMock); @@ -64,6 +68,7 @@ class FsmTest : public ::testing::Test { } ArduinoMock *arduinoMock; + BeeperMock *beeperMock; ComMock *comMock; SolenoidsMock *solenoidsMock; TesterMock *testerMock; @@ -150,9 +155,12 @@ TEST_F(FsmTest, test_dispatch_knit) { fsm->setState(s_knit); EXPECT_CALL(*arduinoMock, digitalWrite(LED_PIN_A, 1)); EXPECT_CALL(*comMock, update); + EXPECT_CALL(*comMock, send_reqLine); + EXPECT_CALL(*beeperMock, finishedLine); fsm->dispatch(); // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(beeperMock)); ASSERT_TRUE(Mock::VerifyAndClear(comMock)); } diff --git a/test/test_knitter.cpp b/test/test_knitter.cpp index 74cab462a..60741c216 100644 --- a/test/test_knitter.cpp +++ b/test/test_knitter.cpp @@ -219,8 +219,17 @@ TEST_F(KnitterTest, test_startKnitting_NoMachine) { Machine_t m = knitter->getMachineType(); ASSERT_EQ(m, NoMachine); fsm->setState(s_ready); - ASSERT_EQ(knitter->startKnitting(m, 0, NUM_NEEDLES[m] - 1, pattern, false), - false); + ASSERT_TRUE( + knitter->startKnitting(m, 0, NUM_NEEDLES[m] - 1, pattern, false) != 0); + + // test expectations without destroying instance + ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); +} + +TEST_F(KnitterTest, test_startKnitting_invalidMachine) { + uint8_t pattern[] = {1}; + fsm->setState(s_ready); + ASSERT_TRUE(knitter->startKnitting(NUM_MACHINES, 0, 1, pattern, false) != 0); // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); @@ -229,9 +238,8 @@ TEST_F(KnitterTest, test_startKnitting_NoMachine) { TEST_F(KnitterTest, test_startKnitting_notReady) { uint8_t pattern[] = {1}; // fsm->setState(s_init); - ASSERT_EQ( - knitter->startKnitting(Kh910, 0, NUM_NEEDLES[Kh910] - 1, pattern, false), - false); + ASSERT_TRUE(knitter->startKnitting(Kh910, 0, NUM_NEEDLES[Kh910] - 1, pattern, + false) != 0); // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); @@ -242,9 +250,8 @@ TEST_F(KnitterTest, test_startKnitting_Kh910) { get_to_ready(); EXPECT_CALL(*encodersMock, init); EXPECT_CALL(*beeperMock, ready); - ASSERT_EQ( - knitter->startKnitting(Kh910, 0, NUM_NEEDLES[Kh910] - 1, pattern, false), - true); + ASSERT_TRUE(knitter->startKnitting(Kh910, 0, NUM_NEEDLES[Kh910] - 1, pattern, + false) == 0); // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); @@ -258,9 +265,8 @@ TEST_F(KnitterTest, test_startKnitting_Kh270) { get_to_ready(); EXPECT_CALL(*encodersMock, init); EXPECT_CALL(*beeperMock, ready); - ASSERT_EQ( - knitter->startKnitting(Kh270, 0, NUM_NEEDLES[Kh270] - 1, pattern, false), - true); + ASSERT_TRUE(knitter->startKnitting(Kh270, 0, NUM_NEEDLES[Kh270] - 1, pattern, + false) == 0); // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); @@ -274,17 +280,15 @@ TEST_F(KnitterTest, test_startKnitting_failures) { get_to_ready(); // `m_stopNeedle` lower than `m_startNeedle` - ASSERT_EQ(knitter->startKnitting(Kh910, 1, 0, pattern, false), false); + ASSERT_TRUE(knitter->startKnitting(Kh910, 1, 0, pattern, false) != 0); // `m_stopNeedle` out of range - ASSERT_EQ( - knitter->startKnitting(Kh910, 0, NUM_NEEDLES[Kh910], pattern, false), - false); + ASSERT_TRUE(knitter->startKnitting(Kh910, 0, NUM_NEEDLES[Kh910], pattern, + false) != 0); // null pattern - ASSERT_EQ( - knitter->startKnitting(Kh910, 0, NUM_NEEDLES[Kh910] - 1, nullptr, false), - false); + ASSERT_TRUE(knitter->startKnitting(Kh910, 0, NUM_NEEDLES[Kh910] - 1, nullptr, + false) != 0); // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(solenoidsMock)); diff --git a/test/test_tester.cpp b/test/test_tester.cpp index 361505d4d..a2362b837 100644 --- a/test/test_tester.cpp +++ b/test/test_tester.cpp @@ -29,6 +29,7 @@ #include #include +using ::testing::_; using ::testing::An; using ::testing::AtLeast; using ::testing::Mock; @@ -70,6 +71,8 @@ class TesterTest : public ::testing::Test { TEST_F(TesterTest, test_setUp) { EXPECT_CALL(*arduinoMock, millis); + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); tester->setUp(); ASSERT_FALSE(tester->m_autoReadOn); ASSERT_FALSE(tester->m_autoTestOn); @@ -78,70 +81,97 @@ TEST_F(TesterTest, test_setUp) { } TEST_F(TesterTest, test_helpCmd) { + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); tester->helpCmd(); } TEST_F(TesterTest, test_sendCmd) { + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); tester->sendCmd(); } TEST_F(TesterTest, test_beepCmd) { - EXPECT_CALL(*arduinoMock, analogWrite).Times(AtLeast(1)); - EXPECT_CALL(*arduinoMock, delay).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); + EXPECT_CALL(*arduinoMock, analogWrite(PIEZO_PIN, _)).Times(AtLeast(1)); tester->beepCmd(); } TEST_F(TesterTest, test_setSingleCmd_fail1) { const uint8_t buf[] = {setSingleCmd_msgid, 0}; + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); tester->setSingleCmd(buf, 2); } TEST_F(TesterTest, test_setSingleCmd_fail2) { const uint8_t buf[] = {setSingleCmd_msgid, 16, 0}; + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); tester->setSingleCmd(buf, 3); } TEST_F(TesterTest, test_setSingleCmd_fail3) { const uint8_t buf[] = {setSingleCmd_msgid, 15, 2}; + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); tester->setSingleCmd(buf, 3); } TEST_F(TesterTest, test_setSingleCmd_success) { const uint8_t buf[] = {setSingleCmd_msgid, 15, 1}; + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); tester->setSingleCmd(buf, 3); } TEST_F(TesterTest, test_setAllCmd_fail1) { const uint8_t buf[] = {setAllCmd_msgid, 0}; + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); tester->setAllCmd(buf, 2); } TEST_F(TesterTest, test_setAllCmd_success) { const uint8_t buf[] = {setAllCmd_msgid, 0xff, 0xff}; + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); tester->setAllCmd(buf, 3); } TEST_F(TesterTest, test_readEOLsensorsCmd) { + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)); EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_R)); tester->readEOLsensorsCmd(); } TEST_F(TesterTest, test_readEncodersCmd_low) { + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); EXPECT_CALL(*arduinoMock, digitalRead).WillRepeatedly(Return(LOW)); tester->readEncodersCmd(); } TEST_F(TesterTest, test_readEncodersCmd_high) { + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); EXPECT_CALL(*arduinoMock, digitalRead).WillRepeatedly(Return(HIGH)); tester->readEncodersCmd(); } TEST_F(TesterTest, test_autoReadCmd) { + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); tester->autoReadCmd(); } TEST_F(TesterTest, test_autoTestCmd) { + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); tester->autoTestCmd(); } @@ -182,6 +212,8 @@ TEST_F(TesterTest, test_loop_autoTestEven) { tester->m_timerEventOdd = false; tester->m_autoReadOn = true; tester->m_autoTestOn = true; + EXPECT_CALL(*serialMock, write(_, _)); + EXPECT_CALL(*serialMock, write(SLIP::END)); EXPECT_CALL(*arduinoMock, digitalRead).Times(0); EXPECT_CALL(*arduinoMock, digitalWrite).Times(2); tester->loop(); @@ -193,6 +225,8 @@ TEST_F(TesterTest, test_loop_autoTestOdd) { tester->m_timerEventOdd = true; tester->m_autoReadOn = true; tester->m_autoTestOn = true; + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_L)); EXPECT_CALL(*arduinoMock, analogRead(EOL_PIN_R)); EXPECT_CALL(*arduinoMock, digitalRead).Times(3); @@ -203,7 +237,7 @@ TEST_F(TesterTest, test_loop_autoTestOdd) { TEST_F(TesterTest, test_startTest_fail) { // can't start test from state `s_knit` EXPECT_CALL(*fsmMock, getState).WillOnce(Return(s_knit)); - ASSERT_EQ(tester->startTest(Kh910), false); + ASSERT_TRUE(tester->startTest(Kh910) != 0); // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(fsmMock)); @@ -211,8 +245,14 @@ TEST_F(TesterTest, test_startTest_fail) { TEST_F(TesterTest, test_startTest_success) { EXPECT_CALL(*fsmMock, getState).WillOnce(Return(s_ready)); - ASSERT_EQ(tester->startTest(Kh930), true); + EXPECT_CALL(*fsmMock, setState(s_test)); + EXPECT_CALL(*knitterMock, setMachineType(Kh930)); + EXPECT_CALL(*serialMock, write(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*serialMock, write(SLIP::END)).Times(AtLeast(1)); + EXPECT_CALL(*arduinoMock, millis); + ASSERT_TRUE(tester->startTest(Kh930) == 0); // test expectations without destroying instance ASSERT_TRUE(Mock::VerifyAndClear(fsmMock)); + ASSERT_TRUE(Mock::VerifyAndClear(knitterMock)); } From fe48c26a4c772f24d31adcf4105020e4c088cf6e Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 23 Aug 2020 20:04:40 -0400 Subject: [PATCH 15/17] Remove SerialCommand dependency. --- .gitmodules | 3 -- libraries/SerialCommand | 1 - src/ayab/Makefile | 2 +- static_analysis.sh | 2 +- test/mocks/SerialCommand_mock.cpp | 63 ------------------------------- test/mocks/SerialCommand_mock.h | 40 -------------------- 6 files changed, 2 insertions(+), 109 deletions(-) delete mode 160000 libraries/SerialCommand delete mode 100644 test/mocks/SerialCommand_mock.cpp delete mode 100644 test/mocks/SerialCommand_mock.h diff --git a/.gitmodules b/.gitmodules index e3e059ec9..00bc255d4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "libraries/PacketSerial"] path = libraries/PacketSerial url = https://github.com/bakercp/PacketSerial.git -[submodule "libraries/SerialCommand"] - path = libraries/SerialCommand - url = https://github.com/kroimon/Arduino-SerialCommand.git [submodule "libraries/SoftI2CMaster"] path = libraries/SoftI2CMaster url = https://github.com/todbot/SoftI2CMaster.git diff --git a/libraries/SerialCommand b/libraries/SerialCommand deleted file mode 160000 index 76ebd2d60..000000000 --- a/libraries/SerialCommand +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 76ebd2d60ef07071384930d3d92414b4131bf657 diff --git a/src/ayab/Makefile b/src/ayab/Makefile index 4e66baa44..2a4c4c5cf 100644 --- a/src/ayab/Makefile +++ b/src/ayab/Makefile @@ -6,7 +6,7 @@ PROJECT_DIR = $(shell dirname $(shell pwd))/.. $(info $(PROJECT_DIR)) -ARDUINO_LIBS = Wire Adafruit_MCP23008 SoftI2CMaster PacketSerial SerialCommand +ARDUINO_LIBS = Wire Adafruit_MCP23008 SoftI2CMaster PacketSerial USER_LIB_PATH = $(realpath $(PROJECT_DIR)/libraries) TARGET = ayab diff --git a/static_analysis.sh b/static_analysis.sh index 9baab9a1f..ea51a79a7 100755 --- a/static_analysis.sh +++ b/static_analysis.sh @@ -1,2 +1,2 @@ #!/bin/bash -clang-tidy src/ayab/$1.cpp --fix -- -isystem /usr/share/arduino/hardware/arduino/cores/arduino/ -isystem /usr/lib/avr/include/ -isystem /usr/share/arduino/hardware/arduino/variants/standard -isystem libraries/SerialCommand/ -isystem libraries/Adafruit_MCP23008/ -isystem /usr/share/arduino/libraries/Wire/ -isystem libraries/SoftI2CMaster/ -isystem libraries/PacketSerial/src/ -DCLANG_TIDY "${@:2}" +clang-tidy src/ayab/$1.cpp --fix -- -isystem /usr/share/arduino/hardware/arduino/cores/arduino/ -isystem /usr/lib/avr/include/ -isystem /usr/share/arduino/hardware/arduino/variants/standard -isystem libraries/Adafruit_MCP23008/ -isystem /usr/share/arduino/libraries/Wire/ -isystem libraries/SoftI2CMaster/ -isystem libraries/PacketSerial/src/ -DCLANG_TIDY "${@:2}" diff --git a/test/mocks/SerialCommand_mock.cpp b/test/mocks/SerialCommand_mock.cpp deleted file mode 100644 index 278698817..000000000 --- a/test/mocks/SerialCommand_mock.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/*!` - * \file SerialCommand_mock.cpp - * - * This file is part of AYAB. - * - * AYAB is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * AYAB is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with AYAB. If not, see . - * - * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller - * Modified Work Copyright 2020 Sturla Lange, Tom Price - * http://ayab-knitting.com - */ - -#include -#include - -static SerialCommandMock *gSerialCommandMock = NULL; -SerialCommandMock *serialCommandMockInstance() { - if (!gSerialCommandMock) { - gSerialCommandMock = new SerialCommandMock(); - } - return gSerialCommandMock; -} - -void releaseSerialCommandMock() { - if (gSerialCommandMock) { - delete gSerialCommandMock; - gSerialCommandMock = NULL; - } -} - -SerialCommand::SerialCommand() { -} - -char *SerialCommand::next() { - assert(gSerialCommandMock != nullptr); - return gSerialCommandMock->next(); -} - -void SerialCommand::addCommand(const char *command, void (*function)()) { - assert(gSerialCommandMock != nullptr); - gSerialCommandMock->addCommand(command, function); -} - -void SerialCommand::setDefaultHandler(void (*function)(const char *command)) { - assert(gSerialCommandMock != nullptr); - gSerialCommandMock->setDefaultHandler(function); -} - -void SerialCommand::readSerial() { - assert(gSerialCommandMock != nullptr); - gSerialCommandMock->readSerial(); -} diff --git a/test/mocks/SerialCommand_mock.h b/test/mocks/SerialCommand_mock.h deleted file mode 100644 index b4c52ba8e..000000000 --- a/test/mocks/SerialCommand_mock.h +++ /dev/null @@ -1,40 +0,0 @@ -/*!` - * \file SerialCommand_mock.h - * - * This file is part of AYAB. - * - * AYAB is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * AYAB is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with AYAB. If not, see . - * - * Original Work Copyright 2013 Christian Obersteiner, Andreas Müller - * Modified Work Copyright 2020 Sturla Lange, Tom Price - * http://ayab-knitting.com - */ - -#ifndef SERIAL_COMMAND_MOCK_H_ -#define SERIAL_COMMAND_MOCK_H_ - -#include - -class SerialCommandMock { -public: - MOCK_METHOD0(next, char *()); - MOCK_METHOD2(addCommand, void(const char *command, void (*function)())); - MOCK_METHOD1(setDefaultHandler, void(void (*function)(const char *command))); - MOCK_METHOD0(readSerial, void()); -}; - -SerialCommandMock *serialCommandMockInstance(); -void releaseSerialCommandMock(); - -#endif // SERIAL_COMMAND_MOCK_H_ From 4e5a447e2f00dced5c43c4210c05b7f1c7c919e2 Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 23 Aug 2020 20:10:26 -0400 Subject: [PATCH 16/17] Update CHANGELOG.md --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86442e299..a3c132a36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 1.0.0 / Unreleased +* Migrate to AYAB API v6 +* Remove dependency on SerialCommand library +* Add informative error codes +* Allow carriage to start on the right-hand side moving left +* Add run-time hardware tests * Migrate to generic firmware from machine-specific versions * Change libraries to submodules * Add unit tests that can run in the absence of the hardware -* Add GPLv3 license for the repository, and LGPLv2.1 for the SerialCommand library +* Add GPLv3 license * Add development environment documentation to README * Add firmware update instructions to README * Add CHANGELOG.md From 7ac33140a7f1f8c200b229732cdf78a59400ae39 Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 24 Aug 2020 02:52:19 -0400 Subject: [PATCH 17/17] Update FSM documentation. --- doc/finite_state_machine.md | 40 +++++++++++++++++-------------------- src/ayab/main.cpp | 10 +++++++--- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/doc/finite_state_machine.md b/doc/finite_state_machine.md index a7c7643b6..3ba18f648 100644 --- a/doc/finite_state_machine.md +++ b/doc/finite_state_machine.md @@ -1,27 +1,23 @@ -# Finite State Machine +### Finite State Machine -The knitting machine finite state machine is defined in the Knitter class. +The finite state machine is defined in the `Fsm` class. -A graphical representation follows. +|State|| +--:|:-- +`Init` | Wait for carriage to be put in the correct location. +`Ready` | Wait to start operation. +`Knit` | Knitting mode. +`Test` | Hardware testing mode. +||| -\startuml -skinparam shadowing false -skinparam ArrowColor DimGray -skinparam state { - backgroundColor LightSteelBlue - BorderColor Gray -} -hide empty description +A tabular representation of state transitions follows. -Init : Wait for carriage to be put in the correct location. -Ready: Wait to start operation. -Operate: Knit. -Test: Calculate but don't exercise solenoids. +|Transition|| +--:|:-- +`Init -> Test` |`Tester::startTest()` +`Ready -> Test` |`Tester::startTest()` +`Test -> Init` | `Tester::quitCmd()` +`Init -> Ready` | `Knitter::isReady()` +`Ready -> Knit` | `Knitter::startKnitting()` +`Knit -> Ready` | `m_workedOnLine && m_lastLineFlag` -Reset --> Init -Init --> Test : startTest() -Ready --> Test : startTest() -Init -> Ready : Direction == Right\n&& HallActive == Left -Ready -> Operate : startOperation() -Operate -> Ready : lastLine()\n&& finished -\enduml diff --git a/src/ayab/main.cpp b/src/ayab/main.cpp index b7745589e..96bf39d97 100644 --- a/src/ayab/main.cpp +++ b/src/ayab/main.cpp @@ -32,8 +32,9 @@ #include "solenoids.h" #include "tester.h" -// global definitions -// references everywhere else must use `extern` +// Global definitions: references elsewhere must use `extern`. +// Each of the following is a pointer to a singleton class +// containing static methods. GlobalBeeper *beeper; GlobalCom *com; GlobalEncoders *encoders; @@ -42,7 +43,10 @@ GlobalKnitter *knitter; GlobalSolenoids *solenoids; GlobalTester *tester; -// initialize static members +// Initialize static members. +// Each singleton class contains a pointer to a static instance +// that implements a public interface. When testing, a pointer +// to an instance of a mock class can be substituted. BeeperInterface *GlobalBeeper::m_instance = new Beeper(); ComInterface *GlobalCom::m_instance = new Com(); EncodersInterface *GlobalEncoders::m_instance = new Encoders();