Compare commits
19 Commits
f069022f5c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 414d01382c | |||
| 70718fae26 | |||
| 4dce81046c | |||
| dd788c203a | |||
| 2a09f770ca | |||
| c72126ab4c | |||
| c514769929 | |||
| 0a1359572f | |||
| 9417db243f | |||
| 195bcb7726 | |||
| ec50685c51 | |||
| 5289e91525 | |||
| 80b40cafcb | |||
| ce46892fde | |||
| e2d8358e4f | |||
| 797a104f1f | |||
| 08f89c6baf | |||
| 4153487f7a | |||
| cbf188e554 |
18
.github/workflows/build.yaml
vendored
Normal file
18
.github/workflows/build.yaml
vendored
Normal 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
|
||||
198
README.md
198
README.md
@@ -12,6 +12,45 @@ Library for sending and receiving PowerView packets so that blinds can be contro
|
||||
- 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
|
||||
|
||||
@@ -28,7 +67,7 @@ A PowerView packet consists of a header (which contains a variable length addres
|
||||
| 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` |
|
||||
| 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) |
|
||||
@@ -46,7 +85,7 @@ Note: this type of address seems to be used when a hub is activating a scene (an
|
||||
| 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.
|
||||
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 |
|
||||
@@ -72,7 +111,7 @@ Note: this type of payload is used by both the hub and pebble remotes. Used for
|
||||
#### 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) |
|
||||
| 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) |
|
||||
|
||||
@@ -82,7 +121,7 @@ Note: this type of payload is used by the hub. If sent to a blind without values
|
||||
| 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) |
|
||||
| 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` |
|
||||
@@ -92,88 +131,113 @@ Note: this type of payload is used by the hub. If sent to a blind without values
|
||||
|---|---|---|---|---|
|
||||
| 0 | 16 | Checksum | A CRC16 checksum of the packet data | `0x0000`-`0xFFFF` |
|
||||
|
||||
## Examples
|
||||
## Sample Packets
|
||||
|
||||
### Open blinds in group 4
|
||||
|
||||
`C01100056CFFFF369E86063C0400369E525500B988`
|
||||
```
|
||||
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` | |
|
||||
| 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`
|
||||
```
|
||||
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` | |
|
||||
| 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`
|
||||
```
|
||||
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` | |
|
||||
| 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`
|
||||
```
|
||||
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` | |
|
||||
| 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`
|
||||
```
|
||||
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!)
|
||||
| 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` | |
|
||||
|
||||
@@ -7,17 +7,16 @@
|
||||
|
||||
class BufferFiller {
|
||||
public:
|
||||
BufferFiller(uint8_t protocolVersion);
|
||||
BufferFiller();
|
||||
~BufferFiller();
|
||||
|
||||
bool fill(uint8_t *buffer, const Packet* packet);
|
||||
|
||||
private:
|
||||
uint8_t protocolVersion;
|
||||
|
||||
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 setSourceAddress(uint8_t *buffer, uint8_t offset, uint16_t source);
|
||||
void setDestinationAddress(uint8_t *buffer, uint8_t offset, uint16_t destination);
|
||||
|
||||
@@ -14,6 +14,7 @@ enum class PacketType {
|
||||
MOVE_TO_SAVED_POSITION,
|
||||
FIELDS,
|
||||
FIELD_COMMAND,
|
||||
ACTIVATE_SCENE,
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
@@ -35,7 +36,11 @@ struct FieldsParameters {
|
||||
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 {
|
||||
uint16_t source;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "BufferFiller.h"
|
||||
|
||||
BufferFiller::BufferFiller(uint8_t protocolVersion) :
|
||||
protocolVersion(protocolVersion)
|
||||
BufferFiller::BufferFiller()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -40,59 +39,74 @@ bool BufferFiller::fill(uint8_t *buffer, const Packet* packet) {
|
||||
|
||||
switch(packet->type) {
|
||||
case PacketType::STOP:
|
||||
setPacketSize(buffer, 0x11);
|
||||
setPacketSize(buffer, dataOffset, 3);
|
||||
buffer[dataOffset + 0] = 0x52;
|
||||
buffer[dataOffset + 1] = 0x53;
|
||||
buffer[dataOffset + 2] = 0x00;
|
||||
break;
|
||||
case PacketType::CLOSE:
|
||||
setPacketSize(buffer, 0x11);
|
||||
setPacketSize(buffer, dataOffset, 3);
|
||||
buffer[dataOffset + 0] = 0x52;
|
||||
buffer[dataOffset + 1] = 0x44;
|
||||
buffer[dataOffset + 2] = 0x00;
|
||||
break;
|
||||
case PacketType::OPEN:
|
||||
setPacketSize(buffer, 0x11);
|
||||
setPacketSize(buffer, dataOffset, 3);
|
||||
buffer[dataOffset + 0] = 0x52;
|
||||
buffer[dataOffset + 1] = 0x55;
|
||||
buffer[dataOffset + 2] = 0x00;
|
||||
break;
|
||||
case PacketType::CLOSE_SLOW:
|
||||
setPacketSize(buffer, 0x11);
|
||||
setPacketSize(buffer, dataOffset, 3);
|
||||
buffer[dataOffset + 0] = 0x52;
|
||||
buffer[dataOffset + 1] = 0x4C;
|
||||
buffer[dataOffset + 2] = 0x00;
|
||||
break;
|
||||
case PacketType::OPEN_SLOW:
|
||||
setPacketSize(buffer, 0x11);
|
||||
setPacketSize(buffer, dataOffset, 3);
|
||||
buffer[dataOffset + 0] = 0x52;
|
||||
buffer[dataOffset + 1] = 0x52;
|
||||
buffer[dataOffset + 2] = 0x00;
|
||||
break;
|
||||
case PacketType::MOVE_TO_SAVED_POSITION:
|
||||
setPacketSize(buffer, 0x11);
|
||||
setPacketSize(buffer, dataOffset, 3);
|
||||
buffer[dataOffset + 0] = 0x52;
|
||||
buffer[dataOffset + 1] = 0x48;
|
||||
buffer[dataOffset + 2] = 0x00;
|
||||
break;
|
||||
case PacketType::FIELDS: {
|
||||
if (!std::holds_alternative<FieldsParameters>(packet->parameters)) {
|
||||
return false;
|
||||
}
|
||||
FieldsParameters parameters = std::get<FieldsParameters>(packet->parameters);
|
||||
// 0x10 is the number of bytes without any fields
|
||||
setPacketSize(buffer, 0x10 + calculateTotalFieldSize(parameters));
|
||||
setPacketSize(buffer, dataOffset, calculateTotalFieldSize(parameters));
|
||||
buffer[dataOffset + 0] = 0x21;
|
||||
buffer[dataOffset + 1] = 0x5A;
|
||||
setFieldsData(buffer, dataOffset + 2, parameters);
|
||||
break;
|
||||
}
|
||||
case PacketType::FIELD_COMMAND: {
|
||||
if (!std::holds_alternative<FieldsParameters>(packet->parameters)) {
|
||||
return false;
|
||||
}
|
||||
FieldsParameters parameters = std::get<FieldsParameters>(packet->parameters);
|
||||
// 0x10 is the number of bytes without any fields
|
||||
setPacketSize(buffer, 0x10 + calculateTotalFieldSize(parameters));
|
||||
setPacketSize(buffer, dataOffset, calculateTotalFieldSize(parameters));
|
||||
buffer[dataOffset + 0] = 0x3F;
|
||||
buffer[dataOffset + 1] = 0x5A;
|
||||
setFieldsData(buffer, dataOffset + 2, parameters);
|
||||
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:
|
||||
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) {
|
||||
buffer[1] = length; // Packet size
|
||||
void BufferFiller::setPacketSize(uint8_t *buffer, uint8_t 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) {
|
||||
@@ -149,7 +167,7 @@ void BufferFiller::setConstants(uint8_t *buffer) {
|
||||
buffer[5] = 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) {
|
||||
@@ -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 totalSize = 0;
|
||||
uint8_t totalSize = 2;
|
||||
for (size_t i = 0; i < parameters.fields.size(); i++) {
|
||||
Field field = parameters.fields[i];
|
||||
totalSize += calculateFieldSize(field);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "PacketParser.h"
|
||||
#include <cstddef>
|
||||
|
||||
PacketParser::PacketParser()
|
||||
{
|
||||
@@ -73,6 +74,9 @@ bool PacketParser::parsePacket(const uint8_t *buffer, Packet& packet)
|
||||
std::vector<Field> fields;
|
||||
parseFields(buffer, 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 {
|
||||
packet.type = PacketType::UNKNOWN;
|
||||
packet.parameters = std::monostate{};
|
||||
|
||||
310
test/test_buffer_filler/test_buffer_filler.cpp
Normal file
310
test/test_buffer_filler/test_buffer_filler.cpp
Normal 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();
|
||||
}
|
||||
@@ -72,7 +72,7 @@ void test_parse_rolling_codes()
|
||||
delete[] packet_data;
|
||||
}
|
||||
|
||||
void test_broadcast_source_address()
|
||||
void test_groups_source_address()
|
||||
{
|
||||
const uint8_t* packet_data = hex_string_to_array("C01100056CFFFF369E86063C0400369E525300");
|
||||
|
||||
@@ -87,7 +87,7 @@ void test_broadcast_source_address()
|
||||
delete[] packet_data;
|
||||
}
|
||||
|
||||
void test_broadcast_single_group()
|
||||
void test_groups_single_group()
|
||||
{
|
||||
const uint8_t* packet_data = hex_string_to_array("C01100056CFFFF369E86063C0400369E525300");
|
||||
|
||||
@@ -103,7 +103,7 @@ void test_broadcast_single_group()
|
||||
delete[] packet_data;
|
||||
}
|
||||
|
||||
void test_broadcast_multiple_groups()
|
||||
void test_groups_multiple_groups()
|
||||
{
|
||||
const uint8_t* packet_data = hex_string_to_array("C01100056CFFFF369E86063C03040500369E525300");
|
||||
|
||||
@@ -151,6 +151,98 @@ void test_unicast_destination_address()
|
||||
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();
|
||||
@@ -158,11 +250,16 @@ int runUnityTests(void)
|
||||
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_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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user