This commit is contained in:
Jonas Rabenstein 2025-12-23 10:10:00 +01:00
commit d10ec6546d
11 changed files with 0 additions and 715 deletions

View file

@ -1,275 +0,0 @@
#if 0
#include "esphome/core/component_iterator.h"
#include "esphome/core/log.h"
#include "esphome/components/esp32_ble/ble.h"
#include "controller.h"
#include "protocol.h"
namespace {
const char *const TAG = "brmesh.controller";
template <size_t addr_size, size_t data_size>
std::enable_if_t<(addr_size == 3 || addr_size == std::dynamic_extent), uint16_t>
crc16(const std::span<const uint8_t, addr_size> &addr, const std::span<const uint8_t, data_size> &data, const uint16_t init = 0xFFFF)
{
constexpr auto step = [](uint16_t result, uint8_t byte)->uint16_t{
result ^= static_cast<uint16_t>(byte) << 8;
for (int j = 0; j < 4; j++) {
uint16_t tmp = result << 1;
if (result & 0x8000)
tmp ^= 0x1021;
result = tmp << 1;
if (tmp & 0x8000)
result ^= 0x1021;
}
return result;
};
auto result = init;
for (const auto &c:addr)
result = step(result, c);
for (const uint8_t &c:data)
result = step(result, esphome::reverse_bits(c));
return ~esphome::reverse_bits(result);
}
}
namespace esphome {
namespace brmesh {
Controller::Controller(esp32_ble::ESP32BLE &ble,
const Key &key,
const uint16_t adv_interval_min,
const uint16_t adv_interval_max,
const uint16_t adv_duration,
const uint16_t adv_gap)
: ble_(ble), key_(key),
adv_interval_min_(adv_interval_min),
adv_interval_max_(adv_interval_max),
adv_duration_(adv_duration),
adv_gap_(adv_duration)
{
}
void Controller::setup()
{
ESP_LOGCONFIG(TAG, "BRmesh Controller:\n"
" key: %02hx:%02hx:%02hx:%02hx\n"
" adv interval: %hd-%hd\n"
" adv duration: %hdms\n"
" adv gap: %hdms",
key_[0], key_[1], key_[2], key_[3],
adv_interval_min_, adv_interval_max_,
adv_duration_,
adv_gap_);
ble_.register_gap_event_handler(this);
}
void Controller::loop()
{
auto *light = q_.peek();
disable_loop();
if (light == nullptr)
light = q_.peek();
if (light == nullptr)
return;
}
void Controller::advertise(Light &light)
{
esp_ble_adv_params_t adv_params = {
.adv_int_min = adv_interval_min_,
.adv_int_max = adv_interval_max_,
.adv_type = ADV_TYPE_NONCONN_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.peer_addr = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
.peer_addr_type = BLE_ADDR_TYPE_PUBLIC,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
std::array<uint8_t, 31> adv_data_raw[31];
uint8_t adv_data_len = 0;
// Add flags
adv_data_raw[adv_data_len++] = 2;
adv_data_raw[adv_data_len++] = ESP_BLE_AD_TYPE_FLAG;
adv_data_raw[adv_data_len++] = ESP_BLE_ADV_FLAG_BREDR_NOT_SPT | ESP_BLE_ADV_FLAG_GEN_DISC;
// Add manufacturer data
const auto cmd_data_len = adv_data_len++;
adv_data_raw[adv_data_len++] = ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE;
adv_data_raw[adv_data_len++] = MANUFACTURER_DATA_ID & 0xFF;
adv_data_raw[adv_data_len++] = (MANUFACTURER_DATA_ID >> 8) & 0xFF;
const auto cmd = command(std::span(adv_data_raw).subspan(adv_data_len), light);
adv_data_raw[cmd_data_len] = cmd.size_bytes();
adv_data_len += cmd.size_bytes();
esp_err_t err = esp_ble_gap_config_adv_data_raw(adv_data_raw, adv_data_len);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "Error setting raw advertisement data (err=%d): %s", err, esp_err_to_name(err));
return;
}
err = esp_ble_gap_start_advertising(&adv_params);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "Error starting advertisement (err=%d): %s", err, esp_err_to_name(err));
return;
}
ESP_LOGD(TAG, "advertise id=%hd", light.id());
}
void
Controller::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
ESP_LOD(TAG, "advertise: %hdms\n", adv_duration_);
set_timeout(adv_duration_, esp_ble_gap_stop_advertising);
return;
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
ESP_LOGD(TAG, "advertise gap: %hdms\n", adv_gap_);
set_timeout(adv_gap_, [this](){
q_.dequeue();
auto *light = q_.peek();
if (light == nullptr)
advertise(*light);
});
return;
default:
return;
}
}
void
Controller::enqueue(Light &light)
{
if (!q_.enqueue(light))
return;
defer([this](){ advertise(*q_.peek()); });
}
std::vector<uint8_t> Controller::single_control(uint32_t light_id_, const std::vector<uint8_t> &light_data)
{
std::vector<uint8_t> result_data(12);
result_data[0] = 2 | (((0xfffffff & (light_data.size() + 1)) << 4));
result_data[1] = light_id_;
std::copy(light_data.begin(), light_data.end(), result_data.begin() + 2);
// Debug output - print payload as hex
auto hex_str = vector_to_hex_string(result_data).data();
ESP_LOGD(TAG, "Inner Payload (%d bytes): %s", result_data.size(), hex_str);
return this->generate_command(5, light_id_, result_data, true);
}
std::vector<uint8_t> Controller::generate_command(uint8_t n, uint32_t light_id_, const std::vector<uint8_t> &data, bool forward)
{
uint8_t& sequence = seq_;
// Create command body with header
std::vector<uint8_t> body(data.size() + 4);
uint8_t i2 = (light_id_ / 256);
// Construct header
body[0] = (i2 & 0b1111) | ((n & 0b111) << 4) | (forward ? 0x80 : 0);
body[1] = sequence++; // Use and increment sequence number
if (sequence >= 255)
sequence = 1;
body[2] = this->key_[3]; // Safe key
// Copy data
std::copy(data.begin(), data.end(), body.begin() + 4);
// Calculate checksum
uint8_t checksum = 0;
for (size_t i = 0; i < body.size(); i++)
{
if (i != 3)
{
checksum = checksum + body[i];
}
}
body[3] = checksum;
// Encrypt header and data
for (size_t i = 0; i < 4; i++)
{
body[i] = DEFAULT_ENCRYPT_KEY[i & 3] ^ body[i];
}
for (size_t i = 0; i < data.size(); i++)
{
body[4 + i] = this->key_[i & 3] ^ body[4 + i];
}
// Prepare the final payload with RF protocol formatting
std::vector<uint8_t> addr = {DEFAULT_BLE_ADDRESS.begin(), DEFAULT_BLE_ADDRESS.end()};
return prepare_payload(addr, body);
}
std::span<uint8_t> Controller::payload(std::span<uint8_t, 24> &&span, const Light &light)
{
const auto header = span.subspan(0, 3);
header[0] = 0x71;
header[1] = 0x0f;
header[2] = 0x55;
const auto address = span.subspan(3, 3);
std::reverse_copy(address.begin(), address.end(),
DEFAULT_BLE_ADDRESS.cbegin(),
DEFAULT_BLE_ADDRESS.cend());
const auto data = command(span.subspan(6, 10), light);
const auto crc = span.subspan(6 + data.size(), 2);
const auto crc16 = ::crc16(address, data);
crc[0] = static_cast<uint8_t>(crc16);
crc[1] = static_cast<uint8_t>(crc16 >> 8);
auto result = span.subspan(0, header.size() + address.size() + data.size() + crc.size());
// TODO: adjust seed to skip zero byte warmup
Whitening whitening(0x25);
for (size_t i = 0; i< 16; ++i)
(void) whitening.encode(0x00);
whitening.encode(result);
return result;
}
std::span<uint8_t> command(std::span<uint8_t, 10> &&span, const Light &light)
{
static constexpr uint8_t n = 5; // TODO
static constexpr bool forward = true; // TODO
const auto inner = span.subpsna(4, light.command(span.subspan(4, 6)));
auto header = span.subspan(0, 4);
header[0] = (i2 & 0b1111) | ((n & 0b111) << 4) | (forward ? 0x80 : 0);
header[1] = seq_++;
header[2] = key_[3]; // safe key
header[3] = crc(inner);
DEFAULT_ENCRYPT_KEY(header);
key_(inner);
//prepare(DEFAULT_BLE_ADDRESS, span.subspan(4 + inner.size()));
return span.subspan(0, 4 + inner.size());
}
} // namespace brmesh
} // namespace esphome
#endif

