Forked to avoid changing the publioc repo

Dependencies:   BLE_API mbed-dev-bin nRF51822

Fork of microbit-dal by Lancaster University

Committer:
Jonathan Austin
Date:
Thu Apr 07 01:33:22 2016 +0100
Revision:
1:8aa5cdb4ab67
Child:
5:f0f1cecd65d8
Synchronized with git rev 55cb9199

Who changed what in which revision?

UserRevisionLine numberNew contents of line
Jonathan Austin 1:8aa5cdb4ab67 1 /*
Jonathan Austin 1:8aa5cdb4ab67 2 The MIT License (MIT)
Jonathan Austin 1:8aa5cdb4ab67 3
Jonathan Austin 1:8aa5cdb4ab67 4 Copyright (c) 2016 British Broadcasting Corporation.
Jonathan Austin 1:8aa5cdb4ab67 5 This software is provided by Lancaster University by arrangement with the BBC.
Jonathan Austin 1:8aa5cdb4ab67 6
Jonathan Austin 1:8aa5cdb4ab67 7 Permission is hereby granted, free of charge, to any person obtaining a
Jonathan Austin 1:8aa5cdb4ab67 8 copy of this software and associated documentation files (the "Software"),
Jonathan Austin 1:8aa5cdb4ab67 9 to deal in the Software without restriction, including without limitation
Jonathan Austin 1:8aa5cdb4ab67 10 the rights to use, copy, modify, merge, publish, distribute, sublicense,
Jonathan Austin 1:8aa5cdb4ab67 11 and/or sell copies of the Software, and to permit persons to whom the
Jonathan Austin 1:8aa5cdb4ab67 12 Software is furnished to do so, subject to the following conditions:
Jonathan Austin 1:8aa5cdb4ab67 13
Jonathan Austin 1:8aa5cdb4ab67 14 The above copyright notice and this permission notice shall be included in
Jonathan Austin 1:8aa5cdb4ab67 15 all copies or substantial portions of the Software.
Jonathan Austin 1:8aa5cdb4ab67 16
Jonathan Austin 1:8aa5cdb4ab67 17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
Jonathan Austin 1:8aa5cdb4ab67 18 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
Jonathan Austin 1:8aa5cdb4ab67 19 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
Jonathan Austin 1:8aa5cdb4ab67 20 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
Jonathan Austin 1:8aa5cdb4ab67 21 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
Jonathan Austin 1:8aa5cdb4ab67 22 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
Jonathan Austin 1:8aa5cdb4ab67 23 DEALINGS IN THE SOFTWARE.
Jonathan Austin 1:8aa5cdb4ab67 24 */
Jonathan Austin 1:8aa5cdb4ab67 25
Jonathan Austin 1:8aa5cdb4ab67 26 /**
Jonathan Austin 1:8aa5cdb4ab67 27 * Class definition for the MicroBitMessageBus.
Jonathan Austin 1:8aa5cdb4ab67 28 *
Jonathan Austin 1:8aa5cdb4ab67 29 * The MicroBitMessageBus is the common mechanism to deliver asynchronous events on the
Jonathan Austin 1:8aa5cdb4ab67 30 * MicroBit platform. It serves a number of purposes:
Jonathan Austin 1:8aa5cdb4ab67 31 *
Jonathan Austin 1:8aa5cdb4ab67 32 * 1) It provides an eventing abstraction that is independent of the underlying substrate.
Jonathan Austin 1:8aa5cdb4ab67 33 *
Jonathan Austin 1:8aa5cdb4ab67 34 * 2) It provides a mechanism to decouple user code from trusted system code
Jonathan Austin 1:8aa5cdb4ab67 35 * i.e. the basis of a message passing nano kernel.
Jonathan Austin 1:8aa5cdb4ab67 36 *
Jonathan Austin 1:8aa5cdb4ab67 37 * 3) It allows a common high level eventing abstraction across a range of hardware types.e.g. buttons, BLE...
Jonathan Austin 1:8aa5cdb4ab67 38 *
Jonathan Austin 1:8aa5cdb4ab67 39 * 4) It provides a mechanim for extensibility - new devices added via I/O pins can have OO based
Jonathan Austin 1:8aa5cdb4ab67 40 * drivers and communicate via the message bus with minima impact on user level languages.
Jonathan Austin 1:8aa5cdb4ab67 41 *
Jonathan Austin 1:8aa5cdb4ab67 42 * 5) It allows for the possiblility of event / data aggregation, which in turn can save energy.
Jonathan Austin 1:8aa5cdb4ab67 43 *
Jonathan Austin 1:8aa5cdb4ab67 44 * It has the following design principles:
Jonathan Austin 1:8aa5cdb4ab67 45 *
Jonathan Austin 1:8aa5cdb4ab67 46 * 1) Maintain a low RAM footprint where possible
Jonathan Austin 1:8aa5cdb4ab67 47 *
Jonathan Austin 1:8aa5cdb4ab67 48 * 2) Make few assumptions about the underlying platform, but allow optimizations where possible.
Jonathan Austin 1:8aa5cdb4ab67 49 */
Jonathan Austin 1:8aa5cdb4ab67 50 #include "MicroBitConfig.h"
Jonathan Austin 1:8aa5cdb4ab67 51 #include "MicroBitMessageBus.h"
Jonathan Austin 1:8aa5cdb4ab67 52 #include "MicroBitFiber.h"
Jonathan Austin 1:8aa5cdb4ab67 53 #include "ErrorNo.h"
Jonathan Austin 1:8aa5cdb4ab67 54
Jonathan Austin 1:8aa5cdb4ab67 55 /**
Jonathan Austin 1:8aa5cdb4ab67 56 * Default constructor.
Jonathan Austin 1:8aa5cdb4ab67 57 *
Jonathan Austin 1:8aa5cdb4ab67 58 * Adds itself as a fiber component, and also configures itself to be the
Jonathan Austin 1:8aa5cdb4ab67 59 * default EventModel if defaultEventBus is NULL.
Jonathan Austin 1:8aa5cdb4ab67 60 */
Jonathan Austin 1:8aa5cdb4ab67 61 MicroBitMessageBus::MicroBitMessageBus()
Jonathan Austin 1:8aa5cdb4ab67 62 {
Jonathan Austin 1:8aa5cdb4ab67 63 this->listeners = NULL;
Jonathan Austin 1:8aa5cdb4ab67 64 this->evt_queue_head = NULL;
Jonathan Austin 1:8aa5cdb4ab67 65 this->evt_queue_tail = NULL;
Jonathan Austin 1:8aa5cdb4ab67 66 this->queueLength = 0;
Jonathan Austin 1:8aa5cdb4ab67 67
Jonathan Austin 1:8aa5cdb4ab67 68 fiber_add_idle_component(this);
Jonathan Austin 1:8aa5cdb4ab67 69
Jonathan Austin 1:8aa5cdb4ab67 70 if(EventModel::defaultEventBus == NULL)
Jonathan Austin 1:8aa5cdb4ab67 71 EventModel::defaultEventBus = this;
Jonathan Austin 1:8aa5cdb4ab67 72 }
Jonathan Austin 1:8aa5cdb4ab67 73
Jonathan Austin 1:8aa5cdb4ab67 74 /**
Jonathan Austin 1:8aa5cdb4ab67 75 * Invokes a callback on a given MicroBitListener
Jonathan Austin 1:8aa5cdb4ab67 76 *
Jonathan Austin 1:8aa5cdb4ab67 77 * Internal wrapper function, used to enable
Jonathan Austin 1:8aa5cdb4ab67 78 * parameterised callbacks through the fiber scheduler.
Jonathan Austin 1:8aa5cdb4ab67 79 */
Jonathan Austin 1:8aa5cdb4ab67 80 void async_callback(void *param)
Jonathan Austin 1:8aa5cdb4ab67 81 {
Jonathan Austin 1:8aa5cdb4ab67 82 MicroBitListener *listener = (MicroBitListener *)param;
Jonathan Austin 1:8aa5cdb4ab67 83
Jonathan Austin 1:8aa5cdb4ab67 84 // OK, now we need to decide how to behave depending on our configuration.
Jonathan Austin 1:8aa5cdb4ab67 85 // If this a fiber f already active within this listener then check our
Jonathan Austin 1:8aa5cdb4ab67 86 // configuration to determine the correct course of action.
Jonathan Austin 1:8aa5cdb4ab67 87 //
Jonathan Austin 1:8aa5cdb4ab67 88
Jonathan Austin 1:8aa5cdb4ab67 89 if (listener->flags & MESSAGE_BUS_LISTENER_BUSY)
Jonathan Austin 1:8aa5cdb4ab67 90 {
Jonathan Austin 1:8aa5cdb4ab67 91 // Drop this event, if that's how we've been configured.
Jonathan Austin 1:8aa5cdb4ab67 92 if (listener->flags & MESSAGE_BUS_LISTENER_DROP_IF_BUSY)
Jonathan Austin 1:8aa5cdb4ab67 93 return;
Jonathan Austin 1:8aa5cdb4ab67 94
Jonathan Austin 1:8aa5cdb4ab67 95 // Queue this event up for later, if that's how we've been configured.
Jonathan Austin 1:8aa5cdb4ab67 96 if (listener->flags & MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY)
Jonathan Austin 1:8aa5cdb4ab67 97 {
Jonathan Austin 1:8aa5cdb4ab67 98 listener->queue(listener->evt);
Jonathan Austin 1:8aa5cdb4ab67 99 return;
Jonathan Austin 1:8aa5cdb4ab67 100 }
Jonathan Austin 1:8aa5cdb4ab67 101 }
Jonathan Austin 1:8aa5cdb4ab67 102
Jonathan Austin 1:8aa5cdb4ab67 103 // Determine the calling convention for the callback, and invoke...
Jonathan Austin 1:8aa5cdb4ab67 104 // C++ is really bad at this! Especially as the ARM compiler is yet to support C++ 11 :-/
Jonathan Austin 1:8aa5cdb4ab67 105
Jonathan Austin 1:8aa5cdb4ab67 106 // Record that we have a fiber going into this listener...
Jonathan Austin 1:8aa5cdb4ab67 107 listener->flags |= MESSAGE_BUS_LISTENER_BUSY;
Jonathan Austin 1:8aa5cdb4ab67 108
Jonathan Austin 1:8aa5cdb4ab67 109 while (1)
Jonathan Austin 1:8aa5cdb4ab67 110 {
Jonathan Austin 1:8aa5cdb4ab67 111 // Firstly, check for a method callback into an object.
Jonathan Austin 1:8aa5cdb4ab67 112 if (listener->flags & MESSAGE_BUS_LISTENER_METHOD)
Jonathan Austin 1:8aa5cdb4ab67 113 listener->cb_method->fire(listener->evt);
Jonathan Austin 1:8aa5cdb4ab67 114
Jonathan Austin 1:8aa5cdb4ab67 115 // Now a parameterised C function
Jonathan Austin 1:8aa5cdb4ab67 116 else if (listener->flags & MESSAGE_BUS_LISTENER_PARAMETERISED)
Jonathan Austin 1:8aa5cdb4ab67 117 listener->cb_param(listener->evt, listener->cb_arg);
Jonathan Austin 1:8aa5cdb4ab67 118
Jonathan Austin 1:8aa5cdb4ab67 119 // We must have a plain C function
Jonathan Austin 1:8aa5cdb4ab67 120 else
Jonathan Austin 1:8aa5cdb4ab67 121 listener->cb(listener->evt);
Jonathan Austin 1:8aa5cdb4ab67 122
Jonathan Austin 1:8aa5cdb4ab67 123 // If there are more events to process, dequeue the next one and process it.
Jonathan Austin 1:8aa5cdb4ab67 124 if ((listener->flags & MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY) && listener->evt_queue)
Jonathan Austin 1:8aa5cdb4ab67 125 {
Jonathan Austin 1:8aa5cdb4ab67 126 MicroBitEventQueueItem *item = listener->evt_queue;
Jonathan Austin 1:8aa5cdb4ab67 127
Jonathan Austin 1:8aa5cdb4ab67 128 listener->evt = item->evt;
Jonathan Austin 1:8aa5cdb4ab67 129 listener->evt_queue = listener->evt_queue->next;
Jonathan Austin 1:8aa5cdb4ab67 130 delete item;
Jonathan Austin 1:8aa5cdb4ab67 131
Jonathan Austin 1:8aa5cdb4ab67 132 // We spin the scheduler here, to preven any particular event handler from continuously holding onto resources.
Jonathan Austin 1:8aa5cdb4ab67 133 schedule();
Jonathan Austin 1:8aa5cdb4ab67 134 }
Jonathan Austin 1:8aa5cdb4ab67 135 else
Jonathan Austin 1:8aa5cdb4ab67 136 break;
Jonathan Austin 1:8aa5cdb4ab67 137 }
Jonathan Austin 1:8aa5cdb4ab67 138
Jonathan Austin 1:8aa5cdb4ab67 139 // The fiber of exiting... clear our state.
Jonathan Austin 1:8aa5cdb4ab67 140 listener->flags &= ~MESSAGE_BUS_LISTENER_BUSY;
Jonathan Austin 1:8aa5cdb4ab67 141 }
Jonathan Austin 1:8aa5cdb4ab67 142
Jonathan Austin 1:8aa5cdb4ab67 143 /**
Jonathan Austin 1:8aa5cdb4ab67 144 * Queue the given event for processing at a later time.
Jonathan Austin 1:8aa5cdb4ab67 145 * Add the given event at the tail of our queue.
Jonathan Austin 1:8aa5cdb4ab67 146 *
Jonathan Austin 1:8aa5cdb4ab67 147 * @param The event to queue.
Jonathan Austin 1:8aa5cdb4ab67 148 */
Jonathan Austin 1:8aa5cdb4ab67 149 void MicroBitMessageBus::queueEvent(MicroBitEvent &evt)
Jonathan Austin 1:8aa5cdb4ab67 150 {
Jonathan Austin 1:8aa5cdb4ab67 151 int processingComplete;
Jonathan Austin 1:8aa5cdb4ab67 152
Jonathan Austin 1:8aa5cdb4ab67 153 MicroBitEventQueueItem *prev = evt_queue_tail;
Jonathan Austin 1:8aa5cdb4ab67 154
Jonathan Austin 1:8aa5cdb4ab67 155 // Now process all handler regsitered as URGENT.
Jonathan Austin 1:8aa5cdb4ab67 156 // These pre-empt the queue, and are useful for fast, high priority services.
Jonathan Austin 1:8aa5cdb4ab67 157 processingComplete = this->process(evt, true);
Jonathan Austin 1:8aa5cdb4ab67 158
Jonathan Austin 1:8aa5cdb4ab67 159 // If we've already processed all event handlers, we're all done.
Jonathan Austin 1:8aa5cdb4ab67 160 // No need to queue the event.
Jonathan Austin 1:8aa5cdb4ab67 161 if (processingComplete)
Jonathan Austin 1:8aa5cdb4ab67 162 return;
Jonathan Austin 1:8aa5cdb4ab67 163
Jonathan Austin 1:8aa5cdb4ab67 164 // If we need to queue, but there is no space, then there's nothg we can do.
Jonathan Austin 1:8aa5cdb4ab67 165 if (queueLength >= MESSAGE_BUS_LISTENER_MAX_QUEUE_DEPTH)
Jonathan Austin 1:8aa5cdb4ab67 166 return;
Jonathan Austin 1:8aa5cdb4ab67 167
Jonathan Austin 1:8aa5cdb4ab67 168 // Otherwise, we need to queue this event for later processing...
Jonathan Austin 1:8aa5cdb4ab67 169 // We queue this event at the tail of the queue at the point where we entered queueEvent()
Jonathan Austin 1:8aa5cdb4ab67 170 // This is important as the processing above *may* have generated further events, and
Jonathan Austin 1:8aa5cdb4ab67 171 // we want to maintain ordering of events.
Jonathan Austin 1:8aa5cdb4ab67 172 MicroBitEventQueueItem *item = new MicroBitEventQueueItem(evt);
Jonathan Austin 1:8aa5cdb4ab67 173
Jonathan Austin 1:8aa5cdb4ab67 174 // The queue was empty when we entered this function, so queue our event at the start of the queue.
Jonathan Austin 1:8aa5cdb4ab67 175 __disable_irq();
Jonathan Austin 1:8aa5cdb4ab67 176
Jonathan Austin 1:8aa5cdb4ab67 177 if (prev == NULL)
Jonathan Austin 1:8aa5cdb4ab67 178 {
Jonathan Austin 1:8aa5cdb4ab67 179 item->next = evt_queue_head;
Jonathan Austin 1:8aa5cdb4ab67 180 evt_queue_head = item;
Jonathan Austin 1:8aa5cdb4ab67 181 }
Jonathan Austin 1:8aa5cdb4ab67 182 else
Jonathan Austin 1:8aa5cdb4ab67 183 {
Jonathan Austin 1:8aa5cdb4ab67 184 item->next = prev->next;
Jonathan Austin 1:8aa5cdb4ab67 185 prev->next = item;
Jonathan Austin 1:8aa5cdb4ab67 186 }
Jonathan Austin 1:8aa5cdb4ab67 187
Jonathan Austin 1:8aa5cdb4ab67 188 if (item->next == NULL)
Jonathan Austin 1:8aa5cdb4ab67 189 evt_queue_tail = item;
Jonathan Austin 1:8aa5cdb4ab67 190
Jonathan Austin 1:8aa5cdb4ab67 191 queueLength++;
Jonathan Austin 1:8aa5cdb4ab67 192
Jonathan Austin 1:8aa5cdb4ab67 193 __enable_irq();
Jonathan Austin 1:8aa5cdb4ab67 194 }
Jonathan Austin 1:8aa5cdb4ab67 195
Jonathan Austin 1:8aa5cdb4ab67 196 /**
Jonathan Austin 1:8aa5cdb4ab67 197 * Extract the next event from the front of the event queue (if present).
Jonathan Austin 1:8aa5cdb4ab67 198 *
Jonathan Austin 1:8aa5cdb4ab67 199 * @return a pointer to the MicroBitEventQueueItem that is at the head of the list.
Jonathan Austin 1:8aa5cdb4ab67 200 */
Jonathan Austin 1:8aa5cdb4ab67 201 MicroBitEventQueueItem* MicroBitMessageBus::dequeueEvent()
Jonathan Austin 1:8aa5cdb4ab67 202 {
Jonathan Austin 1:8aa5cdb4ab67 203 MicroBitEventQueueItem *item = NULL;
Jonathan Austin 1:8aa5cdb4ab67 204
Jonathan Austin 1:8aa5cdb4ab67 205 __disable_irq();
Jonathan Austin 1:8aa5cdb4ab67 206
Jonathan Austin 1:8aa5cdb4ab67 207 if (evt_queue_head != NULL)
Jonathan Austin 1:8aa5cdb4ab67 208 {
Jonathan Austin 1:8aa5cdb4ab67 209 item = evt_queue_head;
Jonathan Austin 1:8aa5cdb4ab67 210 evt_queue_head = item->next;
Jonathan Austin 1:8aa5cdb4ab67 211
Jonathan Austin 1:8aa5cdb4ab67 212 if (evt_queue_head == NULL)
Jonathan Austin 1:8aa5cdb4ab67 213 evt_queue_tail = NULL;
Jonathan Austin 1:8aa5cdb4ab67 214
Jonathan Austin 1:8aa5cdb4ab67 215 queueLength--;
Jonathan Austin 1:8aa5cdb4ab67 216 }
Jonathan Austin 1:8aa5cdb4ab67 217
Jonathan Austin 1:8aa5cdb4ab67 218 __enable_irq();
Jonathan Austin 1:8aa5cdb4ab67 219
Jonathan Austin 1:8aa5cdb4ab67 220
Jonathan Austin 1:8aa5cdb4ab67 221 return item;
Jonathan Austin 1:8aa5cdb4ab67 222 }
Jonathan Austin 1:8aa5cdb4ab67 223
Jonathan Austin 1:8aa5cdb4ab67 224 /**
Jonathan Austin 1:8aa5cdb4ab67 225 * Cleanup any MicroBitListeners marked for deletion from the list.
Jonathan Austin 1:8aa5cdb4ab67 226 *
Jonathan Austin 1:8aa5cdb4ab67 227 * @return The number of listeners removed from the list.
Jonathan Austin 1:8aa5cdb4ab67 228 */
Jonathan Austin 1:8aa5cdb4ab67 229 int MicroBitMessageBus::deleteMarkedListeners()
Jonathan Austin 1:8aa5cdb4ab67 230 {
Jonathan Austin 1:8aa5cdb4ab67 231 MicroBitListener *l, *p;
Jonathan Austin 1:8aa5cdb4ab67 232 int removed = 0;
Jonathan Austin 1:8aa5cdb4ab67 233
Jonathan Austin 1:8aa5cdb4ab67 234 l = listeners;
Jonathan Austin 1:8aa5cdb4ab67 235 p = NULL;
Jonathan Austin 1:8aa5cdb4ab67 236
Jonathan Austin 1:8aa5cdb4ab67 237 // Walk this list of event handlers. Delete any that match the given listener.
Jonathan Austin 1:8aa5cdb4ab67 238 while (l != NULL)
Jonathan Austin 1:8aa5cdb4ab67 239 {
Jonathan Austin 1:8aa5cdb4ab67 240 if (l->flags & MESSAGE_BUS_LISTENER_DELETING && !l->flags & MESSAGE_BUS_LISTENER_BUSY)
Jonathan Austin 1:8aa5cdb4ab67 241 {
Jonathan Austin 1:8aa5cdb4ab67 242 if (p == NULL)
Jonathan Austin 1:8aa5cdb4ab67 243 listeners = l->next;
Jonathan Austin 1:8aa5cdb4ab67 244 else
Jonathan Austin 1:8aa5cdb4ab67 245 p->next = l->next;
Jonathan Austin 1:8aa5cdb4ab67 246
Jonathan Austin 1:8aa5cdb4ab67 247 // delete the listener.
Jonathan Austin 1:8aa5cdb4ab67 248 MicroBitListener *t = l;
Jonathan Austin 1:8aa5cdb4ab67 249 l = l->next;
Jonathan Austin 1:8aa5cdb4ab67 250
Jonathan Austin 1:8aa5cdb4ab67 251 delete t;
Jonathan Austin 1:8aa5cdb4ab67 252 removed++;
Jonathan Austin 1:8aa5cdb4ab67 253
Jonathan Austin 1:8aa5cdb4ab67 254 continue;
Jonathan Austin 1:8aa5cdb4ab67 255 }
Jonathan Austin 1:8aa5cdb4ab67 256
Jonathan Austin 1:8aa5cdb4ab67 257 p = l;
Jonathan Austin 1:8aa5cdb4ab67 258 l = l->next;
Jonathan Austin 1:8aa5cdb4ab67 259 }
Jonathan Austin 1:8aa5cdb4ab67 260
Jonathan Austin 1:8aa5cdb4ab67 261 return removed;
Jonathan Austin 1:8aa5cdb4ab67 262 }
Jonathan Austin 1:8aa5cdb4ab67 263
Jonathan Austin 1:8aa5cdb4ab67 264 /**
Jonathan Austin 1:8aa5cdb4ab67 265 * Periodic callback from MicroBit.
Jonathan Austin 1:8aa5cdb4ab67 266 *
Jonathan Austin 1:8aa5cdb4ab67 267 * Process at least one event from the event queue, if it is not empty.
Jonathan Austin 1:8aa5cdb4ab67 268 * We then continue processing events until something appears on the runqueue.
Jonathan Austin 1:8aa5cdb4ab67 269 */
Jonathan Austin 1:8aa5cdb4ab67 270 void MicroBitMessageBus::idleTick()
Jonathan Austin 1:8aa5cdb4ab67 271 {
Jonathan Austin 1:8aa5cdb4ab67 272 // Clear out any listeners marked for deletion
Jonathan Austin 1:8aa5cdb4ab67 273 this->deleteMarkedListeners();
Jonathan Austin 1:8aa5cdb4ab67 274
Jonathan Austin 1:8aa5cdb4ab67 275 MicroBitEventQueueItem *item = this->dequeueEvent();
Jonathan Austin 1:8aa5cdb4ab67 276
Jonathan Austin 1:8aa5cdb4ab67 277 // Whilst there are events to process and we have no useful other work to do, pull them off the queue and process them.
Jonathan Austin 1:8aa5cdb4ab67 278 while (item)
Jonathan Austin 1:8aa5cdb4ab67 279 {
Jonathan Austin 1:8aa5cdb4ab67 280 // send the event to all standard event listeners.
Jonathan Austin 1:8aa5cdb4ab67 281 this->process(item->evt);
Jonathan Austin 1:8aa5cdb4ab67 282
Jonathan Austin 1:8aa5cdb4ab67 283 // Free the queue item.
Jonathan Austin 1:8aa5cdb4ab67 284 delete item;
Jonathan Austin 1:8aa5cdb4ab67 285
Jonathan Austin 1:8aa5cdb4ab67 286 // If we have created some useful work to do, we stop processing.
Jonathan Austin 1:8aa5cdb4ab67 287 // This helps to minimise the number of blocked fibers we create at any point in time, therefore
Jonathan Austin 1:8aa5cdb4ab67 288 // also reducing the RAM footprint.
Jonathan Austin 1:8aa5cdb4ab67 289 if(!scheduler_runqueue_empty())
Jonathan Austin 1:8aa5cdb4ab67 290 break;
Jonathan Austin 1:8aa5cdb4ab67 291
Jonathan Austin 1:8aa5cdb4ab67 292 // Pull the next event to process, if there is one.
Jonathan Austin 1:8aa5cdb4ab67 293 item = this->dequeueEvent();
Jonathan Austin 1:8aa5cdb4ab67 294 }
Jonathan Austin 1:8aa5cdb4ab67 295 }
Jonathan Austin 1:8aa5cdb4ab67 296
Jonathan Austin 1:8aa5cdb4ab67 297 /**
Jonathan Austin 1:8aa5cdb4ab67 298 * Indicates whether or not we have any background work to do.
Jonathan Austin 1:8aa5cdb4ab67 299 *
Jonathan Austin 1:8aa5cdb4ab67 300 * @return 1 if there are any events waitingto be processed, 0 otherwise.
Jonathan Austin 1:8aa5cdb4ab67 301 */
Jonathan Austin 1:8aa5cdb4ab67 302 int MicroBitMessageBus::isIdleCallbackNeeded()
Jonathan Austin 1:8aa5cdb4ab67 303 {
Jonathan Austin 1:8aa5cdb4ab67 304 return !(evt_queue_head == NULL);
Jonathan Austin 1:8aa5cdb4ab67 305 }
Jonathan Austin 1:8aa5cdb4ab67 306
Jonathan Austin 1:8aa5cdb4ab67 307 /**
Jonathan Austin 1:8aa5cdb4ab67 308 * Queues the given event to be sent to all registered recipients.
Jonathan Austin 1:8aa5cdb4ab67 309 *
Jonathan Austin 1:8aa5cdb4ab67 310 * @param evt The event to send.
Jonathan Austin 1:8aa5cdb4ab67 311 *
Jonathan Austin 1:8aa5cdb4ab67 312 * @code
Jonathan Austin 1:8aa5cdb4ab67 313 * MicroBitMessageBus bus;
Jonathan Austin 1:8aa5cdb4ab67 314 *
Jonathan Austin 1:8aa5cdb4ab67 315 * // Creates and sends the MicroBitEvent using bus.
Jonathan Austin 1:8aa5cdb4ab67 316 * MicrobitEvent evt(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK);
Jonathan Austin 1:8aa5cdb4ab67 317 *
Jonathan Austin 1:8aa5cdb4ab67 318 * // Creates the MicrobitEvent, but delays the sending of that event.
Jonathan Austin 1:8aa5cdb4ab67 319 * MicrobitEvent evt1(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK, CREATE_ONLY);
Jonathan Austin 1:8aa5cdb4ab67 320 *
Jonathan Austin 1:8aa5cdb4ab67 321 * bus.send(evt1);
Jonathan Austin 1:8aa5cdb4ab67 322 *
Jonathan Austin 1:8aa5cdb4ab67 323 * // This has the same effect!
Jonathan Austin 1:8aa5cdb4ab67 324 * evt1.fire()
Jonathan Austin 1:8aa5cdb4ab67 325 * @endcode
Jonathan Austin 1:8aa5cdb4ab67 326 */
Jonathan Austin 1:8aa5cdb4ab67 327 int MicroBitMessageBus::send(MicroBitEvent evt)
Jonathan Austin 1:8aa5cdb4ab67 328 {
Jonathan Austin 1:8aa5cdb4ab67 329 // We simply queue processing of the event until we're scheduled in normal thread context.
Jonathan Austin 1:8aa5cdb4ab67 330 // We do this to avoid the possibility of executing event handler code in IRQ context, which may bring
Jonathan Austin 1:8aa5cdb4ab67 331 // hidden race conditions to kids code. Queuing all events ensures causal ordering (total ordering in fact).
Jonathan Austin 1:8aa5cdb4ab67 332 this->queueEvent(evt);
Jonathan Austin 1:8aa5cdb4ab67 333 return MICROBIT_OK;
Jonathan Austin 1:8aa5cdb4ab67 334 }
Jonathan Austin 1:8aa5cdb4ab67 335
Jonathan Austin 1:8aa5cdb4ab67 336 /**
Jonathan Austin 1:8aa5cdb4ab67 337 * Internal function, used to deliver the given event to all relevant recipients.
Jonathan Austin 1:8aa5cdb4ab67 338 * Normally, this is called once an event has been removed from the event queue.
Jonathan Austin 1:8aa5cdb4ab67 339 *
Jonathan Austin 1:8aa5cdb4ab67 340 * @param evt The event to send.
Jonathan Austin 1:8aa5cdb4ab67 341 *
Jonathan Austin 1:8aa5cdb4ab67 342 * @param urgent The type of listeners to process (optional). If set to true, only listeners defined as urgent and non-blocking will be processed
Jonathan Austin 1:8aa5cdb4ab67 343 * otherwise, all other (standard) listeners will be processed. Defaults to false.
Jonathan Austin 1:8aa5cdb4ab67 344 *
Jonathan Austin 1:8aa5cdb4ab67 345 * @return 1 if all matching listeners were processed, 0 if further processing is required.
Jonathan Austin 1:8aa5cdb4ab67 346 *
Jonathan Austin 1:8aa5cdb4ab67 347 * @note It is recommended that all external code uses the send() function instead of this function,
Jonathan Austin 1:8aa5cdb4ab67 348 * or the constructors provided by MicrobitEvent.
Jonathan Austin 1:8aa5cdb4ab67 349 */
Jonathan Austin 1:8aa5cdb4ab67 350 int MicroBitMessageBus::process(MicroBitEvent &evt, bool urgent)
Jonathan Austin 1:8aa5cdb4ab67 351 {
Jonathan Austin 1:8aa5cdb4ab67 352 MicroBitListener *l;
Jonathan Austin 1:8aa5cdb4ab67 353 int complete = 1;
Jonathan Austin 1:8aa5cdb4ab67 354 bool listenerUrgent;
Jonathan Austin 1:8aa5cdb4ab67 355
Jonathan Austin 1:8aa5cdb4ab67 356 l = listeners;
Jonathan Austin 1:8aa5cdb4ab67 357 while (l != NULL)
Jonathan Austin 1:8aa5cdb4ab67 358 {
Jonathan Austin 1:8aa5cdb4ab67 359 if((l->id == evt.source || l->id == MICROBIT_ID_ANY) && (l->value == evt.value || l->value == MICROBIT_EVT_ANY))
Jonathan Austin 1:8aa5cdb4ab67 360 {
Jonathan Austin 1:8aa5cdb4ab67 361 listenerUrgent = (l->flags & MESSAGE_BUS_LISTENER_IMMEDIATE) == MESSAGE_BUS_LISTENER_IMMEDIATE;
Jonathan Austin 1:8aa5cdb4ab67 362 if(listenerUrgent == urgent && !(l->flags & MESSAGE_BUS_LISTENER_DELETING))
Jonathan Austin 1:8aa5cdb4ab67 363 {
Jonathan Austin 1:8aa5cdb4ab67 364 l->evt = evt;
Jonathan Austin 1:8aa5cdb4ab67 365
Jonathan Austin 1:8aa5cdb4ab67 366 // OK, if this handler has regisitered itself as non-blocking, we just execute it directly...
Jonathan Austin 1:8aa5cdb4ab67 367 // This is normally only done for trusted system components.
Jonathan Austin 1:8aa5cdb4ab67 368 // Otherwise, we invoke it in a 'fork on block' context, that will automatically create a fiber
Jonathan Austin 1:8aa5cdb4ab67 369 // should the event handler attempt a blocking operation, but doesn't have the overhead
Jonathan Austin 1:8aa5cdb4ab67 370 // of creating a fiber needlessly. (cool huh?)
Jonathan Austin 1:8aa5cdb4ab67 371 if (l->flags & MESSAGE_BUS_LISTENER_NONBLOCKING || !fiber_scheduler_running())
Jonathan Austin 1:8aa5cdb4ab67 372 async_callback(l);
Jonathan Austin 1:8aa5cdb4ab67 373 else
Jonathan Austin 1:8aa5cdb4ab67 374 invoke(async_callback, l);
Jonathan Austin 1:8aa5cdb4ab67 375 }
Jonathan Austin 1:8aa5cdb4ab67 376 else
Jonathan Austin 1:8aa5cdb4ab67 377 {
Jonathan Austin 1:8aa5cdb4ab67 378 complete = 0;
Jonathan Austin 1:8aa5cdb4ab67 379 }
Jonathan Austin 1:8aa5cdb4ab67 380 }
Jonathan Austin 1:8aa5cdb4ab67 381
Jonathan Austin 1:8aa5cdb4ab67 382 l = l->next;
Jonathan Austin 1:8aa5cdb4ab67 383 }
Jonathan Austin 1:8aa5cdb4ab67 384
Jonathan Austin 1:8aa5cdb4ab67 385 return complete;
Jonathan Austin 1:8aa5cdb4ab67 386 }
Jonathan Austin 1:8aa5cdb4ab67 387
Jonathan Austin 1:8aa5cdb4ab67 388 /**
Jonathan Austin 1:8aa5cdb4ab67 389 * Add the given MicroBitListener to the list of event handlers, unconditionally.
Jonathan Austin 1:8aa5cdb4ab67 390 *
Jonathan Austin 1:8aa5cdb4ab67 391 * @param listener The MicroBitListener to add.
Jonathan Austin 1:8aa5cdb4ab67 392 *
Jonathan Austin 1:8aa5cdb4ab67 393 * @return MICROBIT_OK if the listener is valid, MICROBIT_INVALID_PARAMETER otherwise.
Jonathan Austin 1:8aa5cdb4ab67 394 */
Jonathan Austin 1:8aa5cdb4ab67 395 int MicroBitMessageBus::add(MicroBitListener *newListener)
Jonathan Austin 1:8aa5cdb4ab67 396 {
Jonathan Austin 1:8aa5cdb4ab67 397 MicroBitListener *l, *p;
Jonathan Austin 1:8aa5cdb4ab67 398 int methodCallback;
Jonathan Austin 1:8aa5cdb4ab67 399
Jonathan Austin 1:8aa5cdb4ab67 400 //handler can't be NULL!
Jonathan Austin 1:8aa5cdb4ab67 401 if (newListener == NULL)
Jonathan Austin 1:8aa5cdb4ab67 402 return MICROBIT_INVALID_PARAMETER;
Jonathan Austin 1:8aa5cdb4ab67 403
Jonathan Austin 1:8aa5cdb4ab67 404 l = listeners;
Jonathan Austin 1:8aa5cdb4ab67 405
Jonathan Austin 1:8aa5cdb4ab67 406 // Firstly, we treat a listener as an idempotent operation. Ensure we don't already have this handler
Jonathan Austin 1:8aa5cdb4ab67 407 // registered in a that will already capture these events. If we do, silently ignore.
Jonathan Austin 1:8aa5cdb4ab67 408
Jonathan Austin 1:8aa5cdb4ab67 409 // We always check the ID, VALUE and CB_METHOD fields.
Jonathan Austin 1:8aa5cdb4ab67 410 // If we have a callback to a method, check the cb_method class. Otherwise, the cb function point is sufficient.
Jonathan Austin 1:8aa5cdb4ab67 411 while (l != NULL)
Jonathan Austin 1:8aa5cdb4ab67 412 {
Jonathan Austin 1:8aa5cdb4ab67 413 methodCallback = (newListener->flags & MESSAGE_BUS_LISTENER_METHOD) && (l->flags & MESSAGE_BUS_LISTENER_METHOD);
Jonathan Austin 1:8aa5cdb4ab67 414
Jonathan Austin 1:8aa5cdb4ab67 415 if (l->id == newListener->id && l->value == newListener->value && (methodCallback ? *l->cb_method == *newListener->cb_method : l->cb == newListener->cb))
Jonathan Austin 1:8aa5cdb4ab67 416 {
Jonathan Austin 1:8aa5cdb4ab67 417 // We have a perfect match for this event listener already registered.
Jonathan Austin 1:8aa5cdb4ab67 418 // If it's marked for deletion, we simply resurrect the listener, and we're done.
Jonathan Austin 1:8aa5cdb4ab67 419 // Either way, we return an error code, as the *new* listener should be released...
Jonathan Austin 1:8aa5cdb4ab67 420 if(l->flags & MESSAGE_BUS_LISTENER_DELETING)
Jonathan Austin 1:8aa5cdb4ab67 421 l->flags &= ~MESSAGE_BUS_LISTENER_DELETING;
Jonathan Austin 1:8aa5cdb4ab67 422
Jonathan Austin 1:8aa5cdb4ab67 423 return MICROBIT_NOT_SUPPORTED;
Jonathan Austin 1:8aa5cdb4ab67 424 }
Jonathan Austin 1:8aa5cdb4ab67 425
Jonathan Austin 1:8aa5cdb4ab67 426 l = l->next;
Jonathan Austin 1:8aa5cdb4ab67 427 }
Jonathan Austin 1:8aa5cdb4ab67 428
Jonathan Austin 1:8aa5cdb4ab67 429 // We have a valid, new event handler. Add it to the list.
Jonathan Austin 1:8aa5cdb4ab67 430 // if listeners is null - we can automatically add this listener to the list at the beginning...
Jonathan Austin 1:8aa5cdb4ab67 431 if (listeners == NULL)
Jonathan Austin 1:8aa5cdb4ab67 432 {
Jonathan Austin 1:8aa5cdb4ab67 433 listeners = newListener;
Jonathan Austin 1:8aa5cdb4ab67 434 MicroBitEvent(MICROBIT_ID_MESSAGE_BUS_LISTENER, newListener->id);
Jonathan Austin 1:8aa5cdb4ab67 435
Jonathan Austin 1:8aa5cdb4ab67 436 return MICROBIT_OK;
Jonathan Austin 1:8aa5cdb4ab67 437 }
Jonathan Austin 1:8aa5cdb4ab67 438
Jonathan Austin 1:8aa5cdb4ab67 439 // We maintain an ordered list of listeners.
Jonathan Austin 1:8aa5cdb4ab67 440 // The chain is held stictly in increasing order of ID (first level), then value code (second level).
Jonathan Austin 1:8aa5cdb4ab67 441 // Find the correct point in the chain for this event.
Jonathan Austin 1:8aa5cdb4ab67 442 // Adding a listener is a rare occurance, so we just walk the list...
Jonathan Austin 1:8aa5cdb4ab67 443
Jonathan Austin 1:8aa5cdb4ab67 444 p = listeners;
Jonathan Austin 1:8aa5cdb4ab67 445 l = listeners;
Jonathan Austin 1:8aa5cdb4ab67 446
Jonathan Austin 1:8aa5cdb4ab67 447 while (l != NULL && l->id < newListener->id)
Jonathan Austin 1:8aa5cdb4ab67 448 {
Jonathan Austin 1:8aa5cdb4ab67 449 p = l;
Jonathan Austin 1:8aa5cdb4ab67 450 l = l->next;
Jonathan Austin 1:8aa5cdb4ab67 451 }
Jonathan Austin 1:8aa5cdb4ab67 452
Jonathan Austin 1:8aa5cdb4ab67 453 while (l != NULL && l->id == newListener->id && l->value < newListener->value)
Jonathan Austin 1:8aa5cdb4ab67 454 {
Jonathan Austin 1:8aa5cdb4ab67 455 p = l;
Jonathan Austin 1:8aa5cdb4ab67 456 l = l->next;
Jonathan Austin 1:8aa5cdb4ab67 457 }
Jonathan Austin 1:8aa5cdb4ab67 458
Jonathan Austin 1:8aa5cdb4ab67 459 //add at front of list
Jonathan Austin 1:8aa5cdb4ab67 460 if (p == listeners && (newListener->id < p->id || (p->id == newListener->id && p->value > newListener->value)))
Jonathan Austin 1:8aa5cdb4ab67 461 {
Jonathan Austin 1:8aa5cdb4ab67 462 newListener->next = p;
Jonathan Austin 1:8aa5cdb4ab67 463
Jonathan Austin 1:8aa5cdb4ab67 464 //this new listener is now the front!
Jonathan Austin 1:8aa5cdb4ab67 465 listeners = newListener;
Jonathan Austin 1:8aa5cdb4ab67 466 }
Jonathan Austin 1:8aa5cdb4ab67 467
Jonathan Austin 1:8aa5cdb4ab67 468 //add after p
Jonathan Austin 1:8aa5cdb4ab67 469 else
Jonathan Austin 1:8aa5cdb4ab67 470 {
Jonathan Austin 1:8aa5cdb4ab67 471 newListener->next = p->next;
Jonathan Austin 1:8aa5cdb4ab67 472 p->next = newListener;
Jonathan Austin 1:8aa5cdb4ab67 473 }
Jonathan Austin 1:8aa5cdb4ab67 474
Jonathan Austin 1:8aa5cdb4ab67 475 MicroBitEvent(MICROBIT_ID_MESSAGE_BUS_LISTENER, newListener->id);
Jonathan Austin 1:8aa5cdb4ab67 476 return MICROBIT_OK;
Jonathan Austin 1:8aa5cdb4ab67 477 }
Jonathan Austin 1:8aa5cdb4ab67 478
Jonathan Austin 1:8aa5cdb4ab67 479 /**
Jonathan Austin 1:8aa5cdb4ab67 480 * Remove the given MicroBitListener from the list of event handlers.
Jonathan Austin 1:8aa5cdb4ab67 481 *
Jonathan Austin 1:8aa5cdb4ab67 482 * @param listener The MicroBitListener to remove.
Jonathan Austin 1:8aa5cdb4ab67 483 *
Jonathan Austin 1:8aa5cdb4ab67 484 * @return MICROBIT_OK if the listener is valid, MICROBIT_INVALID_PARAMETER otherwise.
Jonathan Austin 1:8aa5cdb4ab67 485 */
Jonathan Austin 1:8aa5cdb4ab67 486 int MicroBitMessageBus::remove(MicroBitListener *listener)
Jonathan Austin 1:8aa5cdb4ab67 487 {
Jonathan Austin 1:8aa5cdb4ab67 488 MicroBitListener *l;
Jonathan Austin 1:8aa5cdb4ab67 489 int removed = 0;
Jonathan Austin 1:8aa5cdb4ab67 490
Jonathan Austin 1:8aa5cdb4ab67 491 //handler can't be NULL!
Jonathan Austin 1:8aa5cdb4ab67 492 if (listener == NULL)
Jonathan Austin 1:8aa5cdb4ab67 493 return MICROBIT_INVALID_PARAMETER;
Jonathan Austin 1:8aa5cdb4ab67 494
Jonathan Austin 1:8aa5cdb4ab67 495 l = listeners;
Jonathan Austin 1:8aa5cdb4ab67 496
Jonathan Austin 1:8aa5cdb4ab67 497 // Walk this list of event handlers. Delete any that match the given listener.
Jonathan Austin 1:8aa5cdb4ab67 498 while (l != NULL)
Jonathan Austin 1:8aa5cdb4ab67 499 {
Jonathan Austin 1:8aa5cdb4ab67 500 if ((listener->flags & MESSAGE_BUS_LISTENER_METHOD) == (l->flags & MESSAGE_BUS_LISTENER_METHOD))
Jonathan Austin 1:8aa5cdb4ab67 501 {
Jonathan Austin 1:8aa5cdb4ab67 502 if(((listener->flags & MESSAGE_BUS_LISTENER_METHOD) && (*l->cb_method == *listener->cb_method)) ||
Jonathan Austin 1:8aa5cdb4ab67 503 ((!(listener->flags & MESSAGE_BUS_LISTENER_METHOD) && l->cb == listener->cb)))
Jonathan Austin 1:8aa5cdb4ab67 504 {
Jonathan Austin 1:8aa5cdb4ab67 505 if ((listener->id == MICROBIT_ID_ANY || listener->id == l->id) && (listener->value == MICROBIT_EVT_ANY || listener->value == l->value))
Jonathan Austin 1:8aa5cdb4ab67 506 {
Jonathan Austin 1:8aa5cdb4ab67 507 // Found a match. mark this to be removed from the list.
Jonathan Austin 1:8aa5cdb4ab67 508 l->flags |= MESSAGE_BUS_LISTENER_DELETING;
Jonathan Austin 1:8aa5cdb4ab67 509 removed++;
Jonathan Austin 1:8aa5cdb4ab67 510 }
Jonathan Austin 1:8aa5cdb4ab67 511 }
Jonathan Austin 1:8aa5cdb4ab67 512 }
Jonathan Austin 1:8aa5cdb4ab67 513
Jonathan Austin 1:8aa5cdb4ab67 514 l = l->next;
Jonathan Austin 1:8aa5cdb4ab67 515 }
Jonathan Austin 1:8aa5cdb4ab67 516
Jonathan Austin 1:8aa5cdb4ab67 517 if (removed > 0)
Jonathan Austin 1:8aa5cdb4ab67 518 return MICROBIT_OK;
Jonathan Austin 1:8aa5cdb4ab67 519 else
Jonathan Austin 1:8aa5cdb4ab67 520 return MICROBIT_INVALID_PARAMETER;
Jonathan Austin 1:8aa5cdb4ab67 521 }
Jonathan Austin 1:8aa5cdb4ab67 522
Jonathan Austin 1:8aa5cdb4ab67 523 /**
Jonathan Austin 1:8aa5cdb4ab67 524 * Returns the microBitListener with the given position in our list.
Jonathan Austin 1:8aa5cdb4ab67 525 *
Jonathan Austin 1:8aa5cdb4ab67 526 * @param n The position in the list to return.
Jonathan Austin 1:8aa5cdb4ab67 527 *
Jonathan Austin 1:8aa5cdb4ab67 528 * @return the MicroBitListener at postion n in the list, or NULL if the position is invalid.
Jonathan Austin 1:8aa5cdb4ab67 529 */
Jonathan Austin 1:8aa5cdb4ab67 530 MicroBitListener* MicroBitMessageBus::elementAt(int n)
Jonathan Austin 1:8aa5cdb4ab67 531 {
Jonathan Austin 1:8aa5cdb4ab67 532 MicroBitListener *l = listeners;
Jonathan Austin 1:8aa5cdb4ab67 533
Jonathan Austin 1:8aa5cdb4ab67 534 while (n > 0)
Jonathan Austin 1:8aa5cdb4ab67 535 {
Jonathan Austin 1:8aa5cdb4ab67 536 if (l == NULL)
Jonathan Austin 1:8aa5cdb4ab67 537 return NULL;
Jonathan Austin 1:8aa5cdb4ab67 538
Jonathan Austin 1:8aa5cdb4ab67 539 n--;
Jonathan Austin 1:8aa5cdb4ab67 540 l = l->next;
Jonathan Austin 1:8aa5cdb4ab67 541 }
Jonathan Austin 1:8aa5cdb4ab67 542
Jonathan Austin 1:8aa5cdb4ab67 543 return l;
Jonathan Austin 1:8aa5cdb4ab67 544 }
Jonathan Austin 1:8aa5cdb4ab67 545
Jonathan Austin 1:8aa5cdb4ab67 546 /**
Jonathan Austin 1:8aa5cdb4ab67 547 * Destructor for MicroBitMessageBus, where we deregister this instance from the array of fiber components.
Jonathan Austin 1:8aa5cdb4ab67 548 */
Jonathan Austin 1:8aa5cdb4ab67 549 MicroBitMessageBus::~MicroBitMessageBus()
Jonathan Austin 1:8aa5cdb4ab67 550 {
Jonathan Austin 1:8aa5cdb4ab67 551 fiber_remove_idle_component(this);
Jonathan Austin 1:8aa5cdb4ab67 552 }