2 ******************************************************************************
4 * @file eventdispatcher.c
5 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
6 * @brief Event dispatcher, distributes object events as callbacks. Alternative
7 * to using tasks and queues. All callbacks are invoked from the event task.
8 * @see The GNU Public License (GPL) Version 3
10 *****************************************************************************/
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 3 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22 * You should have received a copy of the GNU General Public License along
23 * with this program; if not, write to the Free Software Foundation, Inc.,
24 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 #include <openpilot.h>
29 #include <callbackinfo.h>
32 #if defined(PIOS_EVENTDISAPTCHER_QUEUE)
33 #define MAX_QUEUE_SIZE PIOS_EVENTDISAPTCHER_QUEUE
35 #define MAX_QUEUE_SIZE 20
38 #if defined(PIOS_EVENTDISPATCHER_STACK_SIZE)
39 #define STACK_SIZE PIOS_EVENTDISPATCHER_STACK_SIZE
41 #define STACK_SIZE configMINIMAL_STACK_SIZE
42 #endif /* PIOS_EVENTDISPATCHER_STACK_SIZE */
44 #define CALLBACK_PRIORITY CALLBACK_PRIORITY_CRITICAL
45 #define TASK_PRIORITY CALLBACK_TASK_FLIGHTCONTROL
46 #define MAX_UPDATE_PERIOD_MS 1000
52 * Event callback information
55 UAVObjEvent ev
; /** The actual event */
56 UAVObjEventCallback cb
; /** The callback function, or zero if none */
57 xQueueHandle queue
; /** The queue or zero if none */
58 bool lowpriority
; /** set to true for telemetry and other low priority stuffs, prevent raising warning */
62 * List of object properties that are needed for the periodic updates.
64 struct PeriodicObjectListStruct
{
65 EventCallbackInfo evInfo
; /** Event callback information */
66 uint16_t updatePeriodMs
; /** Update period in ms or 0 if no periodic updates are needed */
67 int32_t timeToNextUpdateMs
; /** Time delay to the next update */
68 struct PeriodicObjectListStruct
*next
; /** Needed by linked list library (utlist.h) */
70 typedef struct PeriodicObjectListStruct PeriodicObjectList
;
73 static PeriodicObjectList
*mObjList
;
74 static xQueueHandle mQueue
;
75 static DelayedCallbackInfo
*eventSchedulerCallback
;
76 static xSemaphoreHandle mMutex
;
77 static EventStats mStats
;
80 static int32_t processPeriodicUpdates();
81 static void eventTask();
82 static int32_t eventPeriodicCreate(UAVObjEvent
*ev
, UAVObjEventCallback cb
, xQueueHandle queue
, uint16_t periodMs
);
83 static int32_t eventPeriodicUpdate(UAVObjEvent
*ev
, UAVObjEventCallback cb
, xQueueHandle queue
, uint16_t periodMs
);
84 static uint16_t randomizePeriod(uint16_t periodMs
);
88 * Initialize the dispatcher
89 * \return Success (0), failure (-1)
91 int32_t EventDispatcherInitialize()
93 // Initialize variables
95 memset(&mStats
, 0, sizeof(EventStats
));
98 mMutex
= xSemaphoreCreateRecursiveMutex();
103 // Create event queue
104 mQueue
= xQueueCreate(MAX_QUEUE_SIZE
, sizeof(EventCallbackInfo
));
107 eventSchedulerCallback
= PIOS_CALLBACKSCHEDULER_Create(&eventTask
, CALLBACK_PRIORITY
, TASK_PRIORITY
, CALLBACKINFO_RUNNING_EVENTDISPATCHER
, STACK_SIZE
* 4);
108 PIOS_CALLBACKSCHEDULER_Dispatch(eventSchedulerCallback
);
115 * Get the statistics counters
116 * @param[out] statsOut The statistics counters will be copied there
118 void EventGetStats(EventStats
*statsOut
)
120 xSemaphoreTakeRecursive(mMutex
, portMAX_DELAY
);
121 memcpy(statsOut
, &mStats
, sizeof(EventStats
));
122 xSemaphoreGiveRecursive(mMutex
);
126 * Clear the statistics counters
128 void EventClearStats()
130 xSemaphoreTakeRecursive(mMutex
, portMAX_DELAY
);
131 memset(&mStats
, 0, sizeof(EventStats
));
132 xSemaphoreGiveRecursive(mMutex
);
136 * Dispatch an event by invoking the supplied callback. The function
137 * returns imidiatelly, the callback is invoked from the event task.
138 * \param[in] ev The event to be dispatched
139 * \param[in] cb The callback function
140 * \return Success (0), failure (-1)
142 int32_t EventCallbackDispatch(UAVObjEvent
*ev
, UAVObjEventCallback cb
)
144 EventCallbackInfo evInfo
;
146 // Initialize event callback information
147 memcpy(&evInfo
.ev
, ev
, sizeof(UAVObjEvent
));
151 int32_t result
= xQueueSend(mQueue
, &evInfo
, 0); // will not block if queue is full
152 PIOS_CALLBACKSCHEDULER_Dispatch(eventSchedulerCallback
);
157 * Dispatch an event at periodic intervals.
158 * \param[in] ev The event to be dispatched
159 * \param[in] cb The callback to be invoked
160 * \param[in] periodMs The period the event is generated
161 * \return Success (0), failure (-1)
163 int32_t EventPeriodicCallbackCreate(UAVObjEvent
*ev
, UAVObjEventCallback cb
, uint16_t periodMs
)
165 return eventPeriodicCreate(ev
, cb
, 0, periodMs
);
169 * Update the period of a periodic event.
170 * \param[in] ev The event to be dispatched
171 * \param[in] cb The callback to be invoked
172 * \param[in] periodMs The period the event is generated
173 * \return Success (0), failure (-1)
175 int32_t EventPeriodicCallbackUpdate(UAVObjEvent
*ev
, UAVObjEventCallback cb
, uint16_t periodMs
)
177 return eventPeriodicUpdate(ev
, cb
, 0, periodMs
);
181 * Dispatch an event at periodic intervals.
182 * \param[in] ev The event to be dispatched
183 * \param[in] queue The queue that the event will be pushed in
184 * \param[in] periodMs The period the event is generated
185 * \return Success (0), failure (-1)
187 int32_t EventPeriodicQueueCreate(UAVObjEvent
*ev
, xQueueHandle queue
, uint16_t periodMs
)
189 return eventPeriodicCreate(ev
, 0, queue
, periodMs
);
193 * Update the period of a periodic event.
194 * \param[in] ev The event to be dispatched
195 * \param[in] queue The queue
196 * \param[in] periodMs The period the event is generated
197 * \return Success (0), failure (-1)
199 int32_t EventPeriodicQueueUpdate(UAVObjEvent
*ev
, xQueueHandle queue
, uint16_t periodMs
)
201 return eventPeriodicUpdate(ev
, 0, queue
, periodMs
);
205 * Dispatch an event through a callback at periodic intervals.
206 * \param[in] ev The event to be dispatched
207 * \param[in] cb The callback to be invoked or zero if none
208 * \param[in] queue The queue or zero if none
209 * \param[in] periodMs The period the event is generated
210 * \return Success (0), failure (-1)
212 static int32_t eventPeriodicCreate(UAVObjEvent
*ev
, UAVObjEventCallback cb
, xQueueHandle queue
, uint16_t periodMs
)
214 PeriodicObjectList
*objEntry
;
217 xSemaphoreTakeRecursive(mMutex
, portMAX_DELAY
);
218 // Check that the object is not already connected
219 LL_FOREACH(mObjList
, objEntry
) {
220 if (objEntry
->evInfo
.cb
== cb
&&
221 objEntry
->evInfo
.queue
== queue
&&
222 objEntry
->evInfo
.ev
.obj
== ev
->obj
&&
223 objEntry
->evInfo
.ev
.instId
== ev
->instId
&&
224 objEntry
->evInfo
.ev
.event
== ev
->event
) {
225 // Already registered, do nothing
226 xSemaphoreGiveRecursive(mMutex
);
231 objEntry
= (PeriodicObjectList
*)pios_malloc(sizeof(PeriodicObjectList
));
232 if (objEntry
== NULL
) {
235 objEntry
->evInfo
.ev
.obj
= ev
->obj
;
236 objEntry
->evInfo
.ev
.instId
= ev
->instId
;
237 objEntry
->evInfo
.ev
.event
= ev
->event
;
238 objEntry
->evInfo
.cb
= cb
;
239 objEntry
->evInfo
.queue
= queue
;
240 objEntry
->updatePeriodMs
= periodMs
;
241 objEntry
->timeToNextUpdateMs
= randomizePeriod(periodMs
); // avoid bunching of updates
243 LL_APPEND(mObjList
, objEntry
);
245 xSemaphoreGiveRecursive(mMutex
);
250 * Update the period of a periodic event.
251 * \param[in] ev The event to be dispatched
252 * \param[in] cb The callback to be invoked or zero if none
253 * \param[in] queue The queue or zero if none
254 * \param[in] periodMs The period the event is generated
255 * \return Success (0), failure (-1)
257 static int32_t eventPeriodicUpdate(UAVObjEvent
*ev
, UAVObjEventCallback cb
, xQueueHandle queue
, uint16_t periodMs
)
259 PeriodicObjectList
*objEntry
;
262 xSemaphoreTakeRecursive(mMutex
, portMAX_DELAY
);
264 LL_FOREACH(mObjList
, objEntry
) {
265 if (objEntry
->evInfo
.cb
== cb
&&
266 objEntry
->evInfo
.queue
== queue
&&
267 objEntry
->evInfo
.ev
.obj
== ev
->obj
&&
268 objEntry
->evInfo
.ev
.instId
== ev
->instId
&&
269 objEntry
->evInfo
.ev
.event
== ev
->event
) {
270 // Object found, update period
271 objEntry
->updatePeriodMs
= periodMs
;
272 objEntry
->timeToNextUpdateMs
= randomizePeriod(periodMs
); // avoid bunching of updates
274 xSemaphoreGiveRecursive(mMutex
);
278 // If this point is reached the object was not found
279 xSemaphoreGiveRecursive(mMutex
);
284 * Delayed event callback, responsible of invoking (event) callbacks.
286 static void eventTask()
288 static uint32_t timeToNextUpdateMs
= 0;
289 EventCallbackInfo evInfo
;
291 // Wait for queue message
292 int limit
= MAX_QUEUE_SIZE
;
294 while (xQueueReceive(mQueue
, &evInfo
, 0) == pdTRUE
) {
295 // Invoke callback, if any
296 if (evInfo
.cb
!= 0) {
297 evInfo
.cb(&evInfo
.ev
); // the function is expected to copy the event information
299 // limit loop to max queue size to slightly reduce the impact of recursive events
305 // Process periodic updates
306 if ((xTaskGetTickCount() * portTICK_RATE_MS
) >= timeToNextUpdateMs
) {
307 timeToNextUpdateMs
= processPeriodicUpdates();
310 PIOS_CALLBACKSCHEDULER_Schedule(eventSchedulerCallback
, timeToNextUpdateMs
- (xTaskGetTickCount() * portTICK_RATE_MS
), CALLBACK_UPDATEMODE_SOONER
);
314 * Handle periodic updates for all objects.
315 * \return The system time until the next update (in ms) or -1 if failed
317 static int32_t processPeriodicUpdates()
319 PeriodicObjectList
*objEntry
;
321 int32_t timeToNextUpdate
;
325 xSemaphoreTakeRecursive(mMutex
, portMAX_DELAY
);
327 // Iterate through each object and update its timer, if zero then transmit object.
328 // Also calculate smallest delay to next update.
329 timeToNextUpdate
= xTaskGetTickCount() * portTICK_RATE_MS
+ MAX_UPDATE_PERIOD_MS
;
330 LL_FOREACH(mObjList
, objEntry
) {
331 // If object is configured for periodic updates
332 if (objEntry
->updatePeriodMs
> 0) {
333 // Check if time for the next update
334 timeNow
= xTaskGetTickCount() * portTICK_RATE_MS
;
335 if (objEntry
->timeToNextUpdateMs
<= timeNow
) {
337 offset
= (timeNow
- objEntry
->timeToNextUpdateMs
) % objEntry
->updatePeriodMs
;
338 objEntry
->timeToNextUpdateMs
= timeNow
+ objEntry
->updatePeriodMs
- offset
;
339 // Invoke callback, if one
340 if (objEntry
->evInfo
.cb
!= 0) {
341 objEntry
->evInfo
.cb(&objEntry
->evInfo
.ev
); // the function is expected to copy the event information
343 // Push event to queue, if one
344 if (objEntry
->evInfo
.queue
!= 0) {
345 if (xQueueSend(objEntry
->evInfo
.queue
, &objEntry
->evInfo
.ev
, 0) != pdTRUE
&& !objEntry
->evInfo
.ev
.lowPriority
) { // do not block if queue is full
346 if (objEntry
->evInfo
.ev
.obj
!= NULL
) {
347 mStats
.lastErrorID
= UAVObjGetID(objEntry
->evInfo
.ev
.obj
);
349 ++mStats
.eventErrors
;
353 // Update minimum delay
354 if (objEntry
->timeToNextUpdateMs
< timeToNextUpdate
) {
355 timeToNextUpdate
= objEntry
->timeToNextUpdateMs
;
361 xSemaphoreGiveRecursive(mMutex
);
362 return timeToNextUpdate
;
366 * Return a psedorandom integer from 0 to periodMs
367 * Based on the Park-Miller-Carta Pseudo-Random Number Generator
368 * http://www.firstpr.com.au/dsp/rand31/
370 static uint16_t randomizePeriod(uint16_t periodMs
)
372 static uint32_t seed
= 1;
375 lo
= 16807 * (seed
& 0xFFFF);
376 hi
= 16807 * (seed
>> 16);
377 lo
+= (hi
& 0x7FFF) << 16;
379 if (lo
> 0x7FFFFFFF) {
383 return (uint16_t)(((float)periodMs
* (float)lo
) / (float)0x7FFFFFFF);