View file

@ -1,78 +0,0 @@
#pragma once
#include <queue>
#include <mutex>
#include <vector>
#include "esphome/core/component.h"
#include "esphome/components/esp32_ble/ble.h"
#include "q.h"
#include "key.h"
namespace esphome {
namespace brmesh {
class Light;
class Controller : public Component, public esp32_ble::GAPEventHandler
{
public:
Controller(esp32_ble::ESP32BLE *ble, const Key &key, uint16_t adv_interval_min, uint16_t adv_interval_max, uint16_t adv_duration, uint16_t adv_gap)
: Controller(*ble, key, adv_interval_min, adv_interval_max, adv_duration, adv_gap) {}
Controller(esp32_ble::ESP32BLE &ble, const Key &key, uint16_t adv_interval_min, uint16_t adv_interval_max, uint16_t adv_duration, uint16_t adv_gap);
std::vector<uint8_t> single_control(uint32_t addr, const std::vector<uint8_t> &light_data);
void enqueue(Light &);
// interface: Component
void setup() override;
void loop() override;
// interface: GAPEventHandler
void gap_event_handler(esp_gap_ble_cb_event_t, esp_ble_gap_cb_param_t *);
protected:
struct Command
{
std::vector<uint8_t> data;
uint32_t timestamp;
uint8_t retries{0};
static constexpr uint8_t MAX_RETRIES = 3;
};
enum class AdvertiseState
{
IDLE,
ADVERTISING,
GAP
};
AdvertiseState adv_state_{AdvertiseState::IDLE};
uint32_t state_start_time_{0};
// Protocol implementation
std::vector<uint8_t> generate_command(uint8_t n, uint32_t light_id_, const std::vector<uint8_t> &data, bool forward = true);
static const uint16_t MANUFACTURER_DATA_ID = 0xfff0;
private:
esp32_ble::ESP32BLE &ble_;
Key key_;
uint16_t adv_interval_min_;
uint16_t adv_interval_max_;
uint16_t adv_duration_;
uint16_t adv_gap_;
Q<Light> q_ = {};
uint8_t seq_ = 0;
void advertise(Light &);
std::span<uint8_t> payload(std::span<uint8_t, 24> &span, const Light &light);
std::span<uint8_t> command(std::span<uint8_t, 24> &&span, const Light &light);
};
} // namespace brmesh
} // namespace esphome

