diff --git a/src/state_machine/ControllerAndSM.dia b/src/state_machine/ControllerAndSM.dia new file mode 100644 index 0000000..a2975b9 Binary files /dev/null and b/src/state_machine/ControllerAndSM.dia differ diff --git a/src/state_machine/ControllerAndSM.png b/src/state_machine/ControllerAndSM.png new file mode 100644 index 0000000..878a240 Binary files /dev/null and b/src/state_machine/ControllerAndSM.png differ diff --git a/src/state_machine/README.setup_and_run b/src/state_machine/README.setup_and_run new file mode 100644 index 0000000..368bc3d --- /dev/null +++ b/src/state_machine/README.setup_and_run @@ -0,0 +1,38 @@ +The REAME file attempts to explain how the Controller & SM operate. +Please review the UML included in this folder for further details. + +Setup phase: + +1. Create a SM: sm = new SDtateMachine(); +2. Create state/s: st = new State**(); +3. Configure the SM with initial state: +sm->setBaseState(stateNA); +4. Configure the SM by mapping actions/results +to the next state (per state): +sm->mapStateResult2NextState(stateNA, State::RET_SUCCESS, stateInit); +The above can be described as follows: +An event received while in stateNA, triggers the event method on that state, +and if it returns RET_SUCCESS, the SM current state is moved to stateInit. +Any other result value, by default keeps the current state unchanged. + +5. Create a controller: controller = new CardController(); +6. Create a card/device and bind to controller: card = new Device(controller); +7. Register the card & the SM it needs to work with to the controller: +card.devID = controller->regCard(sm); +The devID is used to identify the card in the controller. +When notifications arrive from the card, they include the devID and event, +and the controller can map the devID to its SM. + +Runtime: +1. Start the controller so it will listen to inclomming messages: +controller->run(); +Usually such a controller will run on a seperated thread, blocking on +msg_recv(); +2. The card/device sends notifications to the controllen it is bind to: +card.sendEvent(CardController::EV_EXIST); +It is usually done through a message service service: msg_send(msg); +The information sent includes the card devID and the event ID. +3. The controller receives the message, using the devID determine the SM +it belongs to and run on it the event. +(it actually executes the event method on the current state) + diff --git a/src/state_machine/card_controller.cpp b/src/state_machine/card_controller.cpp new file mode 100644 index 0000000..f65dddd --- /dev/null +++ b/src/state_machine/card_controller.cpp @@ -0,0 +1,45 @@ +/* + * card_controller.cpp + * + * Created on: Nov 16, 2014 + * Author: edwardh + */ + +#include "card_controller.h" + +#include + +#ifndef FOREVER +#define FOREVER() for(;;) +#endif + +CardController::CardController() : number_of_registered_cards(0) +{ + memset(this->cardSM, 0, MAX_SUPPORTED_CARDS); +} + +int CardController::regCard(DStateMachine *sm) +{ + this->cardSM[number_of_registered_cards] = sm; + return number_of_registered_cards++; +} + +void CardController::run() +{ +// Do here some Init stuff... + +// Enter the main loop, receive messages from Device/s (Cards), +// identify the sender by devID and lookup his SM. +// We are left with executing the event on the SM. +// +// FOREVER() +// { +// msg = recv_msg(); +// switch (msg->event) +// { +// case EV_EXIST: +// this->cardSM[msg->devID]->execEvent(...event...); +// } +// } +} + diff --git a/src/state_machine/card_controller.h b/src/state_machine/card_controller.h new file mode 100644 index 0000000..2026b6e --- /dev/null +++ b/src/state_machine/card_controller.h @@ -0,0 +1,41 @@ +/* + * card_controller.h + * + * Description: + * The controller is serving the cards (devices) by providing them means + * to register to the controller with their SM and giving each a unique id for communication. + * + * regCard() is used for card registration. + * run() is used for running the controller, usually in it's own task in an endless loop, + * blocking on recv messages. + * + * Created on: Nov 16, 2014 + * Author: edwardh + */ + +#ifndef STATE_MACHINE_CARD_CONTROLLER_H_ +#define STATE_MACHINE_CARD_CONTROLLER_H_ + +#include "dynamic_state_machine.h" + +class CardController +{ +public: + CardController(); + virtual ~CardController(){} + + enum eventID {EV_NONE, EV_EXIST, EV_INIT_SUCCESS, EV_INIT_FAIL, EV_PROV_SUCCESS, EV_PROV_FAIL, EV_COUNT}; + + int regCard(DStateMachine *sm); + + virtual void run(); + + static const int MAX_SUPPORTED_CARDS = 32; + +protected: + DStateMachine *cardSM[MAX_SUPPORTED_CARDS]; + int number_of_registered_cards; +}; + + +#endif /* STATE_MACHINE_CARD_CONTROLLER_H_ */ diff --git a/src/state_machine/card_states.h b/src/state_machine/card_states.h new file mode 100644 index 0000000..9f8858d --- /dev/null +++ b/src/state_machine/card_states.h @@ -0,0 +1,55 @@ +/* + * card_states.h + * + * This file contains specific states with their relevant events (as methods). + * For each state, an event will execute the relevant method, + * performing some action and returning a result. + * Note: In the below definition, no actions are performed and each method + * returns a specific result. In real usage, an action/method may return + * with different results. + * + * Created on: Nov 15, 2014 + * Author: edwardh + */ + +#ifndef STATE_MACHINE_CARD_STATES_H_ +#define STATE_MACHINE_CARD_STATES_H_ + +#include "state.h" + + +class StateNA : public State +{ +public: + virtual stateResult evExist() {return RET_SUCCESS;} +}; + +class StateInit : public State +{ +public: + virtual stateResult evInitSuccess(){return RET_SUCCESS;} + virtual stateResult evInitFail() {return RET_FAIL;} +}; + +class StateProv : public State +{ +public: + virtual stateResult evProvSuccess(){return RET_SUCCESS;} + virtual stateResult evProvFail() {return RET_FAIL;} +}; + +class StateReady : public State +{ +public: +}; + +class StateFault : public State +{ +public: + virtual stateResult evInitSuccess(){return RET_SUCCESS;} + virtual stateResult evInitFail() {return RET_FAIL;} +}; + + + +#endif /* STATE_MACHINE_CARD_STATES_H_ */ diff --git a/src/state_machine/device.h b/src/state_machine/device.h new file mode 100644 index 0000000..b213ee7 --- /dev/null +++ b/src/state_machine/device.h @@ -0,0 +1,46 @@ +/* + * device.h + * + * Description: + * A device is a generalization of a card, which has a reference to a controller + * and gets from the controller an ID (identifying it at the controller). + * + * A device can send an event to the controller through the sendEvent method. + * + * Created on: Nov 16, 2014 + * Author: edwardh + */ + +#ifndef STATE_MACHINE_DEVICE_H_ +#define STATE_MACHINE_DEVICE_H_ + +#include "card_controller.h" + +class Device +{ +public: + Device(CardController *controller) : devID(-1), ctrl(controller) {} + virtual ~Device(){}; + + virtual void sendEvent(CardController::eventID evID) + { + (void)evID; + /* + * Build a message and send it to the controller. + * msg->event = evID; + * msg->devID = this->devID; + * send_msg_to_controller(msg); + */ + } + + int devID; + +// // For debug only (actually for tests. TODO: Should be cleaned up and moved to the test. +// CardController::eventID lastEvent() {return last_event_id;} +// CardController::eventID last_event_id; +private: + CardController *ctrl; +}; + + +#endif /* STATE_MACHINE_DEVICE_H_ */ diff --git a/src/state_machine/dynamic_state_machine.cpp b/src/state_machine/dynamic_state_machine.cpp new file mode 100644 index 0000000..2d7d957 --- /dev/null +++ b/src/state_machine/dynamic_state_machine.cpp @@ -0,0 +1,38 @@ +/* + * dynamic_state_machine.cpp + * + * Created on: Nov 15, 2014 + * Author: edwardh + */ + + +#include "dynamic_state_machine.h" + + + +void DStateMachine::setBaseState(State *st) +{ + this->state = st; +} + + +void DStateMachine::mapDefault(State *default_nextState) +{ + this->default_next_state = default_nextState; +} + + +void DStateMachine::mapStateResult2NextState(State *st, State::stateResult ret_st, State *nextSt) +{ + if(ret_st < State::RET_COUNT) + st->stateMap[ret_st] = nextSt; +} + + +void DStateMachine::execEvent(State::stateResult result) +{ + State *nextState = this->state->stateMap[result]; + + if(NULL != nextState) + this->state = nextState; +} diff --git a/src/state_machine/dynamic_state_machine.h b/src/state_machine/dynamic_state_machine.h new file mode 100644 index 0000000..829408d --- /dev/null +++ b/src/state_machine/dynamic_state_machine.h @@ -0,0 +1,65 @@ +/* + * dynamic_state_machine.h + * + * A State Machine implementation with configurable state transitions. + * It maintains state decoupling, allowing it to scale and support multiple setups. + * + * Setup/Definition logic: + * - Create a State Machine. + * - Create states. + * - Set the SM initial state. + * - Set the SM default behavior: Action to take if no map is found. + * (NULL specifies to remain in the same state) + * - Map State & Result to the next State. (state, result, nextState) + * Receiving an event wile in state, triggers a state operation, resulting in a StateResult. + * SM State is changed to the nextState based on the result. + * + * Operation: + * - Usually called from a controller that receives events in an asynchronous mode. + * Usage example: + * State *state = sm->getCurrentState(); + * sm->execEvent(state->evExist()); + * // At this stage sm->getCurrentState() will return a new state. + * + * + * Created on: Nov 15, 2014 + * Author: edwardh + */ + +#ifndef DYNAMIC_STATE_MACHINE_H_ +#define DYNAMIC_STATE_MACHINE_H_ + +#include "state.h" + +class DStateMachine +{ +public: + // SM Setup // + void setBaseState(State *st); + + void mapDefault(State *default_nextState); + + void mapStateResult2NextState(State *st, State::stateResult ret_st, State *nextSt); + + // SM Operations // + void execEvent(State::stateResult result); //TODO: Ugly, it should not process the event at the calee, but in the SM. + // Need to convert this into a function pointer (or find a better solution). + + State *getCurrentState(){return state;}; + +private: + State *state; + State *default_next_state; + + /* + * Currently, the State MAP is maintained by each state independently. + * Located at the State "interface" to decouple individual states from each other. + * A different design approach could be to keep & manage the MAP here, in the SM, however + * it will require a special data structure to be maintain. + * It needs to be reconsidered again, some more thought should be put into this. + */ +}; + + + +#endif /* DYNAMIC_STATE_MACHINE_H_ */ diff --git a/src/state_machine/state.h b/src/state_machine/state.h new file mode 100644 index 0000000..0a7a496 --- /dev/null +++ b/src/state_machine/state.h @@ -0,0 +1,38 @@ +/* + * state.h + * + * Description: + * The State interface defines the available events with their default action. + * (Here the defaults are to do nothing and return a FAIL response) + * In addition, it contains the storage of the state map (mapping result to next state). + * (Each state has such a map which is populated by the SM during configuration) + * + * Created on: Nov 15, 2014 + * Author: edwardh + */ + +#ifndef STATE_MACHINE_STATE_H_ +#define STATE_MACHINE_STATE_H_ + +#include "string.h" + +class State +{ +public: + State(){memset(stateMap, 0, sizeof(stateMap));} + virtual ~State(){} + + // RET_COUNT must be left last, representing the number of possible results. + enum stateResult {RET_SUCCESS=0, RET_FAIL, RET_COUNT}; + + virtual stateResult evExist() {return RET_FAIL;} + virtual stateResult evInitSuccess(){return RET_FAIL;} + virtual stateResult evInitFail() {return RET_FAIL;} + virtual stateResult evProvSuccess(){return RET_FAIL;} + virtual stateResult evProvFail() {return RET_FAIL;} + + State *stateMap[RET_COUNT]; +}; + + +#endif /* STATE_MACHINE_STATE_H_ */ diff --git a/unit_tester/build/mk_common/utest_config_production_path.mk b/unit_tester/build/mk_common/utest_config_production_path.mk index d23f027..c28c589 100755 --- a/unit_tester/build/mk_common/utest_config_production_path.mk +++ b/unit_tester/build/mk_common/utest_config_production_path.mk @@ -1,16 +1,18 @@ -# -# -# - -# When all source files in a folder are under tests, it is prefered to add the folder instead of adding individual source files. -SRC_DIRS = \ - $(PRODUCTION_SOURCES)/llist -# - -SRC_FILES = \ -# $(PRODUCTION_SOURCES)/source_file.cpp -# - -INCLUDE_DIRS +=\ - $(PRODUCTION_SOURCES)/ +# +# +# + +# When all source files in a folder are under tests, it is prefered to add the folder instead of adding individual source files. +SRC_DIRS = \ + $(PRODUCTION_SOURCES)/llist\ + $(PRODUCTION_SOURCES)/state_machine\ +# + +SRC_FILES = \ +# $(PRODUCTION_SOURCES)/source_file.cpp +# + +INCLUDE_DIRS +=\ + $(PRODUCTION_SOURCES)/\ + $(PRODUCTION_SOURCES)/state_machine\ # \ No newline at end of file diff --git a/unit_tester/build/mk_common/utest_config_test_path.mk b/unit_tester/build/mk_common/utest_config_test_path.mk index 6fa1005..bddd8da 100755 --- a/unit_tester/build/mk_common/utest_config_test_path.mk +++ b/unit_tester/build/mk_common/utest_config_test_path.mk @@ -1,19 +1,20 @@ -# -# -# - -INCLUDE_DIRS =\ - .\ - $(TEST_ROOT)/helpers/include\ - $(TEST_ROOT)/mocks/include\ - /usr/include\ - $(CPPUTEST_HOME)/include\ - -TEST_SRC_DIRS = \ - $(TEST_ROOT)/helpers/src\ - $(TEST_ROOT)/tests\ - $(TEST_ROOT)/tests/llist_test\ -# - -MOCKS_SRC_DIRS =\ +# +# +# + +INCLUDE_DIRS =\ + .\ + $(TEST_ROOT)/helpers/include\ + $(TEST_ROOT)/mocks/include\ + /usr/include\ + $(CPPUTEST_HOME)/include\ + +TEST_SRC_DIRS = \ + $(TEST_ROOT)/helpers/src\ + $(TEST_ROOT)/tests\ + $(TEST_ROOT)/tests/llist_test\ + $(TEST_ROOT)/tests/state_machine_test\ +# + +MOCKS_SRC_DIRS =\ $(TEST_ROOT)/mocks/src\ \ No newline at end of file diff --git a/unit_tester/tests/state_machine_test/card_controller_test.cpp b/unit_tester/tests/state_machine_test/card_controller_test.cpp new file mode 100644 index 0000000..02f1a34 --- /dev/null +++ b/unit_tester/tests/state_machine_test/card_controller_test.cpp @@ -0,0 +1,154 @@ +/* + * card_controller_test.cpp + * + * The Controller test utilizes both the Dynamic SM implementation and the CardController logic. + * We use an inherited controller & card class to allow unit tests to be performed. + * + * Created on: Nov 16, 2014 + * Author: edwardh + */ + + +#include "CppUTest/TestHarness.h" + +#include "card_controller.h" +#include "device.h" + +#include "dynamic_state_machine.h" +#include "card_states.h" + + +// Implementation of CardController for testing purposes. +class CardController4Test : public CardController +{ +public: + void setInputEvent(CardController::eventID evID) {input_event_id = evID;} + void setInputDev(int devID) {input_dev_id = devID;} + + virtual void run() + { + DStateMachine *sm = this->cardSM[input_dev_id]; + State *st = sm->getCurrentState(); + + if(CardController::EV_EXIST == input_event_id) + sm->execEvent(st->evExist()); + } +private: + CardController::eventID input_event_id; + int input_dev_id; +}; + + +// Implementation of Device for testing purposes. +class CardDevice : public Device +{ +public: + CardDevice(CardController *controller) : Device(controller), last_event_id(CardController::EV_NONE){} + void sendEvent(CardController::eventID evID){last_event_id = evID;} + + CardController::eventID lastEvent() {return last_event_id;} + CardController::eventID last_event_id; +}; + +TEST_GROUP(card_controller) +{ + DStateMachine *sm; + State *stateNA; + State *stateInit; + State *stateReady; + State *stateProv; + State *stateFault; + + void setupStateMachine(DStateMachine *sMachine) + { + sMachine->setBaseState(stateNA); + sMachine->mapDefault(NULL); + sMachine->mapStateResult2NextState(stateNA, State::RET_SUCCESS, stateInit); + sMachine->mapStateResult2NextState(stateInit, State::RET_SUCCESS, stateProv); + sMachine->mapStateResult2NextState(stateInit, State::RET_FAIL, stateFault); + sMachine->mapStateResult2NextState(stateProv, State::RET_SUCCESS, stateReady); + sMachine->mapStateResult2NextState(stateFault, State::RET_FAIL, stateInit); + } + + void createCardStates() + { + stateNA = new StateNA(); + stateInit = new StateInit(); + stateReady = new StateReady(); + stateProv = new StateProv(); + stateFault = new StateFault(); + } + + void destroyCardStates() + { + delete stateNA; + delete stateInit; + delete stateReady; + delete stateProv; + delete stateFault; + } + + void setup() + { + sm = new DStateMachine(); + createCardStates(); + setupStateMachine(sm); + } + + void teardown() + { + destroyCardStates(); + delete sm; + } +}; + +TEST(card_controller, create_controller) +{ + CardController *controller = new CardController4Test(); + delete controller; +} + +TEST(card_controller, register_card_onto_controller__get_id) +{ + CardController *controller = new CardController4Test(); + + Device card(controller); + card.devID = controller->regCard(sm); + + LONGS_EQUAL(0, card.devID); + + delete controller; +} + +TEST(card_controller, register_2cards_onto_controller__get_next_unique_id) +{ + CardController *controller = new CardController4Test(); + + Device card1(controller); + Device card2(controller); + card1.devID = controller->regCard(sm); + card2.devID = controller->regCard(sm); + + LONGS_EQUAL(1, card2.devID); + + delete controller; +} + +TEST(card_controller, card_triggers_an_event__controller_executes_sm_event) +{ + CardController *controller = new CardController4Test(); + + CardDevice card(controller); + card.devID = controller->regCard(sm); + card.sendEvent(CardController::EV_EXIST); + + // Inject input (Event ID & Device ID) + ((CardController4Test*)controller)->setInputEvent(card.lastEvent()); + ((CardController4Test*)controller)->setInputDev(card.devID); + + controller->run(); + + POINTERS_EQUAL(stateInit, sm->getCurrentState()); + + delete controller; +} diff --git a/unit_tester/tests/state_machine_test/dynamic_state_machine_test.cpp b/unit_tester/tests/state_machine_test/dynamic_state_machine_test.cpp new file mode 100644 index 0000000..8d181e7 --- /dev/null +++ b/unit_tester/tests/state_machine_test/dynamic_state_machine_test.cpp @@ -0,0 +1,115 @@ +/* + * dynamic_state_machine_test.cpp + * + * Created on: Nov 15, 2014 + * Author: edwardh + */ + + + +#include "CppUTest/TestHarness.h" + +#include "dynamic_state_machine.h" +#include "card_states.h" + + + +TEST_GROUP(dynamic_sm) +{ + DStateMachine *sm; + State *stateNA; + State *stateInit; + State *stateReady; + State *stateProv; + State *stateFault; + + void setupStateMachine(DStateMachine *sMachine) + { + sMachine->setBaseState(stateNA); + sMachine->mapDefault(NULL); + sMachine->mapStateResult2NextState(stateNA, State::RET_SUCCESS, stateInit); + sMachine->mapStateResult2NextState(stateInit, State::RET_SUCCESS, stateProv); + sMachine->mapStateResult2NextState(stateInit, State::RET_FAIL, stateFault); + sMachine->mapStateResult2NextState(stateProv, State::RET_SUCCESS, stateReady); + sMachine->mapStateResult2NextState(stateFault, State::RET_FAIL, stateInit); + } + + void createCardStates() + { + stateNA = new StateNA(); + stateInit = new StateInit(); + stateReady = new StateReady(); + stateProv = new StateProv(); + stateFault = new StateFault(); + } + + void destroyCardStates() + { + delete stateNA; + delete stateInit; + delete stateReady; + delete stateProv; + delete stateFault; + } + + void setup() + { + sm = new DStateMachine(); + createCardStates(); + + setupStateMachine(sm); + } + + void teardown() + { + destroyCardStates(); + delete sm; + } +}; + + +TEST(dynamic_sm, define_sm) +{ + DStateMachine *_sm = new DStateMachine(); + _sm->setBaseState(stateNA); + _sm->mapDefault(NULL); + _sm->mapStateResult2NextState(stateNA, State::RET_SUCCESS, stateInit); + _sm->mapStateResult2NextState(stateInit, State::RET_SUCCESS, stateProv); + _sm->mapStateResult2NextState(stateInit, State::RET_FAIL, stateFault); + _sm->mapStateResult2NextState(stateProv, State::RET_SUCCESS, stateReady); + _sm->mapStateResult2NextState(stateFault, State::RET_FAIL, stateInit); + + POINTERS_EQUAL(stateNA, _sm->getCurrentState()); + + delete _sm; +} + +TEST(dynamic_sm, state_switch_evExist_sNA__moved2_StateInit) +{ + State *state = sm->getCurrentState(); + + //TODO: Ugly, it should not process the event here, but in the SM. + // Need to convert this into a function pointer (or find a better solution). + sm->execEvent(state->evExist()); + + POINTERS_EQUAL(stateInit, sm->getCurrentState()); +} + +TEST(dynamic_sm, state_switch_evInitFail_sInit__moved2_StateFault) +{ + sm->setBaseState(stateInit); + + State *state = sm->getCurrentState(); + sm->execEvent(state->evInitFail()); + + POINTERS_EQUAL(stateFault, sm->getCurrentState()); +} + +TEST(dynamic_sm, unsupported_event_for_the_given_state__remain_in_state) +{ + State *state = sm->getCurrentState(); + + sm->execEvent(state->evProvSuccess()); + + POINTERS_EQUAL(stateNA, sm->getCurrentState()); +}