diff --git a/inc/bluetooth/ExternalEvents.h b/inc/bluetooth/ExternalEvents.h index 3606df0..ca5a422 100644 --- a/inc/bluetooth/ExternalEvents.h +++ b/inc/bluetooth/ExternalEvents.h @@ -2,6 +2,7 @@ #define EXTERNAL_EVENTS_H #define MICROBIT_ID_BLE 1000 +#define MICROBIT_ID_BLE_UART 1001 #include "MESEvents.h" diff --git a/inc/bluetooth/MicroBitUARTService.h b/inc/bluetooth/MicroBitUARTService.h new file mode 100644 index 0000000..2df4648 --- /dev/null +++ b/inc/bluetooth/MicroBitUARTService.h @@ -0,0 +1,247 @@ +#ifndef MICROBIT_UART_SERVICE_H +#define MICROBIT_UART_SERVICE_H + +#include "mbed.h" +#include "ble/UUID.h" +#include "ble/BLE.h" +#include "MicroBitConfig.h" +#include "MicroBitSerial.h" + +#define MICROBIT_UART_S_DEFAULT_BUF_SIZE 20 + +#define MICROBIT_UART_S_EVT_DELIM_MATCH 1 +#define MICROBIT_UART_S_EVT_HEAD_MATCH 2 +#define MICROBIT_UART_S_EVT_RX_FULL 3 + +class MicroBitUARTService +{ + uint8_t* rxBuffer; + + uint8_t* txBuffer; + + uint8_t rxBufferHead; + uint8_t rxBufferTail; + uint8_t rxBufferSize; + + uint8_t txBufferSize; + + uint32_t txCharacteristicHandle; + + // Bluetooth stack we're running on. + BLEDevice &ble; + + //delimeters used for matching on receive. + ManagedString delimeters; + + //a variable used when a user calls the eventAfter() method. + int rxBuffHeadMatch; + + /** + * A callback function for whenever a Bluetooth device writes to our TX characteristic. + */ + void onDataWritten(const GattWriteCallbackParams *params); + + /** + * An internal method that copies values from a circular buffer to a linear buffer. + * + * @param circularBuff a pointer to the source circular buffer + * @param circularBuffSize the size of the circular buffer + * @param linearBuff a pointer to the destination linear buffer + * @param tailPosition the tail position in the circular buffer you want to copy from + * @param headPosition the head position in the circular buffer you want to copy to + * + * @note this method assumes that the linear buffer has the appropriate amount of + * memory to contain the copy operation + */ + void circularCopy(uint8_t *circularBuff, uint8_t circularBuffSize, uint8_t *linearBuff, uint16_t tailPosition, uint16_t headPosition); + + public: + + /** + * Constructor for the UARTService. + * @param _ble an instance of BLEDevice + * @param rxBufferSize the size of the rxBuffer + * @param txBufferSize the size of the txBuffer + * + * @note defaults to 20 + */ + MicroBitUARTService(BLEDevice &_ble, uint8_t rxBufferSize = MICROBIT_UART_S_DEFAULT_BUF_SIZE, uint8_t txBufferSize = MICROBIT_UART_S_DEFAULT_BUF_SIZE); + + /** + * Retreives a single character from our RxBuffer. + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will attempt to read a single character, and return immediately + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will configure the event and block the current fiber until the + * event is received. + * + * @return MICROBIT_INVALID_PARAMETER if the mode given is SYNC_SPINWAIT, a character or MICROBIT_NO_DATA + */ + int getc(MicroBitSerialMode mode = SYNC_SLEEP); + + /** + * places a single character into our transmission buffer, + * + * @param c the character to transmit + * + * @return the number of characters written (0, or 1). + */ + int putc(char c); + + /** + * Copies characters into the buffer used for Transmitting to the central device. + * + * @param buf a buffer containing length number of bytes. + * @param length the size of the buffer. + * + * @return the number of characters copied into the buffer + * + * @note no modes for sending are available at the moment, due to interrupt overhead. + */ + int send(const uint8_t *buf, int length); + + /** + * Copies characters into the buffer used for Transmitting to the central device. + * + * @param s the string to transmit + * + * @return the number of characters copied into the buffer + * + * @note no modes for sending are available at the moment, due to interrupt overhead. + */ + int send(ManagedString s); + + /** + * Reads a number of characters from the rxBuffer and fills user given buffer. + * + * @param buf a pointer to a buffer of len bytes. + * @param len the size of the user allocated buffer + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will attempt to read all available characters, and return immediately + * until the buffer limit is reached + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will first of all determine whether the given number of characters + * are available in our buffer, if not, it will set an event and sleep + * until the number of characters are avaialable. + * + * @return the number of characters digested + */ + int read(uint8_t *buf, int len, MicroBitSerialMode mode = SYNC_SLEEP); + + /** + * Reads a number of characters from the rxBuffer and returns them as a ManagedString + * + * @param len the number of characters to read. + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will attempt to read all available characters, and return immediately + * until the buffer limit is reached + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will first of all determine whether the given number of characters + * are available in our buffer, if not, it will set an event and sleep + * until the number of characters are avaialable. + * + * @return an empty ManagedString on error, or a ManagedString containing characters + */ + ManagedString read(int len, MicroBitSerialMode mode = SYNC_SLEEP); + + /** + * Reads characters until a character matches one of the given delimeters + * + * @param delimeters the number of characters to match against + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will attempt read the immediate buffer, and look for a match. + * If there isn't, an empty ManagedString will be returned. + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will first of all consider the characters in the immediate buffer, + * if a match is not found, it will block on an event, fired when a + * character is matched. + * + * @return an empty ManagedString on error, or a ManagedString containing characters + */ + ManagedString readUntil(ManagedString delimeters, MicroBitSerialMode mode = SYNC_SLEEP); + + /** + * Configures an event to be fired on a match with one of the delimeters. + * + * @param delimeters the characters to match received characters against e.g. ManagedString("\r\n") + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will configure the event and return immediately. + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will configure the event and block the current fiber until the + * event is received. + * + * @return MICROBIT_INVALID_PARAMETER if the mode given is SYNC_SPINWAIT, otherwise MICROBIT_OK. + * + * @note delimeters are matched on a per byte basis. + */ + int eventOn(ManagedString delimeters, MicroBitSerialMode mode = ASYNC); + + /** + * Configures an event to be fired after "len" characters. + * + * @param len the number of characters to wait before triggering the event + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will configure the event and return immediately. + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will configure the event and block the current fiber until the + * event is received. + * + * @return MICROBIT_INVALID_PARAMETER if the mode given is SYNC_SPINWAIT, otherwise MICROBIT_OK. + */ + int eventAfter(int len, MicroBitSerialMode mode = ASYNC); + + /** + * Determines if we have space in our rxBuff. + * + * @return 1 if we have space, 0 if we do not. + */ + int isReadable(); + + /** + * @return The currently buffered number of bytes in our rxBuff. + */ + int rxBufferedSize(); + + /** + * @return The currently buffered number of bytes in our txBuff. + */ + int txBufferedSize(); +}; + +extern const uint8_t UARTServiceBaseUUID[UUID::LENGTH_OF_LONG_UUID]; +extern const uint16_t UARTServiceShortUUID; +extern const uint16_t UARTServiceTXCharacteristicShortUUID; +extern const uint16_t UARTServiceRXCharacteristicShortUUID; + +extern const uint8_t UARTServiceUUID[UUID::LENGTH_OF_LONG_UUID]; +extern const uint8_t UARTServiceUUID_reversed[UUID::LENGTH_OF_LONG_UUID]; + +extern const uint8_t UARTServiceTXCharacteristicUUID[UUID::LENGTH_OF_LONG_UUID]; +extern const uint8_t UARTServiceRXCharacteristicUUID[UUID::LENGTH_OF_LONG_UUID]; + +#endif diff --git a/inc/core/NotifyEvents.h b/inc/core/NotifyEvents.h index b54ef1b..5777d29 100644 --- a/inc/core/NotifyEvents.h +++ b/inc/core/NotifyEvents.h @@ -3,5 +3,6 @@ #define MICROBIT_DISPLAY_EVT_FREE 1 #define MICROBIT_SERIAL_EVT_TX_EMPTY 2 +#define MICROBIT_UART_S_EVT_TX_EMPTY 3 #endif diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 5ca57a2..3ef4074 100755 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -50,6 +50,7 @@ set(YOTTA_AUTO_MICROBIT-DAL_CPP_FILES "bluetooth/MicroBitLEDService.cpp" "bluetooth/MicroBitMagnetometerService.cpp" "bluetooth/MicroBitTemperatureService.cpp" + "bluetooth/MicroBitUARTService.cpp" ) execute_process(WORKING_DIRECTORY "../../yotta_modules/${PROJECT_NAME}" COMMAND "git" "log" "--pretty=format:%h" "-n" "1" OUTPUT_VARIABLE git_hash) diff --git a/source/bluetooth/MicroBitUARTService.cpp b/source/bluetooth/MicroBitUARTService.cpp new file mode 100644 index 0000000..76496d8 --- /dev/null +++ b/source/bluetooth/MicroBitUARTService.cpp @@ -0,0 +1,508 @@ +#include "ble/UUID.h" + +#include "ExternalEvents.h" +#include "MicroBitUARTService.h" +#include "MicroBitFiber.h" +#include "ErrorNo.h" +#include "NotifyEvents.h" + +static uint8_t txBufferHead = 0; +static uint8_t txBufferTail = 0; + +static GattCharacteristic* rxCharacteristic = NULL; + +/** + * A callback function for whenever a Bluetooth device consumes our RX Buffer + */ +void on_confirmation_received_callback(uint16_t handle) +{ +#if CONFIG_ENABLED(MICROBIT_DBG) + SERIAL_DEBUG.printf("RECEIVED!! %d \r\n",handle); +#endif + if(handle == rxCharacteristic->getValueAttribute().getHandle()) + { + txBufferTail = txBufferHead; + MicroBitEvent(MICROBIT_ID_NOTIFY, MICROBIT_UART_S_EVT_TX_EMPTY); + } +} + +/** + * Constructor for the UARTService. + * @param _ble an instance of BLEDevice + * @param rxBufferSize the size of the rxBuffer + * @param txBufferSize the size of the txBuffer + * + * @note defaults to 20 + */ +MicroBitUARTService::MicroBitUARTService(BLEDevice &_ble, uint8_t rxBufferSize, uint8_t txBufferSize) : ble(_ble) +{ + + txBuffer = (uint8_t *)malloc(txBufferSize); + rxBuffer = (uint8_t *)malloc(rxBufferSize); + + rxBufferHead = 0; + rxBufferTail = 0; + this->rxBufferSize = rxBufferSize; + + txBufferHead = 0; + txBufferTail = 0; + this->txBufferSize = txBufferSize; + + GattCharacteristic txCharacteristic(UARTServiceTXCharacteristicUUID, rxBuffer, 1, rxBufferSize, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE); + + rxCharacteristic = new GattCharacteristic(UARTServiceRXCharacteristicUUID, txBuffer, 1, txBufferSize, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_INDICATE); + + GattCharacteristic *charTable[] = {&txCharacteristic, rxCharacteristic}; + + GattService uartService(UARTServiceUUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *)); + + _ble.addService(uartService); + + this->txCharacteristicHandle = txCharacteristic.getValueAttribute().getHandle(); + + _ble.gattServer().onDataWritten(this, &MicroBitUARTService::onDataWritten); + _ble.gattServer().onConfirmationReceived(on_confirmation_received_callback); +} + +/** + * A callback function for whenever a Bluetooth device writes to our TX characteristic. + */ +void MicroBitUARTService::onDataWritten(const GattWriteCallbackParams *params) { + if (params->handle == this->txCharacteristicHandle) + { + uint16_t bytesWritten = params->len; + + for(int byteIterator = 0; byteIterator < bytesWritten; byteIterator++) + { + int newHead = (rxBufferHead + 1) % rxBufferSize; + + if(newHead != rxBufferTail) + { + char c = params->data[byteIterator]; + + int delimeterOffset = 0; + int delimLength = this->delimeters.length(); + + //iterate through our delimeters (if any) to see if there is a match + while(delimeterOffset < delimLength) + { + //fire an event if there is to block any waiting fibers + if(this->delimeters.charAt(delimeterOffset) == c) + MicroBitEvent(MICROBIT_ID_BLE_UART, MICROBIT_UART_S_EVT_DELIM_MATCH); + + delimeterOffset++; + } + + rxBuffer[rxBufferHead] = c; + + rxBufferHead = newHead; + + if(rxBufferHead == rxBuffHeadMatch) + { + rxBuffHeadMatch = -1; + MicroBitEvent(MICROBIT_ID_BLE_UART, MICROBIT_UART_S_EVT_HEAD_MATCH); + } + } + else + MicroBitEvent(MICROBIT_ID_BLE_UART, MICROBIT_UART_S_EVT_RX_FULL); + } + } +} + +/** + * An internal method that copies values from a circular buffer to a linear buffer. + * + * @param circularBuff a pointer to the source circular buffer + * @param circularBuffSize the size of the circular buffer + * @param linearBuff a pointer to the destination linear buffer + * @param tailPosition the tail position in the circular buffer you want to copy from + * @param headPosition the head position in the circular buffer you want to copy to + * + * @note this method assumes that the linear buffer has the appropriate amount of + * memory to contain the copy operation + */ +void MicroBitUARTService::circularCopy(uint8_t *circularBuff, uint8_t circularBuffSize, uint8_t *linearBuff, uint16_t tailPosition, uint16_t headPosition) +{ + int toBuffIndex = 0; + + while(tailPosition != headPosition) + { + linearBuff[toBuffIndex++] = circularBuff[tailPosition]; + + tailPosition = (tailPosition + 1) % circularBuffSize; + } +} + +/** + * Retreives a single character from our RxBuffer. + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will attempt to read a single character, and return immediately + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will configure the event and block the current fiber until the + * event is received. + * + * @return MICROBIT_INVALID_PARAMETER if the mode given is SYNC_SPINWAIT, a character or MICROBIT_NO_DATA + */ +int MicroBitUARTService::getc(MicroBitSerialMode mode) +{ + if(mode == SYNC_SPINWAIT) + return MICROBIT_INVALID_PARAMETER; + + if(mode == ASYNC) + { + if(!isReadable()) + return MICROBIT_NO_DATA; + } + + if(mode == SYNC_SLEEP) + { + if(!isReadable()) + eventAfter(1, mode); + } + + char c = rxBuffer[rxBufferTail]; + + rxBufferTail = (rxBufferTail + 1) % rxBufferSize; + + return c; +} + +/** + * places a single character into our transmission buffer, + * + * @param c the character to transmit + * + * @return the number of characters written (0, or 1). + */ +int MicroBitUARTService::putc(char c) +{ + return (send((uint8_t *)&c, 1) == 1) ? 1 : EOF; +} + +/** + * Copies characters into the buffer used for Transmitting to the central device. + * + * @param buf a buffer containing length number of bytes. + * @param length the size of the buffer. + * + * @return the number of characters copied into the buffer + * + * @note no modes for sending are available at the moment, due to interrupt overhead. + */ +int MicroBitUARTService::send(const uint8_t *buf, int length) +{ + if(length < 1) + return MICROBIT_INVALID_PARAMETER; + + int bytesWritten = 0; + + if (ble.getGapState().connected) { + + for(int bufferIterator = 0; bufferIterator < length; bufferIterator++) + { + int nextHead = (txBufferHead + 1) % txBufferSize; + + if(nextHead != txBufferTail) + { + txBuffer[txBufferHead] = buf[bufferIterator]; + + txBufferHead = nextHead; + + bytesWritten++; + } + } + + int size = txBufferedSize(); + +#if CONFIG_ENABLED(MICROBIT_DBG) + SERIAL_DEBUG.printf("tx size: %d", size); +#endif + + uint8_t temp[size] = { 0 }; + + circularCopy(txBuffer, txBufferSize, temp, txBufferTail, txBufferHead); + +#if CONFIG_ENABLED(MICROBIT_DBG) + for(int i = 0; i < size; i++) + SERIAL_DEBUG.printf("%c",temp[i]); +#endif + + ble.gattServer().write(rxCharacteristic->getValueAttribute().getHandle(), temp, size); + } + +#if CONFIG_ENABLED(MICROBIT_DBG) + SERIAL_DEBUG.printf("written: %d \r\n",bytesWritten); +#endif + + return bytesWritten; +} + +/** + * Copies characters into the buffer used for Transmitting to the central device. + * + * @param s the string to transmit + * + * @return the number of characters copied into the buffer + * + * @note no modes for sending are available at the moment, due to interrupt overhead. + */ +int MicroBitUARTService::send(ManagedString s) +{ + return send((uint8_t *)s.toCharArray(), s.length()); +} + +/** + * Reads a number of characters from the rxBuffer and fills user given buffer. + * + * @param buf a pointer to a buffer of len bytes. + * @param len the size of the user allocated buffer + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will attempt to read all available characters, and return immediately + * until the buffer limit is reached + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will first of all determine whether the given number of characters + * are available in our buffer, if not, it will set an event and sleep + * until the number of characters are avaialable. + * + * @return the number of characters digested + */ +int MicroBitUARTService::read(uint8_t *buf, int len, MicroBitSerialMode mode) +{ + if(mode == SYNC_SPINWAIT) + return MICROBIT_INVALID_PARAMETER; + + int i = 0; + + if(mode == ASYNC) + { + int c; + + while((c = getc(mode)) > 0 && i < len) + { + buf[i] = c; + i++; + } + } + + if(mode == SYNC_SLEEP) + { + if(len > rxBufferedSize()) + eventAfter(len - rxBufferedSize(), mode); + + while(i < len) + { + buf[i] = (char)getc(mode); + i++; + } + } + + return i; +} + +/** + * Reads a number of characters from the rxBuffer and returns them as a ManagedString + * + * @param len the number of characters to read. + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will attempt to read all available characters, and return immediately + * until the buffer limit is reached + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will first of all determine whether the given number of characters + * are available in our buffer, if not, it will set an event and sleep + * until the number of characters are avaialable. + * + * @return an empty ManagedString on error, or a ManagedString containing characters + */ +ManagedString MicroBitUARTService::read(int len, MicroBitSerialMode mode) +{ + uint8_t buf[len + 1] = { 0 }; + + int ret = read(buf, len, mode); + + if(ret < 1) + return ManagedString(); + + return ManagedString((const char *)buf); +} + +/** + * Reads characters until a character matches one of the given delimeters + * + * @param delimeters the number of characters to match against + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will attempt read the immediate buffer, and look for a match. + * If there isn't, an empty ManagedString will be returned. + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will first of all consider the characters in the immediate buffer, + * if a match is not found, it will block on an event, fired when a + * character is matched. + * + * @return an empty ManagedString on error, or a ManagedString containing characters + */ +ManagedString MicroBitUARTService::readUntil(ManagedString delimeters, MicroBitSerialMode mode) +{ + if(mode == SYNC_SPINWAIT) + return MICROBIT_INVALID_PARAMETER; + + int localTail = rxBufferTail; + int preservedTail = rxBufferTail; + + int foundIndex = -1; + + //ASYNC mode just iterates through our stored characters checking for any matches. + while(localTail != rxBufferHead && foundIndex == -1) + { + //we use localTail to prevent modification of the actual tail. + char c = rxBuffer[localTail]; + + for(int delimeterIterator = 0; delimeterIterator < delimeters.length(); delimeterIterator++) + if(delimeters.charAt(delimeterIterator) == c) + foundIndex = localTail; + + localTail = (localTail + 1) % rxBufferSize; + } + + //if our mode is SYNC_SLEEP, we set up an event to be fired when we see a + //matching character. + if(mode == SYNC_SLEEP && foundIndex == -1) + { + eventOn(delimeters, mode); + + foundIndex = rxBufferHead - 1; + + this->delimeters = ManagedString(); + } + + if(foundIndex >= 0) + { + //calculate our local buffer size + int localBuffSize = (preservedTail > foundIndex) ? (rxBufferSize - preservedTail) + foundIndex : foundIndex - preservedTail; + + uint8_t localBuff[localBuffSize + 1] = { 0 }; + + circularCopy(rxBuffer, rxBufferSize, localBuff, preservedTail, foundIndex); + + //plus one for the character we listened for... + rxBufferTail = (rxBufferTail + localBuffSize + 1) % rxBufferSize; + + return ManagedString((char *)localBuff, localBuffSize); + } + + return ManagedString(); +} + +/** + * Configures an event to be fired on a match with one of the delimeters. + * + * @param delimeters the characters to match received characters against e.g. ManagedString("\r\n") + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will configure the event and return immediately. + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will configure the event and block the current fiber until the + * event is received. + * + * @return MICROBIT_INVALID_PARAMETER if the mode given is SYNC_SPINWAIT, otherwise MICROBIT_OK. + * + * @note delimeters are matched on a per byte basis. + */ +int MicroBitUARTService::eventOn(ManagedString delimeters, MicroBitSerialMode mode) +{ + if(mode == SYNC_SPINWAIT) + return MICROBIT_INVALID_PARAMETER; + + //configure our head match... + this->delimeters = delimeters; + + //block! + if(mode == SYNC_SLEEP) + fiber_wait_for_event(MICROBIT_ID_BLE_UART, MICROBIT_UART_S_EVT_DELIM_MATCH); + + return MICROBIT_OK; +} + +/** + * Configures an event to be fired after "len" characters. + * + * @param len the number of characters to wait before triggering the event + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will configure the event and return immediately. + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will configure the event and block the current fiber until the + * event is received. + * + * @return MICROBIT_INVALID_PARAMETER if the mode given is SYNC_SPINWAIT, otherwise MICROBIT_OK. + */ +int MicroBitUARTService::eventAfter(int len, MicroBitSerialMode mode) +{ + if(mode == SYNC_SPINWAIT) + return MICROBIT_INVALID_PARAMETER; + + //configure our head match... + this->rxBuffHeadMatch = (rxBufferHead + len) % rxBufferSize; + + //block! + if(mode == SYNC_SLEEP) + fiber_wait_for_event(MICROBIT_ID_BLE_UART, MICROBIT_UART_S_EVT_HEAD_MATCH); + + return MICROBIT_OK; +} + +/** + * Determines if we have space in our rxBuff. + * + * @return 1 if we have space, 0 if we do not. + * + * @note the reason we do not wrap the super's readable() method is so that we + * don't interfere with communities that use manual calls to uBit.serial.readable() + */ +int MicroBitUARTService::isReadable() +{ + return (rxBufferTail != rxBufferHead) ? 1 : 0; +} + +/** + * @return The currently buffered number of bytes in our rxBuff. + */ +int MicroBitUARTService::rxBufferedSize() +{ + if(rxBufferTail > rxBufferHead) + return (rxBufferSize - rxBufferTail) + rxBufferHead; + + return rxBufferHead - rxBufferTail; +} + +/** + * @return The currently buffered number of bytes in our txBuff. + */ +int MicroBitUARTService::txBufferedSize() +{ + if(txBufferTail > txBufferHead) + return (txBufferSize - txBufferTail) + txBufferHead; + + return txBufferHead - txBufferTail; +}