Compare commits

29 Commits

Author SHA1 Message Date
414d01382c Update versions of actions 2024-04-20 12:14:40 +10:00
70718fae26 Update README to reflect activate scene command being supported 2024-04-20 12:12:27 +10:00
4dce81046c Updated README with more details about fields packets 2024-04-20 12:10:14 +10:00
dd788c203a Add test for filling a fields packet that sets values 2024-04-20 12:09:36 +10:00
2a09f770ca Updated comment about a particular byte offset 2024-04-20 11:50:42 +10:00
c72126ab4c Add support for filling activate scene packets 2024-04-20 11:46:32 +10:00
c514769929 Add an additional helper for setting packet size based on length of data 2024-04-20 11:46:14 +10:00
0a1359572f Add initial test for filling fields 2024-04-20 11:44:16 +10:00
9417db243f Avoid crashing if an incorrect parameter is passed to BufferFiller 2024-04-20 11:31:45 +10:00
195bcb7726 Add initial tests for BufferFiller 2024-04-20 11:30:43 +10:00
ec50685c51 Remove protocolVersion field from BufferFiller 2024-04-19 15:35:17 +10:00
5289e91525 Add tests for parsing packets containing fields 2024-04-19 15:08:16 +10:00
80b40cafcb Add support for parsing activate scene packets 2024-04-19 15:07:47 +10:00
ce46892fde Add test for parsing broadcast source ID 2024-04-19 15:07:12 +10:00
e2d8358e4f FIx wrong name for groups tests 2024-04-19 15:05:45 +10:00
797a104f1f Improve examples of packets and add small code sample 2024-04-18 01:03:44 +10:00
08f89c6baf Include cstddef in PacketParser.cpp to fix error about size_t
Some checks failed
RFPowerView CI / build (push) Failing after 7s
2024-04-13 17:47:50 +10:00
4153487f7a Add build.yaml for Github 2024-04-13 17:47:50 +10:00
cbf188e554 Fixes to readme 2024-04-13 17:35:57 +10:00
f069022f5c Document protocol and added examples 2024-04-13 17:27:15 +10:00
a398a0b679 Add more parser tests 2024-04-13 17:26:52 +10:00
168c1414bd Improve failure message when a packet parser test fails 2024-04-13 17:25:49 +10:00
83f6e8fa52 Improve failure message when a CRC test fails 2024-04-13 17:25:33 +10:00
5b6d951cbc Simplify test setup using a helper to convert hex strings to byte arrays 2024-04-13 17:25:13 +10:00
88caab56a2 Commit generated .vscode/extensions.json 2024-04-10 21:14:04 +10:00
d3325b71cd Fixes to unit tests to work on native platform 2024-04-10 21:13:45 +10:00
3d315be27f Add a native environment to platformio.ini 2024-04-10 21:12:33 +10:00
71dbf1f4ca Rename runParseUnityTests to just runUnityTests 2024-04-10 21:11:12 +10:00
7f7e02a8b7 Avoid including Arduino.h where possible 2024-04-10 21:10:31 +10:00
14 changed files with 924 additions and 41 deletions

18
.github/workflows/build.yaml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: RFPowerView CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install PlatformIO
run: pip install --upgrade platformio
- name: Run RFPowerView tests
run: pio test --environment native

10
.vscode/extensions.json vendored Normal file
View 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"
]
}

241
README.md
View File

