Zephyr C++ Toolkit
Loading...
Searching...
No Matches
zct::EventThread< EventType > Class Template Reference

Use this class in your objects to create a thread that can wait for events. More...

#include <EventThread.hpp>

Collaboration diagram for zct::EventThread< EventType >:

Public Types

using MsgQueueItem = std::variant< EventType, std::function< void()> >
 

Public Member Functions

 EventThread (const char *name, k_thread_stack_t *threadStack, size_t threadStackSize, int threadPriority, size_t eventQueueBufferNumItems)
 
 ~EventThread ()
 
void start ()
 
void onExternalEvent (std::function< void(const EventType &)> callback)
 
void sendEvent (const EventType &event)
 
TimerManagertimerManager ()
 
void exitEventLoop ()
 
void runInLoop (std::function< void()> func)
 

Protected Member Functions

void runEventLoop ()
 

Static Protected Member Functions

static void staticThreadFunction (void *arg1, void *arg2, void *arg3)
 

Protected Attributes

const char * m_name = nullptr
 
void * m_msgQueueBuffer = nullptr
 
struct k_thread m_thread
 
struct k_msgq m_threadMsgQueue
 
k_thread_stack_t * m_threadStack
 
size_t m_threadStackSize
 
int m_threadPriority
 
TimerManager m_timerManager
 
std::function< void(const EventType &)> m_externalEventCallback = nullptr
 
bool m_exitEventLoop = false
 

Detailed Description

template<typename EventType>
class zct::EventThread< EventType >

Use this class in your objects to create a thread that can wait for events.

This class spawns a new Zephyr thread.

The user is responsible for making sure the thread function returns if you want to destroy this object. This is because the destructor blocks until the thread is complete. The best way to do this is to implement a exit event and send it to the event thread. The event thread returns when it receives the exit event.

Making sure to return from the thread function is only important if you are destroying the object, e.g. in testing.

This class works really well with event driven programming and hierarchical state machines (HSM) like NinjaHSM.

Below is an example of how to use this class.

#include <variant>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(EventThreadTests, LOG_LEVEL_DBG);
//================================================================================================//
// EVENTS
//================================================================================================//
namespace Events {
struct Exit {};
struct LedFlashing {
uint32_t flashRateMs;
};
struct TimerExpired {};
// Create a generic event type that can be anyone of the specific events above.
typedef std::variant<LedFlashing, Exit, TimerExpired> Generic;
}
//================================================================================================//
// EVENT THREAD
//================================================================================================//
class Led {
public:
Led() :
m_eventThread(
"Led",
m_threadStack,
THREAD_STACK_SIZE,
7,
EVENT_QUEUE_NUM_ITEMS
),
m_flashingTimer("FlashingTimer", [this]() {
Events::TimerExpired timerExpiredEvent;
handleEvent(timerExpiredEvent);
})
{
// Register timers
m_eventThread.timerManager().registerTimer(m_flashingTimer);
// Register external event callback
m_eventThread.onExternalEvent([this](const Events::Generic& event) {
handleEvent(event);
});
// Start the event loop (it runs it it's own thread)
m_eventThread.start();
}
~Led() {
// Send the exit event to the event thread
Events::Exit exitEvent;
m_eventThread.sendEvent(exitEvent);
}
void flash(uint32_t flashRateMs) {
Events::LedFlashing ledFlashingEvent = { .flashRateMs = flashRateMs };
// This is how use send "external" events (i.e. not timer events) to
// the event thread. The event thread will call handleEvent() when it
// receives this event.
m_eventThread.sendEvent(ledFlashingEvent);
}
private:
static constexpr size_t EVENT_QUEUE_NUM_ITEMS = 10;
static constexpr size_t THREAD_STACK_SIZE = 512;
K_KERNEL_STACK_MEMBER(m_threadStack, THREAD_STACK_SIZE);
zct::Timer m_flashingTimer;
bool m_ledIsOn = false;
void handleEvent(const Events::Generic& event) {
if (std::holds_alternative<Events::LedFlashing>(event)) {
// Start the timer to flash the LED
m_flashingTimer.start(1000, 1000);
LOG_INF("Starting flashing. Turning LED on...");
m_ledIsOn = true;
} else if (std::holds_alternative<Events::Exit>(event)) {
// This will cause the event loop to end (and the thread to exit)
// once we return from handleEvent().
m_eventThread.exitEventLoop();
} else if (std::holds_alternative<Events::TimerExpired>(event)) {
LOG_INF("Toggling LED to %d.", !m_ledIsOn);
m_ledIsOn = !m_ledIsOn;
}
}
};
int main() {
Led led;
// Start the LED flashing. The flashing will happen
// in the LED event thread.
led.flash(1000);
// Wait 2.5s. The LED should flash twice in this time.
k_sleep(K_MSEC(2500));
}
Use this class in your objects to create a thread that can wait for events.
Definition EventThread.hpp:37
void exitEventLoop()
Definition EventThread.hpp:177
A timer that can be used to execute callbacks at regular intervals in an event driven application.
Definition Timer.hpp:39
void start(int64_t period_ms)
Definition Timer.hpp:63
LOG_MODULE_REGISTER(EventThread, LOG_LEVEL_DBG)

Member Typedef Documentation

◆ MsgQueueItem

template<typename EventType >
using zct::EventThread< EventType >::MsgQueueItem = std::variant<EventType, std::function<void()> >

The type of items that can be sent to the event thread. Can either be an event or a function to run in the context of the event thread.

This is a variant of the event type and a function to run in the context of the event thread.

