Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Major refactor #3

Merged
merged 43 commits into from
Aug 9, 2024
Merged

Major refactor #3

merged 43 commits into from
Aug 9, 2024

Conversation

shermp
Copy link
Owner

@shermp shermp commented Jul 13, 2024

I wasn't too happy with how... organic the code had gotten, so I decided to do a major refactor.

Quoting myself from the commit message:

  • Switch USB audio code to C++.
  • Add new HA and HAManager classes.
  • Add PseudoAtomic library for proper variable syncing. This allows compiling in Release mode.
  • The shared audio buffer is now a C++ class.
  • Various other refactors.

No real improvements on the audio side. I am currently doing zero buffering, so the connection is unstable, but properly synced audio. Some ideas might be to detect underruns and insert silent packets to compensate, or figure out how to properly buffer without the stereo going out of sync.

I'd be interested to see your thoughts on this refactor @thewierdnut . I think it's better than what I had before, but I'm biased.

shermp added 8 commits July 13, 2024 15:30
I'm not going to try and break this into smaller commits - sorry.

Switch USB audio code to C++.

Add new HA and HAManager classes.

Add PseudoAtomic library for proper variable syncing. This allows compiling in Release mode.

The shared audio buffer is now a C++ class.

Various other refactors.
@thewierdnut
Copy link

Finally got my pico. What console settings are you using to connect to the compiled-in serial port? I've tried both minicom and kermit with various settings and haven't seen any output.

@shermp
Copy link
Owner Author

shermp commented Jul 13, 2024

Settings are in the readme, but here they are:

  • Baud Rate: 115200
  • Data Bits: 8
  • Stop Bits:" 1
  • Parity: None
  • DTR and RTS enabled

@shermp
Copy link
Owner Author

shermp commented Jul 13, 2024

The other problem you might have is if you connect too late, you might not see any new output. That's why I prefer UART debugging myself, because my debug probe serial is always connected.

@shermp
Copy link
Owner Author

shermp commented Jul 13, 2024

Oh, just checked, without hearing aids connected, there is no output. Maybe I need to add some scanning logs back in...

EDIT: There is some output, but it might be too late by the time the USB serial reconnects.

uint8_t type;
uint8_t event_type;
uint8_t address_type;
uint8_t rssi;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rssi is actually an int8_t. The values I have observed usually sit from -100 to about -30 or so.

@thewierdnut
Copy link

I'm still having trouble during the service discovery part. I think you are trying to initiate the service discovery before the connection has been secured, so the hearing device ignores the request.

I've attached an hci dump: pico_output.log

Complete,
};

constexpr uint16_t buff_size_sdu = 161;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be at least 167, to account for the l2cap header

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I'm aware, the SDU is the application data size, and does not contain l2cap headers. That's the PDU, and I believe btstack handles that automatically.

@shermp
Copy link
Owner Author

shermp commented Jul 14, 2024

Thanks for that log @thewierdnut

It shows me that your device doesn't support LE Secure connections, so pairing fails.

I'm still having trouble during the service discovery part. I think you are trying to initiate the service discovery before the connection has been secured, so the hearing device ignores the request.

Yeah, I think I know what I'm doing wrong there.

I've tried changing some stuff this morning, but it hasn't worked yet and I've got to go to work. I'll pick back up this evening.

Sigh... I'm back to the pairing game...

@shermp
Copy link
Owner Author

shermp commented Jul 17, 2024

I've made a few changes, I will be interested to see if that helps for your device @thewierdnut

I'm just setting the default connection parameters up front, and doing away with the parameter update.

I'm trying to perform the ASP notification subscription after the L2CAP connection is created (this is what Android appears to do, at least according to my android HCI snoop).

I also now attempt to delete a pairing on reencryption failure.

@thewierdnut
Copy link

I'll give it a try.

A small note about the android setup state machine... As soon as the Gatt is connected, it triggers a call to UpdateBleConnParams, and if it ever gets a parameter update with the wrong parameters, it just calls UpdateBleConnParams again. It will actually connect to the gap socket as soon as the the encryption is complete and the characteristics are known (discovered or cached), which means the connection parameter update process and the service discovery can actually run in parallel. Once the connection parameters are correct and the socket is available, then it finally calls the ACP start.

@thewierdnut
Copy link

Still isn't happy
pico_output.log

@shermp
Copy link
Owner Author

shermp commented Jul 17, 2024

Why oh why oh why? What am I doing wrong?

@thewierdnut
Copy link

I get the distinct idea that we are somehow overcomplicating this. I'm tempted to steal a btstack sample app and just statically connect to the psm via a mac address, just to see if it is stable.

Also... what do you want to accomplish with this particular peer review? Do you want me to review code style? design? functionality? I think we have moved past that somewhat into troubleshooting a specific hearing aid.

