Technical Details - K7MDL2/IC-705-BLE-Serial-Example GitHub Wiki
Technical Details:
Some of this is chronological as it was discovered during development. Moved from the Readme 9/22/2024.
UUID from device:
I noted that on BT Classic, the BT address is shown on the 705 Paired devices list. For BLE the UUID I send is not used for display and a number is auto_assigned starting with 1, incrementing for each new device I try pairing, same or unique name.
CI-V Pairing and Sign-in state engine:
In the IC-705_BLE_Decoder_Simple program I now track the state of pairing and sign in. The BLE scanner will be restarted on a failed connection attempt, or keep scanning looking for a qualified server (the radio). The messages are so far undocumented. Looking at 1 code sample found, it sends a UUID string. From where is this derived? I made one up. For BT classic SPP the BT address is displayed on the radio, formatted as a 6-byte BT address with colons. For BLE a sequencial number is assigned starting with 1. The 2nd connection is 00:00:00:00:00:02 for example.
The 3 commands to pair or reconnect are 0x61, 0x62, 0x63. 0x64 is a final reply on success. The radio does not reply to a 0x61 msg but does use the number as evidenced by it creating a new pairing entry if you keep the same name and change the number. Try to connect while not paired and not in pairing mode will fail with a disconnect event. I tried not sending msg 61 and it still pairs and auto-assigns a number as normal but you may find the reply for the name comand missing.
Conclusion: 0xFE 0xF1 0x00 0x61 is the UUID. The UUID is 36 bytes. The total msg length must be 41 bytes to get a 0x62 msg reply. You can still get a pairing and reconnect with less but the 0x62 reply message is suppressed.
Message format details:
Observation: If the ID message 0x61 sent is of a certain lemgth (41 bytes, 36 for the ID) then the name (0x62) reply is sent, otherwise the 0x62 is suppressed, still get the token reply. Maybe a reject message collision, ? Sending a BT address results in no reply for 0x62 (Name). Still works though. If the state engine requires the name reply msg, then you have to get the ID right.
When only the ID number was changed a 2nd pairing with the same name but address shows as #2. So it is looking at the number even though there is no reply. Change the ID length (hence the ID value) and a reconnect is rejected.
Try to connect with no existing matching pairing or radio not in pairing mode and you get an error resulting a a disconnect.
The below is seen when you have Warning or Error level debug turned on.
[ 8513][E][BLERemoteCharacteristic.cpp:598] writeValue(): esp_ble_gattc_write_char: rc=259 Unknown ESP_ERR error
[ 8523][E][BLERemoteCharacteristic.cpp:581] writeValue(): Disconnected
Connect Sequence
// ******** Below is a working sequence **********************************************************
UUID = 0xFE, 0xF1, 0x00, 0x61, UUID, 0xFD - must be 41bytes total, 36 for the UUID.
radio reply = None Observed
NAME = 0xFE, 0xF1, 0x00, 0x62, name string, 0xFD // Name must be exactly 16 bytes! - Too short and no reply - 21 bytes total
radio reply = 0xFE, 0xF1, 0x00, 0x62, 0xFD
TOKEN = 0xFE, 0xF1, 0x00, 0x63, 0xEE, 0x39, 0x09, 0x10, 0xFD // a fixed value 9 byes total
radio reply = 0xFE, 0xF1, 0x00, 0x63, 0x00, 0xFD // byte 4 = 0 is already paired - PAIR = EXISTING
radio reply = 0xFE, 0xF1, 0x00, 0x63, 0x01, 0xFD // byte 4 = 1 is pairing accepted - PAIR = SUCCESS
CI-V bus access granted (CIV_granted)
radio reply = 0xFE, 0xF1, 0x00, 0x64, 0xFD // CI-V bus access is granted
****************************** Notes *************************************************************
When Pairing, we get these reponses . PAIR is an attribute of the TOKEN message, byte 4.
NAME, TOKEN, PAIR==SUCCESS, CIV_CONNECTED
If already paired, we get these fewer reponses
TOKEN, PAIR=EXISTING, CIV_CONNECTED
If we are not already paired and the radio is not in pairing reception mode, there are no CIV responses but we will receive a BLE server disconnect. The code needs to restart scanning, get a new or different server address, and try again. More precisely, need to reconnect at the BLE layer and resend the CI-V layer until the radio answers, which could be never, or a long time. We can retry until we get disconnected or power off. One of the problems with the long retry is the possiblity of malloc errors and heap crashes.
If the radio is connected to another device, then same as above, no CI-V response, but stays BLE connected (?? verify this). Radio accepts up to 4 connections (as I read the docs found) so if there are enough headsets, mics, PTT buttons, CI-V monitors/decoders connected, you might get a server connection fail.
I have observed that if the pairing process does not complete for what ever reason, the BLE link will disconnect after just a few seconds. This requires running through the client (aka central) connect-to-server (aka peripheral) function while waiting for a CI-V Access completion. This would be a normal scenario if the radio is not in pairing mode, turned off, or out of range.
Aug 3, 2024 Note: The extended demo file IC-705_BLE_Decoder_Simple.ino has extensions to the stripped down uart demo file to to communicate with the 705. I have been working on improving the ability to recover from disconnects. Initially it did pretty good for pairing and reconnecting on short term disconnects, However, if you turned off the the radio and wait a while, the connect-to-server function gets stuck at the "Connected to server" step. With debug on the last BLECLient event is ESP_GATTC_UNREG_EVT which is client unregistered. See Issue #1 (now closed) for details. The soluton was simple - avoid this state!. In the onDisconnect callback doConnect is set to false. This causes the main loop top run the scanner scanner until it finds a vaid radio. The only place that should set doConnect to true is in the Scanner callback onResult function when a valid radio is found and the address is set.
While the onDisconnect solution worked to avoid connecting to non existing radios, it was too slow to be reliable. I am now using pClient->isConnected in the main loop. Have to be sure the pointer is not null before you use it, it does change and you owill see an exception crash. Leaving * off pointer declarations also does that :-).
Other changes are targeted at avoiding malloc errors (which I saw) when rerunning Scan_BLE_Servers() by clearing the discovered list each scan.
pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory
I also did more experiments with passing BT addresses in various formats but so far the radio has never acknowledged any 0x61 messages and the displayed address is always 00:00:00:00:00:xx where xx starts with 01 for each new BLE client paired. Do not know if that is expected or not.
So as of Aug 4, I think the Decoder_Simple version is a pretty robust template to add fancy UI, SD card config, band decoder and PTT breakout, etc. This was my first BLE project, just happen to pick one that seems to have an undocumented interface to the radio. Also the examples for BLE UART all seems to be server side. Another oddity is Icom chose to make the RX and TX characteristic UUIDs the same, most products are different IDs. I assume this is because CI-V is a simplex bus.
When I thought everything was working I later found nothing or little works. I now think much of this was due to my trying to track the state of each message success and BLE link status. When changing the contents of the ID or Name message the replies stopped so the state engine failed. Now I understand better when you get replies and when you don't so I think it will be more sane now. I just look for the final success message and track the BLE link status which is how you know about connection failures/rejections. For this I am now using isConnected() (when there is a valid pointer).
8/8/2024: I tried to auto-switch between BT and USB Host. It gets tricky. You can configure BT to start up but can do so only after the USB Host has been run through a bit, else the USB host task hangs, usually causing an OS watchdog timeout and reboot, but often not. Also you only get 1 plug and unplug cycle, else timeout. In this case a reboot is faster and easy way to work around this for now. The USB disconnect event calls ESP.restart().
Instead of auto-failover, I now have 2 buttons that switch between modes. No reboot required. A (Left button) is BT mode, C (right button) is USB mode. Hold the A (BT) button until you see the screen change to "Connecting to BT". Hold USB C button until you see it change. BT goes into a blocking discovery loop so reading buttons without interrupts is not easy o break out of a loop of that. Maybe make it a one shot per button. You can use CI-V auto-address discovery which works beter if you roate the dial. I can swap between 2 radios this way, one on BT and one on USB.
I tried to configure the USB Host and Peripheral (main) ports for 2 virtual serial channels. So far have not got it to work. It requires changing a config file setting. The Arduino library is preconfigured so wont pick up some edits. They use a KConfig file approach with Cmake and have you to run esp idf.py menuconfig (once you loaded the TinyUSB component) in your project directory 'main' folder which is not set up like Arduino. the menuconfig writes out the KConfig file where the library will look for it and override the default. CFG_TUD_COUNT 2 is the key. The ESP32-S3, maybe all ESPs, did not support this until a few months ago. There is one example file cdc-multi, but requires the above process to work. Big learning curve here.
I studied the timing of sent vs reply messages between BT and USB. The radio sends out new frequency values when you turn the VFO very fast and I get all of those. For a while every 4th reply was missing the first bytes. I rebooted the radio and never saw it again. That was on USB only. I also experienced replies taking 5 seconds over USB for quite a while. Eventually this went away as I retructured control flags for the buttons so it was something in my code. BT however was very fast during that time and every TX is answered immediately, within a few milliseconds if not the same.