From a1ce5d2bf0fe33035f6b0329fb4169afb86ffbad Mon Sep 17 00:00:00 2001 From: Tom Date: Sat, 22 Aug 2020 20:44:42 -0400 Subject: [PATCH] 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)); +}