Overview

The protocol is based on TCP and is based around commands targeting object IDs that work similar to OIDs in SNMP. Messages are protected against transfer failures by means of a 2-byte CRC checksum. The protocol in itself isn't very complicated, but there are some things like escaping, length calculation and plant communication to look out for.

There is no method to tell that a communication was not understood at the protocol level, meaning that it is up to the implementor to figure out a way to let the client know that something was not understood. Vendor devices may simply fail to respond or respond with an empty payload, or do something else. It is advised to pay attention to the checksums and errors during payload decoding. Vendor devices do facilitate special OIDs that contain information about their state, but that is an implementation detail of the specific devices. If implementing an application, one could specify some OIDs that can be used to report if the previous command resulted in an error, for example.

Protocol Data Units

The Protocol Data Units (PDU) are comprised of:

  1. A start token (+)

  2. The command byte

  3. The length of the ID and data payload

  4. For plant communication, the address, omitted for standard frames

  5. The object ID

  6. Payload (optional)

  7. CRC16 checksum

Data is encoded as big endian, a leading 0x00 before the start token is allowed.

Element

Bytes

Remarks

Start token

1

+ character, 0x2b.

Command

1

Payload length

1

All but long read / long write

2

Long read / long write

Address

4

For plant communication

0

Omitted for standard frames

Object ID

4

Payload

N

Payload is optional for some commands

CRC16

2

Hint

The OIDs are actually an implementation detail of the device. The protocol only defines that they are 4 bytes in length. All other details like the data type, whether a payload can be used and so on are up to the implementer, so in order to implement your own application, you would simply define your own OIDs with associated data types instead of using the ones in the Registry or the examples.

Escaping

Certain characters are escaped by inserting the escape token - (0x2d) into the stream before the byte that requires escaping. When the start token (+) or escape token is encountered in the data stream (unless it's the very first byte for the start token), the escape token is inserted before the token that it escapes. On decoding, when the escape token is encountered, the next character is interpreted as data and not as start token or escape token. Thus, if the task is to encode a plus sign (usually in a string), then a minus is added before the plus sign to escape it.

Checksum

The checksum algorithm used is a special version of CRC16 using a CCITT polynom (0x1021). It varies from other implementation by appending a NULL byte to the input if its length is uneven before commencing with the calculation.

Commands

There are two groups of commands: Standard communication commands that are sent to a device and the device replies, as well as Plant communication commands, which are standard commands ORed with 0x40.

Commands not listed here are either not known or are reserved, and should not be used with the devices as it is not known what effect this could have.

Command

Value

Description

READ

0x01

Request the current value of an object ID. No payload.

WRITE

0x02

Write the payload to the object ID.

LONG_WRITE

0x03

When writing "long" payloads.

RESERVED

0x04

RESPONSE

0x05

Normal response to a read or write command.

LONG_RESPONSE

0x06

Response with a "long" payload.

RESERVED

0x07

READ_PERIODICALLY

0x08

Request automatic, periodic sending of an OIDs value.

Reserved

0x09-0x40

PLANT_READ

0x41

READ for plant communication.

PLANT_WRITE

0x42

WRITE for plant communication.

PLANT_LONG_WRITE

0x43

LONG_WRITE for plant communication.

RESERVED

0x44

PLANT_RESPONSE

0x45

RESPONSE for plant communication.

PLANT_LONG_RESPONSE

0x46

LONG_RESPONSE for plant communication.

RESERVED

0x47

PLANT_READ_PERIODICALLY

0x48

READ_PERIODICALLY for plant communication.

EXTENSION

0x3c

Unknown, see below.

The EXTENSION command

EXTENSION does not follow the semantics of other commands and cannot be parsed by rctclient. It is believed to be a single-byte payload; a frame often observed is 0x2b3ce1, which is sent by the official app uppon connecting to a device to "switch to COM protocol". In this case, 0xe1 is the commands payload, and a normal frame follows immediately after, which leads to the conclusion of this command always being three bytes in length.

READ_PERIODICALLY

Registers a OID for being sent periodically. The device will send the current value of the OID at an interval defined in pas.period (see Registry). Up to 64 OIDs can be registered with vendor devices, but the protocol does not impose a limit, and all registered OIDs will be served at the same interval setting. When sending this command, the device immediately responds with the current value of the OID, and will then periodically send the current value.

To disable, set pas.period to 0, which clears the list of registered OIDs, effectively disabling the feature. No method exists for removing a single OID, one has to clear it, then set pas.period and re-register all desired OIDs.

Warning

The implementation has not been tested yet, please don't hesitate to open an issue if you run into problems or have more insight into the matter.

Frame length

The frame length is 1 byte for all commands except LONG_RESPONSE and LONG_WRITE and their PLANT_ counterparts, which use 2 bytes (most siginificant byte first). The length denotes how many bytes of data follow it. Escape tokens are not counted, and it does not include the two-byte header before it (start token and command) and does also not include the two-byte CRC16 at the end of the frame. In order to fully receive a frame, after reversing any escaping, the buffer should therefor hold 2 + length + 2 bytes.

Plant communication

With plant communication, one device acts as plant leader and relays commands addressed at subordinate devices to them and their responses back to the client. Vendor devices need to be linked together, each has its own address.

To use plant communication, use the PLANT_* variations of the normal commands (READPLANT_READ and so on) and supply the address property. The leader device forwards the command to the device that has the address set, all other devices ignore the frame. The answer from the addressed device is then sent back to the client by the leader, with a PLANT_* response command and the address set to that of the addressed device.

Warning

Plant communication has not been tested and the implementation simply follows what is known. The authors do not have a setup to test this kind of communication. We'd greatly appreciate traffic dumps of actual plant communication or feedback if it works or not.

Frame by example

The following is a dissection of a frame sent to the device (read request) and its response from the device.

Request

Setting:

  • READ request, so command is 0x01

  • The OID battery.soc is 0x959930BF

  • No payload and no address and nothing to escape.

Data: 2b 01 04 959930bf 0d65

ID:   1  2  3  4        5

ID

Bytes

Description

1

2b

Start token

2

01

Command: READ

3

04

Length of the data that follows, it's the OID of 4 bytes.

4

959930bf

Data, which in this example consists of the OID only.

5

0d65

CRC16 checksum.

Response

The response for the command (read battery state of charge) is disected below. The string has been split up for ease of reading, but it is a single byte stream.

The raw response looks like this (in hexadecimal): 002b0508959930bf3e97b1919c86

Data: 00 2b 05 08 959930bf 3e97b191 9c86

ID:   1  2  3  4  5        6        7

ID

Bytes

Description

1

00

Data before the start of the command. It is ignored.

2

2b

Start token, all data before this is ignored.

3

05

Command, this is a RESPONSE.

4

08

Length field, 4 byte OID and 4 byte payload.

5

959930bf

The OID this response carries.

6

3e97b191

Payload data, as per the OID this is a big endian float value of roughly 0.296

7

9x86

CRC16 checksum.

The payload in this example is a big endian floating point number. The data type can be looked up in the Registry.