[bluetooth_proxy] Allow changing active/passive via api (#8649)

This commit is contained in:
Jesse Hills 2025-04-30 00:43:55 +12:00 committed by GitHub
parent 5f9a509bdc
commit ecb91b0101
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 262 additions and 7 deletions

View File

@ -61,6 +61,7 @@ service APIConnection {
rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {}
rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {}
rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
rpc bluetooth_scanner_set_mode(BluetoothScannerSetModeRequest) returns (void) {}
rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {}
rpc voice_assistant_get_configuration(VoiceAssistantConfigurationRequest) returns (VoiceAssistantConfigurationResponse) {}
@ -1472,6 +1473,37 @@ message BluetoothDeviceClearCacheResponse {
int32 error = 3;
}
enum BluetoothScannerState {
BLUETOOTH_SCANNER_STATE_IDLE = 0;
BLUETOOTH_SCANNER_STATE_STARTING = 1;
BLUETOOTH_SCANNER_STATE_RUNNING = 2;
BLUETOOTH_SCANNER_STATE_FAILED = 3;
BLUETOOTH_SCANNER_STATE_STOPPING = 4;
BLUETOOTH_SCANNER_STATE_STOPPED = 5;
}
enum BluetoothScannerMode {
BLUETOOTH_SCANNER_MODE_PASSIVE = 0;
BLUETOOTH_SCANNER_MODE_ACTIVE = 1;
}
message BluetoothScannerStateResponse {
option(id) = 126;
option(source) = SOURCE_SERVER;
option(ifdef) = "USE_BLUETOOTH_PROXY";
BluetoothScannerState state = 1;
BluetoothScannerMode mode = 2;
}
message BluetoothScannerSetModeRequest {
option(id) = 127;
option(source) = SOURCE_CLIENT;
option(ifdef) = "USE_BLUETOOTH_PROXY";
BluetoothScannerMode mode = 1;
}
// ==================== PUSH TO TALK ====================
enum VoiceAssistantSubscribeFlag {
VOICE_ASSISTANT_SUBSCRIBE_NONE = 0;

View File

@ -1475,6 +1475,11 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_
resp.limit = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_limit();
return resp;
}
void APIConnection::bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->bluetooth_scanner_set_mode(
msg.mode == enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE);
}
#endif
#ifdef USE_VOICE_ASSISTANT

View File

@ -221,6 +221,7 @@ class APIConnection : public APIServerConnection {
void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override;
BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
const SubscribeBluetoothConnectionsFreeRequest &msg) override;
void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) override;
#endif
#ifdef USE_HOMEASSISTANT_TIME

View File

@ -422,6 +422,38 @@ const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::Bluet
}
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<> const char *proto_enum_to_string<enums::BluetoothScannerState>(enums::BluetoothScannerState value) {
switch (value) {
case enums::BLUETOOTH_SCANNER_STATE_IDLE:
return "BLUETOOTH_SCANNER_STATE_IDLE";
case enums::BLUETOOTH_SCANNER_STATE_STARTING:
return "BLUETOOTH_SCANNER_STATE_STARTING";
case enums::BLUETOOTH_SCANNER_STATE_RUNNING:
return "BLUETOOTH_SCANNER_STATE_RUNNING";
case enums::BLUETOOTH_SCANNER_STATE_FAILED:
return "BLUETOOTH_SCANNER_STATE_FAILED";
case enums::BLUETOOTH_SCANNER_STATE_STOPPING:
return "BLUETOOTH_SCANNER_STATE_STOPPING";
case enums::BLUETOOTH_SCANNER_STATE_STOPPED:
return "BLUETOOTH_SCANNER_STATE_STOPPED";
default:
return "UNKNOWN";
}
}
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<> const char *proto_enum_to_string<enums::BluetoothScannerMode>(enums::BluetoothScannerMode value) {
switch (value) {
case enums::BLUETOOTH_SCANNER_MODE_PASSIVE:
return "BLUETOOTH_SCANNER_MODE_PASSIVE";
case enums::BLUETOOTH_SCANNER_MODE_ACTIVE:
return "BLUETOOTH_SCANNER_MODE_ACTIVE";
default:
return "UNKNOWN";
}
}
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<>
const char *proto_enum_to_string<enums::VoiceAssistantSubscribeFlag>(enums::VoiceAssistantSubscribeFlag value) {
switch (value) {
@ -6775,6 +6807,61 @@ void BluetoothDeviceClearCacheResponse::dump_to(std::string &out) const {
out.append("}");
}
#endif
bool BluetoothScannerStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->state = value.as_enum<enums::BluetoothScannerState>();
return true;
}
case 2: {
this->mode = value.as_enum<enums::BluetoothScannerMode>();
return true;
}
default:
return false;
}
}
void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum<enums::BluetoothScannerState>(1, this->state);
buffer.encode_enum<enums::BluetoothScannerMode>(2, this->mode);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothScannerStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothScannerStateResponse {\n");
out.append(" state: ");
out.append(proto_enum_to_string<enums::BluetoothScannerState>(this->state));
out.append("\n");
out.append(" mode: ");
out.append(proto_enum_to_string<enums::BluetoothScannerMode>(this->mode));
out.append("\n");
out.append("}");
}
#endif
bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->mode = value.as_enum<enums::BluetoothScannerMode>();
return true;
}
default:
return false;
}
}
void BluetoothScannerSetModeRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum<enums::BluetoothScannerMode>(1, this->mode);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothScannerSetModeRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothScannerSetModeRequest {\n");
out.append(" mode: ");
out.append(proto_enum_to_string<enums::BluetoothScannerMode>(this->mode));
out.append("\n");
out.append("}");
}
#endif
bool SubscribeVoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {

View File

@ -169,6 +169,18 @@ enum BluetoothDeviceRequestType : uint32_t {
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5,
BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6,
};
enum BluetoothScannerState : uint32_t {
BLUETOOTH_SCANNER_STATE_IDLE = 0,
BLUETOOTH_SCANNER_STATE_STARTING = 1,
BLUETOOTH_SCANNER_STATE_RUNNING = 2,
BLUETOOTH_SCANNER_STATE_FAILED = 3,
BLUETOOTH_SCANNER_STATE_STOPPING = 4,
BLUETOOTH_SCANNER_STATE_STOPPED = 5,
};
enum BluetoothScannerMode : uint32_t {
BLUETOOTH_SCANNER_MODE_PASSIVE = 0,
BLUETOOTH_SCANNER_MODE_ACTIVE = 1,
};
enum VoiceAssistantSubscribeFlag : uint32_t {
VOICE_ASSISTANT_SUBSCRIBE_NONE = 0,
VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1,
@ -1742,6 +1754,29 @@ class BluetoothDeviceClearCacheResponse : public ProtoMessage {
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothScannerStateResponse : public ProtoMessage {
public:
enums::BluetoothScannerState state{};
enums::BluetoothScannerMode mode{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothScannerSetModeRequest : public ProtoMessage {
public:
enums::BluetoothScannerMode mode{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class SubscribeVoiceAssistantRequest : public ProtoMessage {
public:
bool subscribe{false};

View File

@ -472,6 +472,16 @@ bool APIServerConnectionBase::send_bluetooth_device_clear_cache_response(const B
return this->send_message_<BluetoothDeviceClearCacheResponse>(msg, 88);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
bool APIServerConnectionBase::send_bluetooth_scanner_state_response(const BluetoothScannerStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_bluetooth_scanner_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<BluetoothScannerStateResponse>(msg, 126);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#endif
#ifdef USE_VOICE_ASSISTANT
#endif
#ifdef USE_VOICE_ASSISTANT
@ -1212,6 +1222,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_noise_encryption_set_key_request: %s", msg.dump().c_str());
#endif
this->on_noise_encryption_set_key_request(msg);
#endif
break;
}
case 127: {
#ifdef USE_BLUETOOTH_PROXY
BluetoothScannerSetModeRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_scanner_set_mode_request: %s", msg.dump().c_str());
#endif
this->on_bluetooth_scanner_set_mode_request(msg);
#endif
break;
}
@ -1705,6 +1726,19 @@ void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request(
this->unsubscribe_bluetooth_le_advertisements(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->bluetooth_scanner_set_mode(msg);
}
#endif
#ifdef USE_VOICE_ASSISTANT
void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) {
if (!this->is_connection_setup()) {

View File

@ -234,6 +234,12 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_scanner_state_response(const BluetoothScannerStateResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){};
#endif
@ -440,6 +446,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_BLUETOOTH_PROXY
virtual void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) = 0;
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
#endif
@ -551,6 +560,9 @@ class APIServerConnection : public APIServerConnectionBase {
void on_unsubscribe_bluetooth_le_advertisements_request(
const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) override;
#endif
#ifdef USE_VOICE_ASSISTANT
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
#endif

View File

@ -25,6 +25,22 @@ std::vector<uint64_t> get_128bit_uuid_vec(esp_bt_uuid_t uuid_source) {
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
void BluetoothProxy::setup() {
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
if (this->api_connection_ != nullptr) {
this->send_bluetooth_scanner_state_(state);
}
});
}
void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state) {
api::BluetoothScannerStateResponse resp;
resp.state = static_cast<api::enums::BluetoothScannerState>(state);
resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
this->api_connection_->send_bluetooth_scanner_state_response(resp);
}
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || this->raw_advertisements_)
return false;
@ -453,6 +469,8 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection
this->api_connection_ = api_connection;
this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS;
this->parent_->recalculate_advertisement_parser_types();
this->send_bluetooth_scanner_state_(this->parent_->get_scanner_state());
}
void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connection) {
@ -525,6 +543,17 @@ void BluetoothProxy::send_device_unpairing(uint64_t address, bool success, esp_e
this->api_connection_->send_bluetooth_device_unpairing_response(call);
}
void BluetoothProxy::bluetooth_scanner_set_mode(bool active) {
if (this->parent_->get_scan_active() == active) {
return;
}
ESP_LOGD(TAG, "Setting scanner mode to %s", active ? "active" : "passive");
this->parent_->set_scan_active(active);
this->parent_->stop_scan();
this->parent_->set_scan_continuous(
true); // Set this to true to automatically start scanning again when it has cleaned up.
}
BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace bluetooth_proxy

View File

@ -41,6 +41,7 @@ enum BluetoothProxyFeature : uint32_t {
FEATURE_PAIRING = 1 << 3,
FEATURE_CACHE_CLEARING = 1 << 4,
FEATURE_RAW_ADVERTISEMENTS = 1 << 5,
FEATURE_STATE_AND_MODE = 1 << 6,
};
enum BluetoothProxySubscriptionFlag : uint32_t {
@ -53,6 +54,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) override;
void dump_config() override;
void setup() override;
void loop() override;
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
@ -84,6 +86,8 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
void send_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK);
void send_device_clear_cache(uint64_t address, bool success, esp_err_t error = ESP_OK);
void bluetooth_scanner_set_mode(bool active);
static void uint64_to_bd_addr(uint64_t address, esp_bd_addr_t bd_addr) {
bd_addr[0] = (address >> 40) & 0xff;
bd_addr[1] = (address >> 32) & 0xff;
@ -107,6 +111,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
uint32_t flags = 0;
flags |= BluetoothProxyFeature::FEATURE_PASSIVE_SCAN;
flags |= BluetoothProxyFeature::FEATURE_RAW_ADVERTISEMENTS;
flags |= BluetoothProxyFeature::FEATURE_STATE_AND_MODE;
if (this->active_) {
flags |= BluetoothProxyFeature::FEATURE_ACTIVE_CONNECTIONS;
flags |= BluetoothProxyFeature::FEATURE_REMOTE_CACHING;
@ -124,6 +129,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
protected:
void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state);
BluetoothConnection *get_connection_(uint64_t address, bool reserve);

View File

@ -245,7 +245,7 @@ void ESP32BLETracker::stop_scan_() {
return;
}
this->cancel_timeout("scan");
this->scanner_state_ = ScannerState::STOPPING;
this->set_scanner_state_(ScannerState::STOPPING);
esp_err_t err = esp_ble_gap_stop_scanning();
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_stop_scanning failed: %d", err);
@ -272,7 +272,7 @@ void ESP32BLETracker::start_scan_(bool first) {
}
return;
}
this->scanner_state_ = ScannerState::STARTING;
this->set_scanner_state_(ScannerState::STARTING);
ESP_LOGD(TAG, "Starting scan, set scanner state to STARTING.");
if (!first) {
for (auto *listener : this->listeners_)
@ -315,7 +315,7 @@ void ESP32BLETracker::end_of_scan_() {
for (auto *listener : this->listeners_)
listener->on_scan_end();
this->scanner_state_ = ScannerState::IDLE;
this->set_scanner_state_(ScannerState::IDLE);
}
void ESP32BLETracker::register_client(ESPBTClient *client) {
@ -398,9 +398,9 @@ void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble
}
if (param.status == ESP_BT_STATUS_SUCCESS) {
this->scan_start_fail_count_ = 0;
this->scanner_state_ = ScannerState::RUNNING;
this->set_scanner_state_(ScannerState::RUNNING);
} else {
this->scanner_state_ = ScannerState::FAILED;
this->set_scanner_state_(ScannerState::FAILED);
if (this->scan_start_fail_count_ != std::numeric_limits<uint8_t>::max()) {
this->scan_start_fail_count_++;
}
@ -422,7 +422,7 @@ void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_
ESP_LOGE(TAG, "Scan was stopped when stop complete.");
}
}
this->scanner_state_ = ScannerState::STOPPED;
this->set_scanner_state_(ScannerState::STOPPED);
}
void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param) {
@ -449,7 +449,7 @@ void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_re
ESP_LOGE(TAG, "Scan was stopped when scan completed.");
}
}
this->scanner_state_ = ScannerState::STOPPED;
this->set_scanner_state_(ScannerState::STOPPED);
}
}
@ -460,6 +460,11 @@ void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
}
}
void ESP32BLETracker::set_scanner_state_(ScannerState state) {
this->scanner_state_ = state;
this->scanner_state_callbacks_.call(state);
}
ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); }
optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData &data) {
if (!data.uuid.contains(0x4C, 0x00))

View File

@ -218,6 +218,7 @@ class ESP32BLETracker : public Component,
void set_scan_interval(uint32_t scan_interval) { scan_interval_ = scan_interval; }
void set_scan_window(uint32_t scan_window) { scan_window_ = scan_window; }
void set_scan_active(bool scan_active) { scan_active_ = scan_active; }
bool get_scan_active() const { return scan_active_; }
void set_scan_continuous(bool scan_continuous) { scan_continuous_ = scan_continuous; }
/// Setup the FreeRTOS task and the Bluetooth stack.
@ -241,6 +242,11 @@ class ESP32BLETracker : public Component,
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
void ble_before_disabled_event_handler() override;
void add_scanner_state_callback(std::function<void(ScannerState)> &&callback) {
this->scanner_state_callbacks_.add(std::move(callback));
}
ScannerState get_scanner_state() const { return this->scanner_state_; }
protected:
void stop_scan_();
/// Start a single scan by setting up the parameters and doing some esp-idf calls.
@ -255,6 +261,8 @@ class ESP32BLETracker : public Component,
void gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param);
/// Called when a `ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT` event is received.
void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param);
/// Called to set the scanner state. Will also call callbacks to let listeners know when state is changed.
void set_scanner_state_(ScannerState state);
int app_id_{0};
@ -273,6 +281,7 @@ class ESP32BLETracker : public Component,
bool scan_continuous_;
bool scan_active_;
ScannerState scanner_state_{ScannerState::IDLE};
CallbackManager<void(ScannerState)> scanner_state_callbacks_;
bool ble_was_disabled_{true};
bool raw_advertisements_{false};
bool parse_advertisements_{false};