From c84c2dfbdcddcd701e424e17727b265782241b03 Mon Sep 17 00:00:00 2001 From: Matt Way Date: Thu, 21 Dec 2023 16:43:41 +1100 Subject: [PATCH] Initial code --- .gitignore | 5 ++ include/PacketCRC.h | 18 ++++++ include/PacketParser.h | 56 ++++++++++++++++++ include/PacketReceiver.h | 46 +++++++++++++++ library.json | 27 +++++++++ src/PacketCRC.cpp | 34 +++++++++++ src/PacketParser.cpp | 82 ++++++++++++++++++++++++++ src/PacketReceiver.cpp | 122 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 390 insertions(+) create mode 100644 .gitignore create mode 100644 include/PacketCRC.h create mode 100644 include/PacketParser.h create mode 100644 include/PacketReceiver.h create mode 100644 library.json create mode 100644 src/PacketCRC.cpp create mode 100644 src/PacketParser.cpp create mode 100644 src/PacketReceiver.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/include/PacketCRC.h b/include/PacketCRC.h new file mode 100644 index 0000000..e5d5512 --- /dev/null +++ b/include/PacketCRC.h @@ -0,0 +1,18 @@ +#ifndef PACKET_CRC_H +#define PACKET_CRC_H + +#include + +class PacketCRC { +public: + PacketCRC(); + ~PacketCRC(); + + // Method to calculate CRC for a packet + bool check(const uint8_t* packet); + uint16_t calculate(const uint8_t* packet); +private: + CRC16 crc; +}; + +#endif // PACKET_CRC_H \ No newline at end of file diff --git a/include/PacketParser.h b/include/PacketParser.h new file mode 100644 index 0000000..ca02433 --- /dev/null +++ b/include/PacketParser.h @@ -0,0 +1,56 @@ +#ifndef PACKET_PARSER_H +#define PACKET_PARSER_H + +#include +#include + +// Define packet types +enum class PacketType { + OPEN, + CLOSE, + STOP, + FIELDS, + FIELD_COMMAND, + UNKNOWN +}; + +enum class FieldType { + VALUE, + SET, + FETCH, + UNKNOWN +}; + +struct Field { + uint8_t identifier; + FieldType type; + bool hasValue; + std::variant value; +}; + +struct FieldsParameters { + std::vector fields; +}; + +using PacketParameters = std::variant; + + +// Define Message structure +struct Packet { + uint16_t source; + uint16_t destination; + PacketType type; + PacketParameters parameters; +}; + +class PacketParser { +public: + PacketParser(); + ~PacketParser(); + + bool parsePacket(const uint8_t *buffer, Packet& message); +private: + bool parseFields(const uint8_t *buffer, std::vector& fields); +}; + +#endif // PACKET_PARSER_H \ No newline at end of file diff --git a/include/PacketReceiver.h b/include/PacketReceiver.h new file mode 100644 index 0000000..774be91 --- /dev/null +++ b/include/PacketReceiver.h @@ -0,0 +1,46 @@ +#ifndef PACKET_RECEIVER_H +#define PACKET_RECEIVER_H + +#define CIRCULAR_BUFFER_INT_SAFE + +#include +#include +#include "PacketCRC.h" + +#define BUFFER_SIZE 32 + +#define TOTAL_BUFFER_COUNT 30 +#define RX_BUFFER_COUNT 3 +#define EMPTY_BUFFER_COUNT 30 +#define VALID_BUFFER_COUNT 5 + +class PacketReceiver { +public: + PacketReceiver(RF24 *radio); + ~PacketReceiver(); + + void loop(); + void read(); + + void setPacketCallback(void (*callback)(const uint8_t *buffer)); + void setInvalidPacketCallback(void (*callback)(const uint8_t *buffer)); + +private: + RF24 *radio; + PacketCRC packetCRC; + + uint8_t buffers[TOTAL_BUFFER_COUNT][BUFFER_SIZE]; + CircularBuffer pendingBuffers; + CircularBuffer freeBuffers; + CircularBuffer receivedBuffers; + + void (*packetCallback)(const uint8_t *buffer); + void (*invalidPacketCallback)(const uint8_t *buffer); + + bool isSanePacket(uint8_t *buffer); + bool isValidPacket(uint8_t *buffer); + bool isNewPacket(uint8_t *buffer); + bool isEquivalentPacket(uint8_t *a, uint8_t *b); +}; + +#endif // PACKET_RECEIVER_H \ No newline at end of file diff --git a/library.json b/library.json new file mode 100644 index 0000000..5eda475 --- /dev/null +++ b/library.json @@ -0,0 +1,27 @@ +{ + "name": "RFPowerView", + "version": "0.0.1", + "description": "A library for receiving and sending PowerView packets via an nRF24L01 module", + "keywords": "powerview, hunterdouglas, luxaflex, rf, radio", + "repository": { + "type": "git", + "url": "https://git.mattway.com.au/matt/RFPowerView.git" + }, + "authors": + [ + { + "name": "Matt Way", + "email": "mattyway@gmail.com" + } + ], + "license": "GPL-2.0-only", + "dependencies": { + "robtillaart/CRC": "^1.0.2", + "nrf24/RF24": "^1.4.8", + "rlogiacco/CircularBuffer": "^1.3.3" + }, + "frameworks": "arduino", + "platforms": [ + "espressif8266" + ] + } \ No newline at end of file diff --git a/src/PacketCRC.cpp b/src/PacketCRC.cpp new file mode 100644 index 0000000..107d6dc --- /dev/null +++ b/src/PacketCRC.cpp @@ -0,0 +1,34 @@ +#include "PacketCRC.h" + +PacketCRC::PacketCRC() : crc(CRC16(0x755B, 0xF48B, 0x0000, false, false)) { + // Constructor implementation (if needed) +} + +PacketCRC::~PacketCRC() { + // Destructor implementation (if needed) +} + +bool PacketCRC::check(const uint8_t* packet) { + uint8_t length = packet[1]; + + if (length <= 0 or length > 28) { + return false; + } + + uint16_t result = calculate(packet); + uint8_t checksum1 = (uint8_t)((result & 0xFF00) >> 8); + uint8_t checksum2 = (uint8_t)(result & 0x00FF); + + if (packet[length + 2] == checksum1 && packet[length + 3] == checksum2) { + return true; + } + + return false; +} + +uint16_t PacketCRC::calculate(const uint8_t* packet) { + crc.restart(); + uint8_t length = packet[1]; + crc.add(packet, length + 2); + return crc.calc(); +} \ No newline at end of file diff --git a/src/PacketParser.cpp b/src/PacketParser.cpp new file mode 100644 index 0000000..18bb790 --- /dev/null +++ b/src/PacketParser.cpp @@ -0,0 +1,82 @@ +#include "PacketParser.h" + +PacketParser::PacketParser() +{ +} + +PacketParser::~PacketParser() +{ +} + +bool PacketParser::parsePacket(const uint8_t *buffer, Packet& message) +{ + message.source = (uint16_t)(buffer[14] << 8 | buffer[15]); + message.destination = (uint16_t)(buffer[12] << 8 | buffer[13]); + + if (buffer[16] == 0x52 && buffer[17] == 0x53) { + message.type = PacketType::STOP; + message.parameters = std::monostate{}; + } else if (buffer[16] == 0x52 && buffer[17] == 0x44) { + message.type = PacketType::CLOSE; + message.parameters = std::monostate{}; + } else if (buffer[16] == 0x52 && buffer[17] == 0x55) { + message.type = PacketType::OPEN; + message.parameters = std::monostate{}; + } else if (buffer[16] == 0x21 && buffer[17] == 0x5A) { + message.type = PacketType::FIELDS; + std::vector fields; + parseFields(buffer, fields); + message.parameters = FieldsParameters{fields}; + } else if (buffer[16] == 0x3F && buffer[17] == 0x5A) { + message.type = PacketType::FIELD_COMMAND; + std::vector fields; + parseFields(buffer, fields); + message.parameters = FieldsParameters{fields}; + } else { + message.type = PacketType::UNKNOWN; + message.parameters = std::monostate{}; + return false; + } + + return true; +} + +bool PacketParser::parseFields(const uint8_t *buffer, std::vector& fields) { + uint8_t length = buffer[1] + 2; + + uint8_t offset = 18; + while (offset < length) { + uint8_t fieldLength = buffer[offset]; + if (offset + fieldLength >= length) { + // Not enough space to parse the field + return false; + } + FieldType type; + switch (buffer[offset + 1]) { + case 0x40: + type = FieldType::SET; + break; + case 0x3F: + type = FieldType::FETCH; + break; + case 0x21: + type = FieldType::VALUE; + break; + default: + type = FieldType::UNKNOWN; + break; + } + uint8_t identifier = buffer[offset + 2]; + if (fieldLength == 2) { + fields.push_back(Field{identifier, type, false, std::monostate{}}); + } else if (fieldLength == 3) { + uint8_t value = buffer[offset + 3]; + fields.push_back(Field{identifier, type, true, value}); + } else if (fieldLength == 4) { + uint16_t value = (uint16_t)(buffer[offset + 3] | buffer[offset + 4] << 8); + fields.push_back(Field{identifier, type, true, value}); + } + offset += fieldLength + 1; + } + return true; +} \ No newline at end of file diff --git a/src/PacketReceiver.cpp b/src/PacketReceiver.cpp new file mode 100644 index 0000000..844deea --- /dev/null +++ b/src/PacketReceiver.cpp @@ -0,0 +1,122 @@ +#include "PacketReceiver.h" + +PacketReceiver::PacketReceiver(RF24 *radio) : radio(radio), packetCallback(nullptr), invalidPacketCallback(nullptr) { + for (int i = 0; i < TOTAL_BUFFER_COUNT; i++) { + if (!freeBuffers.isFull()) { + freeBuffers.push(buffers[i]); + } + } +} + +PacketReceiver::~PacketReceiver() { + +} + +void PacketReceiver::loop() { + noInterrupts(); + + while (!pendingBuffers.isEmpty()) { + uint8_t *p = pendingBuffers.pop(); + + bool isSanePacket = this->isSanePacket(p); + bool isValidPacket = this->isValidPacket(p); + bool isNewPacket = this->isNewPacket(p); + + // TODO: We don't keep old invalid packets around, so invalid packets will always be new for now + if (isSanePacket && !isValidPacket && isNewPacket) { + if (invalidPacketCallback != nullptr) { + invalidPacketCallback(p); + } + } + + if (isValidPacket && isNewPacket) { + if (packetCallback != nullptr) { + this->packetCallback(p); + } + + // If there is no space to store this new packet, return the oldest packet + if (receivedBuffers.isFull()) { + freeBuffers.unshift(receivedBuffers.pop()); + } + + // Keep this packet so we can check if packets received later are duplicates + receivedBuffers.unshift(p); + } else { + // Not a valid packet or is a duplicate packet, so return it immediately + freeBuffers.unshift(p); + } + } + + interrupts(); +} + +//IRQ routine used to fill buffer of max size packets -> needs to be short and fast +void PacketReceiver::read() { + bool tx_ds, tx_df, rx_dr; + radio->whatHappened(tx_ds, tx_df, rx_dr); // resets the IRQ pin to HIGH + uint8_t pipe = 0; + + while (radio->available(&pipe)) { + if (!pendingBuffers.isFull() && !freeBuffers.isEmpty()) { + uint8_t* p = freeBuffers.pop(); + radio->read(p, BUFFER_SIZE); + pendingBuffers.unshift(p); + } else { + // Not enough buffers to store the packet + radio->flush_rx(); + break; + } + } +} + +void PacketReceiver::setPacketCallback(void (*callback)(const uint8_t *buffer)) { + this->packetCallback = callback; +} + +void PacketReceiver::setInvalidPacketCallback(void (*callback)(const uint8_t *buffer)) +{ + this->invalidPacketCallback = callback; +} + +bool PacketReceiver::isSanePacket(uint8_t *buffer) { + // Sanity check that the buffer isn't just filled with noise + // First byte contains a header + // Second byte contains the packet length which cannot be larger than 28 bytes (32 bytes - 1 header byte - 1 length byte - 2 CRC bytes) + if (buffer[0] == 0xC0 && buffer[1] <= 28) { + return true; + } + return false; +} + +bool PacketReceiver::isValidPacket(uint8_t *buffer) { + if (isSanePacket(buffer)) { + if (packetCRC.check(buffer)) { + return true; + } + } + return false; +} + +bool PacketReceiver::isNewPacket(uint8_t *buffer) { + for (int i = 0; i < receivedBuffers.size(); i++) { + if (isEquivalentPacket(buffer, receivedBuffers[i])) { + return false; + } + } + return true; +} + +bool PacketReceiver::isEquivalentPacket(uint8_t *a, uint8_t *b) { + // Check length byte + if (a[1] != b[1]) { + return false; + } + // TODO: Could we just check the source and destination address and the rolling code? + uint8_t length = a[1] + 4; + for (int i = 0; i < length; i++) { + if (a[i] != b[i]) { + return false; + } + } + return true; +}