From 414b13084060d0f0b6246be111d45fbd337e6214 Mon Sep 17 00:00:00 2001 From: Gene Gallagher <129112619+egalla204@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:07:48 -0400 Subject: [PATCH 01/86] Release/1.1.12 (#200) https://ccp.sys.comcast.net/browse/RDKEMW-16915 --- CHANGELOG.md | 26 +++++++++- CMakeLists.txt | 2 - include/ctrlm_ipc_voice.h | 2 +- src/auth/ctrlm_auth.h | 1 - src/auth/ctrlm_auth_thunder.cpp | 5 -- src/auth/ctrlm_auth_thunder.h | 1 - src/auth/ctrlm_thunder_plugin_authservice.cpp | 18 ------- src/auth/ctrlm_thunder_plugin_authservice.h | 7 --- src/ctrlm.h | 1 - src/ctrlm_controller.cpp | 4 -- src/ctrlm_controller.h | 1 - src/ctrlm_main.cpp | 46 ------------------ src/ctrlm_network.cpp | 10 ---- src/ctrlm_network.h | 3 -- src/thunder/ctrlm_thunder_plugin.cpp | 47 +++++++++++++++++++ src/thunder/ctrlm_thunder_plugin.h | 29 +++++++++--- .../ctrlm_thunder_plugin_powermanager.cpp | 21 +++++---- src/voice/ctrlm_voice_obj.cpp | 18 ------- src/voice/ctrlm_voice_obj.h | 3 -- src/voice/endpoints/ctrlm_voice_endpoint.cpp | 1 - src/voice/endpoints/ctrlm_voice_endpoint.h | 1 - .../endpoints/ctrlm_voice_endpoint_http.cpp | 8 ---- .../endpoints/ctrlm_voice_endpoint_http.h | 1 - .../ctrlm_voice_endpoint_ws_nextgen.cpp | 8 ---- .../ctrlm_voice_endpoint_ws_nextgen.h | 1 - 25 files changed, 107 insertions(+), 158 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52e8cb2d..8121aa80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,32 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). + + +#### [1.1.12](https://github.com/rdkcentral/control/compare/1.1.11...1.1.12) + +> 9 April 2026 + +- RDKEMW-16333: Update Thunder plugin to use _string and _boolean [`#197`](https://github.com/rdkcentral/control/pull/197) +- RDKEMW-5849 : remove deprecated "experience" code [`#185`](https://github.com/rdkcentral/control/pull/185) + +#### [1.1.11.2](https://github.com/rdkcentral/control/compare/1.1.11.1...1.1.11.2) + +> 7 April 2026 + +- RDKEMW-16711: CHANGELOG for ctrlm hotfix release 1.1.11.2 [`#196`](https://github.com/rdkcentral/control/pull/196) +- RDKEMW-16711 : Add wakeup reason string, on support/1.1.11 [`#195`](https://github.com/rdkcentral/control/pull/195) + +#### [1.1.11.1](https://github.com/rdkcentral/control/compare/1.1.11...1.1.11.1) + +> 1 April 2026 + +- RDKEMW-16330: Update Control Manager to use bool for NSM [`#188`](https://github.com/rdkcentral/control/pull/188) +- RDKEMW-16330: update CHANGELOG for release 1.1.11p1 [`98ea5f5`](https://github.com/rdkcentral/control/commit/98ea5f51f8da9ef6ded7038d760baa3b41cc4a90) + #### [1.1.11](https://github.com/rdkcentral/control/compare/1.1.10...1.1.11) -> 5 March 2026 +> 6 March 2026 - RDKEMW-14589: No UI action with "Info" keypress from rf4ce remote in RF mode [`#181`](https://github.com/rdkcentral/control/pull/181) - RDKEMW-14445 : Add session end and protocol return to telemetry [`#182`](https://github.com/rdkcentral/control/pull/182) @@ -15,7 +38,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - RDKEMW-12930: RF4CE network export XCONF on pair/unpair/etc. [`#177`](https://github.com/rdkcentral/control/pull/177) - RDKEMW-13833: Remove duplicate RFC fetch attempts in listeners [`#179`](https://github.com/rdkcentral/control/pull/179) - #### [1.1.10](https://github.com/rdkcentral/control/compare/1.1.9...1.1.10) diff --git a/CMakeLists.txt b/CMakeLists.txt index 565ab874..112b3c00 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -234,8 +234,6 @@ if(THUNDER) if(AUTH_ACTIVATION_STATUS) add_compile_definitions(AUTH_ACTIVATION_STATUS) endif() - #By default disabled but can be enabled - #add_compile_definitions(AUTH_EXPERIENCE) target_link_libraries(controlMgr RdkCertSelector) endif() endif() diff --git a/include/ctrlm_ipc_voice.h b/include/ctrlm_ipc_voice.h index 665ffaa5..ed96c951 100644 --- a/include/ctrlm_ipc_voice.h +++ b/include/ctrlm_ipc_voice.h @@ -64,7 +64,7 @@ #define CTRLM_VOICE_SESSION_TEXT_MAX_LENGTH (512) ///< Session text string maximum length #define CTRLM_VOICE_SESSION_MSG_MAX_LENGTH (128) ///< Session message string maximum length #define CTRLM_VOICE_QUERY_STRING_MAX_LENGTH (128) ///< Query string maximum name or value length -#define CTRLM_VOICE_QUERY_STRING_MAX_PAIRS (16) ///< Query string maximum number of name/value pairs +#define CTRLM_VOICE_QUERY_STRING_MAX_PAIRS (24) ///< Query string maximum number of name/value pairs #define CTRLM_VOICE_REQUEST_IP_MAX_LENGTH (48) ///< cURL request primary IP address string maximum length (big enough for IPv6) #define CTRLM_VOICE_MIN_UTTERANCE_DURATION_MAXIMUM (600) ///< Maximum value of the utterance duration minimum setting (in milliseconds) diff --git a/src/auth/ctrlm_auth.h b/src/auth/ctrlm_auth.h index 0964335f..bda34998 100644 --- a/src/auth/ctrlm_auth.h +++ b/src/auth/ctrlm_auth.h @@ -34,7 +34,6 @@ class ctrlm_auth_t { virtual bool get_device_id(std::string &device_id) = 0; virtual bool get_account_id(std::string &account_id) = 0; virtual bool get_partner_id(std::string &partner_id) = 0; - virtual bool get_experience(std::string &experience) = 0; virtual bool get_sat(std::string &sat, time_t &expiration) = 0; virtual bool supports_sat_expiration() const = 0; diff --git a/src/auth/ctrlm_auth_thunder.cpp b/src/auth/ctrlm_auth_thunder.cpp index 2f954df3..7e37f8b4 100644 --- a/src/auth/ctrlm_auth_thunder.cpp +++ b/src/auth/ctrlm_auth_thunder.cpp @@ -48,11 +48,6 @@ bool ctrlm_auth_thunder_t::get_partner_id(std::string &partner_id) { return(ret); } -bool ctrlm_auth_thunder_t::get_experience(std::string &experience) { - bool ret = this->plugin->get_experience(experience); - return(ret); -} - bool ctrlm_auth_thunder_t::get_sat(std::string &sat, time_t &expiration) { bool ret = this->plugin->get_sat(sat, expiration); return(ret); diff --git a/src/auth/ctrlm_auth_thunder.h b/src/auth/ctrlm_auth_thunder.h index 3a476e67..67a385db 100644 --- a/src/auth/ctrlm_auth_thunder.h +++ b/src/auth/ctrlm_auth_thunder.h @@ -13,7 +13,6 @@ class ctrlm_auth_thunder_t : public ctrlm_auth_t { virtual bool get_device_id(std::string &device_id); virtual bool get_account_id(std::string &account_id); virtual bool get_partner_id(std::string &partner_id); - virtual bool get_experience(std::string &experience); virtual bool get_sat(std::string &sat, time_t &expiration); virtual bool supports_sat_expiration() const; diff --git a/src/auth/ctrlm_thunder_plugin_authservice.cpp b/src/auth/ctrlm_thunder_plugin_authservice.cpp index 60c57d7a..e5913d07 100644 --- a/src/auth/ctrlm_thunder_plugin_authservice.cpp +++ b/src/auth/ctrlm_thunder_plugin_authservice.cpp @@ -125,24 +125,6 @@ bool ctrlm_thunder_plugin_authservice_t::get_account_id(std::string &account_id) return(ret); } -bool ctrlm_thunder_plugin_authservice_t::get_experience(std::string &experience) { - bool ret = false; - JsonObject params, response; - if(this->call_plugin("getExperience", (void *)¶ms, (void *)&response)) { - if(response["success"].Boolean()) { // If success doesn't exist, it defaults to false which is fine. - experience = response["experience"].String(); - if(!experience.empty()) { - ret = true; - } - } else { - XLOGD_WARN("Success for getExperience was false"); - } - } else { - XLOGD_WARN("Call for getExperience failed"); - } - return(ret); -} - bool ctrlm_thunder_plugin_authservice_t::get_sat(std::string &sat, time_t &expiration) { bool ret = false; JsonObject params, response; diff --git a/src/auth/ctrlm_thunder_plugin_authservice.h b/src/auth/ctrlm_thunder_plugin_authservice.h index c39de41d..169cee88 100644 --- a/src/auth/ctrlm_thunder_plugin_authservice.h +++ b/src/auth/ctrlm_thunder_plugin_authservice.h @@ -75,13 +75,6 @@ class ctrlm_thunder_plugin_authservice_t : public Thunder::Plugin::ctrlm_thunder */ bool get_account_id(std::string &account_id); - /** - * Function that retrieves the Experience String from Authservice. - * @param experience The reference to a string which will contain the Experience String. - * @return True on success otherwise False. - */ - bool get_experience(std::string &experience); - /** * Function that retrieves the SAT Token from Authservice. * @param sat The reference to a string which will contain the SAT Token. diff --git a/src/ctrlm.h b/src/ctrlm.h index 905803d1..376608b3 100644 --- a/src/ctrlm.h +++ b/src/ctrlm.h @@ -458,7 +458,6 @@ gboolean ctrlm_main_has_device_id_get(void); gboolean ctrlm_main_has_device_type_get(void); gboolean ctrlm_main_has_service_account_id_get(void); gboolean ctrlm_main_has_partner_id_get(void); -gboolean ctrlm_main_has_experience_get(void); gboolean ctrlm_main_needs_service_access_token_get(void); void ctrlm_main_invalidate_service_access_token(void); void ctrlm_main_sat_enabled_set(gboolean sat_enabled); diff --git a/src/ctrlm_controller.cpp b/src/ctrlm_controller.cpp index 15496dd3..60173d47 100644 --- a/src/ctrlm_controller.cpp +++ b/src/ctrlm_controller.cpp @@ -118,10 +118,6 @@ string ctrlm_obj_controller_t::partner_id_get() const { return(obj_network_->partner_id_get()); } -string ctrlm_obj_controller_t::experience_get() const { - return(obj_network_->experience_get()); -} - string ctrlm_obj_controller_t::stb_name_get() const { return(obj_network_->stb_name_get()); } diff --git a/src/ctrlm_controller.h b/src/ctrlm_controller.h index 66749062..ae9b800b 100644 --- a/src/ctrlm_controller.h +++ b/src/ctrlm_controller.h @@ -63,7 +63,6 @@ class ctrlm_obj_controller_t std::string device_id_get() const; std::string service_account_id_get() const; std::string partner_id_get() const; - std::string experience_get() const; std::string stb_name_get() const; void set_device_minor_id(int device_minor_id); int get_device_minor_id() const; diff --git a/src/ctrlm_main.cpp b/src/ctrlm_main.cpp index f76c3682..c3da8d6d 100644 --- a/src/ctrlm_main.cpp +++ b/src/ctrlm_main.cpp @@ -302,10 +302,6 @@ static void ctrlm_main_has_service_account_id_set(gboolean has_id); static gboolean ctrlm_load_partner_id(void); static void ctrlm_main_has_partner_id_set(gboolean has_id); #endif -#ifdef AUTH_EXPERIENCE -static gboolean ctrlm_load_experience(void); -static void ctrlm_main_has_experience_set(gboolean has_experience); -#endif #ifdef AUTH_SAT_TOKEN static gboolean ctrlm_load_service_access_token(void); static void ctrlm_main_has_service_access_token_set(gboolean has_token); @@ -1421,30 +1417,6 @@ gboolean ctrlm_load_partner_id(void) { } #endif -#ifdef AUTH_EXPERIENCE -gboolean ctrlm_main_has_experience_get(void) { - return(g_ctrlm.has_experience); -} - -void ctrlm_main_has_experience_set(gboolean has_experience) { - g_ctrlm.has_experience = has_experience; -} - -gboolean ctrlm_load_experience(void) { - if(!g_ctrlm.authservice->get_experience(g_ctrlm.experience)) { - ctrlm_main_has_experience_set(false); - return(false); - } - g_ctrlm.voice_session->voice_stb_data_experience_set(g_ctrlm.experience); - - for(auto const &itr : g_ctrlm.networks) { - itr.second->experience_set(g_ctrlm.experience); - } - ctrlm_main_has_experience_set(true); - return(true); -} -#endif - #ifdef AUTH_SAT_TOKEN gboolean ctrlm_main_needs_service_access_token_get(void) { gboolean ret = false; @@ -1510,12 +1482,6 @@ gboolean ctrlm_has_authservice_data(void) { } #endif -#ifdef AUTH_EXPERIENCE - if(!ctrlm_main_has_experience_get()) { - ret = FALSE; - } -#endif - #ifdef AUTH_SAT_TOKEN if(ctrlm_main_needs_service_access_token_get()) { ret = FALSE; @@ -1566,18 +1532,6 @@ gboolean ctrlm_load_authservice_data(void) { } #endif -#ifdef AUTH_EXPERIENCE - if(!ctrlm_main_has_experience_get()) { - XLOGD_INFO("load experience"); - if(!ctrlm_load_experience()) { - XLOGD_TELEMETRY("failed to load experience"); - ret = FALSE; - } else { - XLOGD_INFO("load experience successfully <%s>", ctrlm_is_pii_mask_enabled() ? "***" : g_ctrlm.experience.c_str()); - } - } -#endif - #ifdef AUTH_SAT_TOKEN if(ctrlm_main_needs_service_access_token_get()) { XLOGD_INFO("load sat token"); diff --git a/src/ctrlm_network.cpp b/src/ctrlm_network.cpp index e8f591c3..26201231 100644 --- a/src/ctrlm_network.cpp +++ b/src/ctrlm_network.cpp @@ -187,16 +187,6 @@ string ctrlm_obj_network_t::partner_id_get() const { return(partner_id_); } -void ctrlm_obj_network_t::experience_set(const string& experience) { - THREAD_ID_VALIDATE(); - experience_ = experience; -} - -string ctrlm_obj_network_t::experience_get() const { - THREAD_ID_VALIDATE(); - return(experience_); -} - void ctrlm_obj_network_t::stb_name_set(const string& stb_name) { THREAD_ID_VALIDATE(); XLOGD_INFO("STB Name <%s>", stb_name.c_str()); diff --git a/src/ctrlm_network.h b/src/ctrlm_network.h index 90ea909f..ca6ad547 100644 --- a/src/ctrlm_network.h +++ b/src/ctrlm_network.h @@ -192,8 +192,6 @@ class ctrlm_obj_network_t std::string service_account_id_get() const; void partner_id_set(const std::string& partner_id); std::string partner_id_get() const; - void experience_set(const std::string& experience); - std::string experience_get() const; void mask_key_codes_set(gboolean mask_key_codes); gboolean mask_key_codes_get() const; void stb_name_set(const std::string& stb_name); @@ -325,7 +323,6 @@ class ctrlm_obj_network_t std::string device_id_; std::string service_account_id_; std::string partner_id_; - std::string experience_; std::string stb_name_; ctrlm_rcu_validation_result_t validation_result_ = CTRLM_RCU_VALIDATION_RESULT_MAX; ctrlm_key_code_t validation_key_ = CTRLM_KEY_CODE_INVALID; diff --git a/src/thunder/ctrlm_thunder_plugin.cpp b/src/thunder/ctrlm_thunder_plugin.cpp index 3040fae5..ca6afffc 100644 --- a/src/thunder/ctrlm_thunder_plugin.cpp +++ b/src/thunder/ctrlm_thunder_plugin.cpp @@ -261,6 +261,53 @@ bool ctrlm_thunder_plugin_t::call_plugin(std::string method, void *params, void return(ret); } +bool ctrlm_thunder_plugin_t::call_plugin_boolean(std::string method, void *params, bool *response) { + bool ret = false; + auto clientObject = (JSONRPC::LinkType*)this->plugin_client; + JsonObject *jsonParams = (JsonObject *)params; + if(clientObject) { + if(!method.empty() && jsonParams && response) { + Core::JSON::Boolean jsonResponse; + uint32_t thunderRet = clientObject->Invoke(CALL_TIMEOUT, _T(method), *jsonParams, jsonResponse); + if(thunderRet != Core::ERROR_NONE) { + XLOGD_ERROR("Thunder call failed <%s> <%u>", method.c_str(), thunderRet); + } else { + *response = jsonResponse.Value(); + ret = true; + } + } else { + XLOGD_ERROR("Invalid parameters"); + } + } else { + XLOGD_ERROR("Client is NULL"); + } + return(ret); +} + +bool ctrlm_thunder_plugin_t::call_plugin_string(std::string method, void *params, std::string *response) { + bool ret = false; + auto clientObject = (JSONRPC::LinkType*)this->plugin_client; + JsonObject *jsonParams = (JsonObject *)params; + if(clientObject) { + if(!method.empty() && jsonParams && response) { + Core::JSON::String jsonString; + uint32_t thunderRet = clientObject->Invoke(CALL_TIMEOUT, _T(method), *jsonParams, jsonString); + if(thunderRet != Core::ERROR_NONE) { + XLOGD_ERROR("Thunder call failed <%s> <%u>", method.c_str(), thunderRet); + } else { + *response = jsonString.Value(); + ret = true; + } + } else { + XLOGD_ERROR("Invalid parameters"); + } + } else { + XLOGD_ERROR("Client is NULL"); + } + return(ret); +} + + bool ctrlm_thunder_plugin_t::call_controller(std::string method, void *params, void *response) { bool ret = false; if(this->controller) { diff --git a/src/thunder/ctrlm_thunder_plugin.h b/src/thunder/ctrlm_thunder_plugin.h index f1a3944a..9bf6d780 100644 --- a/src/thunder/ctrlm_thunder_plugin.h +++ b/src/thunder/ctrlm_thunder_plugin.h @@ -105,9 +105,8 @@ class ctrlm_thunder_plugin_t { std::string callsign_with_api(); /** - * This functions is used to get a Thunder Plugin property. - * @param method The method in which the user wants to call. - * @param params The WPEFramework JsonObject containing the parameters for the call. (We can't include WPEFramework headers in controlMgr .h files as their logging macros clash) + * This function is used to get a Thunder Plugin property. + * @param property The name of the property that the user wants to get * @param response The WPEFramework JsonObject containing the response from the call. (We can't include WPEFramework headers in controlMgr .h files as their logging macros clash) * @param retries The number of retries if the call times out. * @return True if the call succeeded, otherwise False. @@ -115,7 +114,7 @@ class ctrlm_thunder_plugin_t { bool property_get(std::string property, void *response, unsigned int retries = 0); /** - * This functions is used to call a Thunder Plugin method. + * This function is used to call a Thunder Plugin method. * @param method The method in which the user wants to call. * @param params The WPEFramework JsonObject containing the parameters for the call. (We can't include WPEFramework headers in controlMgr .h files as their logging macros clash) * @param response The WPEFramework JsonObject containing the response from the call. (We can't include WPEFramework headers in controlMgr .h files as their logging macros clash) @@ -125,7 +124,25 @@ class ctrlm_thunder_plugin_t { bool call_plugin(std::string method, void *params, void *response, unsigned int retries = 0); /** - * This functions is used to call a Thunder Controller method. + * This function is used to call a Thunder Plugin method. + * @param method The method in which the user wants to call. + * @param params The WPEFramework JsonObject containing the parameters for the call. (We can't include WPEFramework headers in controlMgr .h files as their logging macros clash) + * @param response The boolean pointer which will be assigned the response from the call + * @return True if the call succeeded, otherwise False. + */ + bool call_plugin_boolean(std::string method, void *params, bool *response); + + /** + * This function is used to call a Thunder Plugin method. + * @param method The method in which the user wants to call. + * @param params The WPEFramework JsonObject containing the parameters for the call. (We can't include WPEFramework headers in controlMgr .h files as their logging macros clash) + * @param response The string pointer which will be assigned containing the response from the call. + * @return True if the call succeeded, otherwise False. + */ + bool call_plugin_string(std::string method, void *params, std::string *response); + + /** + * This function is used to call a Thunder Controller method. * @param method The method in which the user wants to call. * @param params The WPEFramework JsonObject containing the parameters for the call. (We can't include WPEFramework headers in controlMgr .h files as their logging macros clash) * @param params The WPEFramework JsonObject containing the response from the call. (We can't include WPEFramework headers in controlMgr .h files as their logging macros clash) @@ -164,4 +181,4 @@ class ctrlm_thunder_plugin_t { }; }; -#endif \ No newline at end of file +#endif diff --git a/src/thunder/ctrlm_thunder_plugin_powermanager.cpp b/src/thunder/ctrlm_thunder_plugin_powermanager.cpp index 6e0c83b0..e84fa983 100755 --- a/src/thunder/ctrlm_thunder_plugin_powermanager.cpp +++ b/src/thunder/ctrlm_thunder_plugin_powermanager.cpp @@ -89,16 +89,15 @@ ctrlm_power_state_t ctrlm_thunder_plugin_powermanager_t::get_power_state() { /* root@pioneer-uhd:~# curl --request POST --url http://127.0.0.1:9998/jsonrpc --header 'Content-Type: application/json' --data '{ "jsonrpc": "2.0", "id": 1234567890, "method": "org.rdk.PowerManager.1.getNetworkStandbyMode", "params": {} }' {"jsonrpc":"2.0","id":1234567890,"result":true} */ bool ctrlm_thunder_plugin_powermanager_t::get_networked_standby_mode() { - JsonObject params, response; + JsonObject params; params = {}; bool networked_standby_mode = false; - sem_wait(&this->semaphore); - if(this->call_plugin("getNetworkStandbyMode", (void *)¶ms, (void *)&response)) { - networked_standby_mode = response["result"].Boolean(); + sem_wait(&this->semaphore); + if(this->call_plugin_boolean("getNetworkStandbyMode", (void *)¶ms, &networked_standby_mode)) { XLOGD_DEBUG("networked_standby_mode is %s", networked_standby_mode?"TRUE":"FALSE"); } else { - XLOGD_ERROR("getNetworkedStandbyMode call failed"); + XLOGD_ERROR("getNetworkStandbyMode call failed"); } sem_post(&this->semaphore); @@ -108,19 +107,23 @@ bool ctrlm_thunder_plugin_powermanager_t::get_networked_standby_mode() { /* root@pioneer-uhd:~# curl --request POST --url http://127.0.0.1:9998/jsonrpc --header 'Content-Type: application/json' --data '{ "jsonrpc": "2.0", "id": 1234567890, "method": "org.rdk.PowerManager.1.getLastWakeupReason", "params": {} }' {"jsonrpc":"2.0","id":1234567890,"result":"COLDBOOT"} */ bool ctrlm_thunder_plugin_powermanager_t::get_wakeup_reason_voice() { - JsonObject params, response; + JsonObject params; + std::string response; params = {}; bool wakeup_reason_voice = false; sem_wait(&this->semaphore); - if(this->call_plugin("getLastWakeupReason", (void *)¶ms, (void *)&response)) { - wakeup_reason_voice = (0 == strncmp(response["result"].String().c_str(), "VOICE", 5)); - XLOGD_DEBUG("voice_wakeup is %s", wakeup_reason_voice?"TRUE":"FALSE"); + if(this->call_plugin_string("getLastWakeupReason", (void *)¶ms, &response)) { + if(response == "VOICE") { + wakeup_reason_voice = true; + } } else { XLOGD_ERROR("getLastWakeupReason call failed"); } sem_post(&this->semaphore); + XLOGD_DEBUG("voice_wakeup is %s", wakeup_reason_voice?"TRUE":"FALSE"); + return wakeup_reason_voice; } diff --git a/src/voice/ctrlm_voice_obj.cpp b/src/voice/ctrlm_voice_obj.cpp index 6e4b354f..a6787b1e 100644 --- a/src/voice/ctrlm_voice_obj.cpp +++ b/src/voice/ctrlm_voice_obj.cpp @@ -2252,18 +2252,6 @@ std::string ctrlm_voice_t::voice_stb_data_partner_id_get() const { return(this->partner_id); } -void ctrlm_voice_t::voice_stb_data_experience_set(std::string &experience) { - XLOGD_DEBUG("Experience Tag set to %s", experience.c_str()); - this->experience = experience; - for(const auto &itr : this->endpoints) { - itr->voice_stb_data_experience_set(experience); - } -} - -std::string ctrlm_voice_t::voice_stb_data_experience_get() const { - return(this->experience); -} - std::string ctrlm_voice_t::voice_stb_data_app_id_http_get() const { return(this->prefs.app_id_http); } @@ -2427,12 +2415,6 @@ bool ctrlm_voice_t::voice_session_has_stb_data() { return(false); } #endif -#ifdef AUTH_EXPERIENCE - if(this->experience == "") { - XLOGD_INFO("No experience tag"); - return(false); - } -#endif #ifdef AUTH_SAT_TOKEN if(this->sat_token_required && this->sat_token[0] == '\0') { XLOGD_INFO("No SAT token"); diff --git a/src/voice/ctrlm_voice_obj.h b/src/voice/ctrlm_voice_obj.h index 38699a6b..3ec217b2 100644 --- a/src/voice/ctrlm_voice_obj.h +++ b/src/voice/ctrlm_voice_obj.h @@ -525,8 +525,6 @@ class ctrlm_voice_t { ctrlm_device_type_t voice_stb_data_device_type_get() const; virtual void voice_stb_data_partner_id_set(std::string &partner_id); std::string voice_stb_data_partner_id_get() const; - virtual void voice_stb_data_experience_set(std::string &experience); - std::string voice_stb_data_experience_get() const; std::string voice_stb_data_app_id_http_get() const; std::string voice_stb_data_app_id_ws_get() const; virtual void voice_stb_data_guide_language_set(const char *language); @@ -662,7 +660,6 @@ class ctrlm_voice_t { std::string device_id; ctrlm_device_type_t device_type; std::string partner_id; - std::string experience; char sat_token[XRSR_SAT_TOKEN_LEN_MAX]; bool sat_token_required; bool mtls_required; diff --git a/src/voice/endpoints/ctrlm_voice_endpoint.cpp b/src/voice/endpoints/ctrlm_voice_endpoint.cpp index 7815cd27..1aed8d46 100644 --- a/src/voice/endpoints/ctrlm_voice_endpoint.cpp +++ b/src/voice/endpoints/ctrlm_voice_endpoint.cpp @@ -63,7 +63,6 @@ void ctrlm_voice_endpoint_t::voice_stb_data_account_number_set(std::string &acco void ctrlm_voice_endpoint_t::voice_stb_data_device_id_set(std::string &device_id) {} void ctrlm_voice_endpoint_t::voice_stb_data_device_type_set(ctrlm_device_type_t device_type) {} void ctrlm_voice_endpoint_t::voice_stb_data_partner_id_set(std::string &partner_id) {} -void ctrlm_voice_endpoint_t::voice_stb_data_experience_set(std::string &experience) {} void ctrlm_voice_endpoint_t::voice_stb_data_guide_language_set(const char *language) {} void ctrlm_voice_endpoint_t::voice_stb_data_mask_pii_set(bool enable) {} diff --git a/src/voice/endpoints/ctrlm_voice_endpoint.h b/src/voice/endpoints/ctrlm_voice_endpoint.h index ac881880..3298348f 100644 --- a/src/voice/endpoints/ctrlm_voice_endpoint.h +++ b/src/voice/endpoints/ctrlm_voice_endpoint.h @@ -50,7 +50,6 @@ class ctrlm_voice_endpoint_t { virtual void voice_stb_data_device_id_set(std::string &device_id); virtual void voice_stb_data_device_type_set(ctrlm_device_type_t device_type); virtual void voice_stb_data_partner_id_set(std::string &partner_id); - virtual void voice_stb_data_experience_set(std::string &experience); virtual void voice_stb_data_guide_language_set(const char *language); virtual void voice_stb_data_mask_pii_set(bool enable); // End Data Setters diff --git a/src/voice/endpoints/ctrlm_voice_endpoint_http.cpp b/src/voice/endpoints/ctrlm_voice_endpoint_http.cpp index 1ec3e647..c119ee43 100644 --- a/src/voice/endpoints/ctrlm_voice_endpoint_http.cpp +++ b/src/voice/endpoints/ctrlm_voice_endpoint_http.cpp @@ -72,14 +72,12 @@ bool ctrlm_voice_endpoint_http_t::open() { std::string device_id = this->voice_obj->voice_stb_data_device_id_get(); std::string partner_id = this->voice_obj->voice_stb_data_partner_id_get(); - std::string experience = this->voice_obj->voice_stb_data_experience_get(); std::string app_id = this->voice_obj->voice_stb_data_app_id_http_get(); std::string language = this->voice_obj->voice_stb_data_guide_language_get().c_str(); xrsv_http_params_t params_http = { .device_id = device_id.c_str(), .partner_id = partner_id.c_str(), - .experience = experience.c_str(), .app_id = app_id.c_str(), .language = language.c_str(), .test_flag = this->voice_obj->voice_stb_data_test_get(), @@ -138,12 +136,6 @@ void ctrlm_voice_endpoint_http_t::voice_stb_data_partner_id_set(std::string &par } } -void ctrlm_voice_endpoint_http_t::voice_stb_data_experience_set(std::string &experience) { - if(this->xrsv_obj_http) { - xrsv_http_update_experience(this->xrsv_obj_http, experience.c_str()); - } -} - void ctrlm_voice_endpoint_http_t::voice_stb_data_guide_language_set(const char *language) { if(this->xrsv_obj_http) { xrsv_http_update_language(this->xrsv_obj_http, language); diff --git a/src/voice/endpoints/ctrlm_voice_endpoint_http.h b/src/voice/endpoints/ctrlm_voice_endpoint_http.h index 9338e20d..79a0d50d 100644 --- a/src/voice/endpoints/ctrlm_voice_endpoint_http.h +++ b/src/voice/endpoints/ctrlm_voice_endpoint_http.h @@ -32,7 +32,6 @@ class ctrlm_voice_endpoint_http_t : public ctrlm_voice_endpoint_t { public: void voice_stb_data_device_id_set(std::string &device_id); void voice_stb_data_partner_id_set(std::string &partner_id); - void voice_stb_data_experience_set(std::string &experience); void voice_stb_data_guide_language_set(const char *language); void voice_stb_data_pii_mask_set(bool enable); diff --git a/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp b/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp index e5165cd7..9abc27db 100644 --- a/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp +++ b/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp @@ -86,7 +86,6 @@ bool ctrlm_voice_endpoint_ws_nextgen_t::open() { } std::string device_id = this->voice_obj->voice_stb_data_device_id_get(); std::string partner_id = this->voice_obj->voice_stb_data_partner_id_get(); - std::string experience = this->voice_obj->voice_stb_data_experience_get(); std::string language = this->voice_obj->voice_stb_data_guide_language_get().c_str(); std::string account_number = this->voice_obj->voice_stb_data_account_number_get(); std::string device_mac = ctrlm_device_mac_get(); @@ -96,7 +95,6 @@ bool ctrlm_voice_endpoint_ws_nextgen_t::open() { .device_id = (device_id.empty() == false ? device_id.c_str() : NULL), .account_id = (account_number.empty() == false ? account_number.c_str() : NULL), .partner_id = (partner_id.empty() == false ? partner_id.c_str() : NULL), - .experience = (experience.empty() == false ? experience.c_str() : NULL), .audio_profile = controller_name_to_audio_profile(""), .audio_model = controller_name_to_audio_model(""), .language = language.c_str(), @@ -235,12 +233,6 @@ void ctrlm_voice_endpoint_ws_nextgen_t::voice_stb_data_partner_id_set(std::strin } } -void ctrlm_voice_endpoint_ws_nextgen_t::voice_stb_data_experience_set(std::string &experience) { - if(this->xrsv_obj_ws_nextgen) { - xrsv_ws_nextgen_update_experience(this->xrsv_obj_ws_nextgen, experience.c_str()); - } -} - void ctrlm_voice_endpoint_ws_nextgen_t::voice_stb_data_guide_language_set(const char *language) { if(this->xrsv_obj_ws_nextgen) { xrsv_ws_nextgen_update_language(this->xrsv_obj_ws_nextgen, language); diff --git a/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.h b/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.h index 16667f2f..218e1d39 100644 --- a/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.h +++ b/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.h @@ -37,7 +37,6 @@ class ctrlm_voice_endpoint_ws_nextgen_t : public ctrlm_voice_endpoint_t { void voice_stb_data_device_id_set(std::string &device_id); void voice_stb_data_device_type_set(ctrlm_device_type_t device_type); void voice_stb_data_partner_id_set(std::string &partner_id); - void voice_stb_data_experience_set(std::string &experience); void voice_stb_data_guide_language_set(const char *language); void voice_stb_data_mask_pii_set(bool enable); From 3a5102af5479a6143d893b8fdd317036d8ff651e Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 10 Jun 2026 17:13:10 +0000 Subject: [PATCH 02/86] Adding uuids for mfv voice service --- src/ble/hal/utils/bleuuid.cpp | 7 +++++++ src/ble/hal/utils/bleuuid.h | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/ble/hal/utils/bleuuid.cpp b/src/ble/hal/utils/bleuuid.cpp index 62d4294c..a785ed8b 100644 --- a/src/ble/hal/utils/bleuuid.cpp +++ b/src/ble/hal/utils/bleuuid.cpp @@ -113,6 +113,13 @@ bool ble_uuid_names_init(void) BleUuid(BleUuid::PeripheralPreferredConnectionParameters, std::string("Peripheral Preferred Connection Parameters")); BleUuid(BleUuid::ClientCharacteristicConfiguration, std::string("Client Characteristic Configuration")); BleUuid(BleUuid::ReportReference, std::string("Report Reference")); + BleUuid(BleUuid::MfvVoice, std::string("MFV Voice")); + BleUuid(BleUuid::SessionStart, std::string("Session Start")); + BleUuid(BleUuid::DetectionData, std::string("Detection Data")); + BleUuid(BleUuid::WakeWordModelVersion, std::string("Wake Word Model Version")); + BleUuid(BleUuid::PrivacySettings, std::string("Privacy Settings")); + BleUuid(BleUuid::ModelConfiguration, std::string("Model Configuration")); + BleUuid(BleUuid::MfvCapabilities, std::string("MFV Capabilities")); #ifdef CTRLM_BLE_SERVICES ctrlm_ble_uuid_names_install(); #endif diff --git a/src/ble/hal/utils/bleuuid.h b/src/ble/hal/utils/bleuuid.h index 7b9df4f2..583ccaec 100644 --- a/src/ble/hal/utils/bleuuid.h +++ b/src/ble/hal/utils/bleuuid.h @@ -53,6 +53,7 @@ class BleUuid RdkInfrared = 0xf801, RdkFirmwareUpgrade = 0xf802, RdkRemoteControl = 0xf803, + MfvVoice = 0xEA08, }; @@ -105,6 +106,12 @@ class BleUuid AdvertisingConfigCustomList = 0xed06, AssertReport = 0xed07, RawBatteryVoltage = 0xed08, + SessionStart = 0xEA08, + DetectionData = 0xEA09, + WakeWordModelVersion = 0xEA0A, + PrivacySettings = 0xEA0B, + ModelConfiguration = 0xEA0C, + MfvCapabilities = 0xEA0D, }; enum DescriptorType { From e2de84cb160253774129874829d7e26abf993dd9 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 10 Jun 2026 17:27:21 +0000 Subject: [PATCH 03/86] Laying down a skeleton --- .../bleservices/blercumfvvoiceservice.h | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/ble/hal/blercu/bleservices/blercumfvvoiceservice.h diff --git a/src/ble/hal/blercu/bleservices/blercumfvvoiceservice.h b/src/ble/hal/blercu/bleservices/blercumfvvoiceservice.h new file mode 100644 index 00000000..85abde7f --- /dev/null +++ b/src/ble/hal/blercu/bleservices/blercumfvvoiceservice.h @@ -0,0 +1,108 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2017-2020 Sky UK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Changes made by Comcast + * Copyright 2024 Comcast Cable Communications Management, LLC + * Licensed under the Apache License, Version 2.0 + */ + +// +// blercumfvvoiceservice.h +// + +#ifndef BLERCUMFVVOICESERVICE_H +#define BLERCUMFVVOICESERVICE_H + +#include "utils/pendingreply.h" +#include "utils/slot.h" + +#include +#include + + +class BleRcuMfvVoiceService +{ + +protected: + BleRcuMfvVoiceService() = default; + +public: + virtual ~BleRcuMfvVoiceService() = default; + +public: + enum DetectionType { + FullPower=0x01, + Aad=0x02, + BelowThreshold=0x03, + } + + struct DetectionData { + uint16_t start; + uint16_t end; + uint16_t confidence; + }; + + struct ModelVersion { + uint8_t major; + uint8_t minor; + uint8_t patch; + }; + + void writePrivacy(bool enabled, PendingReply<> &&reply) {} + void writeModelConfiguration(uint8_t sensitivity, uint8_t secondary, uint8_t aad, PendingReply<> &&reply) {} + +// signals: + inline void addDetectionTypeChangedSlot(const Slot &func) + { + m_detectionTypeChangedSlots.addSlot(func); + } + inline void addDetectionDataChangedSlot(const Slot &func) + { + m_detectionDataChangedSlots.addSlot(func); + } + inline void addPrivacyChangedSlot(const Slot &func) + { + m_privacyChangedSlots.addSlot(func); + } + inline void addCapabilitiesChangedSlot(const Slot &func) + { + m_capabilitiesChangedSlots.addSlot(func); + } + inline void addStreamStatsChangedSlot(const Slot &func) + { + m_streamStatsChangedSlots.addSlot(func); + } + inline void addReadySlot(const Slot &func) + { + m_readySlots.addSlot(func); + } + +protected: + Slots m_detectionTypeChangedSlots; + Slots m_detectionDataChangedSlots; + Slots m_privacyChangedSlots; + Slots m_capabilitiesChangedSlots; + Slots m_streamStatsChangedSlots; + Slots m_readySlots; + +}; + + +#endif // BLERCUMFVVOICESERVICE_H From e4fcb55fef25222bbaf9f79b04ec28a171ae6e99 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 16 Jun 2026 16:28:28 +0000 Subject: [PATCH 04/86] Initial skeleton for mfv --- src/CMakeLists.txt | 1 + .../bleservices/blercumfvvoiceservice.h | 55 ++++--- .../hal/blercu/bleservices/blercuservices.h | 2 + .../bleservices/gatt/gatt_mfvvoiceservice.cpp | 140 ++++++++++++++++++ .../bleservices/gatt/gatt_mfvvoiceservice.h | 67 +++++++++ .../blercu/bleservices/gatt/gatt_services.cpp | 21 ++- .../blercu/bleservices/gatt/gatt_services.h | 7 +- 7 files changed, 273 insertions(+), 20 deletions(-) create mode 100644 src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp create mode 100644 src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ab20447c..139fa4f1 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -104,6 +104,7 @@ if(BLE_ENABLED) ble/hal/blercu/bleservices/gatt/gatt_findmeservice.cpp ble/hal/blercu/bleservices/gatt/gatt_infraredservice.cpp ble/hal/blercu/bleservices/gatt/gatt_infraredsignal.cpp + ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp ble/hal/blercu/bleservices/gatt/gatt_remotecontrolservice.cpp ble/hal/blercu/bleservices/gatt/gatt_services.cpp ble/hal/blercu/bleservices/gatt/gatt_upgradeservice.cpp diff --git a/src/ble/hal/blercu/bleservices/blercumfvvoiceservice.h b/src/ble/hal/blercu/bleservices/blercumfvvoiceservice.h index 85abde7f..85c4b93b 100644 --- a/src/ble/hal/blercu/bleservices/blercumfvvoiceservice.h +++ b/src/ble/hal/blercu/bleservices/blercumfvvoiceservice.h @@ -33,8 +33,8 @@ #include "utils/pendingreply.h" #include "utils/slot.h" +#include #include -#include class BleRcuMfvVoiceService @@ -51,56 +51,75 @@ class BleRcuMfvVoiceService FullPower=0x01, Aad=0x02, BelowThreshold=0x03, - } + }; struct DetectionData { uint16_t start; uint16_t end; - uint16_t confidence; + uint16_t confidence_x10; }; struct ModelVersion { uint8_t major; uint8_t minor; - uint8_t patch; }; - void writePrivacy(bool enabled, PendingReply<> &&reply) {} - void writeModelConfiguration(uint8_t sensitivity, uint8_t secondary, uint8_t aad, PendingReply<> &&reply) {} + struct StreamStatsRaw { + std::vector bytes; + }; + + enum Capabilities { + MidfieldVoiceCapable = (1 << 0), + SoftwarePrivacyControl = (1 << 1), + SowwEowwTimingAvailable = (1 << 2), + AadSensitivityControlAvailable = (1 << 3) + }; + +public: + virtual DetectionType detectionType() const = 0; + virtual DetectionData detectionData() const = 0; + virtual ModelVersion wakeWordModelVersion() const = 0; + virtual bool privacyEnabled() const = 0; + virtual std::vector modelConfiguration() const = 0; + virtual uint8_t capabilities() const = 0; + virtual StreamStatsRaw streamStats() const = 0; + + virtual void writePrivacy(bool enabled, PendingReply<> &&reply) = 0; + virtual void writeModelConfiguration(uint8_t sensitivity, uint8_t secondary, uint8_t aad, PendingReply<> &&reply) = 0; // signals: - inline void addDetectionTypeChangedSlot(const Slot &func) + inline void addDetectionTypeChangedSlot(const Slot &func) { m_detectionTypeChangedSlots.addSlot(func); } - inline void addDetectionDataChangedSlot(const Slot &func) + inline void addDetectionDataChangedSlot(const Slot &func) { m_detectionDataChangedSlots.addSlot(func); } - inline void addPrivacyChangedSlot(const Slot &func) + inline void addPrivacyChangedSlot(const Slot &func) { m_privacyChangedSlots.addSlot(func); } - inline void addCapabilitiesChangedSlot(const Slot &func) + inline void addCapabilitiesChangedSlot(const Slot &func) { m_capabilitiesChangedSlots.addSlot(func); } - inline void addStreamStatsChangedSlot(const Slot &func) + inline void addStreamStatsChangedSlot(const Slot &func) { m_streamStatsChangedSlots.addSlot(func); } - inline void addReadySlot(const Slot &func) + inline void addReadySlot(const Slot<> &func) { m_readySlots.addSlot(func); } protected: - Slots m_detectionTypeChangedSlots; - Slots m_detectionDataChangedSlots; - Slots m_privacyChangedSlots; - Slots m_capabilitiesChangedSlots; - Slots m_streamStatsChangedSlots; - Slots m_readySlots; + Slots m_detectionTypeChangedSlots; + Slots m_detectionDataChangedSlots; + Slots m_privacyChangedSlots; + Slots m_capabilitiesChangedSlots; + Slots m_streamStatsChangedSlots; + Slots<> m_readySlots; }; diff --git a/src/ble/hal/blercu/bleservices/blercuservices.h b/src/ble/hal/blercu/bleservices/blercuservices.h index 4a7cdd2c..2c4bc8ba 100644 --- a/src/ble/hal/blercu/bleservices/blercuservices.h +++ b/src/ble/hal/blercu/bleservices/blercuservices.h @@ -43,6 +43,7 @@ class BleRcuFindMeService; class BleRcuInfraredService; class BleRcuUpgradeService; class BleRcuRemoteControlService; +class BleRcuMfvVoiceService; class BleRcuServices @@ -69,6 +70,7 @@ class BleRcuServices virtual std::shared_ptr infraredService() const = 0; virtual std::shared_ptr upgradeService() const = 0; virtual std::shared_ptr remoteControlService() const = 0; + virtual std::shared_ptr mfvVoiceService() const = 0; inline void addReadySlot(const Slot<> &func) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp new file mode 100644 index 00000000..8bcdb5c0 --- /dev/null +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp @@ -0,0 +1,140 @@ +#include "gatt_mfvvoiceservice.h" + +#include "blercu/blegattservice.h" +#include "blercu/blegattcharacteristic.h" + +#include "ctrlm_log_ble.h" + +using namespace std; + +const BleUuid GattMfvVoiceService::m_serviceUuid("0000ea08-bdf0-407c-aaff-d09967f31acd", "MFV Voice", true); +const BleUuid GattMfvVoiceService::m_sessionStartCharUuid("0000ea08-bdf0-407c-aaff-d09967f31acd", "Session Start"); +const BleUuid GattMfvVoiceService::m_detectionDataCharUuid("0000ea09-bdf0-407c-aaff-d09967f31acd", "Detection Data"); +const BleUuid GattMfvVoiceService::m_modelVersionCharUuid("0000ea0a-bdf0-407c-aaff-d09967f31acd", "Wake Word Model Version"); +const BleUuid GattMfvVoiceService::m_privacyCharUuid("0000ea0b-bdf0-407c-aaff-d09967f31acd", "Privacy Settings"); +const BleUuid GattMfvVoiceService::m_modelConfigCharUuid("0000ea0c-bdf0-407c-aaff-d09967f31acd", "Model Configuration"); +const BleUuid GattMfvVoiceService::m_capabilitiesCharUuid("0000ea0d-bdf0-407c-aaff-d09967f31acd", "MFV Capabilities"); + +GattMfvVoiceService::GattMfvVoiceService(GMainLoop *mainLoop) + : m_ready(false) + , m_detectionType(FullPower) + , m_detectionData({ 0, 0, 0 }) + , m_modelVersion({ 0, 0 }) + , m_privacyEnabled(false) + , m_capabilities(0) +{ + (void)mainLoop; +} + +GattMfvVoiceService::~GattMfvVoiceService() +{ + stop(); +} + +BleUuid GattMfvVoiceService::uuid() +{ + return m_serviceUuid; +} + +bool GattMfvVoiceService::isReady() const +{ + return m_ready; +} + +BleRcuMfvVoiceService::DetectionType GattMfvVoiceService::detectionType() const +{ + return m_detectionType; +} + +BleRcuMfvVoiceService::DetectionData GattMfvVoiceService::detectionData() const +{ + return m_detectionData; +} + +BleRcuMfvVoiceService::ModelVersion GattMfvVoiceService::wakeWordModelVersion() const +{ + return m_modelVersion; +} + +bool GattMfvVoiceService::privacyEnabled() const +{ + return m_privacyEnabled; +} + +vector GattMfvVoiceService::modelConfiguration() const +{ + return m_modelConfiguration; +} + +uint8_t GattMfvVoiceService::capabilities() const +{ + return m_capabilities; +} + +BleRcuMfvVoiceService::StreamStatsRaw GattMfvVoiceService::streamStats() const +{ + return m_streamStats; +} + +bool GattMfvVoiceService::start(const shared_ptr &gattService) +{ + if (m_ready) { + XLOGD_WARN("MFV voice service already started"); + return true; + } + + if (!gattService || !gattService->isValid() || (gattService->uuid() != m_serviceUuid)) { + XLOGD_WARN("invalid MFV voice gatt service info"); + return false; + } + + m_sessionStartCharacteristic = gattService->characteristic(m_sessionStartCharUuid); + m_detectionDataCharacteristic = gattService->characteristic(m_detectionDataCharUuid); + m_modelVersionCharacteristic = gattService->characteristic(m_modelVersionCharUuid); + m_privacyCharacteristic = gattService->characteristic(m_privacyCharUuid); + m_modelConfigCharacteristic = gattService->characteristic(m_modelConfigCharUuid); + m_capabilitiesCharacteristic = gattService->characteristic(m_capabilitiesCharUuid); + + if (!m_sessionStartCharacteristic || !m_sessionStartCharacteristic->isValid() || + !m_detectionDataCharacteristic || !m_detectionDataCharacteristic->isValid() || + !m_modelVersionCharacteristic || !m_modelVersionCharacteristic->isValid() || + !m_privacyCharacteristic || !m_privacyCharacteristic->isValid() || + !m_modelConfigCharacteristic || !m_modelConfigCharacteristic->isValid() || + !m_capabilitiesCharacteristic || !m_capabilitiesCharacteristic->isValid()) { + XLOGD_ERROR("failed to discover one or more MFV characteristics"); + stop(); + return false; + } + + m_ready = true; + m_readySlots.invoke(); + return true; +} + +void GattMfvVoiceService::stop() +{ + m_ready = false; + m_sessionStartCharacteristic.reset(); + m_detectionDataCharacteristic.reset(); + m_modelVersionCharacteristic.reset(); + m_privacyCharacteristic.reset(); + m_modelConfigCharacteristic.reset(); + m_capabilitiesCharacteristic.reset(); +} + +void GattMfvVoiceService::writePrivacy(bool enabled, PendingReply<> &&reply) +{ + (void)enabled; + reply.setError("MFV privacy write not implemented"); + reply.finish(); +} + +void GattMfvVoiceService::writeModelConfiguration(uint8_t sensitivity, uint8_t secondary, uint8_t aad, PendingReply<> &&reply) +{ + (void)sensitivity; + (void)secondary; + (void)aad; + reply.setError("MFV model configuration write not implemented"); + reply.finish(); + +} \ No newline at end of file diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h b/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h new file mode 100644 index 00000000..3d8658ad --- /dev/null +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h @@ -0,0 +1,67 @@ +#ifndef GATT_MFVVOICESERVICE_H +#define GATT_MFVVOICESERVICE_H + +#include "blercu/bleservices/blercumfvvoiceservice.h" +#include "utils/bleuuid.h" + +#include + +class BleGattService; +class BleGattCharacteristic; + +class GattMfvVoiceService : public BleRcuMfvVoiceService +{ +public: + GattMfvVoiceService(GMainLoop *mainLoop = NULL); + ~GattMfvVoiceService() final; + +public: + static BleUuid uuid(); + +public: + bool isReady() const; + + DetectionType detectionType() const override; + DetectionData detectionData() const override; + ModelVersion wakeWordModelVersion() const override; + bool privacyEnabled() const override; + std::vector modelConfiguration() const override; + uint8_t capabilities() const override; + StreamStatsRaw streamStats() const override; + + void writePrivacy(bool enabled, PendingReply<> &&reply) override; + void writeModelConfiguration(uint8_t sensitivity, uint8_t secondary, uint8_t aad, PendingReply<> &&reply) override; + +public: + bool start(const std::shared_ptr &gattService); + void stop(); + +private: + bool m_ready; + + DetectionType m_detectionType; + DetectionData m_detectionData; + ModelVersion m_modelVersion; + bool m_privacyEnabled; + std::vector m_modelConfiguration; + uint8_t m_capabilities; + StreamStatsRaw m_streamStats; + + std::shared_ptr m_sessionStartCharacteristic; + std::shared_ptr m_detectionDataCharacteristic; + std::shared_ptr m_modelVersionCharacteristic; + std::shared_ptr m_privacyCharacteristic; + std::shared_ptr m_modelConfigCharacteristic; + std::shared_ptr m_capabilitiesCharacteristic; + +private: + static const BleUuid m_serviceUuid; + static const BleUuid m_sessionStartCharUuid; + static const BleUuid m_detectionDataCharUuid; + static const BleUuid m_modelVersionCharUuid; + static const BleUuid m_privacyCharUuid; + static const BleUuid m_modelConfigCharUuid; + static const BleUuid m_capabilitiesCharUuid; +}; + +#endif // !defined(GATT_MFVVOICESERVICE_H) \ No newline at end of file diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_services.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_services.cpp index 6029daca..d029eba0 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_services.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_services.cpp @@ -34,6 +34,7 @@ #include "gatt_deviceinfoservice.h" #include "gatt_findmeservice.h" #include "gatt_infraredservice.h" +#include "gatt_mfvvoiceservice.h" #include "gatt_upgradeservice.h" #include "gatt_remotecontrolservice.h" @@ -69,6 +70,7 @@ GattServices::GattServices(const BleAddress &address, , m_batteryService(make_shared(mainLoop)) , m_findMeService(make_shared(mainLoop)) , m_remoteControlService(make_shared(mainLoop)) + , m_mfvVoiceService(make_shared(mainLoop)) { // Add the built-in services to the list of services available @@ -126,6 +128,7 @@ void GattServices::init() m_stateMachine.addState(ResolvedServicesSuperState, StartingInfraredServiceState, string("StartingInfraredService")); m_stateMachine.addState(ResolvedServicesSuperState, StartingUpgradeServiceState, string("StartingUpgradeServiceState")); m_stateMachine.addState(ResolvedServicesSuperState, StartingRemoteControlServiceState, string("StartingRemoteControlServiceState")); + m_stateMachine.addState(ResolvedServicesSuperState, StartingMfvVoiceServiceState, string("StartingMfvVoiceServiceState")); m_stateMachine.addState(ResolvedServicesSuperState, ReadyState, string("Ready")); m_stateMachine.addState(StoppingState, string("Stopping")); @@ -142,7 +145,8 @@ void GattServices::init() // Need to start RemoteControl service first so that we read the last keypress characterisitic as soon as possible m_stateMachine.addTransition(GettingGattServicesState, GotGattServicesEvent, StartingRemoteControlServiceState); - m_stateMachine.addTransition(StartingRemoteControlServiceState, RemoteControlServiceReadyEvent, StartingDeviceInfoServiceState); + m_stateMachine.addTransition(StartingRemoteControlServiceState, RemoteControlServiceReadyEvent, StartingMfvVoiceServiceState); + m_stateMachine.addTransition(StartingMfvVoiceServiceState, MfvVoiceServiceReadyEvent, StartingDeviceInfoServiceState); m_stateMachine.addTransition(StartingDeviceInfoServiceState, DeviceInfoServiceReadyEvent, StartingBatteryServiceState); m_stateMachine.addTransition(StartingBatteryServiceState, BatteryServiceReadyEvent, StartingFindMeServiceState); @@ -270,12 +274,17 @@ void GattServices::onEnteredState(int state) serviceOk = startService(m_remoteControlService, RemoteControlServiceReadyEvent, serviceNotUsed); break; + case StartingMfvVoiceServiceState: + serviceOk = startService(m_mfvVoiceService, MfvVoiceServiceReadyEvent, serviceNotUsed); + break; + case ReadyState: m_readySlots.invoke(); break; case StoppingState: m_remoteControlService->stop(); + m_mfvVoiceService->stop(); m_deviceInfoService->stop(); m_batteryService->stop(); m_findMeService->stop(); @@ -514,3 +523,13 @@ shared_ptr GattServices::remoteControlService() cons { return m_remoteControlService; } + +// ----------------------------------------------------------------------------- +/*! + \overload + + */ +shared_ptr GattServices::mfvVoiceService() const +{ + return m_mfvVoiceService; +} diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_services.h b/src/ble/hal/blercu/bleservices/gatt/gatt_services.h index d1bd3b54..d2b0ec9c 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_services.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_services.h @@ -46,6 +46,7 @@ class GattFindMeService; class GattInfraredService; class GattUpgradeService; class GattRemoteControlService; +class GattMfvVoiceService; @@ -72,6 +73,7 @@ class GattServices : public BleRcuServices std::shared_ptr infraredService() const override; std::shared_ptr upgradeService() const override; std::shared_ptr remoteControlService() const override; + std::shared_ptr mfvVoiceService() const override; private: template @@ -94,6 +96,7 @@ class GattServices : public BleRcuServices StartingTouchServiceState, StartingUpgradeServiceState, StartingRemoteControlServiceState, + StartingMfvVoiceServiceState, ReadyState, StoppingState }; @@ -131,6 +134,7 @@ class GattServices : public BleRcuServices std::size_t m_upgradeServiceIndex = 0; std::shared_ptr m_remoteControlService; + std::shared_ptr m_mfvVoiceService; private: static const Event::Type StartServicesRequestEvent = Event::Type(Event::User + 1); @@ -144,7 +148,8 @@ class GattServices : public BleRcuServices static const Event::Type InfraredServiceReadyEvent = Event::Type(Event::User + 8); static const Event::Type UpgradeServiceReadyEvent = Event::Type(Event::User + 9); static const Event::Type RemoteControlServiceReadyEvent = Event::Type(Event::User + 10); - static const Event::Type ServicesStoppedEvent = Event::Type(Event::User + 11); + static const Event::Type MfvVoiceServiceReadyEvent = Event::Type(Event::User + 11); + static const Event::Type ServicesStoppedEvent = Event::Type(Event::User + 12); }; From 1c6c1a4b5eeb0a9d522ed522047bbcc6a194d9da Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 16 Jun 2026 16:29:32 +0000 Subject: [PATCH 05/86] Update --- src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp | 2 +- src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp index 8bcdb5c0..5e56ff9a 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp @@ -137,4 +137,4 @@ void GattMfvVoiceService::writeModelConfiguration(uint8_t sensitivity, uint8_t s reply.setError("MFV model configuration write not implemented"); reply.finish(); -} \ No newline at end of file +} diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h b/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h index 3d8658ad..6e2fe967 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h @@ -64,4 +64,4 @@ class GattMfvVoiceService : public BleRcuMfvVoiceService static const BleUuid m_capabilitiesCharUuid; }; -#endif // !defined(GATT_MFVVOICESERVICE_H) \ No newline at end of file +#endif // !defined(GATT_MFVVOICESERVICE_H) From 3fdcabfb228a07b10b32c4c0be4406b5ce650e2a Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 16 Jun 2026 16:51:05 +0000 Subject: [PATCH 06/86] Start impl --- include/ctrlm_hal_ble.h | 10 + src/ble/ctrlm_ble_rcu_interface.cpp | 57 ++ src/ble/hal/blercu/blercudevice.h | 10 + .../bleservices/gatt/gatt_mfvvoiceservice.cpp | 677 ++++++++++++++++-- .../bleservices/gatt/gatt_mfvvoiceservice.h | 55 +- src/ble/hal/blercu/bluez/blercudevice.cpp | 30 + src/ble/hal/blercu/bluez/blercudevice_p.h | 5 + 7 files changed, 772 insertions(+), 72 deletions(-) diff --git a/include/ctrlm_hal_ble.h b/include/ctrlm_hal_ble.h index f9cb6a14..ac2924fa 100644 --- a/include/ctrlm_hal_ble.h +++ b/include/ctrlm_hal_ble.h @@ -85,6 +85,10 @@ typedef enum { CTRLM_HAL_BLE_PROPERTY_WAKEUP_CONFIG, CTRLM_HAL_BLE_PROPERTY_WAKEUP_CUSTOM_LIST, CTRLM_HAL_BLE_PROPERTY_IRDBS_SUPPORTED, + CTRLM_HAL_BLE_PROPERTY_MFV_DETECTION_TYPE, + CTRLM_HAL_BLE_PROPERTY_MFV_DETECTION_DATA, + CTRLM_HAL_BLE_PROPERTY_MFV_PRIVACY, + CTRLM_HAL_BLE_PROPERTY_MFV_CAPABILITIES, CTRLM_HAL_BLE_PROPERTY_UNKNOWN } ctrlm_hal_ble_RcuProperty_t; /// @} @@ -123,6 +127,12 @@ typedef struct { int wakeup_custom_list[CTRLM_WAKEUP_CONFIG_LIST_MAX_SIZE]; int wakeup_custom_list_size; uint8_t irdbs_supported; + uint8_t mfv_detection_type; + uint16_t mfv_ww_start; + uint16_t mfv_ww_end; + uint16_t mfv_confidence_x10; + bool mfv_privacy_enabled; + uint8_t mfv_capabilities; } ctrlm_hal_ble_rcu_data_t; typedef struct { diff --git a/src/ble/ctrlm_ble_rcu_interface.cpp b/src/ble/ctrlm_ble_rcu_interface.cpp index f90cdc67..5b01036b 100644 --- a/src/ble/ctrlm_ble_rcu_interface.cpp +++ b/src/ble/ctrlm_ble_rcu_interface.cpp @@ -21,6 +21,7 @@ #include "ctrlm_ble_rcu_interface.h" #include "ctrlm_ble_utils.h" #include "ctrlm_voice_obj.h" +#include "blercu/bleservices/blercumfvvoiceservice.h" #define CTRLM_BLE_KEY_MSG_QUEUE_MSG_MAX (10) @@ -484,6 +485,62 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) device->addAdvConfigCustomListChangedSlot(Slot &>(m_isAlive, advConfigCustomListChangedSlot)); + // MFV Voice Service slots + auto mfvService = device->mfvVoiceService(); + if (mfvService) { + auto mfvDetectionTypeSlot = [this, address](BleRcuMfvVoiceService::DetectionType type) + { + XLOGD_INFO("BLE RCU %s MFV detection type = 0x%02X", address.toString().c_str(), static_cast(type)); + + ctrlm_hal_ble_RcuStatusData_t params; + params.property_updated = CTRLM_HAL_BLE_PROPERTY_MFV_DETECTION_TYPE; + params.rcu_data.ieee_address = address.toUInt64(); + params.rcu_data.mfv_detection_type = static_cast(type); + m_rcuStatusChangedSlots.invoke(¶ms); + }; + mfvService->addDetectionTypeChangedSlot(Slot(m_isAlive, mfvDetectionTypeSlot)); + + auto mfvDetectionDataSlot = [this, address](const BleRcuMfvVoiceService::DetectionData &data) + { + XLOGD_INFO("BLE RCU %s MFV detection data: start=%u end=%u confidence=%.1f%%", + address.toString().c_str(), data.start, data.end, data.confidence_x10 / 10.0); + + ctrlm_hal_ble_RcuStatusData_t params; + params.property_updated = CTRLM_HAL_BLE_PROPERTY_MFV_DETECTION_DATA; + params.rcu_data.ieee_address = address.toUInt64(); + params.rcu_data.mfv_ww_start = data.start; + params.rcu_data.mfv_ww_end = data.end; + params.rcu_data.mfv_confidence_x10 = data.confidence_x10; + m_rcuStatusChangedSlots.invoke(¶ms); + }; + mfvService->addDetectionDataChangedSlot(Slot(m_isAlive, mfvDetectionDataSlot)); + + auto mfvPrivacySlot = [this, address](bool enabled) + { + XLOGD_INFO("BLE RCU %s MFV privacy = %s", address.toString().c_str(), enabled ? "enabled" : "disabled"); + + ctrlm_hal_ble_RcuStatusData_t params; + params.property_updated = CTRLM_HAL_BLE_PROPERTY_MFV_PRIVACY; + params.rcu_data.ieee_address = address.toUInt64(); + params.rcu_data.mfv_privacy_enabled = enabled; + m_rcuStatusChangedSlots.invoke(¶ms); + }; + mfvService->addPrivacyChangedSlot(Slot(m_isAlive, mfvPrivacySlot)); + + auto mfvCapabilitiesSlot = [this, address](uint8_t caps) + { + XLOGD_INFO("BLE RCU %s MFV capabilities = 0x%02X", address.toString().c_str(), caps); + + ctrlm_hal_ble_RcuStatusData_t params; + params.property_updated = CTRLM_HAL_BLE_PROPERTY_MFV_CAPABILITIES; + params.rcu_data.ieee_address = address.toUInt64(); + params.rcu_data.mfv_capabilities = caps; + m_rcuStatusChangedSlots.invoke(¶ms); + }; + mfvService->addCapabilitiesChangedSlot(Slot(m_isAlive, mfvCapabilitiesSlot)); + } + + auto audioStreamingChangedSlot = [this, address](bool streaming) { XLOGD_DEBUG("BLE RCU audio streaming changed to %s", streaming ? "TRUE" : "FALSE"); diff --git a/src/ble/hal/blercu/blercudevice.h b/src/ble/hal/blercu/blercudevice.h index 8ab7cc44..cbad1356 100644 --- a/src/ble/hal/blercu/blercudevice.h +++ b/src/ble/hal/blercu/blercudevice.h @@ -48,6 +48,7 @@ class BleRcuFindMeService; class BleRcuInfraredService; class BleRcuUpgradeService; class BleRcuRemoteControlService; +class BleRcuMfvVoiceService; @@ -97,6 +98,7 @@ class BleRcuDevice virtual std::shared_ptr infraredService() const = 0; virtual std::shared_ptr upgradeService() const = 0; virtual std::shared_ptr remoteControlService() const = 0; + virtual std::shared_ptr mfvVoiceService() const = 0; // Device Info Service virtual std::string firmwareRevision() const = 0; @@ -133,6 +135,10 @@ class BleRcuDevice virtual void addAdvConfigChangedSlot(const Slot &func) = 0; virtual void addAdvConfigCustomListChangedSlot(const Slot &> &func) = 0; + // MFV Voice Service + virtual void addMfvDetectionTypeChangedSlot(const Slot &func) = 0; + virtual void addMfvPrivacyChangedSlot(const Slot &func) = 0; + // Voice Service virtual uint8_t audioGainLevel() const = 0; virtual void setAudioGainLevel(uint8_t value) = 0; @@ -229,5 +235,9 @@ template<> inline std::shared_ptr BleRcuDevice::service() const { return remoteControlService(); } +template<> +inline std::shared_ptr BleRcuDevice::service() const +{ return mfvVoiceService(); } + #endif // !defined(BLERCUDEVICE_H) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp index 5e56ff9a..0ee9f480 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp @@ -7,134 +7,671 @@ using namespace std; -const BleUuid GattMfvVoiceService::m_serviceUuid("0000ea08-bdf0-407c-aaff-d09967f31acd", "MFV Voice", true); -const BleUuid GattMfvVoiceService::m_sessionStartCharUuid("0000ea08-bdf0-407c-aaff-d09967f31acd", "Session Start"); -const BleUuid GattMfvVoiceService::m_detectionDataCharUuid("0000ea09-bdf0-407c-aaff-d09967f31acd", "Detection Data"); -const BleUuid GattMfvVoiceService::m_modelVersionCharUuid("0000ea0a-bdf0-407c-aaff-d09967f31acd", "Wake Word Model Version"); -const BleUuid GattMfvVoiceService::m_privacyCharUuid("0000ea0b-bdf0-407c-aaff-d09967f31acd", "Privacy Settings"); -const BleUuid GattMfvVoiceService::m_modelConfigCharUuid("0000ea0c-bdf0-407c-aaff-d09967f31acd", "Model Configuration"); -const BleUuid GattMfvVoiceService::m_capabilitiesCharUuid("0000ea0d-bdf0-407c-aaff-d09967f31acd", "MFV Capabilities"); +const BleUuid GattMfvVoiceService::m_serviceUuid(BleUuid::MfvVoice); +const BleUuid GattMfvVoiceService::m_sessionStartCharUuid(BleUuid::SessionStart); +const BleUuid GattMfvVoiceService::m_detectionDataCharUuid(BleUuid::DetectionData); +const BleUuid GattMfvVoiceService::m_modelVersionCharUuid(BleUuid::WakeWordModelVersion); +const BleUuid GattMfvVoiceService::m_privacyCharUuid(BleUuid::PrivacySettings); +const BleUuid GattMfvVoiceService::m_modelConfigCharUuid(BleUuid::ModelConfiguration); +const BleUuid GattMfvVoiceService::m_capabilitiesCharUuid(BleUuid::MfvCapabilities); + +// ----------------------------------------------------------------------------- +/*! + Constructs the MFV Voice GATT service. + */ GattMfvVoiceService::GattMfvVoiceService(GMainLoop *mainLoop) - : m_ready(false) - , m_detectionType(FullPower) - , m_detectionData({ 0, 0, 0 }) - , m_modelVersion({ 0, 0 }) - , m_privacyEnabled(false) - , m_capabilities(0) + : m_isAlive(make_shared(true)) + , m_detectionType(FullPower) + , m_detectionData({ 0, 0, 0 }) + , m_modelVersion({ 0, 0 }) + , m_privacyEnabled(false) + , m_capabilities(0) + , m_initialReadsRemaining(0) { - (void)mainLoop; + m_stateMachine.setGMainLoop(mainLoop); + init(); } GattMfvVoiceService::~GattMfvVoiceService() { - stop(); + *m_isAlive = false; + stop(); } +// ----------------------------------------------------------------------------- +/*! + Returns the constant gatt service uuid. + */ BleUuid GattMfvVoiceService::uuid() { - return m_serviceUuid; + return m_serviceUuid; +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Configures and starts the state machine + */ +void GattMfvVoiceService::init() +{ + m_stateMachine.setObjectName(string("GattMfvVoiceService")); + + // add all the states and the super state groupings + m_stateMachine.addState(IdleState, string("Idle")); + m_stateMachine.addState(RunningState, string("Running")); + m_stateMachine.addState(RunningState, ReadInitialValuesState, string("ReadInitialValues")); + m_stateMachine.addState(RunningState, EnableNotificationsState, string("EnableNotifications")); + + // add the transitions: From State -> Event -> To State + m_stateMachine.addTransition(IdleState, StartServiceRequestEvent, ReadInitialValuesState); + m_stateMachine.addTransition(ReadInitialValuesState, InitialValuesRetrievedEvent, EnableNotificationsState); + m_stateMachine.addTransition(EnableNotificationsState, RetryStartNotifyEvent, EnableNotificationsState); + m_stateMachine.addTransition(RunningState, StopServiceRequestEvent, IdleState); + + // connect to the state entry signal + m_stateMachine.addEnteredHandler(Slot(m_isAlive, std::bind(&GattMfvVoiceService::onEnteredState, this, std::placeholders::_1))); + + // set the initial state of the state machine and start it + m_stateMachine.setInitialState(IdleState); + m_stateMachine.start(); } +// ----------------------------------------------------------------------------- +/*! + \reimp + */ bool GattMfvVoiceService::isReady() const { - return m_ready; + return m_stateMachine.inState(EnableNotificationsState); } BleRcuMfvVoiceService::DetectionType GattMfvVoiceService::detectionType() const { - return m_detectionType; + return m_detectionType; } BleRcuMfvVoiceService::DetectionData GattMfvVoiceService::detectionData() const { - return m_detectionData; + return m_detectionData; } BleRcuMfvVoiceService::ModelVersion GattMfvVoiceService::wakeWordModelVersion() const { - return m_modelVersion; + return m_modelVersion; } bool GattMfvVoiceService::privacyEnabled() const { - return m_privacyEnabled; + return m_privacyEnabled; } vector GattMfvVoiceService::modelConfiguration() const { - return m_modelConfiguration; + return m_modelConfigurationData; } uint8_t GattMfvVoiceService::capabilities() const { - return m_capabilities; + return m_capabilities; } BleRcuMfvVoiceService::StreamStatsRaw GattMfvVoiceService::streamStats() const { - return m_streamStats; + return m_streamStats; } + +// ----------------------------------------------------------------------------- +/*! + \reimp + + Starts the service by discovering characteristics from the GATT profile. + */ bool GattMfvVoiceService::start(const shared_ptr &gattService) { - if (m_ready) { - XLOGD_WARN("MFV voice service already started"); - return true; - } - - if (!gattService || !gattService->isValid() || (gattService->uuid() != m_serviceUuid)) { - XLOGD_WARN("invalid MFV voice gatt service info"); - return false; - } - - m_sessionStartCharacteristic = gattService->characteristic(m_sessionStartCharUuid); - m_detectionDataCharacteristic = gattService->characteristic(m_detectionDataCharUuid); - m_modelVersionCharacteristic = gattService->characteristic(m_modelVersionCharUuid); - m_privacyCharacteristic = gattService->characteristic(m_privacyCharUuid); - m_modelConfigCharacteristic = gattService->characteristic(m_modelConfigCharUuid); - m_capabilitiesCharacteristic = gattService->characteristic(m_capabilitiesCharUuid); - - if (!m_sessionStartCharacteristic || !m_sessionStartCharacteristic->isValid() || - !m_detectionDataCharacteristic || !m_detectionDataCharacteristic->isValid() || - !m_modelVersionCharacteristic || !m_modelVersionCharacteristic->isValid() || - !m_privacyCharacteristic || !m_privacyCharacteristic->isValid() || - !m_modelConfigCharacteristic || !m_modelConfigCharacteristic->isValid() || - !m_capabilitiesCharacteristic || !m_capabilitiesCharacteristic->isValid()) { - XLOGD_ERROR("failed to discover one or more MFV characteristics"); - stop(); - return false; - } - - m_ready = true; - m_readySlots.invoke(); - return true; + // sanity check the supplied info is valid + if (!gattService || !gattService->isValid() || (gattService->uuid() != m_serviceUuid)) { + XLOGD_WARN("invalid MFV voice gatt service info"); + return false; + } + + // discover characteristics (each is required) + if (!m_sessionStartCharacteristic || !m_sessionStartCharacteristic->isValid()) { + m_sessionStartCharacteristic = gattService->characteristic(m_sessionStartCharUuid); + if (!m_sessionStartCharacteristic || !m_sessionStartCharacteristic->isValid()) { + XLOGD_ERROR("failed to get MFV Session Start characteristic"); + return false; + } + } + if (!m_detectionDataCharacteristic || !m_detectionDataCharacteristic->isValid()) { + m_detectionDataCharacteristic = gattService->characteristic(m_detectionDataCharUuid); + if (!m_detectionDataCharacteristic || !m_detectionDataCharacteristic->isValid()) { + XLOGD_ERROR("failed to get MFV Detection Data characteristic"); + return false; + } + } + if (!m_modelVersionCharacteristic || !m_modelVersionCharacteristic->isValid()) { + m_modelVersionCharacteristic = gattService->characteristic(m_modelVersionCharUuid); + if (!m_modelVersionCharacteristic || !m_modelVersionCharacteristic->isValid()) { + XLOGD_ERROR("failed to get MFV Wake Word Model Version characteristic"); + return false; + } + } + if (!m_privacyCharacteristic || !m_privacyCharacteristic->isValid()) { + m_privacyCharacteristic = gattService->characteristic(m_privacyCharUuid); + if (!m_privacyCharacteristic || !m_privacyCharacteristic->isValid()) { + XLOGD_ERROR("failed to get MFV Privacy Settings characteristic"); + return false; + } + } + if (!m_modelConfigCharacteristic || !m_modelConfigCharacteristic->isValid()) { + m_modelConfigCharacteristic = gattService->characteristic(m_modelConfigCharUuid); + if (!m_modelConfigCharacteristic || !m_modelConfigCharacteristic->isValid()) { + XLOGD_ERROR("failed to get MFV Model Configuration characteristic"); + return false; + } + } + if (!m_capabilitiesCharacteristic || !m_capabilitiesCharacteristic->isValid()) { + m_capabilitiesCharacteristic = gattService->characteristic(m_capabilitiesCharUuid); + if (!m_capabilitiesCharacteristic || !m_capabilitiesCharacteristic->isValid()) { + XLOGD_ERROR("failed to get MFV Capabilities characteristic"); + return false; + } + } + + // check we're not already started + if (m_stateMachine.state() != IdleState) { + XLOGD_WARN("MFV voice service already started"); + return true; + } + + m_stateMachine.postEvent(StartServiceRequestEvent); + return true; } +// ----------------------------------------------------------------------------- +/*! + Stops the service and resets all characteristics. + */ void GattMfvVoiceService::stop() { - m_ready = false; - m_sessionStartCharacteristic.reset(); - m_detectionDataCharacteristic.reset(); - m_modelVersionCharacteristic.reset(); - m_privacyCharacteristic.reset(); - m_modelConfigCharacteristic.reset(); - m_capabilitiesCharacteristic.reset(); + m_stateMachine.cancelDelayedEvents(RetryStartNotifyEvent); + m_stateMachine.postEvent(StopServiceRequestEvent); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Private slot called on entry to a new state. + */ +void GattMfvVoiceService::onEnteredState(int state) +{ + if (state == IdleState) { + m_stateMachine.cancelDelayedEvents(RetryStartNotifyEvent); + + if (m_sessionStartCharacteristic) { + m_sessionStartCharacteristic->disableNotifications(); + } + if (m_detectionDataCharacteristic) { + m_detectionDataCharacteristic->disableNotifications(); + } + if (m_privacyCharacteristic) { + m_privacyCharacteristic->disableNotifications(); + } + + m_sessionStartCharacteristic.reset(); + m_detectionDataCharacteristic.reset(); + m_modelVersionCharacteristic.reset(); + m_privacyCharacteristic.reset(); + m_modelConfigCharacteristic.reset(); + m_capabilitiesCharacteristic.reset(); + + } else if (state == ReadInitialValuesState) { + // Read all readable characteristics. Track completion with a counter; + // the last read to finish will post the InitialValuesRetrievedEvent. + m_initialReadsRemaining = 4; + requestCapabilities(); + requestModelVersion(); + requestPrivacy(); + requestModelConfig(); + + } else if (state == EnableNotificationsState) { + requestStartSessionStartNotify(); + requestStartDetectionDataNotify(); + requestStartPrivacyNotify(); + + // Emit the ready signal so GattServices knows the service is up + m_readySlots.invoke(); + } +} + + +// ============================================================================= +// Initial value reads +// ============================================================================= + +// ----------------------------------------------------------------------------- +/*! + \internal + + Reads the MFV Capabilities characteristic (1 byte bitmask). + */ +void GattMfvVoiceService::requestCapabilities() +{ + auto replyHandler = [this](PendingReply> *reply) + { + if (reply->isError()) { + XLOGD_ERROR("failed to read MFV Capabilities due to <%s>", reply->errorMessage().c_str()); + } else { + std::vector value = reply->result(); + if (value.size() == 1) { + m_capabilities = value[0]; + XLOGD_INFO("MFV Capabilities = 0x%02X (midfield=%d privacy_ctrl=%d soww_eoww=%d aad_ctrl=%d)", + m_capabilities, + !!(m_capabilities & MidfieldVoiceCapable), + !!(m_capabilities & SoftwarePrivacyControl), + !!(m_capabilities & SowwEowwTimingAvailable), + !!(m_capabilities & AadSensitivityControlAvailable)); + } else { + XLOGD_ERROR("MFV Capabilities has invalid length (%zu bytes, expected 1)", value.size()); + } + } + if (--m_initialReadsRemaining <= 0) { + m_stateMachine.postEvent(InitialValuesRetrievedEvent); + } + }; + + m_capabilitiesCharacteristic->readValue(PendingReply>(m_isAlive, replyHandler)); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Reads the Wake Word Model Version characteristic (2 bytes: major, minor). + */ +void GattMfvVoiceService::requestModelVersion() +{ + auto replyHandler = [this](PendingReply> *reply) + { + if (reply->isError()) { + XLOGD_ERROR("failed to read MFV Model Version due to <%s>", reply->errorMessage().c_str()); + } else { + std::vector value = reply->result(); + if (value.size() == 2) { + m_modelVersion.major = value[0]; + m_modelVersion.minor = value[1]; + XLOGD_INFO("MFV Wake Word Model Version = %u.%u", m_modelVersion.major, m_modelVersion.minor); + } else { + XLOGD_ERROR("MFV Model Version has invalid length (%zu bytes, expected 2)", value.size()); + } + } + if (--m_initialReadsRemaining <= 0) { + m_stateMachine.postEvent(InitialValuesRetrievedEvent); + } + }; + + m_modelVersionCharacteristic->readValue(PendingReply>(m_isAlive, replyHandler)); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Reads the Privacy Settings characteristic (1 byte boolean). + */ +void GattMfvVoiceService::requestPrivacy() +{ + auto replyHandler = [this](PendingReply> *reply) + { + if (reply->isError()) { + XLOGD_ERROR("failed to read MFV Privacy due to <%s>", reply->errorMessage().c_str()); + } else { + std::vector value = reply->result(); + if (value.size() == 1) { + m_privacyEnabled = (value[0] != 0); + XLOGD_INFO("MFV Privacy = %s", m_privacyEnabled ? "enabled" : "disabled"); + } else { + XLOGD_ERROR("MFV Privacy has invalid length (%zu bytes, expected 1)", value.size()); + } + } + if (--m_initialReadsRemaining <= 0) { + m_stateMachine.postEvent(InitialValuesRetrievedEvent); + } + }; + + m_privacyCharacteristic->readValue(PendingReply>(m_isAlive, replyHandler)); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Reads the Model Configuration characteristic (3 bytes: sensitivity, + secondary sensitivity, AAD sensitivity). + */ +void GattMfvVoiceService::requestModelConfig() +{ + auto replyHandler = [this](PendingReply> *reply) + { + if (reply->isError()) { + XLOGD_ERROR("failed to read MFV Model Config due to <%s>", reply->errorMessage().c_str()); + } else { + std::vector value = reply->result(); + if (value.size() == 3) { + m_modelConfigurationData = value; + XLOGD_INFO("MFV Model Config: sensitivity=%u secondary=%u aad=%u", + value[0], value[1], value[2]); + } else { + XLOGD_ERROR("MFV Model Config has invalid length (%zu bytes, expected 3)", value.size()); + } + } + if (--m_initialReadsRemaining <= 0) { + m_stateMachine.postEvent(InitialValuesRetrievedEvent); + } + }; + + m_modelConfigCharacteristic->readValue(PendingReply>(m_isAlive, replyHandler)); +} + + +// ============================================================================= +// Notification subscriptions +// ============================================================================= + +// ----------------------------------------------------------------------------- +/*! + \internal + + Enables notifications on the Session Start characteristic (0xEA08). + On notification: 1 byte detection type flags. + */ +void GattMfvVoiceService::requestStartSessionStartNotify() +{ + auto replyHandler = [this](PendingReply<> *reply) + { + if (reply->isError()) { + XLOGD_ERROR("failed to enable Session Start notifications due to <%s>", + reply->errorMessage().c_str()); + m_stateMachine.cancelDelayedEvents(RetryStartNotifyEvent); + m_stateMachine.postDelayedEvent(RetryStartNotifyEvent, 2000); + } else { + XLOGD_DEBUG("Session Start notifications enabled successfully"); + } + }; + + m_sessionStartCharacteristic->enableNotifications( + Slot &>(m_isAlive, + std::bind(&GattMfvVoiceService::onSessionStartChanged, this, std::placeholders::_1)), + PendingReply<>(m_isAlive, replyHandler)); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Enables notifications on the Detection Data characteristic (0xEA09). + On notification: 6 bytes (start_lo, start_hi, end_lo, end_hi, conf_lo, conf_hi). + */ +void GattMfvVoiceService::requestStartDetectionDataNotify() +{ + auto replyHandler = [this](PendingReply<> *reply) + { + if (reply->isError()) { + XLOGD_ERROR("failed to enable Detection Data notifications due to <%s>", + reply->errorMessage().c_str()); + m_stateMachine.cancelDelayedEvents(RetryStartNotifyEvent); + m_stateMachine.postDelayedEvent(RetryStartNotifyEvent, 2000); + } else { + XLOGD_DEBUG("Detection Data notifications enabled successfully"); + } + }; + + m_detectionDataCharacteristic->enableNotifications( + Slot &>(m_isAlive, + std::bind(&GattMfvVoiceService::onDetectionDataChanged, this, std::placeholders::_1)), + PendingReply<>(m_isAlive, replyHandler)); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Enables notifications on the Privacy Settings characteristic (0xEA0B). + On notification: 1 byte boolean (1 = enabled, 0 = disabled). + */ +void GattMfvVoiceService::requestStartPrivacyNotify() +{ + auto replyHandler = [this](PendingReply<> *reply) + { + if (reply->isError()) { + XLOGD_ERROR("failed to enable Privacy notifications due to <%s>", + reply->errorMessage().c_str()); + m_stateMachine.cancelDelayedEvents(RetryStartNotifyEvent); + m_stateMachine.postDelayedEvent(RetryStartNotifyEvent, 2000); + } else { + XLOGD_DEBUG("Privacy notifications enabled successfully"); + } + }; + + m_privacyCharacteristic->enableNotifications( + Slot &>(m_isAlive, + std::bind(&GattMfvVoiceService::onPrivacyChanged, this, std::placeholders::_1)), + PendingReply<>(m_isAlive, replyHandler)); } + +// ============================================================================= +// Notification handlers (payload parsers) +// ============================================================================= + +// ----------------------------------------------------------------------------- +/*! + \internal + + Called when a Session Start notification is received from the RCU. + Payload: 1 byte detection type (0x01=FullPower, 0x02=AAD, 0x03=BelowThreshold). + */ +void GattMfvVoiceService::onSessionStartChanged(const std::vector &newValue) +{ + if (newValue.size() != 1) { + XLOGD_ERROR("Session Start notification has invalid length (%zu bytes, expected 1)", newValue.size()); + return; + } + + uint8_t raw = newValue[0]; + if (raw < 0x01 || raw > 0x03) { + XLOGD_WARN("Session Start notification has unknown detection type 0x%02X", raw); + } + + m_detectionType = static_cast(raw); + XLOGD_INFO("MFV Session Start: detection type = 0x%02X", raw); + + m_detectionTypeChangedSlots.invoke(m_detectionType); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Called when a Detection Data notification is received from the RCU. + Payload: 6 bytes, little-endian. + Bytes 0-1: start of wake word (uint16_t) + Bytes 2-3: end of wake word (uint16_t) + Bytes 4-5: confidence score x10 (uint16_t) + */ +void GattMfvVoiceService::onDetectionDataChanged(const std::vector &newValue) +{ + if (newValue.size() != 6) { + XLOGD_ERROR("Detection Data notification has invalid length (%zu bytes, expected 6)", newValue.size()); + return; + } + + m_detectionData.start = static_cast(newValue[0]) | (static_cast(newValue[1]) << 8); + m_detectionData.end = static_cast(newValue[2]) | (static_cast(newValue[3]) << 8); + m_detectionData.confidence_x10 = static_cast(newValue[4]) | (static_cast(newValue[5]) << 8); + + XLOGD_INFO("MFV Detection Data: start=%u end=%u confidence=%.1f%%", + m_detectionData.start, m_detectionData.end, + m_detectionData.confidence_x10 / 10.0); + + m_detectionDataChangedSlots.invoke(m_detectionData); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Called when a Privacy Settings notification is received from the RCU. + Payload: 1 byte boolean (1 = enabled, 0 = disabled). + */ +void GattMfvVoiceService::onPrivacyChanged(const std::vector &newValue) +{ + if (newValue.size() != 1) { + XLOGD_ERROR("Privacy notification has invalid length (%zu bytes, expected 1)", newValue.size()); + return; + } + + m_privacyEnabled = (newValue[0] != 0); + XLOGD_INFO("MFV Privacy changed: %s", m_privacyEnabled ? "enabled" : "disabled"); + + m_privacyChangedSlots.invoke(m_privacyEnabled); +} + + +// ============================================================================= +// Write handlers +// ============================================================================= + +// ----------------------------------------------------------------------------- +/*! + \reimp + + Writes the Privacy Settings characteristic. + Payload: 1 byte (1 = enable, 0 = disable). + */ void GattMfvVoiceService::writePrivacy(bool enabled, PendingReply<> &&reply) { - (void)enabled; - reply.setError("MFV privacy write not implemented"); - reply.finish(); + if (!isReady()) { + reply.setError("MFV service is not ready"); + reply.finish(); + return; + } + + if (m_promiseResults) { + reply.setError("MFV write already in progress"); + reply.finish(); + return; + } + + if (!m_privacyCharacteristic || !m_privacyCharacteristic->isValid()) { + reply.setError("MFV Privacy characteristic is not valid"); + reply.finish(); + return; + } + + m_promiseResults = make_shared>(std::move(reply)); + + const vector value(1, enabled ? 0x01 : 0x00); + XLOGD_INFO("Writing MFV Privacy = %s", enabled ? "enabled" : "disabled"); + + m_privacyCharacteristic->writeValue(value, PendingReply<>(m_isAlive, + std::bind(&GattMfvVoiceService::onWritePrivacyReply, this, std::placeholders::_1))); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Callback for the Privacy write completion. + */ +void GattMfvVoiceService::onWritePrivacyReply(PendingReply<> *reply) +{ + if (reply->isError()) { + XLOGD_ERROR("failed to write MFV Privacy due to <%s>", reply->errorMessage().c_str()); + if (m_promiseResults) { + m_promiseResults->setError(reply->errorMessage()); + m_promiseResults->finish(); + m_promiseResults.reset(); + } + } else { + XLOGD_INFO("MFV Privacy written successfully"); + if (m_promiseResults) { + m_promiseResults->finish(); + m_promiseResults.reset(); + } + } } +// ----------------------------------------------------------------------------- +/*! + \reimp + + Writes the Model Configuration characteristic. + Payload: 3 bytes (sensitivity, secondary sensitivity, AAD sensitivity). + AAD sensitivity must be in [60..95] in 5 dB increments. + */ void GattMfvVoiceService::writeModelConfiguration(uint8_t sensitivity, uint8_t secondary, uint8_t aad, PendingReply<> &&reply) { - (void)sensitivity; - (void)secondary; - (void)aad; - reply.setError("MFV model configuration write not implemented"); - reply.finish(); + if (!isReady()) { + reply.setError("MFV service is not ready"); + reply.finish(); + return; + } + + if (m_promiseResults) { + reply.setError("MFV write already in progress"); + reply.finish(); + return; + } + + if (!m_modelConfigCharacteristic || !m_modelConfigCharacteristic->isValid()) { + reply.setError("MFV Model Configuration characteristic is not valid"); + reply.finish(); + return; + } + + // Validate AAD sensitivity range per spec: [60..95] in 5 dB increments + if (aad != 0 && (aad < 60 || aad > 95 || (aad % 5) != 0)) { + XLOGD_ERROR("MFV AAD sensitivity %u is out of valid range [60..95 step 5]", aad); + reply.setError("AAD sensitivity out of valid range [60..95 step 5]"); + reply.finish(); + return; + } + m_promiseResults = make_shared>(std::move(reply)); + + const vector value = { sensitivity, secondary, aad }; + XLOGD_INFO("Writing MFV Model Config: sensitivity=%u secondary=%u aad=%u", + sensitivity, secondary, aad); + + m_modelConfigCharacteristic->writeValue(value, PendingReply<>(m_isAlive, + std::bind(&GattMfvVoiceService::onWriteModelConfigReply, this, std::placeholders::_1))); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Callback for the Model Configuration write completion. + */ +void GattMfvVoiceService::onWriteModelConfigReply(PendingReply<> *reply) +{ + if (reply->isError()) { + XLOGD_ERROR("failed to write MFV Model Config due to <%s>", reply->errorMessage().c_str()); + if (m_promiseResults) { + m_promiseResults->setError(reply->errorMessage()); + m_promiseResults->finish(); + m_promiseResults.reset(); + } + } else { + XLOGD_INFO("MFV Model Config written successfully"); + if (m_promiseResults) { + m_promiseResults->finish(); + m_promiseResults.reset(); + } + } } diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h b/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h index 6e2fe967..a71fe783 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h @@ -3,6 +3,7 @@ #include "blercu/bleservices/blercumfvvoiceservice.h" #include "utils/bleuuid.h" +#include "utils/statemachine.h" #include @@ -36,17 +37,59 @@ class GattMfvVoiceService : public BleRcuMfvVoiceService bool start(const std::shared_ptr &gattService); void stop(); +// signals: + inline void addReadySlot(const Slot<> &func) + { + m_readySlots.addSlot(func); + } + +private: + enum State { + IdleState, + RunningState, + ReadInitialValuesState, + EnableNotificationsState, + }; + + void init(); + + void onEnteredState(int state); + + // Initial reads + void requestCapabilities(); + void requestModelVersion(); + void requestPrivacy(); + void requestModelConfig(); + + // Enable notifications + void requestStartSessionStartNotify(); + void requestStartDetectionDataNotify(); + void requestStartPrivacyNotify(); + + // Notification handlers + void onSessionStartChanged(const std::vector &newValue); + void onDetectionDataChanged(const std::vector &newValue); + void onPrivacyChanged(const std::vector &newValue); + + // Write reply handlers + void onWritePrivacyReply(PendingReply<> *reply); + void onWriteModelConfigReply(PendingReply<> *reply); + private: - bool m_ready; + std::shared_ptr m_isAlive; + + StateMachine m_stateMachine; DetectionType m_detectionType; DetectionData m_detectionData; ModelVersion m_modelVersion; bool m_privacyEnabled; - std::vector m_modelConfiguration; + std::vector m_modelConfigurationData; uint8_t m_capabilities; StreamStatsRaw m_streamStats; + int m_initialReadsRemaining; + std::shared_ptr m_sessionStartCharacteristic; std::shared_ptr m_detectionDataCharacteristic; std::shared_ptr m_modelVersionCharacteristic; @@ -54,6 +97,8 @@ class GattMfvVoiceService : public BleRcuMfvVoiceService std::shared_ptr m_modelConfigCharacteristic; std::shared_ptr m_capabilitiesCharacteristic; + std::shared_ptr> m_promiseResults; + private: static const BleUuid m_serviceUuid; static const BleUuid m_sessionStartCharUuid; @@ -62,6 +107,12 @@ class GattMfvVoiceService : public BleRcuMfvVoiceService static const BleUuid m_privacyCharUuid; static const BleUuid m_modelConfigCharUuid; static const BleUuid m_capabilitiesCharUuid; + +private: + static const Event::Type StartServiceRequestEvent = Event::Type(Event::User + 1); + static const Event::Type StopServiceRequestEvent = Event::Type(Event::User + 2); + static const Event::Type InitialValuesRetrievedEvent = Event::Type(Event::User + 3); + static const Event::Type RetryStartNotifyEvent = Event::Type(Event::User + 4); }; #endif // !defined(GATT_MFVVOICESERVICE_H) diff --git a/src/ble/hal/blercu/bluez/blercudevice.cpp b/src/ble/hal/blercu/bluez/blercudevice.cpp index 08aa0287..15025041 100644 --- a/src/ble/hal/blercu/bluez/blercudevice.cpp +++ b/src/ble/hal/blercu/bluez/blercudevice.cpp @@ -43,6 +43,7 @@ #include "blercu/bleservices/blercuupgradeservice.h" #include "blercu/bleservices/blercufindmeservice.h" #include "blercu/bleservices/blercuremotecontrolservice.h" +#include "blercu/bleservices/blercumfvvoiceservice.h" #include "interfaces/bluezdeviceinterface.h" @@ -830,6 +831,15 @@ std::shared_ptr BleRcuDeviceBluez::remoteControlServ return nullptr; } } + +std::shared_ptr BleRcuDeviceBluez::mfvVoiceService() const +{ + if (m_services && m_services->isValid()) { + return m_services->mfvVoiceService(); + } else { + return nullptr; + } +} // ----------------------------------------------------------------------------- /*! \fn bool BleRcuDevice::isValid() const @@ -1102,6 +1112,26 @@ void BleRcuDeviceBluez::addAdvConfigCustomListChangedSlot(const Slot &func) +{ + (void)func; + // MFV detection type slots are registered directly via mfvVoiceService() + // in ctrlm_ble_rcu_interface.cpp +} + +void BleRcuDeviceBluez::addMfvPrivacyChangedSlot(const Slot &func) +{ + auto svc = mfvVoiceService(); + if (svc) { + svc->addPrivacyChangedSlot(func); + } +} + + //////////////////////////////////////////////////////////////////////////////////////////////////////// // Voice Service //////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/ble/hal/blercu/bluez/blercudevice_p.h b/src/ble/hal/blercu/bluez/blercudevice_p.h index 39ecd86c..68179f1e 100644 --- a/src/ble/hal/blercu/bluez/blercudevice_p.h +++ b/src/ble/hal/blercu/bluez/blercudevice_p.h @@ -108,6 +108,7 @@ class BleRcuDeviceBluez : public BleRcuDevice std::shared_ptr infraredService() const override; std::shared_ptr upgradeService() const override; std::shared_ptr remoteControlService() const override; + std::shared_ptr mfvVoiceService() const override; void onDeviceConnectedChanged(bool connected); void onDevicePairedChanged(bool paired); @@ -152,6 +153,10 @@ class BleRcuDeviceBluez : public BleRcuDevice void addAdvConfigChangedSlot(const Slot &func) override; void addAdvConfigCustomListChangedSlot(const Slot &> &func) override; + // MFV Voice Service + void addMfvDetectionTypeChangedSlot(const Slot &func) override; + void addMfvPrivacyChangedSlot(const Slot &func) override; + // Voice Service uint8_t audioGainLevel() const override; void setAudioGainLevel(uint8_t value) override; From 7dbebb596a44346b0a743e0f12d52476cd8e595e Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 23 Jun 2026 13:14:13 +0000 Subject: [PATCH 07/86] More changes before testing locally --- src/ble/ctrlm_ble_controller.cpp | 49 ++++++++++++++++++++++++++++++++ src/ble/ctrlm_ble_controller.h | 18 ++++++++++++ src/ble/ctrlm_ble_network.cpp | 17 +++++++++++ 3 files changed, 84 insertions(+) diff --git a/src/ble/ctrlm_ble_controller.cpp b/src/ble/ctrlm_ble_controller.cpp index f1a87c22..b3ec2e40 100644 --- a/src/ble/ctrlm_ble_controller.cpp +++ b/src/ble/ctrlm_ble_controller.cpp @@ -527,6 +527,48 @@ ctrlm_timestamp_t ctrlm_obj_controller_ble_t::getVoiceStartTimeLocal() const { return(voice_start_time_local_); } +void ctrlm_obj_controller_ble_t::setMfvDetectionType(uint8_t type) { + mfv_detection_type_ = type; +} + +uint8_t ctrlm_obj_controller_ble_t::getMfvDetectionType() const { + return mfv_detection_type_; +} + +void ctrlm_obj_controller_ble_t::setMfvDetectionData(uint16_t start, uint16_t end, uint16_t confidence_x10) { + mfv_ww_start_ = start; + mfv_ww_end_ = end; + mfv_confidence_x10_ = confidence_x10; +} + +uint16_t ctrlm_obj_controller_ble_t::getMfvWwStart() const { + return mfv_ww_start_; +} + +uint16_t ctrlm_obj_controller_ble_t::getMfvWwEnd() const { + return mfv_ww_end_; +} + +uint16_t ctrlm_obj_controller_ble_t::getMfvConfidence() const { + return mfv_confidence_x10_; +} + +void ctrlm_obj_controller_ble_t::setMfvPrivacy(bool enabled) { + mfv_privacy_enabled_ = enabled; +} + +bool ctrlm_obj_controller_ble_t::getMfvPrivacy() const { + return mfv_privacy_enabled_; +} + +void ctrlm_obj_controller_ble_t::setMfvCapabilities(uint8_t caps) { + mfv_capabilities_ = caps; +} + +uint8_t ctrlm_obj_controller_ble_t::getMfvCapabilities() const { + return mfv_capabilities_; +} + void ctrlm_obj_controller_ble_t::setSupportedIrdbs(uint8_t vendor_support_bitmask) { this->irdbs_supported_ = vendor_support_bitmask; @@ -668,6 +710,13 @@ void ctrlm_obj_controller_ble_t::print_status() { XLOGD_INFO("Programmed AVR IRDB Code : %s", irdb_entry_id_name_avr_->to_string().c_str()); XLOGD_INFO(""); voice_metrics_->print(__FUNCTION__); + XLOGD_INFO(""); + XLOGD_INFO("MFV Detection Type : 0x%02X", mfv_detection_type_); + XLOGD_INFO("MFV WW Start : %u", mfv_ww_start_); + XLOGD_INFO("MFV WW End : %u", mfv_ww_end_); + XLOGD_INFO("MFV Confidence : %.1f%%", mfv_confidence_x10_ / 10.0); + XLOGD_INFO("MFV Privacy : %s", mfv_privacy_enabled_ ? "enabled" : "disabled"); + XLOGD_INFO("MFV Capabilities : 0x%02X", mfv_capabilities_); XLOGD_WARN("------------------------------------------------------------"); } diff --git a/src/ble/ctrlm_ble_controller.h b/src/ble/ctrlm_ble_controller.h index 432f5c76..35a02e95 100644 --- a/src/ble/ctrlm_ble_controller.h +++ b/src/ble/ctrlm_ble_controller.h @@ -147,6 +147,17 @@ class ctrlm_obj_controller_ble_t : public ctrlm_obj_controller_t { ctrlm_timestamp_t getVoiceStartTimeKey() const; ctrlm_timestamp_t getVoiceStartTimeLocal() const; + void setMfvDetectionType(uint8_t type); + uint8_t getMfvDetectionType() const; + void setMfvDetectionData(uint16_t start, uint16_t end, uint16_t confidence_x10); + uint16_t getMfvWwStart() const; + uint16_t getMfvWwEnd() const; + uint16_t getMfvConfidence() const; + void setMfvPrivacy(bool enabled); + bool getMfvPrivacy() const; + void setMfvCapabilities(uint8_t caps); + uint8_t getMfvCapabilities() const; + void update_controller_id_and_db_entry(std::string name, ctrlm_network_id_t network_id, ctrlm_controller_id_t new_id); private: @@ -197,6 +208,13 @@ class ctrlm_obj_controller_ble_t : public ctrlm_obj_controller_t { bool type_z_supported_ = false; + uint8_t mfv_detection_type_ = 0; + uint16_t mfv_ww_start_ = 0; + uint16_t mfv_ww_end_ = 0; + uint16_t mfv_confidence_x10_ = 0; + bool mfv_privacy_enabled_ = false; + uint8_t mfv_capabilities_ = 0; + bool conn_param_update_before_ota_supported_ = false; ctrlm_sw_version_t conn_param_update_before_ota_version_; diff --git a/src/ble/ctrlm_ble_network.cpp b/src/ble/ctrlm_ble_network.cpp index 57cdb6da..ba877f89 100644 --- a/src/ble/ctrlm_ble_network.cpp +++ b/src/ble/ctrlm_ble_network.cpp @@ -1934,6 +1934,23 @@ void ctrlm_obj_network_ble_t::ind_process_rcu_status(void *data, int size) { report_status = false; print_status = false; break; + case CTRLM_HAL_BLE_PROPERTY_MFV_DETECTION_TYPE: + controller->setMfvDetectionType(dqm->rcu_data.mfv_detection_type); + XLOGD_INFO("Controller <%s> MFV detection type = 0x%02X", controller->ieee_address_get().to_string().c_str(), dqm->rcu_data.mfv_detection_type); + break; + case CTRLM_HAL_BLE_PROPERTY_MFV_DETECTION_DATA: + controller->setMfvDetectionData(dqm->rcu_data.mfv_ww_start, dqm->rcu_data.mfv_ww_end, dqm->rcu_data.mfv_confidence_x10); + XLOGD_INFO("Controller <%s> MFV detection data: start=%u end=%u confidence=%.1f%%", + controller->ieee_address_get().to_string().c_str(), dqm->rcu_data.mfv_ww_start, dqm->rcu_data.mfv_ww_end, dqm->rcu_data.mfv_confidence_x10 / 10.0); + break; + case CTRLM_HAL_BLE_PROPERTY_MFV_PRIVACY: + controller->setMfvPrivacy(dqm->rcu_data.mfv_privacy_enabled); + XLOGD_INFO("Controller <%s> MFV privacy = %s", controller->ieee_address_get().to_string().c_str(), dqm->rcu_data.mfv_privacy_enabled ? "enabled" : "disabled"); + break; + case CTRLM_HAL_BLE_PROPERTY_MFV_CAPABILITIES: + controller->setMfvCapabilities(dqm->rcu_data.mfv_capabilities); + XLOGD_INFO("Controller <%s> MFV capabilities = 0x%02X", controller->ieee_address_get().to_string().c_str(), dqm->rcu_data.mfv_capabilities); + break; default: XLOGD_WARN("Unhandled Property: %d !!!!!!!!!!!!!!!!!!!!!!!!", dqm->property_updated); report_status = false; From 54ac9f8f530e9a27be30e1e93b0ddabb1970f538 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 24 Jun 2026 12:06:07 +0000 Subject: [PATCH 08/86] Fix for missing MFV values --- src/ble/ctrlm_ble_rcu_interface.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/ble/ctrlm_ble_rcu_interface.cpp b/src/ble/ctrlm_ble_rcu_interface.cpp index 5b01036b..bd2503fb 100644 --- a/src/ble/ctrlm_ble_rcu_interface.cpp +++ b/src/ble/ctrlm_ble_rcu_interface.cpp @@ -538,6 +538,19 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) m_rcuStatusChangedSlots.invoke(¶ms); }; mfvService->addCapabilitiesChangedSlot(Slot(m_isAlive, mfvCapabilitiesSlot)); + + auto publishMfvSnapshot = [mfvService, mfvDetectionTypeSlot, mfvDetectionDataSlot, mfvPrivacySlot, mfvCapabilitiesSlot]() + { + mfvDetectionTypeSlot(mfvService->detectionType()); + mfvDetectionDataSlot(mfvService->detectionData()); + mfvPrivacySlot(mfvService->privacyEnabled()); + mfvCapabilitiesSlot(mfvService->capabilities()); + }; + + mfvService->addReadySlot(Slot<>(m_isAlive, publishMfvSnapshot)); + if (mfvService->isReady()) { + publishMfvSnapshot(); + } } @@ -733,6 +746,18 @@ ctrlm_hal_ble_rcu_data_t ctrlm_ble_rcu_interface_t::getAllDeviceProperties(uint6 ret.audio_codecs = device->audioCodecs(); ret.audio_gain_level = device->audioGainLevel(); + // MFV Voice Service + auto mfvService = device->mfvVoiceService(); + if (mfvService) { + auto detectionData = mfvService->detectionData(); + ret.mfv_detection_type = static_cast(mfvService->detectionType()); + ret.mfv_ww_start = detectionData.start; + ret.mfv_ww_end = detectionData.end; + ret.mfv_confidence_x10 = detectionData.confidence_x10; + ret.mfv_privacy_enabled = mfvService->privacyEnabled(); + ret.mfv_capabilities = mfvService->capabilities(); + } + return ret; } From 4fbaa0f0e3eeab9c3ec27cf0b8491ed77f455026 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 24 Jun 2026 13:19:11 +0000 Subject: [PATCH 09/86] Fix --- src/ble/ctrlm_ble_rcu_interface.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ble/ctrlm_ble_rcu_interface.cpp b/src/ble/ctrlm_ble_rcu_interface.cpp index bd2503fb..ea237d0f 100644 --- a/src/ble/ctrlm_ble_rcu_interface.cpp +++ b/src/ble/ctrlm_ble_rcu_interface.cpp @@ -548,9 +548,6 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) }; mfvService->addReadySlot(Slot<>(m_isAlive, publishMfvSnapshot)); - if (mfvService->isReady()) { - publishMfvSnapshot(); - } } From 5ced3bd59572d704b35d15205ebd0c6aa2cb083d Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 24 Jun 2026 15:38:05 +0000 Subject: [PATCH 10/86] Fix --- src/ble/ctrlm_ble_rcu_interface.cpp | 3 +++ src/ble/hal/blercu/bleservices/blercumfvvoiceservice.h | 2 ++ src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ble/ctrlm_ble_rcu_interface.cpp b/src/ble/ctrlm_ble_rcu_interface.cpp index ea237d0f..bd2503fb 100644 --- a/src/ble/ctrlm_ble_rcu_interface.cpp +++ b/src/ble/ctrlm_ble_rcu_interface.cpp @@ -548,6 +548,9 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) }; mfvService->addReadySlot(Slot<>(m_isAlive, publishMfvSnapshot)); + if (mfvService->isReady()) { + publishMfvSnapshot(); + } } diff --git a/src/ble/hal/blercu/bleservices/blercumfvvoiceservice.h b/src/ble/hal/blercu/bleservices/blercumfvvoiceservice.h index 85c4b93b..eef26d21 100644 --- a/src/ble/hal/blercu/bleservices/blercumfvvoiceservice.h +++ b/src/ble/hal/blercu/bleservices/blercumfvvoiceservice.h @@ -76,6 +76,8 @@ class BleRcuMfvVoiceService }; public: + virtual bool isReady() const = 0; + virtual DetectionType detectionType() const = 0; virtual DetectionData detectionData() const = 0; virtual ModelVersion wakeWordModelVersion() const = 0; diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h b/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h index a71fe783..09577cb7 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h @@ -20,7 +20,7 @@ class GattMfvVoiceService : public BleRcuMfvVoiceService static BleUuid uuid(); public: - bool isReady() const; + bool isReady() const override; DetectionType detectionType() const override; DetectionData detectionData() const override; From 3f9aba63aa98d28c10e36854824255994d2a8a78 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 24 Jun 2026 20:56:09 +0000 Subject: [PATCH 11/86] Updates --- src/CMakeLists.txt | 1 - src/ble/ctrlm_ble_rcu_interface.cpp | 47 +- src/ble/hal/blercu/blercudevice.h | 10 - .../blercu/bleservices/blercuaudioservice.h | 82 +++ .../hal/blercu/bleservices/blercuservices.h | 2 - .../gatt/gatt_audioservice_rdk.cpp | 518 ++++++++++++++ .../bleservices/gatt/gatt_audioservice_rdk.h | 64 ++ .../bleservices/gatt/gatt_mfvvoiceservice.cpp | 677 ------------------ .../bleservices/gatt/gatt_mfvvoiceservice.h | 118 --- .../blercu/bleservices/gatt/gatt_services.cpp | 21 +- .../blercu/bleservices/gatt/gatt_services.h | 7 +- src/ble/hal/blercu/bluez/blercudevice.cpp | 30 +- src/ble/hal/blercu/bluez/blercudevice_p.h | 5 - src/ble/hal/utils/bleuuid.cpp | 1 - src/ble/hal/utils/bleuuid.h | 1 - 15 files changed, 684 insertions(+), 900 deletions(-) delete mode 100644 src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp delete mode 100644 src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 139fa4f1..ab20447c 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -104,7 +104,6 @@ if(BLE_ENABLED) ble/hal/blercu/bleservices/gatt/gatt_findmeservice.cpp ble/hal/blercu/bleservices/gatt/gatt_infraredservice.cpp ble/hal/blercu/bleservices/gatt/gatt_infraredsignal.cpp - ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp ble/hal/blercu/bleservices/gatt/gatt_remotecontrolservice.cpp ble/hal/blercu/bleservices/gatt/gatt_services.cpp ble/hal/blercu/bleservices/gatt/gatt_upgradeservice.cpp diff --git a/src/ble/ctrlm_ble_rcu_interface.cpp b/src/ble/ctrlm_ble_rcu_interface.cpp index bd2503fb..4d65eb3b 100644 --- a/src/ble/ctrlm_ble_rcu_interface.cpp +++ b/src/ble/ctrlm_ble_rcu_interface.cpp @@ -21,7 +21,7 @@ #include "ctrlm_ble_rcu_interface.h" #include "ctrlm_ble_utils.h" #include "ctrlm_voice_obj.h" -#include "blercu/bleservices/blercumfvvoiceservice.h" +#include "blercu/bleservices/blercuaudioservice.h" #define CTRLM_BLE_KEY_MSG_QUEUE_MSG_MAX (10) @@ -485,10 +485,10 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) device->addAdvConfigCustomListChangedSlot(Slot &>(m_isAlive, advConfigCustomListChangedSlot)); - // MFV Voice Service slots - auto mfvService = device->mfvVoiceService(); - if (mfvService) { - auto mfvDetectionTypeSlot = [this, address](BleRcuMfvVoiceService::DetectionType type) + // MFV Voice characteristics (part of the RDK Voice / audio service) + auto audioSvc = device->audioService(); + if (audioSvc) { + auto mfvDetectionTypeSlot = [this, address](BleRcuAudioService::DetectionType type) { XLOGD_INFO("BLE RCU %s MFV detection type = 0x%02X", address.toString().c_str(), static_cast(type)); @@ -498,9 +498,9 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) params.rcu_data.mfv_detection_type = static_cast(type); m_rcuStatusChangedSlots.invoke(¶ms); }; - mfvService->addDetectionTypeChangedSlot(Slot(m_isAlive, mfvDetectionTypeSlot)); + audioSvc->addMfvDetectionTypeChangedSlot(Slot(m_isAlive, mfvDetectionTypeSlot)); - auto mfvDetectionDataSlot = [this, address](const BleRcuMfvVoiceService::DetectionData &data) + auto mfvDetectionDataSlot = [this, address](const BleRcuAudioService::DetectionData &data) { XLOGD_INFO("BLE RCU %s MFV detection data: start=%u end=%u confidence=%.1f%%", address.toString().c_str(), data.start, data.end, data.confidence_x10 / 10.0); @@ -513,7 +513,7 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) params.rcu_data.mfv_confidence_x10 = data.confidence_x10; m_rcuStatusChangedSlots.invoke(¶ms); }; - mfvService->addDetectionDataChangedSlot(Slot(m_isAlive, mfvDetectionDataSlot)); + audioSvc->addMfvDetectionDataChangedSlot(Slot(m_isAlive, mfvDetectionDataSlot)); auto mfvPrivacySlot = [this, address](bool enabled) { @@ -525,7 +525,7 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) params.rcu_data.mfv_privacy_enabled = enabled; m_rcuStatusChangedSlots.invoke(¶ms); }; - mfvService->addPrivacyChangedSlot(Slot(m_isAlive, mfvPrivacySlot)); + audioSvc->addMfvPrivacyChangedSlot(Slot(m_isAlive, mfvPrivacySlot)); auto mfvCapabilitiesSlot = [this, address](uint8_t caps) { @@ -537,20 +537,7 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) params.rcu_data.mfv_capabilities = caps; m_rcuStatusChangedSlots.invoke(¶ms); }; - mfvService->addCapabilitiesChangedSlot(Slot(m_isAlive, mfvCapabilitiesSlot)); - - auto publishMfvSnapshot = [mfvService, mfvDetectionTypeSlot, mfvDetectionDataSlot, mfvPrivacySlot, mfvCapabilitiesSlot]() - { - mfvDetectionTypeSlot(mfvService->detectionType()); - mfvDetectionDataSlot(mfvService->detectionData()); - mfvPrivacySlot(mfvService->privacyEnabled()); - mfvCapabilitiesSlot(mfvService->capabilities()); - }; - - mfvService->addReadySlot(Slot<>(m_isAlive, publishMfvSnapshot)); - if (mfvService->isReady()) { - publishMfvSnapshot(); - } + audioSvc->addMfvCapabilitiesChangedSlot(Slot(m_isAlive, mfvCapabilitiesSlot)); } @@ -746,16 +733,16 @@ ctrlm_hal_ble_rcu_data_t ctrlm_ble_rcu_interface_t::getAllDeviceProperties(uint6 ret.audio_codecs = device->audioCodecs(); ret.audio_gain_level = device->audioGainLevel(); - // MFV Voice Service - auto mfvService = device->mfvVoiceService(); - if (mfvService) { - auto detectionData = mfvService->detectionData(); - ret.mfv_detection_type = static_cast(mfvService->detectionType()); + // MFV data (part of the RDK Voice / audio service) + auto audioSvc = device->audioService(); + if (audioSvc) { + auto detectionData = audioSvc->mfvDetectionData(); + ret.mfv_detection_type = static_cast(audioSvc->mfvDetectionType()); ret.mfv_ww_start = detectionData.start; ret.mfv_ww_end = detectionData.end; ret.mfv_confidence_x10 = detectionData.confidence_x10; - ret.mfv_privacy_enabled = mfvService->privacyEnabled(); - ret.mfv_capabilities = mfvService->capabilities(); + ret.mfv_privacy_enabled = audioSvc->mfvPrivacyEnabled(); + ret.mfv_capabilities = audioSvc->mfvCapabilities(); } diff --git a/src/ble/hal/blercu/blercudevice.h b/src/ble/hal/blercu/blercudevice.h index cbad1356..8ab7cc44 100644 --- a/src/ble/hal/blercu/blercudevice.h +++ b/src/ble/hal/blercu/blercudevice.h @@ -48,7 +48,6 @@ class BleRcuFindMeService; class BleRcuInfraredService; class BleRcuUpgradeService; class BleRcuRemoteControlService; -class BleRcuMfvVoiceService; @@ -98,7 +97,6 @@ class BleRcuDevice virtual std::shared_ptr infraredService() const = 0; virtual std::shared_ptr upgradeService() const = 0; virtual std::shared_ptr remoteControlService() const = 0; - virtual std::shared_ptr mfvVoiceService() const = 0; // Device Info Service virtual std::string firmwareRevision() const = 0; @@ -135,10 +133,6 @@ class BleRcuDevice virtual void addAdvConfigChangedSlot(const Slot &func) = 0; virtual void addAdvConfigCustomListChangedSlot(const Slot &> &func) = 0; - // MFV Voice Service - virtual void addMfvDetectionTypeChangedSlot(const Slot &func) = 0; - virtual void addMfvPrivacyChangedSlot(const Slot &func) = 0; - // Voice Service virtual uint8_t audioGainLevel() const = 0; virtual void setAudioGainLevel(uint8_t value) = 0; @@ -235,9 +229,5 @@ template<> inline std::shared_ptr BleRcuDevice::service() const { return remoteControlService(); } -template<> -inline std::shared_ptr BleRcuDevice::service() const -{ return mfvVoiceService(); } - #endif // !defined(BLERCUDEVICE_H) diff --git a/src/ble/hal/blercu/bleservices/blercuaudioservice.h b/src/ble/hal/blercu/bleservices/blercuaudioservice.h index bb1b02df..c18065b1 100644 --- a/src/ble/hal/blercu/bleservices/blercuaudioservice.h +++ b/src/ble/hal/blercu/bleservices/blercuaudioservice.h @@ -36,6 +36,9 @@ #include "utils/audioformat.h" #include "ctrlm_hal.h" +#include +#include + class BleRcuAudioService { protected: @@ -81,6 +84,57 @@ class BleRcuAudioService virtual void status(uint32_t &lastError, uint32_t &expectedPackets, uint32_t &actualPackets, int32_t &voiceKeyHeldMs) = 0; +// MFV (Mic-Free Voice) types +public: + enum DetectionType { + FullPower = 0x01, + Aad = 0x02, + BelowThreshold = 0x03, + }; + + struct DetectionData { + uint16_t start = 0; + uint16_t end = 0; + uint16_t confidence_x10 = 0; + }; + + struct ModelVersion { + uint8_t major = 0; + uint8_t minor = 0; + }; + + struct StreamStatsRaw { + std::vector bytes; + }; + + enum Capabilities { + MidfieldVoiceCapable = (1 << 0), + SoftwarePrivacyControl = (1 << 1), + SowwEowwTimingAvailable = (1 << 2), + AadSensitivityControlAvailable = (1 << 3), + }; + +// MFV accessors (default no-ops for audio services without MFV support) +public: + virtual DetectionType mfvDetectionType() const { return FullPower; } + virtual DetectionData mfvDetectionData() const { return {}; } + virtual ModelVersion mfvModelVersion() const { return {}; } + virtual bool mfvPrivacyEnabled() const { return false; } + virtual std::vector mfvModelConfiguration() const { return {}; } + virtual uint8_t mfvCapabilities() const { return 0; } + virtual StreamStatsRaw mfvStreamStats() const { return {}; } + + virtual void writeMfvPrivacy(bool enabled, PendingReply<> &&reply) + { + reply.setError("MFV not supported"); + reply.finish(); + } + virtual void writeMfvModelConfiguration(uint8_t sensitivity, uint8_t secondary, uint8_t aad, PendingReply<> &&reply) + { + reply.setError("MFV not supported"); + reply.finish(); + } + // signals: inline void addStreamingChangedSlot(const Slot &func) { @@ -95,10 +149,38 @@ class BleRcuAudioService m_audioCodecsChangedSlots.addSlot(func); } +// MFV signals: + inline void addMfvDetectionTypeChangedSlot(const Slot &func) + { + m_mfvDetectionTypeChangedSlots.addSlot(func); + } + inline void addMfvDetectionDataChangedSlot(const Slot &func) + { + m_mfvDetectionDataChangedSlots.addSlot(func); + } + inline void addMfvPrivacyChangedSlot(const Slot &func) + { + m_mfvPrivacyChangedSlots.addSlot(func); + } + inline void addMfvCapabilitiesChangedSlot(const Slot &func) + { + m_mfvCapabilitiesChangedSlots.addSlot(func); + } + inline void addMfvStreamStatsChangedSlot(const Slot &func) + { + m_mfvStreamStatsChangedSlots.addSlot(func); + } + protected: Slots m_streamingChangedSlots; Slots m_gainLevelChangedSlots; Slots m_audioCodecsChangedSlots; + + Slots m_mfvDetectionTypeChangedSlots; + Slots m_mfvDetectionDataChangedSlots; + Slots m_mfvPrivacyChangedSlots; + Slots m_mfvCapabilitiesChangedSlots; + Slots m_mfvStreamStatsChangedSlots; }; diff --git a/src/ble/hal/blercu/bleservices/blercuservices.h b/src/ble/hal/blercu/bleservices/blercuservices.h index 2c4bc8ba..4a7cdd2c 100644 --- a/src/ble/hal/blercu/bleservices/blercuservices.h +++ b/src/ble/hal/blercu/bleservices/blercuservices.h @@ -43,7 +43,6 @@ class BleRcuFindMeService; class BleRcuInfraredService; class BleRcuUpgradeService; class BleRcuRemoteControlService; -class BleRcuMfvVoiceService; class BleRcuServices @@ -70,7 +69,6 @@ class BleRcuServices virtual std::shared_ptr infraredService() const = 0; virtual std::shared_ptr upgradeService() const = 0; virtual std::shared_ptr remoteControlService() const = 0; - virtual std::shared_ptr mfvVoiceService() const = 0; inline void addReadySlot(const Slot<> &func) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index 265a5d83..b9d86bdb 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -49,6 +49,13 @@ using namespace std; const BleUuid GattAudioServiceRdk::m_serviceUuid(BleUuid::RdkVoice); +const BleUuid GattAudioServiceRdk::m_mfvSessionStartCharUuid(BleUuid::SessionStart); +const BleUuid GattAudioServiceRdk::m_mfvDetectionDataCharUuid(BleUuid::DetectionData); +const BleUuid GattAudioServiceRdk::m_mfvModelVersionCharUuid(BleUuid::WakeWordModelVersion); +const BleUuid GattAudioServiceRdk::m_mfvPrivacyCharUuid(BleUuid::PrivacySettings); +const BleUuid GattAudioServiceRdk::m_mfvModelConfigCharUuid(BleUuid::ModelConfiguration); +const BleUuid GattAudioServiceRdk::m_mfvCapabilitiesCharUuid(BleUuid::MfvCapabilities); + GattAudioServiceRdk::GattAudioServiceRdk(GMainLoop* mainLoop) : GattAudioService(AUDIO_FRAME_SIZE, 5, AUDIO_PACKET_DURATION_USEC, AUDIO_SEQ_NUM_MAX, mainLoop) { } @@ -97,6 +104,16 @@ bool GattAudioServiceRdk::start(const shared_ptr &gattServ requestGainLevel(); requestAudioCodecs(); + // Discover MFV characteristics (optional - not all remotes support MFV) + if (getMfvCharacteristics(gattService)) { + m_mfvSupported = true; + m_mfvInitialReadsRemaining = 4; + requestMfvCapabilities(); + requestMfvModelVersion(); + requestMfvPrivacy(); + requestMfvModelConfig(); + } + return(GattAudioService::start(gattService)); } @@ -124,6 +141,24 @@ void GattAudioServiceRdk::onEnteredIdle() { m_audioCtrlCharacteristic.reset(); m_audioDataCharacteristic.reset(); + // Clean up MFV characteristics + if (m_mfvSessionStartCharacteristic) { + m_mfvSessionStartCharacteristic->disableNotifications(); + m_mfvSessionStartCharacteristic.reset(); + } + if (m_mfvDetectionDataCharacteristic) { + m_mfvDetectionDataCharacteristic->disableNotifications(); + m_mfvDetectionDataCharacteristic.reset(); + } + if (m_mfvPrivacyCharacteristic) { + m_mfvPrivacyCharacteristic->disableNotifications(); + m_mfvPrivacyCharacteristic.reset(); + } + m_mfvModelVersionCharacteristic.reset(); + m_mfvModelConfigCharacteristic.reset(); + m_mfvCapabilitiesCharacteristic.reset(); + m_mfvSupported = false; + GattAudioService::onEnteredIdle(); } @@ -596,3 +631,486 @@ void GattAudioServiceRdk::setGainLevel(uint8_t level) XLOGD_INFO("sending gain level = %u", level); m_audioGainCharacteristic->writeValue(value, PendingReply<>(getIsAlivePtr(), replyHandler)); } + + +// ============================================================================= +// MFV (Mic-Free Voice) implementation +// ============================================================================= + +// ----------------------------------------------------------------------------- +/*! + \internal + + Attempts to discover all MFV characteristics from the RDK Voice GATT service. + Returns true if all were found, false otherwise. + */ +bool GattAudioServiceRdk::getMfvCharacteristics(const shared_ptr &gattService) +{ + m_mfvSessionStartCharacteristic = gattService->characteristic(m_mfvSessionStartCharUuid); + if (!m_mfvSessionStartCharacteristic || !m_mfvSessionStartCharacteristic->isValid()) { + XLOGD_INFO("MFV Session Start characteristic not found (MFV not supported)"); + return false; + } + + m_mfvDetectionDataCharacteristic = gattService->characteristic(m_mfvDetectionDataCharUuid); + if (!m_mfvDetectionDataCharacteristic || !m_mfvDetectionDataCharacteristic->isValid()) { + XLOGD_INFO("MFV Detection Data characteristic not found"); + return false; + } + + m_mfvModelVersionCharacteristic = gattService->characteristic(m_mfvModelVersionCharUuid); + if (!m_mfvModelVersionCharacteristic || !m_mfvModelVersionCharacteristic->isValid()) { + XLOGD_INFO("MFV Wake Word Model Version characteristic not found"); + return false; + } + + m_mfvPrivacyCharacteristic = gattService->characteristic(m_mfvPrivacyCharUuid); + if (!m_mfvPrivacyCharacteristic || !m_mfvPrivacyCharacteristic->isValid()) { + XLOGD_INFO("MFV Privacy Settings characteristic not found"); + return false; + } + + m_mfvModelConfigCharacteristic = gattService->characteristic(m_mfvModelConfigCharUuid); + if (!m_mfvModelConfigCharacteristic || !m_mfvModelConfigCharacteristic->isValid()) { + XLOGD_INFO("MFV Model Configuration characteristic not found"); + return false; + } + + m_mfvCapabilitiesCharacteristic = gattService->characteristic(m_mfvCapabilitiesCharUuid); + if (!m_mfvCapabilitiesCharacteristic || !m_mfvCapabilitiesCharacteristic->isValid()) { + XLOGD_INFO("MFV Capabilities characteristic not found"); + return false; + } + + XLOGD_INFO("All MFV characteristics discovered successfully"); + return true; +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Called when the last MFV initial read completes. Enables MFV notifications + and fires the changed slots with initial values. + */ +void GattAudioServiceRdk::onMfvInitialReadComplete() +{ + requestStartMfvSessionStartNotify(); + requestStartMfvDetectionDataNotify(); + requestStartMfvPrivacyNotify(); + + // Publish initial values via changed slots + m_mfvCapabilitiesChangedSlots.invoke(m_mfvCapabilitiesValue); + m_mfvPrivacyChangedSlots.invoke(m_mfvPrivacyEnabled); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Reads the MFV Capabilities characteristic (1 byte bitmask). + */ +void GattAudioServiceRdk::requestMfvCapabilities() +{ + auto replyHandler = [this](PendingReply> *reply) + { + if (reply->isError()) { + XLOGD_ERROR("failed to read MFV Capabilities due to <%s>", reply->errorMessage().c_str()); + } else { + std::vector value = reply->result(); + if (value.size() == 1) { + m_mfvCapabilitiesValue = value[0]; + XLOGD_INFO("MFV Capabilities = 0x%02X (midfield=%d privacy_ctrl=%d soww_eoww=%d aad_ctrl=%d)", + m_mfvCapabilitiesValue, + !!(m_mfvCapabilitiesValue & MidfieldVoiceCapable), + !!(m_mfvCapabilitiesValue & SoftwarePrivacyControl), + !!(m_mfvCapabilitiesValue & SowwEowwTimingAvailable), + !!(m_mfvCapabilitiesValue & AadSensitivityControlAvailable)); + } else { + XLOGD_ERROR("MFV Capabilities has invalid length (%zu bytes, expected 1)", value.size()); + } + } + if (--m_mfvInitialReadsRemaining <= 0) { + onMfvInitialReadComplete(); + } + }; + + m_mfvCapabilitiesCharacteristic->readValue(PendingReply>(getIsAlivePtr(), replyHandler)); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Reads the Wake Word Model Version characteristic (2 bytes: major, minor). + */ +void GattAudioServiceRdk::requestMfvModelVersion() +{ + auto replyHandler = [this](PendingReply> *reply) + { + if (reply->isError()) { + XLOGD_ERROR("failed to read MFV Model Version due to <%s>", reply->errorMessage().c_str()); + } else { + std::vector value = reply->result(); + if (value.size() == 2) { + m_mfvModelVersionData.major = value[0]; + m_mfvModelVersionData.minor = value[1]; + XLOGD_INFO("MFV Wake Word Model Version = %u.%u", m_mfvModelVersionData.major, m_mfvModelVersionData.minor); + } else { + XLOGD_ERROR("MFV Model Version has invalid length (%zu bytes, expected 2)", value.size()); + } + } + if (--m_mfvInitialReadsRemaining <= 0) { + onMfvInitialReadComplete(); + } + }; + + m_mfvModelVersionCharacteristic->readValue(PendingReply>(getIsAlivePtr(), replyHandler)); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Reads the Privacy Settings characteristic (1 byte boolean). + */ +void GattAudioServiceRdk::requestMfvPrivacy() +{ + auto replyHandler = [this](PendingReply> *reply) + { + if (reply->isError()) { + XLOGD_ERROR("failed to read MFV Privacy due to <%s>", reply->errorMessage().c_str()); + } else { + std::vector value = reply->result(); + if (value.size() == 1) { + m_mfvPrivacyEnabled = (value[0] != 0); + XLOGD_INFO("MFV Privacy = %s", m_mfvPrivacyEnabled ? "enabled" : "disabled"); + } else { + XLOGD_ERROR("MFV Privacy has invalid length (%zu bytes, expected 1)", value.size()); + } + } + if (--m_mfvInitialReadsRemaining <= 0) { + onMfvInitialReadComplete(); + } + }; + + m_mfvPrivacyCharacteristic->readValue(PendingReply>(getIsAlivePtr(), replyHandler)); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Reads the Model Configuration characteristic (3 bytes: sensitivity, + secondary sensitivity, AAD sensitivity). + */ +void GattAudioServiceRdk::requestMfvModelConfig() +{ + auto replyHandler = [this](PendingReply> *reply) + { + if (reply->isError()) { + XLOGD_ERROR("failed to read MFV Model Config due to <%s>", reply->errorMessage().c_str()); + } else { + std::vector value = reply->result(); + if (value.size() == 3) { + m_mfvModelConfigurationData = value; + XLOGD_INFO("MFV Model Config: sensitivity=%u secondary=%u aad=%u", + value[0], value[1], value[2]); + } else { + XLOGD_ERROR("MFV Model Config has invalid length (%zu bytes, expected 3)", value.size()); + } + } + if (--m_mfvInitialReadsRemaining <= 0) { + onMfvInitialReadComplete(); + } + }; + + m_mfvModelConfigCharacteristic->readValue(PendingReply>(getIsAlivePtr(), replyHandler)); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Enables notifications on the Session Start characteristic (0xEA08). + */ +void GattAudioServiceRdk::requestStartMfvSessionStartNotify() +{ + auto replyHandler = [this](PendingReply<> *reply) + { + if (reply->isError()) { + XLOGD_ERROR("failed to enable MFV Session Start notifications due to <%s>", + reply->errorMessage().c_str()); + } else { + XLOGD_DEBUG("MFV Session Start notifications enabled successfully"); + } + }; + + m_mfvSessionStartCharacteristic->enableNotifications( + Slot &>(getIsAlivePtr(), + std::bind(&GattAudioServiceRdk::onMfvSessionStartChanged, this, std::placeholders::_1)), + PendingReply<>(getIsAlivePtr(), replyHandler)); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Enables notifications on the Detection Data characteristic (0xEA09). + */ +void GattAudioServiceRdk::requestStartMfvDetectionDataNotify() +{ + auto replyHandler = [this](PendingReply<> *reply) + { + if (reply->isError()) { + XLOGD_ERROR("failed to enable MFV Detection Data notifications due to <%s>", + reply->errorMessage().c_str()); + } else { + XLOGD_DEBUG("MFV Detection Data notifications enabled successfully"); + } + }; + + m_mfvDetectionDataCharacteristic->enableNotifications( + Slot &>(getIsAlivePtr(), + std::bind(&GattAudioServiceRdk::onMfvDetectionDataChanged, this, std::placeholders::_1)), + PendingReply<>(getIsAlivePtr(), replyHandler)); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Enables notifications on the Privacy Settings characteristic (0xEA0B). + */ +void GattAudioServiceRdk::requestStartMfvPrivacyNotify() +{ + auto replyHandler = [this](PendingReply<> *reply) + { + if (reply->isError()) { + XLOGD_ERROR("failed to enable MFV Privacy notifications due to <%s>", + reply->errorMessage().c_str()); + } else { + XLOGD_DEBUG("MFV Privacy notifications enabled successfully"); + } + }; + + m_mfvPrivacyCharacteristic->enableNotifications( + Slot &>(getIsAlivePtr(), + std::bind(&GattAudioServiceRdk::onMfvPrivacyChanged, this, std::placeholders::_1)), + PendingReply<>(getIsAlivePtr(), replyHandler)); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Called when a Session Start notification is received from the RCU. + Payload: 1 byte detection type (0x01=FullPower, 0x02=AAD, 0x03=BelowThreshold). + */ +void GattAudioServiceRdk::onMfvSessionStartChanged(const std::vector &newValue) +{ + if (newValue.size() != 1) { + XLOGD_ERROR("MFV Session Start notification has invalid length (%zu bytes, expected 1)", newValue.size()); + return; + } + + uint8_t raw = newValue[0]; + if (raw < 0x01 || raw > 0x03) { + XLOGD_WARN("MFV Session Start notification has unknown detection type 0x%02X", raw); + } + + m_mfvDetectionType = static_cast(raw); + XLOGD_INFO("MFV Session Start: detection type = 0x%02X", raw); + + m_mfvDetectionTypeChangedSlots.invoke(m_mfvDetectionType); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Called when a Detection Data notification is received from the RCU. + Payload: 6 bytes, little-endian (start, end, confidence_x10). + */ +void GattAudioServiceRdk::onMfvDetectionDataChanged(const std::vector &newValue) +{ + if (newValue.size() != 6) { + XLOGD_ERROR("MFV Detection Data notification has invalid length (%zu bytes, expected 6)", newValue.size()); + return; + } + + m_mfvDetectionData.start = static_cast(newValue[0]) | (static_cast(newValue[1]) << 8); + m_mfvDetectionData.end = static_cast(newValue[2]) | (static_cast(newValue[3]) << 8); + m_mfvDetectionData.confidence_x10 = static_cast(newValue[4]) | (static_cast(newValue[5]) << 8); + + XLOGD_INFO("MFV Detection Data: start=%u end=%u confidence=%.1f%%", + m_mfvDetectionData.start, m_mfvDetectionData.end, + m_mfvDetectionData.confidence_x10 / 10.0); + + m_mfvDetectionDataChangedSlots.invoke(m_mfvDetectionData); +} + +// ----------------------------------------------------------------------------- +/*! + \internal + + Called when a Privacy Settings notification is received from the RCU. + Payload: 1 byte boolean (1 = enabled, 0 = disabled). + */ +void GattAudioServiceRdk::onMfvPrivacyChanged(const std::vector &newValue) +{ + if (newValue.size() != 1) { + XLOGD_ERROR("MFV Privacy notification has invalid length (%zu bytes, expected 1)", newValue.size()); + return; + } + + m_mfvPrivacyEnabled = (newValue[0] != 0); + XLOGD_INFO("MFV Privacy changed: %s", m_mfvPrivacyEnabled ? "enabled" : "disabled"); + + m_mfvPrivacyChangedSlots.invoke(m_mfvPrivacyEnabled); +} + +// ============================================================================= +// MFV accessors +// ============================================================================= + +BleRcuAudioService::DetectionType GattAudioServiceRdk::mfvDetectionType() const +{ + return m_mfvDetectionType; +} + +BleRcuAudioService::DetectionData GattAudioServiceRdk::mfvDetectionData() const +{ + return m_mfvDetectionData; +} + +BleRcuAudioService::ModelVersion GattAudioServiceRdk::mfvModelVersion() const +{ + return m_mfvModelVersionData; +} + +bool GattAudioServiceRdk::mfvPrivacyEnabled() const +{ + return m_mfvPrivacyEnabled; +} + +std::vector GattAudioServiceRdk::mfvModelConfiguration() const +{ + return m_mfvModelConfigurationData; +} + +uint8_t GattAudioServiceRdk::mfvCapabilities() const +{ + return m_mfvCapabilitiesValue; +} + +BleRcuAudioService::StreamStatsRaw GattAudioServiceRdk::mfvStreamStats() const +{ + return m_mfvStreamStatsData; +} + +// ============================================================================= +// MFV write handlers +// ============================================================================= + +void GattAudioServiceRdk::writeMfvPrivacy(bool enabled, PendingReply<> &&reply) +{ + if (!m_mfvSupported) { + reply.setError("MFV not supported on this remote"); + reply.finish(); + return; + } + + if (m_mfvPromiseResults) { + reply.setError("MFV write already in progress"); + reply.finish(); + return; + } + + if (!m_mfvPrivacyCharacteristic || !m_mfvPrivacyCharacteristic->isValid()) { + reply.setError("MFV Privacy characteristic is not valid"); + reply.finish(); + return; + } + + m_mfvPromiseResults = make_shared>(std::move(reply)); + + const vector value(1, enabled ? 0x01 : 0x00); + XLOGD_INFO("Writing MFV Privacy = %s", enabled ? "enabled" : "disabled"); + + m_mfvPrivacyCharacteristic->writeValue(value, PendingReply<>(getIsAlivePtr(), + std::bind(&GattAudioServiceRdk::onWriteMfvPrivacyReply, this, std::placeholders::_1))); +} + +void GattAudioServiceRdk::onWriteMfvPrivacyReply(PendingReply<> *reply) +{ + if (reply->isError()) { + XLOGD_ERROR("failed to write MFV Privacy due to <%s>", reply->errorMessage().c_str()); + if (m_mfvPromiseResults) { + m_mfvPromiseResults->setError(reply->errorMessage()); + m_mfvPromiseResults->finish(); + m_mfvPromiseResults.reset(); + } + } else { + XLOGD_INFO("MFV Privacy written successfully"); + if (m_mfvPromiseResults) { + m_mfvPromiseResults->finish(); + m_mfvPromiseResults.reset(); + } + } +} + +void GattAudioServiceRdk::writeMfvModelConfiguration(uint8_t sensitivity, uint8_t secondary, uint8_t aad, PendingReply<> &&reply) +{ + if (!m_mfvSupported) { + reply.setError("MFV not supported on this remote"); + reply.finish(); + return; + } + + if (m_mfvPromiseResults) { + reply.setError("MFV write already in progress"); + reply.finish(); + return; + } + + if (!m_mfvModelConfigCharacteristic || !m_mfvModelConfigCharacteristic->isValid()) { + reply.setError("MFV Model Configuration characteristic is not valid"); + reply.finish(); + return; + } + + if (aad != 0 && (aad < 60 || aad > 95 || (aad % 5) != 0)) { + XLOGD_ERROR("MFV AAD sensitivity %u is out of valid range [60..95 step 5]", aad); + reply.setError("AAD sensitivity out of valid range [60..95 step 5]"); + reply.finish(); + return; + } + + m_mfvPromiseResults = make_shared>(std::move(reply)); + + const vector value = { sensitivity, secondary, aad }; + XLOGD_INFO("Writing MFV Model Config: sensitivity=%u secondary=%u aad=%u", + sensitivity, secondary, aad); + + m_mfvModelConfigCharacteristic->writeValue(value, PendingReply<>(getIsAlivePtr(), + std::bind(&GattAudioServiceRdk::onWriteMfvModelConfigReply, this, std::placeholders::_1))); +} + +void GattAudioServiceRdk::onWriteMfvModelConfigReply(PendingReply<> *reply) +{ + if (reply->isError()) { + XLOGD_ERROR("failed to write MFV Model Config due to <%s>", reply->errorMessage().c_str()); + if (m_mfvPromiseResults) { + m_mfvPromiseResults->setError(reply->errorMessage()); + m_mfvPromiseResults->finish(); + m_mfvPromiseResults.reset(); + } + } else { + XLOGD_INFO("MFV Model Config written successfully"); + if (m_mfvPromiseResults) { + m_mfvPromiseResults->finish(); + m_mfvPromiseResults.reset(); + } + } +} diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h index f0662604..821e75c7 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h @@ -67,6 +67,18 @@ class GattAudioServiceRdk : public GattAudioService uint8_t gainLevel() const override; void setGainLevel(uint8_t level) override; + // MFV accessors + DetectionType mfvDetectionType() const override; + DetectionData mfvDetectionData() const override; + ModelVersion mfvModelVersion() const override; + bool mfvPrivacyEnabled() const override; + std::vector mfvModelConfiguration() const override; + uint8_t mfvCapabilities() const override; + StreamStatsRaw mfvStreamStats() const override; + + void writeMfvPrivacy(bool enabled, PendingReply<> &&reply) override; + void writeMfvModelConfiguration(uint8_t sensitivity, uint8_t secondary, uint8_t aad, PendingReply<> &&reply) override; + private: bool getAudioCodecsCharacteristic(const std::shared_ptr &gattService); @@ -75,6 +87,30 @@ class GattAudioServiceRdk : public GattAudioService bool getAudioDataCharacteristic(const std::shared_ptr &gattService); bool getAudioInfoCharacteristic(const std::shared_ptr &gattService); + // MFV characteristic discovery + bool getMfvCharacteristics(const std::shared_ptr &gattService); + + // MFV initial reads + void requestMfvCapabilities(); + void requestMfvModelVersion(); + void requestMfvPrivacy(); + void requestMfvModelConfig(); + void onMfvInitialReadComplete(); + + // MFV notification enable + void requestStartMfvSessionStartNotify(); + void requestStartMfvDetectionDataNotify(); + void requestStartMfvPrivacyNotify(); + + // MFV notification handlers + void onMfvSessionStartChanged(const std::vector &newValue); + void onMfvDetectionDataChanged(const std::vector &newValue); + void onMfvPrivacyChanged(const std::vector &newValue); + + // MFV write handlers + void onWriteMfvPrivacyReply(PendingReply<> *reply); + void onWriteMfvModelConfigReply(PendingReply<> *reply); + void requestGainLevel(); void requestAudioCodecs(); @@ -89,6 +125,34 @@ class GattAudioServiceRdk : public GattAudioService std::shared_ptr m_audioInfoCharacteristic; std::shared_ptr m_audioCodecsCharacteristic; + // MFV characteristics and data + bool m_mfvSupported = false; + int m_mfvInitialReadsRemaining = 0; + + DetectionType m_mfvDetectionType = FullPower; + DetectionData m_mfvDetectionData; + ModelVersion m_mfvModelVersionData; + bool m_mfvPrivacyEnabled = false; + std::vector m_mfvModelConfigurationData; + uint8_t m_mfvCapabilitiesValue = 0; + StreamStatsRaw m_mfvStreamStatsData; + + std::shared_ptr> m_mfvPromiseResults; + + std::shared_ptr m_mfvSessionStartCharacteristic; + std::shared_ptr m_mfvDetectionDataCharacteristic; + std::shared_ptr m_mfvModelVersionCharacteristic; + std::shared_ptr m_mfvPrivacyCharacteristic; + std::shared_ptr m_mfvModelConfigCharacteristic; + std::shared_ptr m_mfvCapabilitiesCharacteristic; + + static const BleUuid m_mfvSessionStartCharUuid; + static const BleUuid m_mfvDetectionDataCharUuid; + static const BleUuid m_mfvModelVersionCharUuid; + static const BleUuid m_mfvPrivacyCharUuid; + static const BleUuid m_mfvModelConfigCharUuid; + static const BleUuid m_mfvCapabilitiesCharUuid; + }; #endif // !defined(GATT_AUDIOSERVICE_RDK_H) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp deleted file mode 100644 index 0ee9f480..00000000 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.cpp +++ /dev/null @@ -1,677 +0,0 @@ -#include "gatt_mfvvoiceservice.h" - -#include "blercu/blegattservice.h" -#include "blercu/blegattcharacteristic.h" - -#include "ctrlm_log_ble.h" - -using namespace std; - -const BleUuid GattMfvVoiceService::m_serviceUuid(BleUuid::MfvVoice); -const BleUuid GattMfvVoiceService::m_sessionStartCharUuid(BleUuid::SessionStart); -const BleUuid GattMfvVoiceService::m_detectionDataCharUuid(BleUuid::DetectionData); -const BleUuid GattMfvVoiceService::m_modelVersionCharUuid(BleUuid::WakeWordModelVersion); -const BleUuid GattMfvVoiceService::m_privacyCharUuid(BleUuid::PrivacySettings); -const BleUuid GattMfvVoiceService::m_modelConfigCharUuid(BleUuid::ModelConfiguration); -const BleUuid GattMfvVoiceService::m_capabilitiesCharUuid(BleUuid::MfvCapabilities); - - -// ----------------------------------------------------------------------------- -/*! - Constructs the MFV Voice GATT service. - */ -GattMfvVoiceService::GattMfvVoiceService(GMainLoop *mainLoop) - : m_isAlive(make_shared(true)) - , m_detectionType(FullPower) - , m_detectionData({ 0, 0, 0 }) - , m_modelVersion({ 0, 0 }) - , m_privacyEnabled(false) - , m_capabilities(0) - , m_initialReadsRemaining(0) -{ - m_stateMachine.setGMainLoop(mainLoop); - init(); -} - -GattMfvVoiceService::~GattMfvVoiceService() -{ - *m_isAlive = false; - stop(); -} - -// ----------------------------------------------------------------------------- -/*! - Returns the constant gatt service uuid. - */ -BleUuid GattMfvVoiceService::uuid() -{ - return m_serviceUuid; -} - -// ----------------------------------------------------------------------------- -/*! - \internal - - Configures and starts the state machine - */ -void GattMfvVoiceService::init() -{ - m_stateMachine.setObjectName(string("GattMfvVoiceService")); - - // add all the states and the super state groupings - m_stateMachine.addState(IdleState, string("Idle")); - m_stateMachine.addState(RunningState, string("Running")); - m_stateMachine.addState(RunningState, ReadInitialValuesState, string("ReadInitialValues")); - m_stateMachine.addState(RunningState, EnableNotificationsState, string("EnableNotifications")); - - // add the transitions: From State -> Event -> To State - m_stateMachine.addTransition(IdleState, StartServiceRequestEvent, ReadInitialValuesState); - m_stateMachine.addTransition(ReadInitialValuesState, InitialValuesRetrievedEvent, EnableNotificationsState); - m_stateMachine.addTransition(EnableNotificationsState, RetryStartNotifyEvent, EnableNotificationsState); - m_stateMachine.addTransition(RunningState, StopServiceRequestEvent, IdleState); - - // connect to the state entry signal - m_stateMachine.addEnteredHandler(Slot(m_isAlive, std::bind(&GattMfvVoiceService::onEnteredState, this, std::placeholders::_1))); - - // set the initial state of the state machine and start it - m_stateMachine.setInitialState(IdleState); - m_stateMachine.start(); -} - -// ----------------------------------------------------------------------------- -/*! - \reimp - */ -bool GattMfvVoiceService::isReady() const -{ - return m_stateMachine.inState(EnableNotificationsState); -} - -BleRcuMfvVoiceService::DetectionType GattMfvVoiceService::detectionType() const -{ - return m_detectionType; -} - -BleRcuMfvVoiceService::DetectionData GattMfvVoiceService::detectionData() const -{ - return m_detectionData; -} - -BleRcuMfvVoiceService::ModelVersion GattMfvVoiceService::wakeWordModelVersion() const -{ - return m_modelVersion; -} - -bool GattMfvVoiceService::privacyEnabled() const -{ - return m_privacyEnabled; -} - -vector GattMfvVoiceService::modelConfiguration() const -{ - return m_modelConfigurationData; -} - -uint8_t GattMfvVoiceService::capabilities() const -{ - return m_capabilities; -} - -BleRcuMfvVoiceService::StreamStatsRaw GattMfvVoiceService::streamStats() const -{ - return m_streamStats; -} - - -// ----------------------------------------------------------------------------- -/*! - \reimp - - Starts the service by discovering characteristics from the GATT profile. - */ -bool GattMfvVoiceService::start(const shared_ptr &gattService) -{ - // sanity check the supplied info is valid - if (!gattService || !gattService->isValid() || (gattService->uuid() != m_serviceUuid)) { - XLOGD_WARN("invalid MFV voice gatt service info"); - return false; - } - - // discover characteristics (each is required) - if (!m_sessionStartCharacteristic || !m_sessionStartCharacteristic->isValid()) { - m_sessionStartCharacteristic = gattService->characteristic(m_sessionStartCharUuid); - if (!m_sessionStartCharacteristic || !m_sessionStartCharacteristic->isValid()) { - XLOGD_ERROR("failed to get MFV Session Start characteristic"); - return false; - } - } - if (!m_detectionDataCharacteristic || !m_detectionDataCharacteristic->isValid()) { - m_detectionDataCharacteristic = gattService->characteristic(m_detectionDataCharUuid); - if (!m_detectionDataCharacteristic || !m_detectionDataCharacteristic->isValid()) { - XLOGD_ERROR("failed to get MFV Detection Data characteristic"); - return false; - } - } - if (!m_modelVersionCharacteristic || !m_modelVersionCharacteristic->isValid()) { - m_modelVersionCharacteristic = gattService->characteristic(m_modelVersionCharUuid); - if (!m_modelVersionCharacteristic || !m_modelVersionCharacteristic->isValid()) { - XLOGD_ERROR("failed to get MFV Wake Word Model Version characteristic"); - return false; - } - } - if (!m_privacyCharacteristic || !m_privacyCharacteristic->isValid()) { - m_privacyCharacteristic = gattService->characteristic(m_privacyCharUuid); - if (!m_privacyCharacteristic || !m_privacyCharacteristic->isValid()) { - XLOGD_ERROR("failed to get MFV Privacy Settings characteristic"); - return false; - } - } - if (!m_modelConfigCharacteristic || !m_modelConfigCharacteristic->isValid()) { - m_modelConfigCharacteristic = gattService->characteristic(m_modelConfigCharUuid); - if (!m_modelConfigCharacteristic || !m_modelConfigCharacteristic->isValid()) { - XLOGD_ERROR("failed to get MFV Model Configuration characteristic"); - return false; - } - } - if (!m_capabilitiesCharacteristic || !m_capabilitiesCharacteristic->isValid()) { - m_capabilitiesCharacteristic = gattService->characteristic(m_capabilitiesCharUuid); - if (!m_capabilitiesCharacteristic || !m_capabilitiesCharacteristic->isValid()) { - XLOGD_ERROR("failed to get MFV Capabilities characteristic"); - return false; - } - } - - // check we're not already started - if (m_stateMachine.state() != IdleState) { - XLOGD_WARN("MFV voice service already started"); - return true; - } - - m_stateMachine.postEvent(StartServiceRequestEvent); - return true; -} - -// ----------------------------------------------------------------------------- -/*! - Stops the service and resets all characteristics. - */ -void GattMfvVoiceService::stop() -{ - m_stateMachine.cancelDelayedEvents(RetryStartNotifyEvent); - m_stateMachine.postEvent(StopServiceRequestEvent); -} - -// ----------------------------------------------------------------------------- -/*! - \internal - - Private slot called on entry to a new state. - */ -void GattMfvVoiceService::onEnteredState(int state) -{ - if (state == IdleState) { - m_stateMachine.cancelDelayedEvents(RetryStartNotifyEvent); - - if (m_sessionStartCharacteristic) { - m_sessionStartCharacteristic->disableNotifications(); - } - if (m_detectionDataCharacteristic) { - m_detectionDataCharacteristic->disableNotifications(); - } - if (m_privacyCharacteristic) { - m_privacyCharacteristic->disableNotifications(); - } - - m_sessionStartCharacteristic.reset(); - m_detectionDataCharacteristic.reset(); - m_modelVersionCharacteristic.reset(); - m_privacyCharacteristic.reset(); - m_modelConfigCharacteristic.reset(); - m_capabilitiesCharacteristic.reset(); - - } else if (state == ReadInitialValuesState) { - // Read all readable characteristics. Track completion with a counter; - // the last read to finish will post the InitialValuesRetrievedEvent. - m_initialReadsRemaining = 4; - requestCapabilities(); - requestModelVersion(); - requestPrivacy(); - requestModelConfig(); - - } else if (state == EnableNotificationsState) { - requestStartSessionStartNotify(); - requestStartDetectionDataNotify(); - requestStartPrivacyNotify(); - - // Emit the ready signal so GattServices knows the service is up - m_readySlots.invoke(); - } -} - - -// ============================================================================= -// Initial value reads -// ============================================================================= - -// ----------------------------------------------------------------------------- -/*! - \internal - - Reads the MFV Capabilities characteristic (1 byte bitmask). - */ -void GattMfvVoiceService::requestCapabilities() -{ - auto replyHandler = [this](PendingReply> *reply) - { - if (reply->isError()) { - XLOGD_ERROR("failed to read MFV Capabilities due to <%s>", reply->errorMessage().c_str()); - } else { - std::vector value = reply->result(); - if (value.size() == 1) { - m_capabilities = value[0]; - XLOGD_INFO("MFV Capabilities = 0x%02X (midfield=%d privacy_ctrl=%d soww_eoww=%d aad_ctrl=%d)", - m_capabilities, - !!(m_capabilities & MidfieldVoiceCapable), - !!(m_capabilities & SoftwarePrivacyControl), - !!(m_capabilities & SowwEowwTimingAvailable), - !!(m_capabilities & AadSensitivityControlAvailable)); - } else { - XLOGD_ERROR("MFV Capabilities has invalid length (%zu bytes, expected 1)", value.size()); - } - } - if (--m_initialReadsRemaining <= 0) { - m_stateMachine.postEvent(InitialValuesRetrievedEvent); - } - }; - - m_capabilitiesCharacteristic->readValue(PendingReply>(m_isAlive, replyHandler)); -} - -// ----------------------------------------------------------------------------- -/*! - \internal - - Reads the Wake Word Model Version characteristic (2 bytes: major, minor). - */ -void GattMfvVoiceService::requestModelVersion() -{ - auto replyHandler = [this](PendingReply> *reply) - { - if (reply->isError()) { - XLOGD_ERROR("failed to read MFV Model Version due to <%s>", reply->errorMessage().c_str()); - } else { - std::vector value = reply->result(); - if (value.size() == 2) { - m_modelVersion.major = value[0]; - m_modelVersion.minor = value[1]; - XLOGD_INFO("MFV Wake Word Model Version = %u.%u", m_modelVersion.major, m_modelVersion.minor); - } else { - XLOGD_ERROR("MFV Model Version has invalid length (%zu bytes, expected 2)", value.size()); - } - } - if (--m_initialReadsRemaining <= 0) { - m_stateMachine.postEvent(InitialValuesRetrievedEvent); - } - }; - - m_modelVersionCharacteristic->readValue(PendingReply>(m_isAlive, replyHandler)); -} - -// ----------------------------------------------------------------------------- -/*! - \internal - - Reads the Privacy Settings characteristic (1 byte boolean). - */ -void GattMfvVoiceService::requestPrivacy() -{ - auto replyHandler = [this](PendingReply> *reply) - { - if (reply->isError()) { - XLOGD_ERROR("failed to read MFV Privacy due to <%s>", reply->errorMessage().c_str()); - } else { - std::vector value = reply->result(); - if (value.size() == 1) { - m_privacyEnabled = (value[0] != 0); - XLOGD_INFO("MFV Privacy = %s", m_privacyEnabled ? "enabled" : "disabled"); - } else { - XLOGD_ERROR("MFV Privacy has invalid length (%zu bytes, expected 1)", value.size()); - } - } - if (--m_initialReadsRemaining <= 0) { - m_stateMachine.postEvent(InitialValuesRetrievedEvent); - } - }; - - m_privacyCharacteristic->readValue(PendingReply>(m_isAlive, replyHandler)); -} - -// ----------------------------------------------------------------------------- -/*! - \internal - - Reads the Model Configuration characteristic (3 bytes: sensitivity, - secondary sensitivity, AAD sensitivity). - */ -void GattMfvVoiceService::requestModelConfig() -{ - auto replyHandler = [this](PendingReply> *reply) - { - if (reply->isError()) { - XLOGD_ERROR("failed to read MFV Model Config due to <%s>", reply->errorMessage().c_str()); - } else { - std::vector value = reply->result(); - if (value.size() == 3) { - m_modelConfigurationData = value; - XLOGD_INFO("MFV Model Config: sensitivity=%u secondary=%u aad=%u", - value[0], value[1], value[2]); - } else { - XLOGD_ERROR("MFV Model Config has invalid length (%zu bytes, expected 3)", value.size()); - } - } - if (--m_initialReadsRemaining <= 0) { - m_stateMachine.postEvent(InitialValuesRetrievedEvent); - } - }; - - m_modelConfigCharacteristic->readValue(PendingReply>(m_isAlive, replyHandler)); -} - - -// ============================================================================= -// Notification subscriptions -// ============================================================================= - -// ----------------------------------------------------------------------------- -/*! - \internal - - Enables notifications on the Session Start characteristic (0xEA08). - On notification: 1 byte detection type flags. - */ -void GattMfvVoiceService::requestStartSessionStartNotify() -{ - auto replyHandler = [this](PendingReply<> *reply) - { - if (reply->isError()) { - XLOGD_ERROR("failed to enable Session Start notifications due to <%s>", - reply->errorMessage().c_str()); - m_stateMachine.cancelDelayedEvents(RetryStartNotifyEvent); - m_stateMachine.postDelayedEvent(RetryStartNotifyEvent, 2000); - } else { - XLOGD_DEBUG("Session Start notifications enabled successfully"); - } - }; - - m_sessionStartCharacteristic->enableNotifications( - Slot &>(m_isAlive, - std::bind(&GattMfvVoiceService::onSessionStartChanged, this, std::placeholders::_1)), - PendingReply<>(m_isAlive, replyHandler)); -} - -// ----------------------------------------------------------------------------- -/*! - \internal - - Enables notifications on the Detection Data characteristic (0xEA09). - On notification: 6 bytes (start_lo, start_hi, end_lo, end_hi, conf_lo, conf_hi). - */ -void GattMfvVoiceService::requestStartDetectionDataNotify() -{ - auto replyHandler = [this](PendingReply<> *reply) - { - if (reply->isError()) { - XLOGD_ERROR("failed to enable Detection Data notifications due to <%s>", - reply->errorMessage().c_str()); - m_stateMachine.cancelDelayedEvents(RetryStartNotifyEvent); - m_stateMachine.postDelayedEvent(RetryStartNotifyEvent, 2000); - } else { - XLOGD_DEBUG("Detection Data notifications enabled successfully"); - } - }; - - m_detectionDataCharacteristic->enableNotifications( - Slot &>(m_isAlive, - std::bind(&GattMfvVoiceService::onDetectionDataChanged, this, std::placeholders::_1)), - PendingReply<>(m_isAlive, replyHandler)); -} - -// ----------------------------------------------------------------------------- -/*! - \internal - - Enables notifications on the Privacy Settings characteristic (0xEA0B). - On notification: 1 byte boolean (1 = enabled, 0 = disabled). - */ -void GattMfvVoiceService::requestStartPrivacyNotify() -{ - auto replyHandler = [this](PendingReply<> *reply) - { - if (reply->isError()) { - XLOGD_ERROR("failed to enable Privacy notifications due to <%s>", - reply->errorMessage().c_str()); - m_stateMachine.cancelDelayedEvents(RetryStartNotifyEvent); - m_stateMachine.postDelayedEvent(RetryStartNotifyEvent, 2000); - } else { - XLOGD_DEBUG("Privacy notifications enabled successfully"); - } - }; - - m_privacyCharacteristic->enableNotifications( - Slot &>(m_isAlive, - std::bind(&GattMfvVoiceService::onPrivacyChanged, this, std::placeholders::_1)), - PendingReply<>(m_isAlive, replyHandler)); -} - - -// ============================================================================= -// Notification handlers (payload parsers) -// ============================================================================= - -// ----------------------------------------------------------------------------- -/*! - \internal - - Called when a Session Start notification is received from the RCU. - Payload: 1 byte detection type (0x01=FullPower, 0x02=AAD, 0x03=BelowThreshold). - */ -void GattMfvVoiceService::onSessionStartChanged(const std::vector &newValue) -{ - if (newValue.size() != 1) { - XLOGD_ERROR("Session Start notification has invalid length (%zu bytes, expected 1)", newValue.size()); - return; - } - - uint8_t raw = newValue[0]; - if (raw < 0x01 || raw > 0x03) { - XLOGD_WARN("Session Start notification has unknown detection type 0x%02X", raw); - } - - m_detectionType = static_cast(raw); - XLOGD_INFO("MFV Session Start: detection type = 0x%02X", raw); - - m_detectionTypeChangedSlots.invoke(m_detectionType); -} - -// ----------------------------------------------------------------------------- -/*! - \internal - - Called when a Detection Data notification is received from the RCU. - Payload: 6 bytes, little-endian. - Bytes 0-1: start of wake word (uint16_t) - Bytes 2-3: end of wake word (uint16_t) - Bytes 4-5: confidence score x10 (uint16_t) - */ -void GattMfvVoiceService::onDetectionDataChanged(const std::vector &newValue) -{ - if (newValue.size() != 6) { - XLOGD_ERROR("Detection Data notification has invalid length (%zu bytes, expected 6)", newValue.size()); - return; - } - - m_detectionData.start = static_cast(newValue[0]) | (static_cast(newValue[1]) << 8); - m_detectionData.end = static_cast(newValue[2]) | (static_cast(newValue[3]) << 8); - m_detectionData.confidence_x10 = static_cast(newValue[4]) | (static_cast(newValue[5]) << 8); - - XLOGD_INFO("MFV Detection Data: start=%u end=%u confidence=%.1f%%", - m_detectionData.start, m_detectionData.end, - m_detectionData.confidence_x10 / 10.0); - - m_detectionDataChangedSlots.invoke(m_detectionData); -} - -// ----------------------------------------------------------------------------- -/*! - \internal - - Called when a Privacy Settings notification is received from the RCU. - Payload: 1 byte boolean (1 = enabled, 0 = disabled). - */ -void GattMfvVoiceService::onPrivacyChanged(const std::vector &newValue) -{ - if (newValue.size() != 1) { - XLOGD_ERROR("Privacy notification has invalid length (%zu bytes, expected 1)", newValue.size()); - return; - } - - m_privacyEnabled = (newValue[0] != 0); - XLOGD_INFO("MFV Privacy changed: %s", m_privacyEnabled ? "enabled" : "disabled"); - - m_privacyChangedSlots.invoke(m_privacyEnabled); -} - - -// ============================================================================= -// Write handlers -// ============================================================================= - -// ----------------------------------------------------------------------------- -/*! - \reimp - - Writes the Privacy Settings characteristic. - Payload: 1 byte (1 = enable, 0 = disable). - */ -void GattMfvVoiceService::writePrivacy(bool enabled, PendingReply<> &&reply) -{ - if (!isReady()) { - reply.setError("MFV service is not ready"); - reply.finish(); - return; - } - - if (m_promiseResults) { - reply.setError("MFV write already in progress"); - reply.finish(); - return; - } - - if (!m_privacyCharacteristic || !m_privacyCharacteristic->isValid()) { - reply.setError("MFV Privacy characteristic is not valid"); - reply.finish(); - return; - } - - m_promiseResults = make_shared>(std::move(reply)); - - const vector value(1, enabled ? 0x01 : 0x00); - XLOGD_INFO("Writing MFV Privacy = %s", enabled ? "enabled" : "disabled"); - - m_privacyCharacteristic->writeValue(value, PendingReply<>(m_isAlive, - std::bind(&GattMfvVoiceService::onWritePrivacyReply, this, std::placeholders::_1))); -} - -// ----------------------------------------------------------------------------- -/*! - \internal - - Callback for the Privacy write completion. - */ -void GattMfvVoiceService::onWritePrivacyReply(PendingReply<> *reply) -{ - if (reply->isError()) { - XLOGD_ERROR("failed to write MFV Privacy due to <%s>", reply->errorMessage().c_str()); - if (m_promiseResults) { - m_promiseResults->setError(reply->errorMessage()); - m_promiseResults->finish(); - m_promiseResults.reset(); - } - } else { - XLOGD_INFO("MFV Privacy written successfully"); - if (m_promiseResults) { - m_promiseResults->finish(); - m_promiseResults.reset(); - } - } -} - -// ----------------------------------------------------------------------------- -/*! - \reimp - - Writes the Model Configuration characteristic. - Payload: 3 bytes (sensitivity, secondary sensitivity, AAD sensitivity). - AAD sensitivity must be in [60..95] in 5 dB increments. - */ -void GattMfvVoiceService::writeModelConfiguration(uint8_t sensitivity, uint8_t secondary, uint8_t aad, PendingReply<> &&reply) -{ - if (!isReady()) { - reply.setError("MFV service is not ready"); - reply.finish(); - return; - } - - if (m_promiseResults) { - reply.setError("MFV write already in progress"); - reply.finish(); - return; - } - - if (!m_modelConfigCharacteristic || !m_modelConfigCharacteristic->isValid()) { - reply.setError("MFV Model Configuration characteristic is not valid"); - reply.finish(); - return; - } - - // Validate AAD sensitivity range per spec: [60..95] in 5 dB increments - if (aad != 0 && (aad < 60 || aad > 95 || (aad % 5) != 0)) { - XLOGD_ERROR("MFV AAD sensitivity %u is out of valid range [60..95 step 5]", aad); - reply.setError("AAD sensitivity out of valid range [60..95 step 5]"); - reply.finish(); - return; - } - - m_promiseResults = make_shared>(std::move(reply)); - - const vector value = { sensitivity, secondary, aad }; - XLOGD_INFO("Writing MFV Model Config: sensitivity=%u secondary=%u aad=%u", - sensitivity, secondary, aad); - - m_modelConfigCharacteristic->writeValue(value, PendingReply<>(m_isAlive, - std::bind(&GattMfvVoiceService::onWriteModelConfigReply, this, std::placeholders::_1))); -} - -// ----------------------------------------------------------------------------- -/*! - \internal - - Callback for the Model Configuration write completion. - */ -void GattMfvVoiceService::onWriteModelConfigReply(PendingReply<> *reply) -{ - if (reply->isError()) { - XLOGD_ERROR("failed to write MFV Model Config due to <%s>", reply->errorMessage().c_str()); - if (m_promiseResults) { - m_promiseResults->setError(reply->errorMessage()); - m_promiseResults->finish(); - m_promiseResults.reset(); - } - } else { - XLOGD_INFO("MFV Model Config written successfully"); - if (m_promiseResults) { - m_promiseResults->finish(); - m_promiseResults.reset(); - } - } -} diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h b/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h deleted file mode 100644 index 09577cb7..00000000 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_mfvvoiceservice.h +++ /dev/null @@ -1,118 +0,0 @@ -#ifndef GATT_MFVVOICESERVICE_H -#define GATT_MFVVOICESERVICE_H - -#include "blercu/bleservices/blercumfvvoiceservice.h" -#include "utils/bleuuid.h" -#include "utils/statemachine.h" - -#include - -class BleGattService; -class BleGattCharacteristic; - -class GattMfvVoiceService : public BleRcuMfvVoiceService -{ -public: - GattMfvVoiceService(GMainLoop *mainLoop = NULL); - ~GattMfvVoiceService() final; - -public: - static BleUuid uuid(); - -public: - bool isReady() const override; - - DetectionType detectionType() const override; - DetectionData detectionData() const override; - ModelVersion wakeWordModelVersion() const override; - bool privacyEnabled() const override; - std::vector modelConfiguration() const override; - uint8_t capabilities() const override; - StreamStatsRaw streamStats() const override; - - void writePrivacy(bool enabled, PendingReply<> &&reply) override; - void writeModelConfiguration(uint8_t sensitivity, uint8_t secondary, uint8_t aad, PendingReply<> &&reply) override; - -public: - bool start(const std::shared_ptr &gattService); - void stop(); - -// signals: - inline void addReadySlot(const Slot<> &func) - { - m_readySlots.addSlot(func); - } - -private: - enum State { - IdleState, - RunningState, - ReadInitialValuesState, - EnableNotificationsState, - }; - - void init(); - - void onEnteredState(int state); - - // Initial reads - void requestCapabilities(); - void requestModelVersion(); - void requestPrivacy(); - void requestModelConfig(); - - // Enable notifications - void requestStartSessionStartNotify(); - void requestStartDetectionDataNotify(); - void requestStartPrivacyNotify(); - - // Notification handlers - void onSessionStartChanged(const std::vector &newValue); - void onDetectionDataChanged(const std::vector &newValue); - void onPrivacyChanged(const std::vector &newValue); - - // Write reply handlers - void onWritePrivacyReply(PendingReply<> *reply); - void onWriteModelConfigReply(PendingReply<> *reply); - -private: - std::shared_ptr m_isAlive; - - StateMachine m_stateMachine; - - DetectionType m_detectionType; - DetectionData m_detectionData; - ModelVersion m_modelVersion; - bool m_privacyEnabled; - std::vector m_modelConfigurationData; - uint8_t m_capabilities; - StreamStatsRaw m_streamStats; - - int m_initialReadsRemaining; - - std::shared_ptr m_sessionStartCharacteristic; - std::shared_ptr m_detectionDataCharacteristic; - std::shared_ptr m_modelVersionCharacteristic; - std::shared_ptr m_privacyCharacteristic; - std::shared_ptr m_modelConfigCharacteristic; - std::shared_ptr m_capabilitiesCharacteristic; - - std::shared_ptr> m_promiseResults; - -private: - static const BleUuid m_serviceUuid; - static const BleUuid m_sessionStartCharUuid; - static const BleUuid m_detectionDataCharUuid; - static const BleUuid m_modelVersionCharUuid; - static const BleUuid m_privacyCharUuid; - static const BleUuid m_modelConfigCharUuid; - static const BleUuid m_capabilitiesCharUuid; - -private: - static const Event::Type StartServiceRequestEvent = Event::Type(Event::User + 1); - static const Event::Type StopServiceRequestEvent = Event::Type(Event::User + 2); - static const Event::Type InitialValuesRetrievedEvent = Event::Type(Event::User + 3); - static const Event::Type RetryStartNotifyEvent = Event::Type(Event::User + 4); -}; - -#endif // !defined(GATT_MFVVOICESERVICE_H) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_services.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_services.cpp index d029eba0..6029daca 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_services.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_services.cpp @@ -34,7 +34,6 @@ #include "gatt_deviceinfoservice.h" #include "gatt_findmeservice.h" #include "gatt_infraredservice.h" -#include "gatt_mfvvoiceservice.h" #include "gatt_upgradeservice.h" #include "gatt_remotecontrolservice.h" @@ -70,7 +69,6 @@ GattServices::GattServices(const BleAddress &address, , m_batteryService(make_shared(mainLoop)) , m_findMeService(make_shared(mainLoop)) , m_remoteControlService(make_shared(mainLoop)) - , m_mfvVoiceService(make_shared(mainLoop)) { // Add the built-in services to the list of services available @@ -128,7 +126,6 @@ void GattServices::init() m_stateMachine.addState(ResolvedServicesSuperState, StartingInfraredServiceState, string("StartingInfraredService")); m_stateMachine.addState(ResolvedServicesSuperState, StartingUpgradeServiceState, string("StartingUpgradeServiceState")); m_stateMachine.addState(ResolvedServicesSuperState, StartingRemoteControlServiceState, string("StartingRemoteControlServiceState")); - m_stateMachine.addState(ResolvedServicesSuperState, StartingMfvVoiceServiceState, string("StartingMfvVoiceServiceState")); m_stateMachine.addState(ResolvedServicesSuperState, ReadyState, string("Ready")); m_stateMachine.addState(StoppingState, string("Stopping")); @@ -145,8 +142,7 @@ void GattServices::init() // Need to start RemoteControl service first so that we read the last keypress characterisitic as soon as possible m_stateMachine.addTransition(GettingGattServicesState, GotGattServicesEvent, StartingRemoteControlServiceState); - m_stateMachine.addTransition(StartingRemoteControlServiceState, RemoteControlServiceReadyEvent, StartingMfvVoiceServiceState); - m_stateMachine.addTransition(StartingMfvVoiceServiceState, MfvVoiceServiceReadyEvent, StartingDeviceInfoServiceState); + m_stateMachine.addTransition(StartingRemoteControlServiceState, RemoteControlServiceReadyEvent, StartingDeviceInfoServiceState); m_stateMachine.addTransition(StartingDeviceInfoServiceState, DeviceInfoServiceReadyEvent, StartingBatteryServiceState); m_stateMachine.addTransition(StartingBatteryServiceState, BatteryServiceReadyEvent, StartingFindMeServiceState); @@ -274,17 +270,12 @@ void GattServices::onEnteredState(int state) serviceOk = startService(m_remoteControlService, RemoteControlServiceReadyEvent, serviceNotUsed); break; - case StartingMfvVoiceServiceState: - serviceOk = startService(m_mfvVoiceService, MfvVoiceServiceReadyEvent, serviceNotUsed); - break; - case ReadyState: m_readySlots.invoke(); break; case StoppingState: m_remoteControlService->stop(); - m_mfvVoiceService->stop(); m_deviceInfoService->stop(); m_batteryService->stop(); m_findMeService->stop(); @@ -523,13 +514,3 @@ shared_ptr GattServices::remoteControlService() cons { return m_remoteControlService; } - -// ----------------------------------------------------------------------------- -/*! - \overload - - */ -shared_ptr GattServices::mfvVoiceService() const -{ - return m_mfvVoiceService; -} diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_services.h b/src/ble/hal/blercu/bleservices/gatt/gatt_services.h index d2b0ec9c..d1bd3b54 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_services.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_services.h @@ -46,7 +46,6 @@ class GattFindMeService; class GattInfraredService; class GattUpgradeService; class GattRemoteControlService; -class GattMfvVoiceService; @@ -73,7 +72,6 @@ class GattServices : public BleRcuServices std::shared_ptr infraredService() const override; std::shared_ptr upgradeService() const override; std::shared_ptr remoteControlService() const override; - std::shared_ptr mfvVoiceService() const override; private: template @@ -96,7 +94,6 @@ class GattServices : public BleRcuServices StartingTouchServiceState, StartingUpgradeServiceState, StartingRemoteControlServiceState, - StartingMfvVoiceServiceState, ReadyState, StoppingState }; @@ -134,7 +131,6 @@ class GattServices : public BleRcuServices std::size_t m_upgradeServiceIndex = 0; std::shared_ptr m_remoteControlService; - std::shared_ptr m_mfvVoiceService; private: static const Event::Type StartServicesRequestEvent = Event::Type(Event::User + 1); @@ -148,8 +144,7 @@ class GattServices : public BleRcuServices static const Event::Type InfraredServiceReadyEvent = Event::Type(Event::User + 8); static const Event::Type UpgradeServiceReadyEvent = Event::Type(Event::User + 9); static const Event::Type RemoteControlServiceReadyEvent = Event::Type(Event::User + 10); - static const Event::Type MfvVoiceServiceReadyEvent = Event::Type(Event::User + 11); - static const Event::Type ServicesStoppedEvent = Event::Type(Event::User + 12); + static const Event::Type ServicesStoppedEvent = Event::Type(Event::User + 11); }; diff --git a/src/ble/hal/blercu/bluez/blercudevice.cpp b/src/ble/hal/blercu/bluez/blercudevice.cpp index 15025041..7cd77f6e 100644 --- a/src/ble/hal/blercu/bluez/blercudevice.cpp +++ b/src/ble/hal/blercu/bluez/blercudevice.cpp @@ -43,7 +43,7 @@ #include "blercu/bleservices/blercuupgradeservice.h" #include "blercu/bleservices/blercufindmeservice.h" #include "blercu/bleservices/blercuremotecontrolservice.h" -#include "blercu/bleservices/blercumfvvoiceservice.h" +#include "blercu/bleservices/blercuaudioservice.h" #include "interfaces/bluezdeviceinterface.h" @@ -832,14 +832,6 @@ std::shared_ptr BleRcuDeviceBluez::remoteControlServ } } -std::shared_ptr BleRcuDeviceBluez::mfvVoiceService() const -{ - if (m_services && m_services->isValid()) { - return m_services->mfvVoiceService(); - } else { - return nullptr; - } -} // ----------------------------------------------------------------------------- /*! \fn bool BleRcuDevice::isValid() const @@ -1112,26 +1104,6 @@ void BleRcuDeviceBluez::addAdvConfigCustomListChangedSlot(const Slot &func) -{ - (void)func; - // MFV detection type slots are registered directly via mfvVoiceService() - // in ctrlm_ble_rcu_interface.cpp -} - -void BleRcuDeviceBluez::addMfvPrivacyChangedSlot(const Slot &func) -{ - auto svc = mfvVoiceService(); - if (svc) { - svc->addPrivacyChangedSlot(func); - } -} - - //////////////////////////////////////////////////////////////////////////////////////////////////////// // Voice Service //////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/ble/hal/blercu/bluez/blercudevice_p.h b/src/ble/hal/blercu/bluez/blercudevice_p.h index 68179f1e..39ecd86c 100644 --- a/src/ble/hal/blercu/bluez/blercudevice_p.h +++ b/src/ble/hal/blercu/bluez/blercudevice_p.h @@ -108,7 +108,6 @@ class BleRcuDeviceBluez : public BleRcuDevice std::shared_ptr infraredService() const override; std::shared_ptr upgradeService() const override; std::shared_ptr remoteControlService() const override; - std::shared_ptr mfvVoiceService() const override; void onDeviceConnectedChanged(bool connected); void onDevicePairedChanged(bool paired); @@ -153,10 +152,6 @@ class BleRcuDeviceBluez : public BleRcuDevice void addAdvConfigChangedSlot(const Slot &func) override; void addAdvConfigCustomListChangedSlot(const Slot &> &func) override; - // MFV Voice Service - void addMfvDetectionTypeChangedSlot(const Slot &func) override; - void addMfvPrivacyChangedSlot(const Slot &func) override; - // Voice Service uint8_t audioGainLevel() const override; void setAudioGainLevel(uint8_t value) override; diff --git a/src/ble/hal/utils/bleuuid.cpp b/src/ble/hal/utils/bleuuid.cpp index a785ed8b..73b4b7a7 100644 --- a/src/ble/hal/utils/bleuuid.cpp +++ b/src/ble/hal/utils/bleuuid.cpp @@ -113,7 +113,6 @@ bool ble_uuid_names_init(void) BleUuid(BleUuid::PeripheralPreferredConnectionParameters, std::string("Peripheral Preferred Connection Parameters")); BleUuid(BleUuid::ClientCharacteristicConfiguration, std::string("Client Characteristic Configuration")); BleUuid(BleUuid::ReportReference, std::string("Report Reference")); - BleUuid(BleUuid::MfvVoice, std::string("MFV Voice")); BleUuid(BleUuid::SessionStart, std::string("Session Start")); BleUuid(BleUuid::DetectionData, std::string("Detection Data")); BleUuid(BleUuid::WakeWordModelVersion, std::string("Wake Word Model Version")); diff --git a/src/ble/hal/utils/bleuuid.h b/src/ble/hal/utils/bleuuid.h index 583ccaec..cec73d45 100644 --- a/src/ble/hal/utils/bleuuid.h +++ b/src/ble/hal/utils/bleuuid.h @@ -53,7 +53,6 @@ class BleUuid RdkInfrared = 0xf801, RdkFirmwareUpgrade = 0xf802, RdkRemoteControl = 0xf803, - MfvVoice = 0xEA08, }; From 42fea9431e24c25d65a56fb9723d06c75deadd99 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 25 Jun 2026 13:23:09 +0000 Subject: [PATCH 12/86] Removing unused file --- .../bleservices/blercumfvvoiceservice.h | 129 ------------------ 1 file changed, 129 deletions(-) delete mode 100644 src/ble/hal/blercu/bleservices/blercumfvvoiceservice.h diff --git a/src/ble/hal/blercu/bleservices/blercumfvvoiceservice.h b/src/ble/hal/blercu/bleservices/blercumfvvoiceservice.h deleted file mode 100644 index eef26d21..00000000 --- a/src/ble/hal/blercu/bleservices/blercumfvvoiceservice.h +++ /dev/null @@ -1,129 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2017-2020 Sky UK - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * Changes made by Comcast - * Copyright 2024 Comcast Cable Communications Management, LLC - * Licensed under the Apache License, Version 2.0 - */ - -// -// blercumfvvoiceservice.h -// - -#ifndef BLERCUMFVVOICESERVICE_H -#define BLERCUMFVVOICESERVICE_H - -#include "utils/pendingreply.h" -#include "utils/slot.h" - -#include -#include - - -class BleRcuMfvVoiceService -{ - -protected: - BleRcuMfvVoiceService() = default; - -public: - virtual ~BleRcuMfvVoiceService() = default; - -public: - enum DetectionType { - FullPower=0x01, - Aad=0x02, - BelowThreshold=0x03, - }; - - struct DetectionData { - uint16_t start; - uint16_t end; - uint16_t confidence_x10; - }; - - struct ModelVersion { - uint8_t major; - uint8_t minor; - }; - - struct StreamStatsRaw { - std::vector bytes; - }; - - enum Capabilities { - MidfieldVoiceCapable = (1 << 0), - SoftwarePrivacyControl = (1 << 1), - SowwEowwTimingAvailable = (1 << 2), - AadSensitivityControlAvailable = (1 << 3) - }; - -public: - virtual bool isReady() const = 0; - - virtual DetectionType detectionType() const = 0; - virtual DetectionData detectionData() const = 0; - virtual ModelVersion wakeWordModelVersion() const = 0; - virtual bool privacyEnabled() const = 0; - virtual std::vector modelConfiguration() const = 0; - virtual uint8_t capabilities() const = 0; - virtual StreamStatsRaw streamStats() const = 0; - - virtual void writePrivacy(bool enabled, PendingReply<> &&reply) = 0; - virtual void writeModelConfiguration(uint8_t sensitivity, uint8_t secondary, uint8_t aad, PendingReply<> &&reply) = 0; - -// signals: - inline void addDetectionTypeChangedSlot(const Slot &func) - { - m_detectionTypeChangedSlots.addSlot(func); - } - inline void addDetectionDataChangedSlot(const Slot &func) - { - m_detectionDataChangedSlots.addSlot(func); - } - inline void addPrivacyChangedSlot(const Slot &func) - { - m_privacyChangedSlots.addSlot(func); - } - inline void addCapabilitiesChangedSlot(const Slot &func) - { - m_capabilitiesChangedSlots.addSlot(func); - } - inline void addStreamStatsChangedSlot(const Slot &func) - { - m_streamStatsChangedSlots.addSlot(func); - } - inline void addReadySlot(const Slot<> &func) - { - m_readySlots.addSlot(func); - } - -protected: - Slots m_detectionTypeChangedSlots; - Slots m_detectionDataChangedSlots; - Slots m_privacyChangedSlots; - Slots m_capabilitiesChangedSlots; - Slots m_streamStatsChangedSlots; - Slots<> m_readySlots; - -}; - - -#endif // BLERCUMFVVOICESERVICE_H From cb35b0f58b3536d333fd14eb90a911666deebd61 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 25 Jun 2026 13:36:51 +0000 Subject: [PATCH 13/86] Removing uneeded diffs --- src/ble/hal/blercu/bluez/blercudevice.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ble/hal/blercu/bluez/blercudevice.cpp b/src/ble/hal/blercu/bluez/blercudevice.cpp index 7cd77f6e..6fc62e26 100644 --- a/src/ble/hal/blercu/bluez/blercudevice.cpp +++ b/src/ble/hal/blercu/bluez/blercudevice.cpp @@ -831,7 +831,6 @@ std::shared_ptr BleRcuDeviceBluez::remoteControlServ return nullptr; } } - // ----------------------------------------------------------------------------- /*! \fn bool BleRcuDevice::isValid() const From 0e34662da8b59e468bd4546b6e5f0f7e4ef09b50 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 25 Jun 2026 13:42:07 +0000 Subject: [PATCH 14/86] Updates --- src/ble/ctrlm_ble_rcu_interface.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ble/ctrlm_ble_rcu_interface.cpp b/src/ble/ctrlm_ble_rcu_interface.cpp index 4d65eb3b..f911574a 100644 --- a/src/ble/ctrlm_ble_rcu_interface.cpp +++ b/src/ble/ctrlm_ble_rcu_interface.cpp @@ -486,8 +486,7 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) // MFV Voice characteristics (part of the RDK Voice / audio service) - auto audioSvc = device->audioService(); - if (audioSvc) { + if (auto audioSvc = device->audioService()) { auto mfvDetectionTypeSlot = [this, address](BleRcuAudioService::DetectionType type) { XLOGD_INFO("BLE RCU %s MFV detection type = 0x%02X", address.toString().c_str(), static_cast(type)); @@ -734,8 +733,7 @@ ctrlm_hal_ble_rcu_data_t ctrlm_ble_rcu_interface_t::getAllDeviceProperties(uint6 ret.audio_gain_level = device->audioGainLevel(); // MFV data (part of the RDK Voice / audio service) - auto audioSvc = device->audioService(); - if (audioSvc) { + if (auto audioSvc = device->audioService()) { auto detectionData = audioSvc->mfvDetectionData(); ret.mfv_detection_type = static_cast(audioSvc->mfvDetectionType()); ret.mfv_ww_start = detectionData.start; From c3e97e5cb3cbfb68618029ed97df5e48fa8cbd11 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 25 Jun 2026 13:52:28 +0000 Subject: [PATCH 15/86] Updates --- src/ble/ctrlm_ble_controller.cpp | 12 ++++++++---- src/ble/ctrlm_ble_controller.h | 7 ++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/ble/ctrlm_ble_controller.cpp b/src/ble/ctrlm_ble_controller.cpp index b3ec2e40..05fec2d1 100644 --- a/src/ble/ctrlm_ble_controller.cpp +++ b/src/ble/ctrlm_ble_controller.cpp @@ -535,10 +535,10 @@ uint8_t ctrlm_obj_controller_ble_t::getMfvDetectionType() const { return mfv_detection_type_; } -void ctrlm_obj_controller_ble_t::setMfvDetectionData(uint16_t start, uint16_t end, uint16_t confidence_x10) { +void ctrlm_obj_controller_ble_t::setMfvDetectionData(uint16_t start, uint16_t end, uint16_t confidence) { mfv_ww_start_ = start; mfv_ww_end_ = end; - mfv_confidence_x10_ = confidence_x10; + mfv_confidence_ = confidence; } uint16_t ctrlm_obj_controller_ble_t::getMfvWwStart() const { @@ -550,7 +550,11 @@ uint16_t ctrlm_obj_controller_ble_t::getMfvWwEnd() const { } uint16_t ctrlm_obj_controller_ble_t::getMfvConfidence() const { - return mfv_confidence_x10_; + return mfv_confidence_; +} + +float ctrlm_obj_controller_ble_t::getMfvConfidencePercent() const { + return mfv_confidence_ / 10.0f; } void ctrlm_obj_controller_ble_t::setMfvPrivacy(bool enabled) { @@ -714,7 +718,7 @@ void ctrlm_obj_controller_ble_t::print_status() { XLOGD_INFO("MFV Detection Type : 0x%02X", mfv_detection_type_); XLOGD_INFO("MFV WW Start : %u", mfv_ww_start_); XLOGD_INFO("MFV WW End : %u", mfv_ww_end_); - XLOGD_INFO("MFV Confidence : %.1f%%", mfv_confidence_x10_ / 10.0); + XLOGD_INFO("MFV Confidence : %.1f%%", mfv_confidence_ / 10.0); XLOGD_INFO("MFV Privacy : %s", mfv_privacy_enabled_ ? "enabled" : "disabled"); XLOGD_INFO("MFV Capabilities : 0x%02X", mfv_capabilities_); XLOGD_WARN("------------------------------------------------------------"); diff --git a/src/ble/ctrlm_ble_controller.h b/src/ble/ctrlm_ble_controller.h index 35a02e95..2930cc94 100644 --- a/src/ble/ctrlm_ble_controller.h +++ b/src/ble/ctrlm_ble_controller.h @@ -149,10 +149,11 @@ class ctrlm_obj_controller_ble_t : public ctrlm_obj_controller_t { void setMfvDetectionType(uint8_t type); uint8_t getMfvDetectionType() const; - void setMfvDetectionData(uint16_t start, uint16_t end, uint16_t confidence_x10); + void setMfvDetectionData(uint16_t start, uint16_t end, uint16_t confidence); uint16_t getMfvWwStart() const; uint16_t getMfvWwEnd() const; - uint16_t getMfvConfidence() const; + uint16_t getMfvConfidence() const; // returns raw value encoded as integer * 10 (e.g. 953 = 95.3%) + float getMfvConfidencePercent() const; // returns confidence as a percentage (e.g. 95.3) void setMfvPrivacy(bool enabled); bool getMfvPrivacy() const; void setMfvCapabilities(uint8_t caps); @@ -211,7 +212,7 @@ class ctrlm_obj_controller_ble_t : public ctrlm_obj_controller_t { uint8_t mfv_detection_type_ = 0; uint16_t mfv_ww_start_ = 0; uint16_t mfv_ww_end_ = 0; - uint16_t mfv_confidence_x10_ = 0; + uint16_t mfv_confidence_ = 0; // encoded as integer * 10 (e.g. 953 = 95.3%) bool mfv_privacy_enabled_ = false; uint8_t mfv_capabilities_ = 0; From bc4a206642e49fd553e7b871502d24d1b2c05552 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 25 Jun 2026 13:56:29 +0000 Subject: [PATCH 16/86] Updates --- src/ble/hal/blercu/bleservices/blercuaudioservice.h | 2 +- src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/blercuaudioservice.h b/src/ble/hal/blercu/bleservices/blercuaudioservice.h index c18065b1..6279acd8 100644 --- a/src/ble/hal/blercu/bleservices/blercuaudioservice.h +++ b/src/ble/hal/blercu/bleservices/blercuaudioservice.h @@ -84,7 +84,7 @@ class BleRcuAudioService virtual void status(uint32_t &lastError, uint32_t &expectedPackets, uint32_t &actualPackets, int32_t &voiceKeyHeldMs) = 0; -// MFV (Mic-Free Voice) types +// MFV types public: enum DetectionType { FullPower = 0x01, diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index b9d86bdb..4765ceb3 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -634,7 +634,7 @@ void GattAudioServiceRdk::setGainLevel(uint8_t level) // ============================================================================= -// MFV (Mic-Free Voice) implementation +// MFV implementation // ============================================================================= // ----------------------------------------------------------------------------- From 22351ea3fed93cd12c2dbd7b5c392bedc56059ca Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 25 Jun 2026 14:18:06 +0000 Subject: [PATCH 17/86] Fixing notifications --- .../gatt/gatt_audioservice_rdk.cpp | 23 ++++++++++++++++--- .../bleservices/gatt/gatt_audioservice_rdk.h | 1 + 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index 4765ceb3..a47ed0e8 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -107,6 +107,7 @@ bool GattAudioServiceRdk::start(const shared_ptr &gattServ // Discover MFV characteristics (optional - not all remotes support MFV) if (getMfvCharacteristics(gattService)) { m_mfvSupported = true; + m_mfvInitialReadsComplete = false; m_mfvInitialReadsRemaining = 4; requestMfvCapabilities(); requestMfvModelVersion(); @@ -158,6 +159,7 @@ void GattAudioServiceRdk::onEnteredIdle() { m_mfvModelConfigCharacteristic.reset(); m_mfvCapabilitiesCharacteristic.reset(); m_mfvSupported = false; + m_mfvInitialReadsComplete = false; GattAudioService::onEnteredIdle(); } @@ -218,6 +220,14 @@ void GattAudioServiceRdk::onEnteredEnableNotificationsState() } GattAudioService::onEnteredEnableNotificationsState(); + + // Enable MFV notifications here if initial reads have already completed, + // otherwise onMfvInitialReadComplete() will enable them when they finish. + if (m_mfvSupported && m_mfvInitialReadsComplete) { + requestStartMfvSessionStartNotify(); + requestStartMfvDetectionDataNotify(); + requestStartMfvPrivacyNotify(); + } } // ----------------------------------------------------------------------------- @@ -695,13 +705,20 @@ bool GattAudioServiceRdk::getMfvCharacteristics(const shared_ptr Date: Thu, 25 Jun 2026 15:39:50 +0000 Subject: [PATCH 18/86] Start voice session during mfv detection type property update --- src/ble/ctrlm_ble_network.cpp | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/ble/ctrlm_ble_network.cpp b/src/ble/ctrlm_ble_network.cpp index ba877f89..401a82a9 100644 --- a/src/ble/ctrlm_ble_network.cpp +++ b/src/ble/ctrlm_ble_network.cpp @@ -1934,10 +1934,37 @@ void ctrlm_obj_network_ble_t::ind_process_rcu_status(void *data, int size) { report_status = false; print_status = false; break; - case CTRLM_HAL_BLE_PROPERTY_MFV_DETECTION_TYPE: + case CTRLM_HAL_BLE_PROPERTY_MFV_DETECTION_TYPE: { controller->setMfvDetectionType(dqm->rcu_data.mfv_detection_type); XLOGD_INFO("Controller <%s> MFV detection type = 0x%02X", controller->ieee_address_get().to_string().c_str(), dqm->rcu_data.mfv_detection_type); + + // Start a voice session for FullPower (0x01) and AAD (0x02) detections. + // BelowThreshold (0x03) is a low-confidence secondary detection - do not start a session. + // TODO: Get clarity on if BelowThreshold detections should start a sessions (assume not for now) + uint8_t detection_type = dqm->rcu_data.mfv_detection_type; + if (detection_type == 0x01 || detection_type == 0x02) { + rdkx_timestamp_t detectionTime; + rdkx_timestamp_get(&detectionTime); + controller->setVoiceStartTime(detectionTime); + + XLOGD_INFO("------------------------------------------------------------------------"); + XLOGD_INFO("MFV wake word detected (%s) for device: %s", + detection_type == 0x01 ? "FullPower" : "AAD", + controller->ieee_address_get().to_string().c_str()); + XLOGD_INFO("------------------------------------------------------------------------"); + + ctrlm_voice_iarm_call_voice_session_t v_params; + v_params.ieee_address = dqm->rcu_data.ieee_address; + + ctrlm_main_queue_msg_voice_session_t msg; + errno_t safec_rc = memset_s(&msg, sizeof(msg), 0, sizeof(msg)); + ERR_CHK(safec_rc); + msg.params = &v_params; + + req_process_voice_session_begin(&msg, sizeof(msg)); + } break; + } case CTRLM_HAL_BLE_PROPERTY_MFV_DETECTION_DATA: controller->setMfvDetectionData(dqm->rcu_data.mfv_ww_start, dqm->rcu_data.mfv_ww_end, dqm->rcu_data.mfv_confidence_x10); XLOGD_INFO("Controller <%s> MFV detection data: start=%u end=%u confidence=%.1f%%", From 721e4187b3bb080c3dac507a87f0e25e2b04d26d Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 26 Jun 2026 13:10:32 +0000 Subject: [PATCH 19/86] Updates --- src/ble/ctrlm_ble_rcu_interface.cpp | 6 +++--- src/ble/hal/blercu/bleservices/blercuaudioservice.h | 2 +- .../blercu/bleservices/gatt/gatt_audioservice_rdk.cpp | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ble/ctrlm_ble_rcu_interface.cpp b/src/ble/ctrlm_ble_rcu_interface.cpp index f911574a..02016203 100644 --- a/src/ble/ctrlm_ble_rcu_interface.cpp +++ b/src/ble/ctrlm_ble_rcu_interface.cpp @@ -502,14 +502,14 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) auto mfvDetectionDataSlot = [this, address](const BleRcuAudioService::DetectionData &data) { XLOGD_INFO("BLE RCU %s MFV detection data: start=%u end=%u confidence=%.1f%%", - address.toString().c_str(), data.start, data.end, data.confidence_x10 / 10.0); + address.toString().c_str(), data.start, data.end, data.confidence / 10.0); ctrlm_hal_ble_RcuStatusData_t params; params.property_updated = CTRLM_HAL_BLE_PROPERTY_MFV_DETECTION_DATA; params.rcu_data.ieee_address = address.toUInt64(); params.rcu_data.mfv_ww_start = data.start; params.rcu_data.mfv_ww_end = data.end; - params.rcu_data.mfv_confidence_x10 = data.confidence_x10; + params.rcu_data.mfv_confidence_x10 = data.confidence; m_rcuStatusChangedSlots.invoke(¶ms); }; audioSvc->addMfvDetectionDataChangedSlot(Slot(m_isAlive, mfvDetectionDataSlot)); @@ -738,7 +738,7 @@ ctrlm_hal_ble_rcu_data_t ctrlm_ble_rcu_interface_t::getAllDeviceProperties(uint6 ret.mfv_detection_type = static_cast(audioSvc->mfvDetectionType()); ret.mfv_ww_start = detectionData.start; ret.mfv_ww_end = detectionData.end; - ret.mfv_confidence_x10 = detectionData.confidence_x10; + ret.mfv_confidence_x10 = detectionData.confidence; ret.mfv_privacy_enabled = audioSvc->mfvPrivacyEnabled(); ret.mfv_capabilities = audioSvc->mfvCapabilities(); } diff --git a/src/ble/hal/blercu/bleservices/blercuaudioservice.h b/src/ble/hal/blercu/bleservices/blercuaudioservice.h index 6279acd8..e8d390f8 100644 --- a/src/ble/hal/blercu/bleservices/blercuaudioservice.h +++ b/src/ble/hal/blercu/bleservices/blercuaudioservice.h @@ -95,7 +95,7 @@ class BleRcuAudioService struct DetectionData { uint16_t start = 0; uint16_t end = 0; - uint16_t confidence_x10 = 0; + uint16_t confidence = 0; // encoded as percentage * 10 (e.g. 953 = 95.3%) }; struct ModelVersion { diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index a47ed0e8..f608f4dd 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -947,7 +947,7 @@ void GattAudioServiceRdk::onMfvSessionStartChanged(const std::vector &n \internal Called when a Detection Data notification is received from the RCU. - Payload: 6 bytes, little-endian (start, end, confidence_x10). + Payload: 6 bytes, little-endian (start, end, confidence). */ void GattAudioServiceRdk::onMfvDetectionDataChanged(const std::vector &newValue) { @@ -956,13 +956,13 @@ void GattAudioServiceRdk::onMfvDetectionDataChanged(const std::vector & return; } - m_mfvDetectionData.start = static_cast(newValue[0]) | (static_cast(newValue[1]) << 8); - m_mfvDetectionData.end = static_cast(newValue[2]) | (static_cast(newValue[3]) << 8); - m_mfvDetectionData.confidence_x10 = static_cast(newValue[4]) | (static_cast(newValue[5]) << 8); + m_mfvDetectionData.start = static_cast(newValue[0]) | (static_cast(newValue[1]) << 8); + m_mfvDetectionData.end = static_cast(newValue[2]) | (static_cast(newValue[3]) << 8); + m_mfvDetectionData.confidence = static_cast(newValue[4]) | (static_cast(newValue[5]) << 8); XLOGD_INFO("MFV Detection Data: start=%u end=%u confidence=%.1f%%", m_mfvDetectionData.start, m_mfvDetectionData.end, - m_mfvDetectionData.confidence_x10 / 10.0); + m_mfvDetectionData.confidence / 10.0); m_mfvDetectionDataChangedSlots.invoke(m_mfvDetectionData); } From def3fe13ab1501337b700b5d5e803106629a0e59 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 26 Jun 2026 13:16:59 +0000 Subject: [PATCH 20/86] Updates --- src/ble/ctrlm_ble_rcu_interface.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ble/ctrlm_ble_rcu_interface.cpp b/src/ble/ctrlm_ble_rcu_interface.cpp index 02016203..8c1c8a19 100644 --- a/src/ble/ctrlm_ble_rcu_interface.cpp +++ b/src/ble/ctrlm_ble_rcu_interface.cpp @@ -485,7 +485,6 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) device->addAdvConfigCustomListChangedSlot(Slot &>(m_isAlive, advConfigCustomListChangedSlot)); - // MFV Voice characteristics (part of the RDK Voice / audio service) if (auto audioSvc = device->audioService()) { auto mfvDetectionTypeSlot = [this, address](BleRcuAudioService::DetectionType type) { From ea1b75d7caa18fcc89a9aa04dc621c6253ea48ab Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 26 Jun 2026 13:22:18 +0000 Subject: [PATCH 21/86] Updates --- include/ctrlm_hal_ble.h | 2 +- src/ble/ctrlm_ble_network.cpp | 4 ++-- src/ble/ctrlm_ble_rcu_interface.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/ctrlm_hal_ble.h b/include/ctrlm_hal_ble.h index ac2924fa..9ae5b4cb 100644 --- a/include/ctrlm_hal_ble.h +++ b/include/ctrlm_hal_ble.h @@ -130,7 +130,7 @@ typedef struct { uint8_t mfv_detection_type; uint16_t mfv_ww_start; uint16_t mfv_ww_end; - uint16_t mfv_confidence_x10; + uint16_t mfv_confidence; // encoded as percentage * 10 (e.g. 976 = 97.6%) bool mfv_privacy_enabled; uint8_t mfv_capabilities; } ctrlm_hal_ble_rcu_data_t; diff --git a/src/ble/ctrlm_ble_network.cpp b/src/ble/ctrlm_ble_network.cpp index 401a82a9..4460e2f3 100644 --- a/src/ble/ctrlm_ble_network.cpp +++ b/src/ble/ctrlm_ble_network.cpp @@ -1966,9 +1966,9 @@ void ctrlm_obj_network_ble_t::ind_process_rcu_status(void *data, int size) { break; } case CTRLM_HAL_BLE_PROPERTY_MFV_DETECTION_DATA: - controller->setMfvDetectionData(dqm->rcu_data.mfv_ww_start, dqm->rcu_data.mfv_ww_end, dqm->rcu_data.mfv_confidence_x10); + controller->setMfvDetectionData(dqm->rcu_data.mfv_ww_start, dqm->rcu_data.mfv_ww_end, dqm->rcu_data.mfv_confidence); XLOGD_INFO("Controller <%s> MFV detection data: start=%u end=%u confidence=%.1f%%", - controller->ieee_address_get().to_string().c_str(), dqm->rcu_data.mfv_ww_start, dqm->rcu_data.mfv_ww_end, dqm->rcu_data.mfv_confidence_x10 / 10.0); + controller->ieee_address_get().to_string().c_str(), dqm->rcu_data.mfv_ww_start, dqm->rcu_data.mfv_ww_end, dqm->rcu_data.mfv_confidence / 10.0); break; case CTRLM_HAL_BLE_PROPERTY_MFV_PRIVACY: controller->setMfvPrivacy(dqm->rcu_data.mfv_privacy_enabled); diff --git a/src/ble/ctrlm_ble_rcu_interface.cpp b/src/ble/ctrlm_ble_rcu_interface.cpp index 8c1c8a19..4ec7d4b1 100644 --- a/src/ble/ctrlm_ble_rcu_interface.cpp +++ b/src/ble/ctrlm_ble_rcu_interface.cpp @@ -508,7 +508,7 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) params.rcu_data.ieee_address = address.toUInt64(); params.rcu_data.mfv_ww_start = data.start; params.rcu_data.mfv_ww_end = data.end; - params.rcu_data.mfv_confidence_x10 = data.confidence; + params.rcu_data.mfv_confidence = data.confidence; m_rcuStatusChangedSlots.invoke(¶ms); }; audioSvc->addMfvDetectionDataChangedSlot(Slot(m_isAlive, mfvDetectionDataSlot)); @@ -737,7 +737,7 @@ ctrlm_hal_ble_rcu_data_t ctrlm_ble_rcu_interface_t::getAllDeviceProperties(uint6 ret.mfv_detection_type = static_cast(audioSvc->mfvDetectionType()); ret.mfv_ww_start = detectionData.start; ret.mfv_ww_end = detectionData.end; - ret.mfv_confidence_x10 = detectionData.confidence; + ret.mfv_confidence = detectionData.confidence; ret.mfv_privacy_enabled = audioSvc->mfvPrivacyEnabled(); ret.mfv_capabilities = audioSvc->mfvCapabilities(); } From f69e58ed5a9930195b393e266ad31607d4456e75 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 26 Jun 2026 13:36:30 +0000 Subject: [PATCH 22/86] Updates --- .../bleservices/gatt/gatt_audioservice_rdk.cpp | 15 +++++++++------ .../bleservices/gatt/gatt_audioservice_rdk.h | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index f608f4dd..f2f3d004 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -42,6 +42,7 @@ #define AUDIO_PACKET_DURATION_USEC (12000) #define AUDIO_FRAME_SIZE (100) +#define MFV_INITIAL_READ_COUNT (4) #define AUDIO_SEQ_NUM_MAX (0xFF) @@ -107,8 +108,7 @@ bool GattAudioServiceRdk::start(const shared_ptr &gattServ // Discover MFV characteristics (optional - not all remotes support MFV) if (getMfvCharacteristics(gattService)) { m_mfvSupported = true; - m_mfvInitialReadsComplete = false; - m_mfvInitialReadsRemaining = 4; + m_mfvInitialReadsRemaining = MFV_INITIAL_READ_COUNT; requestMfvCapabilities(); requestMfvModelVersion(); requestMfvPrivacy(); @@ -159,7 +159,7 @@ void GattAudioServiceRdk::onEnteredIdle() { m_mfvModelConfigCharacteristic.reset(); m_mfvCapabilitiesCharacteristic.reset(); m_mfvSupported = false; - m_mfvInitialReadsComplete = false; + m_mfvInitialReadsRemaining = 0; GattAudioService::onEnteredIdle(); } @@ -223,7 +223,7 @@ void GattAudioServiceRdk::onEnteredEnableNotificationsState() // Enable MFV notifications here if initial reads have already completed, // otherwise onMfvInitialReadComplete() will enable them when they finish. - if (m_mfvSupported && m_mfvInitialReadsComplete) { + if (areMfvInitialReadsComplete()) { requestStartMfvSessionStartNotify(); requestStartMfvDetectionDataNotify(); requestStartMfvPrivacyNotify(); @@ -526,6 +526,11 @@ void GattAudioServiceRdk::requestGainLevel() m_audioGainCharacteristic->readValue(PendingReply>(getIsAlivePtr(), replyHandler)); } +bool GattAudioServiceRdk::areMfvInitialReadsComplete() const +{ + return m_mfvSupported && (m_mfvInitialReadsRemaining == 0); +} + // ----------------------------------------------------------------------------- /*! \internal @@ -705,8 +710,6 @@ bool GattAudioServiceRdk::getMfvCharacteristics(const shared_ptr Date: Mon, 29 Jun 2026 13:59:36 +0000 Subject: [PATCH 23/86] Addressing review comments, adding 'mfv_below_threshold_session_enabled' setting to start session when detection type is below threshold --- include/ctrlm_hal_ble.h | 10 +++++++++- src/ble/ctrlm_ble_controller.cpp | 4 ++-- src/ble/ctrlm_ble_controller.h | 6 +++--- src/ble/ctrlm_ble_network.cpp | 29 ++++++++++++++++++++--------- src/ble/ctrlm_ble_network.h | 3 ++- src/ble/ctrlm_ble_rcu_interface.cpp | 4 ++-- src/ctrlm_config_default.json | 3 ++- 7 files changed, 40 insertions(+), 19 deletions(-) diff --git a/include/ctrlm_hal_ble.h b/include/ctrlm_hal_ble.h index 9ae5b4cb..5e236fbe 100644 --- a/include/ctrlm_hal_ble.h +++ b/include/ctrlm_hal_ble.h @@ -53,6 +53,14 @@ typedef enum { CTRLM_HAL_BLE_VOICE_STREAM_END_UNKNOWN } ctrlm_hal_ble_VoiceStreamEnd_t; +/// @brief MFV wake word detection types (matches BleRcuAudioService::DetectionType values). +typedef enum { + CTRLM_HAL_BLE_MFV_DETECTION_UNKNOWN = 0x00, + CTRLM_HAL_BLE_MFV_DETECTION_FULL_POWER = 0x01, + CTRLM_HAL_BLE_MFV_DETECTION_AAD = 0x02, + CTRLM_HAL_BLE_MFV_DETECTION_BELOW_THRESHOLD = 0x03, +} ctrlm_hal_ble_MfvDetectionType_t; + /// @brief Supported voice data encoding types. typedef enum { CTRLM_HAL_BLE_PROPERTY_IEEE_ADDDRESS = 0, @@ -127,7 +135,7 @@ typedef struct { int wakeup_custom_list[CTRLM_WAKEUP_CONFIG_LIST_MAX_SIZE]; int wakeup_custom_list_size; uint8_t irdbs_supported; - uint8_t mfv_detection_type; + ctrlm_hal_ble_MfvDetectionType_t mfv_detection_type; uint16_t mfv_ww_start; uint16_t mfv_ww_end; uint16_t mfv_confidence; // encoded as percentage * 10 (e.g. 976 = 97.6%) diff --git a/src/ble/ctrlm_ble_controller.cpp b/src/ble/ctrlm_ble_controller.cpp index 05fec2d1..339b67e9 100644 --- a/src/ble/ctrlm_ble_controller.cpp +++ b/src/ble/ctrlm_ble_controller.cpp @@ -527,11 +527,11 @@ ctrlm_timestamp_t ctrlm_obj_controller_ble_t::getVoiceStartTimeLocal() const { return(voice_start_time_local_); } -void ctrlm_obj_controller_ble_t::setMfvDetectionType(uint8_t type) { +void ctrlm_obj_controller_ble_t::setMfvDetectionType(ctrlm_hal_ble_MfvDetectionType_t type) { mfv_detection_type_ = type; } -uint8_t ctrlm_obj_controller_ble_t::getMfvDetectionType() const { +ctrlm_hal_ble_MfvDetectionType_t ctrlm_obj_controller_ble_t::getMfvDetectionType() const { return mfv_detection_type_; } diff --git a/src/ble/ctrlm_ble_controller.h b/src/ble/ctrlm_ble_controller.h index 2930cc94..82ae7360 100644 --- a/src/ble/ctrlm_ble_controller.h +++ b/src/ble/ctrlm_ble_controller.h @@ -147,8 +147,8 @@ class ctrlm_obj_controller_ble_t : public ctrlm_obj_controller_t { ctrlm_timestamp_t getVoiceStartTimeKey() const; ctrlm_timestamp_t getVoiceStartTimeLocal() const; - void setMfvDetectionType(uint8_t type); - uint8_t getMfvDetectionType() const; + void setMfvDetectionType(ctrlm_hal_ble_MfvDetectionType_t type); + ctrlm_hal_ble_MfvDetectionType_t getMfvDetectionType() const; void setMfvDetectionData(uint16_t start, uint16_t end, uint16_t confidence); uint16_t getMfvWwStart() const; uint16_t getMfvWwEnd() const; @@ -209,7 +209,7 @@ class ctrlm_obj_controller_ble_t : public ctrlm_obj_controller_t { bool type_z_supported_ = false; - uint8_t mfv_detection_type_ = 0; + ctrlm_hal_ble_MfvDetectionType_t mfv_detection_type_ = CTRLM_HAL_BLE_MFV_DETECTION_UNKNOWN; uint16_t mfv_ww_start_ = 0; uint16_t mfv_ww_end_ = 0; uint16_t mfv_confidence_ = 0; // encoded as integer * 10 (e.g. 953 = 95.3%) diff --git a/src/ble/ctrlm_ble_network.cpp b/src/ble/ctrlm_ble_network.cpp index 4460e2f3..060e01f2 100644 --- a/src/ble/ctrlm_ble_network.cpp +++ b/src/ble/ctrlm_ble_network.cpp @@ -303,6 +303,13 @@ ctrlm_obj_network_ble_t::ctrlm_obj_network_ble_t(ctrlm_network_type_t type, ctrl XLOGD_INFO("BLE voice support is disabled by config"); } } + json_value = json_object_get(obj_options, JSON_BOOL_NAME_NETWORK_BLE_OPTIONS_MFV_BELOW_THRESHOLD_SESSION_ENABLED); + if(json_value != NULL && json_is_boolean(json_value)) { + mfv_below_threshold_session_enabled_ = json_boolean_value(json_value); + if(mfv_below_threshold_session_enabled_) { + XLOGD_INFO("MFV below-threshold detections will start a voice session"); + } + } } } @@ -1935,21 +1942,25 @@ void ctrlm_obj_network_ble_t::ind_process_rcu_status(void *data, int size) { print_status = false; break; case CTRLM_HAL_BLE_PROPERTY_MFV_DETECTION_TYPE: { - controller->setMfvDetectionType(dqm->rcu_data.mfv_detection_type); - XLOGD_INFO("Controller <%s> MFV detection type = 0x%02X", controller->ieee_address_get().to_string().c_str(), dqm->rcu_data.mfv_detection_type); - - // Start a voice session for FullPower (0x01) and AAD (0x02) detections. - // BelowThreshold (0x03) is a low-confidence secondary detection - do not start a session. - // TODO: Get clarity on if BelowThreshold detections should start a sessions (assume not for now) - uint8_t detection_type = dqm->rcu_data.mfv_detection_type; - if (detection_type == 0x01 || detection_type == 0x02) { + const ctrlm_hal_ble_MfvDetectionType_t detection_type = dqm->rcu_data.mfv_detection_type; + controller->setMfvDetectionType(detection_type); + XLOGD_INFO("Controller <%s> MFV detection type = 0x%02X", controller->ieee_address_get().to_string().c_str(), detection_type); + + // Start a voice session for FullPower and AAD detections. BelowThreshold is a low-confidence + // secondary detection which is controlled by a config (mfv_below_threshold_session_enabled) and + // defaults to false to avoid flooding the server with false triggers from the field. + const bool start_session = (detection_type == CTRLM_HAL_BLE_MFV_DETECTION_FULL_POWER || + detection_type == CTRLM_HAL_BLE_MFV_DETECTION_AAD || + (detection_type == CTRLM_HAL_BLE_MFV_DETECTION_BELOW_THRESHOLD && mfv_below_threshold_session_enabled_)); + if (start_session) { rdkx_timestamp_t detectionTime; rdkx_timestamp_get(&detectionTime); controller->setVoiceStartTime(detectionTime); XLOGD_INFO("------------------------------------------------------------------------"); XLOGD_INFO("MFV wake word detected (%s) for device: %s", - detection_type == 0x01 ? "FullPower" : "AAD", + detection_type == CTRLM_HAL_BLE_MFV_DETECTION_FULL_POWER ? "FullPower" : + detection_type == CTRLM_HAL_BLE_MFV_DETECTION_AAD ? "AAD" : "BelowThreshold", controller->ieee_address_get().to_string().c_str()); XLOGD_INFO("------------------------------------------------------------------------"); diff --git a/src/ble/ctrlm_ble_network.h b/src/ble/ctrlm_ble_network.h index 1182889a..c555118e 100644 --- a/src/ble/ctrlm_ble_network.h +++ b/src/ble/ctrlm_ble_network.h @@ -211,7 +211,8 @@ class ctrlm_obj_network_ble_t : public ctrlm_obj_network_t { ctrlm_controller_id_t find_controller_from_upgrade_session_uuid(const std::string &uuid); json_t * json_config_ = NULL; - bool voice_disabled_ = false; + bool voice_disabled_ = false; + bool mfv_below_threshold_session_enabled_ = false; bool upgrade_in_progress_ = false; bool unpair_on_remote_request_ = true; ctrlm_ble_unpair_metrics_t last_rcu_unpair_metrics_; diff --git a/src/ble/ctrlm_ble_rcu_interface.cpp b/src/ble/ctrlm_ble_rcu_interface.cpp index 4ec7d4b1..9f77d307 100644 --- a/src/ble/ctrlm_ble_rcu_interface.cpp +++ b/src/ble/ctrlm_ble_rcu_interface.cpp @@ -493,7 +493,7 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) ctrlm_hal_ble_RcuStatusData_t params; params.property_updated = CTRLM_HAL_BLE_PROPERTY_MFV_DETECTION_TYPE; params.rcu_data.ieee_address = address.toUInt64(); - params.rcu_data.mfv_detection_type = static_cast(type); + params.rcu_data.mfv_detection_type = static_cast(type); m_rcuStatusChangedSlots.invoke(¶ms); }; audioSvc->addMfvDetectionTypeChangedSlot(Slot(m_isAlive, mfvDetectionTypeSlot)); @@ -734,7 +734,7 @@ ctrlm_hal_ble_rcu_data_t ctrlm_ble_rcu_interface_t::getAllDeviceProperties(uint6 // MFV data (part of the RDK Voice / audio service) if (auto audioSvc = device->audioService()) { auto detectionData = audioSvc->mfvDetectionData(); - ret.mfv_detection_type = static_cast(audioSvc->mfvDetectionType()); + ret.mfv_detection_type = static_cast(audioSvc->mfvDetectionType()); ret.mfv_ww_start = detectionData.start; ret.mfv_ww_end = detectionData.end; ret.mfv_confidence = detectionData.confidence; diff --git a/src/ctrlm_config_default.json b/src/ctrlm_config_default.json index af65c196..78d21e89 100755 --- a/src/ctrlm_config_default.json +++ b/src/ctrlm_config_default.json @@ -217,7 +217,8 @@ }, "network_ble" : { "options": { - "disable_voice" : false + "disable_voice" : false, + "mfv_below_threshold_session_enabled" : false }, "timeouts": { "discovery": 15000, From 8aa708a14f50db429e639ef62ff4748bd2b4a230 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 12:10:04 +0000 Subject: [PATCH 24/86] Update --- include/ctrlm_hal_ble.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/ctrlm_hal_ble.h b/include/ctrlm_hal_ble.h index 5e236fbe..2561f199 100644 --- a/include/ctrlm_hal_ble.h +++ b/include/ctrlm_hal_ble.h @@ -59,7 +59,7 @@ typedef enum { CTRLM_HAL_BLE_MFV_DETECTION_FULL_POWER = 0x01, CTRLM_HAL_BLE_MFV_DETECTION_AAD = 0x02, CTRLM_HAL_BLE_MFV_DETECTION_BELOW_THRESHOLD = 0x03, -} ctrlm_hal_ble_MfvDetectionType_t; +} ctrlm_hal_ble_MfvDetType_t; /// @brief Supported voice data encoding types. typedef enum { @@ -135,7 +135,7 @@ typedef struct { int wakeup_custom_list[CTRLM_WAKEUP_CONFIG_LIST_MAX_SIZE]; int wakeup_custom_list_size; uint8_t irdbs_supported; - ctrlm_hal_ble_MfvDetectionType_t mfv_detection_type; + ctrlm_hal_ble_MfvDetType_t mfv_detection_type; uint16_t mfv_ww_start; uint16_t mfv_ww_end; uint16_t mfv_confidence; // encoded as percentage * 10 (e.g. 976 = 97.6%) From 99db214f41b755e7e127d3f89644a68c45e31a77 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 08:12:11 -0400 Subject: [PATCH 25/86] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../blercu/bleservices/gatt/gatt_audioservice_rdk.cpp | 11 +++++++---- .../blercu/bleservices/gatt/gatt_audioservice_rdk.h | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index f2f3d004..0a4c2408 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -158,9 +158,13 @@ void GattAudioServiceRdk::onEnteredIdle() { m_mfvModelVersionCharacteristic.reset(); m_mfvModelConfigCharacteristic.reset(); m_mfvCapabilitiesCharacteristic.reset(); + if (m_mfvPromiseResults) { + m_mfvPromiseResults->setError("MFV operation cancelled"); + m_mfvPromiseResults->finish(); + m_mfvPromiseResults.reset(); + } m_mfvSupported = false; m_mfvInitialReadsRemaining = 0; - GattAudioService::onEnteredIdle(); } @@ -934,15 +938,14 @@ void GattAudioServiceRdk::onMfvSessionStartChanged(const std::vector &n return; } - uint8_t raw = newValue[0]; + const uint8_t raw = newValue[0]; if (raw < 0x01 || raw > 0x03) { XLOGD_WARN("MFV Session Start notification has unknown detection type 0x%02X", raw); + return; } m_mfvDetectionType = static_cast(raw); XLOGD_INFO("MFV Session Start: detection type = 0x%02X", raw); - - m_mfvDetectionTypeChangedSlots.invoke(m_mfvDetectionType); } // ----------------------------------------------------------------------------- diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h index 47554b6a..cb618435 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h @@ -130,7 +130,7 @@ class GattAudioServiceRdk : public GattAudioService bool m_mfvSupported = false; int m_mfvInitialReadsRemaining = 0; - DetectionType m_mfvDetectionType = FullPower; + DetectionType m_mfvDetectionType = Unknown; DetectionData m_mfvDetectionData; ModelVersion m_mfvModelVersionData; bool m_mfvPrivacyEnabled = false; From a96afdd1d9f967caf4fb8365df4a5d4ae2ab7b8d Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 08:47:39 -0400 Subject: [PATCH 26/86] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ble/hal/blercu/bleservices/blercuaudioservice.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ble/hal/blercu/bleservices/blercuaudioservice.h b/src/ble/hal/blercu/bleservices/blercuaudioservice.h index e8d390f8..c9402560 100644 --- a/src/ble/hal/blercu/bleservices/blercuaudioservice.h +++ b/src/ble/hal/blercu/bleservices/blercuaudioservice.h @@ -87,6 +87,7 @@ class BleRcuAudioService // MFV types public: enum DetectionType { + Unknown = 0x00, FullPower = 0x01, Aad = 0x02, BelowThreshold = 0x03, From 11c00a6cd151fa9556afc9c2b93c04373154e9ea Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 08:48:05 -0400 Subject: [PATCH 27/86] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ble/hal/blercu/bleservices/blercuaudioservice.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ble/hal/blercu/bleservices/blercuaudioservice.h b/src/ble/hal/blercu/bleservices/blercuaudioservice.h index c9402560..86457839 100644 --- a/src/ble/hal/blercu/bleservices/blercuaudioservice.h +++ b/src/ble/hal/blercu/bleservices/blercuaudioservice.h @@ -117,7 +117,7 @@ class BleRcuAudioService // MFV accessors (default no-ops for audio services without MFV support) public: - virtual DetectionType mfvDetectionType() const { return FullPower; } + virtual DetectionType mfvDetectionType() const { return Unknown; } virtual DetectionData mfvDetectionData() const { return {}; } virtual ModelVersion mfvModelVersion() const { return {}; } virtual bool mfvPrivacyEnabled() const { return false; } From b8224be046ac55cb26e3c045f25f6cfc987663ad Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 08:49:44 -0400 Subject: [PATCH 28/86] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ble/ctrlm_ble_network.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ble/ctrlm_ble_network.cpp b/src/ble/ctrlm_ble_network.cpp index 060e01f2..aa28850a 100644 --- a/src/ble/ctrlm_ble_network.cpp +++ b/src/ble/ctrlm_ble_network.cpp @@ -1944,7 +1944,7 @@ void ctrlm_obj_network_ble_t::ind_process_rcu_status(void *data, int size) { case CTRLM_HAL_BLE_PROPERTY_MFV_DETECTION_TYPE: { const ctrlm_hal_ble_MfvDetectionType_t detection_type = dqm->rcu_data.mfv_detection_type; controller->setMfvDetectionType(detection_type); - XLOGD_INFO("Controller <%s> MFV detection type = 0x%02X", controller->ieee_address_get().to_string().c_str(), detection_type); + XLOGD_INFO("Controller <%s> MFV detection type = 0x%02X", controller->ieee_address_get().to_string().c_str(), static_cast(detection_type)); // Start a voice session for FullPower and AAD detections. BelowThreshold is a low-confidence // secondary detection which is controlled by a config (mfv_below_threshold_session_enabled) and From 76201ed7d7b91d51c6f2aa35c91fe64f77d09e44 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 08:51:20 -0400 Subject: [PATCH 29/86] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ble/ctrlm_ble_controller.cpp | 4 ++-- src/ble/ctrlm_ble_network.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ble/ctrlm_ble_controller.cpp b/src/ble/ctrlm_ble_controller.cpp index 339b67e9..8b588881 100644 --- a/src/ble/ctrlm_ble_controller.cpp +++ b/src/ble/ctrlm_ble_controller.cpp @@ -715,12 +715,12 @@ void ctrlm_obj_controller_ble_t::print_status() { XLOGD_INFO(""); voice_metrics_->print(__FUNCTION__); XLOGD_INFO(""); - XLOGD_INFO("MFV Detection Type : 0x%02X", mfv_detection_type_); + XLOGD_INFO("MFV Detection Type : 0x%02X", static_cast(mfv_detection_type_)); XLOGD_INFO("MFV WW Start : %u", mfv_ww_start_); XLOGD_INFO("MFV WW End : %u", mfv_ww_end_); XLOGD_INFO("MFV Confidence : %.1f%%", mfv_confidence_ / 10.0); XLOGD_INFO("MFV Privacy : %s", mfv_privacy_enabled_ ? "enabled" : "disabled"); - XLOGD_INFO("MFV Capabilities : 0x%02X", mfv_capabilities_); + XLOGD_INFO("MFV Capabilities : 0x%02X", static_cast(mfv_capabilities_)); XLOGD_WARN("------------------------------------------------------------"); } diff --git a/src/ble/ctrlm_ble_network.cpp b/src/ble/ctrlm_ble_network.cpp index aa28850a..9aaef52d 100644 --- a/src/ble/ctrlm_ble_network.cpp +++ b/src/ble/ctrlm_ble_network.cpp @@ -1987,7 +1987,7 @@ void ctrlm_obj_network_ble_t::ind_process_rcu_status(void *data, int size) { break; case CTRLM_HAL_BLE_PROPERTY_MFV_CAPABILITIES: controller->setMfvCapabilities(dqm->rcu_data.mfv_capabilities); - XLOGD_INFO("Controller <%s> MFV capabilities = 0x%02X", controller->ieee_address_get().to_string().c_str(), dqm->rcu_data.mfv_capabilities); + XLOGD_INFO("Controller <%s> MFV capabilities = 0x%02X", controller->ieee_address_get().to_string().c_str(), static_cast(dqm->rcu_data.mfv_capabilities)); break; default: XLOGD_WARN("Unhandled Property: %d !!!!!!!!!!!!!!!!!!!!!!!!", dqm->property_updated); From e1035776c3ec62c4c4ebd97e274f223bef2a0f4b Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 12:56:16 +0000 Subject: [PATCH 30/86] Update --- src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index 0a4c2408..d6450af4 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -721,7 +721,7 @@ void GattAudioServiceRdk::onMfvInitialReadComplete() // Enable notifications now only if the audio state machine has already passed // through EnableNotificationsState (i.e. the service is already ready/streaming). // If not, onEnteredEnableNotificationsState() will handle it when the time comes. - if (!stateMachineIsIdle()) { + if (isReady()) { requestStartMfvSessionStartNotify(); requestStartMfvDetectionDataNotify(); requestStartMfvPrivacyNotify(); From 0bae6b61ee4ecf8ac9d244d815a9a20c76901f48 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 08:59:11 -0400 Subject: [PATCH 31/86] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index 88c90338..3ad20d2c 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -945,6 +945,7 @@ void GattAudioServiceRdk::onMfvSessionStartChanged(const std::vector &n } m_mfvDetectionType = static_cast(raw); + m_mfvDetectionTypeChangedSlots.invoke(m_mfvDetectionType); XLOGD_INFO("MFV Session Start: detection type = 0x%02X", raw); } From c762f0e289e76fc0035ba3f02cf442c7970fd10d Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 09:00:02 -0400 Subject: [PATCH 32/86] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ble/hal/blercu/bluez/blercudevice.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ble/hal/blercu/bluez/blercudevice.cpp b/src/ble/hal/blercu/bluez/blercudevice.cpp index 6812a0ac..575c8a5f 100644 --- a/src/ble/hal/blercu/bluez/blercudevice.cpp +++ b/src/ble/hal/blercu/bluez/blercudevice.cpp @@ -43,7 +43,6 @@ #include "blercu/bleservices/blercuupgradeservice.h" #include "blercu/bleservices/blercufindmeservice.h" #include "blercu/bleservices/blercuremotecontrolservice.h" -#include "blercu/bleservices/blercuaudioservice.h" #include "interfaces/bluezdeviceinterface.h" From 939543c3cc70ac931daf2bebfd66294f0a902b1e Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 13:11:53 +0000 Subject: [PATCH 33/86] Update from copilot review comments --- .../gatt/gatt_audioservice_rdk.cpp | 78 ++++++++++++------- .../bleservices/gatt/gatt_audioservice_rdk.h | 2 + 2 files changed, 53 insertions(+), 27 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index 3ad20d2c..8bf25a02 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -83,6 +83,8 @@ BleUuid GattAudioServiceRdk::uuid() */ bool GattAudioServiceRdk::start(const shared_ptr &gattService) { + m_mfvNotificationsEnabled = false; + // sanity check the supplied info is valid if (!gattService->isValid() || (gattService->uuid() != m_serviceUuid)) { XLOGD_WARN("invalid voice gatt service info"); @@ -129,6 +131,8 @@ void GattAudioServiceRdk::stop() } void GattAudioServiceRdk::onEnteredIdle() { + m_mfvNotificationsEnabled = false; + if (m_audioDataCharacteristic) { XLOGD_INFO("Disabling notifications for m_audioDataCharacteristic"); m_audioDataCharacteristic->disableNotifications(); @@ -191,6 +195,8 @@ void GattAudioServiceRdk::onEnteredEnableNotificationsState() stateMachinePostEvent(GattErrorEvent); } else { + maybeEnableMfvNotifications(); + // notifications enabled so post an event to the state machine stateMachinePostEvent(NotificationsEnabledEvent); } @@ -214,6 +220,8 @@ void GattAudioServiceRdk::onEnteredEnableNotificationsState() } if (m_audioDataCharacteristic->notificationsEnabled()) { + maybeEnableMfvNotifications(); + // notifications already enabled so post an event to the state machine stateMachinePostEvent(NotificationsEnabledEvent); } else { @@ -224,14 +232,6 @@ void GattAudioServiceRdk::onEnteredEnableNotificationsState() } GattAudioService::onEnteredEnableNotificationsState(); - - // Enable MFV notifications here if initial reads have already completed, - // otherwise onMfvInitialReadComplete() will enable them when they finish. - if (areMfvInitialReadsComplete()) { - requestStartMfvSessionStartNotify(); - requestStartMfvDetectionDataNotify(); - requestStartMfvPrivacyNotify(); - } } // ----------------------------------------------------------------------------- @@ -665,42 +665,59 @@ void GattAudioServiceRdk::setGainLevel(uint8_t level) */ bool GattAudioServiceRdk::getMfvCharacteristics(const shared_ptr &gattService) { - m_mfvSessionStartCharacteristic = gattService->characteristic(m_mfvSessionStartCharUuid); - if (!m_mfvSessionStartCharacteristic || !m_mfvSessionStartCharacteristic->isValid()) { + // TODO: Figure out if some characteristics are optional and if so, which ones. For now, require all of them to be present. + + // Reset any previous discovery result so failure paths are unambiguous. + m_mfvSessionStartCharacteristic.reset(); + m_mfvDetectionDataCharacteristic.reset(); + m_mfvModelVersionCharacteristic.reset(); + m_mfvPrivacyCharacteristic.reset(); + m_mfvModelConfigCharacteristic.reset(); + m_mfvCapabilitiesCharacteristic.reset(); + + auto mfvSessionStartCharacteristic = gattService->characteristic(m_mfvSessionStartCharUuid); + if (!mfvSessionStartCharacteristic || !mfvSessionStartCharacteristic->isValid()) { XLOGD_INFO("MFV Session Start characteristic not found (MFV not supported)"); return false; } - m_mfvDetectionDataCharacteristic = gattService->characteristic(m_mfvDetectionDataCharUuid); - if (!m_mfvDetectionDataCharacteristic || !m_mfvDetectionDataCharacteristic->isValid()) { + auto mfvDetectionDataCharacteristic = gattService->characteristic(m_mfvDetectionDataCharUuid); + if (!mfvDetectionDataCharacteristic || !mfvDetectionDataCharacteristic->isValid()) { XLOGD_INFO("MFV Detection Data characteristic not found"); return false; } - m_mfvModelVersionCharacteristic = gattService->characteristic(m_mfvModelVersionCharUuid); - if (!m_mfvModelVersionCharacteristic || !m_mfvModelVersionCharacteristic->isValid()) { + auto mfvModelVersionCharacteristic = gattService->characteristic(m_mfvModelVersionCharUuid); + if (!mfvModelVersionCharacteristic || !mfvModelVersionCharacteristic->isValid()) { XLOGD_INFO("MFV Wake Word Model Version characteristic not found"); return false; } - m_mfvPrivacyCharacteristic = gattService->characteristic(m_mfvPrivacyCharUuid); - if (!m_mfvPrivacyCharacteristic || !m_mfvPrivacyCharacteristic->isValid()) { + auto mfvPrivacyCharacteristic = gattService->characteristic(m_mfvPrivacyCharUuid); + if (!mfvPrivacyCharacteristic || !mfvPrivacyCharacteristic->isValid()) { XLOGD_INFO("MFV Privacy Settings characteristic not found"); return false; } - m_mfvModelConfigCharacteristic = gattService->characteristic(m_mfvModelConfigCharUuid); - if (!m_mfvModelConfigCharacteristic || !m_mfvModelConfigCharacteristic->isValid()) { + auto mfvModelConfigCharacteristic = gattService->characteristic(m_mfvModelConfigCharUuid); + if (!mfvModelConfigCharacteristic || !mfvModelConfigCharacteristic->isValid()) { XLOGD_INFO("MFV Model Configuration characteristic not found"); return false; } - m_mfvCapabilitiesCharacteristic = gattService->characteristic(m_mfvCapabilitiesCharUuid); - if (!m_mfvCapabilitiesCharacteristic || !m_mfvCapabilitiesCharacteristic->isValid()) { + auto mfvCapabilitiesCharacteristic = gattService->characteristic(m_mfvCapabilitiesCharUuid); + if (!mfvCapabilitiesCharacteristic || !mfvCapabilitiesCharacteristic->isValid()) { XLOGD_INFO("MFV Capabilities characteristic not found"); return false; } + m_mfvSessionStartCharacteristic = std::move(mfvSessionStartCharacteristic); + m_mfvDetectionDataCharacteristic = std::move(mfvDetectionDataCharacteristic); + m_mfvModelVersionCharacteristic = std::move(mfvModelVersionCharacteristic); + m_mfvPrivacyCharacteristic = std::move(mfvPrivacyCharacteristic); + m_mfvModelConfigCharacteristic = std::move(mfvModelConfigCharacteristic); + m_mfvCapabilitiesCharacteristic = std::move(mfvCapabilitiesCharacteristic); + XLOGD_INFO("All MFV characteristics discovered successfully"); return true; } @@ -718,14 +735,21 @@ void GattAudioServiceRdk::onMfvInitialReadComplete() m_mfvCapabilitiesChangedSlots.invoke(m_mfvCapabilitiesValue); m_mfvPrivacyChangedSlots.invoke(m_mfvPrivacyEnabled); - // Enable notifications now only if the audio state machine has already passed - // through EnableNotificationsState (i.e. the service is already ready/streaming). - // If not, onEnteredEnableNotificationsState() will handle it when the time comes. - if (isReady()) { - requestStartMfvSessionStartNotify(); - requestStartMfvDetectionDataNotify(); - requestStartMfvPrivacyNotify(); + // Attempt to enable MFV notifications once initial reads are complete. + // This may run before audio notifications are enabled, which is acceptable. + maybeEnableMfvNotifications(); +} + +void GattAudioServiceRdk::maybeEnableMfvNotifications() +{ + if (!m_mfvSupported || !areMfvInitialReadsComplete() || m_mfvNotificationsEnabled) { + return; } + + m_mfvNotificationsEnabled = true; + requestStartMfvSessionStartNotify(); + requestStartMfvDetectionDataNotify(); + requestStartMfvPrivacyNotify(); } // ----------------------------------------------------------------------------- diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h index cb618435..d2380e20 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h @@ -97,6 +97,7 @@ class GattAudioServiceRdk : public GattAudioService void requestMfvModelConfig(); bool areMfvInitialReadsComplete() const; void onMfvInitialReadComplete(); + void maybeEnableMfvNotifications(); // MFV notification enable void requestStartMfvSessionStartNotify(); @@ -129,6 +130,7 @@ class GattAudioServiceRdk : public GattAudioService // MFV characteristics and data bool m_mfvSupported = false; int m_mfvInitialReadsRemaining = 0; + bool m_mfvNotificationsEnabled = false; DetectionType m_mfvDetectionType = Unknown; DetectionData m_mfvDetectionData; From 38eece09e14b352140189a2792c4d410a2eb7ee8 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 13:33:14 +0000 Subject: [PATCH 34/86] Updating from copilot comments --- .../gatt/gatt_audioservice_rdk.cpp | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index 8bf25a02..471fc882 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -762,6 +762,11 @@ void GattAudioServiceRdk::requestMfvCapabilities() { auto replyHandler = [this](PendingReply> *reply) { + if (!m_mfvSupported || (m_mfvInitialReadsRemaining <= 0)) { + XLOGD_DEBUG("ignoring late MFV Capabilities read callback (MFV inactive)"); + return; + } + if (reply->isError()) { XLOGD_ERROR("failed to read MFV Capabilities due to <%s>", reply->errorMessage().c_str()); } else { @@ -778,7 +783,7 @@ void GattAudioServiceRdk::requestMfvCapabilities() XLOGD_ERROR("MFV Capabilities has invalid length (%zu bytes, expected 1)", value.size()); } } - if (--m_mfvInitialReadsRemaining <= 0) { + if (--m_mfvInitialReadsRemaining == 0) { onMfvInitialReadComplete(); } }; @@ -796,6 +801,11 @@ void GattAudioServiceRdk::requestMfvModelVersion() { auto replyHandler = [this](PendingReply> *reply) { + if (!m_mfvSupported || (m_mfvInitialReadsRemaining <= 0)) { + XLOGD_DEBUG("ignoring late MFV Model Version read callback (MFV inactive)"); + return; + } + if (reply->isError()) { XLOGD_ERROR("failed to read MFV Model Version due to <%s>", reply->errorMessage().c_str()); } else { @@ -808,7 +818,7 @@ void GattAudioServiceRdk::requestMfvModelVersion() XLOGD_ERROR("MFV Model Version has invalid length (%zu bytes, expected 2)", value.size()); } } - if (--m_mfvInitialReadsRemaining <= 0) { + if (--m_mfvInitialReadsRemaining == 0) { onMfvInitialReadComplete(); } }; @@ -826,6 +836,11 @@ void GattAudioServiceRdk::requestMfvPrivacy() { auto replyHandler = [this](PendingReply> *reply) { + if (!m_mfvSupported || (m_mfvInitialReadsRemaining <= 0)) { + XLOGD_DEBUG("ignoring late MFV Privacy read callback (MFV inactive)"); + return; + } + if (reply->isError()) { XLOGD_ERROR("failed to read MFV Privacy due to <%s>", reply->errorMessage().c_str()); } else { @@ -837,7 +852,7 @@ void GattAudioServiceRdk::requestMfvPrivacy() XLOGD_ERROR("MFV Privacy has invalid length (%zu bytes, expected 1)", value.size()); } } - if (--m_mfvInitialReadsRemaining <= 0) { + if (--m_mfvInitialReadsRemaining == 0) { onMfvInitialReadComplete(); } }; @@ -856,6 +871,11 @@ void GattAudioServiceRdk::requestMfvModelConfig() { auto replyHandler = [this](PendingReply> *reply) { + if (!m_mfvSupported || (m_mfvInitialReadsRemaining <= 0)) { + XLOGD_DEBUG("ignoring late MFV Model Config read callback (MFV inactive)"); + return; + } + if (reply->isError()) { XLOGD_ERROR("failed to read MFV Model Config due to <%s>", reply->errorMessage().c_str()); } else { @@ -868,7 +888,7 @@ void GattAudioServiceRdk::requestMfvModelConfig() XLOGD_ERROR("MFV Model Config has invalid length (%zu bytes, expected 3)", value.size()); } } - if (--m_mfvInitialReadsRemaining <= 0) { + if (--m_mfvInitialReadsRemaining == 0) { onMfvInitialReadComplete(); } }; From 38bee20ad8a933e9583ee0d9380cfe1e1db87280 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 13:42:04 +0000 Subject: [PATCH 35/86] Update --- include/ctrlm_hal_ble.h | 70 ++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/include/ctrlm_hal_ble.h b/include/ctrlm_hal_ble.h index 73c74456..082bdb39 100644 --- a/include/ctrlm_hal_ble.h +++ b/include/ctrlm_hal_ble.h @@ -59,7 +59,7 @@ typedef enum { CTRLM_HAL_BLE_MFV_DETECTION_FULL_POWER = 0x01, CTRLM_HAL_BLE_MFV_DETECTION_AAD = 0x02, CTRLM_HAL_BLE_MFV_DETECTION_BELOW_THRESHOLD = 0x03, -} ctrlm_hal_ble_MfvDetType_t; +} ctrlm_hal_ble_MfvDetectionType_t; /// @brief Supported voice data encoding types. typedef enum { @@ -107,40 +107,40 @@ typedef enum { /// @details The Control Manager HAL provides structures that are used in calls to the HAL networks. typedef struct { - int device_minor_id; - uint8_t battery_level; - bool connected; - unsigned long long ieee_address; - char fw_revision[CTRLM_MAX_PARAM_STR_LEN]; - char hw_revision[CTRLM_MAX_PARAM_STR_LEN]; - char sw_revision[CTRLM_MAX_PARAM_STR_LEN]; - char manufacturer[CTRLM_MAX_PARAM_STR_LEN]; - char model[CTRLM_MAX_PARAM_STR_LEN]; - char name[CTRLM_MAX_PARAM_STR_LEN]; - char serial_number[CTRLM_MAX_PARAM_STR_LEN]; - int ir_code; - uint8_t audio_gain_level; - uint32_t audio_codecs; - bool audio_streaming; - unsigned int touch_mode; - bool touch_mode_settable; - bool is_upgrading; - int upgrade_progress; - char upgrade_error[CTRLM_MAX_PARAM_STR_LEN]; - ctrlm_ble_RcuUnpairReason_t unpair_reason; - ctrlm_ble_RcuRebootReason_t reboot_reason; - char assert_report[CTRLM_RCU_ASSERT_REPORT_MAX_SIZE+1]; - uint16_t last_wakeup_key; - uint8_t wakeup_config; - int wakeup_custom_list[CTRLM_WAKEUP_CONFIG_LIST_MAX_SIZE]; - int wakeup_custom_list_size; - uint8_t irdbs_supported; - ctrlm_hal_ble_MfvDetType_t mfv_detection_type; - uint16_t mfv_ww_start; - uint16_t mfv_ww_end; - uint16_t mfv_confidence; // encoded as percentage * 10 (e.g. 976 = 97.6%) - bool mfv_privacy_enabled; - uint8_t mfv_capabilities; + int device_minor_id; + uint8_t battery_level; + bool connected; + unsigned long long ieee_address; + char fw_revision[CTRLM_MAX_PARAM_STR_LEN]; + char hw_revision[CTRLM_MAX_PARAM_STR_LEN]; + char sw_revision[CTRLM_MAX_PARAM_STR_LEN]; + char manufacturer[CTRLM_MAX_PARAM_STR_LEN]; + char model[CTRLM_MAX_PARAM_STR_LEN]; + char name[CTRLM_MAX_PARAM_STR_LEN]; + char serial_number[CTRLM_MAX_PARAM_STR_LEN]; + int ir_code; + uint8_t audio_gain_level; + uint32_t audio_codecs; + bool audio_streaming; + unsigned int touch_mode; + bool touch_mode_settable; + bool is_upgrading; + int upgrade_progress; + char upgrade_error[CTRLM_MAX_PARAM_STR_LEN]; + ctrlm_ble_RcuUnpairReason_t unpair_reason; + ctrlm_ble_RcuRebootReason_t reboot_reason; + char assert_report[CTRLM_RCU_ASSERT_REPORT_MAX_SIZE+1]; + uint16_t last_wakeup_key; + uint8_t wakeup_config; + int wakeup_custom_list[CTRLM_WAKEUP_CONFIG_LIST_MAX_SIZE]; + int wakeup_custom_list_size; + uint8_t irdbs_supported; + ctrlm_hal_ble_MfvDetectionType_t mfv_detection_type; + uint16_t mfv_ww_start; + uint16_t mfv_ww_end; + uint16_t mfv_confidence; // encoded as percentage * 10 (e.g. 976 = 97.6%) + bool mfv_privacy_enabled; + uint8_t mfv_capabilities; } ctrlm_hal_ble_rcu_data_t; typedef struct { From c81c4e2452b92f36dea35c0702945e6d73fd9ea9 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 10:07:08 -0400 Subject: [PATCH 36/86] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ble/hal/blercu/bleservices/blercuaudioservice.h | 4 ++++ .../bleservices/gatt/gatt_audioservice_rdk.cpp | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/ble/hal/blercu/bleservices/blercuaudioservice.h b/src/ble/hal/blercu/bleservices/blercuaudioservice.h index 86457839..611a9134 100644 --- a/src/ble/hal/blercu/bleservices/blercuaudioservice.h +++ b/src/ble/hal/blercu/bleservices/blercuaudioservice.h @@ -127,11 +127,15 @@ class BleRcuAudioService virtual void writeMfvPrivacy(bool enabled, PendingReply<> &&reply) { + (void)enabled; reply.setError("MFV not supported"); reply.finish(); } virtual void writeMfvModelConfiguration(uint8_t sensitivity, uint8_t secondary, uint8_t aad, PendingReply<> &&reply) { + (void)sensitivity; + (void)secondary; + (void)aad; reply.setError("MFV not supported"); reply.finish(); } diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index 471fc882..c206d44d 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -84,6 +84,8 @@ BleUuid GattAudioServiceRdk::uuid() bool GattAudioServiceRdk::start(const shared_ptr &gattService) { m_mfvNotificationsEnabled = false; + m_mfvSupported = false; + m_mfvInitialReadsRemaining = 0; // sanity check the supplied info is valid if (!gattService->isValid() || (gattService->uuid() != m_serviceUuid)) { @@ -167,6 +169,16 @@ void GattAudioServiceRdk::onEnteredIdle() { m_mfvPromiseResults->finish(); m_mfvPromiseResults.reset(); } + + // Reset cached MFV values so callers don't observe stale data after stop/disconnect. + m_mfvDetectionType = Unknown; + m_mfvDetectionData = {}; + m_mfvModelVersionData = {}; + m_mfvPrivacyEnabled = false; + m_mfvModelConfigurationData.clear(); + m_mfvCapabilitiesValue = 0; + m_mfvStreamStatsData = {}; + m_mfvSupported = false; m_mfvInitialReadsRemaining = 0; GattAudioService::onEnteredIdle(); From f0a10a17cec63975698028c618646667c03e4f9d Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 10:17:42 -0400 Subject: [PATCH 37/86] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../gatt/gatt_audioservice_rdk.cpp | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index c206d44d..631034d8 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -758,10 +758,24 @@ void GattAudioServiceRdk::maybeEnableMfvNotifications() return; } - m_mfvNotificationsEnabled = true; - requestStartMfvSessionStartNotify(); - requestStartMfvDetectionDataNotify(); - requestStartMfvPrivacyNotify(); + // Only request notification enables that are not already enabled. + if (m_mfvSessionStartCharacteristic && !m_mfvSessionStartCharacteristic->notificationsEnabled()) { + requestStartMfvSessionStartNotify(); + } + if (m_mfvDetectionDataCharacteristic && !m_mfvDetectionDataCharacteristic->notificationsEnabled()) { + requestStartMfvDetectionDataNotify(); + } + if (m_mfvPrivacyCharacteristic && !m_mfvPrivacyCharacteristic->notificationsEnabled()) { + requestStartMfvPrivacyNotify(); + } + + // Mark enabled once all required characteristics report notifications enabled. + if (m_mfvSessionStartCharacteristic && m_mfvDetectionDataCharacteristic && m_mfvPrivacyCharacteristic && + m_mfvSessionStartCharacteristic->notificationsEnabled() && + m_mfvDetectionDataCharacteristic->notificationsEnabled() && + m_mfvPrivacyCharacteristic->notificationsEnabled()) { + m_mfvNotificationsEnabled = true; + } } // ----------------------------------------------------------------------------- From b019ce89f9f7778a52c53a787a7007386b6efe6c Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 14:39:15 +0000 Subject: [PATCH 38/86] Updates --- src/ble/ctrlm_ble_rcu_interface.cpp | 4 +-- .../gatt/gatt_audioservice_rdk.cpp | 31 ++++++++++++++----- .../bleservices/gatt/gatt_audioservice_rdk.h | 4 +++ 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/ble/ctrlm_ble_rcu_interface.cpp b/src/ble/ctrlm_ble_rcu_interface.cpp index 436a92e7..8eb7e381 100644 --- a/src/ble/ctrlm_ble_rcu_interface.cpp +++ b/src/ble/ctrlm_ble_rcu_interface.cpp @@ -499,7 +499,7 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) ctrlm_hal_ble_RcuStatusData_t params; params.property_updated = CTRLM_HAL_BLE_PROPERTY_MFV_DETECTION_TYPE; params.rcu_data.ieee_address = address.toUInt64(); - params.rcu_data.mfv_detection_type = static_cast(type); + params.rcu_data.mfv_detection_type = static_cast(type); m_rcuStatusChangedSlots.invoke(¶ms); }; audioSvc->addMfvDetectionTypeChangedSlot(Slot(m_isAlive, mfvDetectionTypeSlot)); @@ -740,7 +740,7 @@ ctrlm_hal_ble_rcu_data_t ctrlm_ble_rcu_interface_t::getAllDeviceProperties(uint6 // MFV data (part of the RDK Voice / audio service) if (auto audioSvc = device->audioService()) { auto detectionData = audioSvc->mfvDetectionData(); - ret.mfv_detection_type = static_cast(audioSvc->mfvDetectionType()); + ret.mfv_detection_type = static_cast(audioSvc->mfvDetectionType()); ret.mfv_ww_start = detectionData.start; ret.mfv_ww_end = detectionData.end; ret.mfv_confidence = detectionData.confidence; diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index 631034d8..7bac2c5f 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -86,6 +86,10 @@ bool GattAudioServiceRdk::start(const shared_ptr &gattServ m_mfvNotificationsEnabled = false; m_mfvSupported = false; m_mfvInitialReadsRemaining = 0; + m_mfvCapabilitiesReadValid = false; + m_mfvModelVersionReadValid = false; + m_mfvPrivacyReadValid = false; + m_mfvModelConfigReadValid = false; // sanity check the supplied info is valid if (!gattService->isValid() || (gattService->uuid() != m_serviceUuid)) { @@ -134,6 +138,10 @@ void GattAudioServiceRdk::stop() void GattAudioServiceRdk::onEnteredIdle() { m_mfvNotificationsEnabled = false; + m_mfvCapabilitiesReadValid = false; + m_mfvModelVersionReadValid = false; + m_mfvPrivacyReadValid = false; + m_mfvModelConfigReadValid = false; if (m_audioDataCharacteristic) { XLOGD_INFO("Disabling notifications for m_audioDataCharacteristic"); @@ -743,9 +751,13 @@ bool GattAudioServiceRdk::getMfvCharacteristics(const shared_ptrnotificationsEnabled()) { requestStartMfvDetectionDataNotify(); } - if (m_mfvPrivacyCharacteristic && !m_mfvPrivacyCharacteristic->notificationsEnabled()) { + if (m_mfvPrivacyReadValid && m_mfvPrivacyCharacteristic && !m_mfvPrivacyCharacteristic->notificationsEnabled()) { requestStartMfvPrivacyNotify(); } - // Mark enabled once all required characteristics report notifications enabled. - if (m_mfvSessionStartCharacteristic && m_mfvDetectionDataCharacteristic && m_mfvPrivacyCharacteristic && + // Mark enabled once all required notifications are enabled. Privacy is optional here + // if its initial read did not complete successfully. + if (m_mfvSessionStartCharacteristic && m_mfvDetectionDataCharacteristic && m_mfvSessionStartCharacteristic->notificationsEnabled() && m_mfvDetectionDataCharacteristic->notificationsEnabled() && - m_mfvPrivacyCharacteristic->notificationsEnabled()) { + (!m_mfvPrivacyReadValid || (m_mfvPrivacyCharacteristic && m_mfvPrivacyCharacteristic->notificationsEnabled()))) { m_mfvNotificationsEnabled = true; } } @@ -799,6 +812,7 @@ void GattAudioServiceRdk::requestMfvCapabilities() std::vector value = reply->result(); if (value.size() == 1) { m_mfvCapabilitiesValue = value[0]; + m_mfvCapabilitiesReadValid = true; XLOGD_INFO("MFV Capabilities = 0x%02X (midfield=%d privacy_ctrl=%d soww_eoww=%d aad_ctrl=%d)", m_mfvCapabilitiesValue, !!(m_mfvCapabilitiesValue & MidfieldVoiceCapable), @@ -839,6 +853,7 @@ void GattAudioServiceRdk::requestMfvModelVersion() if (value.size() == 2) { m_mfvModelVersionData.major = value[0]; m_mfvModelVersionData.minor = value[1]; + m_mfvModelVersionReadValid = true; XLOGD_INFO("MFV Wake Word Model Version = %u.%u", m_mfvModelVersionData.major, m_mfvModelVersionData.minor); } else { XLOGD_ERROR("MFV Model Version has invalid length (%zu bytes, expected 2)", value.size()); @@ -873,6 +888,7 @@ void GattAudioServiceRdk::requestMfvPrivacy() std::vector value = reply->result(); if (value.size() == 1) { m_mfvPrivacyEnabled = (value[0] != 0); + m_mfvPrivacyReadValid = true; XLOGD_INFO("MFV Privacy = %s", m_mfvPrivacyEnabled ? "enabled" : "disabled"); } else { XLOGD_ERROR("MFV Privacy has invalid length (%zu bytes, expected 1)", value.size()); @@ -908,6 +924,7 @@ void GattAudioServiceRdk::requestMfvModelConfig() std::vector value = reply->result(); if (value.size() == 3) { m_mfvModelConfigurationData = value; + m_mfvModelConfigReadValid = true; XLOGD_INFO("MFV Model Config: sensitivity=%u secondary=%u aad=%u", value[0], value[1], value[2]); } else { diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h index d2380e20..ceaae91f 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h @@ -131,6 +131,10 @@ class GattAudioServiceRdk : public GattAudioService bool m_mfvSupported = false; int m_mfvInitialReadsRemaining = 0; bool m_mfvNotificationsEnabled = false; + bool m_mfvCapabilitiesReadValid = false; + bool m_mfvModelVersionReadValid = false; + bool m_mfvPrivacyReadValid = false; + bool m_mfvModelConfigReadValid = false; DetectionType m_mfvDetectionType = Unknown; DetectionData m_mfvDetectionData; From cc586dc5394c6a0f35074f56ce4e4eed333d6098 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 14:52:36 +0000 Subject: [PATCH 39/86] Centralizing MFV state --- .../gatt/gatt_audioservice_rdk.cpp | 82 ++++++++++--------- .../bleservices/gatt/gatt_audioservice_rdk.h | 46 +++++++++-- 2 files changed, 81 insertions(+), 47 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index 7bac2c5f..d0d309d6 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -83,13 +83,7 @@ BleUuid GattAudioServiceRdk::uuid() */ bool GattAudioServiceRdk::start(const shared_ptr &gattService) { - m_mfvNotificationsEnabled = false; - m_mfvSupported = false; - m_mfvInitialReadsRemaining = 0; - m_mfvCapabilitiesReadValid = false; - m_mfvModelVersionReadValid = false; - m_mfvPrivacyReadValid = false; - m_mfvModelConfigReadValid = false; + m_mfvState.reset(); // sanity check the supplied info is valid if (!gattService->isValid() || (gattService->uuid() != m_serviceUuid)) { @@ -115,8 +109,8 @@ bool GattAudioServiceRdk::start(const shared_ptr &gattServ // Discover MFV characteristics (optional - not all remotes support MFV) if (getMfvCharacteristics(gattService)) { - m_mfvSupported = true; - m_mfvInitialReadsRemaining = MFV_INITIAL_READ_COUNT; + m_mfvState.supported = true; + m_mfvState.initialReadsRemaining = MFV_INITIAL_READ_COUNT; requestMfvCapabilities(); requestMfvModelVersion(); requestMfvPrivacy(); @@ -137,11 +131,7 @@ void GattAudioServiceRdk::stop() } void GattAudioServiceRdk::onEnteredIdle() { - m_mfvNotificationsEnabled = false; - m_mfvCapabilitiesReadValid = false; - m_mfvModelVersionReadValid = false; - m_mfvPrivacyReadValid = false; - m_mfvModelConfigReadValid = false; + m_mfvState.reset(); if (m_audioDataCharacteristic) { XLOGD_INFO("Disabling notifications for m_audioDataCharacteristic"); @@ -187,8 +177,6 @@ void GattAudioServiceRdk::onEnteredIdle() { m_mfvCapabilitiesValue = 0; m_mfvStreamStatsData = {}; - m_mfvSupported = false; - m_mfvInitialReadsRemaining = 0; GattAudioService::onEnteredIdle(); } @@ -552,7 +540,7 @@ void GattAudioServiceRdk::requestGainLevel() bool GattAudioServiceRdk::areMfvInitialReadsComplete() const { - return m_mfvSupported && (m_mfvInitialReadsRemaining == 0); + return m_mfvState.areInitialReadsComplete(); } // ----------------------------------------------------------------------------- @@ -752,10 +740,10 @@ bool GattAudioServiceRdk::getMfvCharacteristics(const shared_ptrnotificationsEnabled()) { + if (m_mfvSessionStartCharacteristic && + !m_mfvSessionStartCharacteristic->notificationsEnabled() && + !m_mfvState.sessionStartNotifyRequested) { + m_mfvState.sessionStartNotifyRequested = true; requestStartMfvSessionStartNotify(); } - if (m_mfvDetectionDataCharacteristic && !m_mfvDetectionDataCharacteristic->notificationsEnabled()) { + if (m_mfvDetectionDataCharacteristic && + !m_mfvDetectionDataCharacteristic->notificationsEnabled() && + !m_mfvState.detectionDataNotifyRequested) { + m_mfvState.detectionDataNotifyRequested = true; requestStartMfvDetectionDataNotify(); } - if (m_mfvPrivacyReadValid && m_mfvPrivacyCharacteristic && !m_mfvPrivacyCharacteristic->notificationsEnabled()) { + if (m_mfvState.privacyReadValid && + m_mfvPrivacyCharacteristic && + !m_mfvPrivacyCharacteristic->notificationsEnabled() && + !m_mfvState.privacyNotifyRequested) { + m_mfvState.privacyNotifyRequested = true; requestStartMfvPrivacyNotify(); } @@ -786,8 +784,8 @@ void GattAudioServiceRdk::maybeEnableMfvNotifications() if (m_mfvSessionStartCharacteristic && m_mfvDetectionDataCharacteristic && m_mfvSessionStartCharacteristic->notificationsEnabled() && m_mfvDetectionDataCharacteristic->notificationsEnabled() && - (!m_mfvPrivacyReadValid || (m_mfvPrivacyCharacteristic && m_mfvPrivacyCharacteristic->notificationsEnabled()))) { - m_mfvNotificationsEnabled = true; + (!m_mfvState.privacyReadValid || (m_mfvPrivacyCharacteristic && m_mfvPrivacyCharacteristic->notificationsEnabled()))) { + m_mfvState.notificationsEnabled = true; } } @@ -801,7 +799,7 @@ void GattAudioServiceRdk::requestMfvCapabilities() { auto replyHandler = [this](PendingReply> *reply) { - if (!m_mfvSupported || (m_mfvInitialReadsRemaining <= 0)) { + if (!m_mfvState.supported || (m_mfvState.initialReadsRemaining <= 0)) { XLOGD_DEBUG("ignoring late MFV Capabilities read callback (MFV inactive)"); return; } @@ -812,7 +810,7 @@ void GattAudioServiceRdk::requestMfvCapabilities() std::vector value = reply->result(); if (value.size() == 1) { m_mfvCapabilitiesValue = value[0]; - m_mfvCapabilitiesReadValid = true; + m_mfvState.capabilitiesReadValid = true; XLOGD_INFO("MFV Capabilities = 0x%02X (midfield=%d privacy_ctrl=%d soww_eoww=%d aad_ctrl=%d)", m_mfvCapabilitiesValue, !!(m_mfvCapabilitiesValue & MidfieldVoiceCapable), @@ -823,7 +821,7 @@ void GattAudioServiceRdk::requestMfvCapabilities() XLOGD_ERROR("MFV Capabilities has invalid length (%zu bytes, expected 1)", value.size()); } } - if (--m_mfvInitialReadsRemaining == 0) { + if (--m_mfvState.initialReadsRemaining == 0) { onMfvInitialReadComplete(); } }; @@ -841,7 +839,7 @@ void GattAudioServiceRdk::requestMfvModelVersion() { auto replyHandler = [this](PendingReply> *reply) { - if (!m_mfvSupported || (m_mfvInitialReadsRemaining <= 0)) { + if (!m_mfvState.supported || (m_mfvState.initialReadsRemaining <= 0)) { XLOGD_DEBUG("ignoring late MFV Model Version read callback (MFV inactive)"); return; } @@ -853,13 +851,13 @@ void GattAudioServiceRdk::requestMfvModelVersion() if (value.size() == 2) { m_mfvModelVersionData.major = value[0]; m_mfvModelVersionData.minor = value[1]; - m_mfvModelVersionReadValid = true; + m_mfvState.modelVersionReadValid = true; XLOGD_INFO("MFV Wake Word Model Version = %u.%u", m_mfvModelVersionData.major, m_mfvModelVersionData.minor); } else { XLOGD_ERROR("MFV Model Version has invalid length (%zu bytes, expected 2)", value.size()); } } - if (--m_mfvInitialReadsRemaining == 0) { + if (--m_mfvState.initialReadsRemaining == 0) { onMfvInitialReadComplete(); } }; @@ -877,7 +875,7 @@ void GattAudioServiceRdk::requestMfvPrivacy() { auto replyHandler = [this](PendingReply> *reply) { - if (!m_mfvSupported || (m_mfvInitialReadsRemaining <= 0)) { + if (!m_mfvState.supported || (m_mfvState.initialReadsRemaining <= 0)) { XLOGD_DEBUG("ignoring late MFV Privacy read callback (MFV inactive)"); return; } @@ -888,13 +886,13 @@ void GattAudioServiceRdk::requestMfvPrivacy() std::vector value = reply->result(); if (value.size() == 1) { m_mfvPrivacyEnabled = (value[0] != 0); - m_mfvPrivacyReadValid = true; + m_mfvState.privacyReadValid = true; XLOGD_INFO("MFV Privacy = %s", m_mfvPrivacyEnabled ? "enabled" : "disabled"); } else { XLOGD_ERROR("MFV Privacy has invalid length (%zu bytes, expected 1)", value.size()); } } - if (--m_mfvInitialReadsRemaining == 0) { + if (--m_mfvState.initialReadsRemaining == 0) { onMfvInitialReadComplete(); } }; @@ -913,7 +911,7 @@ void GattAudioServiceRdk::requestMfvModelConfig() { auto replyHandler = [this](PendingReply> *reply) { - if (!m_mfvSupported || (m_mfvInitialReadsRemaining <= 0)) { + if (!m_mfvState.supported || (m_mfvState.initialReadsRemaining <= 0)) { XLOGD_DEBUG("ignoring late MFV Model Config read callback (MFV inactive)"); return; } @@ -924,14 +922,14 @@ void GattAudioServiceRdk::requestMfvModelConfig() std::vector value = reply->result(); if (value.size() == 3) { m_mfvModelConfigurationData = value; - m_mfvModelConfigReadValid = true; + m_mfvState.modelConfigReadValid = true; XLOGD_INFO("MFV Model Config: sensitivity=%u secondary=%u aad=%u", value[0], value[1], value[2]); } else { XLOGD_ERROR("MFV Model Config has invalid length (%zu bytes, expected 3)", value.size()); } } - if (--m_mfvInitialReadsRemaining == 0) { + if (--m_mfvState.initialReadsRemaining == 0) { onMfvInitialReadComplete(); } }; @@ -950,10 +948,12 @@ void GattAudioServiceRdk::requestStartMfvSessionStartNotify() auto replyHandler = [this](PendingReply<> *reply) { if (reply->isError()) { + m_mfvState.sessionStartNotifyRequested = false; XLOGD_ERROR("failed to enable MFV Session Start notifications due to <%s>", reply->errorMessage().c_str()); } else { XLOGD_DEBUG("MFV Session Start notifications enabled successfully"); + maybeEnableMfvNotifications(); } }; @@ -974,10 +974,12 @@ void GattAudioServiceRdk::requestStartMfvDetectionDataNotify() auto replyHandler = [this](PendingReply<> *reply) { if (reply->isError()) { + m_mfvState.detectionDataNotifyRequested = false; XLOGD_ERROR("failed to enable MFV Detection Data notifications due to <%s>", reply->errorMessage().c_str()); } else { XLOGD_DEBUG("MFV Detection Data notifications enabled successfully"); + maybeEnableMfvNotifications(); } }; @@ -998,10 +1000,12 @@ void GattAudioServiceRdk::requestStartMfvPrivacyNotify() auto replyHandler = [this](PendingReply<> *reply) { if (reply->isError()) { + m_mfvState.privacyNotifyRequested = false; XLOGD_ERROR("failed to enable MFV Privacy notifications due to <%s>", reply->errorMessage().c_str()); } else { XLOGD_DEBUG("MFV Privacy notifications enabled successfully"); + maybeEnableMfvNotifications(); } }; @@ -1126,7 +1130,7 @@ BleRcuAudioService::StreamStatsRaw GattAudioServiceRdk::mfvStreamStats() const void GattAudioServiceRdk::writeMfvPrivacy(bool enabled, PendingReply<> &&reply) { - if (!m_mfvSupported) { + if (!m_mfvState.supported) { reply.setError("MFV not supported on this remote"); reply.finish(); return; @@ -1173,7 +1177,7 @@ void GattAudioServiceRdk::onWriteMfvPrivacyReply(PendingReply<> *reply) void GattAudioServiceRdk::writeMfvModelConfiguration(uint8_t sensitivity, uint8_t secondary, uint8_t aad, PendingReply<> &&reply) { - if (!m_mfvSupported) { + if (!m_mfvState.supported) { reply.setError("MFV not supported on this remote"); reply.finish(); return; diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h index ceaae91f..235d2637 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h @@ -127,14 +127,44 @@ class GattAudioServiceRdk : public GattAudioService std::shared_ptr m_audioInfoCharacteristic; std::shared_ptr m_audioCodecsCharacteristic; - // MFV characteristics and data - bool m_mfvSupported = false; - int m_mfvInitialReadsRemaining = 0; - bool m_mfvNotificationsEnabled = false; - bool m_mfvCapabilitiesReadValid = false; - bool m_mfvModelVersionReadValid = false; - bool m_mfvPrivacyReadValid = false; - bool m_mfvModelConfigReadValid = false; + struct MfvState { + bool supported = false; + int initialReadsRemaining = 0; + bool notificationsEnabled = false; + + bool capabilitiesReadValid = false; + bool modelVersionReadValid = false; + bool privacyReadValid = false; + bool modelConfigReadValid = false; + + bool sessionStartNotifyRequested = false; + bool detectionDataNotifyRequested = false; + bool privacyNotifyRequested = false; + + void reset() + { + supported = false; + initialReadsRemaining = 0; + notificationsEnabled = false; + + capabilitiesReadValid = false; + modelVersionReadValid = false; + privacyReadValid = false; + modelConfigReadValid = false; + + sessionStartNotifyRequested = false; + detectionDataNotifyRequested = false; + privacyNotifyRequested = false; + } + + bool areInitialReadsComplete() const + { + return supported && (initialReadsRemaining == 0); + } + }; + + // MFV characteristics and state + MfvState m_mfvState; DetectionType m_mfvDetectionType = Unknown; DetectionData m_mfvDetectionData; From ccb26961c2236b70c21c19617faf82d39acd2d9d Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 14:58:50 +0000 Subject: [PATCH 40/86] Clean up --- .../hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp | 7 +------ .../hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index d0d309d6..95a66049 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -538,11 +538,6 @@ void GattAudioServiceRdk::requestGainLevel() m_audioGainCharacteristic->readValue(PendingReply>(getIsAlivePtr(), replyHandler)); } -bool GattAudioServiceRdk::areMfvInitialReadsComplete() const -{ - return m_mfvState.areInitialReadsComplete(); -} - // ----------------------------------------------------------------------------- /*! \internal @@ -754,7 +749,7 @@ void GattAudioServiceRdk::onMfvInitialReadComplete() void GattAudioServiceRdk::maybeEnableMfvNotifications() { - if (!m_mfvState.supported || !areMfvInitialReadsComplete() || m_mfvState.notificationsEnabled) { + if (!m_mfvState.supported || !m_mfvState.areInitialReadsComplete() || m_mfvState.notificationsEnabled) { return; } diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h index 235d2637..db1e7e09 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h @@ -95,7 +95,6 @@ class GattAudioServiceRdk : public GattAudioService void requestMfvModelVersion(); void requestMfvPrivacy(); void requestMfvModelConfig(); - bool areMfvInitialReadsComplete() const; void onMfvInitialReadComplete(); void maybeEnableMfvNotifications(); From 841cabba3a303db7aa09806a71b97e0f503e40e8 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 15:34:02 +0000 Subject: [PATCH 41/86] Updates --- src/ble/ctrlm_ble_rcu_interface.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ble/ctrlm_ble_rcu_interface.cpp b/src/ble/ctrlm_ble_rcu_interface.cpp index 8eb7e381..436a92e7 100644 --- a/src/ble/ctrlm_ble_rcu_interface.cpp +++ b/src/ble/ctrlm_ble_rcu_interface.cpp @@ -499,7 +499,7 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) ctrlm_hal_ble_RcuStatusData_t params; params.property_updated = CTRLM_HAL_BLE_PROPERTY_MFV_DETECTION_TYPE; params.rcu_data.ieee_address = address.toUInt64(); - params.rcu_data.mfv_detection_type = static_cast(type); + params.rcu_data.mfv_detection_type = static_cast(type); m_rcuStatusChangedSlots.invoke(¶ms); }; audioSvc->addMfvDetectionTypeChangedSlot(Slot(m_isAlive, mfvDetectionTypeSlot)); @@ -740,7 +740,7 @@ ctrlm_hal_ble_rcu_data_t ctrlm_ble_rcu_interface_t::getAllDeviceProperties(uint6 // MFV data (part of the RDK Voice / audio service) if (auto audioSvc = device->audioService()) { auto detectionData = audioSvc->mfvDetectionData(); - ret.mfv_detection_type = static_cast(audioSvc->mfvDetectionType()); + ret.mfv_detection_type = static_cast(audioSvc->mfvDetectionType()); ret.mfv_ww_start = detectionData.start; ret.mfv_ww_end = detectionData.end; ret.mfv_confidence = detectionData.confidence; From 1047802979954e0b32de91174e1f0bd291769f3a Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 15:36:00 +0000 Subject: [PATCH 42/86] Updates --- src/ble/ctrlm_ble_rcu_interface.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ble/ctrlm_ble_rcu_interface.cpp b/src/ble/ctrlm_ble_rcu_interface.cpp index 436a92e7..23526e50 100644 --- a/src/ble/ctrlm_ble_rcu_interface.cpp +++ b/src/ble/ctrlm_ble_rcu_interface.cpp @@ -496,7 +496,7 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) { XLOGD_INFO("BLE RCU %s MFV detection type = 0x%02X", address.toString().c_str(), static_cast(type)); - ctrlm_hal_ble_RcuStatusData_t params; + ctrlm_hal_ble_RcuStatusData_t params = {}; params.property_updated = CTRLM_HAL_BLE_PROPERTY_MFV_DETECTION_TYPE; params.rcu_data.ieee_address = address.toUInt64(); params.rcu_data.mfv_detection_type = static_cast(type); @@ -509,7 +509,7 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) XLOGD_INFO("BLE RCU %s MFV detection data: start=%u end=%u confidence=%.1f%%", address.toString().c_str(), data.start, data.end, data.confidence / 10.0); - ctrlm_hal_ble_RcuStatusData_t params; + ctrlm_hal_ble_RcuStatusData_t params = {}; params.property_updated = CTRLM_HAL_BLE_PROPERTY_MFV_DETECTION_DATA; params.rcu_data.ieee_address = address.toUInt64(); params.rcu_data.mfv_ww_start = data.start; @@ -523,7 +523,7 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) { XLOGD_INFO("BLE RCU %s MFV privacy = %s", address.toString().c_str(), enabled ? "enabled" : "disabled"); - ctrlm_hal_ble_RcuStatusData_t params; + ctrlm_hal_ble_RcuStatusData_t params = {}; params.property_updated = CTRLM_HAL_BLE_PROPERTY_MFV_PRIVACY; params.rcu_data.ieee_address = address.toUInt64(); params.rcu_data.mfv_privacy_enabled = enabled; @@ -535,7 +535,7 @@ bool ctrlm_ble_rcu_interface_t::handleAddedDevice(const BleAddress &address) { XLOGD_INFO("BLE RCU %s MFV capabilities = 0x%02X", address.toString().c_str(), caps); - ctrlm_hal_ble_RcuStatusData_t params; + ctrlm_hal_ble_RcuStatusData_t params = {}; params.property_updated = CTRLM_HAL_BLE_PROPERTY_MFV_CAPABILITIES; params.rcu_data.ieee_address = address.toUInt64(); params.rcu_data.mfv_capabilities = caps; From 990403f638e20b2060d1c11fe4ad0269d441dde9 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 11:43:28 -0400 Subject: [PATCH 43/86] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ble/hal/utils/bleuuid.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ble/hal/utils/bleuuid.h b/src/ble/hal/utils/bleuuid.h index cec73d45..b6413fa4 100644 --- a/src/ble/hal/utils/bleuuid.h +++ b/src/ble/hal/utils/bleuuid.h @@ -105,12 +105,12 @@ class BleUuid AdvertisingConfigCustomList = 0xed06, AssertReport = 0xed07, RawBatteryVoltage = 0xed08, - SessionStart = 0xEA08, - DetectionData = 0xEA09, - WakeWordModelVersion = 0xEA0A, - PrivacySettings = 0xEA0B, - ModelConfiguration = 0xEA0C, - MfvCapabilities = 0xEA0D, + SessionStart = 0xea08, + DetectionData = 0xea09, + WakeWordModelVersion = 0xea0a, + PrivacySettings = 0xea0b, + ModelConfiguration = 0xea0c, + MfvCapabilities = 0xea0d, }; enum DescriptorType { From 2de17dace2b88de0ff1d34b30734be278733866c Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 15:45:05 +0000 Subject: [PATCH 44/86] Update --- .../hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index 95a66049..77fbdac9 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -42,7 +42,6 @@ #define AUDIO_PACKET_DURATION_USEC (12000) #define AUDIO_FRAME_SIZE (100) -#define MFV_INITIAL_READ_COUNT (4) #define AUDIO_SEQ_NUM_MAX (0xFF) @@ -110,10 +109,14 @@ bool GattAudioServiceRdk::start(const shared_ptr &gattServ // Discover MFV characteristics (optional - not all remotes support MFV) if (getMfvCharacteristics(gattService)) { m_mfvState.supported = true; - m_mfvState.initialReadsRemaining = MFV_INITIAL_READ_COUNT; + m_mfvState.initialReadsRemaining = 0; + ++m_mfvState.initialReadsRemaining; requestMfvCapabilities(); + ++m_mfvState.initialReadsRemaining; requestMfvModelVersion(); + ++m_mfvState.initialReadsRemaining; requestMfvPrivacy(); + ++m_mfvState.initialReadsRemaining; requestMfvModelConfig(); } From 86ea479e06cc5b08c83baa97a9463926acec6960 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 11:45:46 -0400 Subject: [PATCH 45/86] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ble/ctrlm_ble_network.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ble/ctrlm_ble_network.cpp b/src/ble/ctrlm_ble_network.cpp index b95c7cd7..f913a9f8 100644 --- a/src/ble/ctrlm_ble_network.cpp +++ b/src/ble/ctrlm_ble_network.cpp @@ -2112,13 +2112,11 @@ void ctrlm_obj_network_ble_t::ind_process_rcu_status(void *data, int size) { controller->ieee_address_get().to_string().c_str()); XLOGD_INFO("------------------------------------------------------------------------"); - ctrlm_voice_iarm_call_voice_session_t v_params; + ctrlm_voice_iarm_call_voice_session_t v_params = {}; v_params.ieee_address = dqm->rcu_data.ieee_address; ctrlm_main_queue_msg_voice_session_t msg; errno_t safec_rc = memset_s(&msg, sizeof(msg), 0, sizeof(msg)); - ERR_CHK(safec_rc); - msg.params = &v_params; req_process_voice_session_begin(&msg, sizeof(msg)); } From 41f3f307772aee7cd44e21a1e9870f32023d4e8d Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 9 Jul 2026 16:38:31 +0000 Subject: [PATCH 46/86] Update --- src/ble/ctrlm_ble_network.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ble/ctrlm_ble_network.cpp b/src/ble/ctrlm_ble_network.cpp index f913a9f8..f4838b2d 100644 --- a/src/ble/ctrlm_ble_network.cpp +++ b/src/ble/ctrlm_ble_network.cpp @@ -2117,6 +2117,8 @@ void ctrlm_obj_network_ble_t::ind_process_rcu_status(void *data, int size) { ctrlm_main_queue_msg_voice_session_t msg; errno_t safec_rc = memset_s(&msg, sizeof(msg), 0, sizeof(msg)); + ERR_CHK(safec_rc); + msg.params = &v_params; req_process_voice_session_begin(&msg, sizeof(msg)); } @@ -2301,6 +2303,10 @@ ctrlm_controller_id_t ctrlm_obj_network_ble_t::controller_add(ctrlm_hal_ble_rcu_ if (rcu_data.wakeup_config != 0xFF) { controller->setWakeupConfig(rcu_data.wakeup_config); } if (rcu_data.wakeup_custom_list_size != 0) { controller->setWakeupCustomList(rcu_data.wakeup_custom_list, rcu_data.wakeup_custom_list_size); } if (rcu_data.last_wakeup_key != 0xFF) { controller->setLastWakeupKey(rcu_data.last_wakeup_key); } + controller->setMfvDetectionType(rcu_data.mfv_detection_type); + controller->setMfvDetectionData(rcu_data.mfv_ww_start, rcu_data.mfv_ww_end, rcu_data.mfv_confidence); + controller->setMfvPrivacy(rcu_data.mfv_privacy_enabled); + controller->setMfvCapabilities(rcu_data.mfv_capabilities); controller->db_store(); } From cdb81881abb7f26753f21764d5c7376ea901bcd3 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 10 Jul 2026 11:58:17 +0000 Subject: [PATCH 47/86] Fix --- src/ble/ctrlm_ble_network.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ble/ctrlm_ble_network.cpp b/src/ble/ctrlm_ble_network.cpp index f4838b2d..598d9db7 100644 --- a/src/ble/ctrlm_ble_network.cpp +++ b/src/ble/ctrlm_ble_network.cpp @@ -2112,7 +2112,7 @@ void ctrlm_obj_network_ble_t::ind_process_rcu_status(void *data, int size) { controller->ieee_address_get().to_string().c_str()); XLOGD_INFO("------------------------------------------------------------------------"); - ctrlm_voice_iarm_call_voice_session_t v_params = {}; + ctrlm_voice_iarm_call_voice_session_t v_params; v_params.ieee_address = dqm->rcu_data.ieee_address; ctrlm_main_queue_msg_voice_session_t msg; From 3d520c8d010a7b3b9bce1596ea1a9ff68c3c6697 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 13 Jul 2026 12:11:26 +0000 Subject: [PATCH 48/86] Update cached value of model config data after write --- .../hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index 77fbdac9..11ff643d 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -1206,6 +1206,9 @@ void GattAudioServiceRdk::writeMfvModelConfiguration(uint8_t sensitivity, uint8_ XLOGD_INFO("Writing MFV Model Config: sensitivity=%u secondary=%u aad=%u", sensitivity, secondary, aad); + m_mfvModelConfigurationData = value; + m_mfvState.modelConfigReadValid = true; + m_mfvModelConfigCharacteristic->writeValue(value, PendingReply<>(getIsAlivePtr(), std::bind(&GattAudioServiceRdk::onWriteMfvModelConfigReply, this, std::placeholders::_1))); } @@ -1214,6 +1217,8 @@ void GattAudioServiceRdk::onWriteMfvModelConfigReply(PendingReply<> *reply) { if (reply->isError()) { XLOGD_ERROR("failed to write MFV Model Config due to <%s>", reply->errorMessage().c_str()); + m_mfvModelConfigurationData.clear(); + m_mfvState.modelConfigReadValid = false; if (m_mfvPromiseResults) { m_mfvPromiseResults->setError(reply->errorMessage()); m_mfvPromiseResults->finish(); From 47b2939547d3facac9d6eb5ee0f3be59a413b03f Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 13 Jul 2026 09:07:36 -0400 Subject: [PATCH 49/86] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../blercu/bleservices/gatt/gatt_audioservice_rdk.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index 11ff643d..3939cdc6 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -769,8 +769,7 @@ void GattAudioServiceRdk::maybeEnableMfvNotifications() m_mfvState.detectionDataNotifyRequested = true; requestStartMfvDetectionDataNotify(); } - if (m_mfvState.privacyReadValid && - m_mfvPrivacyCharacteristic && + if (m_mfvPrivacyCharacteristic && !m_mfvPrivacyCharacteristic->notificationsEnabled() && !m_mfvState.privacyNotifyRequested) { m_mfvState.privacyNotifyRequested = true; @@ -1152,7 +1151,13 @@ void GattAudioServiceRdk::writeMfvPrivacy(bool enabled, PendingReply<> &&reply) XLOGD_INFO("Writing MFV Privacy = %s", enabled ? "enabled" : "disabled"); m_mfvPrivacyCharacteristic->writeValue(value, PendingReply<>(getIsAlivePtr(), - std::bind(&GattAudioServiceRdk::onWriteMfvPrivacyReply, this, std::placeholders::_1))); + [this, enabled](PendingReply<> *reply) { + if (m_mfvState.supported && m_mfvPromiseResults && !reply->isError()) { + m_mfvPrivacyEnabled = enabled; + m_mfvState.privacyReadValid = true; + } + this->onWriteMfvPrivacyReply(reply); + })); } void GattAudioServiceRdk::onWriteMfvPrivacyReply(PendingReply<> *reply) From 83c54946ca866b5b610bb94e5deeba15fd98e158 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 13 Jul 2026 13:27:13 +0000 Subject: [PATCH 50/86] Commenting out stream stats for now --- .../blercu/bleservices/blercuaudioservice.h | 21 +++++++++++-------- .../gatt/gatt_audioservice_rdk.cpp | 10 ++++----- .../bleservices/gatt/gatt_audioservice_rdk.h | 5 +++-- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/blercuaudioservice.h b/src/ble/hal/blercu/bleservices/blercuaudioservice.h index 611a9134..4339bc1d 100644 --- a/src/ble/hal/blercu/bleservices/blercuaudioservice.h +++ b/src/ble/hal/blercu/bleservices/blercuaudioservice.h @@ -104,9 +104,10 @@ class BleRcuAudioService uint8_t minor = 0; }; - struct StreamStatsRaw { - std::vector bytes; - }; + // TODO: All bytes are currently Reserved/TBD in the firmware spec, check to make sure this is still the case and delete if so + // struct StreamStatsRaw { + // std::vector bytes; + // }; enum Capabilities { MidfieldVoiceCapable = (1 << 0), @@ -123,7 +124,8 @@ class BleRcuAudioService virtual bool mfvPrivacyEnabled() const { return false; } virtual std::vector mfvModelConfiguration() const { return {}; } virtual uint8_t mfvCapabilities() const { return 0; } - virtual StreamStatsRaw mfvStreamStats() const { return {}; } + // Not yet implemented: stream stats characteristic is Reserved/TBD in the firmware spec. + // virtual StreamStatsRaw mfvStreamStats() const { return {}; } virtual void writeMfvPrivacy(bool enabled, PendingReply<> &&reply) { @@ -171,10 +173,11 @@ class BleRcuAudioService { m_mfvCapabilitiesChangedSlots.addSlot(func); } - inline void addMfvStreamStatsChangedSlot(const Slot &func) - { - m_mfvStreamStatsChangedSlots.addSlot(func); - } + // Not yet implemented: stream stats characteristic is Reserved/TBD in the firmware spec. + // inline void addMfvStreamStatsChangedSlot(const Slot &func) + // { + // m_mfvStreamStatsChangedSlots.addSlot(func); + // } protected: Slots m_streamingChangedSlots; @@ -185,7 +188,7 @@ class BleRcuAudioService Slots m_mfvDetectionDataChangedSlots; Slots m_mfvPrivacyChangedSlots; Slots m_mfvCapabilitiesChangedSlots; - Slots m_mfvStreamStatsChangedSlots; + // Slots m_mfvStreamStatsChangedSlots; }; diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index 3939cdc6..bb25f0d7 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -178,7 +178,7 @@ void GattAudioServiceRdk::onEnteredIdle() { m_mfvPrivacyEnabled = false; m_mfvModelConfigurationData.clear(); m_mfvCapabilitiesValue = 0; - m_mfvStreamStatsData = {}; + // m_mfvStreamStatsData = {}; GattAudioService::onEnteredIdle(); } @@ -1116,10 +1116,10 @@ uint8_t GattAudioServiceRdk::mfvCapabilities() const return m_mfvCapabilitiesValue; } -BleRcuAudioService::StreamStatsRaw GattAudioServiceRdk::mfvStreamStats() const -{ - return m_mfvStreamStatsData; -} +// BleRcuAudioService::StreamStatsRaw GattAudioServiceRdk::mfvStreamStats() const +// { +// return m_mfvStreamStatsData; +// } // ============================================================================= // MFV write handlers diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h index db1e7e09..3aedab55 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h @@ -74,7 +74,8 @@ class GattAudioServiceRdk : public GattAudioService bool mfvPrivacyEnabled() const override; std::vector mfvModelConfiguration() const override; uint8_t mfvCapabilities() const override; - StreamStatsRaw mfvStreamStats() const override; + // Not yet implemented: stream stats characteristic is Reserved/TBD in the firmware spec. + // StreamStatsRaw mfvStreamStats() const override; void writeMfvPrivacy(bool enabled, PendingReply<> &&reply) override; void writeMfvModelConfiguration(uint8_t sensitivity, uint8_t secondary, uint8_t aad, PendingReply<> &&reply) override; @@ -171,7 +172,7 @@ class GattAudioServiceRdk : public GattAudioService bool m_mfvPrivacyEnabled = false; std::vector m_mfvModelConfigurationData; uint8_t m_mfvCapabilitiesValue = 0; - StreamStatsRaw m_mfvStreamStatsData; + // StreamStatsRaw m_mfvStreamStatsData; std::shared_ptr> m_mfvPromiseResults; From 213d3b5b2ef93d3ad6afed4e03a1a641a5e795c8 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 13 Jul 2026 09:28:12 -0400 Subject: [PATCH 51/86] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../gatt/gatt_audioservice_rdk.cpp | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index bb25f0d7..c079fd9c 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -1205,17 +1205,41 @@ void GattAudioServiceRdk::writeMfvModelConfiguration(uint8_t sensitivity, uint8_ return; } + auto previousConfig = m_mfvModelConfigurationData; + auto previousValid = m_mfvState.modelConfigReadValid; + m_mfvPromiseResults = make_shared>(std::move(reply)); const vector value = { sensitivity, secondary, aad }; XLOGD_INFO("Writing MFV Model Config: sensitivity=%u secondary=%u aad=%u", sensitivity, secondary, aad); - m_mfvModelConfigurationData = value; - m_mfvState.modelConfigReadValid = true; - m_mfvModelConfigCharacteristic->writeValue(value, PendingReply<>(getIsAlivePtr(), - std::bind(&GattAudioServiceRdk::onWriteMfvModelConfigReply, this, std::placeholders::_1))); + [this, value, previousConfig = std::move(previousConfig), previousValid](PendingReply<> *writeReply) mutable { + if (writeReply->isError()) { + XLOGD_ERROR("failed to write MFV Model Config due to <%s>", writeReply->errorMessage().c_str()); + + // Restore last-known-good config on failure. + m_mfvModelConfigurationData = std::move(previousConfig); + m_mfvState.modelConfigReadValid = previousValid; + + if (m_mfvPromiseResults) { + m_mfvPromiseResults->setError(writeReply->errorMessage()); + m_mfvPromiseResults->finish(); + m_mfvPromiseResults.reset(); + } + } else { + XLOGD_INFO("MFV Model Config written successfully"); + + m_mfvModelConfigurationData = value; + m_mfvState.modelConfigReadValid = true; + + if (m_mfvPromiseResults) { + m_mfvPromiseResults->finish(); + m_mfvPromiseResults.reset(); + } + } + })); } void GattAudioServiceRdk::onWriteMfvModelConfigReply(PendingReply<> *reply) From 985ac8a771c26e6ea3c6039558ed54548d619fc7 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 13 Jul 2026 13:45:31 +0000 Subject: [PATCH 52/86] Clean up from copilot comments --- .../gatt/gatt_audioservice_rdk.cpp | 31 ++++--------------- .../bleservices/gatt/gatt_audioservice_rdk.h | 1 - 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index c079fd9c..e7ea842e 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -45,6 +45,9 @@ #define AUDIO_SEQ_NUM_MAX (0xFF) +// Number of async characteristic reads issued during MFV initialization (capabilities, model version, privacy, model configuration) +#define MFV_TOTAL_INITIAL_READS (4) + using namespace std; const BleUuid GattAudioServiceRdk::m_serviceUuid(BleUuid::RdkVoice); @@ -109,14 +112,12 @@ bool GattAudioServiceRdk::start(const shared_ptr &gattServ // Discover MFV characteristics (optional - not all remotes support MFV) if (getMfvCharacteristics(gattService)) { m_mfvState.supported = true; - m_mfvState.initialReadsRemaining = 0; - ++m_mfvState.initialReadsRemaining; + // Set the total count before issuing any reads so that early or synchronous + // completion of one read cannot prematurely trigger onMfvInitialReadComplete(). + m_mfvState.initialReadsRemaining = MFV_TOTAL_INITIAL_READS; requestMfvCapabilities(); - ++m_mfvState.initialReadsRemaining; requestMfvModelVersion(); - ++m_mfvState.initialReadsRemaining; requestMfvPrivacy(); - ++m_mfvState.initialReadsRemaining; requestMfvModelConfig(); } @@ -1241,23 +1242,3 @@ void GattAudioServiceRdk::writeMfvModelConfiguration(uint8_t sensitivity, uint8_ } })); } - -void GattAudioServiceRdk::onWriteMfvModelConfigReply(PendingReply<> *reply) -{ - if (reply->isError()) { - XLOGD_ERROR("failed to write MFV Model Config due to <%s>", reply->errorMessage().c_str()); - m_mfvModelConfigurationData.clear(); - m_mfvState.modelConfigReadValid = false; - if (m_mfvPromiseResults) { - m_mfvPromiseResults->setError(reply->errorMessage()); - m_mfvPromiseResults->finish(); - m_mfvPromiseResults.reset(); - } - } else { - XLOGD_INFO("MFV Model Config written successfully"); - if (m_mfvPromiseResults) { - m_mfvPromiseResults->finish(); - m_mfvPromiseResults.reset(); - } - } -} diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h index 3aedab55..6332a56a 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h @@ -111,7 +111,6 @@ class GattAudioServiceRdk : public GattAudioService // MFV write handlers void onWriteMfvPrivacyReply(PendingReply<> *reply); - void onWriteMfvModelConfigReply(PendingReply<> *reply); void requestGainLevel(); void requestAudioCodecs(); From 444efb046e255cd7efb5a95ddda3dd3757bab27c Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 13 Jul 2026 09:58:33 -0400 Subject: [PATCH 53/86] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index e7ea842e..f221c3b7 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -1216,12 +1216,12 @@ void GattAudioServiceRdk::writeMfvModelConfiguration(uint8_t sensitivity, uint8_ sensitivity, secondary, aad); m_mfvModelConfigCharacteristic->writeValue(value, PendingReply<>(getIsAlivePtr(), - [this, value, previousConfig = std::move(previousConfig), previousValid](PendingReply<> *writeReply) mutable { + [this, value, previousConfig, previousValid](PendingReply<> *writeReply) mutable { if (writeReply->isError()) { XLOGD_ERROR("failed to write MFV Model Config due to <%s>", writeReply->errorMessage().c_str()); // Restore last-known-good config on failure. - m_mfvModelConfigurationData = std::move(previousConfig); + m_mfvModelConfigurationData = previousConfig; m_mfvState.modelConfigReadValid = previousValid; if (m_mfvPromiseResults) { From 21b67958d5f4b0bd95e4ccdd2dce8f7c8cf3f368 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 13 Jul 2026 10:10:12 -0400 Subject: [PATCH 54/86] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index f221c3b7..ed50ff0d 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -1156,6 +1156,7 @@ void GattAudioServiceRdk::writeMfvPrivacy(bool enabled, PendingReply<> &&reply) if (m_mfvState.supported && m_mfvPromiseResults && !reply->isError()) { m_mfvPrivacyEnabled = enabled; m_mfvState.privacyReadValid = true; + m_mfvPrivacyChangedSlots.invoke(m_mfvPrivacyEnabled); } this->onWriteMfvPrivacyReply(reply); })); From 0e8887706148edce848658cfff3afba8769fa020 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 13 Jul 2026 14:18:44 +0000 Subject: [PATCH 55/86] Reverting invoking mfv privacy changed as it's already being handled in another function --- src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index ed50ff0d..f221c3b7 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -1156,7 +1156,6 @@ void GattAudioServiceRdk::writeMfvPrivacy(bool enabled, PendingReply<> &&reply) if (m_mfvState.supported && m_mfvPromiseResults && !reply->isError()) { m_mfvPrivacyEnabled = enabled; m_mfvState.privacyReadValid = true; - m_mfvPrivacyChangedSlots.invoke(m_mfvPrivacyEnabled); } this->onWriteMfvPrivacyReply(reply); })); From 91e49366a010c777209f41dbff5409188c9d7bbb Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 13 Jul 2026 14:28:39 +0000 Subject: [PATCH 56/86] Added session type for voice session calls --- src/ble/ctrlm_ble_network.cpp | 7 +++++-- src/ctrlm_network.h | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ble/ctrlm_ble_network.cpp b/src/ble/ctrlm_ble_network.cpp index 598d9db7..f1f60305 100644 --- a/src/ble/ctrlm_ble_network.cpp +++ b/src/ble/ctrlm_ble_network.cpp @@ -544,8 +544,10 @@ void ctrlm_obj_network_ble_t::req_process_voice_session_begin(void *data, int si XLOGD_ERROR("Controller doesn't exist!"); dqm->params->result = CTRLM_IARM_CALL_RESULT_ERROR_INVALID_PARAMETER; } else { - // Currently BLE RCUs only support push-to-talk, so hardcoding here for now - ctrlm_voice_device_t device = CTRLM_VOICE_DEVICE_PTT; + // BLE RCUs use PTT for keypress-triggered sessions and FF for MFV wake-word sessions. + // The voice_device field of the message is set by the caller; it defaults to + // CTRLM_VOICE_DEVICE_PTT (0) when the struct is zeroed via memset. + ctrlm_voice_device_t device = dqm->voice_device; ctrlm_voice_session_response_status_t voice_status; // only support ADPCM from ble-rcu component @@ -2119,6 +2121,7 @@ void ctrlm_obj_network_ble_t::ind_process_rcu_status(void *data, int size) { errno_t safec_rc = memset_s(&msg, sizeof(msg), 0, sizeof(msg)); ERR_CHK(safec_rc); msg.params = &v_params; + msg.voice_device = CTRLM_VOICE_DEVICE_FF; req_process_voice_session_begin(&msg, sizeof(msg)); } diff --git a/src/ctrlm_network.h b/src/ctrlm_network.h index 592e6e84..0ddd33f9 100644 --- a/src/ctrlm_network.h +++ b/src/ctrlm_network.h @@ -86,6 +86,7 @@ typedef struct { ctrlm_main_queue_msg_header_t header; ctrlm_voice_iarm_call_voice_session_t *params; sem_t * semaphore; + ctrlm_voice_device_t voice_device; // defaults to CTRLM_VOICE_DEVICE_PTT (0) when zeroed } ctrlm_main_queue_msg_voice_session_t; typedef struct { From cd31b572377d8b153f5cf61192e36af58f8e4330 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 13 Jul 2026 15:44:14 +0000 Subject: [PATCH 57/86] Fixing error logging and notification enablement --- .../gatt/gatt_audioservice_rdk.cpp | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index f221c3b7..300ab222 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -946,9 +946,16 @@ void GattAudioServiceRdk::requestStartMfvSessionStartNotify() auto replyHandler = [this](PendingReply<> *reply) { if (reply->isError()) { - m_mfvState.sessionStartNotifyRequested = false; - XLOGD_ERROR("failed to enable MFV Session Start notifications due to <%s>", - reply->errorMessage().c_str()); + // "Notify acquired" means BlueZ already has the CCCD set (e.g. leftover from a + // prior connection). Treat it as success so MFV initialisation can complete. + if (reply->errorMessage().find("Notify acquired") != std::string::npos) { + XLOGD_WARN("MFV Session Start notifications already acquired by BlueZ, treating as success"); + maybeEnableMfvNotifications(); + } else { + m_mfvState.sessionStartNotifyRequested = false; + XLOGD_ERROR("failed to enable MFV Session Start notifications due to <%s>", + reply->errorMessage().c_str()); + } } else { XLOGD_DEBUG("MFV Session Start notifications enabled successfully"); maybeEnableMfvNotifications(); @@ -972,9 +979,14 @@ void GattAudioServiceRdk::requestStartMfvDetectionDataNotify() auto replyHandler = [this](PendingReply<> *reply) { if (reply->isError()) { - m_mfvState.detectionDataNotifyRequested = false; - XLOGD_ERROR("failed to enable MFV Detection Data notifications due to <%s>", - reply->errorMessage().c_str()); + if (reply->errorMessage().find("Notify acquired") != std::string::npos) { + XLOGD_WARN("MFV Detection Data notifications already acquired by BlueZ, treating as success"); + maybeEnableMfvNotifications(); + } else { + m_mfvState.detectionDataNotifyRequested = false; + XLOGD_ERROR("failed to enable MFV Detection Data notifications due to <%s>", + reply->errorMessage().c_str()); + } } else { XLOGD_DEBUG("MFV Detection Data notifications enabled successfully"); maybeEnableMfvNotifications(); @@ -998,9 +1010,14 @@ void GattAudioServiceRdk::requestStartMfvPrivacyNotify() auto replyHandler = [this](PendingReply<> *reply) { if (reply->isError()) { - m_mfvState.privacyNotifyRequested = false; - XLOGD_ERROR("failed to enable MFV Privacy notifications due to <%s>", - reply->errorMessage().c_str()); + if (reply->errorMessage().find("Notify acquired") != std::string::npos) { + XLOGD_WARN("MFV Privacy notifications already acquired by BlueZ, treating as success"); + maybeEnableMfvNotifications(); + } else { + m_mfvState.privacyNotifyRequested = false; + XLOGD_ERROR("failed to enable MFV Privacy notifications due to <%s>", + reply->errorMessage().c_str()); + } } else { XLOGD_DEBUG("MFV Privacy notifications enabled successfully"); maybeEnableMfvNotifications(); From 153541377ff07608915156680de93e932d87720a Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 13 Jul 2026 16:16:55 +0000 Subject: [PATCH 58/86] Using state to retry notifications --- .../bleservices/gatt/gatt_audioservice.cpp | 11 +++ .../bleservices/gatt/gatt_audioservice.h | 3 + .../gatt/gatt_audioservice_rdk.cpp | 92 +++++++++++++++---- .../bleservices/gatt/gatt_audioservice_rdk.h | 12 +++ 4 files changed, 101 insertions(+), 17 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.cpp index ec9819d1..8e30c741 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.cpp @@ -97,6 +97,7 @@ void GattAudioService::init() m_stateMachine.addTransition(ReadyState, StartStreamingRequestEvent, EnableNotificationsState); m_stateMachine.addTransition(EnableNotificationsState, NotificationsEnabledEvent, StartStreamingState); + m_stateMachine.addTransition(EnableNotificationsState, RetryEnableNotificationsEvent, EnableNotificationsState); m_stateMachine.addTransition(StartStreamingState, StreamingStartedEvent, StreamingState); @@ -738,6 +739,16 @@ void GattAudioService::stateMachinePostEvent(const Event::Type event) m_stateMachine.postEvent(event); } +int64_t GattAudioService::stateMachinePostDelayedEvent(const Event::Type event, int delayMs) +{ + return m_stateMachine.postDelayedEvent(event, delayMs); +} + +bool GattAudioService::stateMachineCancelDelayedEvent(int64_t id) +{ + return m_stateMachine.cancelDelayedEvent(id); +} + bool GattAudioService::stateMachineIsIdle() { return m_stateMachine.inState(IdleState); diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.h b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.h index 24864c06..ea8b4d9b 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.h @@ -71,6 +71,8 @@ class GattAudioService : public BleRcuAudioService void updateFrameCountSupport(bool frameCountSupported); void stateMachinePostEvent(const Event::Type event); + int64_t stateMachinePostDelayedEvent(const Event::Type event, int delayMs); + bool stateMachineCancelDelayedEvent(int64_t id); bool stateMachineIsIdle(); void setLastError(BleRcuAudioService::StreamingError error); @@ -168,6 +170,7 @@ class GattAudioService : public BleRcuAudioService static const Event::Type AudioInfoTimeoutEvent = Event::Type(Event::User + 10); static const Event::Type AudioLastFrameTimeoutEvent = Event::Type(Event::User + 11); + static const Event::Type RetryEnableNotificationsEvent = Event::Type(Event::User + 12); }; #endif // !defined(GATT_AUDIOSERVICE_H) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index 300ab222..0dd5bfc2 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -47,6 +47,8 @@ // Number of async characteristic reads issued during MFV initialization (capabilities, model version, privacy, model configuration) #define MFV_TOTAL_INITIAL_READS (4) +#define MFV_NOTIFY_RETRY_DELAY_MS (1000) +#define MFV_NOTIFY_MAX_RETRIES (3) using namespace std; @@ -65,6 +67,10 @@ GattAudioServiceRdk::GattAudioServiceRdk(GMainLoop* mainLoop) : GattAudioService GattAudioServiceRdk::~GattAudioServiceRdk() { + if (m_mfvNotifyRetryEventId >= 0) { + stateMachineCancelDelayedEvent(m_mfvNotifyRetryEventId); + m_mfvNotifyRetryEventId = -1; + } } // ----------------------------------------------------------------------------- @@ -137,6 +143,11 @@ void GattAudioServiceRdk::stop() void GattAudioServiceRdk::onEnteredIdle() { m_mfvState.reset(); + if (m_mfvNotifyRetryEventId >= 0) { + stateMachineCancelDelayedEvent(m_mfvNotifyRetryEventId); + m_mfvNotifyRetryEventId = -1; + } + if (m_audioDataCharacteristic) { XLOGD_INFO("Disabling notifications for m_audioDataCharacteristic"); m_audioDataCharacteristic->disableNotifications(); @@ -195,6 +206,9 @@ void GattAudioServiceRdk::onEnteredIdle() { */ void GattAudioServiceRdk::onEnteredEnableNotificationsState() { + // If we got here via delayed retry event, mark it consumed. + m_mfvNotifyRetryEventId = -1; + auto replyHandlerData = [this](PendingReply<> *reply) { if (reply->isError()) { @@ -787,6 +801,25 @@ void GattAudioServiceRdk::maybeEnableMfvNotifications() } } +void GattAudioServiceRdk::scheduleMfvNotifyRetry() +{ + if (!m_mfvState.supported || m_mfvState.notificationsEnabled || (m_mfvNotifyRetryEventId >= 0)) { + return; + } + + m_mfvNotifyRetryEventId = stateMachinePostDelayedEvent(RetryEnableNotificationsEvent, MFV_NOTIFY_RETRY_DELAY_MS); +} + +void GattAudioServiceRdk::disableMfvAfterNotifyFailures(const char *reason) +{ + XLOGD_ERROR("disabling MFV notifications after repeated failures (%s)", reason); + m_mfvState.supported = false; + m_mfvState.notificationsEnabled = false; + m_mfvState.sessionStartNotifyRequested = false; + m_mfvState.detectionDataNotifyRequested = false; + m_mfvState.privacyNotifyRequested = false; +} + // ----------------------------------------------------------------------------- /*! \internal @@ -946,17 +979,24 @@ void GattAudioServiceRdk::requestStartMfvSessionStartNotify() auto replyHandler = [this](PendingReply<> *reply) { if (reply->isError()) { - // "Notify acquired" means BlueZ already has the CCCD set (e.g. leftover from a - // prior connection). Treat it as success so MFV initialisation can complete. + m_mfvState.sessionStartNotifyRequested = false; + ++m_mfvState.sessionStartNotifyRetries; + if (reply->errorMessage().find("Notify acquired") != std::string::npos) { - XLOGD_WARN("MFV Session Start notifications already acquired by BlueZ, treating as success"); - maybeEnableMfvNotifications(); + XLOGD_WARN("MFV Session Start notify returned 'Notify acquired' but notification state is still disabled; retrying (%u/%u)", + m_mfvState.sessionStartNotifyRetries, MFV_NOTIFY_MAX_RETRIES); + } else { + XLOGD_ERROR("failed to enable MFV Session Start notifications due to <%s> (retry %u/%u)", + reply->errorMessage().c_str(), m_mfvState.sessionStartNotifyRetries, MFV_NOTIFY_MAX_RETRIES); + } + + if (m_mfvState.sessionStartNotifyRetries >= MFV_NOTIFY_MAX_RETRIES) { + disableMfvAfterNotifyFailures("Session Start notify"); } else { - m_mfvState.sessionStartNotifyRequested = false; - XLOGD_ERROR("failed to enable MFV Session Start notifications due to <%s>", - reply->errorMessage().c_str()); + scheduleMfvNotifyRetry(); } } else { + m_mfvState.sessionStartNotifyRetries = 0; XLOGD_DEBUG("MFV Session Start notifications enabled successfully"); maybeEnableMfvNotifications(); } @@ -979,15 +1019,24 @@ void GattAudioServiceRdk::requestStartMfvDetectionDataNotify() auto replyHandler = [this](PendingReply<> *reply) { if (reply->isError()) { + m_mfvState.detectionDataNotifyRequested = false; + ++m_mfvState.detectionDataNotifyRetries; + if (reply->errorMessage().find("Notify acquired") != std::string::npos) { - XLOGD_WARN("MFV Detection Data notifications already acquired by BlueZ, treating as success"); - maybeEnableMfvNotifications(); + XLOGD_WARN("MFV Detection Data notify returned 'Notify acquired' but notification state is still disabled; retrying (%u/%u)", + m_mfvState.detectionDataNotifyRetries, MFV_NOTIFY_MAX_RETRIES); + } else { + XLOGD_ERROR("failed to enable MFV Detection Data notifications due to <%s> (retry %u/%u)", + reply->errorMessage().c_str(), m_mfvState.detectionDataNotifyRetries, MFV_NOTIFY_MAX_RETRIES); + } + + if (m_mfvState.detectionDataNotifyRetries >= MFV_NOTIFY_MAX_RETRIES) { + disableMfvAfterNotifyFailures("Detection Data notify"); } else { - m_mfvState.detectionDataNotifyRequested = false; - XLOGD_ERROR("failed to enable MFV Detection Data notifications due to <%s>", - reply->errorMessage().c_str()); + scheduleMfvNotifyRetry(); } } else { + m_mfvState.detectionDataNotifyRetries = 0; XLOGD_DEBUG("MFV Detection Data notifications enabled successfully"); maybeEnableMfvNotifications(); } @@ -1010,15 +1059,24 @@ void GattAudioServiceRdk::requestStartMfvPrivacyNotify() auto replyHandler = [this](PendingReply<> *reply) { if (reply->isError()) { + m_mfvState.privacyNotifyRequested = false; + ++m_mfvState.privacyNotifyRetries; + if (reply->errorMessage().find("Notify acquired") != std::string::npos) { - XLOGD_WARN("MFV Privacy notifications already acquired by BlueZ, treating as success"); - maybeEnableMfvNotifications(); + XLOGD_WARN("MFV Privacy notify returned 'Notify acquired' but notification state is still disabled; retrying (%u/%u)", + m_mfvState.privacyNotifyRetries, MFV_NOTIFY_MAX_RETRIES); + } else { + XLOGD_ERROR("failed to enable MFV Privacy notifications due to <%s> (retry %u/%u)", + reply->errorMessage().c_str(), m_mfvState.privacyNotifyRetries, MFV_NOTIFY_MAX_RETRIES); + } + + if (m_mfvState.privacyNotifyRetries >= MFV_NOTIFY_MAX_RETRIES) { + disableMfvAfterNotifyFailures("Privacy notify"); } else { - m_mfvState.privacyNotifyRequested = false; - XLOGD_ERROR("failed to enable MFV Privacy notifications due to <%s>", - reply->errorMessage().c_str()); + scheduleMfvNotifyRetry(); } } else { + m_mfvState.privacyNotifyRetries = 0; XLOGD_DEBUG("MFV Privacy notifications enabled successfully"); maybeEnableMfvNotifications(); } diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h index 6332a56a..7a91acfe 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h @@ -98,6 +98,8 @@ class GattAudioServiceRdk : public GattAudioService void requestMfvModelConfig(); void onMfvInitialReadComplete(); void maybeEnableMfvNotifications(); + void scheduleMfvNotifyRetry(); + void disableMfvAfterNotifyFailures(const char *reason); // MFV notification enable void requestStartMfvSessionStartNotify(); @@ -140,6 +142,10 @@ class GattAudioServiceRdk : public GattAudioService bool detectionDataNotifyRequested = false; bool privacyNotifyRequested = false; + uint8_t sessionStartNotifyRetries = 0; + uint8_t detectionDataNotifyRetries = 0; + uint8_t privacyNotifyRetries = 0; + void reset() { supported = false; @@ -154,6 +160,10 @@ class GattAudioServiceRdk : public GattAudioService sessionStartNotifyRequested = false; detectionDataNotifyRequested = false; privacyNotifyRequested = false; + + sessionStartNotifyRetries = 0; + detectionDataNotifyRetries = 0; + privacyNotifyRetries = 0; } bool areInitialReadsComplete() const @@ -182,6 +192,8 @@ class GattAudioServiceRdk : public GattAudioService std::shared_ptr m_mfvModelConfigCharacteristic; std::shared_ptr m_mfvCapabilitiesCharacteristic; + int64_t m_mfvNotifyRetryEventId = -1; + static const BleUuid m_mfvSessionStartCharUuid; static const BleUuid m_mfvDetectionDataCharUuid; static const BleUuid m_mfvModelVersionCharUuid; From 90ea44b1db336fddbe5f4581c8b42427e0164aac Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 14 Jul 2026 11:40:40 +0000 Subject: [PATCH 59/86] Using timer instead of state machine for retry logic --- .../bleservices/gatt/gatt_audioservice.cpp | 11 ----- .../bleservices/gatt/gatt_audioservice.h | 3 -- .../gatt/gatt_audioservice_rdk.cpp | 41 +++++++++++++------ .../bleservices/gatt/gatt_audioservice_rdk.h | 5 +-- 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.cpp index 8e30c741..ec9819d1 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.cpp @@ -97,7 +97,6 @@ void GattAudioService::init() m_stateMachine.addTransition(ReadyState, StartStreamingRequestEvent, EnableNotificationsState); m_stateMachine.addTransition(EnableNotificationsState, NotificationsEnabledEvent, StartStreamingState); - m_stateMachine.addTransition(EnableNotificationsState, RetryEnableNotificationsEvent, EnableNotificationsState); m_stateMachine.addTransition(StartStreamingState, StreamingStartedEvent, StreamingState); @@ -739,16 +738,6 @@ void GattAudioService::stateMachinePostEvent(const Event::Type event) m_stateMachine.postEvent(event); } -int64_t GattAudioService::stateMachinePostDelayedEvent(const Event::Type event, int delayMs) -{ - return m_stateMachine.postDelayedEvent(event, delayMs); -} - -bool GattAudioService::stateMachineCancelDelayedEvent(int64_t id) -{ - return m_stateMachine.cancelDelayedEvent(id); -} - bool GattAudioService::stateMachineIsIdle() { return m_stateMachine.inState(IdleState); diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.h b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.h index ea8b4d9b..24864c06 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.h @@ -71,8 +71,6 @@ class GattAudioService : public BleRcuAudioService void updateFrameCountSupport(bool frameCountSupported); void stateMachinePostEvent(const Event::Type event); - int64_t stateMachinePostDelayedEvent(const Event::Type event, int delayMs); - bool stateMachineCancelDelayedEvent(int64_t id); bool stateMachineIsIdle(); void setLastError(BleRcuAudioService::StreamingError error); @@ -170,7 +168,6 @@ class GattAudioService : public BleRcuAudioService static const Event::Type AudioInfoTimeoutEvent = Event::Type(Event::User + 10); static const Event::Type AudioLastFrameTimeoutEvent = Event::Type(Event::User + 11); - static const Event::Type RetryEnableNotificationsEvent = Event::Type(Event::User + 12); }; #endif // !defined(GATT_AUDIOSERVICE_H) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index 0dd5bfc2..d717b6a3 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -67,9 +67,9 @@ GattAudioServiceRdk::GattAudioServiceRdk(GMainLoop* mainLoop) : GattAudioService GattAudioServiceRdk::~GattAudioServiceRdk() { - if (m_mfvNotifyRetryEventId >= 0) { - stateMachineCancelDelayedEvent(m_mfvNotifyRetryEventId); - m_mfvNotifyRetryEventId = -1; + if (m_mfvNotifyRetryTimer > 0) { + g_source_remove(m_mfvNotifyRetryTimer); + m_mfvNotifyRetryTimer = 0; } } @@ -142,10 +142,9 @@ void GattAudioServiceRdk::stop() void GattAudioServiceRdk::onEnteredIdle() { m_mfvState.reset(); - - if (m_mfvNotifyRetryEventId >= 0) { - stateMachineCancelDelayedEvent(m_mfvNotifyRetryEventId); - m_mfvNotifyRetryEventId = -1; + if (m_mfvNotifyRetryTimer > 0) { + g_source_remove(m_mfvNotifyRetryTimer); + m_mfvNotifyRetryTimer = 0; } if (m_audioDataCharacteristic) { @@ -206,9 +205,6 @@ void GattAudioServiceRdk::onEnteredIdle() { */ void GattAudioServiceRdk::onEnteredEnableNotificationsState() { - // If we got here via delayed retry event, mark it consumed. - m_mfvNotifyRetryEventId = -1; - auto replyHandlerData = [this](PendingReply<> *reply) { if (reply->isError()) { @@ -803,16 +799,37 @@ void GattAudioServiceRdk::maybeEnableMfvNotifications() void GattAudioServiceRdk::scheduleMfvNotifyRetry() { - if (!m_mfvState.supported || m_mfvState.notificationsEnabled || (m_mfvNotifyRetryEventId >= 0)) { + if (!m_mfvState.supported || m_mfvState.notificationsEnabled || (m_mfvNotifyRetryTimer > 0)) { return; } - m_mfvNotifyRetryEventId = stateMachinePostDelayedEvent(RetryEnableNotificationsEvent, MFV_NOTIFY_RETRY_DELAY_MS); + m_mfvNotifyRetryTimer = g_timeout_add(MFV_NOTIFY_RETRY_DELAY_MS, + [](gpointer userData) -> gboolean { + auto *self = static_cast(userData); + self->m_mfvNotifyRetryTimer = 0; + self->maybeEnableMfvNotifications(); + return G_SOURCE_REMOVE; + }, this); } void GattAudioServiceRdk::disableMfvAfterNotifyFailures(const char *reason) { XLOGD_ERROR("disabling MFV notifications after repeated failures (%s)", reason); + if (m_mfvNotifyRetryTimer > 0) { + g_source_remove(m_mfvNotifyRetryTimer); + m_mfvNotifyRetryTimer = 0; + } + + if (m_mfvSessionStartCharacteristic && m_mfvSessionStartCharacteristic->notificationsEnabled()) { + m_mfvSessionStartCharacteristic->disableNotifications(); + } + if (m_mfvDetectionDataCharacteristic && m_mfvDetectionDataCharacteristic->notificationsEnabled()) { + m_mfvDetectionDataCharacteristic->disableNotifications(); + } + if (m_mfvPrivacyCharacteristic && m_mfvPrivacyCharacteristic->notificationsEnabled()) { + m_mfvPrivacyCharacteristic->disableNotifications(); + } + m_mfvState.supported = false; m_mfvState.notificationsEnabled = false; m_mfvState.sessionStartNotifyRequested = false; diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h index 7a91acfe..52c699f9 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h @@ -74,8 +74,7 @@ class GattAudioServiceRdk : public GattAudioService bool mfvPrivacyEnabled() const override; std::vector mfvModelConfiguration() const override; uint8_t mfvCapabilities() const override; - // Not yet implemented: stream stats characteristic is Reserved/TBD in the firmware spec. - // StreamStatsRaw mfvStreamStats() const override; + //StreamStatsRaw mfvStreamStats() const override; void writeMfvPrivacy(bool enabled, PendingReply<> &&reply) override; void writeMfvModelConfiguration(uint8_t sensitivity, uint8_t secondary, uint8_t aad, PendingReply<> &&reply) override; @@ -192,7 +191,7 @@ class GattAudioServiceRdk : public GattAudioService std::shared_ptr m_mfvModelConfigCharacteristic; std::shared_ptr m_mfvCapabilitiesCharacteristic; - int64_t m_mfvNotifyRetryEventId = -1; + unsigned int m_mfvNotifyRetryTimer = 0; static const BleUuid m_mfvSessionStartCharUuid; static const BleUuid m_mfvDetectionDataCharUuid; From 1df2b030fa26efc9ae10dd186a75f5a0528b63be Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 14 Jul 2026 16:03:49 +0000 Subject: [PATCH 60/86] No longer setting a cap fro notify retries. --- .../gatt/gatt_audioservice_rdk.cpp | 71 ++++--------------- .../bleservices/gatt/gatt_audioservice_rdk.h | 1 - 2 files changed, 15 insertions(+), 57 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index d717b6a3..dab8a31f 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -48,7 +48,6 @@ // Number of async characteristic reads issued during MFV initialization (capabilities, model version, privacy, model configuration) #define MFV_TOTAL_INITIAL_READS (4) #define MFV_NOTIFY_RETRY_DELAY_MS (1000) -#define MFV_NOTIFY_MAX_RETRIES (3) using namespace std; @@ -812,31 +811,6 @@ void GattAudioServiceRdk::scheduleMfvNotifyRetry() }, this); } -void GattAudioServiceRdk::disableMfvAfterNotifyFailures(const char *reason) -{ - XLOGD_ERROR("disabling MFV notifications after repeated failures (%s)", reason); - if (m_mfvNotifyRetryTimer > 0) { - g_source_remove(m_mfvNotifyRetryTimer); - m_mfvNotifyRetryTimer = 0; - } - - if (m_mfvSessionStartCharacteristic && m_mfvSessionStartCharacteristic->notificationsEnabled()) { - m_mfvSessionStartCharacteristic->disableNotifications(); - } - if (m_mfvDetectionDataCharacteristic && m_mfvDetectionDataCharacteristic->notificationsEnabled()) { - m_mfvDetectionDataCharacteristic->disableNotifications(); - } - if (m_mfvPrivacyCharacteristic && m_mfvPrivacyCharacteristic->notificationsEnabled()) { - m_mfvPrivacyCharacteristic->disableNotifications(); - } - - m_mfvState.supported = false; - m_mfvState.notificationsEnabled = false; - m_mfvState.sessionStartNotifyRequested = false; - m_mfvState.detectionDataNotifyRequested = false; - m_mfvState.privacyNotifyRequested = false; -} - // ----------------------------------------------------------------------------- /*! \internal @@ -1000,18 +974,13 @@ void GattAudioServiceRdk::requestStartMfvSessionStartNotify() ++m_mfvState.sessionStartNotifyRetries; if (reply->errorMessage().find("Notify acquired") != std::string::npos) { - XLOGD_WARN("MFV Session Start notify returned 'Notify acquired' but notification state is still disabled; retrying (%u/%u)", - m_mfvState.sessionStartNotifyRetries, MFV_NOTIFY_MAX_RETRIES); - } else { - XLOGD_ERROR("failed to enable MFV Session Start notifications due to <%s> (retry %u/%u)", - reply->errorMessage().c_str(), m_mfvState.sessionStartNotifyRetries, MFV_NOTIFY_MAX_RETRIES); - } - - if (m_mfvState.sessionStartNotifyRetries >= MFV_NOTIFY_MAX_RETRIES) { - disableMfvAfterNotifyFailures("Session Start notify"); + XLOGD_WARN("MFV Session Start notify returned 'Notify acquired' but notification state is still disabled; retrying (attempt %u)", + m_mfvState.sessionStartNotifyRetries); } else { - scheduleMfvNotifyRetry(); + XLOGD_ERROR("failed to enable MFV Session Start notifications due to <%s> (retry attempt %u)", + reply->errorMessage().c_str(), m_mfvState.sessionStartNotifyRetries); } + scheduleMfvNotifyRetry(); } else { m_mfvState.sessionStartNotifyRetries = 0; XLOGD_DEBUG("MFV Session Start notifications enabled successfully"); @@ -1040,18 +1009,13 @@ void GattAudioServiceRdk::requestStartMfvDetectionDataNotify() ++m_mfvState.detectionDataNotifyRetries; if (reply->errorMessage().find("Notify acquired") != std::string::npos) { - XLOGD_WARN("MFV Detection Data notify returned 'Notify acquired' but notification state is still disabled; retrying (%u/%u)", - m_mfvState.detectionDataNotifyRetries, MFV_NOTIFY_MAX_RETRIES); + XLOGD_WARN("MFV Detection Data notify returned 'Notify acquired' but notification state is still disabled; retrying (attempt %u)", + m_mfvState.detectionDataNotifyRetries); } else { - XLOGD_ERROR("failed to enable MFV Detection Data notifications due to <%s> (retry %u/%u)", - reply->errorMessage().c_str(), m_mfvState.detectionDataNotifyRetries, MFV_NOTIFY_MAX_RETRIES); - } - - if (m_mfvState.detectionDataNotifyRetries >= MFV_NOTIFY_MAX_RETRIES) { - disableMfvAfterNotifyFailures("Detection Data notify"); - } else { - scheduleMfvNotifyRetry(); + XLOGD_ERROR("failed to enable MFV Detection Data notifications due to <%s> (retry attempt %u)", + reply->errorMessage().c_str(), m_mfvState.detectionDataNotifyRetries); } + scheduleMfvNotifyRetry(); } else { m_mfvState.detectionDataNotifyRetries = 0; XLOGD_DEBUG("MFV Detection Data notifications enabled successfully"); @@ -1080,18 +1044,13 @@ void GattAudioServiceRdk::requestStartMfvPrivacyNotify() ++m_mfvState.privacyNotifyRetries; if (reply->errorMessage().find("Notify acquired") != std::string::npos) { - XLOGD_WARN("MFV Privacy notify returned 'Notify acquired' but notification state is still disabled; retrying (%u/%u)", - m_mfvState.privacyNotifyRetries, MFV_NOTIFY_MAX_RETRIES); - } else { - XLOGD_ERROR("failed to enable MFV Privacy notifications due to <%s> (retry %u/%u)", - reply->errorMessage().c_str(), m_mfvState.privacyNotifyRetries, MFV_NOTIFY_MAX_RETRIES); - } - - if (m_mfvState.privacyNotifyRetries >= MFV_NOTIFY_MAX_RETRIES) { - disableMfvAfterNotifyFailures("Privacy notify"); + XLOGD_WARN("MFV Privacy notify returned 'Notify acquired' but notification state is still disabled; retrying (attempt %u)", + m_mfvState.privacyNotifyRetries); } else { - scheduleMfvNotifyRetry(); + XLOGD_ERROR("failed to enable MFV Privacy notifications due to <%s> (retry attempt %u)", + reply->errorMessage().c_str(), m_mfvState.privacyNotifyRetries); } + scheduleMfvNotifyRetry(); } else { m_mfvState.privacyNotifyRetries = 0; XLOGD_DEBUG("MFV Privacy notifications enabled successfully"); diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h index 52c699f9..d442a3a5 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h @@ -98,7 +98,6 @@ class GattAudioServiceRdk : public GattAudioService void onMfvInitialReadComplete(); void maybeEnableMfvNotifications(); void scheduleMfvNotifyRetry(); - void disableMfvAfterNotifyFailures(const char *reason); // MFV notification enable void requestStartMfvSessionStartNotify(); From 10018c872fba9387068c9536781364a9b85728dd Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 14 Jul 2026 16:32:21 +0000 Subject: [PATCH 61/86] Added logging for debugging --- src/ble/hal/blercu/bluez/blegattcharacteristic.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp index e79849c9..88223780 100644 --- a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp +++ b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp @@ -555,6 +555,11 @@ void BleGattCharacteristicBluez::enablePipeNotifications(const SlotgetName()); if (dbusReply->isError()) { + XLOGD_WARN("AcquireNotify failed for characteristic %s at %s due to <%s:%s>", + m_uuid.toString().c_str(), + m_path.c_str(), + dbusReply->getName().c_str(), + dbusReply->errorMessage().c_str()); reply.setError(dbusReply->errorMessage()); reply.finish(); return; From ab2eb42a2138329a47f1ee26bf9038130023bbdb Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 15 Jul 2026 14:04:18 +0000 Subject: [PATCH 62/86] Potential fix for notify failures --- src/ble/hal/blercu/bluez/blegattcharacteristic.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp index 88223780..a4f6ebeb 100644 --- a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp +++ b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp @@ -627,9 +627,13 @@ void BleGattCharacteristicBluez::enablePipeNotifications(const Slot Date: Wed, 15 Jul 2026 10:11:58 -0400 Subject: [PATCH 63/86] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h | 6 +++--- src/ble/hal/blercu/bluez/blegattcharacteristic.cpp | 6 ++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h index d442a3a5..c74c68c4 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.h @@ -140,9 +140,9 @@ class GattAudioServiceRdk : public GattAudioService bool detectionDataNotifyRequested = false; bool privacyNotifyRequested = false; - uint8_t sessionStartNotifyRetries = 0; - uint8_t detectionDataNotifyRetries = 0; - uint8_t privacyNotifyRetries = 0; + unsigned int sessionStartNotifyRetries = 0; + unsigned int detectionDataNotifyRetries = 0; + unsigned int privacyNotifyRetries = 0; void reset() { diff --git a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp index a4f6ebeb..67a28a44 100644 --- a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp +++ b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp @@ -627,10 +627,8 @@ void BleGattCharacteristicBluez::enablePipeNotifications(const Slot Date: Wed, 15 Jul 2026 15:15:04 +0000 Subject: [PATCH 64/86] Another attempt to get notify working --- src/ble/hal/blercu/bluez/blegattcharacteristic.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp index 67a28a44..886e1b30 100644 --- a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp +++ b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp @@ -560,6 +560,17 @@ void BleGattCharacteristicBluez::enablePipeNotifications(const SlotgetName().c_str(), dbusReply->errorMessage().c_str()); + + // Some BlueZ versions/devices return NotPermitted: Notify acquired + // when CCCD state is already active across reconnects. In that case, + // fall back to StartNotify (DBus notifications) so we can still + // receive notifications instead of failing forever on AcquireNotify. + if (dbusReply->errorMessage().find("Notify acquired") != std::string::npos) { + XLOGD_WARN("falling back to StartNotify for %s after AcquireNotify NotPermitted", m_uuid.toString().c_str()); + enableDbusNotifications(notifyCB, std::move(reply)); + return; + } + reply.setError(dbusReply->errorMessage()); reply.finish(); return; From a489aea8ab48ababc16a37bb3de8434d636e5446 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 15 Jul 2026 11:34:24 -0400 Subject: [PATCH 65/86] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ble/hal/blercu/bluez/blegattcharacteristic.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp index 886e1b30..ac8c73a0 100644 --- a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp +++ b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp @@ -567,7 +567,18 @@ void BleGattCharacteristicBluez::enablePipeNotifications(const SloterrorMessage().find("Notify acquired") != std::string::npos) { XLOGD_WARN("falling back to StartNotify for %s after AcquireNotify NotPermitted", m_uuid.toString().c_str()); - enableDbusNotifications(notifyCB, std::move(reply)); + + // enableDbusNotifications() sets m_notifyEnabled immediately, so wrap the reply to clear state on failure. + auto wrappedReply = PendingReply<>(m_isAlive, [this, reply = std::move(reply)](PendingReply<> *inner) mutable { + reply.setName(inner->getName()); + if (inner->isError()) { + m_notifyEnabled = false; + reply.setError(inner->errorMessage()); + } + reply.finish(); + }); + + enableDbusNotifications(notifyCB, std::move(wrappedReply)); return; } From 96c6e069837028ea6995355fc064234566499906 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 15 Jul 2026 15:37:27 +0000 Subject: [PATCH 66/86] Temp test --- .../hal/blercu/bluez/blegattcharacteristic.cpp | 18 +++++++++--------- .../hal/blercu/bluez/blegattcharacteristic_p.h | 6 ++++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp index ac8c73a0..ef5a921c 100644 --- a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp +++ b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp @@ -422,7 +422,7 @@ void BleGattCharacteristicBluez::writeValueWithoutResponse(const std::vectorStopNotifySync(); } - m_notifyEnabled = false; + m_notifyEnabled.store(false); } @@ -503,14 +503,14 @@ void BleGattCharacteristicBluez::enableDbusNotifications(const SlotStartNotify(notifyCB, std::move(reply)); - m_notifyEnabled = true; + m_notifyEnabled.store(true); } @@ -543,7 +543,7 @@ void BleGattCharacteristicBluez::enablePipeNotifications(const Slot(m_isAlive, [this, reply = std::move(reply)](PendingReply<> *inner) mutable { reply.setName(inner->getName()); if (inner->isError()) { - m_notifyEnabled = false; + m_notifyEnabled.store(false); reply.setError(inner->errorMessage()); } reply.finish(); @@ -627,7 +627,7 @@ void BleGattCharacteristicBluez::enablePipeNotifications(const SlotaddNotificationSlot(notifyCB); @@ -655,5 +655,5 @@ void BleGattCharacteristicBluez::enablePipeNotifications(const Slot + class BleGattNotifyPipe; class BleGattDescriptorBluez; @@ -136,8 +138,8 @@ class BleGattCharacteristicBluez : public BleGattCharacteristic, public BleGattC uint16_t m_flags; BleUuid m_uuid; int m_instanceId; - - bool m_notifyEnabled; + // TODO: Making this thread safe temporarily with atomic until we fix the notify issue + std::atomic m_notifyEnabled; std::shared_ptr m_notifyPipe; std::map> m_descriptors; From 5ab2dde8be44a5f7f309f8c78f0da26dd45fdc9f Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 15 Jul 2026 11:38:41 -0400 Subject: [PATCH 67/86] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- include/ctrlm_hal_ble.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/ctrlm_hal_ble.h b/include/ctrlm_hal_ble.h index 082bdb39..721eb84e 100644 --- a/include/ctrlm_hal_ble.h +++ b/include/ctrlm_hal_ble.h @@ -61,9 +61,7 @@ typedef enum { CTRLM_HAL_BLE_MFV_DETECTION_BELOW_THRESHOLD = 0x03, } ctrlm_hal_ble_MfvDetectionType_t; -/// @brief Supported voice data encoding types. -typedef enum { - CTRLM_HAL_BLE_PROPERTY_IEEE_ADDDRESS = 0, +/// @brief BLE RCU property identifiers used in ctrlm_hal_ble_RcuStatusData_t::property_updated. CTRLM_HAL_BLE_PROPERTY_DEVICE_ID, CTRLM_HAL_BLE_PROPERTY_NAME, CTRLM_HAL_BLE_PROPERTY_MANUFACTURER, From c7671d1ba44a6160d4c376aeab48ba87f5f5f748 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 15 Jul 2026 16:02:39 +0000 Subject: [PATCH 68/86] More changes --- include/ctrlm_hal_ble.h | 4 +++- src/ble/hal/blercu/bluez/blegattcharacteristic.cpp | 11 +++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/include/ctrlm_hal_ble.h b/include/ctrlm_hal_ble.h index 721eb84e..082bdb39 100644 --- a/include/ctrlm_hal_ble.h +++ b/include/ctrlm_hal_ble.h @@ -61,7 +61,9 @@ typedef enum { CTRLM_HAL_BLE_MFV_DETECTION_BELOW_THRESHOLD = 0x03, } ctrlm_hal_ble_MfvDetectionType_t; -/// @brief BLE RCU property identifiers used in ctrlm_hal_ble_RcuStatusData_t::property_updated. +/// @brief Supported voice data encoding types. +typedef enum { + CTRLM_HAL_BLE_PROPERTY_IEEE_ADDDRESS = 0, CTRLM_HAL_BLE_PROPERTY_DEVICE_ID, CTRLM_HAL_BLE_PROPERTY_NAME, CTRLM_HAL_BLE_PROPERTY_MANUFACTURER, diff --git a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp index ef5a921c..a24775d5 100644 --- a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp +++ b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp @@ -572,8 +572,15 @@ void BleGattCharacteristicBluez::enablePipeNotifications(const Slot(m_isAlive, [this, reply = std::move(reply)](PendingReply<> *inner) mutable { reply.setName(inner->getName()); if (inner->isError()) { - m_notifyEnabled.store(false); - reply.setError(inner->errorMessage()); + // If StartNotify also reports "Notify acquired", treat it as success. + // We already registered the Value property callback, and this condition + // indicates BlueZ considers notifications active. + if (inner->errorMessage().find("Notify acquired") != std::string::npos) { + XLOGD_WARN("StartNotify returned 'Notify acquired' for %s; treating as success", m_uuid.toString().c_str()); + } else { + m_notifyEnabled.store(false); + reply.setError(inner->errorMessage()); + } } reply.finish(); }); From c4699138ee5a3d06b8210af2b4993453ae15f6a3 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 15 Jul 2026 16:09:31 +0000 Subject: [PATCH 69/86] Fix --- .../hal/blercu/bluez/blegattcharacteristic.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp index a24775d5..0aa304d7 100644 --- a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp +++ b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp @@ -509,8 +509,19 @@ void BleGattCharacteristicBluez::enableDbusNotifications(const SlotStartNotify(notifyCB, std::move(reply)); - m_notifyEnabled.store(true); + // StartNotify is async; only mark enabled after the reply reports success. + auto wrappedReply = PendingReply<>(m_isAlive, [this, reply = std::move(reply)](PendingReply<> *inner) mutable { + reply.setName(inner->getName()); + if (inner->isError()) { + m_notifyEnabled.store(false); + reply.setError(inner->errorMessage()); + } else { + m_notifyEnabled.store(true); + } + reply.finish(); + }); + + proxy->StartNotify(notifyCB, std::move(wrappedReply)); } From 702d9b20e62334d73818256b9641daa2f167e1c0 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 16 Jul 2026 13:08:31 +0000 Subject: [PATCH 70/86] Reverting changes made to debug/fix the notify issues --- .../blercu/bluez/blegattcharacteristic.cpp | 59 ++++--------------- .../blercu/bluez/blegattcharacteristic_p.h | 6 +- 2 files changed, 11 insertions(+), 54 deletions(-) diff --git a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp index 0aa304d7..fffb1fa3 100644 --- a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp +++ b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp @@ -422,7 +422,7 @@ void BleGattCharacteristicBluez::writeValueWithoutResponse(const std::vectorStopNotifySync(); } - m_notifyEnabled.store(false); + m_notifyEnabled = false; } @@ -503,25 +503,14 @@ void BleGattCharacteristicBluez::enableDbusNotifications(const Slot(m_isAlive, [this, reply = std::move(reply)](PendingReply<> *inner) mutable { - reply.setName(inner->getName()); - if (inner->isError()) { - m_notifyEnabled.store(false); - reply.setError(inner->errorMessage()); - } else { - m_notifyEnabled.store(true); - } - reply.finish(); - }); - - proxy->StartNotify(notifyCB, std::move(wrappedReply)); + proxy->StartNotify(notifyCB, std::move(reply)); + m_notifyEnabled = true; } @@ -554,7 +543,7 @@ void BleGattCharacteristicBluez::enablePipeNotifications(const SlotgetName().c_str(), dbusReply->errorMessage().c_str()); - // Some BlueZ versions/devices return NotPermitted: Notify acquired - // when CCCD state is already active across reconnects. In that case, - // fall back to StartNotify (DBus notifications) so we can still - // receive notifications instead of failing forever on AcquireNotify. - if (dbusReply->errorMessage().find("Notify acquired") != std::string::npos) { - XLOGD_WARN("falling back to StartNotify for %s after AcquireNotify NotPermitted", m_uuid.toString().c_str()); - - // enableDbusNotifications() sets m_notifyEnabled immediately, so wrap the reply to clear state on failure. - auto wrappedReply = PendingReply<>(m_isAlive, [this, reply = std::move(reply)](PendingReply<> *inner) mutable { - reply.setName(inner->getName()); - if (inner->isError()) { - // If StartNotify also reports "Notify acquired", treat it as success. - // We already registered the Value property callback, and this condition - // indicates BlueZ considers notifications active. - if (inner->errorMessage().find("Notify acquired") != std::string::npos) { - XLOGD_WARN("StartNotify returned 'Notify acquired' for %s; treating as success", m_uuid.toString().c_str()); - } else { - m_notifyEnabled.store(false); - reply.setError(inner->errorMessage()); - } - } - reply.finish(); - }); - - enableDbusNotifications(notifyCB, std::move(wrappedReply)); - return; - } - reply.setError(dbusReply->errorMessage()); reply.finish(); return; @@ -645,7 +606,7 @@ void BleGattCharacteristicBluez::enablePipeNotifications(const SlotaddNotificationSlot(notifyCB); @@ -672,6 +633,6 @@ void BleGattCharacteristicBluez::enablePipeNotifications(const Slot - - class BleGattNotifyPipe; class BleGattDescriptorBluez; class BluezGattCharacteristicInterface; @@ -138,8 +135,7 @@ class BleGattCharacteristicBluez : public BleGattCharacteristic, public BleGattC uint16_t m_flags; BleUuid m_uuid; int m_instanceId; - // TODO: Making this thread safe temporarily with atomic until we fix the notify issue - std::atomic m_notifyEnabled; + bool m_notifyEnabled; std::shared_ptr m_notifyPipe; std::map> m_descriptors; From 4b7e2383cf98eece8b6a263a152c4bf46b73f36f Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 16 Jul 2026 13:30:24 +0000 Subject: [PATCH 71/86] Added exponential backoff for retry logic (mfv notify) --- .../bleservices/gatt/gatt_audioservice_rdk.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index dab8a31f..a8f4a28d 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -47,7 +47,8 @@ // Number of async characteristic reads issued during MFV initialization (capabilities, model version, privacy, model configuration) #define MFV_TOTAL_INITIAL_READS (4) -#define MFV_NOTIFY_RETRY_DELAY_MS (1000) +#define MFV_NOTIFY_RETRY_DELAY_MS (1000) +#define MFV_NOTIFY_RETRY_MAX_DELAY_MS (32000) using namespace std; @@ -802,7 +803,17 @@ void GattAudioServiceRdk::scheduleMfvNotifyRetry() return; } - m_mfvNotifyRetryTimer = g_timeout_add(MFV_NOTIFY_RETRY_DELAY_MS, + // Exponential backoff: base * 2^(retryCount-1), capped at MFV_NOTIFY_RETRY_MAX_DELAY_MS. + // Use the max retry count across all MFV characteristics as the backoff index. + unsigned int retryCount = std::max({m_mfvState.sessionStartNotifyRetries, + m_mfvState.detectionDataNotifyRetries, + m_mfvState.privacyNotifyRetries}); + unsigned int shift = (retryCount > 0) ? std::min(retryCount - 1u, 5u) : 0u; + guint delay = std::min((guint)(MFV_NOTIFY_RETRY_DELAY_MS << shift), (guint)MFV_NOTIFY_RETRY_MAX_DELAY_MS); + + XLOGD_DEBUG("scheduling MFV notify retry in %u ms (retry count %u)", delay, retryCount); + + m_mfvNotifyRetryTimer = g_timeout_add(delay, [](gpointer userData) -> gboolean { auto *self = static_cast(userData); self->m_mfvNotifyRetryTimer = 0; From 88cf07a358634bc66fef05d7a0467630063b26af Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 16 Jul 2026 13:32:30 +0000 Subject: [PATCH 72/86] Reverting debug changes --- src/ble/hal/blercu/bluez/blegattcharacteristic.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp index fffb1fa3..e79849c9 100644 --- a/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp +++ b/src/ble/hal/blercu/bluez/blegattcharacteristic.cpp @@ -555,12 +555,6 @@ void BleGattCharacteristicBluez::enablePipeNotifications(const SlotgetName()); if (dbusReply->isError()) { - XLOGD_WARN("AcquireNotify failed for characteristic %s at %s due to <%s:%s>", - m_uuid.toString().c_str(), - m_path.c_str(), - dbusReply->getName().c_str(), - dbusReply->errorMessage().c_str()); - reply.setError(dbusReply->errorMessage()); reply.finish(); return; @@ -628,11 +622,9 @@ void BleGattCharacteristicBluez::enablePipeNotifications(const Slot Date: Thu, 16 Jul 2026 13:37:18 +0000 Subject: [PATCH 73/86] Update --- src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp | 2 +- src/ble/hal/blercu/bluez/blegattcharacteristic_p.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index a8f4a28d..124587e9 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -46,7 +46,7 @@ #define AUDIO_SEQ_NUM_MAX (0xFF) // Number of async characteristic reads issued during MFV initialization (capabilities, model version, privacy, model configuration) -#define MFV_TOTAL_INITIAL_READS (4) +#define MFV_TOTAL_INITIAL_READS (4) #define MFV_NOTIFY_RETRY_DELAY_MS (1000) #define MFV_NOTIFY_RETRY_MAX_DELAY_MS (32000) diff --git a/src/ble/hal/blercu/bluez/blegattcharacteristic_p.h b/src/ble/hal/blercu/bluez/blegattcharacteristic_p.h index 6a2360f5..0ad68f4b 100644 --- a/src/ble/hal/blercu/bluez/blegattcharacteristic_p.h +++ b/src/ble/hal/blercu/bluez/blegattcharacteristic_p.h @@ -33,6 +33,7 @@ #include "../blegattcharacteristic.h" #include "interfaces/bluezgattcharacteristicinterface.h" + class BleGattNotifyPipe; class BleGattDescriptorBluez; class BluezGattCharacteristicInterface; @@ -135,6 +136,7 @@ class BleGattCharacteristicBluez : public BleGattCharacteristic, public BleGattC uint16_t m_flags; BleUuid m_uuid; int m_instanceId; + bool m_notifyEnabled; std::shared_ptr m_notifyPipe; From e7b050b5cd622423bed78401a4e7fb0ed1d3b269 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 16 Jul 2026 13:40:46 +0000 Subject: [PATCH 74/86] Updates --- src/ble/ctrlm_ble_network.h | 4 ++-- .../hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/ble/ctrlm_ble_network.h b/src/ble/ctrlm_ble_network.h index b8956a8b..78b6a132 100644 --- a/src/ble/ctrlm_ble_network.h +++ b/src/ble/ctrlm_ble_network.h @@ -218,13 +218,13 @@ class ctrlm_obj_network_ble_t : public ctrlm_obj_network_t { ctrlm_controller_id_t find_controller_from_upgrade_session_uuid(const std::string &uuid); json_t * json_config_ = NULL; - bool voice_disabled_ = false; - bool mfv_below_threshold_session_enabled_ = false; + bool voice_disabled_ = false; bool upgrade_in_progress_ = false; bool unpair_on_remote_request_ = true; ctrlm_ble_unpair_metrics_t last_rcu_unpair_metrics_; int print_status_defer_count_ = 0; int event_status_defer_count_ = 0; + bool mfv_below_threshold_session_enabled_ = false; std::map controllers_; std::map upgrade_images_; diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index 124587e9..ff4fbb8b 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -767,6 +767,13 @@ void GattAudioServiceRdk::maybeEnableMfvNotifications() return; } + // If a backoff retry timer is pending, don't bypass it — let the timer drive the next + // attempt. The timer callback zeroes m_mfvNotifyRetryTimer before calling back here, + // so timer-driven retries are not affected by this guard. + if (m_mfvNotifyRetryTimer > 0) { + return; + } + // Only request notification enables that are not already enabled. if (m_mfvSessionStartCharacteristic && !m_mfvSessionStartCharacteristic->notificationsEnabled() && From f044a8aa8cb2c2c9fe361a39112be81296e78c5b Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 16 Jul 2026 13:43:48 +0000 Subject: [PATCH 75/86] Update --- src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index ff4fbb8b..ac3eef9e 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -35,6 +35,7 @@ #include "ctrlm_log_ble.h" +#include #include #include From 743383e48428184bfebd116471e6f98c0bd708ad Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 17 Jul 2026 13:15:41 +0000 Subject: [PATCH 76/86] Update --- src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index ac3eef9e..23db53c2 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -1242,6 +1242,7 @@ void GattAudioServiceRdk::onWriteMfvPrivacyReply(PendingReply<> *reply) } } else { XLOGD_INFO("MFV Privacy written successfully"); + m_mfvPrivacyChangedSlots.invoke(m_mfvPrivacyEnabled); if (m_mfvPromiseResults) { m_mfvPromiseResults->finish(); m_mfvPromiseResults.reset(); From b798d90393f71a66be9fed7aa488884776a73e20 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 17 Jul 2026 09:15:52 -0400 Subject: [PATCH 77/86] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index 23db53c2..03d71c89 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -764,7 +764,7 @@ void GattAudioServiceRdk::onMfvInitialReadComplete() void GattAudioServiceRdk::maybeEnableMfvNotifications() { - if (!m_mfvState.supported || !m_mfvState.areInitialReadsComplete() || m_mfvState.notificationsEnabled) { + if (!m_mfvState.supported || !m_mfvState.areInitialReadsComplete()) { return; } From 8d215eadec2bbba283cca9c9e0153c435872f4cf Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 17 Jul 2026 09:16:12 -0400 Subject: [PATCH 78/86] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index 03d71c89..a276414d 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -807,7 +807,7 @@ void GattAudioServiceRdk::maybeEnableMfvNotifications() void GattAudioServiceRdk::scheduleMfvNotifyRetry() { - if (!m_mfvState.supported || m_mfvState.notificationsEnabled || (m_mfvNotifyRetryTimer > 0)) { + if (!m_mfvState.supported || (m_mfvNotifyRetryTimer > 0)) { return; } From 224f3b22a82b5bee7fbf1fb3ce1f6a15646ceb7c Mon Sep 17 00:00:00 2001 From: klu339 Date: Fri, 26 Jun 2026 14:13:37 +0000 Subject: [PATCH 79/86] Init commit MFV interface --- src/CMakeLists.txt | 1 + src/ble/ctrlm_ble_controller.cpp | 9 +++++++++ src/ble/ctrlm_ble_controller.h | 5 +++++ src/ble/ctrlm_ble_network.cpp | 17 +++++++++++++++++ src/ble/ctrlm_ble_network.h | 1 + src/ctrlm_controller.cpp | 9 +++++++++ src/ctrlm_controller.h | 3 +++ src/voice/ctrlm_voice_obj.cpp | 6 ++++++ src/voice/ctrlm_voice_types.h | 1 + 9 files changed, 52 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 99916598..5cfef3db 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -81,6 +81,7 @@ target_sources(controlMgr PRIVATE voice/endpoints/ctrlm_voice_endpoint_ws_nsp.cpp voice/ipc/ctrlm_voice_ipc_iarm_all.cpp voice/ipc/ctrlm_voice_ipc_iarm_legacy.cpp + tv_audio/ctrlm_tv_audio_cap.cpp ) if(BLE_ENABLED) diff --git a/src/ble/ctrlm_ble_controller.cpp b/src/ble/ctrlm_ble_controller.cpp index 76782a67..bcec03ac 100644 --- a/src/ble/ctrlm_ble_controller.cpp +++ b/src/ble/ctrlm_ble_controller.cpp @@ -481,6 +481,15 @@ vector ctrlm_obj_controller_ble_t::get_wakeup_custom_list() const { return wakeup_custom_list_; } +void ctrlm_obj_controller_ble_t::set_mid_field_voice_capable(bool capable) { + XLOGD_DEBUG("Controller %u set mid-field voice capable to %s", controller_id_get(), capable ? "TRUE" : "FALSE"); + mid_field_voice_capable_ = capable; +} + +bool ctrlm_obj_controller_ble_t::get_mid_field_voice_capable() const { + return true; +} + std::string ctrlm_obj_controller_ble_t::wakeupCustomListToString() { std::ostringstream oss; if (!wakeup_custom_list_.empty()) { diff --git a/src/ble/ctrlm_ble_controller.h b/src/ble/ctrlm_ble_controller.h index 82ae7360..a0683de8 100644 --- a/src/ble/ctrlm_ble_controller.h +++ b/src/ble/ctrlm_ble_controller.h @@ -123,6 +123,9 @@ class ctrlm_obj_controller_ble_t : public ctrlm_obj_controller_t { virtual std::vector get_wakeup_custom_list() const; std::string wakeupCustomListToString(); + virtual void set_mid_field_voice_capable(bool capable); + virtual bool get_mid_field_voice_capable() const; + void setUpgradePaused(bool paused); bool getUpgradePaused(); bool getUpgradePauseSupported(void); @@ -225,6 +228,8 @@ class ctrlm_obj_controller_ble_t : public ctrlm_obj_controller_t { bool upgrade_stuck_supported_ = false; ctrlm_sw_version_t upgrade_stuck_version_; + bool mid_field_voice_capable_ = false; + }; // End Class ctrlm_obj_controller_ble_t diff --git a/src/ble/ctrlm_ble_network.cpp b/src/ble/ctrlm_ble_network.cpp index f1f60305..68210fd6 100644 --- a/src/ble/ctrlm_ble_network.cpp +++ b/src/ble/ctrlm_ble_network.cpp @@ -50,6 +50,7 @@ #include "ctrlm_telemetry.h" #include #include "ctrlm_telemetry_event.h" +#include "tv_audio/ctrlm_tv_audio_cap.h" using namespace std; @@ -2852,6 +2853,9 @@ void ctrlm_obj_network_ble_t::power_state_change(gboolean waking_up) { for(auto &controller : controllers_) { controller.second->setLastWakeupKey(CTRLM_KEY_CODE_INVALID); } + } else { + // When waking from deep sleep, re-evaluate mid-field voice device quantity + update_tv_audio_device_qty(); } if (ble_rcu_interface_) { @@ -2942,3 +2946,16 @@ void ctrlm_obj_network_ble_t::start_controller_audio_streaming(ctrlm_voice_start bool ctrlm_obj_network_ble_t::is_managed_by_network(ctrlm_controller_id_t id) { return (id >= BLE_RCU_ID_RANGE_MIN && id < BLE_RCU_ID_RANGE_MAX); } + +void ctrlm_obj_network_ble_t::update_tv_audio_device_qty() { + uint32_t mid_field_device_qty = 0; + for (auto const &it : controllers_) { + if (it.second->get_connected() && it.second->get_mid_field_voice_capable()) { + mid_field_device_qty++; + } + } + ctrlm_tv_audio_cap_t *tv_audio = ctrlm_tv_audio_cap_t::get_instance(); + if (tv_audio) { + tv_audio->update_device_qty(mid_field_device_qty); + } +} diff --git a/src/ble/ctrlm_ble_network.h b/src/ble/ctrlm_ble_network.h index 78b6a132..f6a4407f 100644 --- a/src/ble/ctrlm_ble_network.h +++ b/src/ble/ctrlm_ble_network.h @@ -216,6 +216,7 @@ class ctrlm_obj_network_ble_t : public ctrlm_obj_network_t { ctrlm_controller_id_t get_last_used_controller(void); bool end_voice_session_for_controller(uint64_t ieee_address, ctrlm_voice_session_end_reason_t reason, int32_t audioDuration = -1, int32_t startLag = -1, rdkx_timestamp_t *keyDownTime = NULL, rdkx_timestamp_t *keyUpTime = NULL); ctrlm_controller_id_t find_controller_from_upgrade_session_uuid(const std::string &uuid); + void update_tv_audio_device_qty(); json_t * json_config_ = NULL; bool voice_disabled_ = false; diff --git a/src/ctrlm_controller.cpp b/src/ctrlm_controller.cpp index 0e7b175e..f0ea9e34 100644 --- a/src/ctrlm_controller.cpp +++ b/src/ctrlm_controller.cpp @@ -332,6 +332,15 @@ std::vector ctrlm_obj_controller_t::get_wakeup_custom_list() const { return {}; } +void ctrlm_obj_controller_t::set_mid_field_voice_capable(bool capable) { + XLOGD_WARN("not implemented."); +} + +bool ctrlm_obj_controller_t::get_mid_field_voice_capable() const { + XLOGD_WARN("not implemented."); + return false; +} + void ctrlm_obj_controller_t::set_upgrade_progress(uint8_t progress) { upgrade_progress_ = progress; ctrlm_rcu_upgrade_state_t previous_state = get_upgrade_state(); diff --git a/src/ctrlm_controller.h b/src/ctrlm_controller.h index ae9b800b..551eba63 100644 --- a/src/ctrlm_controller.h +++ b/src/ctrlm_controller.h @@ -94,6 +94,9 @@ class ctrlm_obj_controller_t virtual ctrlm_rcu_wakeup_config_t get_wakeup_config() const; virtual std::vector get_wakeup_custom_list() const; + virtual void set_mid_field_voice_capable(bool capable); + virtual bool get_mid_field_voice_capable() const; + virtual void set_upgrade_progress(uint8_t progress); virtual uint8_t get_upgrade_progress() const; virtual void set_upgrade_state(ctrlm_rcu_upgrade_state_t state); diff --git a/src/voice/ctrlm_voice_obj.cpp b/src/voice/ctrlm_voice_obj.cpp index e6e8636d..7cb26d9d 100644 --- a/src/voice/ctrlm_voice_obj.cpp +++ b/src/voice/ctrlm_voice_obj.cpp @@ -253,6 +253,8 @@ ctrlm_voice_t::ctrlm_voice_t() { this->device_requires_stb_data[CTRLM_VOICE_DEVICE_MICROPHONE] = true; this->device_status[CTRLM_VOICE_DEVICE_MICROPHONE_TAP] = CTRLM_VOICE_DEVICE_STATUS_NONE; this->device_requires_stb_data[CTRLM_VOICE_DEVICE_MICROPHONE_TAP] = true; + this->device_status[CTRLM_VOICE_DEVICE_MFV] = CTRLM_VOICE_DEVICE_STATUS_NONE; + this->device_requires_stb_data[CTRLM_VOICE_DEVICE_MFV] = true; this->device_status[CTRLM_VOICE_DEVICE_INVALID] = CTRLM_VOICE_DEVICE_STATUS_NOT_SUPPORTED; this->device_requires_stb_data[CTRLM_VOICE_DEVICE_INVALID] = true; @@ -3286,6 +3288,10 @@ xrsr_src_t voice_device_to_xrsr(ctrlm_voice_device_t device) { ret = XRSR_SRC_MICROPHONE_TAP; break; } + case CTRLM_VOICE_DEVICE_MFV: { + ret = XRSR_SRC_RCU_MFV; + break; + } default: { XLOGD_ERROR("unrecognized device type %d", device); break; diff --git a/src/voice/ctrlm_voice_types.h b/src/voice/ctrlm_voice_types.h index e65a49ba..cbecbb0a 100644 --- a/src/voice/ctrlm_voice_types.h +++ b/src/voice/ctrlm_voice_types.h @@ -33,6 +33,7 @@ typedef enum { CTRLM_VOICE_DEVICE_FF, CTRLM_VOICE_DEVICE_MICROPHONE, CTRLM_VOICE_DEVICE_MICROPHONE_TAP, + CTRLM_VOICE_DEVICE_MFV, CTRLM_VOICE_DEVICE_INVALID, } ctrlm_voice_device_t; From 5f01bae203709917485f2ac7e18e8ff4265dfe79 Mon Sep 17 00:00:00 2001 From: klu339 Date: Wed, 8 Jul 2026 19:52:01 +0000 Subject: [PATCH 80/86] Add MFV device class and init metadata --- src/voice/ctrlm_voice_obj.cpp | 6 ++++++ src/voice/ctrlm_voice_obj_generic.cpp | 1 + src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp | 8 +++++++- src/voice/ipc/ctrlm_voice_ipc_iarm_thunder.cpp | 1 + 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/voice/ctrlm_voice_obj.cpp b/src/voice/ctrlm_voice_obj.cpp index 7cb26d9d..563da020 100644 --- a/src/voice/ctrlm_voice_obj.cpp +++ b/src/voice/ctrlm_voice_obj.cpp @@ -3193,6 +3193,7 @@ const char *ctrlm_voice_device_str(ctrlm_voice_device_t device) { case CTRLM_VOICE_DEVICE_FF: return("FF"); case CTRLM_VOICE_DEVICE_MICROPHONE: return("MICROPHONE"); case CTRLM_VOICE_DEVICE_MICROPHONE_TAP: return("MICROPHONE_TAP"); + case CTRLM_VOICE_DEVICE_MFV: return("MFV"); case CTRLM_VOICE_DEVICE_INVALID: return("INVALID"); } return("UNKNOWN"); @@ -3261,6 +3262,10 @@ ctrlm_voice_device_t xrsr_to_voice_device(xrsr_src_t device) { ret = CTRLM_VOICE_DEVICE_MICROPHONE_TAP; break; } + case XRSR_SRC_RCU_MFV: { + ret = CTRLM_VOICE_DEVICE_MFV; + break; + } default: { XLOGD_ERROR("unrecognized device type %d", device); break; @@ -3404,6 +3409,7 @@ bool ctrlm_voice_t::is_voice_assistant(ctrlm_voice_device_t device) { bool voice_assistant = false; switch(device) { case CTRLM_VOICE_DEVICE_MICROPHONE: + case CTRLM_VOICE_DEVICE_MFV: case CTRLM_VOICE_DEVICE_FF: { voice_assistant = true; break; diff --git a/src/voice/ctrlm_voice_obj_generic.cpp b/src/voice/ctrlm_voice_obj_generic.cpp index 4cb35f99..97c4dbb3 100644 --- a/src/voice/ctrlm_voice_obj_generic.cpp +++ b/src/voice/ctrlm_voice_obj_generic.cpp @@ -181,6 +181,7 @@ void ctrlm_voice_generic_t::voice_sdk_update_routes() { break; } case CTRLM_VOICE_DEVICE_MICROPHONE: + case CTRLM_VOICE_DEVICE_MFV: case CTRLM_VOICE_DEVICE_FF: { url = &this->prefs.server_url_src_ff; break; diff --git a/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp b/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp index 9abc27db..0880150b 100644 --- a/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp +++ b/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp @@ -22,6 +22,8 @@ #define NEXTGEN_AUDIO_MODEL_PTT "ptt" #define NEXTGEN_AUDIO_MODEL_HF "hf" #define NEXTGEN_AUDIO_PROFILE_HF "FFV" +#define NEXTGEN_AUDIO_MODEL_MFV "mfv" +#define NEXTGEN_AUDIO_PROFILE_MFV "MFV" // Structures typedef struct { @@ -289,7 +291,11 @@ void ctrlm_voice_endpoint_ws_nextgen_t::voice_session_begin_callback_ws_nextgen( config_in.ws.cert_revoked_allow = false; config_in.ws.ocsp_expired_allow = false; - if(is_mic) { + if(source == CTRLM_VOICE_DEVICE_MFV) { + // Reuse the audio profile/model to signal to Vrex that this transaction is from mid-field voice. + xrsv_ws_nextgen_update_audio_profile(this->xrsv_obj_ws_nextgen, NEXTGEN_AUDIO_PROFILE_MFV); + xrsv_ws_nextgen_update_audio_model(this->xrsv_obj_ws_nextgen, NEXTGEN_AUDIO_MODEL_MFV); + } else if(is_mic) { xrsv_ws_nextgen_update_audio_profile(this->xrsv_obj_ws_nextgen, NEXTGEN_AUDIO_PROFILE_HF); xrsv_ws_nextgen_update_audio_model(this->xrsv_obj_ws_nextgen, NEXTGEN_AUDIO_MODEL_HF); } else { diff --git a/src/voice/ipc/ctrlm_voice_ipc_iarm_thunder.cpp b/src/voice/ipc/ctrlm_voice_ipc_iarm_thunder.cpp index 6b407173..0a9482c8 100644 --- a/src/voice/ipc/ctrlm_voice_ipc_iarm_thunder.cpp +++ b/src/voice/ipc/ctrlm_voice_ipc_iarm_thunder.cpp @@ -941,6 +941,7 @@ const char *voice_device_str(ctrlm_voice_device_t device) { case CTRLM_VOICE_DEVICE_FF: return("ff"); case CTRLM_VOICE_DEVICE_MICROPHONE: return("mic"); case CTRLM_VOICE_DEVICE_MICROPHONE_TAP: return("mic_tap"); + case CTRLM_VOICE_DEVICE_MFV: return("mfv"); default: break; } return("invalid"); From 9b22b1616f2d346333331fc37bc46c8c5acf1e9a Mon Sep 17 00:00:00 2001 From: klu339 Date: Tue, 14 Jul 2026 14:55:42 +0000 Subject: [PATCH 81/86] Remove TV audio reference --- src/CMakeLists.txt | 1 - src/ble/ctrlm_ble_network.cpp | 25 ++++--------------------- src/ble/ctrlm_ble_network.h | 1 - 3 files changed, 4 insertions(+), 23 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5cfef3db..99916598 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -81,7 +81,6 @@ target_sources(controlMgr PRIVATE voice/endpoints/ctrlm_voice_endpoint_ws_nsp.cpp voice/ipc/ctrlm_voice_ipc_iarm_all.cpp voice/ipc/ctrlm_voice_ipc_iarm_legacy.cpp - tv_audio/ctrlm_tv_audio_cap.cpp ) if(BLE_ENABLED) diff --git a/src/ble/ctrlm_ble_network.cpp b/src/ble/ctrlm_ble_network.cpp index 68210fd6..8affe7f6 100644 --- a/src/ble/ctrlm_ble_network.cpp +++ b/src/ble/ctrlm_ble_network.cpp @@ -50,7 +50,6 @@ #include "ctrlm_telemetry.h" #include #include "ctrlm_telemetry_event.h" -#include "tv_audio/ctrlm_tv_audio_cap.h" using namespace std; @@ -2079,14 +2078,14 @@ void ctrlm_obj_network_ble_t::ind_process_rcu_status(void *data, int size) { case CTRLM_HAL_BLE_PROPERTY_WAKEUP_CONFIG: controller->setWakeupConfig(dqm->rcu_data.wakeup_config); XLOGD_INFO("Controller <%s> notified wakeup config = <%s>", controller->ieee_address_get().to_string().c_str(), ctrlm_rcu_wakeup_config_str(controller->get_wakeup_config())); - schedule_status_print(); - schedule_status_event(); + //schedule_status_print(); + //schedule_status_event(); break; case CTRLM_HAL_BLE_PROPERTY_WAKEUP_CUSTOM_LIST: controller->setWakeupCustomList(dqm->rcu_data.wakeup_custom_list, dqm->rcu_data.wakeup_custom_list_size); XLOGD_INFO("Controller <%s> notified wakeup custom list = <%s>", controller->ieee_address_get().to_string().c_str(), controller->wakeupCustomListToString().c_str()); - schedule_status_print(); - schedule_status_event(); + //schedule_status_print(); + //schedule_status_event(); break; case CTRLM_HAL_BLE_PROPERTY_IRDBS_SUPPORTED: controller->setSupportedIrdbs(dqm->rcu_data.irdbs_supported); @@ -2853,9 +2852,6 @@ void ctrlm_obj_network_ble_t::power_state_change(gboolean waking_up) { for(auto &controller : controllers_) { controller.second->setLastWakeupKey(CTRLM_KEY_CODE_INVALID); } - } else { - // When waking from deep sleep, re-evaluate mid-field voice device quantity - update_tv_audio_device_qty(); } if (ble_rcu_interface_) { @@ -2946,16 +2942,3 @@ void ctrlm_obj_network_ble_t::start_controller_audio_streaming(ctrlm_voice_start bool ctrlm_obj_network_ble_t::is_managed_by_network(ctrlm_controller_id_t id) { return (id >= BLE_RCU_ID_RANGE_MIN && id < BLE_RCU_ID_RANGE_MAX); } - -void ctrlm_obj_network_ble_t::update_tv_audio_device_qty() { - uint32_t mid_field_device_qty = 0; - for (auto const &it : controllers_) { - if (it.second->get_connected() && it.second->get_mid_field_voice_capable()) { - mid_field_device_qty++; - } - } - ctrlm_tv_audio_cap_t *tv_audio = ctrlm_tv_audio_cap_t::get_instance(); - if (tv_audio) { - tv_audio->update_device_qty(mid_field_device_qty); - } -} diff --git a/src/ble/ctrlm_ble_network.h b/src/ble/ctrlm_ble_network.h index f6a4407f..78b6a132 100644 --- a/src/ble/ctrlm_ble_network.h +++ b/src/ble/ctrlm_ble_network.h @@ -216,7 +216,6 @@ class ctrlm_obj_network_ble_t : public ctrlm_obj_network_t { ctrlm_controller_id_t get_last_used_controller(void); bool end_voice_session_for_controller(uint64_t ieee_address, ctrlm_voice_session_end_reason_t reason, int32_t audioDuration = -1, int32_t startLag = -1, rdkx_timestamp_t *keyDownTime = NULL, rdkx_timestamp_t *keyUpTime = NULL); ctrlm_controller_id_t find_controller_from_upgrade_session_uuid(const std::string &uuid); - void update_tv_audio_device_qty(); json_t * json_config_ = NULL; bool voice_disabled_ = false; From a7e48e17907b9fadcec2b7fcc9f60eed5b9cca43 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 17 Jul 2026 16:29:39 +0000 Subject: [PATCH 82/86] Fix for ff sessions incorrectly coming in as PTT --- .../ctrlm_voice_endpoint_ws_nextgen.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp b/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp index 0880150b..35b8eb7f 100644 --- a/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp +++ b/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp @@ -343,7 +343,7 @@ void ctrlm_voice_endpoint_ws_nextgen_t::voice_session_begin_callback_ws_nextgen( stream_params->keyword_sample_end -= delta; } - if(is_mic) { + if(is_mic || source == CTRLM_VOICE_DEVICE_FF || source == CTRLM_VOICE_DEVICE_MFV) { stream_params->push_to_talk = false; } else { stream_params->push_to_talk = true; @@ -364,7 +364,7 @@ void ctrlm_voice_endpoint_ws_nextgen_t::voice_session_begin_callback_ws_nextgen( config_in.ws.app_config = stream_params_out; XLOGD_AUTOMATION_TELEMETRY("session begin - src <%s> ptt <%s> w_SAT <%s> w_MTLS <%s> w_OCSPst <%s> w_OCSPca <%s> keyword begin <%u> end <%u> doa <%u> gain <%4.1f> db", ctrlm_voice_device_str(source), (stream_params->push_to_talk ? "TRUE" : "FALSE"), has_sat ? "YES" : "NO", use_mtls ? "YES" : "NO", ocsp_verify_stapling ? "YES" : "NO", ocsp_verify_ca ? "YES" : "NO", stream_params->keyword_sample_begin, stream_params->keyword_sample_end, stream_params->keyword_doa, stream_params->dynamic_gain); - } else if(!is_mic || dqm->configuration.user_initiated) { + } else if((!is_mic && source != CTRLM_VOICE_DEVICE_FF && source != CTRLM_VOICE_DEVICE_MFV) || dqm->configuration.user_initiated) { xrsv_ws_nextgen_stream_params_t *stream_params = (xrsv_ws_nextgen_stream_params_t *)malloc(sizeof(xrsv_ws_nextgen_stream_params_t)); if(stream_params != NULL) { @@ -377,6 +377,19 @@ void ctrlm_voice_endpoint_ws_nextgen_t::voice_session_begin_callback_ws_nextgen( config_in.ws.app_config = stream_params; XLOGD_AUTOMATION_TELEMETRY("session begin - src <%s> ptt w_SAT <%s> w_MTLS <%s> w_OCSPst <%s> w_OCSPca <%s>", ctrlm_voice_device_str(source), has_sat ? "YES" : "NO", use_mtls ? "YES" : "NO", ocsp_verify_stapling ? "YES" : "NO", ocsp_verify_ca ? "YES" : "NO"); + } else if(source == CTRLM_VOICE_DEVICE_FF || source == CTRLM_VOICE_DEVICE_MFV) { + xrsv_ws_nextgen_stream_params_t *stream_params = (xrsv_ws_nextgen_stream_params_t *)malloc(sizeof(xrsv_ws_nextgen_stream_params_t)); + + if(stream_params != NULL) { + errno_t safec_rc = memset_s(stream_params, sizeof(*stream_params), 0, sizeof(*stream_params)); + ERR_CHK(safec_rc); + + stream_params->push_to_talk = false; + stream_params->par_eos_timeout = params.par_voice_enabled ? params.par_voice_eos_timeout : 0; + } + config_in.ws.app_config = stream_params; + + XLOGD_AUTOMATION_TELEMETRY("session begin - src <%s> ptt w_SAT <%s> w_MTLS <%s> w_OCSPst <%s> w_OCSPca <%s>", ctrlm_voice_device_str(source), has_sat ? "YES" : "NO", use_mtls ? "YES" : "NO", ocsp_verify_stapling ? "YES" : "NO", ocsp_verify_ca ? "YES" : "NO"); } else { XLOGD_ERROR("session begin - invalid params - src <%s>", ctrlm_voice_device_str(source)); } From d5e18ab61d3a207a4c64c6535dd36f44e125bffd Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 17 Jul 2026 17:36:48 +0000 Subject: [PATCH 83/86] Another change --- src/voice/ctrlm_voice_obj_generic.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/voice/ctrlm_voice_obj_generic.cpp b/src/voice/ctrlm_voice_obj_generic.cpp index 97c4dbb3..aa7d61cf 100644 --- a/src/voice/ctrlm_voice_obj_generic.cpp +++ b/src/voice/ctrlm_voice_obj_generic.cpp @@ -183,7 +183,8 @@ void ctrlm_voice_generic_t::voice_sdk_update_routes() { case CTRLM_VOICE_DEVICE_MICROPHONE: case CTRLM_VOICE_DEVICE_MFV: case CTRLM_VOICE_DEVICE_FF: { - url = &this->prefs.server_url_src_ff; + // If FF URL is not configured, fall back to PTT URL. + url = this->prefs.server_url_src_ff.empty() ? &this->prefs.server_url_src_ptt : &this->prefs.server_url_src_ff; break; } case CTRLM_VOICE_DEVICE_MICROPHONE_TAP: { From c63c23b1c8b33a411578c6ecbe31d6ba52aed055 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 20 Jul 2026 11:49:44 +0000 Subject: [PATCH 84/86] Change from copilot suggestion --- src/ble/ctrlm_ble_network.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ble/ctrlm_ble_network.cpp b/src/ble/ctrlm_ble_network.cpp index 8affe7f6..eabf3a63 100644 --- a/src/ble/ctrlm_ble_network.cpp +++ b/src/ble/ctrlm_ble_network.cpp @@ -2121,7 +2121,7 @@ void ctrlm_obj_network_ble_t::ind_process_rcu_status(void *data, int size) { errno_t safec_rc = memset_s(&msg, sizeof(msg), 0, sizeof(msg)); ERR_CHK(safec_rc); msg.params = &v_params; - msg.voice_device = CTRLM_VOICE_DEVICE_FF; + msg.voice_device = CTRLM_VOICE_DEVICE_MFV; req_process_voice_session_begin(&msg, sizeof(msg)); } From a4283462948efe5dbf0dbaa6158a567e7337371c Mon Sep 17 00:00:00 2001 From: klu339 Date: Wed, 22 Jul 2026 18:56:47 +0000 Subject: [PATCH 85/86] Feed MFV detection data to the voice sdk --- src/ble/ctrlm_ble_controller.cpp | 33 ++++++--- src/ble/ctrlm_ble_controller.h | 17 +++-- src/ble/ctrlm_ble_network.cpp | 74 +++++++++++++++++++ src/ble/ctrlm_ble_network.h | 1 + .../gatt/gatt_audioservice_rdk.cpp | 18 ++++- src/ctrlm_controller.cpp | 9 --- src/ctrlm_controller.h | 3 - src/voice/ctrlm_voice_obj.cpp | 70 +++++++++++++----- src/voice/ctrlm_voice_obj.h | 4 + src/voice/ctrlm_voice_obj_generic.cpp | 16 ++++ .../ctrlm_voice_endpoint_ws_nextgen.cpp | 8 +- 11 files changed, 204 insertions(+), 49 deletions(-) diff --git a/src/ble/ctrlm_ble_controller.cpp b/src/ble/ctrlm_ble_controller.cpp index bcec03ac..01f712e3 100644 --- a/src/ble/ctrlm_ble_controller.cpp +++ b/src/ble/ctrlm_ble_controller.cpp @@ -481,15 +481,6 @@ vector ctrlm_obj_controller_ble_t::get_wakeup_custom_list() const { return wakeup_custom_list_; } -void ctrlm_obj_controller_ble_t::set_mid_field_voice_capable(bool capable) { - XLOGD_DEBUG("Controller %u set mid-field voice capable to %s", controller_id_get(), capable ? "TRUE" : "FALSE"); - mid_field_voice_capable_ = capable; -} - -bool ctrlm_obj_controller_ble_t::get_mid_field_voice_capable() const { - return true; -} - std::string ctrlm_obj_controller_ble_t::wakeupCustomListToString() { std::ostringstream oss; if (!wakeup_custom_list_.empty()) { @@ -583,6 +574,30 @@ uint8_t ctrlm_obj_controller_ble_t::getMfvCapabilities() const { return mfv_capabilities_; } +void ctrlm_obj_controller_ble_t::setMfvDetectionPending(bool pending, int audio_fd) { + mfv_detection_pending_ = pending; + mfv_detection_audio_fd_ = pending ? audio_fd : -1; + if (!pending) { + mfv_detection_data_fresh_ = false; + } +} + +bool ctrlm_obj_controller_ble_t::isMfvDetectionPending() const { + return mfv_detection_pending_; +} + +int ctrlm_obj_controller_ble_t::getMfvDetectionAudioFd() const { + return mfv_detection_audio_fd_; +} + +void ctrlm_obj_controller_ble_t::setMfvDetectionDataFresh(bool fresh) { + mfv_detection_data_fresh_ = fresh; +} + +bool ctrlm_obj_controller_ble_t::isMfvDetectionDataFresh() const { + return mfv_detection_data_fresh_; +} + void ctrlm_obj_controller_ble_t::setSupportedIrdbs(uint8_t vendor_support_bitmask) { this->irdbs_supported_ = vendor_support_bitmask; diff --git a/src/ble/ctrlm_ble_controller.h b/src/ble/ctrlm_ble_controller.h index a0683de8..60c2d37e 100644 --- a/src/ble/ctrlm_ble_controller.h +++ b/src/ble/ctrlm_ble_controller.h @@ -123,9 +123,6 @@ class ctrlm_obj_controller_ble_t : public ctrlm_obj_controller_t { virtual std::vector get_wakeup_custom_list() const; std::string wakeupCustomListToString(); - virtual void set_mid_field_voice_capable(bool capable); - virtual bool get_mid_field_voice_capable() const; - void setUpgradePaused(bool paused); bool getUpgradePaused(); bool getUpgradePauseSupported(void); @@ -162,6 +159,15 @@ class ctrlm_obj_controller_ble_t : public ctrlm_obj_controller_t { void setMfvCapabilities(uint8_t caps); uint8_t getMfvCapabilities() const; + // MFV wake-word (detection-triggered) session state. Such a session starts audio streaming, waits + // for the detection data (wake word timing/confidence), then opens the voice session. See + // ctrlm_obj_network_ble_t::req_process_detection_voice_session_begin(). + void setMfvDetectionPending(bool pending, int audio_fd = -1); + bool isMfvDetectionPending() const; + int getMfvDetectionAudioFd() const; + void setMfvDetectionDataFresh(bool fresh); // fresh detection data received for the current pending session (stale-data safeguard) + bool isMfvDetectionDataFresh() const; + void update_controller_id_and_db_entry(std::string name, ctrlm_network_id_t network_id, ctrlm_controller_id_t new_id); private: @@ -218,6 +224,9 @@ class ctrlm_obj_controller_ble_t : public ctrlm_obj_controller_t { uint16_t mfv_confidence_ = 0; // encoded as integer * 10 (e.g. 953 = 95.3%) bool mfv_privacy_enabled_ = false; uint8_t mfv_capabilities_ = 0; + bool mfv_detection_pending_ = false; // a wake-word session has started audio and is awaiting its detection data + bool mfv_detection_data_fresh_ = false; // fresh detection data arrived for the current pending session + int mfv_detection_audio_fd_ = -1; // fd of the audio stream started for the pending session bool conn_param_update_before_ota_supported_ = false; ctrlm_sw_version_t conn_param_update_before_ota_version_; @@ -228,8 +237,6 @@ class ctrlm_obj_controller_ble_t : public ctrlm_obj_controller_t { bool upgrade_stuck_supported_ = false; ctrlm_sw_version_t upgrade_stuck_version_; - bool mid_field_voice_capable_ = false; - }; // End Class ctrlm_obj_controller_ble_t diff --git a/src/ble/ctrlm_ble_network.cpp b/src/ble/ctrlm_ble_network.cpp index eabf3a63..ad8a3f1a 100644 --- a/src/ble/ctrlm_ble_network.cpp +++ b/src/ble/ctrlm_ble_network.cpp @@ -64,6 +64,10 @@ using namespace std; #define CTRLM_BLE_PRINT_STATUS_DEFER_MAX (20) #define CTRLM_BLE_EVENT_STATUS_DEFER_MAX (20) +// Maximum time to wait for the MFV detection data (wake word timing/confidence) after audio streaming +// has started before opening the voice session without the wake word stream parameters. +#define CTRLM_BLE_MFV_DETECTION_DATA_TIMEOUT (1500) // 1.5 second + #define CTRLM_VENDOR_BLE_NETWORK_DISABLE_FILE "/etc/vendor/input/ble_network_disable" #define CTRLM_VENDOR_BLE_REMOTE_WHITELIST_FILE "/etc/vendor/input/ble_remote_whitelist.json" #define CTRLM_VENDOR_BLE_NETWORK_OPTIONS_FILE "/etc/vendor/input/ble_network_options.json" @@ -74,6 +78,8 @@ typedef struct { guint upgrade_pause_timer_tag; guint print_status_timer_tag; guint event_status_timer_tag; + guint mfv_detection_timer_tag; + unsigned long long mfv_detection_pending_ieee; } ctrlm_ble_network_t; static ctrlm_ble_network_t g_ctrlm_ble_network; @@ -89,6 +95,7 @@ static const vector ctrlm_ble_ir_key_names { static gboolean ctrlm_ble_print_status_timer_cb(gpointer user_data); static gboolean ctrlm_ble_event_status_timer_cb(gpointer user_data); +static gboolean ctrlm_ble_mfv_detection_timer_cb(gpointer user_data); static gboolean ctrlm_ble_upgrade_controllers_timer_cb(gpointer user_data); static gboolean ctrlm_ble_upgrade_resume_timer_cb(gpointer user_data); @@ -349,6 +356,7 @@ ctrlm_obj_network_ble_t::~ctrlm_obj_network_ble_t() { ctrlm_timeout_destroy(&g_ctrlm_ble_network.upgrade_pause_timer_tag); ctrlm_timeout_destroy(&g_ctrlm_ble_network.event_status_timer_tag); ctrlm_timeout_destroy(&g_ctrlm_ble_network.print_status_timer_tag); + ctrlm_timeout_destroy(&g_ctrlm_ble_network.mfv_detection_timer_tag); ctrlm_ble_iarm_terminate(); @@ -622,6 +630,40 @@ void ctrlm_obj_network_ble_t::req_process_voice_session_begin(void *data, int si } } +// Safety timeout - the MFV detection data was not received in time, so release the deferred voice +// session connect anyway (without the wake word stream parameters). +static gboolean ctrlm_ble_mfv_detection_timer_cb(gpointer user_data) { + XLOGD_DEBUG("Enter..."); + ctrlm_network_id_t* net_id = (ctrlm_network_id_t*) user_data; + g_ctrlm_ble_network.mfv_detection_timer_tag = 0; + if (net_id != NULL) { + // Process on the ctrlm main thread to serialize with the other network handlers. + ctrlm_main_queue_handler_push(CTRLM_HANDLER_NETWORK, + (ctrlm_msg_handler_network_t)&ctrlm_obj_network_ble_t::req_process_mfv_detection_timeout, + NULL, 0, NULL, *net_id); + } + return false; +} + +void ctrlm_obj_network_ble_t::req_process_mfv_detection_timeout(void *data, int size) { + THREAD_ID_VALIDATE(); + + unsigned long long ieee_address = g_ctrlm_ble_network.mfv_detection_pending_ieee; + ctrlm_controller_id_t controller_id; + if (!getControllerId(ieee_address, &controller_id)) { + XLOGD_WARN("MFV detection data timeout - controller no longer exists"); + return; + } + if (!controllers_[controller_id]->isMfvDetectionPending()) { + // Detection data already arrived (or the session was cancelled) - nothing to do. + return; + } + controllers_[controller_id]->setMfvDetectionPending(false); + XLOGD_WARN("MFV detection data not received within timeout - releasing voice session connect without wake word stream parameters for device: %s", + controllers_[controller_id]->ieee_address_get().to_string().c_str()); + ctrlm_get_voice_obj()->voice_session_stream_params_update(CTRLM_VOICE_DEVICE_MFV, false, 0, 0, 0.0, 0.0); +} + bool ctrlm_obj_network_ble_t::end_voice_session_for_controller(uint64_t ieee_address, ctrlm_voice_session_end_reason_t reason, int32_t audioDuration, int32_t startLag, rdkx_timestamp_t *keyDownTime, rdkx_timestamp_t *keyUpTime) { ctrlm_controller_id_t controller_id; @@ -2123,7 +2165,17 @@ void ctrlm_obj_network_ble_t::ind_process_rcu_status(void *data, int size) { msg.params = &v_params; msg.voice_device = CTRLM_VOICE_DEVICE_MFV; + // Open the voice session immediately so audio streams right away (draining the BLE + // pipe). Because this is an MFV wake-word session, the speech router holds its + // connect/init in the buffering state until the wake word stream parameters are + // supplied. Those arrive in a separate detection-data notification; a safety timeout + // releases the connect without them if they never come. + controller->setMfvDetectionPending(true); req_process_voice_session_begin(&msg, sizeof(msg)); + + g_ctrlm_ble_network.mfv_detection_pending_ieee = dqm->rcu_data.ieee_address; + ctrlm_timeout_destroy(&g_ctrlm_ble_network.mfv_detection_timer_tag); + g_ctrlm_ble_network.mfv_detection_timer_tag = ctrlm_timeout_create(CTRLM_BLE_MFV_DETECTION_DATA_TIMEOUT, ctrlm_ble_mfv_detection_timer_cb, &id_); } break; } @@ -2131,6 +2183,18 @@ void ctrlm_obj_network_ble_t::ind_process_rcu_status(void *data, int size) { controller->setMfvDetectionData(dqm->rcu_data.mfv_ww_start, dqm->rcu_data.mfv_ww_end, dqm->rcu_data.mfv_confidence); XLOGD_INFO("Controller <%s> MFV detection data: start=%u end=%u confidence=%.1f%%", controller->ieee_address_get().to_string().c_str(), dqm->rcu_data.mfv_ww_start, dqm->rcu_data.mfv_ww_end, dqm->rcu_data.mfv_confidence / 10.0); + + // If a wake-word session is waiting on this detection data, supply its wake word stream + // parameters to the voice engine and release the deferred connect so the vrex init is + // sent with them included. + if (controller->isMfvDetectionPending()) { + controller->setMfvDetectionPending(false); + ctrlm_timeout_destroy(&g_ctrlm_ble_network.mfv_detection_timer_tag); + // Confidence is encoded as percent * 10 (e.g. 991 = 99.1%); normalize to 0.0 - 1.0. + double confidence = dqm->rcu_data.mfv_confidence / 1000.0; + ctrlm_get_voice_obj()->voice_session_stream_params_update(CTRLM_VOICE_DEVICE_MFV, true, + dqm->rcu_data.mfv_ww_start, dqm->rcu_data.mfv_ww_end, confidence, 0.0); + } break; case CTRLM_HAL_BLE_PROPERTY_MFV_PRIVACY: controller->setMfvPrivacy(dqm->rcu_data.mfv_privacy_enabled); @@ -2466,6 +2530,16 @@ void ctrlm_obj_network_ble_t::ind_process_keypress(void *data, int size) { if (key_status == CTRLM_KEY_STATUS_DOWN) { if (controller->isVoiceKey(dqm->event.code)) { + // The MFV remote also has a standard push-to-talk mic button. If a wake-word (MFV) session + // is still held waiting on its detection data when the button is pressed, release its deferred + // connect so it is not left stuck in the buffering state. + if (controller->isMfvDetectionPending()) { + XLOGD_INFO("Push-to-talk pressed while an MFV wake-word session was pending - releasing it for device: %s", controller->ieee_address_get().to_string().c_str()); + controller->setMfvDetectionPending(false); + ctrlm_timeout_destroy(&g_ctrlm_ble_network.mfv_detection_timer_tag); + ctrlm_get_voice_obj()->voice_session_stream_params_update(CTRLM_VOICE_DEVICE_MFV, false, 0, 0, 0.0, 0.0); + } + rdkx_timestamp_t keyDownTime; keyDownTime.tv_sec = dqm->event.time.tv_sec; keyDownTime.tv_nsec = dqm->event.time.tv_usec * 1000; diff --git a/src/ble/ctrlm_ble_network.h b/src/ble/ctrlm_ble_network.h index 78b6a132..b2a52f0f 100644 --- a/src/ble/ctrlm_ble_network.h +++ b/src/ble/ctrlm_ble_network.h @@ -146,6 +146,7 @@ class ctrlm_obj_network_ble_t : public ctrlm_obj_network_t { virtual void req_process_controller_status(void *data, int size); virtual void req_process_voice_session_begin(void *data, int size); + virtual void req_process_mfv_detection_timeout(void *data, int size); virtual void req_process_voice_session_end(void *data, int size); virtual void req_process_start_pairing(void *data, int size); diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp index a276414d..6d6c9f59 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice_rdk.cpp @@ -126,6 +126,13 @@ bool GattAudioServiceRdk::start(const shared_ptr &gattServ requestMfvModelVersion(); requestMfvPrivacy(); requestMfvModelConfig(); + + // Enable the wake-word notifications (Session Start + Detection Data) as early as possible - + // right after the characteristics are discovered - rather than waiting for the initial value + // reads above to complete. Subscribing does not depend on those values, and enabling early + // means wake-word detection data can be received while the service is idle/ready, without first + // having to start an audio stream. + maybeEnableMfvNotifications(); } return(GattAudioService::start(gattService)); @@ -764,7 +771,12 @@ void GattAudioServiceRdk::onMfvInitialReadComplete() void GattAudioServiceRdk::maybeEnableMfvNotifications() { - if (!m_mfvState.supported || !m_mfvState.areInitialReadsComplete()) { + // Notification enabling is intentionally NOT gated on the initial value reads (capabilities / + // model version / privacy / model config). Subscribing to the Session Start and Detection Data + // notifications does not depend on those values, so enabling them is decoupled from the reads and + // can happen as soon as the characteristics are discovered. This lets the remote deliver wake-word + // detection data while the service is idle/ready, without first having to start an audio stream. + if (!m_mfvState.supported) { return; } @@ -1002,7 +1014,7 @@ void GattAudioServiceRdk::requestStartMfvSessionStartNotify() scheduleMfvNotifyRetry(); } else { m_mfvState.sessionStartNotifyRetries = 0; - XLOGD_DEBUG("MFV Session Start notifications enabled successfully"); + XLOGD_INFO("MFV Session Start notifications enabled successfully"); maybeEnableMfvNotifications(); } }; @@ -1037,7 +1049,7 @@ void GattAudioServiceRdk::requestStartMfvDetectionDataNotify() scheduleMfvNotifyRetry(); } else { m_mfvState.detectionDataNotifyRetries = 0; - XLOGD_DEBUG("MFV Detection Data notifications enabled successfully"); + XLOGD_INFO("MFV Detection Data notifications enabled successfully"); maybeEnableMfvNotifications(); } }; diff --git a/src/ctrlm_controller.cpp b/src/ctrlm_controller.cpp index f0ea9e34..0e7b175e 100644 --- a/src/ctrlm_controller.cpp +++ b/src/ctrlm_controller.cpp @@ -332,15 +332,6 @@ std::vector ctrlm_obj_controller_t::get_wakeup_custom_list() const { return {}; } -void ctrlm_obj_controller_t::set_mid_field_voice_capable(bool capable) { - XLOGD_WARN("not implemented."); -} - -bool ctrlm_obj_controller_t::get_mid_field_voice_capable() const { - XLOGD_WARN("not implemented."); - return false; -} - void ctrlm_obj_controller_t::set_upgrade_progress(uint8_t progress) { upgrade_progress_ = progress; ctrlm_rcu_upgrade_state_t previous_state = get_upgrade_state(); diff --git a/src/ctrlm_controller.h b/src/ctrlm_controller.h index 551eba63..ae9b800b 100644 --- a/src/ctrlm_controller.h +++ b/src/ctrlm_controller.h @@ -94,9 +94,6 @@ class ctrlm_obj_controller_t virtual ctrlm_rcu_wakeup_config_t get_wakeup_config() const; virtual std::vector get_wakeup_custom_list() const; - virtual void set_mid_field_voice_capable(bool capable); - virtual bool get_mid_field_voice_capable() const; - virtual void set_upgrade_progress(uint8_t progress); virtual uint8_t get_upgrade_progress() const; virtual void set_upgrade_state(ctrlm_rcu_upgrade_state_t state); diff --git a/src/voice/ctrlm_voice_obj.cpp b/src/voice/ctrlm_voice_obj.cpp index 563da020..5aa53583 100644 --- a/src/voice/ctrlm_voice_obj.cpp +++ b/src/voice/ctrlm_voice_obj.cpp @@ -1453,6 +1453,10 @@ ctrlm_voice_session_response_status_t ctrlm_voice_t::voice_session_req(ctrlm_net request_params.value.audio_fd.audio_format = voice_format_to_xrsr(format); request_params.value.audio_fd.callback = (create_pipe) ? NULL : ctrlm_voice_data_post_processing_cb; // RF4CE does not use pipe read callback request_params.value.audio_fd.user_data = (create_pipe) ? NULL : (void *)this; + // MFV wake-word sessions supply their wake word stream parameters (timing/confidence) after the + // session begins. Flag the request so the speech router holds the connect/init until the + // parameters are provided via voice_session_stream_params_update(). + request_params.value.audio_fd.stream_params_required = (device_type == CTRLM_VOICE_DEVICE_MFV); if(false == xrsr_session_request(voice_device_to_xrsr(device_type), dst_index, voice_format_to_xrsr(format), request_params, uuid, false, false)) { XLOGD_TELEMETRY("Failed to acquire voice session"); @@ -1502,9 +1506,21 @@ ctrlm_voice_session_response_status_t ctrlm_voice_t::voice_session_req(ctrlm_net session->has_stream_params = true; session->stream_params = *stream_params; - xrsr_session_keyword_info_set(XRSR_SRC_RCU_FF, stream_params->pre_keyword_sample_qty, stream_params->keyword_sample_qty); + xrsr_session_keyword_info_set(voice_device_to_xrsr(device_type), stream_params->pre_keyword_sample_qty, stream_params->keyword_sample_qty); } + #ifdef VOICE_BUFFER_STATS + // Reset buffer stats for every session type (PTT, FF, MFV, ...) since voice_session_data_post_processing() + // uses voice_packet_interval as a divisor. Leaving it uninitialized/stale for non-PTT sessions can cause a + // divide-by-zero (SIGFPE) in the post processing path. + voice_buffer_warning_triggered = 0; + voice_buffer_high_watermark = 0; + voice_packet_interval = voice_packet_interval_get(session->format, this->opus_samples_per_packet); + #ifdef TIMING_START_TO_FIRST_FRAGMENT + ctrlm_timestamp_get(&voice_session_begin_timestamp); + #endif + #endif + if(CTRLM_VOICE_DEVICE_PTT == session->voice_device) { errno_t safec_rc = memset_s(&session->status, sizeof(session->status), 0, sizeof(session->status)); ERR_CHK(safec_rc); @@ -1518,15 +1534,6 @@ ctrlm_voice_session_response_status_t ctrlm_voice_t::voice_session_req(ctrlm_net memset(&session->stats_session, 0, sizeof(session->stats_session)); session->stats_session.dropped_retry = ULONG_MAX; // Used to indicate whether controller provides stats or not - #ifdef VOICE_BUFFER_STATS - voice_buffer_warning_triggered = 0; - voice_buffer_high_watermark = 0; - voice_packet_interval = voice_packet_interval_get(session->format, this->opus_samples_per_packet); - #ifdef TIMING_START_TO_FIRST_FRAGMENT - ctrlm_timestamp_get(&voice_session_begin_timestamp); - #endif - #endif - // Start packet timeout, but not if this is a voice session by text or file if(!session->is_session_by_text && !session->is_session_by_file) { if(session->network_type == CTRLM_NETWORK_TYPE_IP || session->is_session_by_fifo) { @@ -1555,6 +1562,11 @@ ctrlm_voice_session_response_status_t ctrlm_voice_t::voice_session_req(ctrlm_net return (this->prefs.par_voice_enabled) ? VOICE_SESSION_RESPONSE_AVAILABLE_PAR_VOICE : VOICE_SESSION_RESPONSE_AVAILABLE; } +bool ctrlm_voice_t::voice_session_stream_params_update(ctrlm_voice_device_t device_type, bool valid, uint32_t keyword_sample_begin, uint32_t keyword_sample_end, double confidence, double signal_noise_ratio) { + XLOGD_INFO("device <%s> %s begin <%u> end <%u> confidence <%.3f> snr <%.2f>", ctrlm_voice_device_str(device_type), valid ? "valid" : "release-only", keyword_sample_begin, keyword_sample_end, confidence, signal_noise_ratio); + return xrsr_session_stream_params_set(voice_device_to_xrsr(device_type), valid, keyword_sample_begin, keyword_sample_end, confidence, signal_noise_ratio); +} + bool ctrlm_voice_t::voice_session_term(std::string &session_id) { for(uint32_t group = VOICE_SESSION_GROUP_DEFAULT; group < VOICE_SESSION_GROUP_QTY; group++) { ctrlm_voice_session_t *session = &this->voice_session[group]; @@ -1693,12 +1705,16 @@ void ctrlm_voice_t::voice_session_data_post_processing(int bytes_sent, const cha // The total packets (received + lost) uint32_t packets_total = session->packets_processed + session->packets_lost; long long session_delta = (session_time - ((packets_total - 1) * this->voice_packet_interval)); // in microseconds - unsigned long watermark = (session_delta / this->voice_packet_interval) + 1; - if(session_delta > 0 && watermark > voice_buffer_high_watermark) { - voice_buffer_high_watermark = watermark; - } - if(session_delta > (long long)(VOICE_BUFFER_WARNING_THRESHOLD * this->voice_packet_interval)) { - voice_buffer_warning_triggered = 1; + if(this->voice_packet_interval == 0) { + XLOGD_WARN("voice_packet_interval is 0 for device <%s> format <%s>; skipping buffer stats to avoid divide-by-zero", ctrlm_voice_device_str(session->voice_device), ctrlm_voice_format_str(session->format)); + } else { + unsigned long watermark = (session_delta / this->voice_packet_interval) + 1; + if(session_delta > 0 && watermark > voice_buffer_high_watermark) { + voice_buffer_high_watermark = watermark; + } + if(session_delta > (long long)(VOICE_BUFFER_WARNING_THRESHOLD * this->voice_packet_interval)) { + voice_buffer_warning_triggered = 1; + } } #endif @@ -2751,7 +2767,27 @@ void ctrlm_voice_t::voice_session_end_callback(ctrlm_voice_session_end_cb_t *ses session->session_active_server = false; if(session->state_src == CTRLM_VOICE_STATE_SRC_STREAMING) { - voice_session_end(session, CTRLM_VOICE_SESSION_END_REASON_OTHER_ERROR); + // The source (e.g. an MFV/FF remote) is still streaming when the server session ended. This + // occurs when end of speech is detected on the box side rather than via a controller key + // release. Map the speech router's end reason to the appropriate ctrlm end reason so that a + // normal end of speech is reported as DONE rather than an error. + ctrlm_voice_session_end_reason_t end_reason; + switch(stats->session_end_reason) { + case XRSR_SESSION_END_REASON_EOS: + case XRSR_SESSION_END_REASON_EOT: { + end_reason = CTRLM_VOICE_SESSION_END_REASON_DONE; + break; + } + case XRSR_SESSION_END_REASON_ERROR_SESSION_TIMEOUT: { + end_reason = CTRLM_VOICE_SESSION_END_REASON_TIMEOUT_MAXIMUM; + break; + } + default: { + end_reason = CTRLM_VOICE_SESSION_END_REASON_OTHER_ERROR; + break; + } + } + voice_session_end(session, end_reason); } else if(!session->session_active_controller) { session->state_src = CTRLM_VOICE_STATE_SRC_READY; diff --git a/src/voice/ctrlm_voice_obj.h b/src/voice/ctrlm_voice_obj.h index 3ec217b2..e7efd86a 100644 --- a/src/voice/ctrlm_voice_obj.h +++ b/src/voice/ctrlm_voice_obj.h @@ -497,6 +497,10 @@ class ctrlm_voice_t { void voice_session_rsp_confirm(bool result, signed long long rsp_time, unsigned int rsp_window, const std::string &err_str, ctrlm_timestamp_t *timestamp); bool voice_session_data(ctrlm_network_id_t network_id, ctrlm_controller_id_t controller_id, const char *buffer, long unsigned int length, ctrlm_timestamp_t *timestamp=NULL, uint8_t *lqi=NULL); bool voice_session_data(ctrlm_network_id_t network_id, ctrlm_controller_id_t controller_id, int fd, const uuid_t *uuid=NULL); + // Supplies wake word stream parameters (timing/confidence) that arrive after the session has begun + // (e.g. BLE MFV detection data) and releases the speech router's deferred connect. Pass valid=false + // to release the connect without wake word parameters (e.g. on a detection-data timeout). + bool voice_session_stream_params_update(ctrlm_voice_device_t device_type, bool valid, uint32_t keyword_sample_begin, uint32_t keyword_sample_end, double confidence, double signal_noise_ratio); void voice_session_data_post_processing(int bytes_sent, const char *action, ctrlm_timestamp_t *timestamp); void voice_session_end(ctrlm_network_id_t network_id, ctrlm_controller_id_t controller_id, ctrlm_voice_session_end_reason_t reason, ctrlm_timestamp_t *timestamp=NULL, ctrlm_voice_session_end_stats_t *stats=NULL); void voice_session_end(ctrlm_voice_session_t *session, ctrlm_voice_session_end_reason_t reason, ctrlm_timestamp_t *timestamp=NULL, ctrlm_voice_session_end_stats_t *stats=NULL); diff --git a/src/voice/ctrlm_voice_obj_generic.cpp b/src/voice/ctrlm_voice_obj_generic.cpp index aa7d61cf..63fbcb89 100644 --- a/src/voice/ctrlm_voice_obj_generic.cpp +++ b/src/voice/ctrlm_voice_obj_generic.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "ctrlm_voice_obj_generic.h" #include "ctrlm.h" @@ -165,6 +166,21 @@ void ctrlm_voice_generic_t::voice_sdk_update_routes() { errno_t safec_rc = memset_s(&routes, sizeof(routes), 0, sizeof(routes)); ERR_CHK(safec_rc); + // TEMPORARY: hardcode all voice server URLs used by ctrlm, overriding any configured/RFC/settings values. + // ctrlm selects the protocol handler by scheme prefix: "vrngs" -> secure ws_nextgen (vrex), translated + // internally to "wss". A raw "wss" prefix is not recognized and is rejected as "unsupported url". + static const char *CTRLM_VOICE_HARDCODED_URL = "vrngs://eng.voice.xvp.eu-1.xcal.tv/vrex/speech/websocket?stbSwVersion=QSE041.004.00U"; + this->prefs.server_url_src_ptt = CTRLM_VOICE_HARDCODED_URL; + this->prefs.server_url_src_ff = CTRLM_VOICE_HARDCODED_URL; + this->prefs.server_url_src_mic_tap = CTRLM_VOICE_HARDCODED_URL; + + // TEMPORARY: allow the hardcoded URL's hostname by adding its pattern to the accepted hostname list. + // Re-add each call (checking for presence) so it survives any config/RFC reload that clears the list. + const std::string hardcoded_host_regex = "^.*\\.xcal\\.tv$"; + if(std::find(this->prefs.server_hosts.begin(), this->prefs.server_hosts.end(), hardcoded_host_regex) == this->prefs.server_hosts.end()) { + this->url_hostname_pattern_add("*.xcal.tv"); + } + bool networked_standby_supported = ctrlm_is_networked_standby_supported(); // iterate over source to url mapping diff --git a/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp b/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp index 35b8eb7f..1571537e 100644 --- a/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp +++ b/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp @@ -292,9 +292,11 @@ void ctrlm_voice_endpoint_ws_nextgen_t::voice_session_begin_callback_ws_nextgen( config_in.ws.ocsp_expired_allow = false; if(source == CTRLM_VOICE_DEVICE_MFV) { - // Reuse the audio profile/model to signal to Vrex that this transaction is from mid-field voice. - xrsv_ws_nextgen_update_audio_profile(this->xrsv_obj_ws_nextgen, NEXTGEN_AUDIO_PROFILE_MFV); - xrsv_ws_nextgen_update_audio_model(this->xrsv_obj_ws_nextgen, NEXTGEN_AUDIO_MODEL_MFV); + // TEMPORARY: hard-code MFV wuw sessions to send the same audio profile/model as the on-device + // HF microphone instead of the MFV-specific values. Revert to NEXTGEN_AUDIO_PROFILE_MFV / + // NEXTGEN_AUDIO_MODEL_MFV to signal to Vrex that this transaction is from mid-field voice. + xrsv_ws_nextgen_update_audio_profile(this->xrsv_obj_ws_nextgen, NEXTGEN_AUDIO_PROFILE_HF); + xrsv_ws_nextgen_update_audio_model(this->xrsv_obj_ws_nextgen, NEXTGEN_AUDIO_MODEL_HF); } else if(is_mic) { xrsv_ws_nextgen_update_audio_profile(this->xrsv_obj_ws_nextgen, NEXTGEN_AUDIO_PROFILE_HF); xrsv_ws_nextgen_update_audio_model(this->xrsv_obj_ws_nextgen, NEXTGEN_AUDIO_MODEL_HF); From 17d6f7024ea88523ebabd0eea9e1a9f21dbfd9c1 Mon Sep 17 00:00:00 2001 From: klu339 Date: Thu, 6 Aug 2026 18:51:02 +0000 Subject: [PATCH 86/86] VREX endpoint for MFV --- src/voice/ctrlm_voice_obj_generic.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/voice/ctrlm_voice_obj_generic.cpp b/src/voice/ctrlm_voice_obj_generic.cpp index 63fbcb89..2b166359 100644 --- a/src/voice/ctrlm_voice_obj_generic.cpp +++ b/src/voice/ctrlm_voice_obj_generic.cpp @@ -169,7 +169,7 @@ void ctrlm_voice_generic_t::voice_sdk_update_routes() { // TEMPORARY: hardcode all voice server URLs used by ctrlm, overriding any configured/RFC/settings values. // ctrlm selects the protocol handler by scheme prefix: "vrngs" -> secure ws_nextgen (vrex), translated // internally to "wss". A raw "wss" prefix is not recognized and is rejected as "unsupported url". - static const char *CTRLM_VOICE_HARDCODED_URL = "vrngs://eng.voice.xvp.eu-1.xcal.tv/vrex/speech/websocket?stbSwVersion=QSE041.004.00U"; + static const char *CTRLM_VOICE_HARDCODED_URL = "vrngs://voice-np-eks-ue2i.vrexcore.net/qa20/vrex/speech"; this->prefs.server_url_src_ptt = CTRLM_VOICE_HARDCODED_URL; this->prefs.server_url_src_ff = CTRLM_VOICE_HARDCODED_URL; this->prefs.server_url_src_mic_tap = CTRLM_VOICE_HARDCODED_URL; @@ -179,6 +179,7 @@ void ctrlm_voice_generic_t::voice_sdk_update_routes() { const std::string hardcoded_host_regex = "^.*\\.xcal\\.tv$"; if(std::find(this->prefs.server_hosts.begin(), this->prefs.server_hosts.end(), hardcoded_host_regex) == this->prefs.server_hosts.end()) { this->url_hostname_pattern_add("*.xcal.tv"); + this->url_hostname_pattern_add("*.vrexcore.net"); } bool networked_standby_supported = ctrlm_is_networked_standby_supported();