View file

@ -1,43 +0,0 @@
#pragma once
#if 0
#include "esphome/core/log.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#ifdef USE_LOGGER
#include "esphome/components/logger/logger.h"
#endif
#include <span>
namespace esphome {
namespace brmesh {
namespace {
template <typename...types>
void hexdump(const unsigned level, const char *tag, unsigned line, const uint8_t *data, size_t size, const char *fmt, types&&...args) {
// TODO: conditional format_hex_pretty
const auto hex = format_hex_pretty(data, size);
ESP_LOGD(tag, fmt, std::forward<types>(args)..., hex.c_str());
}
template <typename...types>
void hexdump(const unsigned level, const char *tag, const void *data, size_t size, const char *fmt, types&&...args) {
hexdump(level, tag, static_cast<const uint8_t *>(data), size, fmt, std::forward<types>(args)...);
}
template <typename...types, typename base_type, size_t size>
void hexdump(const unsigned level, const char *tag, const std::span<base_type, size> &span, const char *fmt, types&&...args) {
hexdump(level, tag, static_cast<const void*>(&span.front()), span.size_bytes(), fmt, std::forward<types>(args)...);
}
template <typename...types>
void hexdump(const char *tag, types&&...args)
{
return hexdump(ESPHOME_LOG_LEVEL_DEBUG, tag, std::forward<types>(args)...);
}
} // namespace
} // namespace brmesh
} // namespace esphome
#endif

View file

@ -1,6 +1,5 @@
#include "light.h"
#include "debug.h"
#include "network.h"
#include "whitening.h"

View file

@ -1,6 +1,5 @@
#include "network.h"
#include "debug.h"
#include "light.h"
#include "whitening.h"

View file

