Merge branch 'simplified-eventing'

This commit is contained in:
Joe Finney 2015-11-01 18:22:27 +00:00
commit 194f19a428
16 changed files with 606 additions and 241 deletions

View file

@ -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
};

View file

@ -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.

View file

@ -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
{

View file

@ -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
//

View file

@ -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

View file

@ -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)

View file

@ -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();

View file

@ -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:

View file

@ -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),

View file

@ -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

View file

@ -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;
}

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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;