Initial code
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.pio
|
||||||
|
.vscode/.browse.c_cpp.db*
|
||||||
|
.vscode/c_cpp_properties.json
|
||||||
|
.vscode/launch.json
|
||||||
|
.vscode/ipch
|
||||||
18
include/PacketCRC.h
Normal file
18
include/PacketCRC.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef PACKET_CRC_H
|
||||||
|
#define PACKET_CRC_H
|
||||||
|
|
||||||
|
#include <CRC16.h>
|
||||||
|
|
||||||
|
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
|
||||||
56
include/PacketParser.h
Normal file
56
include/PacketParser.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#ifndef PACKET_PARSER_H
|
||||||
|
#define PACKET_PARSER_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
// 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<std::monostate, uint8_t, uint16_t> value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FieldsParameters {
|
||||||
|
std::vector<Field> fields;
|
||||||
|
};
|
||||||
|
|
||||||
|
using PacketParameters = std::variant<std::monostate, FieldsParameters>;
|
||||||
|
|
||||||
|
|
||||||
|
// 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<Field>& fields);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PACKET_PARSER_H
|
||||||
46
include/PacketReceiver.h
Normal file
46
include/PacketReceiver.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#ifndef PACKET_RECEIVER_H
|
||||||
|
#define PACKET_RECEIVER_H
|
||||||
|
|
||||||
|
#define CIRCULAR_BUFFER_INT_SAFE
|
||||||
|
|
||||||
|
#include <RF24.h>
|
||||||
|
#include <CircularBuffer.h>
|
||||||
|
#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<uint8_t*, RX_BUFFER_COUNT> pendingBuffers;
|
||||||
|
CircularBuffer<uint8_t*, EMPTY_BUFFER_COUNT> freeBuffers;
|
||||||
|
CircularBuffer<uint8_t*, VALID_BUFFER_COUNT> 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
|
||||||
27
library.json
Normal file
27
library.json
Normal file
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
34
src/PacketCRC.cpp
Normal file
34
src/PacketCRC.cpp
Normal file
@@ -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();
|
||||||
|
}
|
||||||
82
src/PacketParser.cpp
Normal file
82
src/PacketParser.cpp
Normal file
@@ -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<Field> fields;
|
||||||
|
parseFields(buffer, fields);
|
||||||
|
message.parameters = FieldsParameters{fields};
|
||||||
|
} else if (buffer[16] == 0x3F && buffer[17] == 0x5A) {
|
||||||
|
message.type = PacketType::FIELD_COMMAND;
|
||||||
|
std::vector<Field> 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<Field>& 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;
|
||||||
|
}
|
||||||
122
src/PacketReceiver.cpp
Normal file
122
src/PacketReceiver.cpp
Normal file
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user