Skip to content

Latest commit

 

History

History
556 lines (348 loc) · 24.5 KB

README.md

File metadata and controls

556 lines (348 loc) · 24.5 KB

blehid

blehid=github:bsiever/microbit-pxt-blehid

This extension allows the micro:bit V2 to act like a Human Interface Devices (HID) over Bluetooth. It currently supports services for Keyboard, Mouse, Media Keys (play, pause, volume up, etc.), a Gamepad, and an Absolute Mouse (puts the mouse at a specific position on the screen).

See Cool Ideas and Challenges for some examples of how this extension can be used.

Not all services are supported on all devices or operating systems. Operating systems with some support are:

  • Android 11 and later (May work on some Android 9+ devices)
    • Tested on a Galaxy Tab A (8", 2019)
    • Here's an unofficial Android BLE HID support list, which includes details on HID support for about 120 different Android devices.
  • Windows 10 Pro and later
    • Tested on a Microsoft Surface Pro (5th Gen)
  • iOS: Tested on 15.1
    • According to Apple Support, the HID Profile is supported on "iPhone 5s and later, iPad Air and later, and iPod touch (6th generation) and later".
    • Tested on an iPhone 11 Pro
  • macOS: Tested on 11.4 (Big Sur)
    • Tested on an M1 Air, but it should work on any Mac made after 2014 running Big Sur or later

You are welcome to use the Extensions's discussions forum to share any new/different platforms that work or don't:

Not all features are supported on each operating system:

Service Windows macOS Android iOS
Keyboard X X X X
Media X X X
Mouse X X X Note 1
Gamepad X X X
Absolute Mouse X X  

Note 1: iOS can support mouse control if AssistiveTouch is enabled.

Pairing and Programming Quirks

Security is important for wireless Human Interface Devices, like keyboards. The Micro:bit's Bluetooth communication goes through a process called "pairing" to ensure data exchanged is secure. The pairing process works like this:

1. Program the micro:bit

Here's an example program:

input.onButtonPressed(Button.A, function () {
    keyboard.sendString("Hello Bluetooth HID!")
})
keyboard.startKeyboardService()

Notice that it starts a service in the start.

2. Have your device connect to the micro:bit

  1. Open bluetooth preferences on the device you want to interact with (a computer, phone, or tablet),
  2. select the micro:bit that you want to connect to (it will be named something like "uBit [XXXXX]"), and
  3. your phone/computer/tablet may ask if you want to "Pair" (Some devices, like Macs and PCs assume you want to "Pair" because you selected the micro:bit).

Every time pairing happens a new "key" is created to encrypt all future communication. The micro:bit and the other device will both store the key so they can immediately communicate in the future without going through this pairing process. For example, if your micro:bit loses power and re-starts, the other device will automatically connect to it without going through the pairing process again. However, sometimes changes to your program will cause the micro:bit's key to be destroyed! (See Re-programming and keys)

Here are examples of all three steps on each operating system:

iOS Pairing

iOSPairing.mp4

In the video the micro:bit's bluetooth name is "BBC micro:bit". Yours will be named something like "uBit [XXXXX]", where "XXXXX" will be the unique name of your micro:bit.

macOS Pairing

macOSPairing.mp4

In the video the micro:bit's bluetooth name is "BBC micro:bit". Yours will be named something like "uBit [XXXXX]", where "XXXXX" will be the unique name of your micro:bit.

Android Pairing

AndroidPairing.mp4

In the video the micro:bit's bluetooth name is "BBC micro:bit". Yours will be named something like "uBit [XXXXX]", where "XXXXX" will be the unique name of your micro:bit.

Windows Pairing

WindowsPairing.mp4

In the video the micro:bit's bluetooth name is "BBC micro:bit". Yours will be named something like "uBit [XXXXX]", where "XXXXX" will be the unique name of your micro:bit.

Re-programming and keys

Reprogramming the micro:bit can cause the micro:bit's version of the key to be destroyed, but the other device (computer, phone, tablet, etc.) will still have its copy of the key. The other device will still try to connect to the micro:bit, but then it will ignore any commands from the micro:bit since the micro:bit doesn't have a valid key. To be able to communicate the devices will have to exchange keys again. You'll need to make the other device (computer, phone, tablet, etc.) "forget" the key and then go through the pairing process again. You also use bluetooth settings to cause devices to forget keys (un-pair).

Here are examples of unpairing on each operating system:

iOS Un Pair

iOSUNpairing.mp4

In the video the micro:bit's bluetooth name is "BBC micro:bit". Yours will be named something like "uBit [XXXXX]", where "XXXXX" will be the unique name of your micro:bit.

macOS Un Pair

macOSUNpairing.mp4

In the video the micro:bit's bluetooth name is "BBC micro:bit". Yours will be named something like "uBit [XXXXX]", where "XXXXX" will be the unique name of your micro:bit.

Android Un Pair

AndroidUNpairing.mp4

In the video the micro:bit's bluetooth name is "BBC micro:bit". Yours will be named something like "uBit [XXXXX]", where "XXXXX" will be the unique name of your micro:bit.

Windows Un Pair

WindowsUNpairing.mp4

In the video the micro:bit's bluetooth name is "BBC micro:bit". Yours will be named something like "uBit [XXXXX]", where "XXXXX" will be the unique name of your micro:bit.

~hint

Reprogramming without pairing

If you are just making small changes to a paired micro:bit, you can usually reprogram if without losing the stored key.

~

Keyboard Service #keyboard

Starting the Keyboard Service #keyboard-startKeyboardService

keyboard.startKeyboardService()

Starts the keyboard service. This must execute in the start block. All other keyboard blocks require the service be started.

~hint

Showing the iOS Keyboard with Keyboard Service

When the regular Keyboard service is used with iOS it will disable use of the on-screen keyboard. You can re-enable it by sending an eject with the media service

~

Sending keystrokes #keyboard-sendString

keyboard.sendString()

Send a sequence of characters one-at-a-time. For example, [keyboard.sendString('Hello Keyboard!')] is equivalent to typing the individual letters one-at-a-time.

Special Keys #keyboard-keys

keyboard.keys()

Keys that can't be typed in strings. To simulate pressing enter send enter, like: [keyboard.sendString(keyboard.keys(keyboard._Key.enter))]

These can be combined with typed characters by joining them together: [keyboard.sendString('Hello Keyboard!'+keyboard.keys(keyboard._Key.enter))]

Key Modifiers #keyboard-modifiers

keyboard.modifiers()

Modifiers are keys like Control, or Alt, the Windows key on a PC, or the Command key on a Mac. Modifiers modify another key and are often used for special commands. For example, pressing Control and "S" is often used to save files (Command and "S" on Macs). You can send a "Control+S" by joining the Control modifier to "s": [keyboard.sendString("" + keyboard.modifiers(keyboard._Modifier.control) + "s")]. The Modifier modifies the first character joined after the "+". For example, [keyboard.sendString("" + keyboard.modifiers(keyboard._Modifier.control) + "stop")] would send "Contrl+s" and then send "t", "o", and finally "p".

Some commands require multiple modifiers, which can also be joined. All the modifiers in a series add to the first non-modifier. For example, simultaneously pressing Control and Alt and the delete key can be done via: [keyboard.sendString("" + keyboard.modifiers(keyboard._Modifier.control) + keyboard.modifiers(keyboard._Modifier.alt) + keyboard.keys(keyboard._Key.delete))]

It's also possible to send a sequence of modified keys. For example, on Macs Command with "c" copies items and Command with "v" pastes a copied items. A copy/paste/paste could be done by:

[keyboard.sendString("" + keyboard.modifiers(keyboard._Modifier.apple) + "c" + keyboard.modifiers(keyboard._Modifier.apple) + "v" + keyboard.modifiers(keyboard._Modifier.apple) + "v") ]

Sending several simultaneously pressed keys #keyboard-sendSimultaneousKeys

keyboard.sendSimultaneousKeys()

This command is in the "... more" palette of advanced commands. It allows up to 6 simultaneous keys to be sent at the same time. This is like smashing down keys at the exact same time. The "+" on the block can be expanded to give additional control over when keys are released. For example, while playing a video game you may want to hold down on the right arrow to move right: [keyboard.sendSimultaneousKeys(keyboard.keys(keyboard._Key.right), true)].

At some point you may also want to simulate pushing the space bar to jump while still moving to the right: [keyboard.sendSimultaneousKeys("" + keyboard.keys(keyboard._Key.right) + " ", true)]

Finally, you may want to release all the keys: [keyboard.releaseKeys()]

Releasing any "held" keys #keyboard-releaseKeys

keyboard.releaseKeys()

Release any keys that were held down by use of [keyboard.sendSimultaneousKeys("...", true)]

Raw scancodes for additional keys #keyboard-rawScanCode

keyboard.rawScancode()

HID keyboards send "scancodes" that represent keys. You may want to send keys that aren't covered here, like the Function Keys (F1, etc.). You can do this by sending the scancode for the key. Supported scancodes can be found starting on page 83 of the "HID Usage Tables for Universal Serial Bus (USB) v 1.21". If you look up Keyboard F1 in the table, you'll find it has a scancode of 112 (in the AT-101 column of the table). So, to send an F1: [keyboard.sendString(keyboard.rawScancode(112))]

Controlling Rates #keyboard-setEventsPerSecond

keyboard.setEventsPerSecond()

Set the number of keys that can be sent per second. The maximum is 33 and the minimum is 5. The default is 28.

Detecting if the keyboard service use has changed #keyboard-setStatusChangeHandler

keyboard.setStatusChangeHandler()

The device using the service needs to "subscribe" to the service. This event handler will indicate that the status of the subscription has changed (either the other device has subscribed or unsubscribed). This is an indicator that the device is "listening" to your code. Some operating systems, like Android, subscribe to every service whether they use it or not.

Detecting if the keyboard service may be in use #keyboard-isEnabled

keyboard.isEnabled()

true indicates that the device is currently subscribed to the service. false indicates the device is not currently subscribed to the service. This may mean that the other device is off or out of range.

Media Service #media #media-keys

Starting the Media Service #media-startMediaService

media.startMediaService()

Starts the media service. This must execute in the start block. All other media blocks require the service be started.

Specific Media Keys

media.keys(media._MediaKey.next)

Select a specific media key to send. Only the keys listed may be sent.

Sending a media key #media-sendCode

media.sendCode()

Send a media key. For example, to send "play/pause" [media.sendCode(media.keys(media._MediaKey.playPause))]

~hint

Showing the iOS Keyboard with Keyboard Service

When the regular Keyboard service is used with iOS it will disable use of the on-screen keyboard. You can re-enable it by sending an eject: [media.sendCode(media.keys(media._MediaKey.eject))]

~

Detecting if the media service use has changed #media-setStatusChangeHandler

media.setStatusChangeHandler()

The device using the service needs to "subscribe" to the service. This event handler will indicate that the status of the subscription has changed (either the other device has subscribed or unsubscribed). This is an indicator that the device is "listening" to your code. However, it isn't perfect. Some operating systems, like Android, subscribe to every service whether they use it or not.

Detecting if the media service may be in use #media-isEnabled

media.isEnabled()

true indicates that the device is currently subscribed to the service. false indicates the device is not currently subscribed to the service. This may mean that the other device is off or out of range.

Mouse Service #mouse

Starting the Mouse Service #mouse-startMouseService

mouse.startMouseService()

Starts the mouse service. This must execute in the start block. All other mouse blocks require the service be started.

mouse.movexy()

Move the mouse by the given amounts in the x (horizontal) and y (vertical) directions. x can be from -127 to 127. y can be from -127 to 127.

mouse.click()

Click the left mouse button.

mouse.middleClick()

Click the middle mouse button.

mouse.rightClick()

Click the right mouse button.

mouse.scroll()

Scroll the scroll wheel by the given number of "clicks". The clicks can be from -127 to 127.

mouse.send()

Send all mouse behavior simultaneously. x is the amount to move horizontaly (-127 to 127), y is the amount to move vertically (-127 to 127), left indicates if the left mouse button is pressed, middle indicates if the middle mouse button is pressed, right indicates if the right mouse button is pressed, scroll is the amount to scroll (-127 to 127), and hold indicates if the buttons should be "held" until the next mouse command (when false the buttons are "clicked").

Detecting if the mouse service use has changed #mouse-setStatusChangeHandler

mouse.setStatusChangeHandler()

The device using the service needs to "subscribe" to the service. This event handler will indicate that the status of the subscription has changed (either the other device has subscribed or unsubscribed). This is an indicator that the device is "listening" to your code. However, it isn't perfect. Some operating systems, like Android, subscribe to every service whether they use it or not.

Detecting if the mouse service may be in use #mouse-isEnabled

mouse.isEnabled()

true indicates that the device is currently subscribed to the service. false indicates the device is not currently subscribed to the service. This may mean that the other device is off or out of range.

~hint

iOS AssistiveTouch #assistivetouch

A mouse can be used in iOS when AssistiveTouch features are enabled, See (https://www.macworld.com/article/232969/how-to-use-a-mouse-with-your-ipad-or-iphone.html for more detail.

~

Absolute Mouse Service #absolute-mouse

Absolute mouse currently only works on macOS and Windows. Interactions beteen Absolute Mouse and Mouse may not be well defined.

Starting the Absolute Mouse Service #absmouse-startAbsoluteMouseService

absmouse.startAbsoluteMouseService()

Starts the absolute mouse service. This must execute in the start block. All other absolute mouse blocks require the service be started.

absmouse.movexy()

Move the mouse pointer to the specific location. x goes from -32767 to 32767. -32767 represents the left side of the screen and 32767 represents the right side of the screen. y goes from -32767 to 32767. -32767 represents the top of the screen and 32767 represents the bottom of the screen. (0,0) is the center of the screen.

~hint

Screen coordinates and mapping

You can:

  • Identify the resolution of your screen (Ex: 2560x1600)
  • Measure the distance over and down to something you want to interact with as well as the total screen width and height.
  • Use the above to compute the approximate coordinates of an item on the sceen
  • Take advantage of the map block ([y=Math.map(0,0,0,0,0)]) to convert from the desired screen coordinate to the absolute mouse coordinate.

For example, if:

  • Screen is 28cm wide and 2560 pixels horizontal resolution
  • The item you want to click on the screen is 6cm from the left

The x coordinate is approximately: 6cm/28cm*2560pix = 548pix

The absolute y coordinate can be computed by: [y=Math.map(548,0,2560,-32767,32767)]

~

absmouse.click()

Click the left mouse button.

absmouse.middleClick()

Click the middle mouse button.

absmouse.rightClick()

Click the right mouse button.

absmouse.send()

Send all absolute mouse behavior simultaneously. x: number, y: number, left: boolean, middle: boolean, right: boolean, hold: boolean): void;

x is the horizontal location to place the mouse (-32767 to 32767), y is the vertical location to place the mouse (-32767 to 327679),left indicates if the left mouse button is pressed, middle indicates if the middle mouse button is pressed, right indicates if the right mouse button is pressed, and hold indicates if the buttons should be "held" until the next mouse command (when false the buttons are "clicked").

Detecting if the absmouse service use has changed #absmouse-setStatusChangeHandler

absmouse.setStatusChangeHandler()

The device using the service needs to "subscribe" to the service. This event handler will indicate that the status of the subscription has changed (either the other device has subscribed or unsubscribed). This is an indicator that the device is "listening" to your code. However, it isn't perfect. Some operating systems, like Android, subscribe to every service whether they use it or not.

Detecting if the absmouse service may be in use #absmouse-isEnabled

absmouse.isEnabled()

true indicates that the device is currently subscribed to the service. false indicates the device is not currently subscribed to the service. This may mean that the other device is off or out of range.

Gamepad Service #gamepad

Starting the Gamepad Service #gamepad-startGamepadService

gamepad.startGamepadService()

Starts the gamepad service. This must execute in the start block. All other gamepad blocks require the service be started.

Direction Pad keys (D-Pad) #gamepad-_dpad

gamepad._dpad(GameDirection.noDirection)

A direction value for the direction pad

Buttons #gamepad-_buttons

gamepad._buttons()

A value indicating if a designated button is pressed or not.

Send a gamepad command #gamepad-send

gamepad.send()

Send all gamepad behavior simultaneously. buttons is a set of buttons and their current state, x is the amount to move horizontally (-127 to 127), y is the amount to move vertically (-127 to 127), dpad is the value of the direction pad, z is the amount to move on the z-axis (-127 to 127), rx is the rotation about the x-axis.

Detecting if the gamepad service use has changed #gamepad-setStatusChangeHandler

gamepad.setStatusChangeHandler()

The device using the service needs to "subscribe" to the service. This event handler will indicate that the status of the subscription has changed (either the other device has subscribed or unsubscribed). This is an indicator that the device is "listening" to your code. However, it isn't perfect. Some operating systems, like Android, subscribe to every service whether they use it or not.

Detecting if the gamepad service may be in use #gamepad-isEnabled

gamepad.isEnabled()

true indicates that the device is currently subscribed to the service. false indicates the device is not currently subscribed to the service. This may mean that the other device is off or out of range.

Cool Ideas and Challenges

Here are some examples: https://youtu.be/n4J5GN72N_4

  • Create a mouse using the accelerometer
  • Create a gamepad using the accelerometer
  • Use the keyboard support to control a slideshow (Google Slides, PowerPoint, Keynote, etc.)
  • Use the micro:bit to take pictures and start/stop recordings on your phone/tablet. Hint: most mobile devices' camera app will take a picture when a volume button is pressed
  • Make a remote control to play/pause music and/or control volume
  • Automate repetitive computer tasks

Tips

iOS Screen Keyboard

Use the Media Eject to allow the keyboard to be used while also using the Keyboard service.

Known Limitations

Windows

  • You may need an application to "remap" the GamePad controls provided by this extension to work with a specific game.

macOS

  • You may need an application to "remap" the GamePad controls provided by this extension to work with a specific game. Applications like Joystick Monitor can help test if/how the GamePad is detected.

iOS

  • Doesn't support a mouse or gamepad unless AssistiveTouch is enabled
  • Doesn't honor media "Stop"
  • Doesn't support Absolute Mouse

Android

  • Doesn't support Absolute Mouse

Misc Links & References

  • I develop micro:bit extensions in my spare time to support activities I'm enthusiastic about, like summer camps and science curricula. You are welcome to become a sponsor of my micro:bit work (one time or recurring payments), which helps offset equipment costs: here. Any support at all is greatly appreciated!

Supported targets

for PXT/microbit

<script src="https://makecode.com/gh-pages-embed.js"></script> <script>makeCodeRender("{{ site.makecode.home_url }}", "{{ site.github.owner_name }}/{{ site.github.repository_name }}");</script>