From 556a50dac7bb2e0eee026e7f9cb51b6be96d2ea3 Mon Sep 17 00:00:00 2001 From: liu weikai Date: Wed, 1 Jul 2026 01:01:52 +0800 Subject: [PATCH] Stabilize nRF BLE reads and share pager tone --- boards/tdeck/src/tdeck_board.cpp | 41 ++- boards/tdeck_pro/src/tdeck_pro_board.cpp | 41 ++- boards/tlora_pager/src/tlora_pager_board.cpp | 71 +---- docs/MESHTASTIC_PHONE_PROTOCOL_REFERENCE.md | 2 + docs/meshtastic_ble_timing.md | 11 +- .../ui/audio/pager_notification_tone.h | 254 ++++++++++++++++++ .../include/ble/meshtastic_ble.h | 13 +- .../arduino_common/src/ble/meshtastic_ble.cpp | 132 ++++++--- 8 files changed, 447 insertions(+), 118 deletions(-) create mode 100644 modules/core_sys/include/platform/ui/audio/pager_notification_tone.h diff --git a/boards/tdeck/src/tdeck_board.cpp b/boards/tdeck/src/tdeck_board.cpp index 97e013ce..7865430f 100644 --- a/boards/tdeck/src/tdeck_board.cpp +++ b/boards/tdeck/src/tdeck_board.cpp @@ -3,8 +3,7 @@ #include "board/sd_utils.h" #include "display/drivers/ST7789TDeck.h" #include "platform/esp/arduino_common/storage/sd_card_runtime.h" -#include -#include +#include "platform/ui/audio/pager_notification_tone.h" #include #include #include @@ -1349,7 +1348,8 @@ void TDeckBoard::playMessageTone() return; } - static const char kMessageToneRtttl[] = "MsgRcv:d=4,o=6,b=200:32e,32g,32b,16c7"; + static constexpr size_t kFramesPerChunk = 128; + namespace pager_tone = ::platform::ui::audio::pager_notification; AudioOutputI2S audio_out(1, AudioOutputI2S::EXTERNAL_I2S); #if defined(DAC_I2S_MCLK) @@ -1357,6 +1357,9 @@ void TDeckBoard::playMessageTone() #else audio_out.SetPinout(DAC_I2S_BCK, DAC_I2S_WS, DAC_I2S_DOUT); #endif + audio_out.SetRate(pager_tone::kPlaybackSampleRateHz); + audio_out.SetBitsPerSample(16); + audio_out.SetChannels(pager_tone::kChannels); float gain = static_cast(message_tone_volume_) / 250.0f; if (gain < 0.0f) { @@ -1368,21 +1371,35 @@ void TDeckBoard::playMessageTone() } audio_out.SetGain(gain); - AudioFileSourcePROGMEM song(kMessageToneRtttl, sizeof(kMessageToneRtttl) - 1); - AudioGeneratorRTTTL generator; - - if (generator.begin(&song, &audio_out)) + if (audio_out.begin()) { - const uint32_t deadline = millis() + 1600; - while (generator.isRunning() && millis() < deadline) + const uint32_t deadline = millis() + 1600U; + int16_t pcm[kFramesPerChunk * pager_tone::kChannels]; + pager_tone::AdpcmPlaybackState tone_state{}; + while (pager_tone::hasMore(tone_state) && millis() < deadline) { - if (!generator.loop()) + const uint16_t frames = pager_tone::fillStereoInterleaved( + tone_state, pcm, static_cast(kFramesPerChunk)); + if (frames == 0U) { break; } - delay(1); + + uint16_t written = 0U; + while (written < frames && millis() < deadline) + { + const uint16_t consumed = + audio_out.ConsumeSamples(&pcm[written * pager_tone::kChannels], + static_cast(frames - written)); + if (consumed == 0U) + { + delay(1); + continue; + } + written = static_cast(written + consumed); + } } - generator.stop(); + audio_out.flush(); } audio_out.stop(); s_playing = false; diff --git a/boards/tdeck_pro/src/tdeck_pro_board.cpp b/boards/tdeck_pro/src/tdeck_pro_board.cpp index ca3e42d9..0a16517e 100644 --- a/boards/tdeck_pro/src/tdeck_pro_board.cpp +++ b/boards/tdeck_pro/src/tdeck_pro_board.cpp @@ -3,8 +3,6 @@ #include "boards/tdeck_pro/tdeck_pro_board.h" #include -#include -#include #include #include #include @@ -16,6 +14,7 @@ #include #include "platform/esp/arduino_common/storage/sd_card_runtime.h" +#include "platform/ui/audio/pager_notification_tone.h" namespace boards::tdeck_pro { @@ -505,11 +504,16 @@ void TDeckProBoard::playMessageTone() return; } - static const char kMessageToneRtttl[] = "Msg:d=4,o=6,b=200:32e,32g,32b,16c7"; + static constexpr size_t kFramesPerChunk = 128; + namespace pager_tone = ::platform::ui::audio::pager_notification; + AudioOutputI2S audio_out(1, AudioOutputI2S::EXTERNAL_I2S); audio_out.SetPinout(profile().optional.i2s_bclk, profile().optional.i2s_lrc, profile().optional.i2s_dout); + audio_out.SetRate(pager_tone::kPlaybackSampleRateHz); + audio_out.SetBitsPerSample(16); + audio_out.SetChannels(pager_tone::kChannels); float gain = static_cast(message_tone_volume_) / 250.0f; if (gain > 0.40f) { @@ -517,20 +521,35 @@ void TDeckProBoard::playMessageTone() } audio_out.SetGain(gain); - AudioFileSourcePROGMEM song(kMessageToneRtttl, sizeof(kMessageToneRtttl) - 1); - AudioGeneratorRTTTL generator; - if (generator.begin(&song, &audio_out)) + if (audio_out.begin()) { - const uint32_t deadline = millis() + 1600; - while (generator.isRunning() && millis() < deadline) + const uint32_t deadline = millis() + 1600U; + int16_t pcm[kFramesPerChunk * pager_tone::kChannels]; + pager_tone::AdpcmPlaybackState tone_state{}; + while (pager_tone::hasMore(tone_state) && millis() < deadline) { - if (!generator.loop()) + const uint16_t frames = pager_tone::fillStereoInterleaved( + tone_state, pcm, static_cast(kFramesPerChunk)); + if (frames == 0U) { break; } - delay(1); + + uint16_t written = 0U; + while (written < frames && millis() < deadline) + { + const uint16_t consumed = + audio_out.ConsumeSamples(&pcm[written * pager_tone::kChannels], + static_cast(frames - written)); + if (consumed == 0U) + { + delay(1); + continue; + } + written = static_cast(written + consumed); + } } - generator.stop(); + audio_out.flush(); } audio_out.stop(); } diff --git a/boards/tlora_pager/src/tlora_pager_board.cpp b/boards/tlora_pager/src/tlora_pager_board.cpp index 21b67a9c..b8dbac48 100644 --- a/boards/tlora_pager/src/tlora_pager_board.cpp +++ b/boards/tlora_pager/src/tlora_pager_board.cpp @@ -9,7 +9,6 @@ #include "freertos/timers.h" #include -#include #include #include #include @@ -19,6 +18,7 @@ #include "display/drivers/ST7796.h" #include "pins_arduino.h" #include "platform/esp/arduino_common/storage/sd_card_runtime.h" +#include "platform/ui/audio/pager_notification_tone.h" #include "platform/ui/settings_store.h" #include "ui/runtime/ui_feedback.h" #include @@ -1093,23 +1093,8 @@ void TLoRaPagerBoard::playMessageTone() s_playing = true; s_last_play_ms = now; - struct ToneStep - { - uint16_t freq_hz; - uint16_t duration_ms; - }; - - static constexpr ToneStep kTone[] = { - {1319, 55}, - {1568, 55}, - {1976, 90}, - }; - static constexpr uint16_t kGapMs = 18; - static constexpr uint32_t kSampleRate = 16000; - static constexpr uint8_t kChannels = 2; static constexpr size_t kFramesPerChunk = 128; - static constexpr float kAmplitude = 0.16f; - static constexpr float kTwoPi = 6.28318530718f; + namespace pager_tone = ::platform::ui::audio::pager_notification; int prev_volume = codec.getVolume(); if (prev_volume < 0 || prev_volume > 100) @@ -1118,7 +1103,8 @@ void TLoRaPagerBoard::playMessageTone() } bool prev_out_mute = codec.getOutMute(); - bool opened = (codec.open(16, kChannels, kSampleRate) == 0); + bool opened = + (codec.open(16, pager_tone::kChannels, pager_tone::kPlaybackSampleRateHz) == 0); if (!opened) { s_playing = false; @@ -1128,48 +1114,19 @@ void TLoRaPagerBoard::playMessageTone() codec.setOutMute(false); codec.setVolume(_message_tone_volume); - int16_t pcm[kFramesPerChunk * kChannels]; - auto write_silence = [&](uint32_t ms) + int16_t pcm[kFramesPerChunk * pager_tone::kChannels]; + pager_tone::AdpcmPlaybackState tone_state{}; + while (pager_tone::hasMore(tone_state)) { - uint32_t remaining = (kSampleRate * ms) / 1000; - while (remaining > 0) + const uint16_t frames = pager_tone::fillStereoInterleaved( + tone_state, pcm, static_cast(kFramesPerChunk)); + if (frames == 0) { - size_t frames = remaining > kFramesPerChunk ? kFramesPerChunk : static_cast(remaining); - memset(pcm, 0, frames * kChannels * sizeof(int16_t)); - codec.write(reinterpret_cast(pcm), frames * kChannels * sizeof(int16_t)); - remaining -= static_cast(frames); - } - }; - - for (size_t i = 0; i < (sizeof(kTone) / sizeof(kTone[0])); ++i) - { - const ToneStep& step = kTone[i]; - uint32_t remaining = (kSampleRate * step.duration_ms) / 1000; - float phase = 0.0f; - const float phase_step = (kTwoPi * static_cast(step.freq_hz)) / static_cast(kSampleRate); - - while (remaining > 0) - { - size_t frames = remaining > kFramesPerChunk ? kFramesPerChunk : static_cast(remaining); - for (size_t n = 0; n < frames; ++n) - { - int16_t sample = static_cast(sinf(phase) * (32767.0f * kAmplitude)); - pcm[n * 2] = sample; - pcm[n * 2 + 1] = sample; - phase += phase_step; - if (phase >= kTwoPi) - { - phase -= kTwoPi; - } - } - codec.write(reinterpret_cast(pcm), frames * kChannels * sizeof(int16_t)); - remaining -= static_cast(frames); - } - - if (i + 1 < (sizeof(kTone) / sizeof(kTone[0]))) - { - write_silence(kGapMs); + break; } + codec.write(reinterpret_cast(pcm), + frames * pager_tone::kChannels * sizeof(int16_t)); + delay(1); } codec.setVolume(static_cast(prev_volume)); diff --git a/docs/MESHTASTIC_PHONE_PROTOCOL_REFERENCE.md b/docs/MESHTASTIC_PHONE_PROTOCOL_REFERENCE.md index 60afff32..cc2caa8e 100644 --- a/docs/MESHTASTIC_PHONE_PROTOCOL_REFERENCE.md +++ b/docs/MESHTASTIC_PHONE_PROTOCOL_REFERENCE.md @@ -565,6 +565,8 @@ Important consequences: - if firmware queues `FromRadio` data but does not cause the app to drain, status updates can appear delayed - if firmware only wakes the app for some variants and not others, phone-side state may lag - if `QueueStatus` or `ROUTING_APP` packets are generated but not drained, UI stays stale +- on nRF52/Bluefruit, the read-authorize callback must stay a lightweight consume marker; `FromRadio` + protobuf encoding, MQTT proxy polling, and characteristic preloading belong in the main runtime loop ## What Must Not Be Misinterpreted diff --git a/docs/meshtastic_ble_timing.md b/docs/meshtastic_ble_timing.md index 74a104e2..36314d6c 100644 --- a/docs/meshtastic_ble_timing.md +++ b/docs/meshtastic_ble_timing.md @@ -182,10 +182,13 @@ Stage 1 完成后: 1. `MeshtasticPhoneCore.notifyFromNum(from_num)` 把真实 `from_num` 交给 nRF52 transport 2. nRF52 transport 将 `from_num` 放入固定深度 pending 队列 -3. 手机完成 `FROMNUM` 订阅后,transport 用同一个真实 `from_num` 发送通知 -4. App 读取 `FROMRADIO` 时进入 `onFromRadioAuthorize()` -5. `writeNextFromRadioForRead()` 调用 `PhoneCore.popToPhone()` 产出下一帧并写入 `FROMRADIO` -6. App 持续读取,直到 `FROMRADIO` 返回空包,表示本轮 drain 完成 +3. 主循环调用 `prepareReadableFromRadio()`,让 `PhoneCore.popToPhone()` 产出下一帧并预装进 `FROMRADIO` +4. 手机完成 `FROMNUM` 订阅且已有预装帧后,transport 用同一个真实 `from_num` 发送通知 +5. App 读取 `FROMRADIO` 时进入 `onFromRadioAuthorize()`;该回调只记录读取/消费状态,不再产出 protobuf 帧 +6. 主循环调用 `consumeReadableFromRadio()` 清空已读预装帧,再准备下一帧 +7. App 持续读取,直到 `FROMRADIO` 返回空包,表示本轮 drain 完成 + +这里有一个稳定性边界:Bluefruit 的 read-authorize 回调不能执行 `popToPhone()`、MQTT proxy 轮询、nanopb 编码或串口日志重活。否则在手机高频 drain、空中包入站和 MQTT downlink 混在一起时,nRF52 侧可能出现无 HardFault 日志的 USB 重枚举/断连。 为了排障,目前固件还打印了这些日志: diff --git a/modules/core_sys/include/platform/ui/audio/pager_notification_tone.h b/modules/core_sys/include/platform/ui/audio/pager_notification_tone.h new file mode 100644 index 00000000..886449c4 --- /dev/null +++ b/modules/core_sys/include/platform/ui/audio/pager_notification_tone.h @@ -0,0 +1,254 @@ +#pragma once + +#include "../../../../../../boards/t_echo_lite/include/boards/t_echo_lite/assets/pager_notification_adpcm.h" + +#include +#include + +namespace platform::ui::audio::pager_notification +{ + +namespace asset = ::boards::t_echo_lite::assets; + +constexpr uint32_t kSourceSampleRateHz = asset::kPagerNotificationSampleRateHz; +constexpr uint32_t kPlaybackSampleRateHz = 44100U; +constexpr uint8_t kChannels = 2U; +static_assert(kPlaybackSampleRateHz % kSourceSampleRateHz == 0, + "Pager notification asset sample rate must divide the playback sample rate"); +constexpr uint8_t kSampleRepeat = + static_cast(kPlaybackSampleRateHz / kSourceSampleRateHz); + +constexpr int8_t kImaAdpcmIndexTable[16] = { + -1, + -1, + -1, + -1, + 2, + 4, + 6, + 8, + -1, + -1, + -1, + -1, + 2, + 4, + 6, + 8, +}; + +constexpr int16_t kImaAdpcmStepTable[89] = { + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 16, + 17, + 19, + 21, + 23, + 25, + 28, + 31, + 34, + 37, + 41, + 45, + 50, + 55, + 60, + 66, + 73, + 80, + 88, + 97, + 107, + 118, + 130, + 143, + 157, + 173, + 190, + 209, + 230, + 253, + 279, + 307, + 337, + 371, + 408, + 449, + 494, + 544, + 598, + 658, + 724, + 796, + 876, + 963, + 1060, + 1166, + 1282, + 1411, + 1552, + 1707, + 1878, + 2066, + 2272, + 2499, + 2749, + 3024, + 3327, + 3660, + 4026, + 4428, + 4871, + 5358, + 5894, + 6484, + 7132, + 7845, + 8630, + 9493, + 10442, + 11487, + 12635, + 13899, + 15289, + 16818, + 18500, + 20350, + 22385, + 24623, + 27086, + 29794, + 32767, +}; + +struct AdpcmPlaybackState +{ + int16_t predictor = asset::kPagerNotificationInitialSample; + int16_t current_sample = asset::kPagerNotificationInitialSample; + uint8_t step_index = 0U; + uint8_t repeat_remaining = 0U; + uint32_t sample_index = 0U; +}; + +inline bool hasMore(const AdpcmPlaybackState& state) +{ + return state.sample_index < asset::kPagerNotificationSampleCount || + state.repeat_remaining > 0U; +} + +inline int16_t clampPcmSample(int32_t sample) +{ + if (sample > 32767) + { + return 32767; + } + if (sample < -32768) + { + return -32768; + } + return static_cast(sample); +} + +inline int16_t nextSourceSample(AdpcmPlaybackState& state) +{ + if (state.sample_index == 0U) + { + state.sample_index = 1U; + return state.current_sample; + } + + const uint32_t nibble_index = state.sample_index - 1U; + const uint8_t encoded = asset::kPagerNotificationAdpcmData[nibble_index / 2U]; + const uint8_t code = (nibble_index & 1U) == 0U ? (encoded & 0x0FU) : (encoded >> 4); + const int16_t step = kImaAdpcmStepTable[state.step_index]; + + int32_t diff = step >> 3; + if ((code & 0x01U) != 0U) + { + diff += step >> 2; + } + if ((code & 0x02U) != 0U) + { + diff += step >> 1; + } + if ((code & 0x04U) != 0U) + { + diff += step; + } + + const int32_t predictor = (code & 0x08U) != 0U + ? static_cast(state.predictor) - diff + : static_cast(state.predictor) + diff; + state.predictor = clampPcmSample(predictor); + state.current_sample = state.predictor; + + const int16_t next_index = + static_cast(state.step_index) + kImaAdpcmIndexTable[code & 0x0FU]; + if (next_index < 0) + { + state.step_index = 0U; + } + else if (next_index > 88) + { + state.step_index = 88U; + } + else + { + state.step_index = static_cast(next_index); + } + + ++state.sample_index; + return state.current_sample; +} + +inline bool nextPlaybackSample(AdpcmPlaybackState& state, int16_t& sample) +{ + if (!hasMore(state)) + { + return false; + } + + if (state.repeat_remaining == 0U) + { + if (state.sample_index >= asset::kPagerNotificationSampleCount) + { + return false; + } + state.current_sample = nextSourceSample(state); + state.repeat_remaining = kSampleRepeat; + } + + sample = state.current_sample; + --state.repeat_remaining; + return true; +} + +inline uint16_t fillStereoInterleaved(AdpcmPlaybackState& state, + int16_t* pcm, + uint16_t max_frames) +{ + uint16_t frames = 0U; + while (frames < max_frames) + { + int16_t sample = 0; + if (!nextPlaybackSample(state, sample)) + { + break; + } + + pcm[frames * kChannels] = sample; + pcm[(frames * kChannels) + 1U] = sample; + ++frames; + } + return frames; +} + +} // namespace platform::ui::audio::pager_notification diff --git a/platform/nrf52/arduino_common/include/ble/meshtastic_ble.h b/platform/nrf52/arduino_common/include/ble/meshtastic_ble.h index f55fe39b..da1141d9 100644 --- a/platform/nrf52/arduino_common/include/ble/meshtastic_ble.h +++ b/platform/nrf52/arduino_common/include/ble/meshtastic_ble.h @@ -16,6 +16,7 @@ #include "platform/shared/ble/phone_ble_runtime.h" #include #include +#include #include #include @@ -45,7 +46,7 @@ class MeshtasticBleService final : public BleService, bool handleToRadio(const uint8_t* data, size_t len); bool enqueueToRadio(const uint8_t* data, size_t len); void flushPendingFromNumNotify(); - bool writeNextFromRadioForRead(); + void handleFromRadioReadRequest(uint16_t conn_handle, uint16_t offset, uintptr_t chr_ptr); void handleConnectEvent(uint16_t conn_handle); void handleDisconnectEvent(uint16_t conn_handle, uint8_t reason); void handleFromNumCccdWrite(uint16_t conn_handle, uint16_t value); @@ -73,6 +74,8 @@ class MeshtasticBleService final : public BleService, void processPendingToRadio(); void processPendingPairingRequest(); void clearToPhoneQueue(); + void prepareReadableFromRadio(); + void consumeReadableFromRadio(); void syncMqttProxySettings(); void markConfigSavePending(bool bluetooth_changed, bool module_changed); void flushPendingConfigSaves(bool force = false); @@ -120,12 +123,16 @@ class MeshtasticBleService final : public BleService, volatile uint16_t pending_pairing_conn_handle_ = BLE_CONN_HANDLE_INVALID; phone::meshtastic::MeshtasticBleFrame session_frame_scratch_{}; + phone::meshtastic::MeshtasticBleFrame from_radio_preloaded_{}; + volatile bool from_radio_preloaded_valid_ = false; + volatile bool from_radio_consume_pending_ = false; volatile bool pending_connect_log_ = false; volatile bool pending_disconnect_log_ = false; volatile bool pending_from_num_cccd_log_ = false; volatile bool pending_pair_complete_log_ = false; volatile bool pending_secured_log_ = false; + volatile bool pending_from_radio_auth_log_ = false; volatile uint16_t pending_connect_conn_handle_ = BLE_CONN_HANDLE_INVALID; volatile uint16_t pending_disconnect_conn_handle_ = BLE_CONN_HANDLE_INVALID; @@ -135,6 +142,10 @@ class MeshtasticBleService final : public BleService, volatile uint16_t pending_pair_complete_conn_handle_ = BLE_CONN_HANDLE_INVALID; volatile uint8_t pending_pair_complete_status_ = 0; volatile uint16_t pending_secured_conn_handle_ = BLE_CONN_HANDLE_INVALID; + volatile uint16_t pending_from_radio_auth_conn_handle_ = BLE_CONN_HANDLE_INVALID; + volatile uint16_t pending_from_radio_auth_offset_ = 0; + volatile uintptr_t pending_from_radio_auth_chr_ = 0; + volatile uintptr_t pending_from_radio_auth_svc_ = 0; volatile bool pending_from_radio_read_log_ = false; volatile bool pending_from_radio_empty_log_ = false; diff --git a/platform/nrf52/arduino_common/src/ble/meshtastic_ble.cpp b/platform/nrf52/arduino_common/src/ble/meshtastic_ble.cpp index badfb921..63aac387 100644 --- a/platform/nrf52/arduino_common/src/ble/meshtastic_ble.cpp +++ b/platform/nrf52/arduino_common/src/ble/meshtastic_ble.cpp @@ -372,18 +372,10 @@ void onFromRadioAuthorize(uint16_t conn_handle, BLECharacteristic* chr, ble_gatt return; } - bleLogBoth("[BLE][nrf52][mt][auth] conn=%u offset=%u chr=%p svc=%p", - static_cast(conn_handle), - static_cast(request->offset), - static_cast(chr), - static_cast(s_active_service)); - - if (request->offset == 0) + if (s_active_service) { - if (s_active_service) - { - (void)s_active_service->writeNextFromRadioForRead(); - } + s_active_service->handleFromRadioReadRequest( + conn_handle, request->offset, reinterpret_cast(chr)); } authorizeRead(conn_handle); @@ -474,8 +466,13 @@ void MeshtasticBleService::start() pending_from_num_cccd_log_ = false; pending_pair_complete_log_ = false; pending_secured_log_ = false; + pending_from_radio_auth_log_ = false; pending_from_radio_read_log_ = false; pending_from_radio_empty_log_ = false; + pending_from_radio_auth_conn_handle_ = BLE_CONN_HANDLE_INVALID; + pending_from_radio_auth_offset_ = 0; + pending_from_radio_auth_chr_ = 0; + pending_from_radio_auth_svc_ = 0; pending_from_radio_empty_reason_ = 0; pending_disconnect_reason_ = 0; pairing_request_pending_ = false; @@ -566,8 +563,13 @@ void MeshtasticBleService::stop() pending_from_num_cccd_log_ = false; pending_pair_complete_log_ = false; pending_secured_log_ = false; + pending_from_radio_auth_log_ = false; pending_from_radio_read_log_ = false; pending_from_radio_empty_log_ = false; + pending_from_radio_auth_conn_handle_ = BLE_CONN_HANDLE_INVALID; + pending_from_radio_auth_offset_ = 0; + pending_from_radio_auth_chr_ = 0; + pending_from_radio_auth_svc_ = 0; pending_from_radio_empty_reason_ = 0; pending_disconnect_reason_ = 0; pairing_request_pending_ = false; @@ -598,6 +600,11 @@ void MeshtasticBleService::update() processPendingPairingRequest(); processPendingToRadio(); + if (from_radio_consume_pending_) + { + consumeReadableFromRadio(); + } + prepareReadableFromRadio(); flushPendingFromNumNotify(); logDeferredBleEvents(); flushPendingConfigSaves(false); @@ -738,25 +745,51 @@ bool MeshtasticBleService::handleToRadio(const uint8_t* data, size_t len) return ok; } -bool MeshtasticBleService::writeNextFromRadioForRead() +void MeshtasticBleService::handleFromRadioReadRequest(uint16_t conn_handle, + uint16_t offset, + uintptr_t chr_ptr) { - uint8_t empty = 0; + pending_from_radio_auth_conn_handle_ = conn_handle; + pending_from_radio_auth_offset_ = offset; + pending_from_radio_auth_chr_ = chr_ptr; + pending_from_radio_auth_svc_ = reinterpret_cast(this); + pending_from_radio_auth_log_ = true; + + if (offset != 0) + { + return; + } + + last_ble_activity_ms_ = millis(); if (!active_ || !connected_ || !phone_session_) { - from_radio_.write(&empty, 0); pending_from_radio_empty_log_ = true; pending_from_radio_empty_reason_ = kFromRadioEmptyInactive; - return false; + return; + } + + if (!from_radio_preloaded_valid_) + { + pending_from_radio_empty_log_ = true; + pending_from_radio_empty_reason_ = kFromRadioEmptyNoFrame; + return; + } + + from_radio_consume_pending_ = true; +} + +void MeshtasticBleService::prepareReadableFromRadio() +{ + if (from_radio_preloaded_valid_ || !active_ || !connected_ || !phone_session_) + { + return; } auto& session_frame = session_frame_scratch_; std::memset(&session_frame, 0, sizeof(session_frame)); if (!phone_session_->popToPhone(&session_frame)) { - from_radio_.write(&empty, 0); - pending_from_radio_empty_log_ = true; - pending_from_radio_empty_reason_ = kFromRadioEmptyNoFrame; - return false; + return; } if (session_frame.len == 0 || session_frame.len > meshtastic_FromRadio_size) @@ -765,27 +798,42 @@ bool MeshtasticBleService::writeNextFromRadioForRead() static_cast(session_frame.from_num), static_cast(session_frame.len), static_cast(meshtastic_FromRadio_size)); + uint8_t empty = 0; from_radio_.write(&empty, 0); pending_from_radio_empty_log_ = true; pending_from_radio_empty_reason_ = kFromRadioEmptyInvalidFrame; - return false; + return; } - from_radio_.write(session_frame.buf, session_frame.len); - pending_from_radio_read_len_ = static_cast(session_frame.len); - pending_from_radio_read_from_num_ = session_frame.from_num; - pending_from_radio_read_log_ = true; - if (pending_from_num_count_ > 0 && - pending_from_num_[pending_from_num_head_] == session_frame.from_num) + from_radio_preloaded_ = session_frame; + from_radio_.write(from_radio_preloaded_.buf, from_radio_preloaded_.len); + from_radio_preloaded_valid_ = true; + + bleLogBoth("[BLE][nrf52][mt][flow] from_radio preload from_num=%08lX len=%u pending_from_num=%u", + static_cast(from_radio_preloaded_.from_num), + static_cast(from_radio_preloaded_.len), + static_cast(pending_from_num_count_)); +} + +void MeshtasticBleService::consumeReadableFromRadio() +{ + from_radio_consume_pending_ = false; + if (!from_radio_preloaded_valid_) { - pending_from_num_head_ = - static_cast((pending_from_num_head_ + 1U) % kPendingFromNumCapacity); - --pending_from_num_count_; + pending_from_radio_empty_log_ = true; + pending_from_radio_empty_reason_ = kFromRadioEmptyNoFrame; + return; } - bleLogBoth("[BLE][nrf52][mt][flow] from_radio authorize frame from_num=%08lX len=%u", - static_cast(session_frame.from_num), - static_cast(session_frame.len)); - return true; + + pending_from_radio_read_len_ = static_cast(from_radio_preloaded_.len); + pending_from_radio_read_from_num_ = from_radio_preloaded_.from_num; + pending_from_radio_read_log_ = true; + + from_radio_preloaded_ = phone::meshtastic::MeshtasticBleFrame{}; + from_radio_preloaded_valid_ = false; + + uint8_t empty = 0; + from_radio_.write(&empty, 0); } bool MeshtasticBleService::enqueueToRadio(const uint8_t* data, size_t len) @@ -852,6 +900,9 @@ void MeshtasticBleService::clearToPhoneQueue() pending_from_num_tail_ = 0; pending_from_num_count_ = 0; session_frame_scratch_ = phone::meshtastic::MeshtasticBleFrame{}; + from_radio_preloaded_ = phone::meshtastic::MeshtasticBleFrame{}; + from_radio_preloaded_valid_ = false; + from_radio_consume_pending_ = false; } void MeshtasticBleService::loadRememberedPhonePeer() @@ -1228,6 +1279,11 @@ void MeshtasticBleService::flushPendingFromNumNotify() return; } + if (!from_radio_preloaded_valid_) + { + return; + } + from_num_.write32(from_num); const bool ok = from_num_.notify32(conn_handle_, from_num); bleLogBoth("[BLE][nrf52][mt][flow] from_num notify value=%08lX source=%08lX conn=%u ok=%u cccd=0x%04X", @@ -1435,6 +1491,16 @@ void MeshtasticBleService::logDeferredBleEvents() static_cast(pending_secured_conn_handle_)); } + if (pending_from_radio_auth_log_) + { + pending_from_radio_auth_log_ = false; + bleLogBoth("[BLE][nrf52][mt][auth] conn=%u offset=%u chr=%p svc=%p", + static_cast(pending_from_radio_auth_conn_handle_), + static_cast(pending_from_radio_auth_offset_), + reinterpret_cast(static_cast(pending_from_radio_auth_chr_)), + reinterpret_cast(static_cast(pending_from_radio_auth_svc_))); + } + if (pending_from_radio_read_log_) { pending_from_radio_read_log_ = false;