@@ -1,4 +1,243 @@
# RFPowerView # RFPowerView
A library for receiving and sending PowerView packets via an nRF24L01 module 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
- Activate scene
## Code Sample
```
#include <Arduino.h>
#include <RFPowerView.h>
// These define which pins should be used to communicate with the nRF24L01 board
#define RF_CE_PIN 5
#define RF_CS_PIN 15
#define RF_IRQ_PIN 4
// Copied from Powerview Hub userdata API. Open http://POWERVIEW_HUB_IP_ADDRESS/api/userdata/ and find the field labeled "rfID"
#define RF_ID 0x2ECB
RFPowerView powerView(RF_CE_PIN, RF_CS_PIN, RF_IRQ_PIN, RF_ID);
void processPacket(const Packet*);
void setup() {
powerView.setPacketReceivedCallback(processPacket);
if (!powerView.begin()) {
// Failed to start RFPowerView
return;
}
}
void loop() {
powerView.loop();
}
void processPacket(const Packet *packet) {
// TODO: React to packet
}
```
See [PowerViewSniffer](https://github.com/mattyway/PowerViewSniffer/) for a more complete example.
## 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 | Unknown | This byte might indicate if the packet was retransmitted by a repeater | `0x86` (when sent from hub or remote), `0x85` (when sent from repeater) |
| 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 or when a blind is communicating with a hub.
#### 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 | Packet Type | Indicates the payloads contains "fields" | `0x3F` (command), `0x21` (report) |
| 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 | Indicates how value should be used | `0x3F` (fetch), `0x40` (set), `0x21` (value) |
| 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` |
## Sample Packets
### Open blinds in group 4
```
C01100056CFFFF369E86063C0400369E525500B988
```
| Byte Offset | Name | Value | Notes |
|---|---|---|---|
| 1 | Length | `0x11` | |
| 4 | Rolling Code 1 | `0x6C` | |
| 7 | Physical Source ID | `0x369E` | ID of pebble remote |
| 10 | Address type | `0x06` | Using Groups address type |
| 11 | Rolling Code 2 | `0x3C` | |
| 12 | Groups | 4 | Only a single group specified |
| 14 | Logical Source ID | `0x369E` | ID of pebble remote (same as Physical Source ID) |
| 19 | Checksum | `0xB988` | |
### Hub requesting values from a blind
```
C019000592FFFF72CB85054E4EF100003F5A023F50023F4D023F54C9F3
```
| Byte Offset | Name | Value | Notes |
|---|---|---|---|
| 1 | Length | `0x19` | |
| 4 | Rolling Code 1 | `0x92` | |
| 7 | Physical Source ID | `0x72CB` | ID of repeater |
| 10 | Address type | `0x05` | Using Unicast address type |
| 11 | Rolling Code 2 | `0x4E` | |
| 12 | Destination ID | `0x4EF1` | ID of blind |
| 14 | Logical Source ID | `0x0000` | ID of hub (different than Physical Source ID) |
| 18 | Field 1 | `023F50` | Field of 2 bytes, Mode = `0x3F` (fetch), ID = `0x50` (position) |
| 21 | Field 2 | `023F4D` | Field of 2 bytes, Mode = `0x3F` (fetch), ID = `0x4D` (unknown) |
| 24 | Field 3 | `023F54` | Field of 2 bytes, Mode = `0x3F` (fetch), ID = `0x54` (unknown) |
| 27 | Checksum | `0xC9F3` | |
### Blind reporting position value to hub
```
C0151005E0FFFF4EF186051A00004EF1215A04215040016670
```
| Byte Offset | Name | Value | Notes |
|---|---|---|---|
| 1 | Length | `0x15` | |
| 4 | Rolling Code 1 | `0xE0` | |
| 7 | Physical Source ID | `0x4EF1` | ID of blind |
| 10 | Address type | `0x05` | Using Unicast address type |
| 11 | Rolling Code 2 | `0x1A` | |
| 12 | Destination ID | `0x0000` | ID of hub |
| 14 | Logical Source ID | `0x4EF1` | ID of blind |
| 18 | Field 1 | `0421504001` | Field of 4 bytes, Mode = `0x21` (value), ID = `0x50` (position), Value = `0x4001` |
| 23 | Checksum | `0x6670` | |
### Blind reporting battery level value to hub
```
C014100558FFFF4EF18605C100004EF1215A0321429DEC23
```
| Byte Offset | Name | Value | Notes |
|---|---|---|---|
| 1 | Length | `0x14` | |
| 4 | Rolling Code 1 | `0x58` | |
| 7 | Physical Source ID | `0x4EF1` | ID of blind |
| 10 | Address type | `0x05` | Using Unicast address type |
| 11 | Rolling Code 2 | `0xC1` | |
| 12 | Destination ID | `0x0000` | ID of hub |
| 14 | Logical Source ID | `0x4EF1` | ID of blind |
| 18 | Field 1 | `0321429D` | Field of 3 bytes, Mode = `0x21` (value), ID = `0x42` (battery level), Value = `0x9D` |
| 22 | Checksum | `0xEC23` | |
### Hub setting position of a blind
```
C015000529FFFF00008605B94EF100003F5A044050FFFF286B
```
| Byte Offset | Name | Value | Notes |
|---|---|---|---|
| 1 | Length | `0x15` | |
| 4 | Rolling Code 1 | `0x29` | |
| 7 | Physical Source ID | `0x0000` | ID of hub |
| 10 | Address type | `0x05` | Using Unicast address type |
| 11 | Rolling Code 2 | `0xB9` | |
| 12 | Destination ID | `0x4EF1` | ID of blind |
| 14 | Logical Source ID | `0x0000` | ID of hub |
| 18 | Field 1 | `044050FFFF` | Field of 4 bytes, Mode = `0x40` (set), ID = `0x50` (position), Value = `0xFFFF` (100% open) |
| 23 | Checksum | `0x286B` | |
### Hub activating a scene
```
C00F0005A1FFFF00008604FF000053471B446B
```
| Byte Offset | Name | Value | Notes |
|---|---|---|---|
| 1 | Length | `0x0F` | |
| 4 | Rolling Code 1 | `0xA1` | |
| 7 | Physical Source ID | `0x0000` | ID of hub |
| 10 | Address type | `0x04` | Using Broadcast address type |
| 11 | Rolling Code 2 | `0xFF` | |
| 12 | Logical Source ID | `0x0000` | ID of hub |
| 16 | Scene ID | `1B` | |
| 17 | Checksum | `0x446B` | |

View File

@@ -1,23 +1,22 @@
#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"
class BufferFiller { class BufferFiller {
public: public:
BufferFiller(uint8_t protocolVersion); BufferFiller();
~BufferFiller(); ~BufferFiller();
bool fill(uint8_t *buffer, const Packet* packet); bool fill(uint8_t *buffer, const Packet* packet);
private: private:
uint8_t protocolVersion;
PacketCRC packetCRC; PacketCRC packetCRC;
void setPacketSize(uint8_t *buffer, uint8_t); void setPacketSize(uint8_t *buffer, uint8_t size);
void setPacketSize(uint8_t *buffer, uint8_t dataOffset, uint8_t dataLength);
void setConstants(uint8_t *buffer); void setConstants(uint8_t *buffer);
void setSourceAddress(uint8_t *buffer, uint8_t offset, uint16_t source); void setSourceAddress(uint8_t *buffer, uint8_t offset, uint16_t source);
void setDestinationAddress(uint8_t *buffer, uint8_t offset, uint16_t destination); void setDestinationAddress(uint8_t *buffer, uint8_t offset, uint16_t destination);

View File

@@ -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 {

View File

@@ -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
@@ -14,6 +14,7 @@ enum class PacketType {
MOVE_TO_SAVED_POSITION, MOVE_TO_SAVED_POSITION,
FIELDS, FIELDS,
FIELD_COMMAND, FIELD_COMMAND,
ACTIVATE_SCENE,
UNKNOWN UNKNOWN
}; };
@@ -35,7 +36,11 @@ struct FieldsParameters {
std::vector<Field> fields; std::vector<Field> fields;
}; };
using PacketParameters = std::variant<std::monostate, FieldsParameters>; struct ActivateSceneParameters {
uint8_t sceneID;
};
using PacketParameters = std::variant<std::monostate, FieldsParameters, ActivateSceneParameters>;
struct BroadcastHeader { struct BroadcastHeader {
uint16_t source; uint16_t source;

View File

@@ -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"

View File

@@ -21,3 +21,13 @@ lib_deps =
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
[env:native]
platform = native
lib_deps =
${env.lib_deps}
ArduinoFake
build_src_filter =
+<**/*.cpp>
-<**/RFPowerView.cpp>
-<**/PacketReceiver.cpp>

View File

@@ -1,7 +1,6 @@
#include "BufferFiller.h" #include "BufferFiller.h"
BufferFiller::BufferFiller(uint8_t protocolVersion) : BufferFiller::BufferFiller()
protocolVersion(protocolVersion)
{ {
} }
@@ -40,59 +39,74 @@ bool BufferFiller::fill(uint8_t *buffer, const Packet* packet) {
switch(packet->type) { switch(packet->type) {
case PacketType::STOP: case PacketType::STOP:
setPacketSize(buffer, 0x11); setPacketSize(buffer, dataOffset, 3);
buffer[dataOffset + 0] = 0x52; buffer[dataOffset + 0] = 0x52;
buffer[dataOffset + 1] = 0x53; buffer[dataOffset + 1] = 0x53;
buffer[dataOffset + 2] = 0x00; buffer[dataOffset + 2] = 0x00;
break; break;
case PacketType::CLOSE: case PacketType::CLOSE:
setPacketSize(buffer, 0x11); setPacketSize(buffer, dataOffset, 3);
buffer[dataOffset + 0] = 0x52; buffer[dataOffset + 0] = 0x52;
buffer[dataOffset + 1] = 0x44; buffer[dataOffset + 1] = 0x44;
buffer[dataOffset + 2] = 0x00; buffer[dataOffset + 2] = 0x00;
break; break;
case PacketType::OPEN: case PacketType::OPEN:
setPacketSize(buffer, 0x11); setPacketSize(buffer, dataOffset, 3);
buffer[dataOffset + 0] = 0x52; buffer[dataOffset + 0] = 0x52;
buffer[dataOffset + 1] = 0x55; buffer[dataOffset + 1] = 0x55;
buffer[dataOffset + 2] = 0x00; buffer[dataOffset + 2] = 0x00;
break; break;
case PacketType::CLOSE_SLOW: case PacketType::CLOSE_SLOW:
setPacketSize(buffer, 0x11); setPacketSize(buffer, dataOffset, 3);
buffer[dataOffset + 0] = 0x52; buffer[dataOffset + 0] = 0x52;
buffer[dataOffset + 1] = 0x4C; buffer[dataOffset + 1] = 0x4C;
buffer[dataOffset + 2] = 0x00; buffer[dataOffset + 2] = 0x00;
break; break;
case PacketType::OPEN_SLOW: case PacketType::OPEN_SLOW:
setPacketSize(buffer, 0x11); setPacketSize(buffer, dataOffset, 3);
buffer[dataOffset + 0] = 0x52; buffer[dataOffset + 0] = 0x52;
buffer[dataOffset + 1] = 0x52; buffer[dataOffset + 1] = 0x52;
buffer[dataOffset + 2] = 0x00; buffer[dataOffset + 2] = 0x00;
break; break;
case PacketType::MOVE_TO_SAVED_POSITION: case PacketType::MOVE_TO_SAVED_POSITION:
setPacketSize(buffer, 0x11); setPacketSize(buffer, dataOffset, 3);
buffer[dataOffset + 0] = 0x52; buffer[dataOffset + 0] = 0x52;
buffer[dataOffset + 1] = 0x48; buffer[dataOffset + 1] = 0x48;
buffer[dataOffset + 2] = 0x00; buffer[dataOffset + 2] = 0x00;
break; break;
case PacketType::FIELDS: { case PacketType::FIELDS: {
if (!std::holds_alternative<FieldsParameters>(packet->parameters)) {
return false;
}
FieldsParameters parameters = std::get<FieldsParameters>(packet->parameters); FieldsParameters parameters = std::get<FieldsParameters>(packet->parameters);
// 0x10 is the number of bytes without any fields setPacketSize(buffer, dataOffset, calculateTotalFieldSize(parameters));
setPacketSize(buffer, 0x10 + calculateTotalFieldSize(parameters));
buffer[dataOffset + 0] = 0x21; buffer[dataOffset + 0] = 0x21;
buffer[dataOffset + 1] = 0x5A; buffer[dataOffset + 1] = 0x5A;
setFieldsData(buffer, dataOffset + 2, parameters); setFieldsData(buffer, dataOffset + 2, parameters);
break; break;
} }
case PacketType::FIELD_COMMAND: { case PacketType::FIELD_COMMAND: {
if (!std::holds_alternative<FieldsParameters>(packet->parameters)) {
return false;
}
FieldsParameters parameters = std::get<FieldsParameters>(packet->parameters); FieldsParameters parameters = std::get<FieldsParameters>(packet->parameters);
// 0x10 is the number of bytes without any fields setPacketSize(buffer, dataOffset, calculateTotalFieldSize(parameters));
setPacketSize(buffer, 0x10 + calculateTotalFieldSize(parameters));
buffer[dataOffset + 0] = 0x3F; buffer[dataOffset + 0] = 0x3F;
buffer[dataOffset + 1] = 0x5A; buffer[dataOffset + 1] = 0x5A;
setFieldsData(buffer, dataOffset + 2, parameters); setFieldsData(buffer, dataOffset + 2, parameters);
break; break;
} }
case PacketType::ACTIVATE_SCENE: {
if (!std::holds_alternative<ActivateSceneParameters>(packet->parameters)) {
return false;
}
ActivateSceneParameters parameters = std::get<ActivateSceneParameters>(packet->parameters);
setPacketSize(buffer, dataOffset, 3);
buffer[dataOffset + 0] = 0x53;
buffer[dataOffset + 1] = 0x47;
buffer[dataOffset + 2] = parameters.sceneID;
break;
}
default: default:
return false; return false;
} }
@@ -136,8 +150,12 @@ void BufferFiller::setFieldsData(uint8_t *buffer, uint8_t offset, const FieldsPa
} }
} }
void BufferFiller::setPacketSize(uint8_t *buffer, uint8_t length) { void BufferFiller::setPacketSize(uint8_t *buffer, uint8_t size) {
buffer[1] = length; // Packet size buffer[1] = size; // Packet size
}
void BufferFiller::setPacketSize(uint8_t *buffer, uint8_t dataOffset, uint8_t dataLength) {
buffer[1] = (dataOffset - 2) + dataLength; // Packet size
} }
void BufferFiller::setConstants(uint8_t *buffer) { void BufferFiller::setConstants(uint8_t *buffer) {
@@ -149,7 +167,7 @@ void BufferFiller::setConstants(uint8_t *buffer) {
buffer[5] = 0xFF; // Constant buffer[5] = 0xFF; // Constant
buffer[6] = 0xFF; // Constant buffer[6] = 0xFF; // Constant
buffer[9] = 0x86; // Constant? buffer[9] = 0x86; // Not sure, seems to be 0x85 if a packet is retransmitted by a repeater
} }
void BufferFiller::setSourceAddress(uint8_t *buffer, uint8_t offset, uint16_t sourceID) { void BufferFiller::setSourceAddress(uint8_t *buffer, uint8_t offset, uint16_t sourceID) {
@@ -189,7 +207,7 @@ void BufferFiller::calculateCRC(uint8_t *buffer) { // must be called after the b
} }
uint8_t BufferFiller::calculateTotalFieldSize(const FieldsParameters& parameters) { uint8_t BufferFiller::calculateTotalFieldSize(const FieldsParameters& parameters) {
uint8_t totalSize = 0; uint8_t totalSize = 2;
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];
totalSize += calculateFieldSize(field); totalSize += calculateFieldSize(field);

View File

@@ -1,4 +1,5 @@
#include "PacketParser.h" #include "PacketParser.h"
#include <cstddef>
PacketParser::PacketParser() PacketParser::PacketParser()
{ {
@@ -73,6 +74,9 @@ bool PacketParser::parsePacket(const uint8_t *buffer, Packet& packet)
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[dataOffset + 0] == 0x53 && buffer[dataOffset + 1] == 0x47) {
packet.type = PacketType::ACTIVATE_SCENE;
packet.parameters = ActivateSceneParameters{buffer[dataOffset + 2]};
} else { } else {
packet.type = PacketType::UNKNOWN; packet.type = PacketType::UNKNOWN;
packet.parameters = std::monostate{}; packet.parameters = std::monostate{};

29
test/hex_helper.h Normal file
View 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

View File

@@ -0,0 +1,310 @@
#include <unity.h>
#include <Arduino.h>
#include "../hex_helper.h"
#include "BufferFiller.h"
void setUp()
{
}
void tearDown()
{
}
void run_fill_test(const Packet &packet, uint8_t *packet_data)
{
BufferFiller bufferFiller;
bool result = bufferFiller.fill(packet_data, &packet);
TEST_ASSERT_TRUE_MESSAGE(result, "Unable to fill packet_data");
}
void test_fill_groups_stop()
{
uint8_t packet_data[32];
Packet packet;
auto header = GroupsHeader {};
header.groups = std::vector<uint8_t>();
header.groups.push_back(0x04);
header.source = 0x369E;
packet.header = header;
packet.type = PacketType::STOP;
packet.rollingCode1 = 0x6C;
packet.rollingCode2 = 0x3C;
run_fill_test(packet, packet_data);
const uint8_t* expected_data = hex_string_to_array("C01100056CFFFF369E86063C0400369E5253003BF8");
TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, packet_data, 21);
delete[] expected_data;
}
void test_fill_groups_close()
{
uint8_t packet_data[32];
Packet packet;
auto header = GroupsHeader {};
header.groups = std::vector<uint8_t>();
header.groups.push_back(0x04);
header.source = 0x369E;
packet.header = header;
packet.type = PacketType::CLOSE;
packet.rollingCode1 = 0x6C;
packet.rollingCode2 = 0x3C;
run_fill_test(packet, packet_data);
const uint8_t* expected_data = hex_string_to_array("C01100056CFFFF369E86063C0400369E524400E80D");
TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, packet_data, 21);
delete[] expected_data;
}
void test_fill_groups_open()
{
uint8_t packet_data[32];
Packet packet;
auto header = GroupsHeader {};
header.groups = std::vector<uint8_t>();
header.groups.push_back(0x04);
header.source = 0x369E;
packet.header = header;
packet.type = PacketType::OPEN;
packet.rollingCode1 = 0x6C;
packet.rollingCode2 = 0x3C;
run_fill_test(packet, packet_data);
const uint8_t* expected_data = hex_string_to_array("C01100056CFFFF369E86063C0400369E525500B988");
TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, packet_data, 21);
delete[] expected_data;
}
void test_fill_activate_scene()
{
uint8_t packet_data[32];
Packet packet;
auto header = BroadcastHeader {};
header.source = 0x0000;
packet.header = header;
packet.type = PacketType::ACTIVATE_SCENE;
auto parameters = ActivateSceneParameters {};
parameters.sceneID = 0x1B;
packet.parameters = parameters;
packet.rollingCode1 = 0xA1;
packet.rollingCode2 = 0xFF;
run_fill_test(packet, packet_data);
const uint8_t* expected_data = hex_string_to_array("C00F0005A1FFFF00008604FF000053471B446B");
TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, packet_data, 19);
delete[] expected_data;
}
void test_fill_multiple_groups()
{
uint8_t packet_data[32];
Packet packet;
auto header = GroupsHeader {};
header.groups = std::vector<uint8_t>();
header.groups.push_back(0x01);
header.groups.push_back(0x02);
header.groups.push_back(0x03);
header.groups.push_back(0x05);
header.source = 0x369E;
packet.header = header;
packet.type = PacketType::OPEN;
packet.rollingCode1 = 0x6C;
packet.rollingCode2 = 0x3C;
run_fill_test(packet, packet_data);
const uint8_t* expected_data = hex_string_to_array("C01400056CFFFF369E86063C0102030500369E525500E050");
TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, packet_data, 24);
delete[] expected_data;
}
void test_fill_unicast_stop()
{
uint8_t packet_data[32];
Packet packet;
auto header = UnicastHeader {};
header.source = 0x0000;
header.destination = 0x4EF1;
packet.header = header;
packet.type = PacketType::STOP;
packet.rollingCode1 = 0x4A;
packet.rollingCode2 = 0xF2;
run_fill_test(packet, packet_data);
const uint8_t* expected_data = hex_string_to_array("C01100054AFFFF00008605F24EF100005253003F1F");
TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, packet_data, 21);
delete[] expected_data;
}
void test_fill_unicast_close()
{
uint8_t packet_data[32];
Packet packet;
auto header = UnicastHeader {};
header.source = 0x0000;
header.destination = 0x4EF1;
packet.header = header;
packet.type = PacketType::CLOSE;
packet.rollingCode1 = 0x4A;
packet.rollingCode2 = 0xF2;
run_fill_test(packet, packet_data);
const uint8_t* expected_data = hex_string_to_array("C01100054AFFFF00008605F24EF10000524400ECEA");
TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, packet_data, 21);
delete[] expected_data;
}
void test_fill_unicast_open()
{
uint8_t packet_data[32];
Packet packet;
auto header = UnicastHeader {};
header.source = 0x0000;
header.destination = 0x4EF1;
packet.header = header;
packet.type = PacketType::OPEN;
packet.rollingCode1 = 0x4A;
packet.rollingCode2 = 0xF2;
run_fill_test(packet, packet_data);
const uint8_t* expected_data = hex_string_to_array("C01100054AFFFF00008605F24EF10000525500BD6F");
TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, packet_data, 21);
delete[] expected_data;
}
void test_fill_fields_fetch()
{
uint8_t packet_data[32];
Packet packet;
auto header = UnicastHeader {};
header.source = 0x0000;
header.destination = 0x4EF1;
packet.header = header;
packet.type = PacketType::FIELD_COMMAND;
auto parameters = FieldsParameters {};
auto field1 = Field {};
field1.identifier = 0x50;
field1.type = FieldType::FETCH;
field1.hasValue = false;
parameters.fields.push_back(field1);
auto field2 = Field {};
field2.identifier = 0x4D;
field2.type = FieldType::FETCH;
field2.hasValue = false;
parameters.fields.push_back(field2);
auto field3 = Field {};
field3.identifier = 0x54;
field3.type = FieldType::FETCH;
field3.hasValue = false;
parameters.fields.push_back(field3);
packet.parameters = parameters;
packet.rollingCode1 = 0x92;
packet.rollingCode2 = 0x4E;
run_fill_test(packet, packet_data);
const uint8_t* expected_data = hex_string_to_array("C019000592FFFF000086054E4EF100003F5A023F50023F4D023F549D76");
TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, packet_data, 29);
delete[] expected_data;
}
void test_fill_fields_set()
{
uint8_t packet_data[32];
Packet packet;
auto header = UnicastHeader {};
header.source = 0x0000;
header.destination = 0x4EF1;
packet.header = header;
packet.type = PacketType::FIELD_COMMAND;
auto parameters = FieldsParameters {};
auto field1 = Field {};
field1.identifier = 0x50;
field1.type = FieldType::SET;
field1.hasValue = true;
field1.value = (uint16_t)0xFFFF;
parameters.fields.push_back(field1);
packet.parameters = parameters;
packet.rollingCode1 = 0x29;
packet.rollingCode2 = 0xB9;
run_fill_test(packet, packet_data);
const uint8_t* expected_data = hex_string_to_array("C015000529FFFF00008605B94EF100003F5A044050FFFF286B");
TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, packet_data, 25);
delete[] expected_data;
}
int runUnityTests(void)
{
UNITY_BEGIN();
RUN_TEST(test_fill_groups_stop);
RUN_TEST(test_fill_groups_close);
RUN_TEST(test_fill_groups_open);
RUN_TEST(test_fill_activate_scene);
RUN_TEST(test_fill_multiple_groups);
RUN_TEST(test_fill_unicast_stop);
RUN_TEST(test_fill_unicast_close);
RUN_TEST(test_fill_unicast_open);
RUN_TEST(test_fill_fields_fetch);
RUN_TEST(test_fill_fields_set);
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();
}

View File

@@ -1,62 +1,83 @@
#include <unity.h> #include <unity.h>
#include <Arduino.h> #include <Arduino.h>
#include "../hex_helper.h"
#include "PacketCRC.h" #include "PacketCRC.h"
void setUp()
{
}
void tearDown()
{
}
void run_calculate_test(const uint8_t *packet_data, const uint16_t expected_checksum) void run_calculate_test(const uint8_t *packet_data, const uint16_t expected_checksum)
{ {
PacketCRC packetCRC; PacketCRC packetCRC;
uint16_t actual_checksum = packetCRC.calculate(packet_data); uint16_t actual_checksum = packetCRC.calculate(packet_data);
TEST_ASSERT_EQUAL_UINT16(expected_checksum, actual_checksum); TEST_ASSERT_EQUAL_HEX16(expected_checksum, actual_checksum);
} }
void test_calculate_withDataLengthOf15() void test_calculate_withDataLengthOf15()
{ {
const uint8_t packet_data[] = {0xC0, 0x0F, 0x00, 0x05, 0xA1, 0xFF, 0xFF, 0x00, 0x00, 0x86, 0x04, 0xFF, 0x00, 0x00, 0x53, 0x47, 0x1B}; const uint8_t* packet_data = hex_string_to_array("C00F0005A1FFFF00008604FF000053471B");
const uint16_t expected_checksum = 0x446B; const uint16_t expected_checksum = 0x446B;
run_calculate_test(packet_data, expected_checksum); run_calculate_test(packet_data, expected_checksum);
delete[] packet_data;
} }
void test_calculate_withDataLengthOf17() void test_calculate_withDataLengthOf17()
{ {
const uint8_t packet_data[] = {0xC0, 0x11, 0x00, 0x05, 0xD7, 0xFF, 0xFF, 0x36, 0x9E, 0x86, 0x06, 0xB3, 0x04, 0x00, 0x36, 0x9E, 0x52, 0x53, 0x00}; const uint8_t* packet_data = hex_string_to_array("C0110005D7FFFF369E8606B30400369E525300");
const uint16_t expected_checksum = 0xE04C; const uint16_t expected_checksum = 0xE04C;
run_calculate_test(packet_data, expected_checksum); run_calculate_test(packet_data, expected_checksum);
delete[] packet_data;
} }
void test_calculate_withDataLengthOf19() void test_calculate_withDataLengthOf19()
{ {
const uint8_t packet_data[] = {0xC0, 0x13, 0x00, 0x05, 0xDD, 0xFF, 0xFF, 0x00, 0x00, 0x86, 0x05, 0x36, 0x4E, 0xF1, 0x00, 0x00, 0x3F, 0x5A, 0x02, 0x3F, 0x42}; const uint8_t* packet_data = hex_string_to_array("C0130005DDFFFF00008605364EF100003F5A023F42");
const uint16_t expected_checksum = 0x9BD3; const uint16_t expected_checksum = 0x9BD3;
run_calculate_test(packet_data, expected_checksum); run_calculate_test(packet_data, expected_checksum);
delete[] packet_data;
} }
void test_calculate_withDataLengthOf20() void test_calculate_withDataLengthOf20()
{ {
const uint8_t packet_data[] = {0xC0, 0x14, 0x10, 0x05, 0x59, 0xFF, 0xFF, 0x4E, 0xF1, 0x86, 0x05, 0xC2, 0x00, 0x00, 0x4E, 0xF1, 0x21, 0x5A, 0x03, 0x21, 0x42, 0x9C}; const uint8_t* packet_data = hex_string_to_array("C014100559FFFF4EF18605C200004EF1215A0321429C");
const uint16_t expected_checksum = 0x4A8B; const uint16_t expected_checksum = 0x4A8B;
run_calculate_test(packet_data, expected_checksum); run_calculate_test(packet_data, expected_checksum);
delete[] packet_data;
} }
void test_calculate_withDataLengthOf21() void test_calculate_withDataLengthOf21()
{ {
const uint8_t packet_data[] = {0xC0, 0x15, 0x10, 0x05, 0xEA, 0xFF, 0xFF, 0x4E, 0xF1, 0x86, 0x05, 0x24, 0x00, 0x00, 0x4E, 0xF1, 0x21, 0x46, 0x4E, 0x98, 0x07, 0x08, 0x01}; const uint8_t* packet_data = hex_string_to_array("C0151005EAFFFF4EF186052400004EF121464E98070801");
const uint16_t expected_checksum = 0x9887; const uint16_t expected_checksum = 0x9887;
run_calculate_test(packet_data, expected_checksum); run_calculate_test(packet_data, expected_checksum);
delete[] packet_data;
} }
void test_calculate_withDataLengthOf25() void test_calculate_withDataLengthOf25()
{ {
const uint8_t packet_data[] = {0xC0, 0x19, 0x00, 0x05, 0x47, 0xFF, 0xFF, 0x00, 0x00, 0x86, 0x05, 0x3D, 0x4E, 0xF1, 0x00, 0x00, 0x3F, 0x5A, 0x02, 0x3F, 0x50, 0x02, 0x3F, 0x4D, 0x02, 0x3F, 0x54}; const uint8_t* packet_data = hex_string_to_array("C019000547FFFF000086053D4EF100003F5A023F50023F4D023F54");
const uint16_t expected_checksum = 0x8318; const uint16_t expected_checksum = 0x8318;
run_calculate_test(packet_data, expected_checksum); run_calculate_test(packet_data, expected_checksum);
delete[] packet_data;
} }
int runUnityTests(void) int runUnityTests(void)
@@ -82,4 +103,9 @@ void setup()
runUnityTests(); runUnityTests();
} }
void loop() {} void loop() {}
int main(void)
{
return runUnityTests();
}

View File

@@ -1,55 +1,265 @@
#include <unity.h> #include <unity.h>
#include <Arduino.h> #include <Arduino.h>
#include "../hex_helper.h"
#include "PacketParser.h" #include "PacketParser.h"
void run_parse_test(const uint8_t *packet_data, Packet& packet) void setUp()
{
}
void tearDown()
{
}
void run_parse_test(const uint8_t *packet_data, Packet &packet)
{ {
PacketParser packetParser; PacketParser packetParser;
bool result = packetParser.parsePacket(packet_data, packet); bool result = packetParser.parsePacket(packet_data, packet);
TEST_ASSERT_TRUE(result); TEST_ASSERT_TRUE_MESSAGE(result, "Unable to parse packet_data");
} }
void test_parse_stop() void test_parse_stop()
{ {
const uint8_t packet_data[] = {0xC0, 0x11, 0x00, 0x05, 0x6C, 0xFF, 0xFF, 0x36, 0x9E, 0x86, 0x06, 0x3C, 0x04, 0x00, 0x36, 0x9E, 0x52, 0x53, 0x00}; const uint8_t* packet_data = hex_string_to_array("C01100056CFFFF369E86063C0400369E525300");
Packet packet; Packet packet;
run_parse_test(packet_data, packet); run_parse_test(packet_data, packet);
TEST_ASSERT_TRUE(packet.type == PacketType::STOP); TEST_ASSERT_TRUE(packet.type == PacketType::STOP);
delete[] packet_data;
} }
void test_parse_close() void test_parse_close()
{ {
const uint8_t packet_data[] = {0xC0, 0x11, 0x00, 0x05, 0x6C, 0xFF, 0xFF, 0x36, 0x9E, 0x86, 0x06, 0x3C, 0x04, 0x00, 0x36, 0x9E, 0x52, 0x44, 0x00}; const uint8_t* packet_data = hex_string_to_array("C01100056CFFFF369E86063C0400369E524400");
Packet packet; Packet packet;
run_parse_test(packet_data, packet); run_parse_test(packet_data, packet);
TEST_ASSERT_TRUE(packet.type == PacketType::CLOSE); TEST_ASSERT_TRUE(packet.type == PacketType::CLOSE);
delete[] packet_data;
} }
void test_parse_open() void test_parse_open()
{ {
const uint8_t packet_data[] = {0xC0, 0x11, 0x00, 0x05, 0x6C, 0xFF, 0xFF, 0x36, 0x9E, 0x86, 0x06, 0x3C, 0x04, 0x00, 0x36, 0x9E, 0x52, 0x55, 0x00}; const uint8_t* packet_data = hex_string_to_array("C01100056CFFFF369E86063C0400369E525500");
Packet packet; Packet packet;
run_parse_test(packet_data, packet); run_parse_test(packet_data, packet);
TEST_ASSERT_TRUE(packet.type == PacketType::OPEN); TEST_ASSERT_TRUE(packet.type == PacketType::OPEN);
delete[] packet_data;
} }
int runParseUnityTests(void) 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_groups_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_groups_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_groups_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;
}
void test_broadcast_source_address()
{
const uint8_t* packet_data = hex_string_to_array("C00F0005A1FFFF00008604FF000053471B446B");
Packet packet;
run_parse_test(packet_data, packet);
auto header = std::get<BroadcastHeader>(packet.header);
TEST_ASSERT_EQUAL_HEX16(0x0000, header.source);
delete[] packet_data;
}
void test_parse_activate_scene()
{
const uint8_t* packet_data = hex_string_to_array("C00F0005A1FFFF00008604FF000053471B446B");
Packet packet;
run_parse_test(packet_data, packet);
auto parameters = std::get<ActivateSceneParameters>(packet.parameters);
TEST_ASSERT_EQUAL_HEX8(0x1B, parameters.sceneID);
delete[] packet_data;
}
void test_parse_fields()
{
const uint8_t* packet_data = hex_string_to_array("C019000592FFFF72CB85054E4EF100003F5A023F50023F4D023F54C9F3");
Packet packet;
run_parse_test(packet_data, packet);
auto parameters = std::get<FieldsParameters>(packet.parameters);
TEST_ASSERT_EQUAL_INT(3, parameters.fields.size());
TEST_ASSERT_EQUAL_HEX8(0x50, parameters.fields[0].identifier);
TEST_ASSERT_FALSE_MESSAGE(parameters.fields[0].hasValue, "Field 0 should not have a value.");
TEST_ASSERT_EQUAL_HEX8(0x4D, parameters.fields[1].identifier);
TEST_ASSERT_FALSE_MESSAGE(parameters.fields[1].hasValue, "Field 1 should not have a value.");
TEST_ASSERT_EQUAL_HEX8(0x54, parameters.fields[2].identifier);
TEST_ASSERT_FALSE_MESSAGE(parameters.fields[2].hasValue, "Field 2 should not have a value.");
delete[] packet_data;
}
void test_parse_fields_data_uint16()
{
const uint8_t* packet_data = hex_string_to_array("C0151005E0FFFF4EF186051A00004EF1215A04215040016670");
Packet packet;
run_parse_test(packet_data, packet);
auto parameters = std::get<FieldsParameters>(packet.parameters);
TEST_ASSERT_EQUAL_INT(1, parameters.fields.size());
TEST_ASSERT_EQUAL_HEX8(0x50, parameters.fields[0].identifier);
TEST_ASSERT_TRUE_MESSAGE(parameters.fields[0].hasValue, "Field 0 should have a value.");
TEST_ASSERT_EQUAL_HEX16(0x0140, std::get<uint16_t>(parameters.fields[0].value));
delete[] packet_data;
}
void test_parse_fields_data_uint8()
{
const uint8_t* packet_data = hex_string_to_array("C014100558FFFF4EF18605C100004EF1215A0321429DEC23");
Packet packet;
run_parse_test(packet_data, packet);
auto parameters = std::get<FieldsParameters>(packet.parameters);
TEST_ASSERT_EQUAL_INT(1, parameters.fields.size());
TEST_ASSERT_EQUAL_HEX8(0x42, parameters.fields[0].identifier);
TEST_ASSERT_TRUE_MESSAGE(parameters.fields[0].hasValue, "Field 0 should have a value.");
TEST_ASSERT_EQUAL_HEX8(0x9D, std::get<uint8_t>(parameters.fields[0].value));
delete[] packet_data;
}
int runUnityTests(void)
{ {
UNITY_BEGIN(); UNITY_BEGIN();
RUN_TEST(test_parse_stop); RUN_TEST(test_parse_stop);
RUN_TEST(test_parse_close); RUN_TEST(test_parse_close);
RUN_TEST(test_parse_open); RUN_TEST(test_parse_open);
RUN_TEST(test_parse_rolling_codes);
RUN_TEST(test_groups_source_address);
RUN_TEST(test_groups_single_group);
RUN_TEST(test_groups_multiple_groups);
RUN_TEST(test_unicast_source_address);
RUN_TEST(test_unicast_destination_address);
RUN_TEST(test_broadcast_source_address);
RUN_TEST(test_parse_activate_scene);
RUN_TEST(test_parse_fields);
RUN_TEST(test_parse_fields_data_uint16);
RUN_TEST(test_parse_fields_data_uint8);
return UNITY_END(); return UNITY_END();
} }
@@ -63,6 +273,11 @@ void setup()
// establishes connection with a board Serial interface // establishes connection with a board Serial interface
delay(2000); delay(2000);
runParseUnityTests(); runUnityTests();
} }
void loop() {} void loop() {}
int main(void)
{
return runUnityTests();
}