mirror of
https://github.com/Threnklyn/esphome-dev.git
synced 2026-05-19 04:33:27 +02:00
73c82862cf
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
286 lines
11 KiB
C++
286 lines
11 KiB
C++
#include "bluetooth_proxy.h"
|
|
|
|
#include "esphome/core/log.h"
|
|
|
|
#ifdef USE_ESP32
|
|
|
|
#include "esphome/components/api/api_server.h"
|
|
|
|
namespace esphome {
|
|
namespace bluetooth_proxy {
|
|
|
|
static const char *const TAG = "bluetooth_proxy";
|
|
|
|
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
|
|
|
|
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
|
if (!api::global_api_server->is_connected())
|
|
return false;
|
|
ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(),
|
|
device.get_rssi());
|
|
this->send_api_packet_(device);
|
|
|
|
return true;
|
|
}
|
|
|
|
void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
|
|
api::BluetoothLEAdvertisementResponse resp;
|
|
resp.address = device.address_uint64();
|
|
if (!device.get_name().empty())
|
|
resp.name = device.get_name();
|
|
resp.rssi = device.get_rssi();
|
|
for (auto uuid : device.get_service_uuids()) {
|
|
resp.service_uuids.push_back(uuid.to_string());
|
|
}
|
|
for (auto &data : device.get_service_datas()) {
|
|
api::BluetoothServiceData service_data;
|
|
service_data.uuid = data.uuid.to_string();
|
|
service_data.data.assign(data.data.begin(), data.data.end());
|
|
resp.service_data.push_back(std::move(service_data));
|
|
}
|
|
for (auto &data : device.get_manufacturer_datas()) {
|
|
api::BluetoothServiceData manufacturer_data;
|
|
manufacturer_data.uuid = data.uuid.to_string();
|
|
manufacturer_data.data.assign(data.data.begin(), data.data.end());
|
|
resp.manufacturer_data.push_back(std::move(manufacturer_data));
|
|
}
|
|
api::global_api_server->send_bluetooth_le_advertisement(resp);
|
|
}
|
|
|
|
void BluetoothProxy::dump_config() {
|
|
ESP_LOGCONFIG(TAG, "Bluetooth Proxy:");
|
|
ESP_LOGCONFIG(TAG, " Active: %s", YESNO(this->active_));
|
|
}
|
|
|
|
int BluetoothProxy::get_bluetooth_connections_free() {
|
|
int free = 0;
|
|
for (auto *connection : this->connections_) {
|
|
if (connection->address_ == 0) {
|
|
free++;
|
|
ESP_LOGV(TAG, "[%d] Free connection", connection->get_connection_index());
|
|
} else {
|
|
ESP_LOGV(TAG, "[%d] Used connection by [%s]", connection->get_connection_index(),
|
|
connection->address_str().c_str());
|
|
}
|
|
}
|
|
return free;
|
|
}
|
|
|
|
void BluetoothProxy::loop() {
|
|
if (!api::global_api_server->is_connected()) {
|
|
for (auto *connection : this->connections_) {
|
|
if (connection->get_address() != 0) {
|
|
connection->disconnect();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
for (auto *connection : this->connections_) {
|
|
if (connection->send_service_ == connection->services_.size()) {
|
|
connection->send_service_ = -1;
|
|
api::global_api_server->send_bluetooth_gatt_services_done(connection->get_address());
|
|
} else if (connection->send_service_ >= 0) {
|
|
auto &service = connection->services_[connection->send_service_];
|
|
api::BluetoothGATTGetServicesResponse resp;
|
|
resp.address = connection->get_address();
|
|
api::BluetoothGATTService service_resp;
|
|
service_resp.uuid = {service->uuid.get_128bit_high(), service->uuid.get_128bit_low()};
|
|
service_resp.handle = service->start_handle;
|
|
if (!service->parsed)
|
|
service->parse_characteristics();
|
|
for (auto &characteristic : service->characteristics) {
|
|
api::BluetoothGATTCharacteristic characteristic_resp;
|
|
characteristic_resp.uuid = {characteristic->uuid.get_128bit_high(), characteristic->uuid.get_128bit_low()};
|
|
characteristic_resp.handle = characteristic->handle;
|
|
characteristic_resp.properties = characteristic->properties;
|
|
if (!characteristic->parsed)
|
|
characteristic->parse_descriptors();
|
|
for (auto &descriptor : characteristic->descriptors) {
|
|
api::BluetoothGATTDescriptor descriptor_resp;
|
|
descriptor_resp.uuid = {descriptor->uuid.get_128bit_high(), descriptor->uuid.get_128bit_low()};
|
|
descriptor_resp.handle = descriptor->handle;
|
|
characteristic_resp.descriptors.push_back(std::move(descriptor_resp));
|
|
}
|
|
service_resp.characteristics.push_back(std::move(characteristic_resp));
|
|
}
|
|
resp.services.push_back(std::move(service_resp));
|
|
api::global_api_server->send_bluetooth_gatt_services(resp);
|
|
// Descriptors are rarely used and can be quite large so we clear them
|
|
// after sending them to save memory. If something actually needs them
|
|
// it can parse them again.
|
|
for (auto &characteristic : service->characteristics) {
|
|
characteristic->release_descriptors();
|
|
}
|
|
connection->send_service_++;
|
|
}
|
|
}
|
|
}
|
|
|
|
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
|
|
for (auto *connection : this->connections_) {
|
|
if (connection->get_address() == address)
|
|
return connection;
|
|
}
|
|
|
|
if (!reserve)
|
|
return nullptr;
|
|
|
|
for (auto *connection : this->connections_) {
|
|
if (connection->get_address() == 0) {
|
|
connection->set_address(address);
|
|
// All connections must start at INIT
|
|
// We only set the state if we allocate the connection
|
|
// to avoid a race where multiple connection attempts
|
|
// are made.
|
|
connection->set_state(espbt::ClientState::INIT);
|
|
return connection;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) {
|
|
switch (msg.request_type) {
|
|
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: {
|
|
auto *connection = this->get_connection_(msg.address, true);
|
|
if (connection == nullptr) {
|
|
ESP_LOGW(TAG, "No free connections available");
|
|
api::global_api_server->send_bluetooth_device_connection(msg.address, false);
|
|
return;
|
|
}
|
|
if (connection->state() == espbt::ClientState::CONNECTED ||
|
|
connection->state() == espbt::ClientState::ESTABLISHED) {
|
|
ESP_LOGW(TAG, "[%d] [%s] Connection already established", connection->get_connection_index(),
|
|
connection->address_str().c_str());
|
|
api::global_api_server->send_bluetooth_device_connection(msg.address, true);
|
|
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
|
|
this->get_bluetooth_connections_limit());
|
|
return;
|
|
} else if (connection->state() != espbt::ClientState::INIT) {
|
|
ESP_LOGW(TAG, "[%d] [%s] Connection already in progress", connection->get_connection_index(),
|
|
connection->address_str().c_str());
|
|
return;
|
|
}
|
|
connection->set_state(espbt::ClientState::SEARCHING);
|
|
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
|
|
this->get_bluetooth_connections_limit());
|
|
break;
|
|
}
|
|
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: {
|
|
auto *connection = this->get_connection_(msg.address, false);
|
|
if (connection == nullptr) {
|
|
api::global_api_server->send_bluetooth_device_connection(msg.address, false);
|
|
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
|
|
this->get_bluetooth_connections_limit());
|
|
return;
|
|
}
|
|
if (connection->state() != espbt::ClientState::IDLE) {
|
|
connection->disconnect();
|
|
} else {
|
|
connection->set_address(0);
|
|
api::global_api_server->send_bluetooth_device_connection(msg.address, false);
|
|
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
|
|
this->get_bluetooth_connections_limit());
|
|
}
|
|
break;
|
|
}
|
|
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR:
|
|
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) {
|
|
auto *connection = this->get_connection_(msg.address, false);
|
|
if (connection == nullptr) {
|
|
ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected");
|
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
|
return;
|
|
}
|
|
|
|
auto err = connection->read_characteristic(msg.handle);
|
|
if (err != ESP_OK) {
|
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
|
}
|
|
}
|
|
|
|
void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg) {
|
|
auto *connection = this->get_connection_(msg.address, false);
|
|
if (connection == nullptr) {
|
|
ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected");
|
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
|
return;
|
|
}
|
|
|
|
auto err = connection->write_characteristic(msg.handle, msg.data, msg.response);
|
|
if (err != ESP_OK) {
|
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
|
}
|
|
}
|
|
|
|
void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg) {
|
|
auto *connection = this->get_connection_(msg.address, false);
|
|
if (connection == nullptr) {
|
|
ESP_LOGW(TAG, "Cannot read GATT descriptor, not connected");
|
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
|
return;
|
|
}
|
|
|
|
auto err = connection->read_descriptor(msg.handle);
|
|
if (err != ESP_OK) {
|
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
|
}
|
|
}
|
|
|
|
void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg) {
|
|
auto *connection = this->get_connection_(msg.address, false);
|
|
if (connection == nullptr) {
|
|
ESP_LOGW(TAG, "Cannot write GATT descriptor, not connected");
|
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
|
return;
|
|
}
|
|
|
|
auto err = connection->write_descriptor(msg.handle, msg.data);
|
|
if (err != ESP_OK) {
|
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
|
}
|
|
}
|
|
|
|
void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg) {
|
|
auto *connection = this->get_connection_(msg.address, false);
|
|
if (connection == nullptr || !connection->connected()) {
|
|
ESP_LOGW(TAG, "Cannot get GATT services, not connected");
|
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED);
|
|
return;
|
|
}
|
|
if (connection->services_.empty()) {
|
|
ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str().c_str());
|
|
api::global_api_server->send_bluetooth_gatt_services_done(msg.address);
|
|
return;
|
|
}
|
|
if (connection->send_service_ == -1) // Don't start sending services again if we're already sending them
|
|
connection->send_service_ = 0;
|
|
}
|
|
|
|
void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg) {
|
|
auto *connection = this->get_connection_(msg.address, false);
|
|
if (connection == nullptr) {
|
|
ESP_LOGW(TAG, "Cannot notify GATT characteristic, not connected");
|
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
|
return;
|
|
}
|
|
|
|
auto err = connection->notify_characteristic(msg.handle, msg.enable);
|
|
if (err != ESP_OK) {
|
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
|
}
|
|
}
|
|
|
|
BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
|
|
|
} // namespace bluetooth_proxy
|
|
} // namespace esphome
|
|
|
|
#endif // USE_ESP32
|