Constructor & Destructor Documentation

◆ EventThread()

template<typename EventType >
zct::EventThread< EventType >::EventThread ( const char *  name,
k_thread_stack_t *  threadStack,
size_t  threadStackSize,
int  threadPriority,
size_t  eventQueueBufferNumItems 
)
inline

Create a new event thread.

Dynamically allocates memory for the event queue buffer. At the moment, you have to allocate the thread stack yourself and pass it in. Once Zephyr's dynamic thread support is out of experimental (and working), hopefully we can just pass in the desired stack size.

Parameters
nameThe name of the this event thread. Used for logging purposes. The Zephyr thread name will also be set to this name.
threadStackThe stack to use for the thread. You can use K_KERNEL_STACK_MEMBER(m_threadStack, THREAD_STACK_SIZE); to declare a stack member in the parent class that encloses this event thread.
threadStackSizeThe size of the stack provided.
threadPriorityThe priority to assign to the thread.
eventQueueBufferNumItemsThe number of items in the event queue.

◆ ~EventThread()

template<typename EventType >
zct::EventThread< EventType >::~EventThread ( )
inline

Destroy the event thread. This will block until the thread has exited.

If you want this function to return, make sure the event loop exits. The best way to do this is to call exitEventLoop() from a timer callback or external event handler.

Member Function Documentation

◆ exitEventLoop()

template<typename EventType >
void zct::EventThread< EventType >::exitEventLoop ( )
inline

Exit the event loop. This will cause runEventLoop() to return. This function must be called from the event thread context (i.e. from a timer callback or from an external event handler).

This will make the event loop return from runEventLoop() as soon as the timer callback or external event handler returns.

Use this in tests to cleanly exit from the event loop and it's corresponding thread.

◆ onExternalEvent()

template<typename EventType >
void zct::EventThread< EventType >::onExternalEvent ( std::function< void(const EventType &)>  callback)
inline

Set the callback function to be called when external events are received.

The callback is executed in the context of the event thread.

This function should be used to setup the external event callback before start() is called to start the event loop.

Parameters
callbackThe callback function to call when an external event is received. The callback will be passed the received event.

◆ runEventLoop()

template<typename EventType >
void zct::EventThread< EventType >::runEventLoop ( )
inlineprotected

Start the event loop. This function never returns and should be called from the thread function that is passed in to the constructor.

This function will:

  • Handle expired timers by calling their callbacks
  • Handle external events by calling the registered external event callback (call onExternalEvent() to register a callback).

This should be called from the thread function that is passed in to the constructor, once any initialisation you want done (e.g. setup some timers) has been done.

◆ runInLoop()

template<typename EventType >
void zct::EventThread< EventType >::runInLoop ( std::function< void()>  func)
inline

Run a function in the context of the event thread. This passes the function through the message queue. When the event loop thread receives it, it runs the function.

Parameters
funcThe function to run. This will be run in the context of the event thread.

◆ sendEvent()

template<typename EventType >
void zct::EventThread< EventType >::sendEvent ( const EventType &  event)
inline

Send an event to this event thread. This can be called from any other thread to send an event to this event thread.

Note
This function is thread safe.
Parameters
eventThe event to send. It is copied into the event queue so it's lifetime only needs to be as long as this function call.

◆ start()

template<typename EventType >
void zct::EventThread< EventType >::start ( )
inline

Start the event thread. This creates and starts the Zephyr thread which will begin running the event loop.

This should be called after the constructor and after any setup (like registering timers) has been completed.

◆ staticThreadFunction()

template<typename EventType >
static void zct::EventThread< EventType >::staticThreadFunction ( void *  arg1,
void *  arg2,
void *  arg3 
)
inlinestaticprotected

The function needed by pass to Zephyr's thread API

◆ timerManager()

template<typename EventType >
TimerManager & zct::EventThread< EventType >::timerManager ( )
inline

Call to get the timer manager for this event thread. This is useful when you want to create timers and then register them with the event thread.

Typically you would call:

myEventThread.timerManager().registerTimer(myTimer);
Returns
A reference to the timer manager for this event thread.

Member Data Documentation

◆ m_exitEventLoop

template<typename EventType >
bool zct::EventThread< EventType >::m_exitEventLoop = false
protected

Used to signal from exitEventLoop() to the code in the runEventLoop() function to exit.

◆ m_externalEventCallback

template<typename EventType >
std::function<void(const EventType&)> zct::EventThread< EventType >::m_externalEventCallback = nullptr
protected

◆ m_msgQueueBuffer

template<typename EventType >
void* zct::EventThread< EventType >::m_msgQueueBuffer = nullptr
protected

◆ m_name

template<typename EventType >
const char* zct::EventThread< EventType >::m_name = nullptr
protected

◆ m_thread

template<typename EventType >
struct k_thread zct::EventThread< EventType >::m_thread
protected

◆ m_threadMsgQueue

template<typename EventType >
struct k_msgq zct::EventThread< EventType >::m_threadMsgQueue
protected

◆ m_threadPriority

template<typename EventType >
int zct::EventThread< EventType >::m_threadPriority
protected

◆ m_threadStack

template<typename EventType >
k_thread_stack_t* zct::EventThread< EventType >::m_threadStack
protected

◆ m_threadStackSize

template<typename EventType >
size_t zct::EventThread< EventType >::m_threadStackSize
protected

◆ m_timerManager

template<typename EventType >
TimerManager zct::EventThread< EventType >::m_timerManager
protected

The documentation for this class was generated from the following file: