2 Commits

Author SHA1 Message Date
Sebastian Muszynski
8c8f77f09a Fix 2025-07-05 21:12:10 +02:00
Sebastian Muszynski
c42093a466 Add controls 2025-07-05 21:07:50 +02:00
9 changed files with 228 additions and 6 deletions

View File

@@ -14,6 +14,12 @@ VotronicBle = votronic_ble_ns.class_(
"VotronicBle", ble_client.BLEClientNode, cg.PollingComponent
)
VOTRONIC_BLE_COMPONENT_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_VOTRONIC_BLE_ID): cv.use_id(VotronicBle),
}
)
CONFIG_SCHEMA = (
cv.Schema(
{

View File

@@ -0,0 +1,102 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import CONF_ICON, CONF_ID
from .. import CONF_VOTRONIC_BLE_ID, VOTRONIC_BLE_COMPONENT_SCHEMA, votronic_ble_ns
DEPENDENCIES = ["votronic_ble"]
CODEOWNERS = ["@syssi"]
CONF_RETRIEVE_SETTINGS = "retrieve_settings"
CONF_RETRIEVE_DEVICE_INFO = "retrieve_device_info"
CONF_RETRIEVE_DAILY_OPERATING_STATISTICS = "retrieve_daily_operating_statistics"
CONF_RETRIEVE_WEEKLY_OPERATING_STATISTICS = "retrieve_weekly_operating_statistics"
CONF_RETRIEVE_MONTHLY_OPERATING_STATISTICS = "retrieve_monthly_operating_statistics"
CONF_RETRIEVE_OPERATING_STATISTICS = "retrieve_operating_statistics"
CONF_RESET_ACCUMULATOR = "reset_accumulator"
ICON_RETRIEVE_DAILY_OPERATING_STATISTICS = "mdi:chart-box-outline"
ICON_RETRIEVE_WEEKLY_OPERATING_STATISTICS = "mdi:chart-box-outline"
ICON_RETRIEVE_MONTHLY_OPERATING_STATISTICS = "mdi:chart-box-outline"
ICON_RETRIEVE_OPERATING_STATISTICS = "mdi:chart-box-outline"
ICON_RESET_ACCUMULATOR = "mdi:history"
BUTTONS = {
CONF_RETRIEVE_DAILY_OPERATING_STATISTICS: 0x01,
CONF_RETRIEVE_WEEKLY_OPERATING_STATISTICS: 0x02,
CONF_RETRIEVE_MONTHLY_OPERATING_STATISTICS: 0x03,
CONF_RETRIEVE_OPERATING_STATISTICS: 0x04,
CONF_RESET_ACCUMULATOR: 0x06,
}
VotronicButton = votronic_ble_ns.class_("VotronicButton", button.Button, cg.Component)
CONFIG_SCHEMA = VOTRONIC_BLE_COMPONENT_SCHEMA.extend(
{
cv.Optional(
CONF_RETRIEVE_DAILY_OPERATING_STATISTICS
): button.BUTTON_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(VotronicButton),
cv.Optional(
CONF_ICON, default=ICON_RETRIEVE_DAILY_OPERATING_STATISTICS
): cv.icon,
}
).extend(
cv.COMPONENT_SCHEMA
),
cv.Optional(
CONF_RETRIEVE_WEEKLY_OPERATING_STATISTICS
): button.BUTTON_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(VotronicButton),
cv.Optional(
CONF_ICON, default=ICON_RETRIEVE_WEEKLY_OPERATING_STATISTICS
): cv.icon,
}
).extend(
cv.COMPONENT_SCHEMA
),
cv.Optional(
CONF_RETRIEVE_MONTHLY_OPERATING_STATISTICS
): button.BUTTON_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(VotronicButton),
cv.Optional(
CONF_ICON, default=ICON_RETRIEVE_MONTHLY_OPERATING_STATISTICS
): cv.icon,
}
).extend(
cv.COMPONENT_SCHEMA
),
cv.Optional(CONF_RETRIEVE_OPERATING_STATISTICS): button.BUTTON_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(VotronicButton),
cv.Optional(
CONF_ICON, default=ICON_RETRIEVE_OPERATING_STATISTICS
): cv.icon,
}
).extend(cv.COMPONENT_SCHEMA),
cv.Optional(CONF_RESET_ACCUMULATOR): button.BUTTON_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(VotronicButton),
cv.Optional(CONF_ICON, default=ICON_RESET_ACCUMULATOR): cv.icon,
}
).extend(cv.COMPONENT_SCHEMA),
}
)
async def to_code(config):
hub = await cg.get_variable(config[CONF_VOTRONIC_BLE_ID])
for key, address in BUTTONS.items():
if key in config:
conf = config[key]
var = cg.new_Pvariable(conf[CONF_ID])
await cg.register_component(var, conf)
await button.register_button(var, conf)
cg.add(var.set_parent(hub))
cg.add(var.set_holding_register(address))

