This commit is contained in:
Jonas Rabenstein 2026-01-04 19:19:00 +01:00
commit e797cdb349
6 changed files with 104 additions and 33 deletions

View file

@ -0,0 +1,35 @@
#pragma once
#include <algorithm>
#include <array>
#include <span>
#include "esphome/core/helpers.h"
namespace esphome {
namespace brmesh {
class Address : public std::array<uint8_t, 3> {
static constexpr uint8_t reverse(uint8_t x) {
x = ((x & 0xAA) >> 1) | ((x & 0x55) << 1);
x = ((x & 0xCC) >> 2) | ((x & 0x33) << 2);
x = ((x & 0xF0) >> 4) | ((x & 0x0F) << 4);
return x;
}
public:
constexpr Address(const uint8_t a, const uint8_t b, const uint8_t c)
: array{reverse(c), reverse(b), reverse(a)} {}
template <size_t size>
std::span<uint8_t> set(const std::span<uint8_t, size> &dst) const {
return set(std::span<uint8_t>(dst));
}
std::span<uint8_t> set(const std::span<uint8_t> &dst) const {
if (dst.size() < size())
return {};
std::copy(cbegin(), cend(), &dst[0]);
return dst.subspan(0, size());
}
};
} // namespace brmesh
} // namespace esphome

View file

@ -188,6 +188,24 @@ Light::advertise(bool advertise) {
network_.advertise(id_, {}); network_.advertise(id_, {});
} }
std::span<uint8_t>
Light::command(const std::span<uint8_t> &output) const
{
const auto hdr = output.subspan(0, 2);
const auto dst = output.subspan(hdr.size());
const auto src = std::span<const uint8_t>(payload_.cbegin(), payload_length_);
if (output.size() < hdr.size() + src.size())
return {};
hdr[0] = 2 | (((0xfffffff & (src.size() + 1)) << 4));
hdr[1] = id_.value();
std::copy(src.begin(), src.end(), dst.begin());
std::ranges::fill(dst.subspan(src.size()), 0);
return output;
}
void void
Light::loop() { Light::loop() {
disable_loop(); disable_loop();
@ -247,7 +265,7 @@ Light::write_state(light::LightState *state) {
count_ = 0; count_ = 0;
//ESP_LOGD(TAG, "%c%02hhX: state: %s", id_.kind(), id_.value(), //ESP_LOGD(TAG, "%c%02hhX: state: %s", id_.kind(), id_.value(),
// format_hex_pretty(&payload[0], payload.size()).c_str()); // format_hex_pretty(&payload[0], payload.size()).c_str());
enable_loop(); //enable_loop();
} }
} // namespace brmesh } // namespace brmesh

View file

@ -23,26 +23,24 @@ public:
class Id : public std::variant<Single, Group> { class Id : public std::variant<Single, Group> {
public: public:
using variant::variant; using variant::variant;
constexpr Id(uint8_t value) : variant(Single{value}) {}
static constexpr char kind(Single) { return 'L'; } static constexpr char kind(Single) { return 'L'; }
static constexpr char kind(Group) { return 'G'; } static constexpr char kind(Group) { return 'G'; }
constexpr char kind() { constexpr char kind() const {
return std::visit([](auto &&value) { return Id::kind(value); }, *this); return std::visit([](auto &&value) { return Id::kind(value); }, *this);
} }
static constexpr uint8_t value(Single value) { return static_cast<uint8_t>(value); } static constexpr uint8_t value(Single value) { return static_cast<uint8_t>(value); }
static constexpr uint8_t value(Group value) { return static_cast<uint8_t>(value); } static constexpr uint8_t value(Group value) { return static_cast<uint8_t>(value); }
constexpr uint8_t value() { constexpr uint8_t value() const {
return std::visit([](auto &&value) { return Id::value(value); }, *this); return std::visit([](auto &&value) { return Id::value(value); }, *this);
} }
}; };
Light(Id id, Network *network) : id_(id), network_(*network) {} Light(Id id, Network *network) : id_(id), network_(*network) {}
Light(Single id, Network *network) : Light(Id{id}, network) {}
// Light(Id id, const Key &key, uint16_t adv_interval_min, uint16_t adv_interval_max)
// : id_(id), key_(key),
// adv_interval_min_(adv_interval_min / 0.625f), adv_interval_max_(adv_interval_max / 0.625f) {}
//
// interface: Component // interface: Component
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { float get_setup_priority() const override {
@ -59,31 +57,16 @@ public:
// callback // callback
void advertise(bool advertise); void advertise(bool advertise);
std::span<uint8_t> command(const std::span<uint8_t> &output) const;
private: private:
Id id_; Id id_;
Network &network_; Network &network_;
#if 0
light::LightState *state_ = nullptr;
const Key key_;
const uint16_t adv_interval_min_;
const uint16_t adv_interval_max_;
static constexpr esp_power_level_t tx_power_{};
uint8_t seq_ = 0;
#endif
uint8_t count_ = 0; uint8_t count_ = 0;
bool advertising_ = false; bool advertising_ = false;
std::array<uint8_t, 6> payload_ = {}; std::array<uint8_t, 6> payload_ = {};
size_t payload_length_ = 0; size_t payload_length_ = 0;
#if 0
void adv_event_(esp_ble_gap_cb_param_t::ble_adv_data_raw_cmpl_evt_param &);
void adv_event_(esp_ble_gap_cb_param_t::ble_adv_start_cmpl_evt_param &);
void adv_event_(esp_ble_gap_cb_param_t::ble_adv_stop_cmpl_evt_param &);
static std::span<uint8_t> adv_data_(const std::span<uint8_t, 31> &, uint8_t seq, light::LightState &);
#endif
}; };
} // namespace brmesh } // namespace brmesh

