diff --git a/inc/ErrorNo.h b/inc/ErrorNo.h index 9781f7f..100c9a3 100644 --- a/inc/ErrorNo.h +++ b/inc/ErrorNo.h @@ -23,6 +23,12 @@ enum ErrorCode{ // The requested operation could not be performed as the device has run out of some essential resource (e.g. allocated memory) MICROBIT_NO_RESOURCES = -1005, + // The requested operation could not be performed as some essential resource is busy (e.g. the display) + MICROBIT_BUSY = -1006, + + // The requested operation was cancelled before it completed. + MICROBIT_CANCELLED = -1007, + // I2C Communication error occured (typically I2C module on processor has locked up.) MICROBIT_I2C_ERROR = -1010 }; diff --git a/inc/MicroBitButton.h b/inc/MicroBitButton.h index 231cadd..59370d4 100644 --- a/inc/MicroBitButton.h +++ b/inc/MicroBitButton.h @@ -31,6 +31,13 @@ #define MICROBIT_BUTTON_SIGMA_THRESH_LO 2 #define MICROBIT_BUTTON_DOUBLE_CLICK_THRESH 50 +enum MicroBitButtonEventConfiguration +{ + MICROBIT_BUTTON_SIMPLE_EVENTS, + MICROBIT_BUTTON_ALL_EVENTS +}; + + /** * Class definition for MicroBit Button. * @@ -38,12 +45,12 @@ */ class MicroBitButton : public MicroBitComponent { - PinName name; // mBed pin name of this pin. - DigitalIn pin; // The mBed object looking after this pin at any point in time (may change!). + PinName name; // mbed pin name of this pin. + DigitalIn pin; // The mbed object looking after this pin at any point in time (may change!). - unsigned long downStartTime; // used to store the current system clock when a button down event occurs - uint8_t sigma; // integration of samples over time. - uint8_t doubleClickTimer; // double click timer (ticks). + unsigned long downStartTime; // used to store the current system clock when a button down event occurs + uint8_t sigma; // integration of samples over time. We use this for debouncing, and noise tolerance for touch sensing + MicroBitButtonEventConfiguration eventConfiguration; // Do we want to generate high level event (clicks), or defer this to another service. public: @@ -69,7 +76,7 @@ class MicroBitButton : public MicroBitComponent * MICROBIT_BUTTON_EVT_HOLD * @endcode */ - MicroBitButton(uint16_t id, PinName name, PinMode mode = PullNone); + MicroBitButton(uint16_t id, PinName name, MicroBitButtonEventConfiguration eventConfiguration = MICROBIT_BUTTON_ALL_EVENTS, PinMode mode = PullNone); /** * Tests if this Button is currently pressed. diff --git a/inc/MicroBitComponent.h b/inc/MicroBitComponent.h index f836a36..3278498 100644 --- a/inc/MicroBitComponent.h +++ b/inc/MicroBitComponent.h @@ -42,7 +42,9 @@ #define MICROBIT_ID_IO_P20 25 //SDA #define MICROBIT_ID_BUTTON_AB 26 // Button A+B multibutton -#define MICROBIT_ID_ALERT 27 // Alert channel, used for general purpose condition synchronisation and alerting. + +#define MICROBIT_ID_NOTIFY 1023 // Notfication channel, for general purpose synchronisation +#define MICROBIT_ID_NOTIFY_ONE 1022 // Notfication channel, for general purpose synchronisation class MicroBitComponent { diff --git a/inc/MicroBitConfig.h b/inc/MicroBitConfig.h index 218d46b..182a79a 100644 --- a/inc/MicroBitConfig.h +++ b/inc/MicroBitConfig.h @@ -88,9 +88,16 @@ // MESSAGE_BUS_LISTENER_NONBLOCKING #ifndef MESSAGE_BUS_LISTENER_DEFAULT_FLAGS -#define MESSAGE_BUS_LISTENER_DEFAULT_FLAGS MESSAGE_BUS_LISTENER_REENTRANT +#define MESSAGE_BUS_LISTENER_DEFAULT_FLAGS MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY #endif +// +// Maximum event queue depth. If a queue exceeds this depth, further events will be dropped. +// Used to prevent message queues growing uncontrollably due to badly behaved user code and causing panic conditions. +// +#ifndef MESSAGE_BUS_LISTENER_MAX_QUEUE_DEPTH +#define MESSAGE_BUS_LISTENER_MAX_QUEUE_DEPTH 20 +#endif // // Core micro:bit services // diff --git a/inc/MicroBitDisplay.h b/inc/MicroBitDisplay.h index 7bd13a8..86e6dd3 100644 --- a/inc/MicroBitDisplay.h +++ b/inc/MicroBitDisplay.h @@ -11,6 +11,7 @@ * MessageBus Event Codes */ #define MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE 1 +#define MICROBIT_DISPLAY_EVT_FREE 2 /** * I/O configurations for common devices. @@ -62,6 +63,7 @@ enum AnimationMode { ANIMATION_MODE_NONE, + ANIMATION_MODE_STOPPED, ANIMATION_MODE_SCROLL_TEXT, ANIMATION_MODE_PRINT_TEXT, ANIMATION_MODE_SCROLL_IMAGE, @@ -102,7 +104,6 @@ class MicroBitDisplay : public MicroBitComponent uint8_t mode; uint8_t greyscaleBitMsk; uint8_t timingCount; - uint16_t nonce; Timeout renderTimer; MicroBitFont font; @@ -220,6 +221,11 @@ class MicroBitDisplay : public MicroBitComponent */ void sendAnimationCompleteEvent(); + /** + * Blocks the current fiber until the display is available (i.e. not effect is being displayed). + * Animations are queued until their time to display. + */ + void waitForFreeDisplay(); public: // The mutable bitmap buffer being rendered to the LED matrix. @@ -241,17 +247,30 @@ public: MicroBitDisplay(uint16_t id, uint8_t x, uint8_t y); /** - * Resets the current given animation. - * @param delay the delay after which the animation is reset. Must be > 0. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * Stops any currently running animation, and any that are waiting to be displayed. */ - int resetAnimation(uint16_t delay); + void stopAnimation(); /** * Frame update method, invoked periodically to strobe the display. */ virtual void systemTick(); + /** + * Prints the given character to the display, if it is not in use. + * + * @param c The character to display. + * @param delay Optional parameter - the time for which to show the character. Zero displays the character forever. + * @return MICROBIT_OK, MICROBIT_BUSY is the screen is in use, or MICROBIT_INVALID_PARAMETER. + * + * Example: + * @code + * uBit.display.printAsync('p'); + * uBit.display.printAsync('p',100); + * @endcode + */ + int printAsync(char c, int delay = 0); + /** * Prints the given string to the display, one character at a time. * Uses the given delay between characters. @@ -268,12 +287,30 @@ public: */ int printAsync(ManagedString s, int delay = MICROBIT_DEFAULT_PRINT_SPEED); + /** + * Prints the given image to the display, if the display is not in use. + * Returns immediately, and executes the animation asynchronously. + * + * @param i The image to display. + * @param x The horizontal position on the screen to display the image (default 0) + * @param y The vertical position on the screen to display the image (default 0) + * @param alpha Treats the brightness level '0' as transparent (default 0) + * @param delay The time to delay between characters, in milliseconds. set to 0 to display forever. (default 0). + * + * Example: + * @code + * MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n"); + * uBit.display.print(i,400); + * @endcode + */ + int printAsync(MicroBitImage i, int x, int y, int alpha, int delay = 0); + /** * Prints the given character to the display. * * @param c The character to display. * @param delay The time to delay between characters, in milliseconds. Must be > 0. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. * * Example: * @code @@ -289,7 +326,7 @@ public: * * @param s The string to display. * @param delay The time to delay between characters, in milliseconds. Must be > 0. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. * * Example: * @code @@ -304,7 +341,7 @@ public: * * @param i The image to display. * @param delay The time to display the image for, or zero to show the image forever. Must be >= 0. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER. * * Example: * @code @@ -312,7 +349,7 @@ public: * uBit.display.print(i,400); * @endcode */ - int print(MicroBitImage i, int x, int y, int alpha, int delay = MICROBIT_DEFAULT_PRINT_SPEED); + int print(MicroBitImage i, int x, int y, int alpha, int delay = 0); /** * Scrolls the given string to the display, from right to left. @@ -321,7 +358,7 @@ public: * * @param s The string to display. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER. * * Example: * @code @@ -337,7 +374,7 @@ public: * @param image The image to display. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param stride The number of pixels to move in each update. Default value is the screen width. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER. * * Example: * @code @@ -354,7 +391,7 @@ public: * * @param s The string to display. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. * * Example: * @code @@ -370,7 +407,7 @@ public: * @param image The image to display. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param stride The number of pixels to move in each update. Default value is the screen width. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. * * Example: * @code @@ -387,7 +424,7 @@ public: * @param image The image to display. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param stride The number of pixels to move in each update. Default value is the screen width. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_BUSY if the screen is in use, or MICROBIT_INVALID_PARAMETER. * * Example: * @code @@ -408,7 +445,7 @@ public: * @param image The image to display. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param stride The number of pixels to move in each update. Default value is the screen width. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. * * Example: * @code diff --git a/inc/MicroBitListener.h b/inc/MicroBitListener.h index af7bf22..6b327b9 100644 --- a/inc/MicroBitListener.h +++ b/inc/MicroBitListener.h @@ -14,6 +14,8 @@ #define MESSAGE_BUS_LISTENER_DROP_IF_BUSY 0x0020 #define MESSAGE_BUS_LISTENER_NONBLOCKING 0x0040 #define MESSAGE_BUS_LISTENER_URGENT 0x0080 +#define MESSAGE_BUS_LISTENER_DELETING 0x8000 + #define MESSAGE_BUS_LISTENER_IMMEDIATE (MESSAGE_BUS_LISTENER_NONBLOCKING | MESSAGE_BUS_LISTENER_URGENT) diff --git a/inc/MicroBitMessageBus.h b/inc/MicroBitMessageBus.h index 301d69c..abd38f7 100644 --- a/inc/MicroBitMessageBus.h +++ b/inc/MicroBitMessageBus.h @@ -64,10 +64,11 @@ class MicroBitMessageBus : public MicroBitComponent * or the constructors provided by MicroBitEvent. * * @param evt The event to send. - * @param mask The type of listeners to process (optional). Matches MicroBitListener flags. If not defined, all standard listeners will be processed. - * @return The 1 if all matching listeners were processed, 0 if further processing is required. + * @param urgent The type of listeners to process (optional). If set to true, only listeners defined as urgent and non-blocking will be processed + * otherwise, all other (standard) listeners will be processed. + * @return 1 if all matching listeners were processed, 0 if further processing is required. */ - int process(MicroBitEvent &evt, uint32_t mask = MESSAGE_BUS_LISTENER_REENTRANT | MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY | MESSAGE_BUS_LISTENER_DROP_IF_BUSY | MESSAGE_BUS_LISTENER_NONBLOCKING); + int process(MicroBitEvent &evt, bool urgent = false); /** * Register a listener function. @@ -222,11 +223,6 @@ class MicroBitMessageBus : public MicroBitComponent */ MicroBitListener *elementAt(int n); - /** - * Returns a 'nonce' for use with the NONCE_ID channel of the message bus. - */ - uint16_t nonce(); - private: /** @@ -243,10 +239,17 @@ class MicroBitMessageBus : public MicroBitComponent */ int remove(MicroBitListener *newListener); + /** + * Cleanup any MicroBitListeners marked for deletion from the list. + * @return The number of listeners removed from the list. + */ + int deleteMarkedListeners(); + MicroBitListener *listeners; // Chain of active listeners. MicroBitEventQueueItem *evt_queue_head; // Head of queued events to be processed. MicroBitEventQueueItem *evt_queue_tail; // Tail of queued events to be processed. uint16_t nonce_val; // The last nonce issued. + uint16_t queueLength; // The number of events currently waiting to be processed. void queueEvent(MicroBitEvent &evt); MicroBitEventQueueItem* dequeueEvent(); diff --git a/inc/MicroBitMultiButton.h b/inc/MicroBitMultiButton.h index 1a8b34e..f54702a 100644 --- a/inc/MicroBitMultiButton.h +++ b/inc/MicroBitMultiButton.h @@ -3,10 +3,12 @@ #include "MicroBit.h" -#define MICROBIT_MULTI_BUTTON_STATE_1 1 -#define MICROBIT_MULTI_BUTTON_STATE_2 2 -#define MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_1 4 -#define MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_2 8 +#define MICROBIT_MULTI_BUTTON_STATE_1 0x01 +#define MICROBIT_MULTI_BUTTON_STATE_2 0x02 +#define MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_1 0x04 +#define MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_2 0x08 +#define MICROBIT_MULTI_BUTTON_SUPRESSED_1 0X10 +#define MICROBIT_MULTI_BUTTON_SUPRESSED_2 0x20 /** * Class definition for MicroBitMultiButton. @@ -22,8 +24,10 @@ class MicroBitMultiButton : public MicroBitComponent uint16_t otherSubButton(uint16_t b); int isSubButtonPressed(uint16_t button); int isSubButtonHeld(uint16_t button); + int isSubButtonSupressed(uint16_t button); void setButtonState(uint16_t button, int value); void setHoldState(uint16_t button, int value); + void setSupressedState(uint16_t button, int value); public: diff --git a/source/MicroBit.cpp b/source/MicroBit.cpp index 00f7481..54c2c17 100644 --- a/source/MicroBit.cpp +++ b/source/MicroBit.cpp @@ -63,8 +63,8 @@ MicroBit::MicroBit() : #endif MessageBus(), display(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_WIDTH, MICROBIT_DISPLAY_HEIGHT), - buttonA(MICROBIT_ID_BUTTON_A,MICROBIT_PIN_BUTTON_A), - buttonB(MICROBIT_ID_BUTTON_B,MICROBIT_PIN_BUTTON_B), + buttonA(MICROBIT_ID_BUTTON_A,MICROBIT_PIN_BUTTON_A, MICROBIT_BUTTON_SIMPLE_EVENTS), + buttonB(MICROBIT_ID_BUTTON_B,MICROBIT_PIN_BUTTON_B, MICROBIT_BUTTON_SIMPLE_EVENTS), buttonAB(MICROBIT_ID_BUTTON_AB,MICROBIT_ID_BUTTON_A,MICROBIT_ID_BUTTON_B), accelerometer(MICROBIT_ID_ACCELEROMETER, MMA8653_DEFAULT_ADDR), compass(MICROBIT_ID_COMPASS, MAG3110_DEFAULT_ADDR), diff --git a/source/MicroBitButton.cpp b/source/MicroBitButton.cpp index e1993c4..1311447 100644 --- a/source/MicroBitButton.cpp +++ b/source/MicroBitButton.cpp @@ -18,17 +18,16 @@ * MICROBIT_BUTTON_EVT_UP * MICROBIT_BUTTON_EVT_CLICK * MICROBIT_BUTTON_EVT_LONG_CLICK - * MICROBIT_BUTTON_EVT_DOUBLE_CLICK * MICROBIT_BUTTON_EVT_HOLD * @endcode */ -MicroBitButton::MicroBitButton(uint16_t id, PinName name, PinMode mode) : pin(name, mode) +MicroBitButton::MicroBitButton(uint16_t id, PinName name, MicroBitButtonEventConfiguration eventConfiguration, PinMode mode) : pin(name, mode) { this->id = id; this->name = name; + this->eventConfiguration = eventConfiguration; this->downStartTime = 0; this->sigma = 0; - this->doubleClickTimer = 0; uBit.addSystemComponent(this); } @@ -71,12 +70,15 @@ void MicroBitButton::systemTick() { status = 0; MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_UP); - - //determine if this is a long click or a normal click and send event - if((ticks - downStartTime) >= MICROBIT_BUTTON_LONG_CLICK_TIME) - MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_LONG_CLICK); - else - MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_CLICK); + + if (eventConfiguration == MICROBIT_BUTTON_ALL_EVENTS) + { + //determine if this is a long click or a normal click and send event + if((ticks - downStartTime) >= MICROBIT_BUTTON_LONG_CLICK_TIME) + MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_LONG_CLICK); + else + MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_CLICK); + } } //if button is pressed and the hold triggered event state is not triggered AND we are greater than the button debounce value diff --git a/source/MicroBitDisplay.cpp b/source/MicroBitDisplay.cpp index dbf4c04..cbedd7d 100644 --- a/source/MicroBitDisplay.cpp +++ b/source/MicroBitDisplay.cpp @@ -236,10 +236,10 @@ MicroBitDisplay::animationUpdate() void MicroBitDisplay::sendAnimationCompleteEvent() { // Signal that we've completed an animation. - MicroBitEvent evt1(id,MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + MicroBitEvent(id,MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); - // Wake up any fibers that were blocked on the animation (if any). - MicroBitEvent evt2(MICROBIT_ID_ALERT, nonce); + // Wake up a fiber that was blocked on the animation (if any). + MicroBitEvent(MICROBIT_ID_NOTIFY_ONE, MICROBIT_DISPLAY_EVT_FREE); } /** @@ -306,7 +306,6 @@ void MicroBitDisplay::updateScrollImage() scrollingImageRendered = true; } - /** * Internal animateImage update method. * Paste the stored bitmap at the appropriate point and stop on the last frame. @@ -317,6 +316,7 @@ void MicroBitDisplay::updateAnimateImage() if (scrollingImagePosition <= -scrollingImage.getWidth() + (MICROBIT_DISPLAY_WIDTH + scrollingImageStride) && scrollingImageRendered) { animationMode = ANIMATION_MODE_NONE; + this->clear(); this->sendAnimationCompleteEvent(); return; } @@ -333,38 +333,84 @@ void MicroBitDisplay::updateAnimateImage() /** * Resets the current given animation. - * @param delay the delay after which the animation is reset. Must be > 0. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. */ -int MicroBitDisplay::resetAnimation(uint16_t delay) +void MicroBitDisplay::stopAnimation() { - //sanitise this value - if(delay <= 0 ) - return MICROBIT_INVALID_PARAMETER; - // Reset any ongoing animation. if (animationMode != ANIMATION_MODE_NONE) { animationMode = ANIMATION_MODE_NONE; - this->sendAnimationCompleteEvent(); + + // Indicate that we've completed an animation. + MicroBitEvent(id,MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + + // Wake up aall fibers that may blocked on the animation (if any). + MicroBitEvent(MICROBIT_ID_NOTIFY, MICROBIT_DISPLAY_EVT_FREE); } // Clear the display and setup the animation timers. this->image.clear(); - this->animationDelay = delay; - this->animationTick = delay-1; +} + +/** + * Blocks the current fiber until the display is available (i.e. not effect is being displayed). + * Animations are queued until their time to display. + * + */ +void MicroBitDisplay::waitForFreeDisplay() +{ + // If there's an ongoing animation, wait for our turn to display. + if (animationMode != ANIMATION_MODE_NONE && animationMode != ANIMATION_MODE_STOPPED) + fiber_wait_for_event(MICROBIT_ID_NOTIFY, MICROBIT_DISPLAY_EVT_FREE); +} + + +/** + * Prints the given character to the display, if it is not in use. + * + * @param c The character to display. + * @param delay Optional parameter - the time for which to show the character. Zero displays the character forever. + * @return MICROBIT_OK, MICROBIT_BUSY is the screen is in use, or MICROBIT_INVALID_PARAMETER. + * + * Example: + * @code + * uBit.display.printAsync('p'); + * uBit.display.printAsync('p',100); + * @endcode + */ +int MicroBitDisplay::printAsync(char c, int delay) +{ + //sanitise this value + if(delay < 0) + return MICROBIT_INVALID_PARAMETER; + + // If the display is free, it's our turn to display. + if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) + { + image.print(c, 0, 0); + + if (delay > 0) + { + animationDelay = delay; + animationTick = 0; + animationMode = ANIMATION_MODE_PRINT_CHARACTER; + } + } + else + { + return MICROBIT_BUSY; + } return MICROBIT_OK; } /** - * Prints the given string to the display, one character at a time. - * Uses the given delay between characters. + * Prints the given string to the display, one character at a time, if the display is not in use. * Returns immediately, and executes the animation asynchronously. * * @param s The string to display. * @param delay The time to delay between characters, in milliseconds. Must be > 0. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER. * * Example: * @code @@ -377,25 +423,75 @@ int MicroBitDisplay::printAsync(ManagedString s, int delay) if(delay <= 0 ) return MICROBIT_INVALID_PARAMETER; - this->resetAnimation(delay); - - this->printingChar = 0; - this->printingText = s; - - animationMode = ANIMATION_MODE_PRINT_TEXT; + if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) + { + printingChar = 0; + printingText = s; + animationDelay = delay; + animationTick = 0; + + animationMode = ANIMATION_MODE_PRINT_TEXT; + } + else + { + return MICROBIT_BUSY; + } + + return MICROBIT_OK; + +} + +/** + * Prints the given image to the display, if the display is not in use. + * Returns immediately, and executes the animation asynchronously. + * + * @param i The image to display. + * @param x The horizontal position on the screen to display the image (default 0) + * @param y The vertical position on the screen to display the image (default 0) + * @param alpha Treats the brightness level '0' as transparent (default 0) + * @param delay The time to delay between characters, in milliseconds. set to 0 to display forever. (default 0). + * + * Example: + * @code + * MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n"); + * uBit.display.print(i,400); + * @endcode + */ +int MicroBitDisplay::printAsync(MicroBitImage i, int x, int y, int alpha, int delay) +{ + if(delay < 0) + return MICROBIT_INVALID_PARAMETER; + + if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) + { + image.paste(i, x, y, alpha); + + if(delay > 0) + { + animationDelay = delay; + animationTick = 0; + animationMode = ANIMATION_MODE_PRINT_CHARACTER; + } + } + else + { + return MICROBIT_BUSY; + } + return MICROBIT_OK; } /** - * Prints the given character to the display. + * Prints the given character to the display, and wait for it to complete. * * @param c The character to display. * @param delay The time to delay between characters, in milliseconds. Must be > 0. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. * * Example: * @code * uBit.display.print('p'); + * uBit.display.print('p',100); * @endcode */ int MicroBitDisplay::print(char c, int delay) @@ -403,17 +499,21 @@ int MicroBitDisplay::print(char c, int delay) if (delay < 0) return MICROBIT_INVALID_PARAMETER; - image.print(c, 0, 0); - - if (delay == 0) - return MICROBIT_OK; - - this->animationDelay = delay; - animationMode = ANIMATION_MODE_PRINT_CHARACTER; - - // Wait for completion. - nonce = uBit.MessageBus.nonce(); - fiber_wait_for_event(MICROBIT_ID_ALERT, nonce); + // If there's an ongoing animation, wait for our turn to display. + this->waitForFreeDisplay(); + + // If the display is free, it's our turn to display. + // If someone called stopAnimation(), then we simply skip... + if (animationMode == ANIMATION_MODE_NONE) + { + this->printAsync(c, delay); + if (delay > 0) + fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + } + else + { + return MICROBIT_CANCELLED; + } return MICROBIT_OK; } @@ -425,7 +525,7 @@ int MicroBitDisplay::print(char c, int delay) * * @param s The string to display. * @param delay The time to delay between characters, in milliseconds. Must be > 0. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. * * Example: * @code @@ -438,12 +538,20 @@ int MicroBitDisplay::print(ManagedString s, int delay) if(delay <= 0 ) return MICROBIT_INVALID_PARAMETER; - // Start the effect. - this->printAsync(s, delay); - - // Wait for completion. - nonce = uBit.MessageBus.nonce(); - fiber_wait_for_event(MICROBIT_ID_ALERT, nonce); + // If there's an ongoing animation, wait for our turn to display. + this->waitForFreeDisplay(); + + // If the display is free, it's our turn to display. + // If someone called stopAnimation(), then we simply skip... + if (animationMode == ANIMATION_MODE_NONE) + { + this->printAsync(s, delay); + fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + } + else + { + return MICROBIT_CANCELLED; + } return MICROBIT_OK; } @@ -454,7 +562,7 @@ int MicroBitDisplay::print(ManagedString s, int delay) * * @param i The image to display. * @param delay The time to display the image for, or zero to show the image forever. Must be >= 0. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. * * Example: * @code @@ -467,17 +575,21 @@ int MicroBitDisplay::print(MicroBitImage i, int x, int y, int alpha, int delay) if(delay < 0) return MICROBIT_INVALID_PARAMETER; - image.paste(i, x, y, alpha); - - if(delay == 0) - return MICROBIT_OK; - - this->animationDelay = delay; - animationMode = ANIMATION_MODE_PRINT_CHARACTER; - - // Wait for completion. - nonce = uBit.MessageBus.nonce(); - fiber_wait_for_event(MICROBIT_ID_ALERT, nonce); + // If there's an ongoing animation, wait for our turn to display. + this->waitForFreeDisplay(); + + // If the display is free, it's our turn to display. + // If someone called stopAnimation(), then we simply skip... + if (animationMode == ANIMATION_MODE_NONE) + { + this->printAsync(i, x, y, alpha, delay); + if (delay > 0) + fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + } + else + { + return MICROBIT_CANCELLED; + } return MICROBIT_OK; } @@ -489,7 +601,7 @@ int MicroBitDisplay::print(MicroBitImage i, int x, int y, int alpha, int delay) * * @param s The string to display. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER. * * Example: * @code @@ -502,13 +614,21 @@ int MicroBitDisplay::scrollAsync(ManagedString s, int delay) if(delay <= 0) return MICROBIT_INVALID_PARAMETER; - this->resetAnimation(delay); - - this->scrollingPosition = width-1; - this->scrollingChar = 0; - this->scrollingText = s; - - animationMode = ANIMATION_MODE_SCROLL_TEXT; + // If the display is free, it's our turn to display. + if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) + { + scrollingPosition = width-1; + scrollingChar = 0; + scrollingText = s; + + animationDelay = delay; + animationTick = 0; + animationMode = ANIMATION_MODE_SCROLL_TEXT; + } + else + { + return MICROBIT_BUSY; + } return MICROBIT_OK; } @@ -520,7 +640,7 @@ int MicroBitDisplay::scrollAsync(ManagedString s, int delay) * @param image The image to display. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param stride The number of pixels to move in each update. Default value is the screen width. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER. * * Example: * @code @@ -533,15 +653,23 @@ int MicroBitDisplay::scrollAsync(MicroBitImage image, int delay, int stride) //sanitise the delay value if(delay <= 0) return MICROBIT_INVALID_PARAMETER; - - this->resetAnimation(delay); - this->scrollingImagePosition = stride < 0 ? width : -image.getWidth(); - this->scrollingImageStride = stride; - this->scrollingImage = image; - this->scrollingImageRendered = false; - - animationMode = ANIMATION_MODE_SCROLL_IMAGE; + // If the display is free, it's our turn to display. + if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) + { + scrollingImagePosition = stride < 0 ? width : -image.getWidth(); + scrollingImageStride = stride; + scrollingImage = image; + scrollingImageRendered = false; + + animationDelay = delay; + animationTick = 0; + animationMode = ANIMATION_MODE_SCROLL_IMAGE; + } + else + { + return MICROBIT_BUSY; + } return MICROBIT_OK; } @@ -553,7 +681,7 @@ int MicroBitDisplay::scrollAsync(MicroBitImage image, int delay, int stride) * * @param s The string to display. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. * * Example: * @code @@ -565,14 +693,25 @@ int MicroBitDisplay::scroll(ManagedString s, int delay) //sanitise this value if(delay <= 0) return MICROBIT_INVALID_PARAMETER; - - // Start the effect. - this->scrollAsync(s, delay); - - // Wait for completion. - nonce = uBit.MessageBus.nonce(); - fiber_wait_for_event(MICROBIT_ID_ALERT, nonce); + // If there's an ongoing animation, wait for our turn to display. + this->waitForFreeDisplay(); + + // If the display is free, it's our turn to display. + // If someone called stopAnimation(), then we simply skip... + if (animationMode == ANIMATION_MODE_NONE) + { + // Start the effect. + this->scrollAsync(s, delay); + + // Wait for completion. + fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + } + else + { + return MICROBIT_CANCELLED; + } + return MICROBIT_OK; } @@ -583,7 +722,7 @@ int MicroBitDisplay::scroll(ManagedString s, int delay) * @param image The image to display. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param stride The number of pixels to move in each update. Default value is the screen width. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. * * Example: * @code @@ -597,12 +736,23 @@ int MicroBitDisplay::scroll(MicroBitImage image, int delay, int stride) if(delay <= 0) return MICROBIT_INVALID_PARAMETER; - // Start the effect. - this->scrollAsync(image, delay, stride); - - // Wait for completion. - nonce = uBit.MessageBus.nonce(); - fiber_wait_for_event(MICROBIT_ID_ALERT, nonce); + // If there's an ongoing animation, wait for our turn to display. + this->waitForFreeDisplay(); + + // If the display is free, it's our turn to display. + // If someone called stopAnimation(), then we simply skip... + if (animationMode == ANIMATION_MODE_NONE) + { + // Start the effect. + this->scrollAsync(image, delay, stride); + + // Wait for completion. + fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + } + else + { + return MICROBIT_CANCELLED; + } return MICROBIT_OK; } @@ -614,7 +764,7 @@ int MicroBitDisplay::scroll(MicroBitImage image, int delay, int stride) * @param image The image to display. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param stride The number of pixels to move in each update. Default value is the screen width. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_BUSY if the screen is in use, or MICROBIT_INVALID_PARAMETER. * * Example: * @code @@ -632,26 +782,26 @@ int MicroBitDisplay::animateAsync(MicroBitImage image, int delay, int stride, in if(delay <= 0) return MICROBIT_INVALID_PARAMETER; - // Assume right to left functionality, to align with scrollString() - stride = -stride; - - // Reset any ongoing animation. - if (animationMode != ANIMATION_MODE_NONE) + // If the display is free, we can display. + if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) { - animationMode = ANIMATION_MODE_NONE; - this->sendAnimationCompleteEvent(); - } - - this->animationDelay = delay; - this->animationTick = delay-1; + // Assume right to left functionality, to align with scrollString() + stride = -stride; - //calculate starting position which is offset by the stride - this->scrollingImagePosition = (startingPosition == MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS)?MICROBIT_DISPLAY_WIDTH + stride:startingPosition; - this->scrollingImageStride = stride; - this->scrollingImage = image; - this->scrollingImageRendered = false; - - animationMode = ANIMATION_MODE_ANIMATE_IMAGE; + //calculate starting position which is offset by the stride + scrollingImagePosition = (startingPosition == MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS) ? MICROBIT_DISPLAY_WIDTH + stride : startingPosition; + scrollingImageStride = stride; + scrollingImage = image; + scrollingImageRendered = false; + + animationDelay = delay; + animationTick = delay-1; + animationMode = ANIMATION_MODE_ANIMATE_IMAGE; + } + else + { + return MICROBIT_BUSY; + } return MICROBIT_OK; } @@ -663,7 +813,7 @@ int MicroBitDisplay::animateAsync(MicroBitImage image, int delay, int stride, in * @param image The image to display. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param stride The number of pixels to move in each update. Default value is the screen width. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, MICROBIT_BUSY if the screen is in use, or MICROBIT_INVALID_PARAMETER. * * Example: * @code @@ -680,14 +830,26 @@ int MicroBitDisplay::animate(MicroBitImage image, int delay, int stride, int sta //sanitise the delay value if(delay <= 0) return MICROBIT_INVALID_PARAMETER; - - // Start the effect. - this->animateAsync(image, delay, stride, startingPosition); - - // Wait for completion. - nonce = uBit.MessageBus.nonce(); - fiber_wait_for_event(MICROBIT_ID_ALERT, nonce); + // If there's an ongoing animation, wait for our turn to display. + this->waitForFreeDisplay(); + + // If the display is free, it's our turn to display. + // If someone called stopAnimation(), then we simply skip... + if (animationMode == ANIMATION_MODE_NONE) + { + // Start the effect. + this->animateAsync(image, delay, stride, startingPosition); + + // Wait for completion. + //TODO: Put this in when we merge tight-validation + //if (delay > 0) + fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + } + else + { + return MICROBIT_CANCELLED; + } return MICROBIT_OK; } diff --git a/source/MicroBitFiber.cpp b/source/MicroBitFiber.cpp index ae539cc..1ae5111 100644 --- a/source/MicroBitFiber.cpp +++ b/source/MicroBitFiber.cpp @@ -15,7 +15,7 @@ */ Fiber *currentFiber = NULL; // The context in which the current fiber is executing. Fiber *forkedFiber = NULL; // The context in which a newly created child fiber is executing. -Fiber *idleFiber = NULL; // IDLE task - performs a power efficient sleep, and system maintenance tasks. +Fiber *idleFiber = NULL; // IDLE task - performs a power efficient sleep, and system maintenance tasks. /* * Scheduler state. @@ -162,6 +162,10 @@ void scheduler_init() idleFiber->tcb.SP = CORTEX_M0_STACK_BASE - 0x04; idleFiber->tcb.LR = (uint32_t) &idle_task; + // Register to receive events in the NOTIFY channel - this is used to implement wait-notify semantics + uBit.MessageBus.listen(MICROBIT_ID_NOTIFY, MICROBIT_EVT_ANY, scheduler_event, MESSAGE_BUS_LISTENER_IMMEDIATE); + uBit.MessageBus.listen(MICROBIT_ID_NOTIFY_ONE, MICROBIT_EVT_ANY, scheduler_event, MESSAGE_BUS_LISTENER_IMMEDIATE); + // Flag that we now have a scheduler running uBit.flags |= MICROBIT_FLAG_SCHEDULER_RUNNING; } @@ -204,6 +208,7 @@ void scheduler_event(MicroBitEvent evt) { Fiber *f = waitQueue; Fiber *t; + int notifyOneComplete = 0; // Check the wait queue, and wake up any fibers as necessary. while (f != NULL) @@ -213,8 +218,21 @@ void scheduler_event(MicroBitEvent evt) // extract the event data this fiber is blocked on. uint16_t id = f->context & 0xFFFF; uint16_t value = (f->context & 0xFFFF0000) >> 16; - - if ((id == MICROBIT_ID_ANY || id == evt.source) && (value == MICROBIT_EVT_ANY || value == evt.value)) + + // Special case for the NOTIFY_ONE channel... + if ((evt.source == MICROBIT_ID_NOTIFY_ONE && id == MICROBIT_ID_NOTIFY) && (value == MICROBIT_EVT_ANY || value == evt.value)) + { + if (!notifyOneComplete) + { + // Wakey wakey! + dequeue_fiber(f); + queue_fiber(f,&runQueue); + notifyOneComplete = 1; + } + } + + // Normal case. + else if ((id == MICROBIT_ID_ANY || id == evt.source) && (value == MICROBIT_EVT_ANY || value == evt.value)) { // Wakey wakey! dequeue_fiber(f); @@ -225,7 +243,8 @@ void scheduler_event(MicroBitEvent evt) } // Unregister this event, as we've woken up all the fibers with this match. - uBit.MessageBus.ignore(evt.source, evt.value, scheduler_event); + if (evt.source != MICROBIT_ID_NOTIFY && evt.source != MICROBIT_ID_NOTIFY_ONE) + uBit.MessageBus.ignore(evt.source, evt.value, scheduler_event); } @@ -309,7 +328,9 @@ void fiber_wait_for_event(uint16_t id, uint16_t value) queue_fiber(f, &waitQueue); // Register to receive this event, so we can wake up the fiber when it happens. - uBit.MessageBus.listen(id, value, scheduler_event, MESSAGE_BUS_LISTENER_IMMEDIATE); + // Special case for teh notify channel, as we always stay registered for that. + if (id != MICROBIT_ID_NOTIFY && id != MICROBIT_ID_NOTIFY_ONE) + uBit.MessageBus.listen(id, value, scheduler_event, MESSAGE_BUS_LISTENER_IMMEDIATE); // Finally, enter the scheduler. schedule(); diff --git a/source/MicroBitListener.cpp b/source/MicroBitListener.cpp index 09f1498..f85535b 100644 --- a/source/MicroBitListener.cpp +++ b/source/MicroBitListener.cpp @@ -23,6 +23,7 @@ MicroBitListener::MicroBitListener(uint16_t id, uint16_t value, void (*handler)( this->cb_arg = NULL; this->flags = flags; this->next = NULL; + this->evt_queue = NULL; } /** @@ -41,6 +42,7 @@ MicroBitListener::MicroBitListener(uint16_t id, uint16_t value, void (*handler)( this->cb_arg = arg; this->flags = flags | MESSAGE_BUS_LISTENER_PARAMETERISED; this->next = NULL; + this->evt_queue = NULL; } /** @@ -58,16 +60,23 @@ MicroBitListener::~MicroBitListener() */ void MicroBitListener::queue(MicroBitEvent e) { - MicroBitEventQueueItem *q = new MicroBitEventQueueItem(e); + int queueDepth; + MicroBitEventQueueItem *p = evt_queue; if (evt_queue == NULL) - evt_queue = q; + evt_queue = new MicroBitEventQueueItem(e); else { - while (p->next != NULL) - p = p->next; + queueDepth = 1; - p->next = q; + while (p->next != NULL) + { + p = p->next; + queueDepth++; + } + + if (queueDepth < MESSAGE_BUS_LISTENER_MAX_QUEUE_DEPTH) + p->next = new MicroBitEventQueueItem(e); } } diff --git a/source/MicroBitMessageBus.cpp b/source/MicroBitMessageBus.cpp index 9be4626..bf85e52 100644 --- a/source/MicroBitMessageBus.cpp +++ b/source/MicroBitMessageBus.cpp @@ -15,18 +15,7 @@ MicroBitMessageBus::MicroBitMessageBus() this->listeners = NULL; this->evt_queue_head = NULL; this->evt_queue_tail = NULL; - this->nonce_val = 0; -} - -/** - * Returns a 'nonce' for use with the NONCE_ID channel of the message bus. - */ -uint16_t MicroBitMessageBus::nonce() -{ - // In the global scheme of things, a terrible nonce generator. - // However, for our purposes, this is simple and adequate for local use. - // This would be a bad idea if our events were networked though - can you think why? - return nonce_val++; + this->queueLength = 0; } /** @@ -78,7 +67,7 @@ void async_callback(void *param) else listener->cb(listener->evt); - // If there are more events to process, dequeue te next one and process it. + // If there are more events to process, dequeue the next one and process it. if ((listener->flags & MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY) && listener->evt_queue) { MicroBitEventQueueItem *item = listener->evt_queue; @@ -86,6 +75,9 @@ void async_callback(void *param) listener->evt = item->evt; listener->evt_queue = listener->evt_queue->next; delete item; + + // We spin the scheduler here, to preven any particular event handler from continuously holding onto resources. + schedule(); } else break; @@ -95,7 +87,6 @@ void async_callback(void *param) listener->flags &= ~MESSAGE_BUS_LISTENER_BUSY; } - /** * Queue the given event for processing at a later time. * Add the given event at the tail of our queue. @@ -106,23 +97,47 @@ void MicroBitMessageBus::queueEvent(MicroBitEvent &evt) { int processingComplete; - // Firstly, process all handler regsitered as URGENT. These pre-empt the queue, and are useful for fast, high priority services. - processingComplete = this->process(evt, MESSAGE_BUS_LISTENER_URGENT); + MicroBitEventQueueItem *prev = evt_queue_tail; - if (!processingComplete) + // Now process all handler regsitered as URGENT. + // These pre-empt the queue, and are useful for fast, high priority services. + processingComplete = this->process(evt, true); + + // If we've already processed all event handlers, we're all done. + // No need to queue the event. + if (processingComplete) + return; + + // If we need to queue, but there is no space, then there's nothg we can do. + if (queueLength >= MESSAGE_BUS_LISTENER_MAX_QUEUE_DEPTH) + return; + + // Otherwise, we need to queue this event for later processing... + // We queue this event at the tail of the queue at the point where we entered queueEvent() + // This is important as the processing above *may* have generated further events, and + // we want to maintain ordering of events. + MicroBitEventQueueItem *item = new MicroBitEventQueueItem(evt); + + // The queue was empty when we entered this function, so queue our event at the start of the queue. + __disable_irq(); + + if (prev == NULL) { - // We need to queue this event for later processing... - MicroBitEventQueueItem *item = new MicroBitEventQueueItem(evt); - - __disable_irq(); - - if (evt_queue_tail == NULL) - evt_queue_head = evt_queue_tail = item; - else - evt_queue_tail->next = item; - - __enable_irq(); + item->next = evt_queue_head; + evt_queue_head = item; + } + else + { + item->next = prev->next; + prev->next = item; } + + if (item->next == NULL) + evt_queue_tail = item; + + queueLength++; + + __enable_irq(); } /** @@ -144,13 +159,55 @@ MicroBitEventQueueItem* MicroBitMessageBus::dequeueEvent() if (evt_queue_head == NULL) evt_queue_tail = NULL; + + queueLength--; } __enable_irq(); + return item; } +/** + * Cleanup any MicroBitListeners marked for deletion from the list. + * @return The number of listeners removed from the list. + */ +int MicroBitMessageBus::deleteMarkedListeners() +{ + MicroBitListener *l, *p; + int removed = 0; + + l = listeners; + p = NULL; + + // Walk this list of event handlers. Delete any that match the given listener. + while (l != NULL) + { + if (l->flags & MESSAGE_BUS_LISTENER_DELETING && !l->flags & MESSAGE_BUS_LISTENER_BUSY) + { + if (p == NULL) + listeners = l->next; + else + p->next = l->next; + + // delete the listener. + MicroBitListener *t = l; + l = l->next; + + delete t; + removed++; + + continue; + } + + p = l; + l = l->next; + } + + return removed; +} + /** * Periodic callback from MicroBit. * Process at least one event from the event queue, if it is not empty. @@ -158,6 +215,9 @@ MicroBitEventQueueItem* MicroBitMessageBus::dequeueEvent() */ void MicroBitMessageBus::idleTick() { + // Clear out any listeners marked for deletion + this->deleteMarkedListeners(); + MicroBitEventQueueItem *item = this->dequeueEvent(); // Whilst there are events to process and we have no useful other work to do, pull them off the queue and process them. @@ -219,20 +279,23 @@ void MicroBitMessageBus::send(MicroBitEvent evt) * This will attempt to call the event handler directly, but spawn a fiber should that * event handler attempt a blocking operation. * @param evt The event to be delivered. - * @param mask The type of listeners to process (optional). Matches MicroBitListener flags. If not defined, all standard listeners will be processed. - * @return The 1 if all matching listeners were processed, 0 if further processing is required. + * @param urgent The type of listeners to process (optional). If set to true, only listeners defined as urgent and non-blocking will be processed + * otherwise, all other (standard) listeners will be processed. + * @return 1 if all matching listeners were processed, 0 if further processing is required. */ -int MicroBitMessageBus::process(MicroBitEvent &evt, uint32_t mask) +int MicroBitMessageBus::process(MicroBitEvent &evt, bool urgent) { MicroBitListener *l; int complete = 1; + bool listenerUrgent; l = listeners; while (l != NULL) { if((l->id == evt.source || l->id == MICROBIT_ID_ANY) && (l->value == evt.value || l->value == MICROBIT_EVT_ANY)) { - if(l->flags & mask) + listenerUrgent = (l->flags & MESSAGE_BUS_LISTENER_IMMEDIATE) == MESSAGE_BUS_LISTENER_IMMEDIATE; + if(listenerUrgent == urgent && !(l->flags & MESSAGE_BUS_LISTENER_DELETING)) { l->evt = evt; @@ -292,6 +355,7 @@ int MicroBitMessageBus::listen(int id, int value, void (*handler)(MicroBitEvent) return MICROBIT_OK; delete newListener; + return MICROBIT_NO_RESOURCES; } @@ -304,9 +368,10 @@ int MicroBitMessageBus::listen(int id, int value, void (*handler)(MicroBitEvent, MicroBitListener *newListener = new MicroBitListener(id, value, handler, arg, flags); if(add(newListener) == MICROBIT_OK) - return MICROBIT_OK; + return MICROBIT_OK; delete newListener; + return MICROBIT_NO_RESOURCES; } @@ -398,7 +463,15 @@ int MicroBitMessageBus::add(MicroBitListener *newListener) methodCallback = (newListener->flags & MESSAGE_BUS_LISTENER_METHOD) && (l->flags & MESSAGE_BUS_LISTENER_METHOD); if (l->id == newListener->id && l->value == newListener->value && (methodCallback ? *l->cb_method == *newListener->cb_method : l->cb == newListener->cb)) + { + // We have a perfect match for this event listener already registered. + // If it's marked for deletion, we simply resurrect the listener, and we're done. + // Either way, we return an error code, as the *new* listener should be released... + if(l->flags & MESSAGE_BUS_LISTENER_DELETING) + l->flags &= ~MESSAGE_BUS_LISTENER_DELETING; + return MICROBIT_NOT_SUPPORTED; + } l = l->next; } @@ -457,7 +530,7 @@ int MicroBitMessageBus::add(MicroBitListener *newListener) */ int MicroBitMessageBus::remove(MicroBitListener *listener) { - MicroBitListener *l, *p; + MicroBitListener *l; int removed = 0; //handler can't be NULL! @@ -465,7 +538,6 @@ int MicroBitMessageBus::remove(MicroBitListener *listener) return MICROBIT_INVALID_PARAMETER; l = listeners; - p = NULL; // Walk this list of event handlers. Delete any that match the given listener. while (l != NULL) @@ -473,33 +545,24 @@ int MicroBitMessageBus::remove(MicroBitListener *listener) if ((listener->flags & MESSAGE_BUS_LISTENER_METHOD) == (l->flags & MESSAGE_BUS_LISTENER_METHOD)) { if(((listener->flags & MESSAGE_BUS_LISTENER_METHOD) && (*l->cb_method == *listener->cb_method)) || - ((!(listener->flags & MESSAGE_BUS_LISTENER_METHOD) && l->cb == listener->cb))) + ((!(listener->flags & MESSAGE_BUS_LISTENER_METHOD) && l->cb == listener->cb))) { if ((listener->id == MICROBIT_ID_ANY || listener->id == l->id) && (listener->value == MICROBIT_EVT_ANY || listener->value == l->value)) { - // Found a match. Remove from the list. - if (p == NULL) - listeners = l->next; - else - p->next = l->next; - - // delete the listener. - MicroBitListener *t = l; - l = l->next; - - delete t; + // Found a match. mark this to be removed from the list. + l->flags |= MESSAGE_BUS_LISTENER_DELETING; removed++; - - continue; } } } - p = l; l = l->next; } - return MICROBIT_OK; + if (removed > 0) + return MICROBIT_OK; + else + return MICROBIT_INVALID_PARAMETER; } /** diff --git a/source/MicroBitMultiButton.cpp b/source/MicroBitMultiButton.cpp index f477756..3cc1342 100644 --- a/source/MicroBitMultiButton.cpp +++ b/source/MicroBitMultiButton.cpp @@ -66,6 +66,16 @@ int MicroBitMultiButton::isSubButtonHeld(uint16_t button) return 0; } +int MicroBitMultiButton::isSubButtonSupressed(uint16_t button) +{ + if (button == button1) + return status & MICROBIT_MULTI_BUTTON_SUPRESSED_1; + + if (button == button2) + return status & MICROBIT_MULTI_BUTTON_SUPRESSED_2; + + return 0; +} void MicroBitMultiButton::setButtonState(uint16_t button, int value) { @@ -105,6 +115,26 @@ void MicroBitMultiButton::setHoldState(uint16_t button, int value) } } +void MicroBitMultiButton::setSupressedState(uint16_t button, int value) +{ + if (button == button1) + { + if (value) + status |= MICROBIT_MULTI_BUTTON_SUPRESSED_1; + else + status &= ~MICROBIT_MULTI_BUTTON_SUPRESSED_1; + } + + if (button == button2) + { + if (value) + status |= MICROBIT_MULTI_BUTTON_SUPRESSED_2; + else + status &= ~MICROBIT_MULTI_BUTTON_SUPRESSED_2; + } +} + + void MicroBitMultiButton::onEvent(MicroBitEvent evt) { int button = evt.source; @@ -118,30 +148,40 @@ void MicroBitMultiButton::onEvent(MicroBitEvent evt) MicroBitEvent e(id, MICROBIT_BUTTON_EVT_DOWN); break; - - case MICROBIT_BUTTON_EVT_UP: - setButtonState(button, 0); - setHoldState(button, 0); - - if(isSubButtonPressed(otherButton)) - MicroBitEvent e(id, MICROBIT_BUTTON_EVT_UP); - break; - - case MICROBIT_BUTTON_EVT_CLICK: - case MICROBIT_BUTTON_EVT_LONG_CLICK: - setButtonState(button, 0); - setHoldState(button, 0); - if(isSubButtonPressed(otherButton)) - MicroBitEvent e(id, evt.value); - - break; - + case MICROBIT_BUTTON_EVT_HOLD: setHoldState(button, 1); if(isSubButtonHeld(otherButton)) MicroBitEvent e(id, MICROBIT_BUTTON_EVT_HOLD); break; + + case MICROBIT_BUTTON_EVT_UP: + if(isSubButtonPressed(otherButton)) + { + MicroBitEvent e(id, MICROBIT_BUTTON_EVT_UP); + + if (isSubButtonHeld(button) && isSubButtonHeld(otherButton)) + MicroBitEvent e(id, MICROBIT_BUTTON_EVT_LONG_CLICK); + else + MicroBitEvent e(id, MICROBIT_BUTTON_EVT_CLICK); + + setSupressedState(otherButton, 1); + } + else if (!isSubButtonSupressed(button)) + { + if (isSubButtonHeld(button)) + MicroBitEvent e(button, MICROBIT_BUTTON_EVT_LONG_CLICK); + else + MicroBitEvent e(button, MICROBIT_BUTTON_EVT_CLICK); + } + + setButtonState(button, 0); + setHoldState(button, 0); + setSupressedState(button, 0); + + break; + } } diff --git a/source/ble-services/MicroBitDFUService.cpp b/source/ble-services/MicroBitDFUService.cpp index 478301d..27753e9 100644 --- a/source/ble-services/MicroBitDFUService.cpp +++ b/source/ble-services/MicroBitDFUService.cpp @@ -158,7 +158,7 @@ void MicroBitDFUService::onDataWritten(const GattWriteCallbackParams *params) */ void MicroBitDFUService::showTick() { - uBit.display.resetAnimation(0); + uBit.display.stopAnimation(); uBit.display.image.setPixelValue(0,3, 255); uBit.display.image.setPixelValue(1,4, 255); @@ -173,7 +173,7 @@ void MicroBitDFUService::showTick() */ void MicroBitDFUService::showNameHistogram() { - uBit.display.resetAnimation(0); + uBit.display.stopAnimation(); uint32_t n = NRF_FICR->DEVICEID[1]; int ld = 1;