From 2456b83c0dd0847038a508e302c04ae124267cc1 Mon Sep 17 00:00:00 2001 From: Mitchell Date: Thu, 13 Aug 2026 18:09:31 -0500 Subject: [PATCH] priv impl and scaffold for driver --- CMakeLists.txt | 2 +- bbnow_driver.cpp | 20 +++ bbnow_priv.cpp | 360 ++++++++++++++++++++++++++++++++++++++ include/bb_expantions.hpp | 2 + include/bbnow_driver.hpp | 74 ++++++++ include/bbnow_priv.hpp | 130 +++++++++++++- include/bbnow_pub.hpp | 10 +- 7 files changed, 591 insertions(+), 7 deletions(-) create mode 100644 bbnow_driver.cpp create mode 100644 bbnow_priv.cpp create mode 100644 include/bbnow_driver.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d1ab58c..85c1e5c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( - SRCS "bbnow_pub.cpp" + SRCS "bbnow_driver.cpp" "bbnow_priv.cpp" "bbnow_pub.cpp" INCLUDE_DIRS "include" "." # PRIV_REQUIRES ) diff --git a/bbnow_driver.cpp b/bbnow_driver.cpp new file mode 100644 index 0000000..3da5d5b --- /dev/null +++ b/bbnow_driver.cpp @@ -0,0 +1,20 @@ +#include "bbnow_driver.hpp" + +esp_err_t BBNowDriver::init() +{ + return ESP_OK; +} + +esp_err_t BBNowDriver::deinit() +{ + return ESP_OK; +} + +esp_err_t BBNowDriver::send( + const MacAddress& destination, + const uint8_t* data, + size_t size +) +{ + return ESP_OK; +} diff --git a/bbnow_priv.cpp b/bbnow_priv.cpp new file mode 100644 index 0000000..9abe662 --- /dev/null +++ b/bbnow_priv.cpp @@ -0,0 +1,360 @@ +#include "bbnow_priv.hpp" + +#include +#include + +const static char TAG[] = "bbnow_priv"; + +size_t BBNowPrivPacket::serialize_size() const { + switch (this->packet_type) { + case BBNowPrivPacketType::NONE: + return -1; + + case BBNowPrivPacketType::ACK: + return BBNowPrivPacket::ACK_PACKET_SIZE; + + case BBNowPrivPacketType::DISCOVERY_RESPONSE: + return BBNowPrivPacket::DISCOVERY_RESPONSE_PACKET_SIZE; + + case BBNowPrivPacketType::EXPANDER_MESSAGE: { + const auto& data = + std::get( + this->packet_data + ); + + return sizeof(BBNowPrivPacketType) + + sizeof(uint16_t) + + data.data.size(); + } + + case BBNowPrivPacketType::INFO_QUERY: + return BBNowPrivPacket::INFO_QUERY_PACKET_SIZE; + + default: + return -1; + } + + return -1; +} + +std::vector BBNowPrivPacket::serialize() const { + auto buf = std::vector(); + + if (this->serialize_size() == static_cast(-1)) { + return buf; + } + + this->serialize_into(buf); + return buf; +} + +size_t BBNowPrivPacket::serialize_into( + std::vector& buf +) const { + size_t initial_size = buf.size(); + size_t size = this->serialize_size(); + + if (size == static_cast(-1)) { + return 0; + } + + buf.reserve(initial_size + size); + + // Push packet type. + static_assert( + sizeof(BBNowPrivPacketType) == 2, + "expect packet type to be 2 bytes" + ); + + uint16_t packet_type = + static_cast(this->packet_type); + + buf.push_back((packet_type >> 8) & 0xFF); + buf.push_back((packet_type >> 0) & 0xFF); + + // Push sequence number. + static_assert( + sizeof(this->sequence) == 2, + "expect sequence number to be 2 bytes" + ); + + buf.push_back((this->sequence >> 8) & 0xFF); + buf.push_back((this->sequence >> 0) & 0xFF); + + switch (this->packet_type) { + case BBNowPrivPacketType::NONE: + break; + + case BBNowPrivPacketType::ACK: + break; + + case BBNowPrivPacketType::DISCOVERY_RESPONSE: { + const auto& data = + std::get( + this->packet_data + ); + + static_assert( + sizeof(data.expantion_type) == 2, + "expect expansion type to be 2 bytes" + ); + + uint16_t expantion_type = + static_cast(data.expantion_type); + + buf.push_back((expantion_type >> 8) & 0xFF); + buf.push_back((expantion_type >> 0) & 0xFF); + + break; + } + + case BBNowPrivPacketType::EXPANDER_MESSAGE: { + const auto& data = + std::get( + this->packet_data + ); + + buf.insert( + buf.end(), + data.data.begin(), + data.data.end() + ); + + break; + } + + case BBNowPrivPacketType::INFO_QUERY: { + const auto& data = + std::get( + this->packet_data + ); + + static_assert( + sizeof(data) == 1, + "expect info query type to be 1 byte" + ); + buf.push_back(data.battery_percentage); + + break; + } + } + + assert(buf.size() - initial_size == size); + return size; +} + +std::optional +BBNowPrivPacket::deserialize( + std::span bytes +) { + // Packet type + sequence number. + static_assert( + sizeof(BBNowPrivPacketType) + sizeof(uint16_t) == 4, + "expect packet type and sequence number to be 4 bytes" + ); + + if (bytes.size() < 4) { + ESP_LOGW( + TAG, + "packet not long enough for priv packet" + ); + return std::nullopt; + } + + // Read packet type. + uint16_t packet_type = + ((uint16_t)bytes[0] << 8) | + ((uint16_t)bytes[1] << 0); + + BBNowPrivPacket packet; + + packet.packet_type = + static_cast(packet_type); + + if (packet.packet_type == BBNowPrivPacketType::NONE) { + ESP_LOGW( + TAG, + "BBNowPrivPacketType was NONE! discarding" + ); + return std::nullopt; + } + + if (packet.packet_type > BBNowPrivPacketType::MAX) { + ESP_LOGW( + TAG, + "BBNowPrivPacketType not in range! discarding" + ); + return std::nullopt; + } + + // Read sequence number. + packet.sequence = + ((uint16_t)bytes[2] << 8) | + ((uint16_t)bytes[3] << 0); + + switch (packet.packet_type) { + case BBNowPrivPacketType::ACK: { + if (bytes.size() < ACK_PACKET_SIZE) { + ESP_LOGW( + TAG, + "ACK packet not long enough" + ); + return std::nullopt; + } + + if (bytes.size() > ACK_PACKET_SIZE) { + ESP_LOGW( + TAG, + "ACK packet longer than expected, " + "decoding anyway" + ); + } + + packet.packet_data = BBNowPrivAckData{}; + break; + } + + case BBNowPrivPacketType::DISCOVERY_RESPONSE: { + if (bytes.size() < DISCOVERY_RESPONSE_PACKET_SIZE) { + ESP_LOGW( + TAG, + "discovery response packet not long enough" + ); + return std::nullopt; + } + + if (bytes.size() > DISCOVERY_RESPONSE_PACKET_SIZE) { + ESP_LOGW( + TAG, + "discovery response packet longer than expected, " + "decoding anyway" + ); + } + + uint16_t expantion_type = + ((uint16_t)bytes[4] << 8) | + ((uint16_t)bytes[5] << 0); + + auto data = BBNowPrivDiscoveryResponseData{ + .expantion_type = + static_cast(expantion_type) + }; + + if (data.expantion_type > BlkBoxExpantion::MAX) { + ESP_LOGW( + TAG, + "BlkBoxExpantion not in range! discarding" + ); + return std::nullopt; + } + + packet.packet_data = std::move(data); + break; + } + + case BBNowPrivPacketType::EXPANDER_MESSAGE: { + // Everything after the 4-byte header is the message. + auto data = BBNowPrivExpanderMessageData{ + .data = std::vector( + bytes.begin() + 4, + bytes.end() + ) + }; + + packet.packet_data = std::move(data); + break; + } + + case BBNowPrivPacketType::INFO_QUERY: { + constexpr size_t INFO_QUERY_PACKET_SIZE = + sizeof(BBNowPrivPacketType) + + sizeof(uint16_t) + + sizeof(BBNowPrivInfoQueryData); + + if (bytes.size() < INFO_QUERY_PACKET_SIZE) { + ESP_LOGW( + TAG, + "info query packet not long enough" + ); + return std::nullopt; + } + + if (bytes.size() > INFO_QUERY_PACKET_SIZE) { + ESP_LOGW( + TAG, + "info query packet longer than expected, " + "decoding anyway" + ); + } + + BBNowPrivInfoQueryData data{ + .battery_percentage = bytes[4] + }; + + packet.packet_data = data; + break; + } + + case BBNowPrivPacketType::NONE: + return std::nullopt; + } + + return packet; +} + +BBNowPrivPacket BBNowPrivPacket::new_ack( + uint16_t sequence +) { + BBNowPrivPacket packet; + + packet.packet_type = BBNowPrivPacketType::ACK; + packet.sequence = sequence; + packet.packet_data = BBNowPrivAckData{}; + + return packet; +} + +BBNowPrivPacket BBNowPrivPacket::new_discovery_response( + uint16_t sequence, + BBNowPrivDiscoveryResponseData data +) { + BBNowPrivPacket packet; + + packet.packet_type = + BBNowPrivPacketType::DISCOVERY_RESPONSE; + + packet.sequence = sequence; + packet.packet_data = std::move(data); + + return packet; +} + +BBNowPrivPacket BBNowPrivPacket::new_expander_message( + uint16_t sequence, + BBNowPrivExpanderMessageData data +) { + BBNowPrivPacket packet; + + packet.packet_type = + BBNowPrivPacketType::EXPANDER_MESSAGE; + + packet.sequence = sequence; + packet.packet_data = std::move(data); + + return packet; +} + +BBNowPrivPacket BBNowPrivPacket::new_info_query( + uint16_t sequence, + BBNowPrivInfoQueryData data +) { + BBNowPrivPacket packet; + + packet.packet_type = + BBNowPrivPacketType::INFO_QUERY; + + packet.sequence = sequence; + packet.packet_data = std::move(data); + + return packet; +} diff --git a/include/bb_expantions.hpp b/include/bb_expantions.hpp index 5d91ebc..55f9459 100644 --- a/include/bb_expantions.hpp +++ b/include/bb_expantions.hpp @@ -5,6 +5,8 @@ #include +#define EXPANDER_DEVICE_NAME_MAX_LEN 32 + /// The expantion device ids in the BLK_BOX ecosystem. enum class BlkBoxExpantion: uint16_t { NONE = 0, diff --git a/include/bbnow_driver.hpp b/include/bbnow_driver.hpp new file mode 100644 index 0000000..d10e099 --- /dev/null +++ b/include/bbnow_driver.hpp @@ -0,0 +1,74 @@ +/// Generic code for driving a BBNOW interface. + +#ifndef _BBNOW_DRIVER_HPP +#define _BBNOW_DRIVER_HPP + +#include +#include +#include +#include +#include + +#include "esp_err.h" + +#include "bb_expantions.hpp" +#include "bbnow_pub.hpp" +#include "bbnow_priv.hpp" + +class BBNowDriver { +public: + using MacAddress = std::array; + + struct PairedExpander { + MacAddress mac; + std::string name; + BlkBoxExpantion type; + }; + + static constexpr uint8_t DEFAULT_CHANNEL = 6; + + static esp_err_t init(); + static esp_err_t deinit(); + + static esp_err_t send_pub(const BBNowPubPacket& packet); + static bool has_pub(); + static bool recv_pub(BBNowPubPacket& packet); + + static esp_err_t send_priv( + const MacAddress& destination, + const BBNowPrivPacket& packet + ); + static bool has_priv(); + static bool recv_priv( + MacAddress& source, + BBNowPrivPacket& packet + ); + + /// Broadcast a discovery packet. + static size_t send_discovery( + BlkBoxExpantion type + ); + + static esp_err_t pair_expander( + BlkBoxExpantion type, + const MacAddress& mac + ); + + static esp_err_t unpair_expander( + const MacAddress& mac + ); + + static std::span get_paired_expanders(); + +private: + static constexpr MacAddress BROADCAST_MAC = { + 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff + }; + + static bool initialized_; + + static std::vector paired_expanders; +}; + +#endif /* _BBNOW_DRIVER_HPP */ \ No newline at end of file diff --git a/include/bbnow_priv.hpp b/include/bbnow_priv.hpp index 586ac22..21a0b3c 100644 --- a/include/bbnow_priv.hpp +++ b/include/bbnow_priv.hpp @@ -4,9 +4,49 @@ #define _BBNOW_PRIV_H #include +#include +#include +#include +#include -#define EXPANDER_DEVICE_NAME_MAX_LEN 32 +#include "bb_expantions.hpp" +/////////////////////////////////////// +// ACK // +///////////////////////////////////// +/// The data associated with the `ACK` type. +struct BBNowPrivAckData { +}; + +/////////////////////////////////////// +// DISCOVERY RESPONSE // +///////////////////////////////////// +/// The data associated with the `DISCOVERY_RESPONSE` type. +struct BBNowPrivDiscoveryResponseData { + BlkBoxExpantion expantion_type{}; +}; + +/////////////////////////////////////// +// EXPANDER MESSAGE // +///////////////////////////////////// +/// The data associated with the `EXPANDER_MESSAGE` type. +struct BBNowPrivExpanderMessageData { + // TODO: this should get flattened into a concrete type, an enum over the expander types. + std::vector data; +}; + +/////////////////////////////////////// +// INFO QUERY // +///////////////////////////////////// +/// The data associated with the `INFO_QUERY` type. +struct BBNowPrivInfoQueryData { + uint8_t battery_percentage; + // TODO: add other status fields that may be useful to query. +}; + +/////////////////////////////////////// +// PRIV PACKET // +///////////////////////////////////// /// The packet type for `BBNowPrivPacket`s. enum class BBNowPrivPacketType: uint16_t { NONE = 0, @@ -14,7 +54,93 @@ enum class BBNowPrivPacketType: uint16_t { // TODO: we will need to add more here for handshaking in the future. DISCOVERY_RESPONSE = 2, EXPANDER_MESSAGE = 3, - MAX = 3, + INFO_QUERY = 4, + MAX = 4, +}; + +/// The data field for the packet. +using BBNowPrivPacketData = std::variant< + std::monostate, + BBNowPrivAckData, + BBNowPrivDiscoveryResponseData, + BBNowPrivExpanderMessageData, + BBNowPrivInfoQueryData +>; + +struct BBNowPrivPacket { + /// ACK packet size. + const static size_t ACK_PACKET_SIZE = 4; + static_assert( + ACK_PACKET_SIZE == sizeof(BBNowPrivPacketType) + sizeof(uint16_t), + "ACK packet size should match it's data size" + ); + + /// Discovery response packet size. + const static size_t DISCOVERY_RESPONSE_PACKET_SIZE = 6; + static_assert( + DISCOVERY_RESPONSE_PACKET_SIZE == sizeof(BBNowPrivPacketType) + + sizeof(uint16_t) + + sizeof(BBNowPrivDiscoveryResponseData), + "Discovery response packet size should match it's data size" + ); + + /// Info query packet size. + const static size_t INFO_QUERY_PACKET_SIZE = 5; + static_assert( + INFO_QUERY_PACKET_SIZE == + sizeof(BBNowPrivPacketType) + + sizeof(uint16_t) + + sizeof(BBNowPrivInfoQueryData), + "Info query packet size should match its data size" + ); + + /// The type of packet this is. + BBNowPrivPacketType packet_type{}; + + /// Sequence number associated with this packet. + uint16_t sequence{}; + + /// The data held by the packet. + BBNowPrivPacketData packet_data{}; + + /// The size of the serialized packet (in bytes). + size_t serialize_size() const; + + /// Serializes the packet to a vector. + std::vector serialize() const; + + /// Serializes the packet into the given vector. + /// + /// Returns the number of bytes written into the buffer. + size_t serialize_into(std::vector& buf) const; + + /// Deserializes a packet from the given bytes. + static std::optional + deserialize(std::span bytes); + + /// Constructs an empty packet. + BBNowPrivPacket() = default; + + /// Constructs a new `ACK` packet. + static BBNowPrivPacket new_ack(uint16_t sequence); + + /// Constructs a new `DISCOVERY_RESPONSE` packet. + static BBNowPrivPacket new_discovery_response( + uint16_t sequence, + BBNowPrivDiscoveryResponseData data + ); + + /// Constructs a new `EXPANDER_MESSAGE` packet. + static BBNowPrivPacket new_expander_message( + uint16_t sequence, + BBNowPrivExpanderMessageData data + ); + + /// Constructs a new `INFO_QUERY` packet. + static BBNowPrivPacket new_info_query( + uint16_t sequence, + BBNowPrivInfoQueryData data + ); }; #endif /* _BBNOW_PRIV_H */ diff --git a/include/bbnow_pub.hpp b/include/bbnow_pub.hpp index 2847044..bd1451a 100644 --- a/include/bbnow_pub.hpp +++ b/include/bbnow_pub.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "bb_expantions.hpp" @@ -30,10 +31,11 @@ enum class BBNowPubPacketType: uint16_t { MAX = 1, }; -/// The data field for the packet -union BlkBoxNowPubPacketData { - BlkBoxNowPubDiscoveryData discovery; -}; +/// The data field for the packet. +using BlkBoxNowPubPacketData = std::variant< + std::monostate, + BlkBoxNowPubDiscoveryData +>; struct BBNowPubPacket { // "bbnp" in ascii