Skip to content

Commit 9164cad

Browse files
committed
Initial release
Update README.md Initial release
0 parents  commit 9164cad

File tree

12 files changed

+1437
-0
lines changed

12 files changed

+1437
-0
lines changed

.gitignore

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/.esphome/
2+
**/.pioenvs/
3+
**/.piolibdeps/
4+
**/src/
5+
**/lib/
6+
**/platformio.ini
7+
**/partitions.csv
8+
9+
*.pyc
10+
11+
/secrets.yaml
12+
13+
/esphome
14+
/.pio
15+
16+
/.vscode/
17+

LICENSE

+709
Large diffs are not rendered by default.

README.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# `ESPHome` components
2+
3+
A collection of my ESPHome components.
4+
Please ⭐️ this repo if you find it useful.
5+
6+
To use this repository you should confugure it inside your yaml-configuration:
7+
```yaml
8+
external_components:
9+
- source: github://myhomeiot/esphome-components
10+
```
11+
12+
or download component into `custom_components` folder (you can use another name) and add following lines to your yaml-configuration:
13+
```yaml
14+
external_components:
15+
- source: custom_components
16+
```
17+
18+
You can take a look at samples of usage of those components in [examples](examples) folder.
19+
20+
## [BLE Client](custom_components/myhomeiot_ble_client)
21+
BLE Client allow to read characteristics from devices.
22+
Difference from build-in [ESPHome BLE Client](https://esphome.io/components/sensor/ble_client.html):
23+
- Always disconnects from device after reading characteristic, this will allow to save device battery. You can specify `update_interval`, defaults to 60min.
24+
- Uses lambda for parsing and extracting data into specific sensors make this component very flexible and useful for prototyping.
25+
- There is no limit to the number of BLE Clients used (build-in BLE Client has limit of 3 instances). This component uses BLE Host component which you should count as one instance of build-in BLE Client. All BLE clients are processed sequentially inside the host component at time when they was detected and update interval reached.
26+
27+
## [BLE Host](custom_components/myhomeiot_ble_host)
28+
Used by BLE Client component.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import esphome.codegen as cg
2+
import esphome.config_validation as cv
3+
from esphome.components import myhomeiot_ble_host, esp32_ble_tracker
4+
from esphome import automation
5+
from esphome.const import (
6+
CONF_ID,
7+
CONF_MAC_ADDRESS,
8+
CONF_SERVICE_UUID,
9+
CONF_TRIGGER_ID,
10+
CONF_ON_VALUE,
11+
)
12+
13+
CODEOWNERS = ["@myhomeiot"]
14+
DEPENDENCIES = ["myhomeiot_ble_host"]
15+
MULTI_CONF = True
16+
17+
CONF_BLE_HOST = "ble_host"
18+
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
19+
20+
myhomeiot_ble_client_ns = cg.esphome_ns.namespace("myhomeiot_ble_client")
21+
MyHomeIOT_BLEClient = myhomeiot_ble_client_ns.class_(
22+
"MyHomeIOT_BLEClient", cg.Component
23+
)
24+
25+
# Triggers
26+
MyHomeIOT_BLEClientValueTrigger = myhomeiot_ble_client_ns.class_(
27+
"MyHomeIOT_BLEClientValueTrigger",
28+
automation.Trigger.template(cg.std_vector.template(cg.uint8)),
29+
)
30+
31+
CONFIG_SCHEMA = (
32+
cv.Schema(
33+
{
34+
cv.GenerateID(): cv.declare_id(MyHomeIOT_BLEClient),
35+
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
36+
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
37+
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
38+
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
39+
{
40+
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MyHomeIOT_BLEClientValueTrigger),
41+
}
42+
),
43+
}
44+
)
45+
.extend(cv.COMPONENT_SCHEMA)
46+
.extend(cv.polling_component_schema("60min"))
47+
.extend(myhomeiot_ble_host.BLE_CLIENT_SCHEMA)
48+
)
49+
50+
def to_code(config):
51+
var = cg.new_Pvariable(config[CONF_ID])
52+
53+
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
54+
cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])))
55+
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
56+
cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])))
57+
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
58+
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
59+
cg.add(var.set_service_uuid128(uuid128))
60+
61+
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
62+
cg.add(var.set_char_uuid16(esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])))
63+
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
64+
cg.add(var.set_char_uuid32(esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])))
65+
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
66+
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_CHARACTERISTIC_UUID])
67+
cg.add(var.set_char_uuid128(uuid128))
68+
69+
yield cg.register_component(var, config)
70+
yield myhomeiot_ble_host.register_ble_client(var, config)
71+
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
72+
73+
for conf in config.get(CONF_ON_VALUE, []):
74+
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
75+
yield automation.build_automation(trigger, [(cg.std_vector.template(cg.uint8), "x")], conf)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#pragma once
2+
3+
#include "esphome/core/automation.h"
4+
#include "myhomeiot_ble_client.h"
5+
6+
#ifdef ARDUINO_ARCH_ESP32
7+
8+
namespace esphome {
9+
namespace myhomeiot_ble_client {
10+
11+
class MyHomeIOT_BLEClientValueTrigger : public Trigger<std::vector<uint8_t>> {
12+
public:
13+
explicit MyHomeIOT_BLEClientValueTrigger(MyHomeIOT_BLEClient *parent) {
14+
parent->add_on_state_callback([this](std::vector<uint8_t> value) { this->trigger(value); });
15+
}
16+
};
17+
18+
} // namespace myhomeiot_ble_client
19+
} // namespace esphome
20+
21+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
#include "myhomeiot_ble_client.h"
2+
#include "esphome/core/log.h"
3+
4+
#ifdef ARDUINO_ARCH_ESP32
5+
6+
#include <esp_gap_ble_api.h>
7+
8+
namespace esphome {
9+
namespace myhomeiot_ble_client {
10+
11+
static const char *TAG = "myhomeiot_ble_client";
12+
13+
void MyHomeIOT_BLEClient::setup() {
14+
this->state_ = esp32_ble_tracker::ClientState::Idle;
15+
}
16+
17+
void MyHomeIOT_BLEClient::dump_config() {
18+
ESP_LOGCONFIG(TAG, "MyHomeIOT BLE Client");
19+
ESP_LOGCONFIG(TAG, " MAC address: %s", to_string(this->address).c_str());
20+
ESP_LOGCONFIG(TAG, " Service UUID: %s", this->service_uuid_.to_string().c_str());
21+
ESP_LOGCONFIG(TAG, " Characteristic UUID: %s", this->char_uuid_.to_string().c_str());
22+
LOG_UPDATE_INTERVAL(this);
23+
}
24+
25+
void MyHomeIOT_BLEClient::loop() {
26+
if (this->state_ == esp32_ble_tracker::ClientState::Discovered)
27+
this->connect();
28+
else if (this->state_ == esp32_ble_tracker::ClientState::Established)
29+
this->disconnect();
30+
}
31+
32+
void MyHomeIOT_BLEClient::connect() {
33+
ESP_LOGI(TAG, "[%s] Connecting", to_string(this->address).c_str());
34+
if (auto status = esp_ble_gattc_open(ble_host_->gattc_if, this->remote_bda, BLE_ADDR_TYPE_PUBLIC, true))
35+
{
36+
ESP_LOGW(TAG, "[%s] open error, status (%d)", to_string(this->address).c_str(), status);
37+
report_error(esp32_ble_tracker::ClientState::Idle);
38+
}
39+
else
40+
this->state_ = esp32_ble_tracker::ClientState::Connecting;
41+
}
42+
43+
void MyHomeIOT_BLEClient::disconnect() {
44+
ESP_LOGI(TAG, "[%s] Disconnecting", to_string(this->address).c_str());
45+
this->state_ = esp32_ble_tracker::ClientState::Idle;
46+
if (auto status = esp_ble_gattc_close(ble_host_->gattc_if, this->conn_id))
47+
ESP_LOGW(TAG, "[%s] close error, status (%d)", to_string(this->address).c_str(), status);
48+
}
49+
50+
void MyHomeIOT_BLEClient::update() {
51+
this->is_update_requested = true;
52+
}
53+
54+
void MyHomeIOT_BLEClient::report_results(uint8_t *data, uint16_t len) {
55+
this->status_clear_warning();
56+
std::vector<uint8_t> ret(data, data + len);
57+
this->callback_.call(ret);
58+
this->is_update_requested = false;
59+
}
60+
61+
void MyHomeIOT_BLEClient::report_error(esp32_ble_tracker::ClientState state) {
62+
this->state_ = state;
63+
this->status_set_warning();
64+
}
65+
66+
bool MyHomeIOT_BLEClient::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
67+
if (!this->is_update_requested || this->state_ != esp32_ble_tracker::ClientState::Idle
68+
|| device.address_uint64() != this->address)
69+
return false;
70+
71+
ESP_LOGD(TAG, "[%s] Found device", device.address_str().c_str());
72+
memcpy(this->remote_bda, device.address(), sizeof(this->remote_bda));
73+
this->state_ = esp32_ble_tracker::ClientState::Discovered;
74+
return true;
75+
}
76+
77+
void MyHomeIOT_BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
78+
esp_ble_gattc_cb_param_t *param) {
79+
switch (event) {
80+
case ESP_GATTC_OPEN_EVT: {
81+
if (memcmp(param->open.remote_bda, this->remote_bda, sizeof(this->remote_bda)) != 0)
82+
break;
83+
if (param->open.status != ESP_GATT_OK) {
84+
ESP_LOGW(TAG, "[%s] OPEN_EVT failed, status (%d), app_id (%d)", to_string(this->address).c_str(),
85+
param->open.status, ble_host_->app_id);
86+
report_error(esp32_ble_tracker::ClientState::Idle);
87+
break;
88+
}
89+
ESP_LOGI(TAG, "[%s] Connected successfully, app_id (%d)", to_string(this->address).c_str(), ble_host_->app_id);
90+
this->conn_id = param->open.conn_id;
91+
if (auto status = esp_ble_gattc_send_mtu_req(ble_host_->gattc_if, param->open.conn_id))
92+
{
93+
ESP_LOGW(TAG, "[%s] send_mtu_req failed, status (%d)", to_string(this->address).c_str(), status);
94+
report_error();
95+
break;
96+
}
97+
this->start_handle = this->end_handle = this->char_handle = ESP_GATT_ILLEGAL_HANDLE;
98+
this->state_ = esp32_ble_tracker::ClientState::Connected;
99+
break;
100+
}
101+
case ESP_GATTC_CFG_MTU_EVT: {
102+
if (param->cfg_mtu.conn_id != this->conn_id)
103+
break;
104+
if (param->cfg_mtu.status != ESP_GATT_OK) {
105+
ESP_LOGW(TAG, "[%s] CFG_MTU_EVT failed, status (%d)", to_string(this->address).c_str(),
106+
param->cfg_mtu.status);
107+
report_error();
108+
break;
109+
}
110+
ESP_LOGV(TAG, "[%s] CFG_MTU_EVT, MTU (%d)", to_string(this->address).c_str(), param->cfg_mtu.mtu);
111+
if (auto status = esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, NULL)) {
112+
ESP_LOGW(TAG, "[%s] search_service failed, status (%d)", to_string(this->address).c_str(), status);
113+
report_error();
114+
}
115+
break;
116+
}
117+
case ESP_GATTC_DISCONNECT_EVT: {
118+
if (memcmp(param->disconnect.remote_bda, this->remote_bda, sizeof(this->remote_bda)) != 0)
119+
break;
120+
ESP_LOGD(TAG, "[%s] DISCONNECT_EVT", to_string(this->address).c_str());
121+
this->state_ = esp32_ble_tracker::ClientState::Idle;
122+
break;
123+
}
124+
case ESP_GATTC_SEARCH_RES_EVT: {
125+
if (param->search_res.conn_id != this->conn_id)
126+
break;
127+
esp32_ble_tracker::ESPBTUUID uuid = param->search_res.srvc_id.uuid.len == ESP_UUID_LEN_16 ? esp32_ble_tracker::ESPBTUUID::from_uint16(param->search_res.srvc_id.uuid.uuid.uuid16)
128+
: param->search_res.srvc_id.uuid.len == ESP_UUID_LEN_32 ? esp32_ble_tracker::ESPBTUUID::from_uint32(param->search_res.srvc_id.uuid.uuid.uuid32)
129+
: esp32_ble_tracker::ESPBTUUID::from_raw(param->search_res.srvc_id.uuid.uuid.uuid128);
130+
if (uuid == this->service_uuid_) {
131+
ESP_LOGD(TAG, "[%s] SEARCH_RES_EVT service (%s) found", to_string(this->address).c_str(),
132+
this->service_uuid_.to_string().c_str());
133+
start_handle = param->search_res.start_handle;
134+
end_handle = param->search_res.end_handle;
135+
}
136+
break;
137+
}
138+
case ESP_GATTC_SEARCH_CMPL_EVT: {
139+
if (param->search_cmpl.conn_id != this->conn_id)
140+
break;
141+
ESP_LOGV(TAG, "[%s] SEARCH_CMPL_EVT", to_string(this->address).c_str());
142+
143+
if (this->start_handle == ESP_GATT_ILLEGAL_HANDLE)
144+
{
145+
ESP_LOGE(TAG, "[%s] SEARCH_CMPL_EVT service (%s) not found", to_string(this->address).c_str(),
146+
this->service_uuid_.to_string().c_str());
147+
report_error();
148+
break;
149+
}
150+
151+
uint16_t offset = 0;
152+
esp_gattc_char_elem_t result;
153+
while (true) {
154+
uint16_t count = 1;
155+
auto status = esp_ble_gattc_get_all_char(ble_host_->gattc_if, this->conn_id,
156+
this->start_handle, this->end_handle, &result, &count, offset);
157+
if (status != ESP_GATT_OK) {
158+
if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND)
159+
break;
160+
ESP_LOGW(TAG, "[%s] get_all_char error, status (%d)", to_string(this->address).c_str(), status);
161+
report_error();
162+
break;
163+
}
164+
if (count == 0)
165+
break;
166+
167+
if (this->char_uuid_ == esp32_ble_tracker::ESPBTUUID::from_uuid(result.uuid)) {
168+
ESP_LOGD(TAG, "[%s] SEARCH_CMPL_EVT char (%s) found", to_string(this->address).c_str(),
169+
this->char_uuid_.to_string().c_str());
170+
this->char_handle = result.char_handle;
171+
172+
if (auto status = esp_ble_gattc_read_char(ble_host_->gattc_if, this->conn_id,
173+
this->char_handle, ESP_GATT_AUTH_REQ_NONE) != ESP_GATT_OK) {
174+
ESP_LOGW(TAG, "[%s] read_char error sending read request, status (%d)",
175+
to_string(this->address).c_str(), status);
176+
this->char_handle = 0;
177+
}
178+
break;
179+
}
180+
offset++;
181+
}
182+
if (this->char_handle == ESP_GATT_ILLEGAL_HANDLE)
183+
{
184+
ESP_LOGE(TAG, "[%s] SEARCH_CMPL_EVT char (%s) not found", to_string(this->address).c_str(),
185+
this->char_uuid_.to_string().c_str());
186+
report_error();
187+
}
188+
break;
189+
}
190+
case ESP_GATTC_READ_CHAR_EVT: {
191+
if (param->read.conn_id != this->conn_id || param->read.handle != this->char_handle)
192+
break;
193+
if (param->read.status != ESP_GATT_OK)
194+
{
195+
ESP_LOGW(TAG, "[%s] READ_CHAR_EVT error reading char at handle (%d), status (%d)", to_string(this->address).c_str(),
196+
param->read.handle, param->read.status);
197+
report_error();
198+
break;
199+
}
200+
201+
report_results(param->read.value, param->read.value_len);
202+
this->state_ = esp32_ble_tracker::ClientState::Established;
203+
break;
204+
}
205+
default:
206+
break;
207+
}
208+
}
209+
210+
std::string MyHomeIOT_BLEClient::to_string(uint64_t address) const {
211+
char buffer[20];
212+
sprintf(buffer, "%02X:%02X:%02X:%02X:%02X:%02X", (uint8_t) (address >> 40), (uint8_t) (address >> 32),
213+
(uint8_t) (address >> 24), (uint8_t) (address >> 16), (uint8_t) (address >> 8), (uint8_t) (address >> 0));
214+
return std::string(buffer);
215+
}
216+
217+
} // namespace myhomeiot_ble_client
218+
} // namespace esphome
219+
220+
#endif

0 commit comments

Comments
 (0)