Sunday, April 23, 2023

Hints on injecting input into Windows (use 'ScanCode' for Unicode!)

 Use ScanCode to inject Unicode characters!

Got a Unicode string and need to inject it using the InputInjector class? Wondering how to inject it since the InjectedInputKeyboardInfo class has a ScanCode and a Virtual Key but no Unicode char? It turns out to be easy!

In the InjectedInputKeyboardInfo class, set the KeyOptions to Unicode and then fill in the ScanCode with the unicode char. Since you've got a string, you'll make a list of InjectedInputKeyboardInfo items and then send them all with the InjectKeyboardInput method.

I know this works because my new keyboard will happily inject  Unicode strings like this: 😋👩‍👩‍👧‍👧!

Handy Links

MSDN learn.microsoft.com ScanCode
Old [UWP]InjectedInputKeyboardInfo with Unicode forum questions that says they have an answer (but don't say what it is): here



Friday, April 14, 2023

Hints on using CircuitPython's adafruit_ble Bluetooth

 Hints on using CircuitPython's adafruit_ble Bluetooth

Some APIs and libraries for Bluetooth are a joy to use: they fit right into our basic concepts of how the protocol work, and match the kinds of tasks we want to do.

And then there's the adafruit_ble library. 

I've been working on a little project using the very nifty AdaFruit  Feature nRF52840, and there's a lot to like about it. They got a ton of the details just *french kiss*, starting with the font on the main device (the 840 is in extra-large letters so you can quickly tell one from the other) and running to their library of compatible "feather" devices and integration into CircuitPython.

But the ble library? They have clearly spent a ton of time and effort on it (which I thank them for). But everything in it is just that little bit backwards from everything I know about Bluetooth.

Example: why doesn't this code work?

Here's a simple example: I've found a device and I know that it has supports the Current Time Service that I want to read. I know it does that because I check to make sure that the service's GUID (0x1805) is part of the device advertisement. This is done with the CircuitPython statement: 

        if BtCurrentTimeServiceClient.uuid in connection

which means I should be able to get that service, right? So this code should work?

    service = connection[BtCurrentTimeServiceClient.uuid]

Because that's what a collection is for: you can check to make sure a key is in the collection, and then pull the value out of the collection. But that's not how CircuitPython works. You can check the services via a guid, but to get a value, you can only do that by providing the type of the object you want out (and it had better derive from Service)

Why this is horrific, and how much time I wasted

Given code that doesn't work, the next step is to figure out how to fix it. Potential fixes I tried included:
  • using a different kind of UUID (UUID versus StandardUUID)
  • getting the UUID from a different place (afafruit_ble versus bleio)
  • testing to make sure that the 'in' wasn't just always returning true by trying a fake UUID
  • connecting at different times
  • stopping the scan before getting data
  • doing a time.sleep(5) before connecting or getting data
  • connecting to the address versus the advertisement
  • getting the service twice
  • pairing
  • iterate through the services (this was really weird)
  • disconnecting when I was done
  • putting it in a try/except block to investigate the exception

That's a lot of investigation just to learn that what's going on is "magic": the CircuitPython library, instead of just boringly reporting on a connected devices's services and characteristics, demands that you carefully create a complete-enough CircuitPython replica of the device you're connecting to.

Doc shortcomings examples

Little snippets of code would have been super handy. All of the documentation assumes that I'm already a Python expert and will instantly understand the Pythonesque ways to getting information

For example, there's a handy "StructCharacteristic" that will put chunk of data out of a Bluetooth characteristic. This is handy when you need to read something like the Current Time Service where the time data is split into 9 different fields, mostly unsigned byte, but one is a 2-byte unsigned short.

But how does one actually read the data? It's not mentioned in the docs or in the source code. Turns out this will read the data:

    char = service.data
    print("    value=", char)
    print("    year=", char[0])
    print("    mon=", char[1])
    print("    day=", char[2])

This works because the data is the characteristic, and seemingly when you get it you get the unpacked version of the raw data.

What should they have done?

Take a look at the Windows Bluetooth (UWP) API. Once you have a device, you can list all the services, and for each service, list all of the characteristics.

Or, if you know more about the device you're connecting to (often the case), you can get just the services and characteristics you're interested in, which is almost certainly going to be faster (no point in poking the Bluetooth device for a bunch of service and characteristic data that you don't care about).

Handy Links

Link to Adafruit Feather nRF52840 Express
Link to the Characteristic class on GitHub which includes Struct