@ -1,64 +0,0 @@
#include <algorithm>
#include "protocol.h"
#include "debug.h"
namespace {
const char *TAG = "fastcon.protocol";
}
namespace esphome
{
namespace brmesh
{
std::vector<uint8_t> get_rf_payload(const std::vector<uint8_t> &addr, const std::vector<uint8_t> &data)
{
const size_t data_offset = 0x12;
const size_t inverse_offset = 0x0f;
const size_t result_data_size = data_offset + addr.size() + data.size();
// Create result buffer including space for checksum
std::vector<uint8_t> resultbuf(result_data_size + 2, 0);
// Set hardcoded values
resultbuf[0x0f] = 0x71;
resultbuf[0x10] = 0x0f;
resultbuf[0x11] = 0x55;
// Copy address in reverse
for (size_t i = 0; i < addr.size(); i++)
{
resultbuf[data_offset + addr.size() - i - 1] = addr[i];
}
// Copy data
std::copy(data.begin(), data.end(), resultbuf.begin() + data_offset + addr.size());
// Reverse bytes in specified range
for (size_t i = inverse_offset; i < inverse_offset + addr.size() + 3; i++)
{
resultbuf[i] = reverse_8(resultbuf[i]);
}
// Add CRC
uint16_t crc = crc16(addr, data);
resultbuf[result_data_size] = crc & 0xFF;
resultbuf[result_data_size + 1] = (crc >> 8) & 0xFF;
return resultbuf;
}
std::vector<uint8_t> prepare_payload(const std::vector<uint8_t> &addr, const std::vector<uint8_t> &data)
{
auto payload = get_rf_payload(addr, data);
// Initialize whitening
WhiteningContext context;
whitening_init(0x25, context);
// Apply whitening to the payload
whitening_encode(payload, context);
// Return only the portion after 0xf bytes
return std::vector<uint8_t>(payload.begin() + 0xf, payload.end());
}
} // namespace brmesh
} // namespace esphome

View file

@ -1,18 +0,0 @@
#pragma once
#include <vector>
#include <array>
#include <cstdint>
#include "utils.h"
namespace esphome
{
namespace brmesh
{
static const std::array<uint8_t, 4> DEFAULT_ENCRYPT_KEY = {0x5e, 0x36, 0x7b, 0xc4};
static const std::array<uint8_t, 3> DEFAULT_BLE_FASTCON_ADDRESS = {0xC1, 0xC2, 0xC3};
std::vector<uint8_t> get_rf_payload(const std::vector<uint8_t> &addr, const std::vector<uint8_t> &data);
std::vector<uint8_t> prepare_payload(const std::vector<uint8_t> &addr, const std::vector<uint8_t> &data);
} // namespace brmesh
} // namespace esphome

View file

@ -1,72 +0,0 @@
#pragma once
namespace esphome {
namespace brmesh {
template <typename T>
class Q {
public:
class Node;
constexpr Q() = default;
bool enqueue(T &);
T *dequeue();
T *peek();
private:
Node *head_ = nullptr;
Node **tail_ = &head_;
};
template <typename T>
class Q<T>::Node {
public:
constexpr Node() = default;
bool enqueue(Node *&head, Node **&tail);
static Node *dequeue(Node *&head, Node **&tail);
private:
Node *next_ = nullptr;
};
template <typename T>
bool Q<T>::enqueue(T &node) {
return static_cast<Node&>(node).enqueue(head_, tail_);
}
template <typename T>
T *Q<T>::dequeue() {
auto *node = Node::dequeue(head_, tail_);
return dynamic_cast<T*>(node);
}
template <typename T>
T *Q<T>::peek() {
auto *node = head_;
return dynamic_cast<T*>(node);
}
template <typename T>
bool Q<T>::Node::enqueue(Node *&head, Node **&tail) {
const bool first = tail == &head;
*tail = this;
tail = &next_;
return first;
}
template <typename T>
Q<T>::Node *Q<T>::Node::dequeue(Node *&head, Node **&tail) {
auto *node = head;
if (node == nullptr)
return nullptr;
head = node->next_;
if (head == nullptr)
tail = &head;
return node;
}
} // namespace brmesh
} // namespace esphome

View file

