phetsims / paper-land

Build and explore multimodal web interactives with pieces of paper!
https://phetsims.github.io/paper-land/
MIT License
19 stars 3 forks source link

Update BLE interface and incorporate some functions into `writeToCharacteristic( )` #246

Open brettfiedler opened 6 months ago

brettfiedler commented 6 months ago

Service Selection Changes

We should simplify the information presented to users. Let's focus on hiding the UUIDs and simplifying language:

image

image

Dropdown:

Clean up Service/Characteristic selection

A lot of services either only have one relevant characteristic, or we are only supporting one. Let's automatically select it or possibly drop the second dropdown altogether when there is only one?

RX (write to the microcontroller, microcontroller is reading)

We can only write to the RX service.

What you write to the microcontroller does not always have to be a string, and it seems micro:bit handles this gracefully, but it would be nice to abstract some of the text part. I think the best option here is to duplicate the RX characteristic and then repurpose one to make the writeToCharacteristic( ) automatically decode (for both RX and TX).

UUID: 6E400003B5A3F393E0A9E50E24DCCA9E

image

Let's also update the writeToCharacteristic( ) function to do add the delimiters to the string and encode the string to UTF-8, which the micro:bit expects. The user will still need to define the string, but it could be a String component, which would make things even simpler.

Example JS from microbit-ratio-game:

// Construct the command string based on LED state
let str = "";
if (readyToLaunch) {
    if (isInGoalRatioDerived) {
        str = "success"; // Command for LED pin 0 Up
    } else {
        str = "failure"; // Command for LED pin 0 Down
    }
}
str = "$" + str + "|"; // Format the command string with start and end markers

// Encode the string to UTF-8
var encoder = new TextEncoder(); // TextEncoder defaults to UTF-8
var encoded = encoder.encode(str);

// Create an ArrayBuffer from the encoded string
var buffer = new ArrayBuffer(encoded.length);
var bufferView = new Uint8Array(buffer);

for (var i = 0; i < encoded.length; i++) {
    bufferView[i] = encoded[i];
}

// Send the command over BLE
writeToCharacteristic(buffer);
console.log(`Command sent: ${str}`);
console.log(buffer);

TX (read from microcontroller, microcontroller is writing)

UUID: 6E400002B5A3F393E0A9E50E24DCCA9E

We can only read from the TX service.

Same logic as the RX, I think the best option here is to duplicate the TX characteristic and then repurpose one to make the writeToCharacteristic( ) automatically decode into a string, and store the string into deviceValue.

Example JS from microbit-ble-demos:

// Assuming `data` is your byte array from UART
const data = deviceValue;

const textDecoder = new TextDecoder('utf-8'); // Default is utf-8, which is typical for UART text data
const text = textDecoder.decode(data);

console.log( 'received text' );
setReceivedUARTString( text );

LED Service

LED Matrix

UUID: E95D7B77251D470AA062FA1922DFA9A8

Let's update writeToCharacteristic( ) by incorporating the code we've used from https://github.com/antefact/microBit.js/blob/b512cdc79994a8d120851bd1ed360ec05e0c5bd1/src/microBit.js#L115C1-L141.

I think the goal here should be that someone only has to define an const icon = [ insert matrix here] and then provide it to the characteristic as icon and that should be good to go. The writeToCharacteristic( ) function will convert it to the Array it expects and send it off.

Also is this function to convert a string to a UTF8Array helpful? https://github.com/antefact/microBit.js/blob/b512cdc79994a8d120851bd1ed360ec05e0c5bd1/src/microBit.js#L346-L375

LED_Matrix_State : uint8[ ]

LED Text

BF EDIT: NEVERMIND. I somehow missed we already had this. Sorry!

Button, Magnetometer, Accelerometer, Temperature

Each can only be read, not written to.

All of the custom services can retain the read/write tabs, since we don't know how they are being used. I don't think it makes a difference which they choose, but it could be helpful for tracking.

Remove Microbit Event service

The event service needs some more time and can't be used in the current implementation. It needs to send the respective requirements service at connection (on either side, client or microbit), then follows up with the respective event service. Not worth our time unless someone is very invested in it. I think UART should be a fine substitution.

brettfiedler commented 6 months ago

{Placeholder for documentation updates to different characteristics, including link to BLE documentation for micro:bit, and showing the expect form of the value read or written}

Micro:bit BLE documentation: https://lancaster-university.github.io/microbit-docs/resources/bluetooth/bluetooth_profile.html

brettfiedler commented 6 months ago

@jessegreenberg , I think the first bit is in place. I want to expand a bit on the information we present in each menu, but the first post should reflect some needed changes that are more structural in nature.

jessegreenberg commented 6 months ago

OK, I think these requests are done. However, for the following I took a slightly different approach.

  • Duplicate "micro:bit RX (write to micro:bit)" and rename the duplicate to "micro:bit RX Text (write string to micro:bit)"
  • Update new RX Text write function so that it does the encoding and the user provides `writeToCharacteristic( "string", dataDelimiter, stringDelimiter) where defaults are: dataDelimiter = "$" and stringDelimiter = "|".
  • Incorporate textDecoder into new TX Text read function so deviceValue returns a string.
  • Update LED Matrix writeToCharacteristic( icon(5x5 uint8Array) ) to automatically convert the matrix to a form the microbit will accept.

Instead of overloading writeToCharacteristic, I think it would be better to have separate function per action. I think that is clearer and will work for multiple characteristics. For example, string decoding may apply to a few characteristics and the "Custom" ones.

For the LED characteristic, you can now use

writeMatrixToCharacteristic( [
    [ 1, 1, 1, 1, 1 ],
    [ 1, 0, 0, 0, 1 ],
    [ 1, 0, 0, 0, 1 ],
    [ 1, 0, 0, 0, 1 ],
    [ 1, 1, 1, 1, 1 ]
] );

to draw a square.

Some of these (like the LED example) are characteristic specific, and if you would like we can hide the function documentation when it isn't relevant. Ill do that if you are OK with this direction.

Ready for you to review on the dev branch @brettfiedler !

brettfiedler commented 6 months ago

I think that approach is great and certainly more generalizable.

Noting the slack conversation here:

Alright, fixed up a few programs. They are much shorter! I'm a little torn on the LED Text service. It expects a UTF8 string, not a Uint8Array with delimiters, which is what our writeStringToCharacteristic function does. So, taking your LED Text Face example will send it with delimiters (though it does work...) to the LED matrix. By torn, I am wondering if we should leave it as an example to encode and document it, or if we should try to remove those documentation entries for writeStringToCharacteristic and add another for the UTF8string.

Or maybe we could just add a couple more "general purpose" functions to all of the write characteristics? writeAsUTF8(), writeAsUint8Array() or something? UTF8s and Uint8[] covers most of the write characteristics in micro:bit. The rest for the most part are reads and are a variety of signed or unsigned (4-32 bit) integers, which I think we can add to the documentation, but do not need to abstract out.

I think adding one more function to the Write characteristics form should cover us well and make the LED Text characteristic easier to use:

Otherwise, I'll check in with AE and company to see if the changes help.

jessegreenberg commented 5 months ago

@brettfiedler what do you think of this?

Then the case where delimeters ARE is made clear by the function name. And the more general function gets a simpler name.

This would require changing code in existing BLE examples so want to check with you before doing this.