I’m very familiar with the HID keyboard and mouse profile and have built products using it but need direction on how to replicate the functionality of a up/down/left/right finger swipe on a iPhone. Any guidance will be valuable. Thanks
I’m very familiar with the HID keyboard and mouse profile and have built products using it but need direction on how to replicate the functionality of a up/down/left/right finger swipe on a iPhone. Any guidance will be valuable. Thanks
What I'm trying to accomplish is exactly what this remote is doing but not sure what type of Bluetooth HID profile it's working with. What it's doing is performing up/down/left/right scroll as if it was a finger swipe. I think it is acting like a digitizer but it shows up as a keyboard profile and not a mouse. There is another thread that talks about HID digitizer and has an example project to go with it but its base is on top of mouse. So I'm little confused on what HID profile to focus on. I do have the packet logs captured from this remote. Is there a way to capture the report profile from a device?
Hi Matthew
If you have access to the device in question you should be able to connect to it from the nRF Connect mobile app, and read out the HID report descriptor from there.
Then you can try to replicate that HID descriptor in your own peripheral implementation.
Best regards
Torbjørn
Hi Ovrebekk,
No worries, I have been working hard figuring a few things out. I started pulling bluetooth packet logs off my iPhone and noticed that the report descriptor is different from the report pulled from my laptop. So once again I had to start over with decoding the report. I have found that the report usage page is a digitizer but the usage is a pen/stylus. I was able to validate the functionality on the iPhone Notes app as when I pushed the up button it gave me a "Pen" warning and started to draw a straight line. BINGO!!!
I'm some what able to get the code to work. The problem that I have now is it will only send a total of 5 messages to the phone and not the total of 8 that I expect. Can you please review the function "digitizer_send()" and see what I need to do to make sure all 8 packets are sent in order. I get no errors. There might be a timing issue that I need to address between sending packets. I see the screen on the phone move to the left but only partial because not all the packets are sent.
Using nRF5_SDK_15.3.0_59ac345 for testing
static void digitizer_send() { // Left Button Press Packets uint8_t packet_1_data[] = { 0x03, 0x20, 0xC3, 0x12 }; uint8_t packet_2_data[] = { 0x03, 0xBC, 0xC2, 0x12 }; uint8_t packet_3_data[] = { 0x03, 0x58, 0xC2, 0x12 }; uint8_t packet_4_data[] = { 0x03, 0xF4, 0xC1, 0x12 }; uint8_t packet_5_data[] = { 0x03, 0x90, 0xC1, 0x12 }; uint8_t packet_6_data[] = { 0x03, 0x2C, 0xC1, 0x12 }; uint8_t packet_7_data[] = { 0x02, 0x2C, 0xC1, 0x12 }; uint8_t packet_8_data[] = { 0x02, 0x2C, 0xC1, 0x12 }; uint32_t err_code; err_code = ble_hids_inp_rep_send(&m_hids, INPUT_REPORT_DIG_INDEX, INPUT_REPORT_DIG_MAX_LEN, packet_1_data, m_conn_handle); err_code = ble_hids_inp_rep_send(&m_hids, INPUT_REPORT_DIG_INDEX, INPUT_REPORT_DIG_MAX_LEN, packet_2_data, m_conn_handle); err_code = ble_hids_inp_rep_send(&m_hids, INPUT_REPORT_DIG_INDEX, INPUT_REPORT_DIG_MAX_LEN, packet_3_data, m_conn_handle); err_code = ble_hids_inp_rep_send(&m_hids, INPUT_REPORT_DIG_INDEX, INPUT_REPORT_DIG_MAX_LEN, packet_4_data, m_conn_handle); err_code = ble_hids_inp_rep_send(&m_hids, INPUT_REPORT_DIG_INDEX, INPUT_REPORT_DIG_MAX_LEN, packet_5_data, m_conn_handle); err_code = ble_hids_inp_rep_send(&m_hids, INPUT_REPORT_DIG_INDEX, INPUT_REPORT_DIG_MAX_LEN, packet_6_data, m_conn_handle); err_code = ble_hids_inp_rep_send(&m_hids, INPUT_REPORT_DIG_INDEX, INPUT_REPORT_DIG_MAX_LEN, packet_7_data, m_conn_handle); err_code = ble_hids_inp_rep_send(&m_hids, INPUT_REPORT_DIG_INDEX, INPUT_REPORT_DIG_MAX_LEN, packet_8_data, m_conn_handle); //APP_ERROR_CHECK(err_code); }
Packet 1: 050D 0902 A101 8501 050D 0920 A102 0942 1500 2501 7501 Packet 2: 9501 8102 0932 8102 9502 8101 7504 0951 250F 9501 8102 Packet 3: 0501 1600 0026 E803 750C 5500 6500 0930 3600 0046 E803 Packet 4: 9501 8102 26E8 0346 E803 0931 8102 C085 0375 0895 0115 Packet 5: 0125 0809 55B1 02C0 050C 0901 A101 8502 1500 2501 7501 Packet 6: 9518 0A23 020A 2102 0A8A 010A AE01 0A96 010A 0103 0A02 Packet 7: 030A 0503 0A06 030A 0703 0A08 030A 8301 0A0B 0309 4009 Packet 8: 3009 6509 7009 6F09 B609 CD09 B509 E209 EA09 E981 02C0 0x05, 0x0D, // USAGE_PAGE (Digitizer) 0x09, 0x02, // USAGE (Pen) 0xA1, 0x01, // Collection (Application) 0x85, 0x01, // REPORT_ID 2 0x05, 0x0D, // USAGE_PAGE (Digitizer) 0x09, 0x20, // USAGE (Stylus) 0xA1, 0x02, // Collection (Logical) 0x09, 0x42, // USAGE (Tip Switch) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x75, 0x01, // REPORT_SIZE (1) 0x95, 0x01, // REPORT_COUNT (1) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x09, 0x32, // USAGE (In Range) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x95, 0x02, // REPORT_COUNT (2) 0x81, 0x01, // Input (Constant) 0x75, 0x04, // REPORT_SIZE (4) 0x09, 0x51, // USAGE (Contact Identifier) 0x25, 0x0F, // LOGICAL_MAXIMUM (15) 0x95, 0x01, // REPORT_SIZE (1) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x16, 0x00, 0x00, // LOGICAL_MINIMUM (0) 0x26, 0xE8, 0x03, // LOGICAL_MAXIMUM (0x03E8 = 1000) 0x75, 0x0C, // REPORT_SIZE (12) 0x55, 0x00, // UNIT_EXPONENT (0) 0x65, 0x00, // UNIT(Inch, EngLinear) 0x09, 0x30, // USAGE (X) 0x36, 0x00, 0x00, // PHYSICAL_MINIMUM (0) 0x46, 0xE8, 0x03, // PHYSICAL_MAXIMUM (0x03E8 = 1000) 0x95, 0x01, // REPORT_COUNT (1) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x26, 0xE8, 0x03, // LOGICAL_MAXIMUM (0x03E8 = 1000) 0x46, 0xE8, 0x03, // PHYSICAL_MAXIMUM (0x03E8 = 1000) 0x09, 0x31, // USAGE (Y) 0x81, 0x02, // INPUT (Data,Var,Abs) 0xC0, // END_COLLECTION 0x85, 0x03, // REPORT_ID 3 0x75, 0x08, // REPORT_SIZE (8) 0x95, 0x01, // REPORT_COUNT (1) 0x15, 0x01, // LOGICAL_MINIMUM (1) 0x25, 0x08, // LOGICAL_MAXIMUM (8) 0x09, 0x55, // USAGE (Contact Count Maximum) 0xB1, 0x02, // FEATURE (Data,Var,Abs) 0xC0 // END_COLLECTION ## UP Value: 03F4 0132 Value: 03F4 2130 Value: 03F4 412E Value: 03F4 612C Value: 03F4 812A Value: 03F4 A128 Value: 03F4 C126 Value: 03F4 E124 Value: 03F4 0123 Value: 03F4 2121 Value: 03F4 411F Value: 03F4 611D Value: 03F4 811B Value: 03F4 A119 Value: 03F4 C117 Value: 03F4 E115 Value: 03F4 0114 Value: 02F4 0114 ## DOWN Value: 03F4 C112 Value: 03F4 A114 Value: 03F4 8116 Value: 03F4 6118 Value: 03F4 411A Value: 03F4 211C Value: 03F4 011E Value: 03F4 E11F Value: 03F4 C121 Value: 03F4 A123 Value: 03F4 8125 Value: 03F4 6127 Value: 03F4 4129 Value: 03F4 212B Value: 03F4 012D Value: 03F4 E12E Value: 03F4 C130 Value: 02F4 C130 ## LEFT Value: 0320 C312 Value: 03BC C212 Value: 0358 C212 Value: 03F4 C112 Value: 0390 C112 Value: 032C C112 Value: 022C C112 Value: 022C C112 ## RIGHT Value: 03C8 C012 Value: 032C C112 Value: 0390 C112 Value: 03F4 C112 Value: 0358 C212 Value: 03BC C212 Value: 02BC C212 Value: 022C C112
Hi Ovrebekk,
I'm still having issues with not all packets are being sent. Like I mentioned before I think it's a timing issue and I'm guessing there is a required delay or acknowledgement that is required before sending another. Still no solution for this. Also, been working on building a packet structure from the report descriptor. Have taken my best guess since it's the first time ever doing it. I'm unable to get the correct values sent to the phone with the current packet structure that I tried creating from the report. Your expert advice will be helpful thanks.
byte[0] = Tip Switch, Range, Contact Id
bit 0 = Tip Switch
bit 1 = In Range
bit 2 = Not Used
bit 3 = Not Used
bit 4 = Contact Id
bit 5 = Contact Id
bit 6 = Contact Id
bit 7 = Contact Id
byte[1] = X Coordinate LSB
byte[2] = X and Y Axis Combined
bit 0 = X Coordinate
bit 1 = X Coordinate
bit 2 = X Coordinate
bit 3 = X Coordinate MSB
bit 4 = Y Coordinate LSB
bit 5 = Y Coordinate
bit 6 = Y Coordinate
bit 7 = Y Coordinate
byte[3] = Y Coordinate MSB
typedef PACKED_STRUCT { uint8_t tip_switch : 1; uint8_t range : 1; uint8_t contact_id : 4; uint16_t x : 12; uint16_t y : 12; } stylus_report_t;
## UP Packet 1 Decode ##
0x03 0xF4 0x01 0x32
00000011 11110100 00000001 00110010
tip_switch = 1
range = 1
contact_id = 0
x = 111101000000 = 801 ??
y = 000100110010 = 306 ??Logged Packet Value: 43C8 C804 typedef PACKED_STRUCT { uint8_t tip_switch : 1; uint8_t range : 1; uint8_t contact_id : 4; uint16_t x : 12; uint16_t y : 12; } stylus_report_t; static void digitizer_send_test() { stylus_report_t report = {0}; report.tip_switch = 1; report.range = 1; report.contact_id = 0; report.x = 801; report.y = 306; uint32_t err_code; err_code = ble_hids_inp_rep_send(&m_hids, INPUT_REPORT_DIG_INDEX, INPUT_REPORT_DIG_MAX_LEN, (uint8_t *)&report, m_conn_handle); APP_ERROR_CHECK(err_code); }
Check for errors by not commenting out this line:
//APP_ERROR_CHECK(err_code);
I am never able to send 8 notifications back-to-back in my own applications using the default configuration. I will get the NRF_ERROR_RESOURCES error and then I wait for BLE_GATTS_EVT_HVN_TX_COMPLETE to send the next notification. I think you can also increase the queue size:
https://devzone.nordicsemi.com/f/nordic-q-a/53941/nrf_error_resources
Hi Jefferson,
Thanks for the feedback. You might be right about the queue size. I tried changing a few settings but with no luck. I noticed that the keyboard example has a key buffer that it uses. Not sure how it all works and what is unloading the buffer. Not sure that is the same way I want to do it. I would think not.
app_error_fault_handler (id=0x00004001, info=0x2000fe10, pc=0x00028553)
I think the correct way is to wait for the event BLE_GATTS_EVT_HVN_TX_COMPLETE before sending the next packet if you get the error NRF_ERROR_RESOURCES. If you just want to see something working quickly, maybe just put a delay after each packet or when you get NRF_ERROR_RESOURCES:
err_code = ble_hids_inp_rep_send(&m_hids, INPUT_REPORT_DIG_INDEX, INPUT_REPORT_DIG_MAX_LEN, packet_1_data, m_conn_handle);
nrf_delay_ms(50);
err_code = ble_hids_inp_rep_send(&m_hids, INPUT_REPORT_DIG_INDEX, INPUT_REPORT_DIG_MAX_LEN, packet_1_data, m_conn_handle);
nrf_delay_ms(50);
...
I think the correct way is to wait for the event BLE_GATTS_EVT_HVN_TX_COMPLETE before sending the next packet if you get the error NRF_ERROR_RESOURCES. If you just want to see something working quickly, maybe just put a delay after each packet or when you get NRF_ERROR_RESOURCES:
err_code = ble_hids_inp_rep_send(&m_hids, INPUT_REPORT_DIG_INDEX, INPUT_REPORT_DIG_MAX_LEN, packet_1_data, m_conn_handle);
nrf_delay_ms(50);
err_code = ble_hids_inp_rep_send(&m_hids, INPUT_REPORT_DIG_INDEX, INPUT_REPORT_DIG_MAX_LEN, packet_1_data, m_conn_handle);
nrf_delay_ms(50);
...
Hi
I agree with Jefferson's comment. When the NRF_ERROR_RESOURCES error occurs you should wait for the next BLE_GATTS_EVT_HVN_TX_COMPLETE event, and then you can continue uploading packets until the error occurs again (and then rinse and repeat).
In order to try sending more packets pr connection event you can try increasing the NRF_SDH_BLE_GAP_EVENT_LENGTH define in sdk_config.h, but as the phone also has it's own limit there is no guarantee that increasing the GAP event length will increase the total number of packets.
Best regards
Torbjørn
Torbjørn & Jefferson,
I was able to successfully get my test code to work. It's not pretty but the concept has been proven. I was able to reverse engineer the remote and combine it with the keyboard report. The solution was to queue up the packets and send them sequentially after each success TX response. If I had time I would use the nrf_queue or nrf_ringbuffer library for this solution.
The final functionality is when you press one of the 4 buttons it will send a stylus/pen swipe sequence. You can see this in action if you are in a drawing app.
I want to thank you Torbjørn for taking the time to answer my questions that lead me to the answers I was seeking. I learned a great deal from this exercise. I have attached the final code for others to reference if desired.
All that is left is to break down the packet structure and build a data structure for. If I get that working I will post the final structure. All I have to go on is what I wrote in a few post back.
Hi guys,
I was facing the same issue than matt_wilson: emulate swipe movement from a custom HID Device.
From this HID Report (https://devzone.nordicsemi.com/f/nordic-q-a/38587/is-multi-touch-hid-possible-with-nrf51822-chip), i'm able to "customise" my swipe movements. It's a quick fix but it actually works.
I start a timer when buttons are pressed, and send the report as this:
static void swipe_timeout_handler(void * p_context) { switch (swipe_type) { case LEFT: y = 15000; x = (30000) - cpt*2500; cpt++; digitizer_send3(0, 1, x, y, true); if(cpt == 5) { digitizer_send3(0, 1, x, y, false); timers_stop(); cpt = 0; } break; case RIGHT: y = 15000; x = cpt*2500; cpt++; digitizer_send3(0, 1, x, y, true); if(cpt == 5) { digitizer_send3(0, 1, x, y, false); timers_stop(); cpt = 0; } break; case UP: y = (30000) - cpt*2500; x = 0; cpt++; digitizer_send3(0, 1, x, y, true); if(cpt == 5) { digitizer_send3(0, 1, x, y, false); timers_stop(); cpt = 0; } break; case DOWN: y = cpt*2500; x = 0; cpt++; digitizer_send3(0, 1, x, y, true); if(cpt == 10) { digitizer_send3(0, 1, x, y, false); timers_stop(); cpt = 0; } break; default: // No implementation needed. break; } }
By doing this way, it is possible to emulate differents kinds of swipe by changing timer, cpt, x, y.
While writing, i've see the matt solution with queuing. Will try this later...
Hi
Good to hear you found a working solution Matthew, and thanks for sharing the code
Same to the others chiming in as well, clearly this is something many are trying to do.
Once you share your final code you can mark you reply as the answer, making it easy for other to find also.
Best regards
Torbjørn
Torbjorn,
Thanks for all your help. I have been in the process of migrating my work to my project code base. I have been using the Adafruit Bluefruit52 library because it has simplified my development time. I would rather do it in Segger Studio but right now I prefer to use this 3rd party library because it handles most of the basic/core functionality of an application. Of course I ran into issues and if you have time please review this other ticket. I'm so close to getting this working but reaching out for some final help because I hit a hard wall...