View file

@ -3,7 +3,7 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.cpp_generator import LambdaExpression from esphome.cpp_generator import LambdaExpression
from esphome.const import CONF_LIGHT_ID, CONF_OUTPUT_ID from esphome.const import CONF_LIGHT_ID, CONF_OUTPUT_ID, CONF_REPEAT
from esphome.core import HexInt from esphome.core import HexInt
from esphome.components import light, esp32_ble from esphome.components import light, esp32_ble
@ -15,14 +15,14 @@ AUTO_LOAD = ["light"]
CONF_NETWORK_ID = "network" CONF_NETWORK_ID = "network"
Light = brmesh_ns.class_("Light", light.LightOutput, cg.Component) Light = brmesh_ns.class_("Light", light.LightOutput, cg.Component)
Single = Light.enum("Single", True) Single = Light.enum('Single', is_class=True)
Group = Light.enum("Group", True);
CONFIG_SCHEMA = light.light_schema(Light, light.LightType.RGB).extend({ CONFIG_SCHEMA = light.light_schema(Light, light.LightType.RGB).extend({
cv.Required(CONF_NETWORK_ID): cv.use_id(Network), cv.Required(CONF_NETWORK_ID): cv.use_id(Network),
cv.Required(CONF_LIGHT_ID): cv.int_range(min=1, max=255), cv.Required(CONF_LIGHT_ID): cv.int_range(min=1, max=255),
# TODO: use CONF_BLE_ID from CONF_NETWORK_ID... # TODO: use CONF_BLE_ID from CONF_NETWORK_ID...
cv.GenerateID(esp32_ble.CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE), cv.GenerateID(esp32_ble.CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE),
cv.Optional(CONF_REPEAT, default=5): cv.positive_not_null_int,
}) })

View file

@ -1,5 +1,6 @@
#include "network.h" #include "network.h"
#include "address.h"
#include "light.h" #include "light.h"
#include "whitening.h" #include "whitening.h"
@ -132,8 +133,9 @@ std::span<uint8_t> command(const std::span<uint8_t> &buffer, const Key &key, Lig
} }
std::span<uint8_t> payload(const std::span<uint8_t> &buffer, const Key &key, Light::Id id, uint8_t seq, const std::span<uint8_t> &light_data) { std::span<uint8_t> payload(const std::span<uint8_t> &buffer, const Key &key, Light::Id id, uint8_t seq, const std::span<uint8_t> &light_data) {
constexpr std::array<uint8_t, 3> ADDRESS = { 0xC3, 0x43, 0x83 }; constexpr Address ADDRESS = { 0xC1, 0xC2, 0xC3 };
constexpr std::array<uint8_t, 3> PREFIX = { 0x71, 0x0F, 0x55 }; //constexpr std::array<uint8_t, 3> PREFIX = { 0x71, 0x0F, 0x55 };
constexpr std::array<uint8_t, 3> PREFIX = { 0x8E, 0xF0, 0xAA };
const auto prefix = subspan(buffer, 0, PREFIX.size()); const auto prefix = subspan(buffer, 0, PREFIX.size());
if (prefix.empty()) if (prefix.empty())
@ -175,8 +177,8 @@ std::span<uint8_t> payload(const std::span<uint8_t> &buffer, const Key &key, Lig
//ESP_LOGV(TAG, "%c%02hhX: raw: %s", id.kind(), id.value(), //ESP_LOGV(TAG, "%c%02hhX: raw: %s", id.kind(), id.value(),
// format_hex_pretty(&result[0], result.size()).c_str()); // format_hex_pretty(&result[0], result.size()).c_str());
for (auto &byte:prefix) //for (auto &byte:prefix)
byte = reverse_bits(byte); // byte = reverse_bits(byte);
//ESP_LOGV(TAG, "%c%02hhX: reverse: %s", id.kind(), id.value(), //ESP_LOGV(TAG, "%c%02hhX: reverse: %s", id.kind(), id.value(),
// format_hex_pretty(&result[0], result.size()).c_str()); // format_hex_pretty(&result[0], result.size()).c_str());
@ -254,11 +256,9 @@ Network::advertise(Light::Id id, const std::span<uint8_t> &payload) {
return; return;
auto err = ESP_OK; auto err = ESP_OK;
#if 0
err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, tx_power_); err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, tx_power_);
if (err != ESP_OK) if (err != ESP_OK)
ESP_LOGW(TAG, "%c%02hhX: tx power set: %s", id.kind(), id.value(), esp_err_to_name(err)); ESP_LOGW(TAG, "%c%02hhX: tx power set: %s", id.kind(), id.value(), esp_err_to_name(err));
#endif
advertise_ = true; advertise_ = true;
err = esp_ble_gap_config_adv_data_raw(&data.front(), data.size()); err = esp_ble_gap_config_adv_data_raw(&data.front(), data.size());

View file

@ -0,0 +1,35 @@
#pragma once
namespace esphome {
namespace brmesh {
class Output : public Component, public light::LightOutput
{
public:
virtual const char *kind() const = 0;
virtual uint8_t id() const = 0;
virtual std::span<uint8_t> command(const std::span<uint8_t> &output) const = 0;
virtual void advertise(bool advertise) const;
// interface: Component
void dump_config() override;
float get_setup_priority() const override {
return setup_priority::AFTER_BLUETOOTH;
}
void setup() override;
void loop() override;
// interface: LightOutput
light::LightTraits get_traits() override;
void setup_state(light::LightState *state) override;
void update_state(light::LightState *state) override;
void write_state(light::LightState *state) override;
private:
Network &network_;
};
} // namespace brmesh
} // namespace esphome