@shermp
Copy link
Owner Author

shermp commented Jul 17, 2024

I get the distinct idea that we are somehow overcomplicating this. I'm tempted to steal a btstack sample app and just statically connect to the psm via a mac address, just to see if it is stable.

Yeah, maybe give that a go. Or feel free to hack away at my code.

Also... what do you want to accomplish with this particular peer review? Do you want me to review code style? design? functionality? I think we have moved past that somewhat into troubleshooting a specific hearing aid.

Just whatever springs to mind really. This troubleshooting is fine, because it's a major refactor in draft state, so I have no worries making major changes. If you can suggest better ways of doing things, feel free, as this is my first ever microcontroller and bluetooth project, so I'm still learning stuff.

Upon re-connecting a HA, there's no need to rediscover it's services and characteristics.
@shermp
Copy link
Owner Author

shermp commented Jul 18, 2024

While I'm not caching the advertising packets, I decided to implement a cache of successfully connected HA's so that subsequent reconnections don't have to rediscover services and characteristics.

Anecdotally, the connection seems less stable after that change, but that could be anything, because bluetooth...

I think once we can get your Starkey's working, I will like to focus on stream stability. I think I will probably need to modify btstack a little to get access to the available outgoing credits.

@shermp
Copy link
Owner Author

shermp commented Jul 19, 2024

Just did a quick bit of logging, it seems the audio streaming never falls behind the PCM stream, at least not to a significant degree. I tried logging with a difference of 8 and 4 packets, and the only time I saw any logging was when things had already gone wrong. I seem to have some state issues...

Maybe I partly just need to find some efficiencies as well. I keep having to remind myself this is running on a dual core ARM Cortex M0+ with 256K of RAM. The M0+ is basically ARM's cheapest and slowest 32-bit CPU.

I may also have to experiment with __not_in_flash_func() to force certain functions to always run from RAM, and not from flash using Execute In Place (XIP). (The XIP cache is 16K I believe).

@shermp
Copy link
Owner Author

shermp commented Jul 19, 2024

Another option might be to investigate the SDK alarm API to schedule the streaming stuff instead of using an infinite loop.

@thewierdnut
Copy link

Here is the latest log output (with advertisements disabled) that you requested via slack:

[Pico-ASHA  INFO] BT ASHA star[Pico-ASHA  INFO] BT ASHA starting.
[Pico-ASHA  INFO] L2CAP Init.
[Pico-ASHA  INFO] SM Init.
[Pico-ASHA  INFO] GATT Client Init.
[Pico-ASHA  INFO] HCI power on.
[Pico-ASHA  INFO] Device DB is empty, allowing a 60s timeout
[Pico-ASHA  INFO] BTstack up and running on 28:CD:C1:0F:41:4A
[Pico-ASHA  INFO] Removing paired devices
[Pico-ASHA  INFO] Start scanning.
[Pico-ASHA  INFO] MFI UUID discovered
[Pico-ASHA  INFO] Hearing aid discovered with addr 9C:9C:1D:96:2F:5E. Connecting...
[Pico-ASHA  INFO] Device connected. Attempt pairing
[Pico-ASHA  INFO] Pairing started
[Pico-ASHA  INFO] Just Works requested
[Pico-ASHA  INFO] Pairing complete, success
[Pico-ASHA  INFO] Device paired. Discovering ASHA service
[Pico-ASHA  INFO] ASHA service found
[Pico-ASHA  INFO] Got ROP Characteristic
[Pico-ASHA  INFO] Characteristic handles: Start: 0x00b9  Value: 0x00ba  End: 0x00bb
[Pico-ASHA  INFO] Got ACP Characteristic
[Pico-ASHA  INFO] Characteristic handles: Start: 0x00bc  Value: 0x00bd  End: 0x00be
[Pico-ASHA  INFO] Got AUS Characteristic
[Pico-ASHA  INFO] Characteristic handles: Start: 0x00bf  Value: 0x00c0  End: 0x00c2
[Pico-ASHA  INFO] Got VOL Characteristic
[Pico-ASHA  INFO] Characteristic handles: Start: 0x00c3  Value: 0x00c4  End: 0x00c5
[Pico-ASHA  INFO] Got PSM Characteristic
[Pico-ASHA  INFO] Characteristic handles: Start: 0x00c6  Value: 0x00c7  End: 0x00c8
[Pico-ASHA  INFO] ASHA characteristic discovery complete
[Pico-ASHA  INFO] Getting ReadOnlyProperties value
[Pico-ASHA  INFO] Completed value read of ReadOnlyProperties
[Pico-ASHA  INFO] Getting PSM value
[Pico-ASHA  INFO] PSM: 161
[Pico-ASHA  INFO] Completed value read of PSM
[Pico-ASHA  INFO] Read Only Properties
  Version:        1
  Side:           Right
  Mode:           Binaural
  CSIS:           No
  Manufacture ID: 00ba
  LE CoC audio:   Yes
  Render delay:   160
  Supports 16KHz: Yes
  Supports 24KHz: No
