• There is a HID module for BLE that helps Espruino to emulate a HID device (like keyboard or media controller) itself.

    What I want to do is exactly the opposite:

    • I bought a cheap 1-button camera trigger for smartphones (that sends a "volume up" button command)

    • want to use it on my Espruino project as a remote switch, but how?

    I can:

    • find and connect it it via NRF.requestDevice (named "AB Shutter3" - prefix "AB" works)

      connected BluetoothRemoteGATTServer: {
      "device": BluetoothDevice: {
      "id": "2a:07:98:10:35:fb public",
      "rssi": -67,
      "data": new Uint8Array([2, 1, 5, 3, 2, 18, 24, 3, 25, 193, 3, 12, 9, 65, 66, 32, 83, 104, 117, 116, 116, 101, 114, 51]).buffer,
      "name": "AB Shutter3",
      "services": [
        "1812"
       ],
      "gatt":  ...
      },
      "connected": true, "handle": 1 }
      
    • find a a single service ( 1812) and fetch it

      service BluetoothRemoteGATTService: {
      "device": BluetoothDevice: {
      "id": "2a:07:98:10:35:fb public",
      "rssi": -67,
      "data": new Uint8Array([2, 1, 5, 3, 2, 18, 24, 3, 25, 193, 3, 12, 9, 65, 66, 32, 83, 104, 117, 116, 116, 101, 114, 51]).buffer,
      "name": "AB Shutter3",
      "services": [
        "1812"
       ],
      "gatt": BluetoothRemoteGATTServer: {
        "device":  ... ,
        "connected": true, "handle": 1 }
      },
      "uuid": "0x1812",
      "isPrimary": true, "start_handle": 15, "end_handle": 31 }
      
    • find a bunch of characteristics:

      {
      "uuid": "0x2a4e",
      "properties": { "broadcast": false, "read": true, "writeWithoutResponse": true, "write": false, "notify": false, "indicate": false, "authenticatedSignedWrites": false }
      }
      {
      "uuid": "0x2a4d",
      "properties": { "broadcast": false, "read": true, "writeWithoutResponse": false, "write": false, "notify": true, "indicate": false, "authenticatedSignedWrites": false }
      }
      {
      "uuid": "0x2a4d",
      "properties": { "broadcast": false, "read": true, "writeWithoutResponse": false, "write": false, "notify": true, "indicate": false, "authenticatedSignedWrites": false }
      }
      {
      "uuid": "0x2a4b",
      "properties": { "broadcast": false, "read": true, "writeWithoutResponse": false, "write": false,
      "notify": false, "indicate": false, "authenticatedSignedWrites": false }
      }
      {
      "uuid": "0x2a4a",
      "properties": { "broadcast": false, "read": true, "writeWithoutResponse": false, "write": false, "notify": false, "indicate": false, "authenticatedSignedWrites": false }
      }
      {
      "uuid": "0x2a4c",
      "properties": { "broadcast": false, "read": false, "writeWithoutResponse": true, "write": false, "notify": false, "indicate": false, "authenticatedSignedWrites": false }
      }
      

    The odd thing is:

    • 0x2a4d exists twice and is the only characteristic that will notify, so this is probably the one I'm looking for, but
    • c..on("characteristicvaluechanged",… and c.startNotifications() doesn't work
    • meaning it works, but the button does not notify anything

    When I pair it with my smartphone there is a dialog popping up first.
    Will there be something to happen first before both are connected?
    Like writing back to one of the other characteristics to say that we'd like to connect?

    Any hints or links appreciated.


    1 Attachment

    • IMG_4290.jpeg
  • Hi - I'd love it if we could figure this out. It sounds like a really useful thing to be able to do.

    Maybe the best thing would be to connect to it via NRF Connect on a phone and see if by poking it in the right way you can see what needs doing to make it work.

    You could look at what has to happen on Espruino to make things work: https://github.com/espruino/Espruino/blo­b/master/targetlibs/nrf5x_12/components/­ble/ble_services/ble_hids/ble_hids.c

    It's a bit hard to decipher but 2A4D definitely looks like the right characteristic to listen for notifies on (I guess if there are two you just have to make sure you use the right one).

    As far as I can see Espruino will just send data to that anyway without initialisation. But it may be you also have to set the protocol mode on some devices (2A4E) - maybe to PROTOCOL_MODE_REPORT (which is just 1).

  • Thanks @Gordon for the hints.

    NRF Connect only shows the Service, no peeking or poking possible (iOS here).

    By the way, this is my code so far:

    var notify = 0;
    var hidService;
    var modeChar, reportChar;
    
    function onInit() {
    
      NRF.requestDevice({
        filters: [{namePrefix: "AB"}]
      }).then((device)=>{
        console.log("FOUND");
        return device.gatt.connect();
      }).then((gatt)=>{
        console.log("CONNECTED", gatt);
        return gatt.getPrimaryService("1812");
      }).then((service)=>{
        hidService = service;
        console.log("SERVICE", hidService);
        return hidService.getCharacteristic(0x2a4e);
        // 2a4e - protocol mode (R-W)
        // 2a4d - report (R-N)
        // 2a4b - report map (R)
        // 2a4a - hid information (R)
        // 2a4c - hid control point (W)    
      }).then((char)=>{
        console.log("MODE", char);
        modeChar = char;
        return char.readValue();
      }).then((d)=>{
        console.log("READ", d.buffer);
        console.log("WRITING 1");
        return modeChar.writeValue(1);
      }).then((d)=>{
        console.log("WRITTEN, GET REPORT");
        return hidService.getCharacteristic(0x2a4d);
      }).then((char)=>{
        console.log("REPORT", char);
        reportChar = char;
        char.on("characteristicvaluechanged",(e)­=>{
          console.log("EVENT", e.targete.value);
        });
        return char.startNotifications();
      }).then((d)=>{
        console.log("DONE");
        return true;
      }).catch((err)=>{
        console.log("ERROR", err);
      }); 
    }
    
    onInit();
    

    And this is the output:

    FOUND
    CONNECTED BluetoothRemoteGATTServer: {
      "device": BluetoothDevice: {
        "id": "2a:07:98:10:35:fb public",
        "rssi": -59,
        "data": new Uint8Array([2, 1, 5, 3, 2, 18, 24, 3, 25, 193, 3, 12, 9, 65, 66, 32, 83, 104, 117, 116, 116, 101, 114, 51]).buffer,
        "name": "AB Shutter3",
        "services": [
          "1812"
         ],
        "gatt":  ...
       },
      "connected": true, "handle": 1 }
    SERVICE BluetoothRemoteGATTService: {
      "device": BluetoothDevice: {
        "id": "2a:07:98:10:35:fb public",
        "rssi": -59,
        "data": new Uint8Array([2, 1, 5, 3, 2, 18, 24, 3, 25, 193, 3, 12, 9, 65, 66, 32, 83, 104, 117, 116, 116, 101, 114, 51]).buffer,
        "name": "AB Shutter3",
        "services": [
          "1812"
         ],
        "gatt": BluetoothRemoteGATTServer: {
          "device":  ... ,
          "connected": true, "handle": 1 }
       },
      "uuid": "0x1812",
      "isPrimary": true, "start_handle": 15, "end_handle": 31 }
    MODE BluetoothRemoteGATTCharacteristic: {
      "service": BluetoothRemoteGATTService: {
        "device": BluetoothDevice: {
          "id": "2a:07:98:10:35:fb public",
          "rssi": -59,
          "data": new Uint8Array([2, 1, 5, 3, 2, 18, 24, 3, 25, 193, 3, 12, 9, 65, 66, 32, 83, 104, 117, 116, 116, 101, 114, 51]).buffer,
          "name": "AB Shutter3",
          "services": [
            "1812"
           ],
          "gatt": BluetoothRemoteGATTServer: {
            "device":  ... ,
            "connected": true, "handle": 1 }
         },
        "uuid": "0x1812",
        "isPrimary": true, "start_handle": 15, "end_handle": 31 },
      "uuid": "0x2a4e",
      "handle_value": 17, "handle_decl": 16,
      "properties": { "broadcast": false, "read": true, "writeWithoutResponse": true, "write": false,
        "notify": false, "indicate": false, "authenticatedSignedWrites": false }
     }
    READ new Uint8Array([1]).buffer
    WRITING 1
    WRITTEN, GET REPORT
    REPORT BluetoothRemoteGATTCharacteristic: {
      "service": BluetoothRemoteGATTService: {
        "device": BluetoothDevice: {
          "id": "2a:07:98:10:35:fb public",
          "rssi": -59,
          "data": new Uint8Array([2, 1, 5, 3, 2, 18, 24, 3, 25, 193, 3, 12, 9, 65, 66, 32, 83, 104, 117, 116, 116, 101, 114, 51]).buffer,
          "name": "AB Shutter3",
          "services": [
            "1812"
           ],
          "gatt": BluetoothRemoteGATTServer: {
            "device":  ... ,
            "connected": true, "handle": 1 }
         },
        "uuid": "0x1812",
        "isPrimary": true, "start_handle": 15, "end_handle": 31 },
      "uuid": "0x2a4d",
      "handle_value": 19, "handle_decl": 18,
      "properties": { "broadcast": false, "read": true, "writeWithoutResponse": false, "write": false,
        "notify": true, "indicate": false, "authenticatedSignedWrites": false }
     }
    DONE
    

    Maybe you see something I don't.
    As far as I can see PROTOCOL_MODE already is set to 1:

    READ new Uint8Array([1]).buffer
    

    What about this BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST?

    I will have to look at the sourcecode more in-depth though.
    Any way to log what is going on when Espruino is in the HID role against a phone or computer?

  • Just digging into this issue and found this HOGP specification here.

    Chapter 5 explains details about bonding and how to establish a connection.
    I could not find much of that in the Espruino API yet, maybe this is the missing part?

    At some point the Host (normally computer/phone; here: Espruino) writes his address into the HID device (peripheral; here: the remote button) causing a bonding.

  • Sorry - I posted the reply in the wrong thread:

    Ahh - could be, yes! I think some devices will only work when they've been bonded.

    Maybe try http://www.espruino.com/Reference#l_Blue­toothRemoteGATTServer_startBonding ?

    When the dialog pops up for iOS do you need to enter a pairing code? There might be some more fiddling required on Espruino if so, but it should still be fine with the default firmware.

    Did you set Espruino itself up as a HID device? Usually when it's a HID device iOS/etc will pick it up and show it in the OS menu - but when it's not a HID device they think it's not important so don't keep it in there

  • Yeah - your other comment about bonding from the neighbor thread did the trick.

    This is working now:

    var gatt;
    
    function onInit() {
    
      NRF.requestDevice({
        filters: [{namePrefix: "AB"}]
      }).then((device)=> {
        console.log("FOUND", device.id, device.name);
        return device.gatt.connect();
      }).then((device)=>{
        gatt = device;
        console.log("CONNECTED", device);
        return gatt.startBonding();
      }).then(function() {
        console.log("BONDED", gatt.getSecurityStatus());
        return gatt.getPrimaryService(0x1812);
      }).then((service)=>{
        console.log("SERVICE", service);
        return service.getCharacteristic(0x2a4d);
      }).then((char)=>{
        console.log("CHAR", char);
        char.on("characteristicvaluechanged",(e)­=>{
          console.log("EVENT", e.target.value);
        });
        return char.startNotifications();
      }).then(()=>{
        console.log("DONE");
        return true;
      }).catch((err)=>{
        console.log("ERROR", err);
      });
    }
    
    onInit();
    

    For a button press I get:

    EVENT DataView: {
      "buffer": new Uint8Array([2, 0]).buffer,
      "byteOffset": 0, "byteLength": 2 }
    

    and for a release:

    EVENT DataView: {
      "buffer": new ArrayBuffer(2),
      "byteOffset": 0, "byteLength": 2 }
    

    The only thing is that when I pull the power from Espruino the bonding seems to be lost and the blue LED on the remote begins flashing again.

    This does not happen when bonded to Laptop/Phone. Maybe it's about the whitelisting.

    But I could live with that.
    Thanks @Gordon for the hint!

  • That's great! Yes, not sure about the bonding...

    But this would make a great library! So I guess if you had a Bluetooth LE keyboard, you could conceivably connect it to Bangle.js or Pixl.js and use it as a fully-fledged computer like https://www.espruino.com/Espruino+Home+C­omputer

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Connect external HID device (Keyboard or remote camera trigger)

Posted by Avatar for ChristianW @ChristianW

Actions