Compare commits
18 Commits
v0.0.2
...
f069022f5c
| Author | SHA1 | Date | |
|---|---|---|---|
| f069022f5c | |||
| a398a0b679 | |||
| 168c1414bd | |||
| 83f6e8fa52 | |||
| 5b6d951cbc | |||
| 88caab56a2 | |||
| d3325b71cd | |||
| 3d315be27f | |||
| 71dbf1f4ca | |||
| 7f7e02a8b7 | |||
| 746d0fad6f | |||
| 4460224202 | |||
| a7db18d8ac | |||
| 52d03a1ac9 | |||
| 07f46ecfe7 | |||
| 90e101c47e | |||
| e488cf86d4 | |||
| 9f9dfbfff2 |
10
.vscode/extensions.json
vendored
Normal file
10
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||||
|
// for the documentation about the extensions.json format
|
||||||
|
"recommendations": [
|
||||||
|
"platformio.platformio-ide"
|
||||||
|
],
|
||||||
|
"unwantedRecommendations": [
|
||||||
|
"ms-vscode.cpptools-extension-pack"
|
||||||
|
]
|
||||||
|
}
|
||||||
177
README.md
177
README.md
@@ -1,2 +1,179 @@
|
|||||||
# RFPowerView
|
# RFPowerView
|
||||||
|
|
||||||
|
Library for sending and receiving PowerView packets so that blinds can be controlled programatically. PowerView is a brand of remote controlled blinds developed by Hunter Douglas (also known as Luxaflex in some regions). This library is intended to be used on a microcontroller like an ESP8266 in conjunction with a nRF24L01 transceiver module. The implementation has been entirely reverse engineered by sniffing packets sent from a PowerView Hub or Pebble remote.
|
||||||
|
|
||||||
|
## Supported commands
|
||||||
|
- Open
|
||||||
|
- Close
|
||||||
|
- Stop
|
||||||
|
- Open slowly
|
||||||
|
- Close slowly
|
||||||
|
- Move to saved position
|
||||||
|
- Move to position
|
||||||
|
- Query position
|
||||||
|
- Query battery level
|
||||||
|
|
||||||
|
## Protocol
|
||||||
|
|
||||||
|
A PowerView packet consists of a header (which contains a variable length address section), a variable length payload, and a two byte checksum at the end. Each packet is limited to being at most 32 bytes due to limitations of the nRF24L01 module.
|
||||||
|
|
||||||
|
### Header
|
||||||
|
|
||||||
|
| Byte Offset | Size (Bits) | Name | Description | Valid Values |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 0 | 8 | ID Byte | Identifies the start of a new packet. | `0xCO` |
|
||||||
|
| 1 | 8 | Length | Specifies how many bytes of header and data come after this byte (excludes checksum bytes). | `0x0A` - `0x1D` |
|
||||||
|
| 2 | 8 | Unknown | Could indicate the type of sender | `0x00` (from hub or remote), `0x01` (from blind) |
|
||||||
|
| 3 | 8 | Fixed | This byte is always the same. | `0x05` |
|
||||||
|
| 4 | 8 | Rolling Code 1 | This byte is incremented by 0x01 each time a packet is sent. | `0x00`-`0xFF` |
|
||||||
|
| 5 | 16 | Fixed | These two bytes are always the same. | `0xFFFF` |
|
||||||
|
| 7 | 16 | Physical Source ID | The ID of device that actually sent the packet (can be different to Logical Source ID when a repeater is being used) | `0x0000`-`0xFFFF` |
|
||||||
|
| 9 | 8 | Fixed | This byte is always the same | `0x86` |
|
||||||
|
| 10 | 8 | Address Type | Indicates which type of address this packet is using. | `0x04` (Broadcast), `0x05` (Unicast), `0x06` (Groups) |
|
||||||
|
| 11 | 8 | Rolling Code 2 | This byte is incremented by 0x01 each time a packet is sent. | `0x00`-`0xFF` |
|
||||||
|
| 12 | Varies | Address | This section is a variable length. | Broadcast, Unicast, or Groups section (see next section) |
|
||||||
|
|
||||||
|
#### Broadcast address
|
||||||
|
| Byte Offset | Size (Bits) | Name | Description | Valid Values |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 0 | 16 | Logical Source ID | The ID of device where the packet originated (hubs seem to always be 0x0000) | `0x0000`-`0xFFFF` |
|
||||||
|
|
||||||
|
Note: this type of address seems to be used when a hub is activating a scene (and multiple blinds could be included in the scene).
|
||||||
|
|
||||||
|
#### Unicast address
|
||||||
|
| Byte Offset | Size (Bits) | Name | Description | Valid Values |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 0 | 16 | Destination ID | The ID of device that should process the packet | `0x0000`-`0xFFFF` |
|
||||||
|
| 2 | 16 | Logical Source ID | The ID of device where the packet originated (hubs seem to always be 0x0000) | `0x0000`-`0xFFFF` |
|
||||||
|
|
||||||
|
Note: this type of address is used when a hub wants to communicate with a specific blind.
|
||||||
|
|
||||||
|
#### Groups address
|
||||||
|
| Byte Offset | Size (Bits) | Name | Description | Valid Values |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 0-5 | 8 | Group Number | Devices assigned to the specified group numbers should process the packet. | `0x01`-`0x06` |
|
||||||
|
| n | 8 | Terminator | Marker to stop processing group numbers | `0x00` |
|
||||||
|
| n+1 | 16 | Logical Source ID | The ID of device where the packet originated | `0x0000`-`0xFFFF` |
|
||||||
|
|
||||||
|
Note: this type of address is used by pebble remotes. The Group Number corresponds to the the numbered buttons on the pebble remote. When multiple group numbers are included, generally they are in ascending order.
|
||||||
|
|
||||||
|
### Payload
|
||||||
|
This section begins at the next byte following the variable length address section. I've observed two types of payloads: simple and "fields".
|
||||||
|
|
||||||
|
### Simple payload
|
||||||
|
| Byte Offset | Size (Bits) | Name | Description | Valid Values |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 0 | 8 | Packet Type | Indicates the payload is a simple command | `0x52` |
|
||||||
|
| 1 | 8 | Command | What should the blind do | `0x55` (Open), `0x44` (Close), `0x53` (Stop), `0x52` (Open Slowly), `0x4C` (Close Slowly), `0x48` (Move to saved position) |
|
||||||
|
| 2 | 8 | End | Marker to indicate end of data | `0x00` |
|
||||||
|
|
||||||
|
Note: this type of payload is used by both the hub and pebble remotes. Used for commands like "Open", "Close", or "Stop"
|
||||||
|
|
||||||
|
#### Fields payload
|
||||||
|
| Byte Offset | Size (Bits) | Name | Description | Valid Values |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 0 | 8 | Field Mode | Indicates the payloads contains "fields" | `0x3F` (fields without values), `0x21` (fields with values) |
|
||||||
|
| 1 | 8 | Fixed | This byte is always the same. | `0x5A` |
|
||||||
|
| 2 | varies | Field data | This section is a variable length depending on the number of fields the payload contains | Field data (see next section) |
|
||||||
|
|
||||||
|
Note: this type of payload is used by the hub. If sent to a blind without values, it will prompt the blind to respond with the value of those fields. If sent to a blind with values, it will prompt the blind to "apply" the value to the fields (eg. by moving to a position). A single payload can contain multiple fields.
|
||||||
|
|
||||||
|
#### Field data
|
||||||
|
| Byte Offset | Size (Bits) | Name | Description | Valid Values |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 0 | 8 | Field Length | Indicates how many bytes are in this field | `0x02`-`0x04` |
|
||||||
|
| 1 | 8 | Field Mode | Seems to always be the same as the first Field Mode | `0x3F` (fields without values), `0x21` (fields with values) |
|
||||||
|
| 2 | 8 | Field ID | The field ID | `0x00`-`0xFF`, eg. `0x50` is Position, `0x42` is Battery level |
|
||||||
|
| 3 (if length=3) | 8 | Field Value | The value of the field | `0x00`-`0xFF` |
|
||||||
|
| 3 (if length=4) | 16 | Field Value | The value of the field | `0x0000`-`0xFFFF` |
|
||||||
|
|
||||||
|
### Checksum
|
||||||
|
| Byte Offset | Size (Bits) | Name | Description | Valid Values |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 0 | 16 | Checksum | A CRC16 checksum of the packet data | `0x0000`-`0xFFFF` |
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Open blinds in group 4
|
||||||
|
|
||||||
|
`C01100056CFFFF369E86063C0400369E525500B988`
|
||||||
|
|
||||||
|
| Name | Value | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| Length | `0x11` | |
|
||||||
|
| Rolling Code 1 | `0x6C` | |
|
||||||
|
| Rolling Code 2 | `0x3C` | |
|
||||||
|
| Physical Source ID | `0x369E` | |
|
||||||
|
| Address type | `0x06` | Groups |
|
||||||
|
| Logical Source ID | `0x369E` | Same as Physical Source ID |
|
||||||
|
| Groups | 4 | Only a single group specified |
|
||||||
|
| Checksum | `0xB988` | |
|
||||||
|
|
||||||
|
|
||||||
|
### Hub requesting values from a blind
|
||||||
|
|
||||||
|
`C019000592FFFF72CB85054E4EF100003F5A023F50023F4D023F54C9F3`
|
||||||
|
|
||||||
|
| Name | Value | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| Length | `0x19` | |
|
||||||
|
| Rolling Code 1 | `0x92` | |
|
||||||
|
| Rolling Code 2 | `0x4E` | |
|
||||||
|
| Physical Source ID | `0x72CB` | ID of repeater |
|
||||||
|
| Address type | `0x05` | Unicast |
|
||||||
|
| Destination ID | `0x4EF1` | ID of blind |
|
||||||
|
| Logical Source ID | `0x0000` | ID of hub |
|
||||||
|
| Field 1 | `023F50` | Field of 2 bytes, ID = `0x50` (position) |
|
||||||
|
| Field 2 | `023F4D` | Field of 2 bytes, ID = `0x4D` (unknown) |
|
||||||
|
| Field 3 | `023F54` | Field of 2 bytes, ID = `0x54` (unknown) |
|
||||||
|
| Checksum | `0xC9F3` | |
|
||||||
|
|
||||||
|
### Blind reporting position value to hub
|
||||||
|
|
||||||
|
`C0151005E0FFFF4EF186051A00004EF1215A04215040016670`
|
||||||
|
|
||||||
|
| Name | Value | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| Length | `0x15` | |
|
||||||
|
| Rolling Code 1 | `0xE0` | |
|
||||||
|
| Rolling Code 2 | `0x1A` | |
|
||||||
|
| Physical Source ID | `0x4EF1` | ID of blind |
|
||||||
|
| Address type | `0x05` | Unicast |
|
||||||
|
| Destination ID | `0x0000` | ID of blind |
|
||||||
|
| Logical Source ID | `0x4EF1` | ID of blind |
|
||||||
|
| Field 1 | `0421504001` | Field of 4 bytes, ID = `0x50` (position), value = `0x4001` |
|
||||||
|
| Checksum | `0x6670` | |
|
||||||
|
|
||||||
|
|
||||||
|
### Blind reporting battery level value to hub
|
||||||
|
|
||||||
|
`C014100558FFFF4EF18605C100004EF1215A0321429DEC23`
|
||||||
|
|
||||||
|
| Name | Value | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| Length | `0x14` | |
|
||||||
|
| Rolling Code 1 | `0x58` | |
|
||||||
|
| Rolling Code 2 | `0xC1` | |
|
||||||
|
| Physical Source ID | `0x4EF1` | ID of blind |
|
||||||
|
| Address type | `0x05` | Unicast |
|
||||||
|
| Destination ID | `0x0000` | ID of blind |
|
||||||
|
| Logical Source ID | `0x4EF1` | ID of blind |
|
||||||
|
| Field 1 | `0321429D` | Field of 3 bytes, ID = `0x42` (battery level), value = `0x9D` |
|
||||||
|
| Checksum | `0xEC23` | |
|
||||||
|
|
||||||
|
### Hub activating a scene
|
||||||
|
|
||||||
|
`C00F0005A1FFFF00008604FF000053471B446B`
|
||||||
|
|
||||||
|
| Name | Value | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| Length | `0x0F` | |
|
||||||
|
| Rolling Code 1 | `0xA1` | |
|
||||||
|
| Rolling Code 2 | `0xFF` | |
|
||||||
|
| Physical Source ID | `0x0000` | ID of hub |
|
||||||
|
| Address type | `0x04` | Broadcast |
|
||||||
|
| Logical Source ID | `0x0000` | ID of blind |
|
||||||
|
| Scene ID | `1B` | |
|
||||||
|
| Checksum | `0x446B` | |
|
||||||
|
|
||||||
|
Note: activating scenes is not supported by the library (yet!)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef BUFFERFILLER_H
|
#ifndef BUFFERFILLER_H
|
||||||
#define BUFFERFILLER_H
|
#define BUFFERFILLER_H
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <stdint.h>
|
||||||
#include "PacketCRC.h"
|
#include "PacketCRC.h"
|
||||||
#include "PacketTypes.h"
|
#include "PacketTypes.h"
|
||||||
|
|
||||||
@@ -19,11 +19,11 @@ private:
|
|||||||
|
|
||||||
void setPacketSize(uint8_t *buffer, uint8_t);
|
void setPacketSize(uint8_t *buffer, uint8_t);
|
||||||
void setConstants(uint8_t *buffer);
|
void setConstants(uint8_t *buffer);
|
||||||
void setSourceAddress(uint8_t *buffer, uint16_t);
|
void setSourceAddress(uint8_t *buffer, uint8_t offset, uint16_t source);
|
||||||
void setDestinationAddress(uint8_t *buffer, uint16_t);
|
void setDestinationAddress(uint8_t *buffer, uint8_t offset, uint16_t destination);
|
||||||
void setRollingCodes(uint8_t *buffer, uint8_t rollingCode1, uint8_t rollingCode2);
|
void setRollingCodes(uint8_t *buffer, uint8_t rollingCode1, uint8_t rollingCode2);
|
||||||
void setProtocolVersion(uint8_t *buffer, uint8_t);
|
void setProtocolVersion(uint8_t *buffer, uint8_t);
|
||||||
void setFieldsData(uint8_t *buffer, const FieldsParameters& parameters);
|
void setFieldsData(uint8_t *buffer, uint8_t offset, const FieldsParameters& parameters);
|
||||||
void calculateCRC(uint8_t *buffer);
|
void calculateCRC(uint8_t *buffer);
|
||||||
void incrementRollingCodes();
|
void incrementRollingCodes();
|
||||||
uint8_t calculateTotalFieldSize(const FieldsParameters& parameters);
|
uint8_t calculateTotalFieldSize(const FieldsParameters& parameters);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef PACKET_PARSER_H
|
#ifndef PACKET_PARSER_H
|
||||||
#define PACKET_PARSER_H
|
#define PACKET_PARSER_H
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <vector>
|
||||||
#include "PacketTypes.h"
|
#include "PacketTypes.h"
|
||||||
|
|
||||||
class PacketParser {
|
class PacketParser {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef PACKET_TYPES_H
|
#ifndef PACKET_TYPES_H
|
||||||
#define PACKET_TYPES_H
|
#define PACKET_TYPES_H
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <stdint.h>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
// Define packet types
|
// Define packet types
|
||||||
@@ -9,6 +9,9 @@ enum class PacketType {
|
|||||||
OPEN,
|
OPEN,
|
||||||
CLOSE,
|
CLOSE,
|
||||||
STOP,
|
STOP,
|
||||||
|
OPEN_SLOW,
|
||||||
|
CLOSE_SLOW,
|
||||||
|
MOVE_TO_SAVED_POSITION,
|
||||||
FIELDS,
|
FIELDS,
|
||||||
FIELD_COMMAND,
|
FIELD_COMMAND,
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
@@ -34,11 +37,25 @@ struct FieldsParameters {
|
|||||||
|
|
||||||
using PacketParameters = std::variant<std::monostate, FieldsParameters>;
|
using PacketParameters = std::variant<std::monostate, FieldsParameters>;
|
||||||
|
|
||||||
|
struct BroadcastHeader {
|
||||||
|
uint16_t source;
|
||||||
|
};
|
||||||
|
|
||||||
// Define Message structure
|
struct UnicastHeader {
|
||||||
struct Packet {
|
|
||||||
uint16_t source;
|
uint16_t source;
|
||||||
uint16_t destination;
|
uint16_t destination;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GroupsHeader {
|
||||||
|
uint16_t source;
|
||||||
|
std::vector<uint8_t> groups;
|
||||||
|
};
|
||||||
|
|
||||||
|
using PacketHeader = std::variant<BroadcastHeader, UnicastHeader, GroupsHeader>;
|
||||||
|
|
||||||
|
// Define Packet structure
|
||||||
|
struct Packet {
|
||||||
|
PacketHeader header;
|
||||||
PacketType type;
|
PacketType type;
|
||||||
PacketParameters parameters;
|
PacketParameters parameters;
|
||||||
uint8_t rollingCode1;
|
uint8_t rollingCode1;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef RFPOWERVIEW_H
|
#ifndef RFPOWERVIEW_H
|
||||||
#define RFPOWERVIEW_H
|
#define RFPOWERVIEW_H
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <stdint.h>
|
||||||
#include <RF24.h>
|
#include <RF24.h>
|
||||||
#include "PacketReceiver.h"
|
#include "PacketReceiver.h"
|
||||||
#include "PacketParser.h"
|
#include "PacketParser.h"
|
||||||
@@ -23,7 +23,10 @@ public:
|
|||||||
bool begin();
|
bool begin();
|
||||||
void loop();
|
void loop();
|
||||||
|
|
||||||
void setPacketCallback(std::function<void(const Packet*)> callback);
|
void setPacketReceivedCallback(std::function<void(const Packet*)> callback);
|
||||||
|
void setValidBufferReceivedCallback(std::function<void(const uint8_t*)> callback);
|
||||||
|
void setInvalidBufferReceivedCallback(std::function<void(const uint8_t*)> callback);
|
||||||
|
|
||||||
bool sendPacket(const Packet* packet);
|
bool sendPacket(const Packet* packet);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -34,7 +37,9 @@ private:
|
|||||||
uint8_t irqPin;
|
uint8_t irqPin;
|
||||||
uint8_t rfID[2];
|
uint8_t rfID[2];
|
||||||
|
|
||||||
std::function<void(const Packet*)> packetCallback;
|
std::function<void(const Packet*)> packetReceivedCallback;
|
||||||
|
std::function<void(const uint8_t*)> validBufferReceivedCallback;
|
||||||
|
std::function<void(const uint8_t*)> invalidBufferReceivedCallback;
|
||||||
|
|
||||||
uint8_t sendBuffer[32];
|
uint8_t sendBuffer[32];
|
||||||
|
|
||||||
|
|||||||
49
library.json
49
library.json
@@ -1,27 +1,24 @@
|
|||||||
{
|
{
|
||||||
"name": "RFPowerView",
|
"name": "RFPowerView",
|
||||||
"version": "0.0.1",
|
"version": "0.0.3",
|
||||||
"description": "A library for receiving and sending PowerView packets via an nRF24L01 module",
|
"description": "A library for receiving and sending PowerView packets via an nRF24L01 module",
|
||||||
"keywords": "powerview, hunterdouglas, luxaflex, rf, radio",
|
"keywords": "powerview, hunterdouglas, luxaflex, rf, radio",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mattway.com.au/matt/RFPowerView.git"
|
"url": "https://github.com/mattyway/RFPowerView.git"
|
||||||
},
|
},
|
||||||
"authors":
|
"authors": [
|
||||||
[
|
{
|
||||||
{
|
"name": "Matt Way",
|
||||||
"name": "Matt Way",
|
"email": "mattyway@gmail.com"
|
||||||
"email": "mattyway@gmail.com"
|
}
|
||||||
}
|
],
|
||||||
],
|
"license": "GPL-2.0-only",
|
||||||
"license": "GPL-2.0-only",
|
"dependencies": {
|
||||||
"dependencies": {
|
"robtillaart/CRC": "1.0.2",
|
||||||
"robtillaart/CRC": "1.0.2",
|
"nrf24/RF24": "1.4.8",
|
||||||
"nrf24/RF24": "1.4.8",
|
"rlogiacco/CircularBuffer": "1.3.3"
|
||||||
"rlogiacco/CircularBuffer": "1.3.3"
|
},
|
||||||
},
|
"frameworks": "arduino",
|
||||||
"frameworks": "arduino",
|
"platforms": ["espressif8266"]
|
||||||
"platforms": [
|
}
|
||||||
"espressif8266"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|||||||
33
platformio.ini
Normal file
33
platformio.ini
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:nodemcuv2]
|
||||||
|
platform = espressif8266
|
||||||
|
board = nodemcuv2
|
||||||
|
framework = arduino
|
||||||
|
|
||||||
|
[env]
|
||||||
|
monitor_speed = 115200
|
||||||
|
test_build_src = true
|
||||||
|
test_framework = unity
|
||||||
|
lib_deps =
|
||||||
|
robtillaart/CRC @ 1.0.2
|
||||||
|
nrf24/RF24 @ 1.4.8
|
||||||
|
rlogiacco/CircularBuffer @ 1.3.3
|
||||||
|
|
||||||
|
[env:native]
|
||||||
|
platform = native
|
||||||
|
lib_deps =
|
||||||
|
${env.lib_deps}
|
||||||
|
ArduinoFake
|
||||||
|
build_src_filter =
|
||||||
|
+<**/*.cpp>
|
||||||
|
-<**/RFPowerView.cpp>
|
||||||
|
-<**/PacketReceiver.cpp>
|
||||||
@@ -11,45 +11,86 @@ BufferFiller::~BufferFiller()
|
|||||||
|
|
||||||
bool BufferFiller::fill(uint8_t *buffer, const Packet* packet) {
|
bool BufferFiller::fill(uint8_t *buffer, const Packet* packet) {
|
||||||
setConstants(buffer);
|
setConstants(buffer);
|
||||||
setProtocolVersion(buffer, protocolVersion);
|
|
||||||
setSourceAddress(buffer, packet->source);
|
int dataOffset = -1;
|
||||||
setDestinationAddress(buffer, packet->destination);
|
|
||||||
|
if (std::holds_alternative<BroadcastHeader>(packet->header)) {
|
||||||
|
setProtocolVersion(buffer, 0x04);
|
||||||
|
auto header = std::get<BroadcastHeader>(packet->header);
|
||||||
|
setSourceAddress(buffer, 12, header.source);
|
||||||
|
dataOffset = 14;
|
||||||
|
} else if (std::holds_alternative<UnicastHeader>(packet->header)) {
|
||||||
|
setProtocolVersion(buffer, 0x05);
|
||||||
|
auto header = std::get<UnicastHeader>(packet->header);
|
||||||
|
setDestinationAddress(buffer, 12, header.destination);
|
||||||
|
setSourceAddress(buffer, 14, header.source);
|
||||||
|
dataOffset = 16;
|
||||||
|
} else if (std::holds_alternative<GroupsHeader>(packet->header)) {
|
||||||
|
setProtocolVersion(buffer, 0x06);
|
||||||
|
auto header = std::get<GroupsHeader>(packet->header);
|
||||||
|
int groupsOffset = 12;
|
||||||
|
for (size_t i = 0; i < header.groups.size(); i++) {
|
||||||
|
buffer[groupsOffset] = header.groups[i];
|
||||||
|
groupsOffset++;
|
||||||
|
}
|
||||||
|
buffer[groupsOffset] = 0x00;
|
||||||
|
setSourceAddress(buffer, groupsOffset + 1, header.source);
|
||||||
|
dataOffset = groupsOffset + 3;
|
||||||
|
}
|
||||||
|
|
||||||
switch(packet->type) {
|
switch(packet->type) {
|
||||||
case PacketType::STOP:
|
case PacketType::STOP:
|
||||||
setPacketSize(buffer, 0x11);
|
setPacketSize(buffer, 0x11);
|
||||||
buffer[16] = 0x52;
|
buffer[dataOffset + 0] = 0x52;
|
||||||
buffer[17] = 0x53;
|
buffer[dataOffset + 1] = 0x53;
|
||||||
buffer[18] = 0x00;
|
buffer[dataOffset + 2] = 0x00;
|
||||||
break;
|
break;
|
||||||
case PacketType::CLOSE:
|
case PacketType::CLOSE:
|
||||||
setPacketSize(buffer, 0x11);
|
setPacketSize(buffer, 0x11);
|
||||||
buffer[16] = 0x52;
|
buffer[dataOffset + 0] = 0x52;
|
||||||
buffer[17] = 0x44;
|
buffer[dataOffset + 1] = 0x44;
|
||||||
buffer[18] = 0x00;
|
buffer[dataOffset + 2] = 0x00;
|
||||||
break;
|
break;
|
||||||
case PacketType::OPEN:
|
case PacketType::OPEN:
|
||||||
setPacketSize(buffer, 0x11);
|
setPacketSize(buffer, 0x11);
|
||||||
buffer[16] = 0x52;
|
buffer[dataOffset + 0] = 0x52;
|
||||||
buffer[17] = 0x55;
|
buffer[dataOffset + 1] = 0x55;
|
||||||
buffer[18] = 0x00;
|
buffer[dataOffset + 2] = 0x00;
|
||||||
|
break;
|
||||||
|
case PacketType::CLOSE_SLOW:
|
||||||
|
setPacketSize(buffer, 0x11);
|
||||||
|
buffer[dataOffset + 0] = 0x52;
|
||||||
|
buffer[dataOffset + 1] = 0x4C;
|
||||||
|
buffer[dataOffset + 2] = 0x00;
|
||||||
|
break;
|
||||||
|
case PacketType::OPEN_SLOW:
|
||||||
|
setPacketSize(buffer, 0x11);
|
||||||
|
buffer[dataOffset + 0] = 0x52;
|
||||||
|
buffer[dataOffset + 1] = 0x52;
|
||||||
|
buffer[dataOffset + 2] = 0x00;
|
||||||
|
break;
|
||||||
|
case PacketType::MOVE_TO_SAVED_POSITION:
|
||||||
|
setPacketSize(buffer, 0x11);
|
||||||
|
buffer[dataOffset + 0] = 0x52;
|
||||||
|
buffer[dataOffset + 1] = 0x48;
|
||||||
|
buffer[dataOffset + 2] = 0x00;
|
||||||
break;
|
break;
|
||||||
case PacketType::FIELDS: {
|
case PacketType::FIELDS: {
|
||||||
FieldsParameters parameters = std::get<FieldsParameters>(packet->parameters);
|
FieldsParameters parameters = std::get<FieldsParameters>(packet->parameters);
|
||||||
// 0x10 is the number of bytes without any fields
|
// 0x10 is the number of bytes without any fields
|
||||||
setPacketSize(buffer, 0x10 + calculateTotalFieldSize(parameters));
|
setPacketSize(buffer, 0x10 + calculateTotalFieldSize(parameters));
|
||||||
buffer[16] = 0x21;
|
buffer[dataOffset + 0] = 0x21;
|
||||||
buffer[17] = 0x5A;
|
buffer[dataOffset + 1] = 0x5A;
|
||||||
setFieldsData(buffer, parameters);
|
setFieldsData(buffer, dataOffset + 2, parameters);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PacketType::FIELD_COMMAND: {
|
case PacketType::FIELD_COMMAND: {
|
||||||
FieldsParameters parameters = std::get<FieldsParameters>(packet->parameters);
|
FieldsParameters parameters = std::get<FieldsParameters>(packet->parameters);
|
||||||
// 0x10 is the number of bytes without any fields
|
// 0x10 is the number of bytes without any fields
|
||||||
setPacketSize(buffer, 0x10 + calculateTotalFieldSize(parameters));
|
setPacketSize(buffer, 0x10 + calculateTotalFieldSize(parameters));
|
||||||
buffer[16] = 0x3F;
|
buffer[dataOffset + 0] = 0x3F;
|
||||||
buffer[17] = 0x5A;
|
buffer[dataOffset + 1] = 0x5A;
|
||||||
setFieldsData(buffer, std::get<FieldsParameters>(packet->parameters));
|
setFieldsData(buffer, dataOffset + 2, parameters);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -62,9 +103,7 @@ bool BufferFiller::fill(uint8_t *buffer, const Packet* packet) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BufferFiller::setFieldsData(uint8_t *buffer, const FieldsParameters& parameters) {
|
void BufferFiller::setFieldsData(uint8_t *buffer, uint8_t offset, const FieldsParameters& parameters) {
|
||||||
uint8_t offset = 18;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < parameters.fields.size(); i++) {
|
for (size_t i = 0; i < parameters.fields.size(); i++) {
|
||||||
Field field = parameters.fields[i];
|
Field field = parameters.fields[i];
|
||||||
uint8_t fieldSize = calculateFieldSize(field);
|
uint8_t fieldSize = calculateFieldSize(field);
|
||||||
@@ -113,20 +152,20 @@ void BufferFiller::setConstants(uint8_t *buffer) {
|
|||||||
buffer[9] = 0x86; // Constant?
|
buffer[9] = 0x86; // Constant?
|
||||||
}
|
}
|
||||||
|
|
||||||
void BufferFiller::setSourceAddress(uint8_t *buffer, uint16_t sourceID) {
|
void BufferFiller::setSourceAddress(uint8_t *buffer, uint8_t offset, uint16_t sourceID) {
|
||||||
// Physical source address (could be the address of a repeater when receiving a packet)
|
// Physical source address (could be the address of a repeater when receiving a packet)
|
||||||
buffer[7] = (uint8_t)((sourceID & 0xFF00) >> 8);
|
buffer[7] = (uint8_t)((sourceID & 0xFF00) >> 8);
|
||||||
buffer[8] = (uint8_t)(sourceID & 0x00FF);
|
buffer[8] = (uint8_t)(sourceID & 0x00FF);
|
||||||
|
|
||||||
// Logical source address (usually the same as the physical source address)
|
// Logical source address (usually the same as the physical source address)
|
||||||
buffer[14] = (uint8_t)((sourceID & 0xFF00) >> 8);
|
buffer[offset + 0] = (uint8_t)((sourceID & 0xFF00) >> 8);
|
||||||
buffer[15] = (uint8_t)(sourceID & 0x00FF);
|
buffer[offset + 1] = (uint8_t)(sourceID & 0x00FF);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BufferFiller::setDestinationAddress(uint8_t *buffer, uint16_t targetID) {
|
void BufferFiller::setDestinationAddress(uint8_t *buffer, uint8_t offset, uint16_t targetID) {
|
||||||
// Logical target address
|
// Logical target address
|
||||||
buffer[12] = (uint8_t)((targetID & 0xFF00) >> 8);
|
buffer[offset + 0] = (uint8_t)((targetID & 0xFF00) >> 8);
|
||||||
buffer[13] = (uint8_t)(targetID & 0x00FF);
|
buffer[offset + 1] = (uint8_t)(targetID & 0x00FF);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BufferFiller::setRollingCodes(uint8_t *buffer, uint8_t rollingCode1, uint8_t rollingCode2) {
|
void BufferFiller::setRollingCodes(uint8_t *buffer, uint8_t rollingCode1, uint8_t rollingCode2) {
|
||||||
|
|||||||
@@ -10,27 +10,65 @@ PacketParser::~PacketParser()
|
|||||||
|
|
||||||
bool PacketParser::parsePacket(const uint8_t *buffer, Packet& packet)
|
bool PacketParser::parsePacket(const uint8_t *buffer, Packet& packet)
|
||||||
{
|
{
|
||||||
packet.source = (uint16_t)(buffer[14] << 8 | buffer[15]);
|
int dataOffset = -1;
|
||||||
packet.destination = (uint16_t)(buffer[12] << 8 | buffer[13]);
|
if (buffer[10] == 0x04) {
|
||||||
|
BroadcastHeader header{};
|
||||||
|
header.source = (uint16_t)(buffer[12] << 8 | buffer[13]);
|
||||||
|
packet.header = header;
|
||||||
|
dataOffset = 14;
|
||||||
|
} else if(buffer[10] == 0x05) {
|
||||||
|
UnicastHeader header{};
|
||||||
|
header.destination = (uint16_t)(buffer[12] << 8 | buffer[13]);
|
||||||
|
header.source = (uint16_t)(buffer[14] << 8 | buffer[15]);
|
||||||
|
packet.header = header;
|
||||||
|
dataOffset = 16;
|
||||||
|
} else if (buffer[10] == 0x06) {
|
||||||
|
GroupsHeader header{};
|
||||||
|
size_t i = 12;
|
||||||
|
uint8_t length = buffer[1] + 2;
|
||||||
|
while (i < length) {
|
||||||
|
if (buffer[i] == 0x00) {
|
||||||
|
dataOffset = i + 3;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
header.groups.push_back(buffer[i]);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (i == length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
header.source = (uint16_t)(buffer[i + 1] << 8 | buffer[i + 2]);
|
||||||
|
packet.header = header;
|
||||||
|
}
|
||||||
|
|
||||||
packet.rollingCode1 = buffer[4];
|
packet.rollingCode1 = buffer[4];
|
||||||
packet.rollingCode2 = buffer[11];
|
packet.rollingCode2 = buffer[11];
|
||||||
|
|
||||||
if (buffer[16] == 0x52 && buffer[17] == 0x53) {
|
if (buffer[dataOffset + 0] == 0x52 && buffer[dataOffset + 1] == 0x53) {
|
||||||
packet.type = PacketType::STOP;
|
packet.type = PacketType::STOP;
|
||||||
packet.parameters = std::monostate{};
|
packet.parameters = std::monostate{};
|
||||||
} else if (buffer[16] == 0x52 && buffer[17] == 0x44) {
|
} else if (buffer[dataOffset + 0] == 0x52 && buffer[dataOffset + 1] == 0x44) {
|
||||||
packet.type = PacketType::CLOSE;
|
packet.type = PacketType::CLOSE;
|
||||||
packet.parameters = std::monostate{};
|
packet.parameters = std::monostate{};
|
||||||
} else if (buffer[16] == 0x52 && buffer[17] == 0x55) {
|
} else if (buffer[dataOffset + 0] == 0x52 && buffer[dataOffset + 1] == 0x55) {
|
||||||
packet.type = PacketType::OPEN;
|
packet.type = PacketType::OPEN;
|
||||||
packet.parameters = std::monostate{};
|
packet.parameters = std::monostate{};
|
||||||
} else if (buffer[16] == 0x21 && buffer[17] == 0x5A) {
|
} else if (buffer[dataOffset + 0] == 0x52 && buffer[dataOffset + 1] == 0x4C) {
|
||||||
|
packet.type = PacketType::CLOSE_SLOW;
|
||||||
|
packet.parameters = std::monostate{};
|
||||||
|
} else if (buffer[dataOffset + 0] == 0x52 && buffer[dataOffset + 1] == 0x52) {
|
||||||
|
packet.type = PacketType::OPEN_SLOW;
|
||||||
|
packet.parameters = std::monostate{};
|
||||||
|
} else if (buffer[dataOffset + 0] == 0x52 && buffer[dataOffset + 1] == 0x48) {
|
||||||
|
packet.type = PacketType::MOVE_TO_SAVED_POSITION;
|
||||||
|
packet.parameters = std::monostate{};
|
||||||
|
} else if (buffer[dataOffset + 0] == 0x21 && buffer[dataOffset + 1] == 0x5A) {
|
||||||
packet.type = PacketType::FIELDS;
|
packet.type = PacketType::FIELDS;
|
||||||
std::vector<Field> fields;
|
std::vector<Field> fields;
|
||||||
parseFields(buffer, fields);
|
parseFields(buffer, fields);
|
||||||
packet.parameters = FieldsParameters{fields};
|
packet.parameters = FieldsParameters{fields};
|
||||||
} else if (buffer[16] == 0x3F && buffer[17] == 0x5A) {
|
} else if (buffer[dataOffset + 0] == 0x3F && buffer[dataOffset + 1] == 0x5A) {
|
||||||
packet.type = PacketType::FIELD_COMMAND;
|
packet.type = PacketType::FIELD_COMMAND;
|
||||||
std::vector<Field> fields;
|
std::vector<Field> fields;
|
||||||
parseFields(buffer, fields);
|
parseFields(buffer, fields);
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ RFPowerView::RFPowerView(uint8_t cePin, uint8_t csPin, uint8_t irqPin, uint16_t
|
|||||||
bufferFiller(0x05),
|
bufferFiller(0x05),
|
||||||
irqPin(irqPin),
|
irqPin(irqPin),
|
||||||
rfID{static_cast<uint8_t>(rfID & 0xFF), static_cast<uint8_t>(rfID >> 8)},
|
rfID{static_cast<uint8_t>(rfID & 0xFF), static_cast<uint8_t>(rfID >> 8)},
|
||||||
packetCallback(nullptr) {}
|
packetReceivedCallback(nullptr),
|
||||||
|
validBufferReceivedCallback(nullptr),
|
||||||
|
invalidBufferReceivedCallback(nullptr) {}
|
||||||
|
|
||||||
bool RFPowerView::begin() {
|
bool RFPowerView::begin() {
|
||||||
if (!radio.begin()) {
|
if (!radio.begin()) {
|
||||||
@@ -60,21 +62,35 @@ void RFPowerView::interruptHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RFPowerView::processBuffer(const uint8_t *buffer) {
|
void RFPowerView::processBuffer(const uint8_t *buffer) {
|
||||||
|
if (validBufferReceivedCallback != nullptr) {
|
||||||
|
validBufferReceivedCallback(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
Packet packet;
|
Packet packet;
|
||||||
bool result = packetParser.parsePacket(buffer, packet);
|
bool result = packetParser.parsePacket(buffer, packet);
|
||||||
if (result) {
|
if (result) {
|
||||||
if (packetCallback != nullptr) {
|
if (packetReceivedCallback != nullptr) {
|
||||||
packetCallback(&packet);
|
packetReceivedCallback(&packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RFPowerView::processInvalidBuffer(const uint8_t *buffer) {
|
void RFPowerView::processInvalidBuffer(const uint8_t *buffer) {
|
||||||
|
if (invalidBufferReceivedCallback != nullptr) {
|
||||||
|
invalidBufferReceivedCallback(buffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RFPowerView::setPacketCallback(std::function<void(const Packet*)> callback) {
|
void RFPowerView::setPacketReceivedCallback(std::function<void(const Packet*)> callback) {
|
||||||
packetCallback = callback;
|
packetReceivedCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RFPowerView::setValidBufferReceivedCallback(std::function<void(const uint8_t*)> callback) {
|
||||||
|
validBufferReceivedCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RFPowerView::setInvalidBufferReceivedCallback(std::function<void(const uint8_t*)> callback) {
|
||||||
|
invalidBufferReceivedCallback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RFPowerView::sendPacket(const Packet* packet) {
|
bool RFPowerView::sendPacket(const Packet* packet) {
|
||||||
|
|||||||
29
test/hex_helper.h
Normal file
29
test/hex_helper.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef HEX_HELPER_H
|
||||||
|
#define HEX_HELPER_H
|
||||||
|
|
||||||
|
#include <cstring> // for strlen
|
||||||
|
|
||||||
|
uint8_t* hex_string_to_array(const char* str) {
|
||||||
|
size_t len = strlen(str);
|
||||||
|
if (len % 2 != 0) {
|
||||||
|
// Cannot handle odd string length
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
size_t data_len = len / 2;
|
||||||
|
|
||||||
|
uint8_t* data = new uint8_t[data_len];
|
||||||
|
|
||||||
|
// Convert each hexadecimal character pair to a byte
|
||||||
|
for (size_t i = 0; i < data_len; ++i) {
|
||||||
|
uint8_t high_nibble = str[i * 2] >= 'A' ? (str[i * 2] - 'A' + 10) : (str[i * 2] - '0');
|
||||||
|
uint8_t low_nibble = str[i * 2 + 1] >= 'A' ? (str[i * 2 + 1] - 'A' + 10) : (str[i * 2 + 1] - '0');
|
||||||
|
|
||||||
|
// Combine the nibbles into a byte
|
||||||
|
data[i] = (high_nibble << 4) | low_nibble;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the pointer to the allocated data array
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // HEX_HELPER_H
|
||||||
111
test/test_crc/test_crc.cpp
Normal file
111
test/test_crc/test_crc.cpp
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
#include <unity.h>
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "../hex_helper.h"
|
||||||
|
#include "PacketCRC.h"
|
||||||
|
|
||||||
|
void setUp()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void tearDown()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void run_calculate_test(const uint8_t *packet_data, const uint16_t expected_checksum)
|
||||||
|
{
|
||||||
|
PacketCRC packetCRC;
|
||||||
|
|
||||||
|
uint16_t actual_checksum = packetCRC.calculate(packet_data);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_HEX16(expected_checksum, actual_checksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_calculate_withDataLengthOf15()
|
||||||
|
{
|
||||||
|
const uint8_t* packet_data = hex_string_to_array("C00F0005A1FFFF00008604FF000053471B");
|
||||||
|
const uint16_t expected_checksum = 0x446B;
|
||||||
|
|
||||||
|
run_calculate_test(packet_data, expected_checksum);
|
||||||
|
|
||||||
|
delete[] packet_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_calculate_withDataLengthOf17()
|
||||||
|
{
|
||||||
|
const uint8_t* packet_data = hex_string_to_array("C0110005D7FFFF369E8606B30400369E525300");
|
||||||
|
const uint16_t expected_checksum = 0xE04C;
|
||||||
|
|
||||||
|
run_calculate_test(packet_data, expected_checksum);
|
||||||
|
|
||||||
|
delete[] packet_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_calculate_withDataLengthOf19()
|
||||||
|
{
|
||||||
|
const uint8_t* packet_data = hex_string_to_array("C0130005DDFFFF00008605364EF100003F5A023F42");
|
||||||
|
const uint16_t expected_checksum = 0x9BD3;
|
||||||
|
|
||||||
|
run_calculate_test(packet_data, expected_checksum);
|
||||||
|
|
||||||
|
delete[] packet_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_calculate_withDataLengthOf20()
|
||||||
|
{
|
||||||
|
const uint8_t* packet_data = hex_string_to_array("C014100559FFFF4EF18605C200004EF1215A0321429C");
|
||||||
|
const uint16_t expected_checksum = 0x4A8B;
|
||||||
|
|
||||||
|
run_calculate_test(packet_data, expected_checksum);
|
||||||
|
|
||||||
|
delete[] packet_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_calculate_withDataLengthOf21()
|
||||||
|
{
|
||||||
|
const uint8_t* packet_data = hex_string_to_array("C0151005EAFFFF4EF186052400004EF121464E98070801");
|
||||||
|
const uint16_t expected_checksum = 0x9887;
|
||||||
|
|
||||||
|
run_calculate_test(packet_data, expected_checksum);
|
||||||
|
|
||||||
|
delete[] packet_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_calculate_withDataLengthOf25()
|
||||||
|
{
|
||||||
|
const uint8_t* packet_data = hex_string_to_array("C019000547FFFF000086053D4EF100003F5A023F50023F4D023F54");
|
||||||
|
const uint16_t expected_checksum = 0x8318;
|
||||||
|
|
||||||
|
run_calculate_test(packet_data, expected_checksum);
|
||||||
|
|
||||||
|
delete[] packet_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
int runUnityTests(void)
|
||||||
|
{
|
||||||
|
UNITY_BEGIN();
|
||||||
|
RUN_TEST(test_calculate_withDataLengthOf15);
|
||||||
|
RUN_TEST(test_calculate_withDataLengthOf17);
|
||||||
|
RUN_TEST(test_calculate_withDataLengthOf19);
|
||||||
|
RUN_TEST(test_calculate_withDataLengthOf20);
|
||||||
|
RUN_TEST(test_calculate_withDataLengthOf21);
|
||||||
|
RUN_TEST(test_calculate_withDataLengthOf25);
|
||||||
|
return UNITY_END();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For Arduino framework
|
||||||
|
*/
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
// Wait ~2 seconds before the Unity test runner
|
||||||
|
// establishes connection with a board Serial interface
|
||||||
|
delay(2000);
|
||||||
|
|
||||||
|
runUnityTests();
|
||||||
|
}
|
||||||
|
void loop() {}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
return runUnityTests();
|
||||||
|
}
|
||||||
186
test/test_packet_parser/test_packet_parser.cpp
Normal file
186
test/test_packet_parser/test_packet_parser.cpp
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
#include <unity.h>
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "../hex_helper.h"
|
||||||
|
#include "PacketParser.h"
|
||||||
|
|
||||||
|
void setUp()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void tearDown()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void run_parse_test(const uint8_t *packet_data, Packet &packet)
|
||||||
|
{
|
||||||
|
PacketParser packetParser;
|
||||||
|
|
||||||
|
bool result = packetParser.parsePacket(packet_data, packet);
|
||||||
|
|
||||||
|
TEST_ASSERT_TRUE_MESSAGE(result, "Unable to parse packet_data");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_parse_stop()
|
||||||
|
{
|
||||||
|
const uint8_t* packet_data = hex_string_to_array("C01100056CFFFF369E86063C0400369E525300");
|
||||||
|
|
||||||
|
Packet packet;
|
||||||
|
|
||||||
|
run_parse_test(packet_data, packet);
|
||||||
|
|
||||||
|
TEST_ASSERT_TRUE(packet.type == PacketType::STOP);
|
||||||
|
|
||||||
|
delete[] packet_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_parse_close()
|
||||||
|
{
|
||||||
|
const uint8_t* packet_data = hex_string_to_array("C01100056CFFFF369E86063C0400369E524400");
|
||||||
|
|
||||||
|
Packet packet;
|
||||||
|
|
||||||
|
run_parse_test(packet_data, packet);
|
||||||
|
|
||||||
|
TEST_ASSERT_TRUE(packet.type == PacketType::CLOSE);
|
||||||
|
|
||||||
|
delete[] packet_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_parse_open()
|
||||||
|
{
|
||||||
|
const uint8_t* packet_data = hex_string_to_array("C01100056CFFFF369E86063C0400369E525500");
|
||||||
|
|
||||||
|
Packet packet;
|
||||||
|
|
||||||
|
run_parse_test(packet_data, packet);
|
||||||
|
|
||||||
|
TEST_ASSERT_TRUE(packet.type == PacketType::OPEN);
|
||||||
|
|
||||||
|
delete[] packet_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_parse_rolling_codes()
|
||||||
|
{
|
||||||
|
const uint8_t* packet_data = hex_string_to_array("C01100056CFFFF369E86063C0400369E525300");
|
||||||
|
Packet packet;
|
||||||
|
|
||||||
|
run_parse_test(packet_data, packet);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_HEX8_MESSAGE(0x6C, packet.rollingCode1, "Rolling Code 1");
|
||||||
|
TEST_ASSERT_EQUAL_HEX8_MESSAGE(0x3C, packet.rollingCode2, "Rolling Code 2");
|
||||||
|
|
||||||
|
delete[] packet_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_broadcast_source_address()
|
||||||
|
{
|
||||||
|
const uint8_t* packet_data = hex_string_to_array("C01100056CFFFF369E86063C0400369E525300");
|
||||||
|
|
||||||
|
Packet packet;
|
||||||
|
|
||||||
|
run_parse_test(packet_data, packet);
|
||||||
|
|
||||||
|
auto header = std::get<GroupsHeader>(packet.header);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_HEX16(0x369E, header.source);
|
||||||
|
|
||||||
|
delete[] packet_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_broadcast_single_group()
|
||||||
|
{
|
||||||
|
const uint8_t* packet_data = hex_string_to_array("C01100056CFFFF369E86063C0400369E525300");
|
||||||
|
|
||||||
|
Packet packet;
|
||||||
|
|
||||||
|
run_parse_test(packet_data, packet);
|
||||||
|
|
||||||
|
auto header = std::get<GroupsHeader>(packet.header);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_MESSAGE(1, header.groups.size(), "Group count");
|
||||||
|
TEST_ASSERT_EQUAL_INT8_MESSAGE(4, header.groups[0], "Group at offset 0");
|
||||||
|
|
||||||
|
delete[] packet_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_broadcast_multiple_groups()
|
||||||
|
{
|
||||||
|
const uint8_t* packet_data = hex_string_to_array("C01100056CFFFF369E86063C03040500369E525300");
|
||||||
|
|
||||||
|
Packet packet;
|
||||||
|
|
||||||
|
run_parse_test(packet_data, packet);
|
||||||
|
|
||||||
|
auto header = std::get<GroupsHeader>(packet.header);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_MESSAGE(3, header.groups.size(), "Group count");
|
||||||
|
TEST_ASSERT_EQUAL_INT8_MESSAGE(3, header.groups[0], "Group at offset 0");
|
||||||
|
TEST_ASSERT_EQUAL_INT8_MESSAGE(4, header.groups[1], "Group at offset 1");
|
||||||
|
TEST_ASSERT_EQUAL_INT8_MESSAGE(5, header.groups[2], "Group at offset 2");
|
||||||
|
|
||||||
|
delete[] packet_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_unicast_source_address()
|
||||||
|
{
|
||||||
|
const uint8_t* packet_data = hex_string_to_array("C019000592FFFF72CB85054E4EF100003F5A023F50023F4D023F54");
|
||||||
|
|
||||||
|
Packet packet;
|
||||||
|
|
||||||
|
run_parse_test(packet_data, packet);
|
||||||
|
|
||||||
|
auto header = std::get<UnicastHeader>(packet.header);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_HEX16(0x0000, header.source);
|
||||||
|
|
||||||
|
delete[] packet_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_unicast_destination_address()
|
||||||
|
{
|
||||||
|
const uint8_t* packet_data = hex_string_to_array("C019000592FFFF72CB85054E4EF100003F5A023F50023F4D023F54");
|
||||||
|
|
||||||
|
Packet packet;
|
||||||
|
|
||||||
|
run_parse_test(packet_data, packet);
|
||||||
|
|
||||||
|
auto header = std::get<UnicastHeader>(packet.header);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_HEX16(0x4EF1, header.destination);
|
||||||
|
|
||||||
|
delete[] packet_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
int runUnityTests(void)
|
||||||
|
{
|
||||||
|
UNITY_BEGIN();
|
||||||
|
RUN_TEST(test_parse_stop);
|
||||||
|
RUN_TEST(test_parse_close);
|
||||||
|
RUN_TEST(test_parse_open);
|
||||||
|
RUN_TEST(test_parse_rolling_codes);
|
||||||
|
RUN_TEST(test_broadcast_source_address);
|
||||||
|
RUN_TEST(test_broadcast_single_group);
|
||||||
|
RUN_TEST(test_broadcast_multiple_groups);
|
||||||
|
RUN_TEST(test_unicast_source_address);
|
||||||
|
RUN_TEST(test_unicast_destination_address);
|
||||||
|
return UNITY_END();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For Arduino framework
|
||||||
|
*/
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
// Wait ~2 seconds before the Unity test runner
|
||||||
|
// establishes connection with a board Serial interface
|
||||||
|
delay(2000);
|
||||||
|
|
||||||
|
runUnityTests();
|
||||||
|
}
|
||||||
|
void loop() {}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
return runUnityTests();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user