diff --git a/OnPod/Common/EventScheduler/include/event.h b/OnPod/Common/EventScheduler/include/event.h new file mode 100644 index 0000000..6fd10f3 --- /dev/null +++ b/OnPod/Common/EventScheduler/include/event.h @@ -0,0 +1,32 @@ +#ifndef NODE_EVENT_H +#define NODE_EVENT_H + +#include + +typedef int16_t eventId_t; // Should range from 0-127. -1 indicates error in finding free ID +typedef uint32_t timeMs_t; // Time in ms + +enum class EventType +{ + FINITE, // Repeat X number of times + FOREVER, // Repeat forever +}; + +enum class EventState +{ + STARTED, + STOPPED, +}; + +struct Event +{ + EventType type = EventType::FINITE; + EventState state = EventState::STOPPED; + void (*callback)(void) = nullptr; + timeMs_t delay_ms = 0; + timeMs_t last_time_called_ms = 0; + uint32_t num_calls_left = 0; + eventId_t id = 0; +}; + +#endif diff --git a/OnPod/Common/EventScheduler/include/event_scheduler.h b/OnPod/Common/EventScheduler/include/event_scheduler.h new file mode 100644 index 0000000..2694957 --- /dev/null +++ b/OnPod/Common/EventScheduler/include/event_scheduler.h @@ -0,0 +1,45 @@ +#ifndef NODE_EVENT_SCHEDULER_H +#define NODE_EVENT_SCHEDULER_H + +#include +#include +#include "event.h" + +class EventScheduler +{ + private: + // Number of events that can be active at once + static const int16_t m_MAX_NUM_EVENTS = 50; + // Map to hold events and their ids + std::map m_events; + + // Return a free event id + eventId_t findFreeEventId(void); + + public: + int16_t getMaxNumEvents(void) { return m_MAX_NUM_EVENTS; }; + // Check if any callbacks must be called and execute them. + // If the repeat limit is reached, reset the event object. + void updateEvents(timeMs_t current_time); + + // Resumes the event's calls + void startEvent(eventId_t event_id); + // Pauses all the event's calls. + // If the event has a certain number of repeats, they will be preserved after starting again. + void stopEvent(eventId_t event_id); + + // Call `callback` every `delay_ms` milliseconds, `repeat_count` times. + // Returns index of event in `m_event_list` or -1 if `m_event_list` is full. + eventId_t callFunctionEvery(timeMs_t delay_ms, void (*callback)(void), + timeMs_t current_time, uint32_t repeat_count); + // Call `callback` every `delay_ms` milliseconds, forever. + // Returns index of event in `m_event_list` or -1 if `m_event_list` is full. + eventId_t callFunctionEvery(timeMs_t delay_ms, void (*callback)(void), + timeMs_t current_time); + // Call `callback` after `delay_ms` milliseconds. + // Returns index of event in `m_event_list` or -1 if `m_event_list` is full. + eventId_t callFunctionAfter(timeMs_t delay_ms, void (*callback)(void), + timeMs_t current_time); +}; + +#endif diff --git a/OnPod/Common/EventScheduler/src/event_scheduler.cpp b/OnPod/Common/EventScheduler/src/event_scheduler.cpp new file mode 100644 index 0000000..befcfc6 --- /dev/null +++ b/OnPod/Common/EventScheduler/src/event_scheduler.cpp @@ -0,0 +1,125 @@ +#include +#include "event_scheduler.h" + +void EventScheduler::updateEvents(timeMs_t current_time) +{ + // Can't erase key/value pairs in the for loop + // Keep track of them in this vector then erase them afterwards + std::vector events_to_erase; + // Iterate over map to find events that are due + for (auto& [id, event] : m_events) + { + if (event.state == EventState::STOPPED) + { + continue; + } + + if (current_time - event.last_time_called_ms >= event.delay_ms) + { + event.callback(); + event.last_time_called_ms = current_time; + if (event.type == EventType::FINITE) + { + event.num_calls_left--; + if (event.num_calls_left == 0) + { + // Add to temporary vector to be removed + events_to_erase.push_back(id); + } + } + } + } + // Remove key/value pairs that had expired + for (auto const id : events_to_erase) + { + m_events.erase(id); + } +} + +eventId_t EventScheduler::findFreeEventId(void) +{ + // If map is too damn big return error + if (m_events.size() >= m_MAX_NUM_EVENTS) + { + return -1; + } + // If empty use first id, 0 + if (m_events.empty()) + { + return 0; + } + // Check for gaps in the map + // Iterate and see if index_being_checked is available + eventId_t index_being_checked = 0; + for (auto event : m_events) + { + if (event.first == index_being_checked) + { + index_being_checked++; + } + else + { + return index_being_checked; + } + } + // If there were no gaps in the map then use the next index up + if (index_being_checked <= m_MAX_NUM_EVENTS) + { + return index_being_checked; + } + return -1; +} + +void EventScheduler::startEvent(eventId_t id) +{ + m_events[id].state = EventState::STARTED; +} + +void EventScheduler::stopEvent(eventId_t id) +{ + m_events[id].state = EventState::STOPPED; +} + +eventId_t EventScheduler::callFunctionEvery(timeMs_t delay_ms, void (*callback)(void), + timeMs_t current_time, uint32_t repeat_count) +{ + eventId_t id = findFreeEventId(); + if (id == -1) + { + return id; + } + Event event; + event.type = EventType::FINITE; + event.state = EventState::STARTED; + event.delay_ms = delay_ms; + event.last_time_called_ms = current_time; + event.callback = callback; + event.num_calls_left = repeat_count; + m_events.insert({id, event}); + return id; +} + +eventId_t EventScheduler::callFunctionEvery(timeMs_t delay_ms, void (*callback)(void), + timeMs_t current_time) +{ + eventId_t id = findFreeEventId(); + if (id == -1) + { + return id; + } + Event event; + event.type = EventType::FOREVER; + event.state = EventState::STARTED; + event.delay_ms = delay_ms; + event.last_time_called_ms = current_time; + event.callback = callback; + event.num_calls_left = 0; + m_events.insert({id, event}); + return id; +} + +eventId_t EventScheduler::callFunctionAfter(timeMs_t delay_ms, void (*callback)(void), + timeMs_t current_time) +{ + return callFunctionEvery(delay_ms, callback, current_time, 1); +} diff --git a/OnPod/Node/Common/PeripheralInterfaces/include/I2CInterface.h b/OnPod/Node/Common/PeripheralInterfaces/include/I2CInterface.h index 0010a93..071a755 100644 --- a/OnPod/Node/Common/PeripheralInterfaces/include/I2CInterface.h +++ b/OnPod/Node/Common/PeripheralInterfaces/include/I2CInterface.h @@ -39,7 +39,7 @@ class I2CInterface HAL_StatusTypeDef memRead(uint16_t devAddress, uint16_t memAddress, uint16_t memAddressSize, uint32_t timeout); - HAL_StatusTypeDef isDeviceReady(uint16_t devAddress, uint32_t trials = 100, uint32_t timeout); + HAL_StatusTypeDef isDeviceReady(uint16_t devAddress, uint32_t trials, uint32_t timeout); /* Config methods for digital and analog noise filters diff --git a/OnPod/Node/Test/EventScheduler/main.cpp b/OnPod/Node/Test/EventScheduler/main.cpp new file mode 100644 index 0000000..d7213a0 --- /dev/null +++ b/OnPod/Node/Test/EventScheduler/main.cpp @@ -0,0 +1,335 @@ +#include +#include "event_scheduler.h" + +///// TEST HELPERS ///// +uint16_t test_callback_run_count = 0; + +void testCallback(void) +{ + test_callback_run_count++; +} + +void resetTestCallback(void) +{ + test_callback_run_count = 0; +} + +///// TESTS ///// + +// startEvent +void startEvent_eventNotStarted_eventStarted(void) +{ + EventScheduler event_scheduler; + eventId_t event = event_scheduler.callFunctionAfter(1000, testCallback, 0); + //event_scheduler.stopEvent(event); + //event_scheduler.startEvent(event); + event_scheduler.updateEvents(1001); + TEST_ASSERT_EQUAL_UINT16(1, test_callback_run_count); +} + +void startEvent_eventStarted_eventStaysStarted(void) +{ + EventScheduler event_scheduler; + eventId_t event = event_scheduler.callFunctionAfter(1000, testCallback, 0); + event_scheduler.startEvent(event); + event_scheduler.updateEvents(1001); + TEST_ASSERT_EQUAL_UINT16(1, test_callback_run_count); + resetTestCallback(); +} + +// stopEvent +void stopEvent_eventStopped_eventStaysStopped(void) +{ + EventScheduler event_scheduler; + eventId_t event = event_scheduler.callFunctionAfter(1000, testCallback, 0); + event_scheduler.stopEvent(event); + event_scheduler.stopEvent(event); + event_scheduler.updateEvents(1001); + TEST_ASSERT_EQUAL_UINT16(0, test_callback_run_count); + resetTestCallback(); +} + +void stopEvent_eventNotStopped_eventStopped(void) +{ + EventScheduler event_scheduler; + eventId_t event = event_scheduler.callFunctionAfter(1000, testCallback, 0); + event_scheduler.startEvent(event); + event_scheduler.stopEvent(event); + event_scheduler.updateEvents(1001); + TEST_ASSERT_EQUAL_UINT16(0, test_callback_run_count); + resetTestCallback(); +} + +// callFunctionEvery (forever) +void callFunctionEveryForever_delayPassedOnce_functionCalledOnce(void) +{ + EventScheduler event_scheduler; + eventId_t event = event_scheduler.callFunctionEvery(1000, testCallback, 0); + event_scheduler.updateEvents(1001); + TEST_ASSERT_EQUAL_UINT16(1, test_callback_run_count); + resetTestCallback(); +} + +void callFunctionEveryForever_delayPassedTwice_functionCalledTwice(void) +{ + EventScheduler event_scheduler; + eventId_t event = event_scheduler.callFunctionEvery(1000, testCallback, 0); + event_scheduler.updateEvents(1001); + event_scheduler.updateEvents(2002); + TEST_ASSERT_EQUAL_UINT16(2, test_callback_run_count); + resetTestCallback(); +} + +void callFunctionEveryForever_delayPassedThousandTimes_functionCalledThousandTimes(void) +{ + EventScheduler event_scheduler; + eventId_t event = event_scheduler.callFunctionEvery(1000, testCallback, 0); + for (uint16_t i = 1; i <= 1000; i++) + { + event_scheduler.updateEvents(i*1001); + } + TEST_ASSERT_EQUAL_UINT16(1000, test_callback_run_count); + resetTestCallback(); +} + +void callFunctionEveryForever_delayNotPassed_functionNotCalled(void) +{ + EventScheduler event_scheduler; + eventId_t event = event_scheduler.callFunctionEvery(1000, testCallback, 0); + event_scheduler.updateEvents(999); + TEST_ASSERT_EQUAL_UINT16(0, test_callback_run_count); + resetTestCallback(); +} + +void callFunctionEveryForever_delayNotPassedThenPassed_functionCalledOnce(void) +{ + EventScheduler event_scheduler; + eventId_t event = event_scheduler.callFunctionEvery(1000, testCallback, 0); + event_scheduler.updateEvents(999); + event_scheduler.updateEvents(1001); + TEST_ASSERT_EQUAL_UINT16(1, test_callback_run_count); + resetTestCallback(); +} + +void callFunctionEveryForever_delayExactlyReached_functionCalledOnce(void) +{ + EventScheduler event_scheduler; + eventId_t event = event_scheduler.callFunctionEvery(1000, testCallback, 0); + event_scheduler.updateEvents(1000); + TEST_ASSERT_EQUAL_UINT16(1, test_callback_run_count); + resetTestCallback(); +} + +// callFunctionEvery (finite) +void callFunctionEveryFinite_delayNotPassed_functionNotCalled(void) +{ + EventScheduler event_scheduler; + eventId_t event = event_scheduler.callFunctionEvery(1000, testCallback, 0, 2); + event_scheduler.updateEvents(999); + TEST_ASSERT_EQUAL_UINT16(0, test_callback_run_count); + resetTestCallback(); +} + +void callFunctionEveryFinite_delayPassed_functionCalledOnce(void) +{ + EventScheduler event_scheduler; + eventId_t event = event_scheduler.callFunctionEvery(1000, testCallback, 0, 2); + event_scheduler.updateEvents(1001); + TEST_ASSERT_EQUAL_UINT16(1, test_callback_run_count); + resetTestCallback(); +} + +void callFunctionEveryFinite_delayPassedTwice_functionCalledTwice(void) +{ + EventScheduler event_scheduler; + eventId_t event = event_scheduler.callFunctionEvery(1000, testCallback, 0, 2); + event_scheduler.updateEvents(1001); + event_scheduler.updateEvents(2002); + TEST_ASSERT_EQUAL_UINT16(2, test_callback_run_count); + resetTestCallback(); +} + +void callFunctionEveryFinite_delayPassedThreeTimes_functionCalledTwice(void) +{ + EventScheduler event_scheduler; + eventId_t event = event_scheduler.callFunctionEvery(1000, testCallback, 0, 2); + event_scheduler.updateEvents(1001); + event_scheduler.updateEvents(2002); + event_scheduler.updateEvents(3003); + TEST_ASSERT_EQUAL_UINT16(2, test_callback_run_count); + resetTestCallback(); +} + +void callFunctionEveryFinite_delayNotPassedThenPassed_functionCalledOnce(void) +{ + EventScheduler event_scheduler; + eventId_t event = event_scheduler.callFunctionEvery(1000, testCallback, 0, 2); + event_scheduler.updateEvents(999); + event_scheduler.updateEvents(1001); + TEST_ASSERT_EQUAL_UINT16(1, test_callback_run_count); + resetTestCallback(); +} + +// callFunctionAfter +void callFunctionAfter_delayPassed_functionCalled(void) +{ + EventScheduler event_scheduler; + event_scheduler.callFunctionAfter(1000, testCallback, 0); + event_scheduler.updateEvents(1001); + TEST_ASSERT_EQUAL_UINT16(1, test_callback_run_count); + resetTestCallback(); +} + +void callFunctionAfter_delayNotPassed_functionNotCalled(void) +{ + EventScheduler event_scheduler; + event_scheduler.callFunctionAfter(1000, testCallback, 0); + event_scheduler.updateEvents(999); + TEST_ASSERT_EQUAL_UINT16(0, test_callback_run_count); + resetTestCallback(); +} + +void callFunctionAfter_delayPassedTwice_functionCalledOnce(void) +{ + EventScheduler event_scheduler; + event_scheduler.callFunctionAfter(1000, testCallback, 0); + event_scheduler.updateEvents(1001); + event_scheduler.updateEvents(2002); + TEST_ASSERT_EQUAL_UINT16(1, test_callback_run_count); + resetTestCallback(); +} + +// findFreeEvent +void findFreeEvent_makeOneEvents_idIsZero(void) +{ + EventScheduler event_scheduler; + eventId_t id = event_scheduler.callFunctionEvery(0, testCallback, 0); + TEST_ASSERT_EQUAL_INT16(0, id); +} + +void findFreeEvent_makeTwoEvents_secondIdIsOne(void) +{ + EventScheduler event_scheduler; + event_scheduler.callFunctionEvery(0, testCallback, 0); + eventId_t id = event_scheduler.callFunctionEvery(0, testCallback, 0); + TEST_ASSERT_EQUAL_INT16(1, id); +} + +void findFreeEvent_expireEventThenMakeSecond_secondIdIsZero(void) +{ + EventScheduler event_scheduler; + event_scheduler.callFunctionAfter(1000, testCallback, 0); + event_scheduler.updateEvents(1001); + eventId_t id = event_scheduler.callFunctionEvery(0, testCallback, 0); + TEST_ASSERT_EQUAL_INT16(0, id); +} + +void findFreeEvent_makeThreeExpireSecondMakeFourth_fourthIdIsOne(void) +{ + EventScheduler event_scheduler; + event_scheduler.callFunctionEvery(1000, testCallback, 0); + event_scheduler.callFunctionAfter(1000, testCallback, 0); + event_scheduler.callFunctionEvery(1000, testCallback, 0); + event_scheduler.updateEvents(1001); + eventId_t id = event_scheduler.callFunctionEvery(0, testCallback, 0); + TEST_ASSERT_EQUAL_INT16(1, id); +} + +void findFreeEvent_makeMaxPlusOneEvents_errorIsReturned(void) +{ + EventScheduler event_scheduler; + // Make MAX events + for (eventId_t id = 0; id < event_scheduler.getMaxNumEvents(); id++) + { + event_scheduler.callFunctionEvery(0, testCallback, 0); + } + eventId_t last_id = event_scheduler.callFunctionEvery(0, testCallback, 0); + TEST_ASSERT_EQUAL_INT16(-1, last_id); +} + +void findFreeEvent_makeMaxThenExpireOneThenMakeEvent_validIdReturned(void) +{ + EventScheduler event_scheduler; + // Make MAX-1 events that won't expire + for (eventId_t id = 0; id < event_scheduler.getMaxNumEvents()-1; id++) + { + event_scheduler.callFunctionEvery(1000, testCallback, 0); + } + // Make final event that will expire + event_scheduler.callFunctionAfter(0, testCallback, 0); + // Try and fail to make a MAX+1th event + event_scheduler.callFunctionEvery(0, testCallback, 0); + // Expire final element + event_scheduler.updateEvents(999); + // Try (successfully) to make a MAXth element + eventId_t id = event_scheduler.callFunctionEvery(0, testCallback, 0); + // Assert id is MAX-1 (-1 to account for account for ids starting at 0) + TEST_ASSERT_EQUAL_INT16(event_scheduler.getMaxNumEvents()-1, id); +} + +int main(void) +{ + UNITY_BEGIN(); + + // startEvent + RUN_TEST(startEvent_eventNotStarted_eventStarted); + resetTestCallback(); + RUN_TEST(startEvent_eventStarted_eventStaysStarted); + resetTestCallback(); + + // stopEvent + RUN_TEST(stopEvent_eventStopped_eventStaysStopped); + resetTestCallback(); + RUN_TEST(stopEvent_eventNotStopped_eventStopped); + resetTestCallback(); + + // callFunctionEvery (forever) + RUN_TEST(callFunctionEveryForever_delayPassedOnce_functionCalledOnce); + resetTestCallback(); + RUN_TEST(callFunctionEveryForever_delayPassedTwice_functionCalledTwice); + resetTestCallback(); + RUN_TEST(callFunctionEveryForever_delayPassedThousandTimes_functionCalledThousandTimes); + resetTestCallback(); + RUN_TEST(callFunctionEveryForever_delayNotPassed_functionNotCalled); + resetTestCallback(); + RUN_TEST(callFunctionEveryForever_delayNotPassedThenPassed_functionCalledOnce); + resetTestCallback(); + RUN_TEST(callFunctionEveryForever_delayExactlyReached_functionCalledOnce); + resetTestCallback(); + + // callFunctionEvery (finite) + RUN_TEST(callFunctionEveryFinite_delayNotPassed_functionNotCalled); + resetTestCallback(); + RUN_TEST(callFunctionEveryFinite_delayPassed_functionCalledOnce); + resetTestCallback(); + RUN_TEST(callFunctionEveryFinite_delayPassedTwice_functionCalledTwice); + resetTestCallback(); + RUN_TEST(callFunctionEveryFinite_delayPassedThreeTimes_functionCalledTwice); + resetTestCallback(); + RUN_TEST(callFunctionEveryFinite_delayNotPassedThenPassed_functionCalledOnce); + resetTestCallback(); + + // callFunctionAfter + RUN_TEST(callFunctionAfter_delayPassed_functionCalled); + resetTestCallback(); + RUN_TEST(callFunctionAfter_delayNotPassed_functionNotCalled); + resetTestCallback(); + RUN_TEST(callFunctionAfter_delayPassedTwice_functionCalledOnce); + resetTestCallback(); + + // Test private method findFreeEvent indirectly + RUN_TEST(findFreeEvent_makeOneEvents_idIsZero); + resetTestCallback(); + RUN_TEST(findFreeEvent_makeTwoEvents_secondIdIsOne); + resetTestCallback(); + RUN_TEST(findFreeEvent_expireEventThenMakeSecond_secondIdIsZero); + resetTestCallback(); + RUN_TEST(findFreeEvent_makeThreeExpireSecondMakeFourth_fourthIdIsOne); + resetTestCallback(); + RUN_TEST(findFreeEvent_makeMaxPlusOneEvents_errorIsReturned); + resetTestCallback(); + RUN_TEST(findFreeEvent_makeMaxThenExpireOneThenMakeEvent_validIdReturned); + resetTestCallback(); + + UNITY_END(); +} diff --git a/OnPod/Node/Test/Test1/main.cpp b/OnPod/Node/Test/Test1/main.cpp deleted file mode 100644 index f52c1e3..0000000 --- a/OnPod/Node/Test/Test1/main.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include -#include "mock_class.h" -#include "driver1.h" - -void testOne(void) -{ - TEST_ASSERT_EQUAL_UINT8(7, 7); -} - -int main(void) -{ - UNITY_BEGIN(); - RUN_TEST(testOne); - UNITY_END(); - return 0; -} - diff --git a/OnPod/Node/platformio.ini b/OnPod/Node/platformio.ini index 34d2862..c72b9a9 100644 --- a/OnPod/Node/platformio.ini +++ b/OnPod/Node/platformio.ini @@ -11,6 +11,7 @@ [platformio] src_dir = ./ include_dir = Common +default_envs = ActuationNode [env:ActuationNode] platform = ststm32 @@ -20,11 +21,14 @@ debug_tool = stlink upload_protocol = stlink src_filter = + + + + +<../Common> build_flags = -I./ActuationNode/BoardFiles/Inc -I./Common/Drivers/include -I./Common/PeripheralManagers/include -I./Common/PeripheralInterfaces/include + -I../Common/EventScheduler/include [env:SenseNode] platform = ststm32 @@ -34,11 +38,14 @@ debug_tool = stlink upload_protocol = stlink src_filter = + + + + +<../Common> build_flags = -I./SenseNode/BoardFiles/Inc -I./Common/Drivers/include -I./Common/PeripheralManagers/include -I./Common/PeripheralInterfaces/include + -I../Common/EventScheduler/include [env:LvdcNode] platform = ststm32 @@ -48,11 +55,14 @@ debug_tool = stlink upload_protocol = stlink src_filter = + + + + +<../Common> build_flags = -I./LvdcNode/BoardFiles/Inc -I./Common/Drivers/include -I./Common/PeripheralManagers/include -I./Common/PeripheralInterfaces/include + -I../Common/EventScheduler/include [env:Testing] platform = native @@ -60,7 +70,13 @@ test_build_project_src = true src_filter = -<*/BoardFiles/Src> -<*/BoardFiles/Inc> + + + + + + + +<../Common> build_flags = -I./Common/MockClasses/include -I./Common/Drivers/include -I./Common/PeripheralManagers/include + -I../Common/EventScheduler/include + -std=c++17