├── .gitignore ├── .vscode └── extensions.json ├── LICENSE ├── README.md ├── boards └── nucleo_l55.json ├── examples └── pingpong │ ├── include │ ├── active_app_messages.h │ ├── active_config.h │ └── pingpong.h │ ├── main.c │ └── pingpong.c ├── include ├── active.h ├── active_assert.h ├── active_config_loader.h ├── active_mem.h ├── active_msg.h ├── active_port.h ├── active_psmsg.h ├── active_timer.h └── active_types.h ├── lib └── README ├── platformio.ini ├── src ├── active.c ├── active_assert.c ├── active_mem.c ├── active_msg.c ├── active_port.c ├── active_ps.c └── active_timer.c ├── test ├── README ├── test_integration_active_object │ ├── active_config.h │ └── test_integration_active_object.c ├── test_integration_active_object_post │ ├── active_config.h │ └── test_integration_active_object_post.c ├── test_integration_active_timer │ ├── active_config.h │ └── test_integration_active_timer.c ├── test_unit_active_mem │ ├── active_config.h │ └── test_unit_active_mem.c └── test_unit_active_msg │ ├── active_config.h │ └── test_unit_active_msg.c └── zephyr ├── CMakeLists.txt └── prj.conf /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .DS_Store 3 | .vscode/ 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Svend Torgersen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Active 2 | 3 | ## What is Active? 4 | 5 | Active is a portable, C-based messaging library for embedded, memory-constrained devices (microcontrollers) based on the Active Object (Actor model) design pattern: https://en.wikipedia.org/wiki/Active_object. 6 | 7 | It simplifies building event-driven, multi-threaded systems by decoupling method execution from method invocation in order to 8 | simplify synchronized access to an object that resides in its own thread of control. 9 | 10 | The Active Object pattern allows one or more independent threads of execution to interleave their access to data modeled as a single object. 11 | A broad class of producer/consumer and reader/writer applications are well suited to this model of concurrency 12 | 13 | The Active framework can be used together with normal threads running in parallel. 14 | 15 | The framework is inspired by the [QP/C framework](https://www.state-machine.com/qpc/) from Quantum Leaps - read more about the active object pattern and why you should use it in embedded programming here: https://www.state-machine.com/active-object 16 | 17 | The key difference between QP/C and Active (beyond QP/C being a great production ready commercial framework), is that QP/C Active objects are a state machine themselves; the state machine receives the dispatch function and calls into the separate state handling functions of an Active object. 18 | 19 | Active decouples this and lets the aplication instead call into a state machine (such as Zephyr's State Machine Framework) to allow for more flexible uses of Active objects. 20 | 21 | ## Maintenance 22 | 23 | This repository should not be considered maintained. It is a hobby project for re-learning embedded programming. 24 | Maintainers and pull requests are welcome. 25 | 26 | ## Functionality: 27 | 28 | - Thread abstration into separate Active objects - each object is running in its own thread with its own private data. 29 | - Event based architecture - all Active objects are blocking until they receive an event, which is then processed by the Active object's dispatch function 30 | - Support for static allocation of events to ROM 31 | - Support for dynamic allocation of events and payloads through global static memory pools with automatic garbage collection 32 | - Several event types - Signals (no arguments) and Messages (with application defined header and pointer to payload) 33 | - Timed messages (one-shot and periodic) for timeouts, system wide ticks and data streaming 34 | - Direct message passing between objects 35 | 36 | 37 | ## Supported frameworks: 38 | - Zephyr RTOS 39 | 40 | All framework and compiler specific code is found in active_port files for simple extension to new frameworks. 41 | The library make use of runtime "polymorphism" to represent Active objects and message types through function pointers and base 42 | classes (structs) for Events and Active objects. 43 | 44 | ## Requirements: 45 | 46 | - A compiler using the C11 standard or later 47 | - A compiler supporting `__has_include` for letting an application configure the library in a separate active_config.h file. GCC and Clang has built-in support for this. 48 | - A queue implementation (Zephyr RTOS: Message queue) 49 | - A kernel supporting threads with priorities (Zephyr RTOS: Using k_thread) 50 | - A method to yield to other threads (Zephyr RTOS: Built-in blocking when pending on queue using K_FOREVER wait) 51 | - Memory allocation through fixed size static pools (Zephyr RTOS: Using memory slabs) 52 | - A timer/scheduler implementation with expiry function and user data support for timed events (Zephyr RTOS: Using k_timer) 53 | 54 | ## Getting started 55 | 56 | The library project is set up as a PlatformIO project (https://platformio.org/) for building and testing. 57 | 58 | The project is set up using the STM32 NUCLEO L552ZE_Q board, using a custom board file located in the boards/ folder. 59 | Refer to PlatformIO documentation for how to change boards for testing Active as a standalone project. 60 | 61 | Usage examples are found in the examples/ folder. Select which example application to build together with framework in platformio.ini (`selected_example`) 62 | 63 | The library can be configured by an application through adding a custom `active_config.h` in the compiler search path to override default settings. 64 | Available configuration settings and defaults are located in `active_config_loader.h`. 65 | 66 | ## Testing 67 | 68 | Unit tests and integration tests are using Unity, setup through the PlatformIO IDE. 69 | PlatformIO does not support boards (for running tests) on a native (e.g. x86) platform using Zephyr RTOS. 70 | 71 | To run tests: 72 | - Modify zephyr/prj.conf to include `CONFIG_NEWLIB_LIBC=y` (Zephyr and Unity use different LibC's) 73 | - Connect a target board 74 | - Find your board's serial device (`ls /dev/tty*`) and update the `monitor_port`field in platformio.ini 75 | - Run `pio test -e test` in a PlatformIO Terminal. 76 | 77 | ## Usage 78 | 79 | ### Set up one or more active objects 80 | 81 | An Active object implementation typically consists of a structure where the super object `Active` is the first member, enabling polymorphism. 82 | 83 | To define an Active object, set up a structure for one or more objects: 84 | 85 | ```C 86 | /* pingpong.h */ 87 | #ifndef PINGPONG_H 88 | #define PINGPONG_H 89 | 90 | #include 91 | 92 | typedef struct pingpong PingPong; 93 | struct pingpong 94 | { 95 | Active super; 96 | PingPong *pingpong; 97 | }; 98 | 99 | void PingPong_init(PingPong *const me, ACT_QueueData const *qd, ACT_ThreadData const *td, PingPong *pp); 100 | 101 | #endif 102 | ``` 103 | 104 | Then, implement the init function for the Active object and the dispatch function: 105 | 106 | ```C 107 | /* pingpong.c */ 108 | #include 109 | 110 | static void PingPong_dispatch(Active *const me, ACT_Evt const *const e) 111 | { 112 | PingPong *p = (PingPong *)me; 113 | if (e->type == ACT_SIGNAL) 114 | { 115 | uint16_t sig = EVT_CAST(e, ACT_Signal)->sig; 116 | switch (sig) 117 | { 118 | case START_SIG: 119 | { 120 | /* Do initialization work */ 121 | break; 122 | } 123 | default: 124 | { 125 | break; 126 | } 127 | } 128 | } 129 | } 130 | 131 | void PingPong_init(PingPong *const me, ACT_QueueData const *qd, ACT_ThreadData const *td, PingPong *pp) 132 | { 133 | ACT_init(&(me->super), PingPong_dispatch, qd, td); 134 | me->pingpong = pp; /* Example private data structure */ 135 | } 136 | ``` 137 | 138 | At last, set up the structures needed for an Active object to run. Each Active object needs their own set of structures. 139 | 140 | ```C 141 | /* main.c */ 142 | #include 143 | #include "pingpong.h" 144 | 145 | PingPong ping, pong; 146 | 147 | #define MAX_MSG 10 148 | 149 | ACT_QBUF(pingQbuf, MAX_MSG); 150 | ACT_Q(pingQ); 151 | ACT_THREAD(pingT); 152 | ACT_THREAD_STACK_DEFINE(pingStack, 512); 153 | ACT_THREAD_STACK_SIZE(pingStackSz, pingStack); 154 | 155 | const static ACT_QueueData qdping = {.maxMsg = MAX_MSG, 156 | .queBuf = pingQbuf, 157 | .queue = &pingQ}; 158 | 159 | const static ACT_ThreadData tdping = {.thread = &pingT, 160 | .pri = 1, 161 | .stack = pingStack, 162 | .stack_size = pingStackSz}; 163 | 164 | /* Duplicate for pong data structure */ 165 | // ... 166 | 167 | int main(void) 168 | { 169 | PingPong_init(&ping, &qdping, &tdping, &pong); 170 | PingPong_init(&pong, &qdpong, &tdpong, &ping); 171 | 172 | ... 173 | } 174 | 175 | ``` 176 | 177 | ### Start active objects 178 | 179 | After Active objects are initialized, they are ready to be started. When an Active object is started, its dispatch function will immediately receive a `ACT_Signal` event with value START_SIG. 180 | 181 | An Active object is typically either started by the `main()` function upon HW initialization being done, or by initializing HW using START_SIG and waiting for interrupts or a timeout (using time events). The application needs to ensure that all Active objects are initialized using the `ACT_init` function before they start sending events to one another. Events that are sent to an Active object are not processed until the Active object is started, but rather remains in the queue. 182 | 183 | ```C 184 | int main(void) 185 | { 186 | PingPong_init(&ping, &qdping, &tdping, &pong); 187 | PingPong_init(&pong, &qdpong, &tdpong, &ping); 188 | 189 | ACT_start(ACT_UPCAST(&ping)); 190 | ACT_start(ACT_UPCAST(&pong)); 191 | 192 | } 193 | 194 | ```` 195 | The `ACT_UPCAST()` macro is a helper macro to upcast application active objects into their parent class to prevent compiler warnings. 196 | 197 | ### Process start event 198 | 199 | The START_SIG Signal will be the first event received by any active object. It is typically used to initialize data structures or state machines as needed or as a trigger to send the first application events. 200 | 201 | 202 | 203 | ### Creating events 204 | 205 | There are three ways of creating events for Active objects: 206 | - Allocated on the heap and initialized using the event's `_init` function 207 | - Allocated in ROM (using const) or on the heap using the event's `_DEFINE` macro. 208 | - Dynamically allocated and initialized using the event's `_new` function. 209 | 210 | The application must itself ensure static storage types or const type for events if needed. 211 | 212 | Interfaces and macros for statically allocated events are found in the `active_msg` header file. 213 | Interfaces for dynamically allocated events are found in the `active_mem` header file. 214 | 215 | When choosing how to create events, it is important to know that an active object might not know when the event has been processed by all recipients. 216 | Therefore, if a statically allocated event or its payload needs to be modified over time, dynamically allocated events are to be preferred to avoid race conditions when modifying it later. Dynamic events allow the application to fire-and-forget the events with automatic garbage collection when processing is done. 217 | 218 | ### Creating events - memory pool sizing 219 | 220 | Memory pools for each event type are instanciated private to the the `active_mem` module. They are allocated compile-time, with configurable pool sizes. 221 | Pool sizes can be found through analyzing the application and monitoring the max usage of memory pools during development and stress testing. (TODO: exposing max use) 222 | 223 | ### Posting events 224 | 225 | An event is posted using the `ACT_postEvt` function: 226 | 227 | ```C 228 | /* pingpong.c */ 229 | #include 230 | 231 | /* Available signals in application header file - first signal starts with USER_SIG*/ 232 | enum AppUserSignal 233 | { 234 | PING = USER_SIG, 235 | PONG 236 | }; 237 | 238 | static const ACT_SIGNAL_DEFINE(pingSig, PING); 239 | static const ACT_SIGNAL_DEFINE(pongSig, PONG); 240 | 241 | static void PingPong_dispatch(Active *const me, ACT_Evt const *const e) 242 | { 243 | PingPong *p = (PingPong *)me; /* To access internal data */ 244 | 245 | if (e->type == ACT_SIGNAL) 246 | { 247 | uint16_t sig = EVT_CAST(e, ACT_Signal)->sig; 248 | switch (sig) 249 | { 250 | case START_SIG: 251 | { 252 | /* Do initialization work */ 253 | break; 254 | } 255 | case PING: 256 | { 257 | ACT_postEvt(me, EVT_UPCAST(&pongSig)); 258 | break; 259 | } 260 | case PONG: 261 | { 262 | ACT_postEvt(me, EVT_UPCAST(&pingSig)); 263 | break; 264 | } 265 | default: 266 | { 267 | break; 268 | } 269 | } 270 | } 271 | } 272 | ``` 273 | 274 | The `EVT_UPCAST()`is available to upcast pointers to specific events up to the base `ACT_Evt`type. 275 | 276 | ### Processing events 277 | 278 | An object is available for processing when it is received by the Active object's dispatch function. Active objects should run to completion on every message processed with no to minimal blocking, as it prevents the Active object from processing further messages. Long running tasks can be deferred to lower priority work threads (such as Zephyr's `workqueue`) or split into multiple steps by having the Active object message itself. 279 | 280 | The lifetime of an event should be assumed to be only while processing it in the dispatch function and will be freed at any time after the dispatch function is complete. The exception here is if the application by design use only static events that are never modified. 281 | 282 | If the Active object for some reason needs to retain the event, it can extend the lifetime of it in the following ways: 283 | - Post the received event again to itself in the dispatch function 284 | - Post the received event again to itself as an attached event of a time event. 285 | - Add a manual memory reference to the received event using `ACT_mem_refinc`. Take care to decrement the reference again later using `ACT_mem_refdec` to ensure event is freed correctly. 286 | 287 | Forwarding an event to other active objects is allowed. When re-posting inside the dispatch function, Active's memory management will ensure the event is not freed prematurely. 288 | 289 | ### Time events 290 | 291 | A Time event is a special event type used for posting normal events at a later time, either as a one-shot event or as a periodic event. 292 | 293 | Typical use cases for Time events are: 294 | - Having a Active object create a timeout Signal event to oneself while waiting for input in a state machine 295 | - Creating periodic system ticks for one or more Active objects using a Signal or Message event. 296 | - Streaming data (e.g. double buffering) between two Active objects (producer / consumer) using a Message event with dynamic payloads 297 | 298 | Creating a time event takes the following arguments: 299 | - An attached `ACT_Evt *` that will be posted at timer expiry (one shot or periodic) 300 | - The Active object `sender`, which is used for posting the event in the senders context (thread) at timer expiry 301 | - The Active object `receiver`, which will receive the attached event 302 | - An application defined expiry function that can be used to set or replace the attached event before it is being posted at expiry. 303 | 304 | Time events can either be declared statically in the application and initialized by `ACT_TimEvt_init` or created dynamically by `ACT_TimEvt_new`. 305 | The application can freely mix dynamic and static events between the time event and attached event. 306 | 307 | A Time event is started by calling `ACT_TimeEvt_start`, with arguments in milliseconds for initial expiry and subsequent timeouts for periodic expiry. 308 | The timer implementations typically guarantee that *at least* the time given as argument has passed. Asynchronous messaging cannot in itself guarantee that an exact amount of time has passed at processing time. 309 | 310 | When starting a time event, the underlying framework's timer implementation will be started. At expiry, the Active framework posts the time event itself back to the sender in an ISR context. Then, the Active object will process the Time event in its own context/thread, call the expiry function if set and then post the attached event to the receiver. 311 | 312 | To stop a one shot Time event before expiry or to stop a running periodic Time event, the `ACT_TimeEvt_stop` is used. 313 | 314 | ### Asserts 315 | 316 | The Active framework contains asserts on a few elements that are critical for operation in an embedded system: 317 | - Null pointers given as argument for mandatory parameters 318 | - Uninitialized events (only for events of static storage type with members initialized to 0) 319 | - Failed memory allocation for dynamic events 320 | - Failure to post events to a queue (full) or get events from a queue 321 | 322 | Asserts are enabled by default and can be configured in an application configuration file `active_config.h`. 323 | When an assert is triggered, the Active framework calls an application defined assert handler or the default handler. 324 | 325 | The following can be configured: 326 | - Assert enable (default: Enabled) 327 | - Assert function (default: `Active_assertHandler`) 328 | - Assert level to trade off metadata detail level vs ROM size for file names etc (default: standard, no file names) 329 | 330 | For standard and full assert levels, the framework includes the caller function's return address and a best guess for CPU program counter (a few lines of code after). 331 | 332 | ### Usage rules - Dynamic events 333 | 334 | Active objects can only post a dynamic (allocated) events *once*. Dynamic events are freed and garbage collected once processed by all receiving Active objects. 335 | If a dynamic event is to be posted multiple times, the Active object needs to set and remove a memory reference to it using `ACT_mem_refinc` (before first post) and `ACT_mem_refdec` (after all posts are complete). 336 | 337 | Dynamic events should be considered immutable by the sender Active object once posted, as the receiving object might preempt the sender at any time. 338 | 339 | When using time events, any dynamic time events or attached dynamic events will be freed when the timer expires (one shot) or when the timer is stopped by the application, which ever comes first. 340 | 341 | Since there is no guarantee for the Active object to know when a one shot Time event expired and was freed, an application must add its own memory reference to the Time event before starting it (and remove after stopping) if there is a need to stop a dynamic one-shot Time event. 342 | 343 | Periodic events using dynamic attached events can register an expiry function to replace the attached event on an expiration. Previously attached events will be then freed once there are no more references to it. 344 | 345 | 346 | ### Usage rules - Dynamic payloads 347 | 348 | *TODO: Not supported yet!* 349 | 350 | Dynamic payloads can be used with for example the ACT_Message event type to enable a data pipe between active objects. 351 | 352 | Dynamic payloads can be used together with dynamic events only. 353 | If the application is utilizing dynamic payloads in events created with `Object_new()`, (such as `ACT_Message->payload`), the Active object can register a free function during Active object initialiation that will be called just before a dynamic event is freed. This allows the Active object to know that all receivers are done processing the event and the payload object can safely be freed. 354 | 355 | The free function can also be used to let application know that event is being freed, so that any payloads can be safely recycled or re-used. Note that for time events, a time event can be freed even if there are still references to an attached event (in a queue to other Active object). 356 | 357 | ## Ideas Roadmap 358 | 359 | 360 | - Improving asserts (test coverage w/o asserts) 361 | - Make max usage of AO queues available to the application 362 | - Review atomic accesses (e.g. memory references) 363 | - Add more usage examples 364 | - Simplify extension of framework with application defined message types. 365 | - Dynamic payloads 366 | - Pub/sub support (TBD if broker-less or internal broker) with enum based topics to reduce ROM size 367 | - Considering: Service discovery for run-time boot strapping of application 368 | - Considering: Build support for connection oriented Active object "bridges" to external interfaces (UART, Bluetooth Low Energy) supporting serialization for "networked" message passing 369 | 370 | 371 | ## License 372 | 373 | This project is licensed under the MIT License. See LICENSE file for details. 374 | -------------------------------------------------------------------------------- /boards/nucleo_l55.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "core": "stm32", 4 | "cpu": "cortex-m33", 5 | "extra_flags": "-DSTM32L552xx", 6 | "f_cpu": "1100000000L", 7 | "mcu": "stm32l552zet6", 8 | "product_line": "STM32L552xx", 9 | "variant": "STM32L5xx/L552Z(C-E)TxQ_L562ZETxQ", 10 | "zephyr": { 11 | "variant": "nucleo_l552ze_q" 12 | } 13 | }, 14 | "connectivity": [ 15 | "can" 16 | ], 17 | "debug": { 18 | "default_tools": [ 19 | "stlink" 20 | ], 21 | "jlink_device": "STM32L552ZE", 22 | "onboard_tools": [ 23 | "stlink" 24 | ], 25 | "openocd_board": "st_nucleo_l5", 26 | "openocd_target": "stm32l5x", 27 | "svd_path": "STM32L552.svd" 28 | }, 29 | "frameworks": [ 30 | "cmsis", 31 | "stm32cube", 32 | "libopencm3", 33 | "zephyr" 34 | ], 35 | "name": "ST Nucleo L552ZE-Q", 36 | "upload": { 37 | "maximum_ram_size": 196608, 38 | "maximum_size": 524288, 39 | "protocol": "stlink", 40 | "protocols": [ 41 | "jlink", 42 | "cmsis-dap", 43 | "stlink", 44 | "blackmagic", 45 | "mbed" 46 | ] 47 | }, 48 | "url": "https://www.st.com/en/evaluation-tools/nucleo-l552ze-q.html", 49 | "vendor": "ST" 50 | } 51 | -------------------------------------------------------------------------------- /examples/pingpong/include/active_app_messages.h: -------------------------------------------------------------------------------- 1 | #ifndef ACTIVE_APP_MESSAGES_H 2 | #define ACTIVE_APP_MESSAGES_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | enum AppUserSignal 9 | { 10 | PING = ACT_USER_SIG, 11 | PONG, 12 | PINGPONG, 13 | TIMEPING, 14 | TIMEPONG 15 | }; 16 | 17 | enum AppMessage 18 | { 19 | SENSOR_READING 20 | }; 21 | 22 | enum PSRootTopics 23 | { 24 | SYSTEM = USER_TOPIC, 25 | SENSOR, 26 | UI, 27 | MAX_ROOT_TOPICS, 28 | }; 29 | 30 | enum PSSystemTopics 31 | { 32 | REBOOT = MAX_ROOT_TOPICS, 33 | MAX_SYSTEM_TOPICS, 34 | }; 35 | 36 | enum PSSensorTopics 37 | { 38 | TEMPERATURE = MAX_SYSTEM_TOPICS, 39 | }; 40 | 41 | #endif /* ACTIVE_APP_MESSAGES_H */ -------------------------------------------------------------------------------- /examples/pingpong/include/active_config.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heliohm/active/f0ac734140a03f472a09e4a649fdcf34d2298f35/examples/pingpong/include/active_config.h -------------------------------------------------------------------------------- /examples/pingpong/include/pingpong.h: -------------------------------------------------------------------------------- 1 | #ifndef PINGPONG_H 2 | #define PINGPONG_H 3 | 4 | #include 5 | 6 | typedef struct 7 | { 8 | Active super; 9 | } PingPong; 10 | 11 | void PingPong_init(PingPong *const me, ACT_QueueData const *qd, ACT_ThreadData const *td); 12 | 13 | #endif -------------------------------------------------------------------------------- /examples/pingpong/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | PingPong ao_ping, ao_pong; 7 | 8 | /* Ping Active Object */ 9 | ACT_QBUF(pingQbuf, 10); 10 | ACT_Q(pingQ); 11 | ACT_THREAD(pingT); 12 | ACT_THREAD_STACK_DEFINE(pingStack, 512); 13 | ACT_THREAD_STACK_SIZE(pingStackSz, pingStack); 14 | 15 | const static ACT_QueueData qdping = {.maxMsg = 10, 16 | .queBuf = pingQbuf, 17 | .queue = &pingQ}; 18 | 19 | const static ACT_ThreadData tdping = {.thread = &pingT, 20 | .pri = 1, 21 | .stack = pingStack, 22 | .stack_size = pingStackSz}; 23 | 24 | /* Pong Active Object */ 25 | ACT_QBUF(pongQbuf, 10); 26 | ACT_Q(pongQ); 27 | ACT_THREAD(pongT); 28 | ACT_THREAD_STACK_DEFINE(pongStack, 512); 29 | ACT_THREAD_STACK_SIZE(pongStackSz, pongStack); 30 | 31 | const static ACT_QueueData qdpong = {.maxMsg = 10, 32 | .queBuf = pongQbuf, 33 | .queue = &pongQ}; 34 | 35 | const static ACT_ThreadData tdpong = {.thread = &pongT, 36 | .pri = 2, 37 | .stack = pongStack, 38 | .stack_size = pongStackSz}; 39 | 40 | /* Global signals */ 41 | const ACT_Signal pingSignal = {.super = {.type = ACT_SIGNAL, ._sender = ACT_UPCAST(&ao_pong), ._dynamic = false}, .sig = PING}; 42 | const ACT_Signal pongSignal = {.super = {.type = ACT_SIGNAL, ._sender = ACT_UPCAST(&ao_ping), ._dynamic = false}, .sig = PONG}; 43 | 44 | const ACT_Signal timPingSignal = {.super = {.type = ACT_SIGNAL, ._sender = ACT_UPCAST(&ao_pong), ._dynamic = false}, .sig = TIMEPING}; 45 | const ACT_Signal timPongSignal = {.super = {.type = ACT_SIGNAL, ._sender = ACT_UPCAST(&ao_ping), ._dynamic = false}, .sig = TIMEPONG}; 46 | 47 | ACT_Evt *expiryFn(ACT_TimEvt const *const te) 48 | { 49 | if (te->e == EVT_UPCAST(&timPongSignal)) 50 | { 51 | return EVT_UPCAST(&timPingSignal); 52 | } 53 | 54 | return EVT_UPCAST(&timPongSignal); 55 | } 56 | 57 | int main(void) 58 | { 59 | 60 | ACT_Signal *s = ACT_Signal_new(ACT_UPCAST(&ao_pong), PINGPONG); 61 | ACT_Signal *s2 = ACT_Signal_new(ACT_UPCAST(&ao_pong), PINGPONG); 62 | ACT_Signal *s3 = ACT_Signal_new(ACT_UPCAST(&ao_pong), PINGPONG); 63 | 64 | PingPong_init(&ao_ping, &qdping, &tdping); 65 | ACT_start(ACT_UPCAST(&ao_ping)); 66 | 67 | PingPong_init(&ao_pong, &qdpong, &tdpong); 68 | ACT_start(ACT_UPCAST(&ao_pong)); 69 | 70 | ACT_postEvt(ACT_UPCAST(&ao_ping), EVT_UPCAST(&pingSignal)); 71 | ACT_postEvt(ACT_UPCAST(&ao_pong), EVT_UPCAST(s)); 72 | ACT_postEvt(ACT_UPCAST(&ao_ping), EVT_UPCAST(s2)); 73 | ACT_postEvt(ACT_UPCAST(&ao_pong), EVT_UPCAST(s3)); 74 | 75 | // Wait until all some messages are freed (PingPong waits 1sec per msg) 76 | ACT_SLEEPMS(500); 77 | 78 | s = ACT_Signal_new(ACT_UPCAST(&ao_ping), PING); 79 | s2 = ACT_Signal_new(ACT_UPCAST(&ao_pong), PINGPONG); 80 | s3 = ACT_Signal_new(ACT_UPCAST(&ao_pong), PINGPONG); 81 | 82 | ACT_postEvt(ACT_UPCAST(&ao_pong), EVT_UPCAST(s)); 83 | ACT_postEvt(ACT_UPCAST(&ao_pong), EVT_UPCAST(s2)); 84 | ACT_postEvt(ACT_UPCAST(&ao_pong), EVT_UPCAST(s3)); 85 | 86 | ACT_SLEEPMS(500); 87 | 88 | s = ACT_Signal_new(ACT_UPCAST(&ao_ping), PING); 89 | 90 | ACT_TimEvt *te = ACT_TimEvt_new(EVT_UPCAST(s), ACT_UPCAST(&ao_ping), ACT_UPCAST(&ao_pong), expiryFn); 91 | ACT_TimeEvt_start(te, 200, 200); 92 | 93 | ACT_SLEEPMS(1000); 94 | ACT_TimeEvt_stop(te); 95 | ACT_SLEEPMS(1000); 96 | 97 | return 0; 98 | } 99 | -------------------------------------------------------------------------------- /examples/pingpong/pingpong.c: -------------------------------------------------------------------------------- 1 | // pingpong.c 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | extern ACT_Signal pingSignal, pongSignal; 10 | 11 | static void PingPong_dispatch(Active *const me, ACT_Evt const *const e) 12 | { 13 | ACT_SLEEPMS(100); 14 | 15 | if (e->type == ACT_SIGNAL) 16 | { 17 | uint16_t sig = EVT_CAST(e, ACT_Signal)->sig; 18 | switch (sig) 19 | { 20 | case ACT_START_SIG: 21 | { 22 | ACT_DBGPRINT("Starting PingPong...\n\n"); 23 | break; 24 | } 25 | case PING: 26 | { 27 | ACT_DBGPRINT("%p received Ping from %p!\n\n", me, e->_sender); 28 | if (e->_sender) 29 | { 30 | ACT_postEvt(e->_sender, EVT_UPCAST(&pongSignal)); 31 | } 32 | break; 33 | } 34 | case PONG: 35 | { 36 | ACT_DBGPRINT("%p received Pong from %p!\n\n", me, e->_sender); 37 | if (e->_sender) 38 | { 39 | ACT_postEvt(e->_sender, EVT_UPCAST(&pingSignal)); 40 | } 41 | break; 42 | } 43 | case PINGPONG: 44 | { 45 | ACT_DBGPRINT("Pingpong received: %i\n\n", sig); 46 | break; 47 | } 48 | case TIMEPING: 49 | { 50 | ACT_DBGPRINT("%p received TimePing from %p!\n\n", me, e->_sender); 51 | break; 52 | } 53 | case TIMEPONG: 54 | { 55 | ACT_DBGPRINT("%p received TimePong from %p!\n\n", me, e->_sender); 56 | break; 57 | } 58 | 59 | default: 60 | { 61 | ACT_DBGPRINT("What is this? %i\n\n", sig); 62 | break; 63 | } 64 | } 65 | } 66 | } 67 | 68 | void PingPong_init(PingPong *const me, ACT_QueueData const *qd, ACT_ThreadData const *td) 69 | { 70 | ACT_init(&(me->super), PingPong_dispatch, qd, td); 71 | } -------------------------------------------------------------------------------- /include/active.h: -------------------------------------------------------------------------------- 1 | #ifndef ACTIVE_H 2 | #define ACTIVE_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | /*************************** 15 | * Active object definitions 16 | ***************************/ 17 | 18 | /* Upcast Active object implementatios */ 19 | #define ACT_UPCAST(ptr) ((Active *const)(ptr)) 20 | 21 | /* Dispatch handler function pointer type for active object implementations */ 22 | typedef void (*ACT_DispatchFn)(Active *me, ACT_Evt const *const e); 23 | 24 | /* Active object data structure. To be used by active object implementations through polymorphism */ 25 | /** 26 | * @brief Active object data structure. Do not access from application 27 | * 28 | */ 29 | struct active_object 30 | { 31 | ACT_THREADPTR(thread); 32 | ACT_QPTR(queue); 33 | ACT_DispatchFn dispatch; 34 | }; 35 | 36 | /** 37 | * @brief Thread/task related data structures for active object. 38 | * @param thread Pointer to thread data structure declared by ACT_THREAD macro 39 | * @param stack Pointer to task stack declared by ACT_THREAD_STACK 40 | * @param stack_size Size of task stack calculated by ACT_THREAD_STACK_SIZE 41 | * @param pri Task priority (port specific) 42 | */ 43 | struct active_threadData 44 | { 45 | ACT_THREADPTR(thread); 46 | ACT_THREAD_STACKPTR(stack); 47 | size_t stack_size; 48 | int pri; 49 | }; 50 | 51 | /* Queue data structures to set up an active object */ 52 | /** 53 | * @brief Queue related data structures for active object. 54 | * @param queue Pointer to queue data structure declared by ACT_Q 55 | * @param queBuf Pointer to message buffer that holds ACT_Evt pointers sent to active object declared by ACT_QBUF 56 | * @param maxMsg Maximum number of unprocessed messages that the active object message queue can hold 57 | */ 58 | struct active_queueData 59 | { 60 | ACT_QPTR(queue); 61 | char *queBuf; 62 | size_t maxMsg; 63 | }; 64 | 65 | /** 66 | * @brief Initialize an active object data structure beforing using it. 67 | * 68 | * @param me Pointer to the active object data structure to initialize 69 | * @param dispatch Application dispatch function that will receive events 70 | * @param qd Pointer to queue related data needed by active object 71 | * @param td Pointer to thread/task related data needed by active object 72 | */ 73 | void ACT_init(Active *const me, ACT_DispatchFn dispatch, ACT_QueueData const *qd, ACT_ThreadData const *td); 74 | /** 75 | * @brief Start the execution of an active object. When the active object starts, the dispatch function will 76 | * receive a signal with START_SIG payload that can be used to initialize the application. 77 | * 78 | * @param me Pointer to the active object that should be started 79 | */ 80 | void ACT_start(Active *const me); 81 | 82 | /* @private - Thread function for all active obects. Used by Active framework ports */ 83 | void ACT_threadFn(Active *me); 84 | 85 | /* Post event directly to receiver */ 86 | /** 87 | * @brief Post an event directly to a receiving active object 88 | * 89 | * @param receiver Pointer to the receiving active object 90 | * @param e Pointer to the event to post to the receiving active objects queue 91 | * @return int Port specific status code 92 | */ 93 | int ACT_postEvt(Active const *const receiver, ACT_Evt const *const e); 94 | 95 | /* @private: Interface for Active timer to post time back to sender object (delegation)*/ 96 | int ACT_postTimEvt(ACT_TimEvt *te); 97 | 98 | /** 99 | * @brief Helper macros 100 | * 101 | */ 102 | 103 | #define ACT_ARG_UNUSED(param) (void)(param) 104 | 105 | #endif /* ACTIVE_H */ 106 | -------------------------------------------------------------------------------- /include/active_assert.h: -------------------------------------------------------------------------------- 1 | #ifndef _ACTIVE_ASSERT_H_ 2 | #define _ACTIVE_ASSERT_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | /** 12 | * @brief @internal - Used by Active assert module to call application assert handler for Active 13 | * 14 | */ 15 | void Active_assert(void *pc, void *lr, char *test, char *file, uint32_t line); 16 | 17 | typedef void (*activeAssertHandler)(Active_AssertInfo *); 18 | 19 | struct active_assertinfo 20 | { 21 | void *pc; /* Approx CPU program counter */ 22 | void *lr; /* CPU return address */ 23 | char *test; /* Test that failed assert */ 24 | char *file; /* Name of file */ 25 | uint32_t line; /* Line of file */ 26 | }; 27 | 28 | #if ACT_ASSERT_ENABLE == 1 && defined(ACT_ASSERT_LEVEL) 29 | 30 | #if ACT_ASSERT_LEVEL == ACT_ASSERT_LEVEL_MIN 31 | #define ACT_ASSERT_IMPL(test, errMsg, file, line, ...) \ 32 | do \ 33 | { \ 34 | if (!(test)) \ 35 | { \ 36 | Active_assert(NULL, NULL, NULL, NULL, 0); \ 37 | } \ 38 | } while (0) 39 | #elif ACT_ASSERT_LEVEL == ACT_ASSERT_LEVEL_STD 40 | #define ACT_ASSERT_IMPL(test, errMsg, file, line, ...) \ 41 | do \ 42 | { \ 43 | if (!(test)) \ 44 | { \ 45 | void *lr = ACT_GET_RETURN_ADDRESS(); \ 46 | void *pc = ACT_GET_PROGRAM_COUNTER(); \ 47 | Active_assert(pc, lr, NULL, NULL, line); \ 48 | } \ 49 | } while (0) 50 | #elif ACT_ASSERT_LEVEL == ACT_ASSERT_LEVEL_FULL 51 | #define ACT_ASSERT_IMPL(test, errMsg, file, line, ...) \ 52 | do \ 53 | { \ 54 | if (!(test)) \ 55 | { \ 56 | void *lr = ACT_GET_RETURN_ADDRESS(); \ 57 | void *pc = ACT_GET_PROGRAM_COUNTER(); \ 58 | Active_assert(pc, lr, #errMsg, file, line); \ 59 | } \ 60 | } while (0) 61 | #else 62 | #error "Invalid assert level" 63 | #endif /* ACT_ASSERT_LEVEL == ACT_ASSERT_LEVEL_MIN */ 64 | #else 65 | #define ACT_ASSERT_IMPL(test, errMsg, file, line, ...) 66 | 67 | #endif /* defined(ACT_ASSERT_ENABLE) && defined(ACT_ASSERT_LEVEL) */ 68 | 69 | #define ACT_ASSERT(test, errMsg, ...) ACT_ASSERT_IMPL(test, errMsg, ACT_GET_FILE(), ACT_GET_LINE(), ##__VA_ARGS__) 70 | 71 | #endif /* _ACTIVE_ASSERT_H */ 72 | -------------------------------------------------------------------------------- /include/active_config_loader.h: -------------------------------------------------------------------------------- 1 | #ifndef _ACTIVE_CONFIG_LOADER_H_ 2 | #define _ACTIVE_CONFIG_LOADER_H_ 3 | 4 | #if defined __has_include 5 | #if __has_include() 6 | #include 7 | #else /* __has_include() */ 8 | #warning "active_config.h file not found during compilation, using minimal defaults for tests. Check include directories." 9 | #endif /* __has_include() */ 10 | #else /* __has_include */ 11 | #warning __has_include macro note supported in compiler. Edit this file to configure library. 12 | // #include /* Comment in this line and remove warning to set up library 13 | #endif /* __has_include */ 14 | 15 | /** 16 | * @brief Defines for Active memory pool sizes 17 | * 18 | */ 19 | #ifndef ACT_MEM_NUM_SIGNALS 20 | #define ACT_MEM_NUM_SIGNALS 3 21 | #endif 22 | 23 | #ifndef ACT_MEM_NUM_MESSAGES 24 | #define ACT_MEM_NUM_MESSAGES 3 25 | #endif 26 | 27 | #ifndef ACT_MEM_NUM_TIMEEVT 28 | #define ACT_MEM_NUM_TIMEEVT 3 29 | #endif 30 | /* 31 | #ifndef ACT_MEM_NUM_OBJPOOLS 32 | #define ACT_MEM_NUM_OBJPOOLS 1 33 | #endif 34 | */ 35 | 36 | /** 37 | * @brief Defines for Active asserts 38 | * 39 | */ 40 | 41 | /* Minimal assert for debugging - use debugger to catch error */ 42 | #define ACT_ASSERT_LEVEL_MIN 0 43 | /* Standard assert for production, providing stack pointer, link register and line number */ 44 | #define ACT_ASSERT_LEVEL_STD 1 45 | /* Full asserts with failed expression, file name and line number */ 46 | #define ACT_ASSERT_LEVEL_FULL 2 47 | 48 | /* Default: Enable asserts */ 49 | #ifndef ACT_ASSERT_ENABLE 50 | #define ACT_ASSERT_ENABLE 1 51 | #endif 52 | 53 | #ifndef ACT_ASSERT_LEVEL 54 | #define ACT_ASSERT_LEVEL ACT_ASSERT_LEVEL_STD 55 | #endif 56 | 57 | #ifndef ACT_ASSERT_FN 58 | #define ACT_ASSERT_FN Active_assertHandler 59 | #endif 60 | 61 | /** 62 | * @brief Active debug printing with ACT_DBGPRINT() - set to 0 to disable 63 | * 64 | */ 65 | 66 | #ifndef ACT_CFG_DEBUG_PRINT 67 | #define ACT_CFG_DEBUG_PRINT 1 68 | #endif 69 | 70 | #endif /* _ACTIVE_CONFIG_LOADER_H */ -------------------------------------------------------------------------------- /include/active_mem.h: -------------------------------------------------------------------------------- 1 | #ifndef ACTIVE_MEM_H 2 | #define ACTIVE_MEM_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | /* Allocate and initialize new signal from the Active global signal memory pool */ 14 | ACT_Signal *ACT_Signal_new(Active const *const me, uint16_t sig); 15 | /* Allocate and initialize new message from the Active global message memory pool */ 16 | ACT_Message *ACT_Message_new(Active const *const me, uint16_t msgHeader, void *msgPayload, uint16_t payloadLen); 17 | /* Allocate and initialize new time event from the Active global time event memory pool */ 18 | ACT_TimEvt *ACT_TimEvt_new(ACT_Evt *const e, const Active *const me, const Active *const receiver, ACT_TimerExpiryFn expFn); 19 | 20 | /* Allocate memory pool to let active objects create memory pools for message payloads. 21 | The memory buffer provided by the user must be aligned to an N-byte boundary, where N is a power of 2 22 | larger than 2 (i.e. 4, 8, 16, …). 23 | 24 | To ensure that all memory blocks in the buffer are similarly aligned to this boundary, 25 | the object size must also be a multiple of N. 26 | */ 27 | ACT_Mempool *ACT_Mempool_new(void *memBuf, size_t objSize, size_t numObjects); 28 | 29 | /* @internal - used by Active framework to increment reference counter on dynamic event */ 30 | void ACT_mem_refinc(const ACT_Evt *e); 31 | /* @internal - used by Active framework to decrement reference counter on dynamic event and trigger freeing*/ 32 | void ACT_mem_refdec(const ACT_Evt *e); 33 | 34 | /* @internal - used by Active framework GC and tests */ 35 | refCnt_t ACT_mem_getRefCount(const ACT_Evt *const e); 36 | /* @internal - used by Active framework tests */ 37 | uint32_t ACT_mem_Signal_getUsed(); 38 | /* @internal - used by Active framework tests */ 39 | uint32_t ACT_mem_Message_getUsed(); 40 | /* @internal - used by Active framework tests */ 41 | uint32_t ACT_mem_TimeEvt_getUsed(); 42 | 43 | /* Garbage collect / free unreferenced event. Must only be used by application to free events that were 44 | never posted by application or attached to a posted time event */ 45 | void ACT_mem_gc(const ACT_Evt *e); 46 | 47 | #endif /* ACTIVE_MEM_H */ 48 | -------------------------------------------------------------------------------- /include/active_msg.h: -------------------------------------------------------------------------------- 1 | #ifndef ACTIVE_MSG_H 2 | #define ACTIVE_MSG_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #define EVT_UPCAST(ptr) ((ACT_Evt *)(ptr)) 11 | #define EVT_CAST(ptr, type) ((type *)(ptr)) 12 | 13 | /** 14 | * @brief Statically and initialize allocate a signal object with no sender information 15 | * 16 | */ 17 | #define ACT_SIGNAL_DEFINE(symbol, signal) ACT_Signal symbol = \ 18 | {.super = (ACT_Evt){.type = ACT_SIGNAL, ._sender = NULL, ._dynamic = false}, \ 19 | .sig = signal}; 20 | 21 | #define ACT_MESSAGE_DEFINE(symbol, msgheader, msgpayloadptr, msgpayloadLen) ACT_Message symbol = \ 22 | {.super = (ACT_Evt){.type = ACT_MESSAGE, ._sender = NULL, ._dynamic = false}, \ 23 | .header = msgheader, \ 24 | .payload = msgpayloadptr, \ 25 | .payloadLen = msgpayloadLen}; 26 | 27 | /****************************** 28 | * Active object message types 29 | *****************************/ 30 | 31 | /* Base event, never to be used directly. Used for polymorphism of other 32 | message types */ 33 | struct active_event 34 | { 35 | Active *_sender; // Sender of event 36 | ACT_EvtType type; // Type of event 37 | const refCnt_t _refcnt; // Number of memory references for event. Const to avoid application modifying by accident. 38 | const bool _dynamic; // Flag for memory management to know if event is dynamic or static. Const to avoid application modifying by accident. 39 | }; 40 | 41 | /* Time event for posting an attached event on a timer (one shot or periodic) */ 42 | struct active_timevt 43 | { 44 | const ACT_Evt super; // Sender and type (for delegation) and memory tracking 45 | const ACT_Evt *e; // Attached event to send on expiry 46 | const Active *receiver; // AO to send message to if sending a direct message 47 | ACT_TimerExpiryFn expFn; // Expiry function to let AO replace attached event at timer expiry 48 | ACT_Timer timer; // Timer instance belonging to time event 49 | }; 50 | 51 | /* Signals active objects that something has happened. Takes no parameters */ 52 | struct active_signal 53 | { 54 | const ACT_Evt super; 55 | uint16_t sig; 56 | }; 57 | 58 | /* Generic message type consisting of header, payload and payload length */ 59 | struct active_message 60 | { 61 | const ACT_Evt super; 62 | void *payload; 63 | uint16_t header; 64 | uint16_t payloadLen; 65 | }; 66 | 67 | /* Reserved signals by Active framework */ 68 | enum ReservedSignals 69 | { 70 | ACT_START_SIG = 0, /* Signal to AO that it has started */ 71 | ACT_USER_SIG /* First user signal starts here */ 72 | }; 73 | 74 | /** 75 | * @brief Initialize a ACT_Signal structure before using it 76 | * 77 | * Do not call function with signal pointer to an object allocated with ACT_Signal_new 78 | * 79 | * @param s Pointer to the statically allocated ACT_Signal structure to initialize 80 | * @param me The active object that will post the ACT_Signal 81 | * @param sig The signal to send. Must be >= ACT_USER_SIG 82 | * 83 | */ 84 | void ACT_Signal_init(ACT_Signal *s, Active const *const me, uint16_t sig); 85 | 86 | /** 87 | * @brief Initialize a ACT_Message structure before using it 88 | * 89 | * Do not call function with ACT_Message pointer to an object allocated with ACT_Message_new 90 | * 91 | * @param m Pointer to the statically allocated ACT_Message structure to initialize 92 | * @param me The Active object that will post the ACT_Message 93 | * @param header Application defined header 94 | * @param payload Pointer to application defined message payload 95 | * @param payloadLen Length of application defined message payload 96 | * 97 | * @warning The Active object does not know when a message is processed by receivers. 98 | * Take care of ensuring the payload data area is not modified until the application by design has ensured processing is done. 99 | * 100 | */ 101 | void ACT_Message_init(ACT_Message *m, Active const *const me, uint16_t header, void *payload, uint16_t payloadLen); 102 | 103 | /** 104 | * @brief Initialize a Time event structure before using it 105 | * 106 | * Do not call function with time event pointer to an object allocated with ACT_TimEvt_new 107 | * 108 | * @param te A pointer to the statically allocated time event structure to initialize 109 | * @param me The active object that will process the time event and post the attached event 110 | * @param e A pointer to the event to be attached and posted on timer expiry. Can be NULL if expiry function is set. 111 | * @param receiver The receiving active object of the attached event 112 | * @param expFn Optional expiry function to that can update the attached event on timer expiry 113 | */ 114 | void ACT_TimEvt_init(ACT_TimEvt *const te, const Active *const me, ACT_Evt *const e, Active const *const receiver, ACT_TimerExpiryFn expFn); 115 | 116 | #endif /* ACTIVE_MSG_H */ 117 | -------------------------------------------------------------------------------- /include/active_port.h: -------------------------------------------------------------------------------- 1 | #ifndef ACTIVE_PORT_H 2 | #define ACTIVE_PORT_H 3 | 4 | #include 5 | 6 | /******************************* 7 | * Compiler intrinsics & CPU architecture 8 | ******************************/ 9 | 10 | #ifndef __arm__ 11 | #error "This port only supports ARM architectures" 12 | #endif /* __arm__ */ 13 | 14 | #ifdef __GNUC__ 15 | 16 | /** 17 | * @brief GCC specific functions for assert handling 18 | * 19 | */ 20 | 21 | /** 22 | * @brief External function call (not inlined) to get caller address around assert (approx PC). 23 | * Static for now that gives every compilation unit a copy 24 | * 25 | */ 26 | #pragma GCC diagnostic ignored "-Wunused-function" 27 | static void *__attribute__((noinline)) active_get_pc() 28 | { 29 | return __builtin_return_address(0); 30 | } 31 | 32 | /* Get program counter */ 33 | #define ACT_GET_PROGRAM_COUNTER() active_get_pc() 34 | /* Get return address of current function */ 35 | #define ACT_GET_RETURN_ADDRESS() __builtin_return_address(0) 36 | /* Base file name (i.e. without full path */ 37 | #define ACT_GET_FILE() __BASE_FILE__ 38 | /* Line in source file */ 39 | #define ACT_GET_LINE() __LINE__ 40 | 41 | #else 42 | #error "No supported compiler found" 43 | #endif /* __GNUC__ */ 44 | 45 | /******************************* 46 | * Platform port 47 | ******************************/ 48 | 49 | #include 50 | 51 | #ifdef __ZEPHYR__ 52 | 53 | /** 54 | * @brief Zephyr RTOS port of a queue used by the Active framework 55 | * 56 | */ 57 | 58 | /* Declare a message queue with name qSym. 59 | Used by application to set up a queue */ 60 | #define ACT_Q(qSym) struct k_msgq qSym 61 | 62 | /* @internal - Declare a pointer to a message queue with name qPtrSym. Used by generic active header file */ 63 | #define ACT_QPTR(qPtrSym) struct k_msgq *qPtrSym 64 | 65 | /* Declare a message queue buffer with name bufName and room for maxMsg messages. 66 | Used by application to set up a buffer for the queue */ 67 | #define ACT_QBUF(bufSym, maxMsg) _Alignas(ACT_Evt *) char bufSym[sizeof(ACT_Evt *) * maxMsg] 68 | 69 | /* @internal - Get an entry from the message queue. Used by active framework to get Events in Active objects. 70 | Function blocks Active object forever until message is put on its queue */ 71 | #define ACT_Q_GET(qPtrSym, evtPtrPtr) k_msgq_get((struct k_msgq *)qPtrSym, evtPtrPtr, K_FOREVER) 72 | #define ACT_Q_GET_SUCCESS_STATUS 0 73 | 74 | /* @internal - Put an entry on the message queue. Used by active framework to post Events to Active objects. 75 | Function does not block but returns immediately */ 76 | #define ACT_Q_PUT(qPtrSym, evtPtrPtr) k_msgq_put((struct k_msgq *)qPtrSym, evtPtrPtr, K_NO_WAIT); 77 | #define ACT_Q_PUT_SUCCESS_STATUS 0 78 | 79 | /** 80 | * @brief Zephyr RTOS port of threads used by the Active framework 81 | * 82 | */ 83 | 84 | /* Declare a thread 85 | Used by the application to set up a thread for the active object */ 86 | #define ACT_THREAD(threadSym) struct k_thread threadSym 87 | 88 | /* @internal - Declare a pointer to thread handler. Used by generic active header file */ 89 | #define ACT_THREADPTR(threadPtrSym) k_tid_t threadPtrSym 90 | 91 | /* Declare *and* initialize a thread stack. 92 | Used by the application to set up a stack for the active object thread */ 93 | #define ACT_THREAD_STACK_DEFINE(stackSym, size) K_THREAD_STACK_DEFINE(stackSym, size) 94 | 95 | /* @internal - Declare a thread stack pointer */ 96 | #define ACT_THREAD_STACKPTR(stackPtrSym) k_thread_stack_t *stackPtrSym; 97 | 98 | /* Declares a size_t type with name stackSizeSym and initialize with the size of the thread stack. 99 | stackSym must point to the stack array directly. 100 | 101 | Used by the application to calculate the true stack size (excl overhead) 102 | */ 103 | #define ACT_THREAD_STACK_SIZE(stackSizeSym, stackSym) const size_t stackSizeSym = K_THREAD_STACK_SIZEOF(stackSym) 104 | 105 | /* Returns a thread priority. Higher number -> lower pri in Zephyr. 106 | Main thread has defauly pri 0. Non-negative numbers are preemptive threads 107 | https://docs.zephyrproject.org/latest/kernel/services/threads/index.html#thread-priorities */ 108 | #define ACT_THREAD_PRI(x) (x) 109 | 110 | /** 111 | * @brief Zephyr RTOS port of a timer 112 | * 113 | */ 114 | 115 | /* @internal - Declare a timer. Used by Time events */ 116 | #define ACT_TIMER(timerSym) struct k_timer timerSym 117 | 118 | /* @internal - Declare a pointer to a timer */ 119 | #define ACT_TIMERPTR(timerPtrSym) struct k_timer *timerPtrSym 120 | 121 | /* @internal - Initialize an ACT_Timer struct */ 122 | #define ACT_TIMER_INIT(timerPtr, expiryFn) k_timer_init(&(timerPtr->impl), expiryFn, NULL) 123 | 124 | /* @internal - Set ACT_Timer application defined parameter */ 125 | #define ACT_TIMER_PARAM_SET(timerPtr, param) k_timer_user_data_set(&(timerPtr->impl), (void *)param) 126 | /* @internal - Get application defined parameter from native (port) timer. */ 127 | #define ACT_TIMER_PARAM_GET(nativeTimerPtr) k_timer_user_data_get(nativeTimerPtr) 128 | 129 | /* @internal - Start ACT_Timer */ 130 | #define ACT_TIMER_START(timerPtr, durationMs, periodMs) k_timer_start(&(timerPtr->impl), K_MSEC(durationMs), K_MSEC(periodMs)) 131 | /* @internal - Stop ACT_Timer */ 132 | #define ACT_TIMER_STOP(timerPtr) k_timer_stop(&(timerPtr->impl)); 133 | 134 | /** 135 | * @brief Zepphyr RTOS port of a memory pool with fixed size objects 136 | * 137 | */ 138 | 139 | /* @internal - Declare *and* initialize a static memory pool */ 140 | #define ACT_MEMPOOL_DEFINE(memPoolSym, type, numObjects) K_MEM_SLAB_DEFINE(memPoolSym, sizeof(type), numObjects, _Alignof(type)) 141 | 142 | /* @internal - Get number of used entries in memory pool. Used for testing */ 143 | #define ACT_MEMPOOL_USED_GET(memPoolPtr) (memPoolPtr)->num_used 144 | 145 | /* @internal - Allocate memory for an object from a specified memory pool */ 146 | #define ACT_MEMPOOL_ALLOC(memPoolPtr, dataPptr) k_mem_slab_alloc(memPoolPtr, (void *)dataPptr, K_NO_WAIT) 147 | /* @internal - Free memory for an object from a specified memory pool */ 148 | #define ACT_MEMPOOL_FREE(memPoolPtr, dataPptr) k_mem_slab_free(memPoolPtr, (void *)dataPptr) 149 | /* @internal - Allocation return status on success */ 150 | #define ACT_MEMPOOL_ALLOC_SUCCESS_STATUS 0 151 | 152 | /* Declare a memory pool */ 153 | // #define ACT_MEMPOOL(memPoolSym) struct k_mem_slab memPoolSym; 154 | 155 | /** 156 | * @brief Zephyr port of debug printing 157 | * 158 | */ 159 | 160 | /* Debug print macro. Using ##__VA_ARGS__ GNU extension to swallow comma on missing extra parameters. Supported by GCC, Clang, XLC */ 161 | #if ACT_CFG_DEBUG_PRINT == 1 162 | #define ACT_DBGPRINT(fmt, ...) printk(fmt, ##__VA_ARGS__) 163 | #else 164 | #define ACT_DBGPRINT(fmt, ...) 165 | #endif 166 | 167 | /** 168 | * @brief Zephyr port of thread sleep / time functions (for examples and tests) 169 | * 170 | */ 171 | 172 | /* Sleep for ms milliseconds */ 173 | #define ACT_SLEEPMS(ms) k_msleep(ms) 174 | 175 | /* Sleep for us microseconds. Use with caution - minimum resolution in Zephyr RTOS 176 | set by clock hardware and CONFIG_SYS_CLOCK_TICKS_PER_SEC */ 177 | #define ACT_SLEEPUS(us) k_usleep(us) 178 | 179 | /* Get current time in ms - used by tests */ 180 | #define ACT_TIMEMS_GET() k_uptime_get() 181 | 182 | /******************************* 183 | * Platform specific functions 184 | **************************** */ 185 | 186 | /** 187 | * @brief Zephyr k_timer expiry function. Called by timer ISR when a k_timer expires. 188 | * Used as adapter between native timer and Active Time event 189 | * 190 | */ 191 | void ACT_NativeTimerExpiryFn(ACT_TIMERPTR(nativeTimerPtr)); 192 | 193 | #else 194 | #error "No supported port of Active library found" 195 | #endif // __ZEPHYR__ 196 | 197 | #endif /* ACTIVE_PORT_H */ -------------------------------------------------------------------------------- /include/active_psmsg.h: -------------------------------------------------------------------------------- 1 | #ifndef _ACTIVE_PSMSG_H_ 2 | #define _ACTIVE_PSMSG_H_ 3 | 4 | /*********************************** 5 | * Publish / subscribe functionality 6 | **********************************/ 7 | 8 | enum ReservedTopics 9 | { 10 | WILDCARD = 0, 11 | USER_TOPIC // First user topic for pub sub starts here 12 | }; 13 | 14 | /* Topic data structure for PubSub functionality */ 15 | typedef struct topic Topic; /* Forward declaration */ 16 | 17 | struct topic 18 | { 19 | uint16_t node; 20 | Topic *child; 21 | }; 22 | // ACT_CASSERT(sizeof(Topic) == 8, "Topic type is not the right size."); 23 | 24 | /* Message to send to a pubsub active object to subscribe or 25 | unsubscribe to a message */ 26 | typedef struct SubMessage 27 | { 28 | ACT_Evt super; 29 | Topic *topic; 30 | } SubMessage; 31 | // ACT_CASSERT(sizeof(SubMessage) == 12, "SubMessage type is not the right size."); 32 | 33 | /* Wrapper message to send to pubsub active object to publish a message. 34 | Includes the metadata of the message and a pointer to the message itself */ 35 | typedef struct PubMessage 36 | { 37 | ACT_Evt super; /* Publish message metadata */ 38 | ACT_Evt *e; /* Attached message to be published to subscribers */ 39 | Topic *topic; /* Topic to publish message to */ 40 | bool sticky; /* Should the message be retained for new subscribers? */ 41 | } PubMessage; 42 | 43 | // ACT_CASSERT(sizeof(PubMessage) == 20, "PubMessage type is not the right size."); 44 | 45 | #endif /* _ACTIVE_PSMSG_H_ */ 46 | -------------------------------------------------------------------------------- /include/active_timer.h: -------------------------------------------------------------------------------- 1 | #ifndef ACTIVE_TIMER_H 2 | #define ACTIVE_TIMER_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | /** 11 | * @brief Data structure for Active timer part of time events. Do not access members directly. 12 | * 13 | */ 14 | struct active_timerData 15 | { 16 | ACT_TIMER(impl); 17 | size_t durationMs; 18 | size_t periodMs; 19 | volatile bool running; 20 | volatile bool sync; 21 | }; 22 | 23 | /** 24 | * @brief Internal: Function to initialize timer part of time events. Must not be used by application. 25 | * 26 | * @param te 27 | */ 28 | void ACT_Timer_init(ACT_TimEvt *te); 29 | 30 | /** 31 | * @brief Internal: Callback to handle timer expiry. Must not be used by application. 32 | * Used by Active framework to let native timer implementation call the generic framework. 33 | * 34 | * @param te The Time event whose timer expired 35 | */ 36 | void ACT_Timer_expiryCB(ACT_TimEvt *const te); 37 | 38 | /** 39 | * @brief Internal: Callback to process time events. Must not be used by application. 40 | * Used by Active framework to process expired time events and post attached events. 41 | */ 42 | void ACT_TimeEvt_dispatch(ACT_TimEvt *te); 43 | 44 | /** 45 | * @brief Time event expiry function prototype. 46 | * When a time event expires, an optional expiry function given during initialization is called to 47 | * let the application replace the attached event or keep track of state in the application. 48 | * 49 | * The expiry function runs in context of the Active object @a me that created the event. 50 | * 51 | * @param te The time event that expired 52 | * 53 | * @return Pointer to event that will replace existing attached event. Existing attached dynamic events are freed. 54 | * @return (ACT_Evt*)NULL - Indicate to framework to keep the existing attached event. 55 | * 56 | */ 57 | typedef ACT_Evt *(*ACT_TimerExpiryFn)(ACT_TimEvt const *const te); 58 | 59 | /** 60 | * @brief Start a time event. The attached event will be posted when the timer expires 61 | * 62 | * This routine starts a time event, which when expires will call an optional user defined expiry function and then post the attached event. 63 | * When a one shot time event expires, a dynamic time event and attached dynamic events will be freed after processing. 64 | * 65 | * @param te The initialized time event to start. Time event must be allocated and 66 | * initialized with ACT_TimEvt_new (dynamic allocated event) or with ACT_TimEvt_init (static event) 67 | * @param durationMs The (minimum) duration until attached event is posted 68 | * @param periodMs The (minimum) duration until subsequent posting of event. Set to 0 for a one-shot timer. 69 | * 70 | * @warning Do not call function with a dynamic time event pointer that is previously stopped or expired, as those are freed. 71 | */ 72 | void ACT_TimeEvt_start(ACT_TimEvt *te, size_t durationMs, size_t periodMs); 73 | 74 | /** 75 | * @brief Stop a time event 76 | * 77 | * This routine stops a running time event. 78 | * All dynamic time events and dynamic attached events will be freed by stopping the time event. (see header for more details) 79 | * 80 | * @param te pointer to the time event to stop 81 | * 82 | * @return true: The timer was running when it was stopped. 83 | * @return false: The timer was already expired (one-shot) or already stopped. 84 | * 85 | * @warning Do not stop a one-shot dynamic timer event once it expired, as the time event and attached event will be freed. 86 | * - Add an additional reference on it using ACT_mem_refinc before starting and decrement it using ACT_mem_refdec after stopping. 87 | * - After expiry, only the time event pointer will be valid for stopping time event; the attached event will be freed. 88 | * @warning Do not use dynamic timer event again after stopping it, as the time event and attached event will be freed. 89 | * @warning Do not stop a timer event from an ISR. 90 | **/ 91 | bool ACT_TimeEvt_stop(ACT_TimEvt *te); 92 | 93 | #endif /* ACTIVE_TIMER_H */ 94 | -------------------------------------------------------------------------------- /include/active_types.h: -------------------------------------------------------------------------------- 1 | #ifndef ACTIVE_TYPES_H 2 | #define ACTIVE_TYPES_H 3 | 4 | #include 5 | 6 | /* Base event for polymorphism of other objects */ 7 | typedef struct active_event ACT_Evt; 8 | typedef struct active_signal ACT_Signal; 9 | typedef struct active_message ACT_Message; 10 | typedef struct active_timevt ACT_TimEvt; 11 | 12 | /* Types of events supported by Active framework */ 13 | typedef enum evtType 14 | { 15 | ACT_UNUSED = 0, // For asserts on uninitialized events of static storage class 16 | ACT_SIGNAL, 17 | ACT_MESSAGE, 18 | ACT_TIMEVT 19 | } ACT_EvtType; 20 | 21 | /* Event memory reference count */ 22 | typedef atomic_ushort refCnt_t; 23 | 24 | /* The active object (actors) in the program */ 25 | typedef struct active_object Active; 26 | 27 | /* Thread data structure for an Active object */ 28 | typedef struct active_threadData ACT_ThreadData; 29 | 30 | /* Queue data structure for an Active object */ 31 | typedef struct active_queueData ACT_QueueData; 32 | 33 | /* Timer data struture type */ 34 | typedef struct active_timerData ACT_Timer; 35 | 36 | /* Memory pool type */ 37 | typedef struct active_mempoolData ACT_Mempool; 38 | 39 | /* Assert handler function prototype */ 40 | typedef struct active_assertinfo Active_AssertInfo; 41 | #endif /* ACTIVE_TYPES_H */ 42 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | [platformio] 11 | 12 | [env] 13 | # Setup 14 | platform = ststm32 15 | board = nucleo_l55 16 | framework = zephyr 17 | 18 | #Print monitor 19 | monitor_port = /dev/tty.usbmodem14203 20 | monitor_speed = 115200 21 | 22 | #Static code analysis 23 | check_tool = cppcheck, clangtidy 24 | check_flags = 25 | cppcheck: --enable=all #--check-config 26 | clangtidy: -fix-errors -format-style=mozilla 27 | check_skip_packages = yes 28 | 29 | #Build 30 | build_unflags = 31 | -std=c99 32 | build_flags = 33 | -std=c11 34 | 35 | [examples] 36 | # Select which example to run 37 | selected_example = pingpong 38 | 39 | [env:debug] 40 | #Build 41 | build_type = debug 42 | build_src_filter = +<*> +<../examples/${examples.selected_example}/*> 43 | build_flags = 44 | -Iexamples/${examples.selected_example}/include 45 | 46 | 47 | 48 | [env:test] 49 | #Testing (Unity) 50 | #debug_test = test_integration_active_timer 51 | test_build_src = yes -------------------------------------------------------------------------------- /src/active.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void ACT_threadFn(Active *const me) 4 | { 5 | ACT_ASSERT(me != NULL, "Active object is null)"); 6 | 7 | static const ACT_SIGNAL_DEFINE(startSignal, ACT_START_SIG); 8 | 9 | // Initialize active object 10 | me->dispatch(me, EVT_UPCAST(&startSignal)); 11 | 12 | while (1) 13 | { 14 | ACT_Evt *e = NULL; 15 | /* Blocking wait for events */ 16 | int status = ACT_Q_GET(me->queue, &e); 17 | 18 | ACT_ASSERT(status == ACT_Q_GET_SUCCESS_STATUS, "ACT_Evt was not retrieved. Error: %i", status); 19 | ACT_ARG_UNUSED(status); 20 | 21 | ACT_ASSERT(e != NULL, "ACT_Evt pointer is null"); 22 | 23 | // Timer events are not processed by the AO dispatch function. 24 | // Instead the attached event is processed in the context of the 25 | // active object that started the timer event 26 | if (e->type == ACT_TIMEVT) 27 | { 28 | ACT_TimEvt *te = EVT_CAST(e, ACT_TimEvt); 29 | ACT_TimeEvt_dispatch(te); 30 | } 31 | // Default: Let AO process event 32 | else 33 | { 34 | me->dispatch(me, e); 35 | } 36 | 37 | // Decrement reference counter added by ACT_postEvt after event is processed 38 | ACT_mem_refdec(e); 39 | } 40 | } 41 | 42 | int ACT_postEvt(Active const *const receiver, ACT_Evt const *const e) 43 | { 44 | ACT_ASSERT(receiver != NULL, "Receiver is null"); 45 | ACT_ASSERT(e != NULL, "ACT_Evt object is null"); 46 | ACT_ASSERT(e->type != ACT_UNUSED, "ACT_Evt object is not initialized"); 47 | 48 | /* Adding a memory ref must be done before putting it on the receiving queue, 49 | in case receiving object is higher priority than running object 50 | (which would decrement the ref counter while processingand potentially free it) */ 51 | ACT_mem_refinc(e); 52 | 53 | int status = ACT_Q_PUT(receiver->queue, &e); 54 | ACT_ASSERT(status == ACT_Q_PUT_SUCCESS_STATUS, "Event not put on queue %p. Error: %i\n\n", receiver->queue, status); 55 | 56 | // Event was not sent, remove memory ref again 57 | if (status != ACT_Q_PUT_SUCCESS_STATUS) 58 | { 59 | ACT_mem_refdec(e); 60 | } 61 | 62 | return status; 63 | } 64 | 65 | inline int ACT_postTimEvt(ACT_TimEvt *te) 66 | { 67 | // Post time event to AO sender's queue so AO framework can 68 | // update and post the attached event in the sender's context 69 | return ACT_postEvt(te->super._sender, EVT_UPCAST(te)); 70 | } -------------------------------------------------------------------------------- /src/active_assert.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* Default assert handler. Can be replaced in application active_config.h 4 | Active_AssertInfo* is a pointer to an auto variable; the aplication needs to do a copy of any members of interest */ 5 | void Active_assertHandler(Active_AssertInfo *assertinfo) 6 | { 7 | while (1) 8 | ; 9 | } 10 | 11 | /* Avoid redefining symbol */ 12 | #if ACT_ASSERT_FN != Active_assertHandler 13 | static extern activeAssertHandler ACT_ASSERT_FN; 14 | #endif /* ACT_ASSERT_FN != Active_assertHandler */ 15 | 16 | void Active_assert(void *pc, void *lr, char *test, char *file, uint32_t line) 17 | { 18 | Active_AssertInfo a = { 19 | .pc = pc, 20 | .lr = lr, 21 | .test = test, 22 | .file = file, 23 | .line = line}; 24 | 25 | ACT_ASSERT_FN(&a); 26 | } -------------------------------------------------------------------------------- /src/active_mem.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | struct active_mempoolData 5 | { 6 | ACT_MEMPOOL(impl) 7 | }; 8 | */ 9 | 10 | static ACT_MEMPOOL_DEFINE(Signal_Mem, ACT_Signal, ACT_MEM_NUM_SIGNALS); 11 | static ACT_MEMPOOL_DEFINE(Message_Mem, ACT_Message, ACT_MEM_NUM_MESSAGES); 12 | static ACT_MEMPOOL_DEFINE(TimeEvt_Mem, ACT_TimEvt, ACT_MEM_NUM_TIMEEVT); 13 | // static ACT_MEMPOOL_DEFINE(MemPool_Mem, ACT_Mempool, ACT_MEM_NUM_OBJPOOLS); 14 | 15 | /* @private - used by Active framework tests */ 16 | uint32_t ACT_mem_Signal_getUsed() 17 | { 18 | return ACT_MEMPOOL_USED_GET(&Signal_Mem); 19 | } 20 | /* @private - used by Active framework tests */ 21 | uint32_t ACT_mem_Message_getUsed() 22 | { 23 | return ACT_MEMPOOL_USED_GET(&Message_Mem); 24 | } 25 | /* @private - used by Active framework tests */ 26 | uint32_t ACT_mem_TimeEvt_getUsed() 27 | { 28 | return ACT_MEMPOOL_USED_GET(&TimeEvt_Mem); 29 | } 30 | static bool ACT_mem_isDynamic(const ACT_Evt *const e) 31 | { 32 | return e->_dynamic; 33 | } 34 | 35 | void ACT_mem_refinc(const ACT_Evt *e) 36 | { 37 | if (ACT_mem_isDynamic(e)) 38 | { 39 | atomic_fetch_add((refCnt_t *)&(e->_refcnt), 1); 40 | ACT_ASSERT(atomic_load(&(e->_refcnt)) != 0, "Overflow in reference counter. ACT_Evt ptr: %p", (void *)e); 41 | } 42 | } 43 | 44 | void ACT_mem_refdec(const ACT_Evt *e) 45 | { 46 | if (ACT_mem_isDynamic(e)) 47 | { 48 | ACT_ASSERT(atomic_load(&(e->_refcnt)) > 0, "Underflow in reference counter. ACT_Evt ptr: %p", (void *)e); 49 | atomic_fetch_sub((refCnt_t *)&(e->_refcnt), 1); 50 | ACT_mem_gc(e); 51 | } 52 | } 53 | 54 | refCnt_t ACT_mem_getRefCount(const ACT_Evt *const e) 55 | { 56 | return atomic_load(&e->_refcnt); 57 | } 58 | 59 | static void ACT_mem_setDynamic(const ACT_Evt *const e) 60 | { 61 | // Cast away const, set dynamic flag to true 62 | bool *dyn = (bool *)&(e->_dynamic); 63 | *dyn = true; 64 | } 65 | 66 | void ACT_mem_gc(const ACT_Evt *e) 67 | { 68 | if (ACT_mem_getRefCount(e) != 0) 69 | { 70 | return; 71 | } 72 | if (!ACT_mem_isDynamic(e)) 73 | { 74 | return; 75 | } 76 | 77 | switch (e->type) 78 | { 79 | case ACT_SIGNAL: 80 | { 81 | ACT_ASSERT(ACT_MEMPOOL_USED_GET(&Signal_Mem) > 0, "No Signal events to free"); 82 | ACT_MEMPOOL_FREE(&Signal_Mem, &e); 83 | break; 84 | } 85 | case ACT_MESSAGE: 86 | { 87 | ACT_ASSERT(ACT_MEMPOOL_USED_GET(&Message_Mem) > 0, "No Message events to free"); 88 | ACT_MEMPOOL_FREE(&Message_Mem, &e); 89 | break; 90 | } 91 | case ACT_TIMEVT: 92 | { 93 | ACT_ASSERT(ACT_MEMPOOL_USED_GET(&TimeEvt_Mem) > 0, "No time events to free"); 94 | ACT_MEMPOOL_FREE(&TimeEvt_Mem, &e); 95 | break; 96 | } 97 | default: 98 | ACT_ASSERT(0, "Invalid event type"); 99 | } 100 | } 101 | 102 | ACT_Signal *ACT_Signal_new(Active const *const me, uint16_t sig) 103 | { 104 | 105 | ACT_Signal *s = NULL; 106 | int status = ACT_MEMPOOL_ALLOC(&Signal_Mem, &s); 107 | ACT_ASSERT(status == ACT_MEMPOOL_ALLOC_SUCCESS_STATUS, "Failed to allocate new Signal"); 108 | 109 | // Initialize signal as static 110 | ACT_Signal_init(s, me, sig); 111 | 112 | // Set event dynamic *after* initialization 113 | ACT_mem_setDynamic(EVT_UPCAST(s)); 114 | 115 | ACT_ARG_UNUSED(status); 116 | return s; 117 | } 118 | 119 | ACT_Message *ACT_Message_new(Active const *const me, uint16_t msgHeader, void *msgPayload, uint16_t payloadLen) 120 | { 121 | ACT_Message *m = NULL; 122 | int status = ACT_MEMPOOL_ALLOC(&Message_Mem, &m); 123 | ACT_ASSERT(status == ACT_MEMPOOL_ALLOC_SUCCESS_STATUS, "Failed to allocate new Message"); 124 | 125 | // Initialize message as static 126 | ACT_Message_init(m, me, msgHeader, msgPayload, payloadLen); 127 | 128 | // Set event dynamic *after* initialization 129 | ACT_mem_setDynamic(EVT_UPCAST(m)); 130 | 131 | ACT_ARG_UNUSED(status); 132 | return m; 133 | } 134 | 135 | ACT_TimEvt *ACT_TimEvt_new(ACT_Evt *const e, const Active *const me, const Active *const receiver, ACT_TimerExpiryFn expFn) 136 | { 137 | 138 | ACT_TimEvt *te = NULL; 139 | int status = ACT_MEMPOOL_ALLOC(&TimeEvt_Mem, &te); 140 | ACT_ASSERT(status == ACT_MEMPOOL_ALLOC_SUCCESS_STATUS, "Failed to allocate new Time Event"); 141 | 142 | ACT_TimEvt_init(te, me, e, receiver, expFn); 143 | 144 | // Set event dynamic *after* initialization 145 | ACT_mem_setDynamic(EVT_UPCAST(te)); 146 | 147 | ACT_ARG_UNUSED(status); 148 | return te; 149 | } 150 | /* 151 | ACT_Mempool *ACT_Mempool_new(void *memBuf, size_t objSize, size_t numObjects) 152 | { 153 | 154 | ACT_Mempool *mem = NULL; 155 | int status_pool = ACT_MEMPOOL_ALLOC(&MemPool_Mem, &mem); 156 | ACT_ASSERT(status_pool == ACT_MEMPOOL_ALLOC_SUCCESS_STATUS, "Failed to allocate new Memory pool"); 157 | 158 | int status_init = k_mem_slab_init(&(mem->impl), memBuf, objSize, numObjects); 159 | ACT_ASSERT(status_init == ACT_MEMPOOL_ALLOC_SUCCESS_STATUS, "Failed to init new Memory pool"); 160 | 161 | ACT_ARG_UNUSED(status_pool); 162 | ACT_ARG_UNUSED(status_init); 163 | 164 | return mem; 165 | } 166 | 167 | void ACT_Mempool_free(ACT_Mempool *memPool) 168 | { 169 | k_mem_slab_free(&MemPool_Mem, (void *)&memPool); 170 | } 171 | 172 | void *Object_new(ACT_Mempool *poolptr) 173 | { 174 | void *obj; 175 | int status = k_mem_slab_alloc(&(poolptr->impl), &obj, K_NO_WAIT); 176 | ACT_ASSERT(status == ACT_MEMPOOL_ALLOC_SUCCESS_STATUS, "Failed to allocate new Object"); 177 | 178 | ACT_ARG_UNUSED(status); 179 | return obj; 180 | } 181 | */ -------------------------------------------------------------------------------- /src/active_msg.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static void ACT_Evt_init(ACT_Evt *const e, Active const *const me, ACT_EvtType type) 5 | { 6 | 7 | ACT_ASSERT(e != NULL, "ACT_Evt is NULL"); 8 | ACT_ASSERT(me != NULL, "Active object sender not set for event"); 9 | 10 | e->type = type; 11 | e->_sender = (Active *)me; 12 | 13 | // Cast away const to clear dynamic field 14 | bool *dyn = (bool *)&(e->_dynamic); 15 | *dyn = false; 16 | 17 | // Cast away const to clear reference count 18 | refCnt_t *cnt = (refCnt_t *)&(e->_refcnt); 19 | *cnt = 0; 20 | } 21 | void ACT_Signal_init(ACT_Signal *s, Active const *const me, uint16_t sig) 22 | { 23 | ACT_ASSERT(sig >= (uint16_t)ACT_USER_SIG, "Signal type is invalid user defined signal"); 24 | 25 | ACT_Evt_init(EVT_UPCAST(s), me, ACT_SIGNAL); 26 | s->sig = sig; 27 | } 28 | 29 | void ACT_Message_init(ACT_Message *m, Active const *const me, uint16_t header, void *payload, uint16_t payloadLen) 30 | { 31 | ACT_Evt_init(EVT_UPCAST(m), me, ACT_MESSAGE); 32 | m->header = header; 33 | m->payload = payload; 34 | m->payloadLen = payloadLen; 35 | } 36 | 37 | void ACT_TimEvt_init(ACT_TimEvt *const te, const Active *const me, ACT_Evt *const e, Active const *const receiver, ACT_TimerExpiryFn expFn) 38 | { 39 | 40 | ACT_ASSERT((e == NULL && expFn != NULL) || e != NULL, "ACT_Evt is null without expiry function set"); 41 | 42 | // Initialize timer event 43 | ACT_Evt_init(EVT_UPCAST(te), me, ACT_TIMEVT); 44 | 45 | te->e = e; 46 | te->receiver = receiver; 47 | te->expFn = expFn; 48 | ACT_Timer_init(te); 49 | } 50 | -------------------------------------------------------------------------------- /src/active_port.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef __ZEPHYR__ 4 | 5 | #include 6 | 7 | /* Zephyr puts limits on aligment of queue buffer and size of queue content (ACT_Evt *): 8 | https://docs.zephyrproject.org/latest/reference/kernel/data_passing/message_queues.html */ 9 | 10 | _Static_assert(sizeof(ACT_Evt) == 12, "ACT_Evt type is not the right size."); 11 | _Static_assert(_Alignof(ACT_Evt *) == 4, "Alignment of ACT_Evt pointer type must be a power of 2"); 12 | 13 | /* Zephyr limitations on memory slab alignment and object size: 14 | https://docs.zephyrproject.org/latest/kernel/memory_management/slabs.html */ 15 | 16 | _Static_assert(sizeof(ACT_TimEvt) == 96, "ACT_TimEvt type is not the right size."); 17 | _Static_assert(_Alignof(ACT_TimEvt) == 8, "Alignment ACT_TimEvt type"); 18 | 19 | _Static_assert(sizeof(ACT_Message) == 20, "ACT_Message type is not the right size."); 20 | _Static_assert(_Alignof(ACT_Message) == 4, "Alignment ACT_Message type"); 21 | 22 | _Static_assert(sizeof(ACT_Signal) == 16, "ACT_Signal type is not the right size."); 23 | _Static_assert(_Alignof(ACT_Signal) == 4, "Alignment ACT_Signal type"); 24 | 25 | /* Zephyr thread entry function */ 26 | static void active_entry(void *arg1, void *arg2, void *arg3) 27 | { 28 | ACT_threadFn((Active *const)arg1); 29 | } 30 | 31 | void ACT_init(Active *const me, ACT_DispatchFn dispatch, ACT_QueueData const *qd, ACT_ThreadData const *td) 32 | { 33 | ACT_ASSERT(me != NULL, "Active object is null)"); 34 | ACT_ASSERT(dispatch != NULL, "Dispatch handler is null"); 35 | 36 | me->dispatch = dispatch; 37 | 38 | k_msgq_init(qd->queue, qd->queBuf, sizeof(ACT_Evt *), qd->maxMsg); 39 | 40 | me->queue = qd->queue; 41 | me->thread = k_thread_create(td->thread, td->stack, td->stack_size, active_entry, (void *)me, NULL, NULL, td->pri, 0, K_FOREVER); 42 | } 43 | 44 | void ACT_start(Active *const me) 45 | { 46 | k_thread_start(me->thread); 47 | } 48 | 49 | void ACT_NativeTimerExpiryFn(ACT_TIMERPTR(nativeTimerPtr)) 50 | { 51 | ACT_TimEvt *te = (ACT_TimEvt *)ACT_TIMER_PARAM_GET(nativeTimerPtr); 52 | ACT_Timer_expiryCB(te); 53 | } 54 | 55 | /* Hook to stop program on assert failures */ 56 | void assert_post_action(const char *file, unsigned int line) 57 | { 58 | while (1) 59 | { 60 | } 61 | } 62 | 63 | #endif /* __ZEPHYR__ */ -------------------------------------------------------------------------------- /src/active_ps.c: -------------------------------------------------------------------------------- 1 | /* Pub / Sub protocol */ 2 | #include 3 | 4 | void ACT_publish(PubMessage *msg) 5 | { 6 | 7 | Topic *topic = msg->topic; 8 | // SubscriberList *lst = fetchSubscribers(topic); 9 | } 10 | 11 | void ACT_subscribe(Active *me, Topic *topic) 12 | { 13 | } 14 | -------------------------------------------------------------------------------- /src/active_timer.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef enum TimerType 4 | { 5 | ONESHOT, 6 | PERIODIC 7 | } TimerType; 8 | 9 | static TimerType getTimerType(const ACT_Timer *const timer) 10 | { 11 | return timer->periodMs == 0 ? ONESHOT : PERIODIC; 12 | } 13 | 14 | // Runs in ISR context - called from underlying port/framework 15 | void ACT_Timer_expiryCB(ACT_TimEvt *const te) 16 | { 17 | 18 | // Post time event 19 | ACT_postTimEvt(te); 20 | 21 | if (getTimerType(&(te->timer)) == ONESHOT) 22 | { 23 | te->timer.running = false; 24 | te->timer.sync = false; // Timer stop synchronization 25 | 26 | // One shot time events - decrement reference immediately 27 | // after posting - posting adds new ref that keeps it alive 28 | ACT_mem_refdec(EVT_UPCAST(te)); 29 | } 30 | } 31 | 32 | void ACT_TimeEvt_dispatch(ACT_TimEvt *const te) 33 | { 34 | if (te->expFn) 35 | { 36 | // Let Active objects expiry function update attached event 37 | ACT_Evt *updated_evt = te->expFn(te); 38 | 39 | if (updated_evt) 40 | { 41 | ACT_Evt *last_evt = (ACT_Evt *)te->e; 42 | te->e = updated_evt; 43 | 44 | // Add ref on new event (might be same as in initial timer start) to persist across posts 45 | ACT_mem_refinc(te->e); 46 | 47 | // Clear ref on last attached event (set by start or previous exp fn) and GC it. 48 | if (last_evt) 49 | { 50 | ACT_mem_refdec(last_evt); 51 | } 52 | } 53 | // Ensure not both initial attached event and return from expiry function was NULL 54 | ACT_ASSERT(te->e != NULL, "Attached event is NULL"); 55 | } 56 | 57 | ACT_postEvt(te->receiver, te->e); 58 | 59 | /* One shot events: Remove ref for freeing once posted */ 60 | if (getTimerType(&(te->timer)) == ONESHOT) 61 | { 62 | ACT_mem_refdec(te->e); 63 | } 64 | } 65 | 66 | /* @private. Initialize the timer part of a Time ACT_Evt. Not to be called by the application */ 67 | void ACT_Timer_init(ACT_TimEvt *te) 68 | { 69 | ACT_Timer *tp = &(te->timer); 70 | ACT_TIMER_INIT(tp, ACT_NativeTimerExpiryFn); 71 | ACT_TIMER_PARAM_SET(tp, te); 72 | 73 | te->timer.running = false; 74 | te->timer.sync = false; 75 | } 76 | 77 | void ACT_TimeEvt_start(ACT_TimEvt *te, size_t durationMs, size_t periodMs) 78 | { 79 | ACT_ASSERT(te != NULL, "Timer event is NULL"); 80 | ACT_ASSERT(te->super.type == ACT_TIMEVT, "Timer event not initialized properly"); 81 | 82 | if (te->timer.running) 83 | { 84 | return; 85 | } 86 | // Add extra reference on timer event and any attached events 87 | // to have a trigger to free them when stopping 88 | 89 | ACT_mem_refinc(EVT_UPCAST(te)); 90 | 91 | // Add reference to any attached event 92 | if (te->e) 93 | { 94 | ACT_mem_refinc(te->e); 95 | } 96 | 97 | te->timer.running = true; 98 | te->timer.sync = false; 99 | te->timer.durationMs = durationMs; 100 | te->timer.periodMs = periodMs; 101 | 102 | ACT_Timer *tp = &(te->timer); 103 | ACT_TIMER_START(tp, durationMs, periodMs); 104 | } 105 | 106 | bool ACT_TimeEvt_stop(ACT_TimEvt *te) 107 | { 108 | bool ret = false; 109 | 110 | if (!te->timer.running) 111 | { 112 | return ret; 113 | } 114 | // Stop timer 115 | te->timer.sync = true; 116 | 117 | ACT_Timer *tp = &(te->timer); 118 | ACT_TIMER_STOP(tp); 119 | 120 | // Timer was stopped and did not expire first -> cleanup dynamic event 121 | 122 | te->timer.running = false; 123 | ret = true; 124 | 125 | // Timer did not expire (ISR) between after calling ACT_TimeEvt_stop 126 | // and timer actually stopping 127 | 128 | // Decrement reference count on time event and attached event 129 | if (te->timer.sync == true) 130 | { 131 | ACT_mem_refdec(EVT_UPCAST(te)); 132 | 133 | if (te->e) 134 | { 135 | ACT_mem_refdec(te->e); 136 | } 137 | } 138 | 139 | return ret; 140 | } 141 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /test/test_integration_active_object/active_config.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heliohm/active/f0ac734140a03f472a09e4a649fdcf34d2298f35/test/test_integration_active_object/active_config.h -------------------------------------------------------------------------------- /test/test_integration_active_object/test_integration_active_object.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static void ao_emptyDispatch(Active *me, ACT_Evt const *const e) 5 | { 6 | } 7 | 8 | static ACT_QBUF(testQBuf, 1); 9 | static ACT_Q(testQ); 10 | static ACT_THREAD(testT); 11 | static ACT_THREAD_STACK_DEFINE(testTStack, 512); 12 | static ACT_THREAD_STACK_SIZE(testTStackSz, testTStack); 13 | 14 | const static ACT_QueueData qdtest = {.maxMsg = 10, 15 | .queBuf = testQBuf, 16 | .queue = &testQ}; 17 | 18 | const static ACT_ThreadData tdtest = {.thread = &testT, 19 | .pri = 1, 20 | .stack = testTStack, 21 | .stack_size = testTStackSz}; 22 | 23 | static bool wasInitSigReceived = false; 24 | 25 | static void ao_initDispatch(Active *me, ACT_Evt const *const e) 26 | { 27 | if (e->type == ACT_SIGNAL && EVT_CAST(e, ACT_Signal)->sig == ACT_START_SIG) 28 | { 29 | wasInitSigReceived = true; 30 | } 31 | } 32 | 33 | static void test_function_active_init() 34 | { 35 | Active ao; 36 | ACT_init(&ao, ao_emptyDispatch, &qdtest, &tdtest); 37 | 38 | TEST_ASSERT_EQUAL(ao_emptyDispatch, ao.dispatch); 39 | TEST_ASSERT_EQUAL(qdtest.queue, ao.queue); 40 | } 41 | 42 | static void test_function_active_start() 43 | { 44 | Active ao; 45 | // Todo: Refactor test to terminate thread after test 46 | ACT_init(&ao, ao_initDispatch, &qdtest, &tdtest); 47 | ACT_start(&ao); 48 | ACT_SLEEPMS(50); 49 | TEST_ASSERT_TRUE(wasInitSigReceived); 50 | } 51 | 52 | void main() 53 | { 54 | ACT_SLEEPMS(2000); 55 | 56 | UNITY_BEGIN(); 57 | 58 | RUN_TEST(test_function_active_init); 59 | RUN_TEST(test_function_active_start); 60 | 61 | UNITY_END(); 62 | } -------------------------------------------------------------------------------- /test/test_integration_active_object_post/active_config.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heliohm/active/f0ac734140a03f472a09e4a649fdcf34d2298f35/test/test_integration_active_object_post/active_config.h -------------------------------------------------------------------------------- /test/test_integration_active_object_post/test_integration_active_object_post.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static ACT_QBUF(testQBuf, 1); 5 | static ACT_Q(testQ); 6 | static ACT_THREAD(testT); 7 | static ACT_THREAD_STACK_DEFINE(testTStack, 512); 8 | static ACT_THREAD_STACK_SIZE(testTStackSz, testTStack); 9 | 10 | const static ACT_QueueData qdtest = {.maxMsg = 1, 11 | .queBuf = testQBuf, 12 | .queue = &testQ}; 13 | 14 | const static ACT_ThreadData tdtest = {.thread = &testT, 15 | .pri = 1, 16 | .stack = testTStack, 17 | .stack_size = testTStackSz}; 18 | 19 | enum TestUserSignal 20 | { 21 | TEST_SIG = ACT_USER_SIG, 22 | TIME_SIG 23 | }; 24 | 25 | Active ao; 26 | ACT_Signal testSig, timeSig; 27 | ACT_TimEvt timeEvt; 28 | 29 | static bool wasTestSigReceived = false, wasTimeSigReceived = false; 30 | 31 | static void ao_dispatch(Active *me, ACT_Evt const *const e) 32 | { 33 | if ((e->type == ACT_SIGNAL) && (EVT_CAST(e, ACT_Signal)->sig == TEST_SIG) && (e == EVT_UPCAST(&testSig))) 34 | { 35 | wasTestSigReceived = true; 36 | } 37 | 38 | if ((e->type == ACT_SIGNAL) && (EVT_CAST(e, ACT_Signal)->sig == TIME_SIG) && (e == EVT_UPCAST(&timeSig))) 39 | { 40 | wasTimeSigReceived = true; 41 | } 42 | } 43 | 44 | static void test_function_active_post() 45 | { 46 | 47 | ACT_postEvt(&ao, EVT_UPCAST(&testSig)); 48 | ACT_SLEEPMS(50); 49 | 50 | TEST_ASSERT_TRUE(wasTestSigReceived); 51 | } 52 | 53 | static void test_function_active_post_timeevt() 54 | { 55 | ACT_TimeEvt_start(&timeEvt, 50, 0); 56 | ACT_SLEEPMS(100); 57 | 58 | TEST_ASSERT_TRUE(wasTimeSigReceived); 59 | } 60 | 61 | void main() 62 | { 63 | ACT_SLEEPMS(2000); 64 | 65 | UNITY_BEGIN(); 66 | 67 | ACT_init(&ao, ao_dispatch, &qdtest, &tdtest); 68 | ACT_start(&ao); 69 | 70 | ACT_Signal_init(&testSig, &ao, TEST_SIG); 71 | ACT_Signal_init(&timeSig, &ao, TIME_SIG); 72 | ACT_TimEvt_init(&timeEvt, &ao, EVT_UPCAST(&timeSig), &ao, NULL); 73 | 74 | RUN_TEST(test_function_active_post); 75 | RUN_TEST(test_function_active_post_timeevt); 76 | 77 | UNITY_END(); 78 | } -------------------------------------------------------------------------------- /test/test_integration_active_timer/active_config.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heliohm/active/f0ac734140a03f472a09e4a649fdcf34d2298f35/test/test_integration_active_timer/active_config.h -------------------------------------------------------------------------------- /test/test_integration_active_timer/test_integration_active_timer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static ACT_QBUF(testQBuf, 1); 5 | static ACT_Q(testQ); 6 | static ACT_THREAD(testT); 7 | static ACT_THREAD_STACK_DEFINE(testTStack, 512); 8 | static ACT_THREAD_STACK_SIZE(testTStackSz, testTStack); 9 | 10 | const static ACT_QueueData qdtest = {.maxMsg = 1, 11 | .queBuf = testQBuf, 12 | .queue = &testQ}; 13 | 14 | const static ACT_ThreadData tdtest = {.thread = &testT, 15 | .pri = 1, 16 | .stack = testTStack, 17 | .stack_size = testTStackSz}; 18 | 19 | enum TestUserSignal 20 | { 21 | ACT_SIGNAL_UNDEFINED = ACT_USER_SIG, 22 | ONESHOT_STATIC_STARTSTOP, 23 | ONESHOT_DYNAMIC_STARTSTOP, 24 | ONESHOT_STATIC_EXPIRE, 25 | ONESHOT_DYNAMIC_EXPIRE, 26 | ONESHOT_DYNAMIC_EXPIRE_STOP, 27 | ONESHOT_STATIC_EXPIRE_EXPFN_1, 28 | ONESHOT_STATIC_EXPIRE_EXPFN_2, 29 | ONESHOT_DYNAMIC_EXPIRE_EXPFN_1, 30 | ONESHOT_DYNAMIC_EXPIRE_EXPFN_2, 31 | PERIODIC_STATIC_EXPIRE, 32 | PERIODIC_DYNAMIC_EXPIRE 33 | }; 34 | 35 | Active ao; 36 | 37 | int64_t timeReceivedMs = 0, lastTimeReceivedMs = 0; 38 | enum TestUserSignal expectedSignal = ACT_SIGNAL_UNDEFINED; 39 | uint16_t eventsReceived, correctEventsReceived; 40 | uint32_t expectedMsgPayload = 0; 41 | 42 | static void ao_dispatch(Active *me, ACT_Evt const *const e) 43 | { 44 | 45 | // Ignore start signal 46 | if (e->type == ACT_SIGNAL && EVT_CAST(e, ACT_Signal)->sig == ACT_START_SIG) 47 | { 48 | return; 49 | } 50 | 51 | // Count total events 52 | eventsReceived++; 53 | 54 | // Ignore events other than signal type in test 55 | if (e->type == ACT_SIGNAL) 56 | { 57 | ACT_Signal *s = EVT_CAST(e, ACT_Signal); 58 | 59 | // Count number of expected events 60 | if (expectedSignal == s->sig) 61 | { 62 | 63 | correctEventsReceived++; 64 | lastTimeReceivedMs = timeReceivedMs; 65 | timeReceivedMs = ACT_TIMEMS_GET(); 66 | } 67 | } 68 | else if (e->type == ACT_MESSAGE) 69 | { 70 | ACT_Message *m = EVT_CAST(e, ACT_Message); 71 | if (m->header == 0xBABA && expectedMsgPayload == *(uint32_t *)m->payload) 72 | { 73 | correctEventsReceived++; 74 | } 75 | } 76 | } 77 | 78 | static void test_function_timer_static_oneshot_startstop() 79 | { 80 | 81 | const size_t timeOutMs = 20; 82 | const size_t periodMs = 0; 83 | 84 | expectedSignal = ONESHOT_STATIC_STARTSTOP; 85 | 86 | ACT_Signal sig; 87 | ACT_Signal_init(&sig, &ao, expectedSignal); 88 | 89 | ACT_TimEvt te; 90 | ACT_TimEvt_init(&te, &ao, EVT_UPCAST(&sig), &ao, NULL); 91 | ACT_TimeEvt_start(&te, timeOutMs, periodMs); 92 | 93 | /* Test timer flag is indicating to be running */ 94 | TEST_ASSERT_TRUE(te.timer.running); 95 | 96 | /* Test timer indicated is was running when it was stopped */ 97 | bool status = ACT_TimeEvt_stop(&te); 98 | TEST_ASSERT_TRUE(status); 99 | 100 | /* Test timer flag is indicating to not be running */ 101 | TEST_ASSERT_FALSE(te.timer.running); 102 | 103 | ACT_SLEEPMS(timeOutMs); 104 | 105 | /* Test timer actually did stop - no event received */ 106 | TEST_ASSERT_EQUAL_UINT16(0, eventsReceived); 107 | TEST_ASSERT_EQUAL_UINT16(0, correctEventsReceived); 108 | } 109 | 110 | static void test_function_timer_dynamic_oneshot_startstop() 111 | { 112 | 113 | const size_t timeOutMs = 20; 114 | const size_t periodMs = 0; 115 | 116 | size_t sigUsed = ACT_mem_Signal_getUsed(); 117 | size_t timeEvtUse = ACT_mem_TimeEvt_getUsed(); 118 | 119 | expectedSignal = ONESHOT_DYNAMIC_STARTSTOP; 120 | 121 | ACT_Signal *sig; 122 | sig = ACT_Signal_new(&ao, ONESHOT_DYNAMIC_STARTSTOP); 123 | 124 | ACT_TimEvt *te; 125 | te = ACT_TimEvt_new(EVT_UPCAST(sig), &ao, &ao, NULL); 126 | 127 | ACT_TimeEvt_start(te, timeOutMs, periodMs); 128 | 129 | /* Test timer flag is indicating to be running */ 130 | TEST_ASSERT_TRUE(te->timer.running); 131 | 132 | /* Test timer indicated is was running when it was stopped */ 133 | bool status = ACT_TimeEvt_stop(te); 134 | TEST_ASSERT_TRUE(status); 135 | 136 | /* Test timer flag is indicating to not be running */ 137 | TEST_ASSERT_FALSE(te->timer.running); 138 | 139 | ACT_SLEEPMS(timeOutMs); 140 | 141 | /* Test timer actually did stop - no event received */ 142 | TEST_ASSERT_EQUAL_UINT16(0, eventsReceived); 143 | TEST_ASSERT_EQUAL_UINT16(0, correctEventsReceived); 144 | 145 | /* Test memory management correctly freeing events when stopping prematurely */ 146 | TEST_ASSERT_EQUAL(sigUsed, ACT_mem_Signal_getUsed()); 147 | TEST_ASSERT_EQUAL(timeEvtUse, ACT_mem_TimeEvt_getUsed()); 148 | } 149 | 150 | /* Test normal operation of a one shot timer that expires normally */ 151 | static void test_function_timer_static_oneshot_expire() 152 | { 153 | 154 | const size_t timeOutMs = 10; 155 | const size_t periodMs = 0; 156 | 157 | expectedSignal = ONESHOT_STATIC_EXPIRE; 158 | 159 | ACT_Signal sig; 160 | ACT_Signal_init(&sig, &ao, expectedSignal); 161 | 162 | ACT_TimEvt te; 163 | ACT_TimEvt_init(&te, &ao, EVT_UPCAST(&sig), &ao, NULL); 164 | 165 | /* Test timer starting and expiring */ 166 | int64_t timeBeforeTest = ACT_TIMEMS_GET(); 167 | 168 | ACT_TimeEvt_start(&te, timeOutMs, periodMs); 169 | 170 | /* Test timer flag is indicating to be running */ 171 | TEST_ASSERT_TRUE(te.timer.running); 172 | 173 | // Run to expiration 174 | ACT_SLEEPMS(timeOutMs); 175 | 176 | /* Test timing is correct */ 177 | TEST_ASSERT_EQUAL_INT32(timeOutMs, (int32_t)(timeReceivedMs - timeBeforeTest)); 178 | 179 | /* Test timer flag is indicating to not longer be running */ 180 | TEST_ASSERT_FALSE(te.timer.running); 181 | 182 | /* Test timer event posted attached event */ 183 | TEST_ASSERT_EQUAL_UINT16(1, eventsReceived); 184 | TEST_ASSERT_EQUAL_UINT16(1, correctEventsReceived); 185 | 186 | /* Test no more events are received => one shot working as intended */ 187 | ACT_SLEEPMS(1 * timeOutMs); 188 | 189 | TEST_ASSERT_EQUAL_INT32(timeOutMs, (int32_t)(timeReceivedMs - timeBeforeTest)); 190 | TEST_ASSERT_EQUAL_UINT16(1, correctEventsReceived); 191 | 192 | /* Stopping an expired one shot timer returns that timer is stopped already */ 193 | bool status = ACT_TimeEvt_stop(&te); 194 | TEST_ASSERT_FALSE(status); 195 | } 196 | 197 | /* Test normal operation of a one shot timer that expires normally */ 198 | static void test_function_timer_dynamic_oneshot_expire() 199 | { 200 | const size_t timeOutMs = 10; 201 | const size_t periodMs = 0; 202 | 203 | size_t sigUsed = ACT_mem_Signal_getUsed(); 204 | size_t timeEvtUse = ACT_mem_TimeEvt_getUsed(); 205 | 206 | expectedSignal = ONESHOT_DYNAMIC_EXPIRE; 207 | 208 | ACT_Signal *sig = ACT_Signal_new(&ao, ONESHOT_DYNAMIC_EXPIRE); 209 | 210 | ACT_TimEvt *te = ACT_TimEvt_new(EVT_UPCAST(sig), &ao, &ao, NULL); 211 | 212 | /* Test timer starting and expiring */ 213 | int64_t timeBeforeTest = ACT_TIMEMS_GET(); 214 | 215 | ACT_TimeEvt_start(te, timeOutMs, periodMs); 216 | 217 | // Run to expiration 218 | ACT_SLEEPMS(timeOutMs); 219 | 220 | /* Test timing is correct */ 221 | TEST_ASSERT_EQUAL_INT32(timeOutMs, (int32_t)(timeReceivedMs - timeBeforeTest)); 222 | 223 | /* Test timer event posted attached event */ 224 | TEST_ASSERT_EQUAL_UINT16(1, eventsReceived); 225 | TEST_ASSERT_EQUAL_UINT16(1, correctEventsReceived); 226 | 227 | /* Test no more events are received => one shot working as intended */ 228 | ACT_SLEEPMS(1 * timeOutMs); 229 | 230 | TEST_ASSERT_EQUAL_INT32(timeOutMs, (int32_t)(timeReceivedMs - timeBeforeTest)); 231 | TEST_ASSERT_EQUAL_UINT16(1, correctEventsReceived); 232 | 233 | /* Test memory management correctly freeing events when stopping prematurely */ 234 | TEST_ASSERT_EQUAL(sigUsed, ACT_mem_Signal_getUsed()); 235 | TEST_ASSERT_EQUAL(timeEvtUse, ACT_mem_TimeEvt_getUsed()); 236 | } 237 | 238 | // Test checking if dynamic time event has expired before stopping 239 | static void test_function_timer_dynamic_oneshot_expire_stop() 240 | { 241 | const size_t timeOutMs = 10; 242 | const size_t sleepMs = 9; 243 | const size_t periodMs = 0; 244 | 245 | size_t sigUsed = ACT_mem_Signal_getUsed(); 246 | size_t timeEvtUse = ACT_mem_TimeEvt_getUsed(); 247 | 248 | expectedSignal = ONESHOT_DYNAMIC_EXPIRE_STOP; 249 | 250 | ACT_Signal *sig; 251 | ACT_TimEvt *te; 252 | 253 | size_t loops = 0; 254 | while (1) 255 | { 256 | sig = ACT_Signal_new(&ao, expectedSignal); 257 | te = ACT_TimEvt_new(EVT_UPCAST(sig), &ao, &ao, NULL); 258 | // Add manual reference on time event to prevent it from being freed when expiring 259 | ACT_mem_refinc(EVT_UPCAST(te)); 260 | 261 | ACT_TimeEvt_start(te, timeOutMs, periodMs); 262 | 263 | // Linearly increasing sleep+busy wait period until hitting timer expiry (potential race condition) 264 | ACT_SLEEPMS(sleepMs); 265 | loops++; 266 | atomic_t i = loops; 267 | while (atomic_dec(&i)) 268 | { 269 | __ASM("NOP"); 270 | } 271 | 272 | bool status = ACT_TimeEvt_stop(te); 273 | ACT_mem_refdec(EVT_UPCAST(te)); 274 | 275 | if (status) 276 | { 277 | break; 278 | } 279 | } 280 | /* Test memory management correctly freeing events when stopping prematurely */ 281 | TEST_ASSERT_EQUAL(sigUsed, ACT_mem_Signal_getUsed()); 282 | TEST_ASSERT_EQUAL(timeEvtUse, ACT_mem_TimeEvt_getUsed()); 283 | } 284 | 285 | static ACT_Evt *static_oneshot_expFn(const ACT_TimEvt *const te) 286 | { 287 | const static ACT_SIGNAL_DEFINE(sig_timer_static_expfn_1, ONESHOT_STATIC_EXPIRE_EXPFN_1); 288 | 289 | ACT_Signal *s = EVT_CAST(te->e, ACT_Signal); 290 | 291 | // Replace event 292 | if (s->sig == ONESHOT_STATIC_EXPIRE_EXPFN_2) 293 | { 294 | return EVT_UPCAST(&sig_timer_static_expfn_1); 295 | } 296 | return (ACT_Evt *)NULL; 297 | } 298 | 299 | /* Test expiry function is working */ 300 | static void test_function_timer_static_expFn() 301 | { 302 | const size_t timeOutMs = 10; 303 | const size_t periodMs = 0; 304 | 305 | expectedSignal = ONESHOT_STATIC_EXPIRE_EXPFN_1; 306 | 307 | ACT_Signal sig; 308 | ACT_Signal_init(&sig, &ao, ONESHOT_STATIC_EXPIRE_EXPFN_1); 309 | 310 | ACT_TimEvt te; 311 | ACT_TimEvt_init(&te, &ao, EVT_UPCAST(&sig), &ao, static_oneshot_expFn); 312 | 313 | ACT_TimeEvt_start(&te, timeOutMs, periodMs); 314 | 315 | // Run to expiration 316 | ACT_SLEEPMS(timeOutMs); 317 | 318 | /* Test timer event posted attached event */ 319 | TEST_ASSERT_EQUAL_UINT16(1, eventsReceived); 320 | TEST_ASSERT_EQUAL_UINT16(1, correctEventsReceived); 321 | 322 | // Re-initialize attached signal that is now stopped with signal that should be replaced 323 | ACT_Signal_init(&sig, &ao, ONESHOT_STATIC_EXPIRE_EXPFN_2); 324 | 325 | ACT_TimeEvt_start(&te, timeOutMs, periodMs); 326 | 327 | // Run to expiration 328 | ACT_SLEEPMS(timeOutMs); 329 | 330 | /* Test timer event posted new event from expiry function */ 331 | TEST_ASSERT_EQUAL_UINT16(2, eventsReceived); 332 | TEST_ASSERT_EQUAL_UINT16(2, correctEventsReceived); 333 | } 334 | 335 | static ACT_Evt *dynamic_oneshot_expFn(const ACT_TimEvt *const te) 336 | { 337 | 338 | ACT_Signal *s = EVT_CAST(te->e, ACT_Signal); 339 | 340 | // Replace event 341 | if (s->sig == ONESHOT_DYNAMIC_EXPIRE_EXPFN_2) 342 | { 343 | return EVT_UPCAST(ACT_Signal_new(&ao, ONESHOT_DYNAMIC_EXPIRE_EXPFN_1)); 344 | } 345 | // Default: Don't replace event 346 | return (ACT_Evt *)NULL; 347 | } 348 | 349 | /* Test expiry function is working */ 350 | static void test_function_timer_dynamic_expFn() 351 | { 352 | const size_t timeOutMs = 10; 353 | const size_t periodMs = 0; 354 | 355 | size_t sigUsed = ACT_mem_Signal_getUsed(); 356 | size_t timeEvtUse = ACT_mem_TimeEvt_getUsed(); 357 | 358 | expectedSignal = ONESHOT_DYNAMIC_EXPIRE_EXPFN_1; 359 | 360 | ACT_Signal *sig = ACT_Signal_new(&ao, ONESHOT_DYNAMIC_EXPIRE_EXPFN_1); 361 | 362 | ACT_TimEvt *te = ACT_TimEvt_new(EVT_UPCAST(sig), &ao, &ao, dynamic_oneshot_expFn); 363 | 364 | ACT_TimeEvt_start(te, timeOutMs, periodMs); 365 | 366 | // Run to expiration 367 | ACT_SLEEPMS(timeOutMs); 368 | 369 | /* Test timer event posted attached event */ 370 | TEST_ASSERT_EQUAL_UINT16(1, eventsReceived); 371 | TEST_ASSERT_EQUAL_UINT16(1, correctEventsReceived); 372 | 373 | // Re-initialize attached signal that is now stopped with signal that should be replaced 374 | sig = ACT_Signal_new(&ao, ONESHOT_DYNAMIC_EXPIRE_EXPFN_2); 375 | te = ACT_TimEvt_new(EVT_UPCAST(sig), &ao, &ao, dynamic_oneshot_expFn); 376 | 377 | ACT_TimeEvt_start(te, timeOutMs, periodMs); 378 | 379 | // Run to expiration 380 | ACT_SLEEPMS(timeOutMs); 381 | 382 | /* Test timer event posted new event from expiry function */ 383 | TEST_ASSERT_EQUAL_UINT16(2, eventsReceived); 384 | TEST_ASSERT_EQUAL_UINT16(2, correctEventsReceived); 385 | 386 | /* Test memory management correctly freeing events when stopping prematurely and expiring */ 387 | TEST_ASSERT_EQUAL(sigUsed, ACT_mem_Signal_getUsed()); 388 | TEST_ASSERT_EQUAL(timeEvtUse, ACT_mem_TimeEvt_getUsed()); 389 | } 390 | 391 | static void test_function_timer_static_periodic_expire() 392 | { 393 | const size_t timeOutMs = 10; 394 | const size_t periodMs = 10; 395 | 396 | const size_t numEvents = 10; 397 | 398 | expectedSignal = PERIODIC_STATIC_EXPIRE; 399 | 400 | ACT_Signal sig; 401 | ACT_Signal_init(&sig, &ao, PERIODIC_STATIC_EXPIRE); 402 | 403 | ACT_TimEvt te; 404 | ACT_TimEvt_init(&te, &ao, EVT_UPCAST(&sig), &ao, NULL); 405 | 406 | ACT_TimeEvt_start(&te, timeOutMs, periodMs); 407 | /* Timer is indicating to be running */ 408 | TEST_ASSERT_TRUE(te.timer.running); 409 | 410 | ACT_SLEEPMS(numEvents * periodMs); 411 | 412 | bool status = ACT_TimeEvt_stop(&te); 413 | 414 | /* Test status returned as running */ 415 | TEST_ASSERT_TRUE(status); 416 | 417 | /* Correct number of events fired */ 418 | TEST_ASSERT_EQUAL_UINT16(numEvents, correctEventsReceived); 419 | 420 | /* Time between two last events is correct */ 421 | TEST_ASSERT_EQUAL_INT32(periodMs, (int32_t)(timeReceivedMs - lastTimeReceivedMs)); 422 | 423 | ACT_SLEEPMS(1 * periodMs); 424 | 425 | /* Timer is stopped - no more events are fired */ 426 | TEST_ASSERT_FALSE(te.timer.running); 427 | TEST_ASSERT_EQUAL_UINT16(numEvents, correctEventsReceived); 428 | } 429 | 430 | static void test_function_timer_dynamic_periodic_expire() 431 | { 432 | const size_t timeOutMs = 10; 433 | const size_t periodMs = 10; 434 | 435 | size_t sigUsed = ACT_mem_Signal_getUsed(); 436 | size_t timeEvtUse = ACT_mem_TimeEvt_getUsed(); 437 | 438 | const size_t numEvents = 10; 439 | 440 | expectedSignal = PERIODIC_DYNAMIC_EXPIRE; 441 | 442 | ACT_Signal *sig; 443 | sig = ACT_Signal_new(&ao, PERIODIC_DYNAMIC_EXPIRE); 444 | 445 | ACT_TimEvt *te; 446 | te = ACT_TimEvt_new(EVT_UPCAST(sig), &ao, &ao, NULL); 447 | 448 | ACT_TimeEvt_start(te, timeOutMs, periodMs); 449 | /* Timer is indicating to be running */ 450 | TEST_ASSERT_TRUE(te->timer.running); 451 | 452 | ACT_SLEEPMS(numEvents * periodMs); 453 | 454 | bool status = ACT_TimeEvt_stop(te); 455 | 456 | /* Test status returned as running */ 457 | TEST_ASSERT_TRUE(status); 458 | 459 | /* Correct number of events fired */ 460 | TEST_ASSERT_EQUAL_UINT16(numEvents, correctEventsReceived); 461 | 462 | /* Time between two last events is correct */ 463 | TEST_ASSERT_EQUAL_INT32(periodMs, (int32_t)(timeReceivedMs - lastTimeReceivedMs)); 464 | 465 | ACT_SLEEPMS(1 * periodMs); 466 | 467 | /* Timer is stopped - no more events are fired */ 468 | TEST_ASSERT_FALSE(te->timer.running); 469 | TEST_ASSERT_EQUAL_UINT16(numEvents, correctEventsReceived); 470 | 471 | /* Test memory management correctly freeing events after stopping */ 472 | TEST_ASSERT_EQUAL(sigUsed, ACT_mem_Signal_getUsed()); 473 | TEST_ASSERT_EQUAL(timeEvtUse, ACT_mem_TimeEvt_getUsed()); 474 | } 475 | 476 | static uint32_t buf0[] = {0xDEADBEEF, 0xDEADBEEF}; 477 | static uint32_t buf1[] = {0xBADDCAFE, 0xBAAAAAAD}; 478 | static const char bufSz = 2; 479 | static uint16_t header = 0xBABA; 480 | static uint32_t *writeBuf = buf0; 481 | static char writeBufIdx = 0; 482 | 483 | ACT_Evt *msgExpFn(const ACT_TimEvt *const te) 484 | { 485 | 486 | static ACT_Message *msg = NULL; 487 | 488 | if (msg) 489 | { 490 | /* Test same event returned last time called is given in expiry function */ 491 | TEST_ASSERT_EQUAL_PTR(msg, te->e); 492 | } 493 | // Prepare message with old write buffer 494 | msg = ACT_Message_new(&ao, header, writeBuf, bufSz); 495 | 496 | // Swap write buffer. 497 | writeBuf = (writeBuf == buf0) ? buf1 : buf0; 498 | writeBufIdx = 0; 499 | 500 | // Return message for updating attached event and posting to receiver 501 | return EVT_UPCAST(msg); 502 | } 503 | 504 | /* Test a periodic dynamic time evt that updates attached message event every expiration. 505 | This is typical for a producer / consumer setting where the consumer is the dispatch function and the 506 | producter is the test function */ 507 | static void test_function_timer_dynamic_periodic_expire_expFn() 508 | { 509 | 510 | const size_t timeOutMs = 10; 511 | const size_t periodMs = 10; 512 | 513 | size_t msgUsed = ACT_mem_Message_getUsed(); 514 | size_t timeEvtUse = ACT_mem_TimeEvt_getUsed(); 515 | 516 | size_t numEvents = 10; 517 | 518 | // Initial time event has no attached event. 519 | ACT_TimEvt *te; 520 | te = ACT_TimEvt_new(EVT_UPCAST(NULL), &ao, &ao, msgExpFn); 521 | 522 | ACT_TimeEvt_start(te, timeOutMs, periodMs); 523 | /* Timer is indicating to be running */ 524 | TEST_ASSERT_TRUE(te->timer.running); 525 | size_t i = numEvents; 526 | while (i--) 527 | { 528 | /* Produce things in buffer 529 | . 530 | . 531 | . 532 | */ 533 | 534 | /* NB: This test is not thread safe, as expiry function is called in ao (active object) context (thread) while 535 | test function runs in different context. 536 | However, an active object creating a time event / message will have expiry function called in same context and be safe. */ 537 | expectedMsgPayload = (writeBuf == buf0 ? buf0[0] : buf1[0]); 538 | ACT_SLEEPMS(periodMs); 539 | } 540 | 541 | bool status = ACT_TimeEvt_stop(te); 542 | 543 | /* Test status returned as running */ 544 | TEST_ASSERT_TRUE(status); 545 | 546 | /* Correct number of events fired */ 547 | TEST_ASSERT_EQUAL_UINT16(numEvents, correctEventsReceived); 548 | 549 | ACT_SLEEPMS(1 * periodMs); 550 | 551 | /* Timer is stopped - no more events are fired */ 552 | TEST_ASSERT_FALSE(te->timer.running); 553 | TEST_ASSERT_EQUAL_UINT16(numEvents, correctEventsReceived); 554 | 555 | /* Test memory management correctly freeing events after stopping */ 556 | TEST_ASSERT_EQUAL(msgUsed, ACT_mem_Message_getUsed()); 557 | TEST_ASSERT_EQUAL(timeEvtUse, ACT_mem_TimeEvt_getUsed()); 558 | } 559 | 560 | void setUp() 561 | { 562 | expectedSignal = ACT_SIGNAL_UNDEFINED; 563 | expectedMsgPayload = 0; 564 | eventsReceived = 0; 565 | correctEventsReceived = 0; 566 | 567 | timeReceivedMs = 0, lastTimeReceivedMs = 0; 568 | } 569 | 570 | void main() 571 | { 572 | ACT_SLEEPMS(2000); 573 | 574 | UNITY_BEGIN(); 575 | 576 | ACT_init(&ao, ao_dispatch, &qdtest, &tdtest); 577 | ACT_start(&ao); 578 | 579 | /* Test a oneshot timer event w attached event that is started and stopped before expiry */ 580 | RUN_TEST(test_function_timer_static_oneshot_startstop); 581 | RUN_TEST(test_function_timer_dynamic_oneshot_startstop); 582 | /* Test stopping a dynamic oneshot timer around expiration (race condition) */ 583 | RUN_TEST(test_function_timer_dynamic_oneshot_expire_stop); 584 | 585 | /* Test a oneshot timer w attached event that is started and runs to expiry */ 586 | RUN_TEST(test_function_timer_static_oneshot_expire); 587 | RUN_TEST(test_function_timer_dynamic_oneshot_expire); 588 | 589 | /* Test a oneshot static timer w static attached event that uses an expiry function to conditiononally replace attached event */ 590 | RUN_TEST(test_function_timer_static_expFn); 591 | RUN_TEST(test_function_timer_dynamic_expFn); 592 | 593 | /* Test a periodic timer w attached event and no expiry function ("tick") */ 594 | RUN_TEST(test_function_timer_static_periodic_expire); 595 | RUN_TEST(test_function_timer_dynamic_periodic_expire); 596 | 597 | /* Test a periodic timer w attached message replaced every expiry (producer/consumer) */ 598 | RUN_TEST(test_function_timer_dynamic_periodic_expire_expFn); 599 | 600 | UNITY_END(); 601 | } 602 | -------------------------------------------------------------------------------- /test/test_unit_active_mem/active_config.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heliohm/active/f0ac734140a03f472a09e4a649fdcf34d2298f35/test/test_unit_active_mem/active_config.h -------------------------------------------------------------------------------- /test/test_unit_active_mem/test_unit_active_mem.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | enum TestUserSignal 5 | { 6 | TEST_SIG = ACT_USER_SIG 7 | }; 8 | 9 | void test_signal_new() 10 | { 11 | Active ao; 12 | ACT_Signal *s = ACT_Signal_new(&ao, TEST_SIG); 13 | 14 | TEST_ASSERT_EQUAL(ACT_SIGNAL, s->super.type); 15 | TEST_ASSERT_EQUAL(&ao, s->super._sender); 16 | TEST_ASSERT_EQUAL(TEST_SIG, s->sig); 17 | TEST_ASSERT_TRUE(s->super._dynamic); 18 | TEST_ASSERT_EQUAL_UINT16(0, s->super._refcnt); 19 | 20 | ACT_mem_gc(EVT_UPCAST(s)); 21 | } 22 | 23 | void test_message_new() 24 | { 25 | Active ao; 26 | 27 | static const uint32_t msgPayload[] = {0xDEADBEEF, 0xBADDCAFE, 0xBAAAAAAD}; 28 | static const size_t len = sizeof(msgPayload) / sizeof(msgPayload[0]); 29 | uint16_t header = 0xBABA; 30 | 31 | ACT_Message *m = ACT_Message_new(&ao, header, (void *)msgPayload, len); 32 | 33 | TEST_ASSERT_EQUAL_UINT16(header, m->header); 34 | TEST_ASSERT_EQUAL_PTR(&msgPayload, m->payload); 35 | TEST_ASSERT_EQUAL_UINT16(len, m->payloadLen); 36 | 37 | TEST_ASSERT_TRUE(m->super._dynamic); 38 | TEST_ASSERT_EQUAL(&ao, m->super._sender); 39 | 40 | ACT_mem_gc(EVT_UPCAST(m)); 41 | } 42 | 43 | void test_timeevt_new() 44 | { 45 | Active ao; 46 | ACT_Evt e; 47 | 48 | ACT_TimEvt *te = ACT_TimEvt_new(&e, &ao, &ao, NULL); 49 | 50 | TEST_ASSERT_EQUAL(ACT_TIMEVT, te->super.type); 51 | TEST_ASSERT_EQUAL(&ao, te->super._sender); 52 | TEST_ASSERT_TRUE(te->super._dynamic); 53 | TEST_ASSERT_EQUAL_UINT16(0, te->super._refcnt); 54 | 55 | TEST_ASSERT_EQUAL(&e, te->e); 56 | TEST_ASSERT_EQUAL(NULL, te->expFn); 57 | TEST_ASSERT_EQUAL(&ao, te->receiver); 58 | 59 | ACT_mem_gc(EVT_UPCAST(te)); 60 | } 61 | 62 | void test_active_mem_gc() 63 | { 64 | 65 | Active ao; 66 | 67 | for (uint16_t i = 0; i < ACT_MEM_NUM_SIGNALS + 10; i++) 68 | { 69 | uint32_t numUsed = ACT_mem_Signal_getUsed(); 70 | ACT_Signal *s = ACT_Signal_new(&ao, TEST_SIG); 71 | TEST_ASSERT_NOT_NULL(s); 72 | TEST_ASSERT_EQUAL(numUsed + 1, ACT_mem_Signal_getUsed()); 73 | ACT_mem_gc(EVT_UPCAST(s)); 74 | TEST_ASSERT_EQUAL(numUsed, ACT_mem_Signal_getUsed()); 75 | } 76 | } 77 | 78 | void test_active_mem_ref_inc() 79 | { 80 | Active ao; 81 | ACT_Signal *s = ACT_Signal_new(&ao, TEST_SIG); 82 | ACT_mem_refinc(EVT_UPCAST(s)); 83 | TEST_ASSERT_EQUAL_UINT16(1, ACT_mem_getRefCount(EVT_UPCAST(s))); 84 | } 85 | 86 | void test_active_mem_ref_dec() 87 | { 88 | Active ao; 89 | 90 | ACT_Signal *s = ACT_Signal_new(&ao, TEST_SIG); 91 | 92 | ACT_mem_refinc(EVT_UPCAST(s)); 93 | ACT_mem_refdec(EVT_UPCAST(s)); 94 | 95 | TEST_ASSERT_EQUAL_UINT16(0, ACT_mem_getRefCount(EVT_UPCAST(s))); 96 | } 97 | 98 | void main() 99 | { 100 | ACT_SLEEPMS(2000); 101 | 102 | UNITY_BEGIN(); 103 | 104 | RUN_TEST(test_active_mem_ref_inc); 105 | RUN_TEST(test_active_mem_ref_dec); 106 | 107 | RUN_TEST(test_signal_new); 108 | RUN_TEST(test_message_new); 109 | RUN_TEST(test_timeevt_new); 110 | RUN_TEST(test_active_mem_gc); 111 | 112 | UNITY_END(); 113 | } -------------------------------------------------------------------------------- /test/test_unit_active_msg/active_config.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heliohm/active/f0ac734140a03f472a09e4a649fdcf34d2298f35/test/test_unit_active_msg/active_config.h -------------------------------------------------------------------------------- /test/test_unit_active_msg/test_unit_active_msg.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | enum TestUserSignal 5 | { 6 | TEST_SIG = ACT_USER_SIG 7 | }; 8 | 9 | ACT_SIGNAL_DEFINE(sm, TEST_SIG); 10 | 11 | void test_macro_signal() 12 | { 13 | TEST_ASSERT_EQUAL(sm.super.type, ACT_SIGNAL); 14 | TEST_ASSERT_EQUAL(sm.sig, TEST_SIG); 15 | TEST_ASSERT_FALSE(sm.super._dynamic); 16 | TEST_ASSERT_NULL(sm.super._sender); 17 | } 18 | 19 | const unsigned char payload[] = {0x1, 0x2, 0x3, 0x4}; 20 | const unsigned char payloadLen = sizeof(payload) / sizeof(payload[0]); 21 | 22 | ACT_MESSAGE_DEFINE(mm, (uint16_t){0xBABA}, (void *)payload, payloadLen); 23 | 24 | void test_macro_message() 25 | { 26 | TEST_ASSERT_EQUAL(ACT_MESSAGE, mm.super.type); 27 | TEST_ASSERT_EQUAL_UINT16(0xBABA, mm.header); 28 | TEST_ASSERT_EQUAL_PTR(payload, mm.payload); 29 | TEST_ASSERT_EQUAL(payloadLen, mm.payloadLen); 30 | TEST_ASSERT_FALSE(mm.super._dynamic); 31 | TEST_ASSERT_NULL(mm.super._sender); 32 | } 33 | 34 | void test_function_signal_init() 35 | { 36 | ACT_Signal s; 37 | Active ao; 38 | ACT_Signal_init(&s, &ao, TEST_SIG); 39 | 40 | TEST_ASSERT_EQUAL(TEST_SIG, s.sig); 41 | TEST_ASSERT_EQUAL(ACT_SIGNAL, s.super.type); 42 | TEST_ASSERT_FALSE(s.super._dynamic); 43 | TEST_ASSERT_EQUAL(&ao, s.super._sender); 44 | } 45 | 46 | void test_function_message_init() 47 | { 48 | ACT_Message m; 49 | Active ao; 50 | 51 | static const uint32_t msgPayload[] = {0xDEADBEEF, 0xBADDCAFE, 0xBAAAAAAD}; 52 | static const size_t len = sizeof(msgPayload) / sizeof(msgPayload[0]); 53 | uint16_t header = 0xBABA; 54 | 55 | ACT_Message_init(&m, &ao, header, (void *)msgPayload, len); 56 | 57 | TEST_ASSERT_EQUAL_UINT16(header, m.header); 58 | TEST_ASSERT_EQUAL_PTR(&msgPayload, m.payload); 59 | TEST_ASSERT_EQUAL_UINT16(len, m.payloadLen); 60 | 61 | TEST_ASSERT_FALSE(m.super._dynamic); 62 | TEST_ASSERT_EQUAL(&ao, m.super._sender); 63 | } 64 | 65 | static ACT_Evt *expFn(ACT_TimEvt const *const te) 66 | { 67 | return (ACT_Evt *)NULL; 68 | } 69 | 70 | void test_function_timeevt_init() 71 | { 72 | ACT_TimEvt te; 73 | Active ao; 74 | ACT_Evt e; 75 | 76 | ACT_TimEvt_init(&te, &ao, &e, &ao, expFn); 77 | 78 | TEST_ASSERT_EQUAL(ACT_TIMEVT, te.super.type); 79 | TEST_ASSERT_FALSE(te.super._dynamic); 80 | TEST_ASSERT_EQUAL(&ao, te.super._sender); 81 | TEST_ASSERT_EQUAL(&e, te.e); 82 | TEST_ASSERT_EQUAL(&ao, te.receiver); 83 | 84 | TEST_ASSERT_FALSE(te.timer.running); 85 | TEST_ASSERT_EQUAL(expFn, te.expFn); 86 | TEST_ASSERT_EQUAL(&te, te.timer.impl.user_data); 87 | } 88 | 89 | void test_macro_evt_upcast() 90 | { 91 | 92 | ACT_Signal *s = NULL; 93 | 94 | TEST_ASSERT_EQUAL(EVT_UPCAST(s), (ACT_Evt *)s); 95 | } 96 | 97 | void test_macro_evt_cast() 98 | { 99 | ACT_Signal *s = NULL; 100 | ACT_Evt *e = (ACT_Evt *)s; 101 | 102 | TEST_ASSERT_EQUAL(s, EVT_CAST(e, ACT_Signal)); 103 | } 104 | 105 | void main() 106 | { 107 | ACT_SLEEPMS(2000); 108 | 109 | UNITY_BEGIN(); 110 | 111 | RUN_TEST(test_macro_signal); 112 | RUN_TEST(test_macro_message); 113 | RUN_TEST(test_function_signal_init); 114 | RUN_TEST(test_function_message_init); 115 | RUN_TEST(test_function_timeevt_init); 116 | RUN_TEST(test_macro_evt_upcast); 117 | RUN_TEST(test_macro_evt_cast); 118 | 119 | UNITY_END(); 120 | } -------------------------------------------------------------------------------- /zephyr/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13.1) 2 | include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) 3 | project(active) 4 | 5 | FILE(GLOB_RECURSE app ../examples/*/*.c) 6 | FILE(GLOB lib ../src/*.c*) 7 | target_sources(app PRIVATE ${app} ) 8 | target_sources(app PRIVATE ${lib} ) -------------------------------------------------------------------------------- /zephyr/prj.conf: -------------------------------------------------------------------------------- 1 | ############ 2 | # Debug 3 | ############ 4 | #https://docs.zephyrproject.org/latest/guides/optimizations/footprint.html 5 | CONFIG_DEBUG=y 6 | CONFIG_MEM_SLAB_TRACE_MAX_UTILIZATION=y 7 | # Add when running Unity 8 | CONFIG_NEWLIB_LIBC=y 9 | 10 | 11 | 12 | #Tracing - TBD 13 | #https://docs.zephyrproject.org/latest/guides/debug_tools/tracing/index.html 14 | 15 | #https://docs.zephyrproject.org/latest/guides/debug_tools/coredump.html 16 | #DEBUG_COREDUMP 17 | 18 | 19 | ######################## 20 | # Tuning & optimization 21 | ######################## 22 | #https://docs.zephyrproject.org/latest/guides/debug_tools/thread-analyzer.html 23 | CONFIG_THREAD_ANALYZER=y 24 | CONFIG_THREAD_ANALYZER_USE_PRINTK=y 25 | CONFIG_THREAD_ANALYZER_AUTO=y 26 | CONFIG_THREAD_ANALYZER_AUTO_INTERVAL=5 27 | 28 | CONFIG_THREAD_NAME=n 29 | #CONFIG_THREAD_MAX_NAME_LEN=10 30 | 31 | 32 | 33 | ############ 34 | # Asserts 35 | ############ 36 | CONFIG_ASSERT=y 37 | CONFIG_ASSERT_LEVEL=2 38 | 39 | ############ 40 | # Kernel 41 | ############ 42 | CONFIG_TICKLESS_KERNEL=y 43 | 44 | #https://docs.zephyrproject.org/latest/reference/kernel/threads/index.html 45 | CONFIG_TIMESLICING=n 46 | CONFIG_NUM_COOP_PRIORITIES=0 47 | #CONFIG_SCHED_DUMB 48 | 49 | ############ 50 | # Stacks & memory 51 | ############ 52 | ##https://docs.zephyrproject.org/latest/guides/optimizations/footprint.html 53 | #CONFIG_ISR_STACK_SIZE 54 | #CONFIG_MAIN_STACK_SIZE 55 | #CONFIG_PRIVILEGED_STACK_SIZE 56 | #CONFIG_IDLE_STACK_SIZE 57 | 58 | ############ 59 | # Board 60 | ############ 61 | CONFIG_COUNTER_RTC_STM32_SAVE_VALUE_BETWEEN_RESETS=y 62 | 63 | 64 | 65 | 66 | ########### 67 | # Printing 68 | ########### 69 | CONFIG_BOOT_BANNER=y 70 | CONFIG_UART_CONSOLE=y 71 | CONFIG_CONSOLE=y 72 | CONFIG_PRINTK=y 73 | --------------------------------------------------------------------------------