@ -1,126 +0,0 @@
#include <vector>
#include <cstdio>
#include "esphome/core/log.h"
#include "utils.h"
namespace esphome
{
namespace brmesh
{
uint8_t reverse_8(uint8_t d)
{
uint8_t result = 0;
for (int i = 0; i < 8; i++)
{
result |= ((d >> i) & 1) << (7 - i);
}
return result;
}
uint16_t reverse_16(uint16_t d)
{
uint16_t result = 0;
for (int i = 0; i < 16; i++)
{
result |= ((d >> i) & 1) << (15 - i);
}
return result;
}
uint16_t crc16(const std::vector<uint8_t> &addr, const std::vector<uint8_t> &data)
{
uint16_t crc = 0xffff;
// Process address in reverse
for (auto it = addr.rbegin(); it != addr.rend(); ++it)
{
crc ^= (static_cast<uint16_t>(*it) << 8);
for (int j = 0; j < 4; j++)
{
uint16_t tmp = crc << 1;
if (crc & 0x8000)
{
tmp ^= 0x1021;
}
crc = tmp << 1;
if (tmp & 0x8000)
{
crc ^= 0x1021;
}
}
}
// Process data
for (size_t i = 0; i < data.size(); i++)
{
crc ^= (static_cast<uint16_t>(reverse_8(data[i])) << 8);
for (int j = 0; j < 4; j++)
{
uint16_t tmp = crc << 1;
if (crc & 0x8000)
{
tmp ^= 0x1021;
}
crc = tmp << 1;
if (tmp & 0x8000)
{
crc ^= 0x1021;
}
}
}
crc = ~reverse_16(crc);
return crc;
}
void whitening_init(uint32_t val, WhiteningContext &ctx)
{
uint32_t v0[] = {(val >> 5), (val >> 4), (val >> 3), (val >> 2)};
ctx.f_0x0 = 1;
ctx.f_0x4 = v0[0] & 1;
ctx.f_0x8 = v0[1] & 1;
ctx.f_0xc = v0[2] & 1;
ctx.f_0x10 = v0[3] & 1;
ctx.f_0x14 = (val >> 1) & 1;
ctx.f_0x18 = val & 1;
}
void whitening_encode(std::vector<uint8_t> &data, WhiteningContext &ctx)
{
for (size_t i = 0; i < data.size(); i++)
{
uint32_t varC = ctx.f_0xc;
uint32_t var14 = ctx.f_0x14;
uint32_t var18 = ctx.f_0x18;
uint32_t var10 = ctx.f_0x10;
uint32_t var8 = var14 ^ ctx.f_0x8;
uint32_t var4 = var10 ^ ctx.f_0x4;
uint32_t _var = var18 ^ varC;
uint32_t var0 = _var ^ ctx.f_0x0;
uint8_t c = data[i];
data[i] = ((c & 0x80) ^ ((var8 ^ var18) << 7)) + ((c & 0x40) ^ (var0 << 6)) + ((c & 0x20) ^ (var4 << 5)) + ((c & 0x10) ^ (var8 << 4)) + ((c & 0x08) ^ (_var << 3)) + ((c & 0x04) ^ (var10 << 2)) + ((c & 0x02) ^ (var14 << 1)) + ((c & 0x01) ^ (var18 << 0));
ctx.f_0x8 = var4;
ctx.f_0xc = var8;
ctx.f_0x10 = var8 ^ varC;
ctx.f_0x14 = var0 ^ var10;
ctx.f_0x18 = var4 ^ var14;
ctx.f_0x0 = var8 ^ var18;
ctx.f_0x4 = var0;
}
}
std::vector<char> vector_to_hex_string(std::vector<uint8_t> &data)
{
std::vector<char> hex_str(data.size() * 2 + 1); // Allocate the vector with the required size
for (size_t i = 0; i < data.size(); i++)
{
sprintf(hex_str.data() + (i * 2), "%02X", data[i]);
}
hex_str[data.size() * 2] = '\0'; // Ensure null termination
return hex_str;
}
} // namespace brmesh
} // namespace esphome

View file

@ -1,36 +0,0 @@
#pragma once
#include <cstdint>
#include <vector>
namespace esphome
{
namespace brmesh
{
// Bit manipulation utilities
uint8_t reverse_8(uint8_t d);
uint16_t reverse_16(uint16_t d);
// CRC calculation
uint16_t crc16(const std::vector<uint8_t> &addr, const std::vector<uint8_t> &data);
// Whitening context and functions
struct WhiteningContext
{
uint32_t f_0x0;
uint32_t f_0x4;
uint32_t f_0x8;
uint32_t f_0xc;
uint32_t f_0x10;
uint32_t f_0x14;
uint32_t f_0x18;
WhiteningContext() : f_0x0(0), f_0x4(0), f_0x8(0), f_0xc(0), f_0x10(0), f_0x14(0), f_0x18(0) {}
};
void whitening_init(uint32_t val, WhiteningContext &ctx);
void whitening_encode(std::vector<uint8_t> &data, WhiteningContext &ctx);
std::vector<char> vector_to_hex_string(std::vector<uint8_t> &data);
} // namespace brmesh
} // namespace esphome

View file

@ -1,5 +1,4 @@
#include "whitening.h"
#include "debug.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"