Protocol Definition - HAEdwin/homeassistant-apsystems_ecu_reader GitHub Wiki
The APSystems ECU (Energy Control Unit) uses a proprietary TCP-based protocol over port 8899. The protocol consists of ASCII command strings and binary response data. This protocol definition is not based on any public or official documentation provided by APsystems. It is an independent interpretation and reverse-engineered understanding. As such, it may contain inaccuracies or misinterpretations. There is no guarantee of correctness, and it is not affiliated with or endorsed by APsystems in any way.
- Protocol: TCP
- Port: 8899
- Encoding: Mixed (ASCII commands, binary responses)
- Byte Order: Big Endian
- Buffer Size: 1024 bytes (default)
All commands follow the pattern: APS<length><type><ecu_id>END\n
All responses follow the pattern:
APS<4-byte-length><4-byte-type><payload><checksum>END\n
Command: APS1100160001END\n
-
Length:
11001(decimal) -
Type:
6 -
Query Type:
0001
Response Structure:
| Offset | Length | Type | Description | Encoding |
|---|---|---|---|---|
| 0-2 | 3 | ASCII | Signature "APS" | UTF-8 |
| 3-6 | 4 | ASCII | Data length | Decimal string |
| 7-8 | 2 | ASCII | Message type | Decimal string |
| 9-12 | 4 | ASCII | Query type "0001" | UTF-8 |
| 13-24 | 12 | ASCII | ECU ID | UTF-8 |
| 25-26 | 2 | ASCII | Data format version | UTF-8 |
| 27-30 | 4 | Binary | Lifetime energy (×10 Wh) | Big Endian Int |
| 31-34 | 4 | Binary | Current power (W) | Big Endian Int |
| 35-38 | 4 | Binary | Today energy (×100 Wh) | Big Endian Int |
Version "01" Additional Fields:
| Offset | Length | Type | Description | Encoding |
|---|---|---|---|---|
| 46-47 | 2 | Binary | Total inverters | Big Endian Int |
| 48-49 | 2 | Binary | Online inverters | Big Endian Int |
| 52-54 | 3 | ASCII | Firmware version length | Decimal string |
| 55+ | VSL | ASCII | Firmware version | UTF-8 |
| 55+VSL | 3 | ASCII | Timezone length | Decimal string |
| 58+VSL | TSL | ASCII | Timezone | UTF-8 |
Version "02" Additional Fields:
| Offset | Length | Type | Description | Encoding |
|---|---|---|---|---|
| 39-40 | 2 | Binary | Total inverters | Big Endian Int |
| 41-42 | 2 | Binary | Online inverters | Big Endian Int |
| 49-51 | 3 | ASCII | Firmware version length | Decimal string |
| 52+ | VSL | ASCII | Firmware version | UTF-8 |
Command: APS1100280002<ECU_ID>END\n
-
Length:
11002(decimal) -
Type:
8 -
Query Type:
0002
Response Structure:
| Offset | Length | Type | Description | Encoding |
|---|---|---|---|---|
| 0-2 | 3 | ASCII | Signature "APS" | UTF-8 |
| 3-6 | 4 | ASCII | Data length | Decimal string |
| 7-8 | 2 | ASCII | Message type | Decimal string |
| 9-12 | 4 | ASCII | Query type "0002" | UTF-8 |
| 14-15 | 2 | ASCII | Status "00" | UTF-8 |
| 15-16 | 2 | ASCII | Replacement flag "01" | UTF-8 |
| 17-18 | 2 | Binary | Inverter quantity | Big Endian Int |
| 19-32 | 14 | Binary | Timestamp | BCD (YYYYMMDDHHMMSS) |
Per-Inverter Data (starting at offset 26):
| Offset | Length | Type | Description | Encoding |
|---|---|---|---|---|
| +0 | 6 | Binary | Inverter UID | Hex (12 chars) |
| +6 | 1 | Binary | Online status | Boolean (0/1) |
| +7 | 2 | ASCII | Inverter type | UTF-8 |
| +9 | 2 | Binary | Frequency (×10 Hz) | Big Endian Int |
| +11 | 2 | Binary | Temperature (+100°C) | Big Endian Int |
2-Channel Inverters (Types "01", "04" - YC600/DS3 series):
| Offset | Length | Type | Description | Encoding |
|---|---|---|---|---|
| +13 | 2 | Binary | Channel 1 Power (W) | Big Endian Int |
| +15 | 2 | Binary | Channel 1 Voltage (V) | Big Endian Int |
| +17 | 2 | Binary | Channel 2 Power (W) | Big Endian Int |
| +19 | 2 | Binary | Channel 2 Voltage (V) | Big Endian Int |
Total record length: 21 bytes
4-Channel Inverters (Types "02", "05" - YC1000/QT2 series):
| Offset | Length | Type | Description | Encoding |
|---|---|---|---|---|
| +13 | 2 | Binary | Channel 1 Power (W) | Big Endian Int |
| +15 | 2 | Binary | Channel 1 Voltage (V) | Big Endian Int |
| +17 | 2 | Binary | Channel 2 Power (W) | Big Endian Int |
| +19 | 2 | Binary | Channel 2 Voltage (V) | Big Endian Int |
| +21 | 2 | Binary | Channel 3 Power (W) | Big Endian Int |
| +23 | 2 | Binary | Channel 3 Voltage (V) | Big Endian Int |
| +25 | 2 | Binary | Channel 4 Power (W) | Big Endian Int |
Total record length: 27 bytes
4-Channel Inverter (Type "03" - QS1 series):
| Offset | Length | Type | Description | Encoding |
|---|---|---|---|---|
| +13 | 2 | Binary | Channel 1 Power (W) | Big Endian Int |
| +15 | 2 | Binary | Voltage (V) | Big Endian Int |
| +17 | 2 | Binary | Channel 2 Power (W) | Big Endian Int |
| +19 | 2 | Binary | Channel 3 Power (W) | Big Endian Int |
| +21 | 2 | Binary | Channel 4 Power (W) | Big Endian Int |
Total record length: 23 bytes
Command: APS1100280030<ECU_ID>END\n
-
Length:
11002(decimal) -
Type:
8 -
Query Type:
0030
Response Structure:
| Offset | Length | Type | Description | Encoding |
|---|---|---|---|---|
| 0-2 | 3 | ASCII | Signature "APS" | UTF-8 |
| 3-6 | 4 | ASCII | Data length | Decimal string |
| 7-8 | 2 | ASCII | Message type | Decimal string |
| 9-12 | 4 | ASCII | Query type "0030" | UTF-8 |
Per-Inverter Signal Data (starting at offset 15):
| Offset | Length | Type | Description | Encoding |
|---|---|---|---|---|
| +0 | 6 | Binary | Inverter UID | Hex (12 chars) |
| +6 | 1 | Binary | Signal strength (0-255) | Raw byte |
Total record length: 7 bytes per inverter
Signal Strength Calculation:
signal_dbm = -100 + (raw_value / 255) * 100
- Extracts UTF-8 encoded strings from binary data
- Parameters:
codec[start:start+amount].decode("utf-8")
- Extracts big-endian integers from binary data
- Parameters:
int.from_bytes(codec[start:start+length], byteorder="big")
- Extracts hexadecimal UID strings (12 characters)
- Parameters:
codec[start:start+length].hex()[:12]
- Extracts BCD-encoded timestamps
- Format: YYYYMMDDHHMMSS → "YYYY-MM-DD HH:MM:SS"
- Parameters:
codec[start:start+amount].hex()
All responses include a checksum validation:
- Extract checksum from bytes 5-9 as decimal
- Verify:
len(data) - 1 == checksum - Verify signature: starts with "APS", ends with "END"
| Type Code | Model | Channels | Description |
|---|---|---|---|
| "01" | YC600 | 2 | 2-channel inverters |
| "02" | YC1000 | 4 | 4-channel inverters |
| "03" | QS1 | 4 | 4-channel (special voltage layout) |
| "04" | DS3 | 2 | 2-channel inverters |
| "05" | QT2 | 4 | 4-channel inverters |
| ECU ID Prefix | Model | Description |
|---|---|---|
| "2160" | ECU-R | Standard ECU |
| "2162" | ECU-R-Pro | Professional ECU |
| "2163" | ECU-B | Bridge ECU |
| "2150" | ECU-C | Commercial ECU (with CT support) |
| "2030" | ECU-3 | Third generation ECU |
- ECU-C Models (215x): Support additional power meter data queries
- Replaced Inverters: When replacement flag ≠ "01", skip processing but advance 9 bytes
- Temperature: Raw value minus 100 gives degrees Celsius
- Energy Values: Lifetime energy divided by 10, today energy divided by 100
- Frequency: Raw value divided by 10 gives Hz
- Connection Management: Each query requires separate connection (open/close)
Common error conditions:
- Invalid checksum
- Malformed signature (not APS...END)
- Socket timeout or connection errors
- Invalid data length
- Index out of bounds during parsing
The protocol supports multiple data format versions indicated by the version field in ECU responses:
- Version "01": Includes timezone information
- Version "02": Simplified format without timezone