[Pico-ASHA  INFO] MFI UUID discovered
[Pico-ASHA  INFO] Hearing aid discovered with addr 9C:9C:1D:98:BE:82. Connecting...
[Pico-ASHA  INFO] Device connected. Attempt pairing
[Pico-ASHA  INFO] Pairing started
[Pico-ASHA  INFO] Just Works requested
[Pico-ASHA  INFO] Pairing complete, success
[Pico-ASHA  INFO] Device paired. Discovering ASHA service
[Pico-ASHA  INFO] ASHA service found
[Pico-ASHA  INFO] Got ROP Characteristic
[Pico-ASHA  INFO] Characteristic handles: Start: 0x00b9  Value: 0x00ba  End: 0x00bb
[Pico-ASHA  INFO] Got ACP Characteristic
[Pico-ASHA  INFO] Characteristic handles: Start: 0x00bc  Value: 0x00bd  End: 0x00be
[Pico-ASHA  INFO] Got AUS Characteristic
[Pico-ASHA  INFO] Characteristic handles: Start: 0x00bf  Value: 0x00c0  End: 0x00c2
[Pico-ASHA  INFO] Got VOL Characteristic
[Pico-ASHA  INFO] Characteristic handles: Start: 0x00c3  Value: 0x00c4  End: 0x00c5
[Pico-ASHA  INFO] Got PSM Characteristic
[Pico-ASHA  INFO] Characteristic handles: Start: 0x00c6  Value: 0x00c7  End: 0x00c8
[Pico-ASHA  INFO] ASHA characteristic discovery complete
[Pico-ASHA  INFO] Getting ReadOnlyProperties value
[Pico-ASHA  INFO] Completed value read of ReadOnlyProperties
[Pico-ASHA  INFO] Getting PSM value
[Pico-ASHA  INFO] PSM: 161
[Pico-ASHA  INFO] Completed value read of PSM
[Pico-ASHA  INFO] Read Only Properties
  Version:        1
  Side:           Left
  Mode:           Binaural
  CSIS:           No
  Manufacture ID: 00ba
  LE CoC audio:   Yes
  Render delay:   160
  Supports 16KHz: Yes
  Supports 24KHz: No
[Pico-ASHA  INFO] Connected to all aid(s) in set.
[Pico-ASHA  INFO] Right: Connecting to L2CAP
[Pico-ASHA  INFO] Left: Connecting to L2CAP
[Pico-ASHA ERROR] Right: L2CAP CoC failed with status code: 0x6a
[Pico-ASHA  INFO] Received disconnection event.
[Pico-ASHA ERROR] Disconnected with reason: 19
[Pico-ASHA  INFO] Right device disconnected.
[Pico-ASHA  INFO] MFI UUID discovered
[Pico-ASHA  INFO] Identity resolving succeeded
[Pico-ASHA  INFO] Connecting to address 9C:9C:1D:96:2F:5E
[Pico-ASHA  INFO] Device connected. Attempt pairing
[Pico-ASHA  INFO] Reencryption started
[Pico-ASHA ERROR] Reencryption failed with ERROR_CODE_PIN_OR_KEY_MISSING
[Pico-ASHA  INFO] Removing paired device with address 9C:9C:1D:96:2F:5E
[Pico-ASHA  INFO] Found. Removing
[Pico-ASHA ERROR] Left: L2CAP CoC failed with status code: 0x6a
[Pico-ASHA  INFO] Received disconnection event.
[Pico-ASHA ERROR] Disconnected with reason: 19
[Pico-ASHA  INFO] Left device disconnected.
[Pico-ASHA  INFO] MFI UUID discovered
[Pico-ASHA  INFO] Identity resolving succeeded
[Pico-ASHA  INFO] Connecting to address 9C:9C:1D:98:BE:82
[Pico-ASHA  INFO] Device connected. Attempt pairing
[Pico-ASHA  INFO] Reencryption started
[Pico-ASHA ERROR] Reencryption failed with ERROR_CODE_PIN_OR_KEY_MISSING
[Pico-ASHA  INFO] Removing paired device with address 9C:9C:1D:98:BE:82
[Pico-ASHA  INFO] Found. Removing

@shermp
Copy link
Owner Author

shermp commented Jul 20, 2024