View File

@@ -0,0 +1,14 @@
#include "votronic_button.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace votronic_ble {
static const char *const TAG = "votronic_ble.button";
void VotronicButton::dump_config() { LOG_BUTTON("", "VotronicBle Button", this); }
void VotronicButton::press_action() { this->parent_->send_command(this->holding_register_); }
} // namespace votronic_ble
} // namespace esphome

View File

@@ -0,0 +1,26 @@
#pragma once
#include "../votronic_ble.h"
#include "esphome/core/component.h"
#include "esphome/components/button/button.h"
namespace esphome {
namespace votronic_ble {
class VotronicBle;
class VotronicButton : public button::Button, public Component {
public:
void set_parent(VotronicBle *parent) { this->parent_ = parent; };
void set_holding_register(uint8_t holding_register) { this->holding_register_ = holding_register; };
void dump_config() override;
void loop() override {}
float get_setup_priority() const override { return setup_priority::DATA; }
protected:
void press_action() override;
VotronicBle *parent_;
uint8_t holding_register_;
};
} // namespace votronic_ble
} // namespace esphome

View File

@@ -58,6 +58,26 @@ void VotronicBle::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
// [60:A4:23:91:8F:55] ESP_GATTC_SEARCH_CMPL_EVT
// [60:A4:23:91:8F:55] Service UUID: 0x1801
// [60:A4:23:91:8F:55] start_handle: 0x1 end_handle: 0x4
// [60:A4:23:91:8F:55] Service UUID: 0x1800
// [60:A4:23:91:8F:55] start_handle: 0x5 end_handle: 0x9
// [60:A4:23:91:8F:55] Service UUID: 0x180A
// [60:A4:23:91:8F:55] start_handle: 0xa end_handle: 0x10
// [60:A4:23:91:8F:55] Service UUID: 1D14D6EE-FD63-4FA1-BFA4-8F47B42119F0
// [60:A4:23:91:8F:55] start_handle: 0x11 end_handle: 0x13
// [60:A4:23:91:8F:55] Service UUID: D0CB6AA7-8548-46D0-99F8-2D02611E5270
// [60:A4:23:91:8F:55] start_handle: 0x14 end_handle: 0x21
// [60:A4:23:91:8F:55] Service UUID: AE64A924-1184-4554-8BBC-295DB9F2324A
// [60:A4:23:91:8F:55] start_handle: 0x22 end_handle: 0xffff
// [60:A4:23:91:8F:55] characteristic 9A082A4E-5BCC-4B1D-9958-A97CFCCFA5EC, handle 0x16, properties 0x12
// [60:A4:23:91:8F:55] characteristic 971CCEC2-521D-42FD-B570-CF46FE5CEB65, handle 0x19, properties 0x12
// [60:A4:23:91:8F:55] characteristic 9E298E7F-2594-49DE-BE51-39153A6250E4, handle 0x1c, properties 0xa
// [60:A4:23:91:8F:55] characteristic CFA6E099-FA0F-43AA-88D2-4508B986A67F, handle 0x1e, properties 0xa
// [60:A4:23:91:8F:55] characteristic D2296045-A715-4458-850F-0800C7E11CEC, handle 0x20, properties 0x1a
// [60:A4:23:91:8F:55] gattc_event_handler: event=18 gattc_if=3
// [60:A4:23:91:8F:55] cfg_mtu status 0, mtu 247
auto *char_battery_computer =
this->parent_->get_characteristic(this->service_monitoring_uuid_, this->char_battery_computer_uuid_);
if (char_battery_computer == nullptr) {
@@ -88,6 +108,14 @@ void VotronicBle::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status2);
}
auto *char_management =
this->parent_->get_characteristic(this->service_monitoring_uuid_, this->char_management_uuid_);
if (char_management == nullptr) {
ESP_LOGW(TAG, "[%s] No management characteristic found at device.", this->parent_->address_str().c_str());
break;
}
this->char_management_handle_ = char_management->handle;
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
@@ -115,6 +143,23 @@ void VotronicBle::update() {
}
}
bool VotronicBle::send_command(uint8_t command) {
ESP_LOGD(TAG, "Send command: 0x%02X", command);
uint8_t frame[1];
frame[0] = command;
auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
this->char_management_handle_, sizeof(frame), frame,
ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
}
return (status == 0);
}
void VotronicBle::on_votronic_ble_data(const uint8_t &handle, const std::vector<uint8_t> &data) {
if (handle == this->char_solar_charger_handle_) {
this->decode_solar_charger_data_(data);

View File

@@ -83,6 +83,7 @@ class VotronicBle : public esphome::ble_client::BLEClientNode, public PollingCom
void on_votronic_ble_data(const uint8_t &handle, const std::vector<uint8_t> &data);
void set_throttle(uint32_t throttle) { this->throttle_ = throttle; }
bool send_command(uint8_t command);
protected:
binary_sensor::BinarySensor *charging_binary_sensor_;
@@ -111,6 +112,7 @@ class VotronicBle : public esphome::ble_client::BLEClientNode, public PollingCom
uint16_t char_battery_computer_handle_{0x22};
uint16_t char_solar_charger_handle_{0x25};
uint16_t char_management_handle_{0x24};
uint32_t last_battery_computer_data_{0};
uint32_t last_solar_charger_data_{0};
uint32_t throttle_;
@@ -123,12 +125,12 @@ class VotronicBle : public esphome::ble_client::BLEClientNode, public PollingCom
esp32_ble_tracker::ESPBTUUID::from_raw("ae64a924-1184-4554-8bbc-295db9f2324a");
esp32_ble_tracker::ESPBTUUID char_battery_computer_uuid_ =
esp32_ble_tracker::ESPBTUUID::from_raw("9a082a4e-5bcc-4b1d-9958-a97cfccfa5ec");
esp32_ble_tracker::ESPBTUUID::from_raw("9a082a4e-5bcc-4b1d-9958-a97cfccfa5ec"); // Handle 0x16 (22)
esp32_ble_tracker::ESPBTUUID char_solar_charger_uuid_ =
esp32_ble_tracker::ESPBTUUID::from_raw("971ccec2-521d-42fd-b570-cf46fe5ceb65");
esp32_ble_tracker::ESPBTUUID::from_raw("971ccec2-521d-42fd-b570-cf46fe5ceb65"); // Handle 0x19 (25)
esp32_ble_tracker::ESPBTUUID char_management_uuid_ =
esp32_ble_tracker::ESPBTUUID::from_raw("ac12f485-cab7-4e0a-aac5-3585918852f6");
esp32_ble_tracker::ESPBTUUID::from_raw("ac12f485-cab7-4e0a-aac5-3585918852f6"); // Handle 0x24 (36)
esp32_ble_tracker::ESPBTUUID char_bulk_data_uuid_ =
esp32_ble_tracker::ESPBTUUID::from_raw("b8a37ffe-c57b-4007-b3c1-ca05a6b7f0c6");

View File

@@ -1,7 +1,7 @@
substitutions:
name: votronic
device_description: "Monitor a votronic device via BLE"
external_components_source: github://syssi/esphome-votronic@drop-patched-ble-stack
external_components_source: github://syssi/esphome-votronic@add-controls
mac_address: 60:A4:23:91:8F:55
esphome:
@@ -76,6 +76,20 @@ binary_sensor:
aes_active:
name: "${name} aes active"
button:
- platform: votronic_ble
votronic_ble_id: votronic0
retrieve_daily_operating_statistics:
name: "${name} retrieve daily operating statistics"
retrieve_weekly_operating_statistics:
name: "${name} retrieve weekly operating statistics"
retrieve_monthly_operating_statistics:
name: "${name} retrieve monthly operating statistics"
retrieve_operating_statistics:
name: "${name} retrieve operating statistics"
reset_accumulator:
name: "${name} reset accumulator"
sensor:
- platform: votronic_ble
votronic_ble_id: votronic0

View File

@@ -1,3 +1,10 @@
#!/bin/bash
esphome -s external_components_source components ${1:-run} ${2:-esp32-ble-example-faker.yaml}
if [[ $2 == tests/* ]]; then
C="../components"
else
C="components"
fi
esphome -s external_components_source $C ${1:-run} ${2:-esp32-ble-example-faker.yaml}

View File

@@ -1,3 +1,9 @@
#!/bin/bash
esphome -s external_components_source components ${1:-run} ${2:-esp8266-solar-charger-example-faker.yaml}
if [[ $2 == tests/* ]]; then
C="../components"
else
C="components"
fi
esphome -s external_components_source $C ${1:-run} ${2:-esp8266-solar-charger-example-faker.yaml}