Thanks, so it seems it's fine up until attempting to create the l2cap channel, at which point it has a melt-down, and for the cherry on top, it refuses to pair after reconnecting.

The l2cap error is L2CAP_CONNECTION_BASEBAND_DISCONNECT. Looking at the btstack source I see the following:

#ifdef ENABLE_L2CAP_LE_CREDIT_BASED_FLOW_CONTROL_MODE
static void l2cap_handle_hci_le_disconnect_event(l2cap_channel_t * channel){
    if (l2cap_send_open_failed_on_hci_disconnect(channel)){
        l2cap_cbm_emit_channel_opened(channel, L2CAP_CONNECTION_BASEBAND_DISCONNECT);
    } else {
        l2cap_emit_channel_closed(channel);
    }
    l2cap_free_channel_entry(channel);
}
#endif

The error is based on what l2cap_send_open_failed_on_hci_disconnect(channel) returns, so lets look at that:

static int l2cap_send_open_failed_on_hci_disconnect(l2cap_channel_t * channel){
    // open cannot fail for for incoming connections
    if (channel->state_var & L2CAP_CHANNEL_STATE_VAR_INCOMING) return 0;

    // check state
    switch (channel->state){
        case L2CAP_STATE_WILL_SEND_CREATE_CONNECTION:
        case L2CAP_STATE_WAIT_CONNECTION_COMPLETE:
        case L2CAP_STATE_WAIT_REMOTE_SUPPORTED_FEATURES:
        case L2CAP_STATE_WAIT_OUTGOING_SECURITY_LEVEL_UPDATE:
        case L2CAP_STATE_WAIT_CLIENT_ACCEPT_OR_REJECT:
        case L2CAP_STATE_WAIT_OUTGOING_EXTENDED_FEATURES:
        case L2CAP_STATE_WAIT_CONNECT_RSP:
        case L2CAP_STATE_CONFIG:
        case L2CAP_STATE_WILL_SEND_CONNECTION_REQUEST:
        case L2CAP_STATE_WILL_SEND_LE_CONNECTION_REQUEST:
        case L2CAP_STATE_WAIT_LE_CONNECTION_RESPONSE:
        case L2CAP_STATE_EMIT_OPEN_FAILED_AND_DISCARD:
            return 1;

        case L2CAP_STATE_OPEN:
        case L2CAP_STATE_CLOSED:
        case L2CAP_STATE_WAIT_INCOMING_EXTENDED_FEATURES:
        case L2CAP_STATE_WAIT_DISCONNECT:
        case L2CAP_STATE_WILL_SEND_CONNECTION_RESPONSE_INSUFFICIENT_SECURITY:
        case L2CAP_STATE_WILL_SEND_CONNECTION_RESPONSE_DECLINE:
        case L2CAP_STATE_WILL_SEND_CONNECTION_RESPONSE_ACCEPT:
        case L2CAP_STATE_WILL_SEND_DISCONNECT_REQUEST:
        case L2CAP_STATE_WILL_SEND_DISCONNECT_RESPONSE:
        case L2CAP_STATE_WILL_SEND_LE_CONNECTION_RESPONSE_DECLINE:
        case L2CAP_STATE_WILL_SEND_LE_CONNECTION_RESPONSE_ACCEPT:
        case L2CAP_STATE_INVALID:
        case L2CAP_STATE_WAIT_INCOMING_SECURITY_LEVEL_UPDATE:
            return 0;

        default:
            // get a "warning" about new states
            btstack_assert(false);
            return 0;
    }
}

In other words, it could be any of the first 12 l2cap states. Very helpful btstack...

@thewierdnut
Copy link

thewierdnut commented Jul 20, 2024

If you look at the raw hci traffic, you see this just prior to the logged disconnect:
image

Basically, it quit because the hearing aid terminated the connection on purpose. My devices tend to just cut the bluetooth connection when they see something they don't like.

The timestamp is in ms from boot
@shermp
Copy link
Owner Author

shermp commented Jul 21, 2024

@thewierdnut I've added a basic timestamp to logs, that should give more context, without majorly screwing up timings like a HCI dump does.

@shermp
Copy link
Owner Author

shermp commented Aug 3, 2024

Apologies for the lack of updates. I started trying some stuff, and then my plans were all derailed early this week.

@thewierdnut
Copy link

No worries. Life happens. I haven't had time to work on it either.

@shermp
Copy link
Owner Author

shermp commented Aug 9, 2024

New Pico SDK is out. I think I'm just gonna merge this as-is, then start hopefully making stability improvements.

@shermp shermp marked this pull request as ready for review August 9, 2024 05:49
@shermp shermp merged commit d7a1802 into main Aug 9, 2024
2 checks passed
@shermp shermp deleted the refactor-1 branch August 9, 2024 05:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants