# ComfoControl Protocol
This document tries to explain the ComfoControl Protocol used by Zehnder ventilation units. You need a
*ComfoConnect LAN C* device to interface with the unit.

**Warning: This documentation is incomplete. If you have more information, don't hesitate to contribute by opening an
issue or making a PR.**

## General packet structure
The messages are send in a TCP-connection to port 56747, and are prepended with a header that resembles the
"Length-prefix protocol buffers" format.

This file [zehnder.proto](protobuf/zehnder.proto) contains a Protocol Buffers definition of the protocol.

## Manually decoding a packet
The `protoc` utility can be used to manually decode a message based on the hex representation of the payload. You need
to strip the headers and extract the `cmd` and `msg` before passing it to `protoc`. See the sections below to find out
what to strip.

Example:
```markdown
0000004 ea886190220044d68a07d85a2e3866fce 0000000000251010800170b3d54264b4 0004 08022002 0a10a886190220044d68a07d85a2e3866fce10001a126950686f6e652076616e2044657374696e79
length_ src______________________________ dst_____________________________ cmd# cmd_____ msg_____________________________________________________________________________
```

Command:
```sh
$ echo "08022002" | xxd -r -p | protoc --decode=GatewayOperation zehnder.proto
```
```javascript
type: RegisterAppRequestType
reference: 2
```

Message:
```sh
$ echo "0a10a886190220044d68a07d85a2e3866fce10001a126950686f6e652076616e2044657374696e79" | xxd -r -p | protoc --decode=RegisterAppRequest zehnder.proto
```
```javascript
uuid: "\250\206\031\002 \004Mh\240}\205\242\343\206o\316"
pin: 0
devicename: "iPhone van Destiny"
```

## Device discovery (`DiscoveryOperation`)
The bridge can be discovered by sending an UDP packet containing `0x0a00` to your network's broadcast address on port
56747. It will respond with the following connection information, also in a UDP packet. The last 6 bytes of the
identifier seems to be the bridge's MAC address. This packet contains no header and can be fully passed to Protocol
Buffers.

Raw response data:
```
12230a0d3139322e3136382e312e32313312100000000000251010800170b3d54264b41801
```
```javascript
searchGatewayResponse {
  ipaddress: "192.168.1.213"
  uuid: "\000\000\000\000\000%\020\020\200\001p\263\325Bd\264"
  version: 1
}
```

You now have the bridge's `ipaddress` and `uuid`. You need these for further communication.

## Bridge communication (`GatewayOperation`)
Further communication with the bridge happens by opening a TCP connection to the bridge on port 56747. Every message
will start with the `length` of the message, excluding this `length`-field itself. 

Note that only `op` and `msg` is valid Protocol Buffers data. The other fields should not be parsed.

### Header
The header then consist of a `src`, `dst` and `op_length`, followed by the `op` and the `msg`. The `src` seems to be
generated by the client, the `dst` has to be obtained from the discovery packet (`uuid`). The `op_length` fields
indicates the length of the `op` field, the rest of the data contains the `msg`.

| Field                 | Data                                 | Remark                                                |
|-----------------------|--------------------------------------|-------------------------------------------------------|
| length (32 bit)       | `0x0000004f`                         | Length of the whole message excluding this field      |
| src (12 bytes)        | `0xaf154804169043898d2da77148f886be` |                                                       |
| dst (12 bytes)        | `0x0000000000251010800170b3d54264b4` |                                                       |
| op_length (16 bit     | `0x0004`                             | Length of the `op` message                            |
| op (variable length)  | `0x08342002`                         | Message with type `GatewayOperation`                  |
| msg (variable length) | `...`                                | Message with type that is stated in `op.type`         |

### Commands
A message consists of a command block (`GatewayOperation`) and a message block (variable type). The command block
contains a `type` and `reference` field. The `type` field indicates the type of the message block. The `reference` field
will contain a incremental number that can be used to link a request with a reply.

This is a list of the commands.

| Request                       | Confirm                       | Notification / Response | Description                |
|-------------------------------|-------------------------------|-------------------------|----------------------------|
| NoOperation                   |                               |                         |                            |
| SetAddressRequestType         | SetAddressConfirmType         |                         |                            |
| RegisterAppRequestType        | RegisterAppConfirmType        |                         | Adds a device in the registration list |
| StartSessionRequestType       | StartSessionConfirmType       |                         | Start as session with the bridge |
| CloseSessionRequestType       | CloseSessionConfirmType       |                         | Terminate your session     |
| ListRegisteredAppsRequestType | ListRegisteredAppsConfirmType |                         | Returns a list of registered apps on the bridge |
| DeregisterAppRequestType      | DeregisterAppConfirmType      |                         | Remove a UUID from the registration list |
| ChangePinRequestType          | ChangePinConfirmType          |                         | Change the PIN code        |
| GetRemoteAccessIdRequestType  | GetRemoteAccessIdConfirmType  |                         |                            |
| SetRemoteAccessIdRequestType  | SetRemoteAccessIdConfirmType  |                         |                            |
| GetSupportIdRequestType       | GetSupportIdConfirmType       |                         |                            |
| GetWebIdRequestType           | GetWebIdConfirmType           |                         |                            |
| SetWebIdRequestType           | SetWebIdConfirmType           |                         |                            |
| SetPushIdRequestType          | SetPushIdConfirmType          |                         |                            |
| DebugRequestType              | DebugConfirmType              |                         |                            |
| UpgradeRequestType            | UpgradeConfirmType            |                         |                            |
| SetDeviceSettingsRequestType  | SetDeviceSettingsConfirmType  |                         |                            |
| VersionRequestType            | VersionConfirmType            |                         |                            |
|                               |                               | GatewayNotificationType |                            |
|                               |                               | KeepAliveType           | You should send these to keep the connection open. |
| FactoryResetType              |                               |                         |                            |
| CnTimeRequestType             | CnTimeConfirmType             |                         | Returns the seconds since 2000-01-01 00:00:00. |
| CnNodeRequestType             |                               | CnNodeNotificationType  |                            |
| CnRmiRequestType              | CnRmiResponseType             |                         |                            |
| CnRmiAsyncRequestType         | CnRmiAsyncConfirmType         | CnRmiAsyncResponseType  |                            |
| CnRpdoRequestType             | CnRpdoConfirmType             | CnRpdoNotificationType  |                            |
|                               |                               | CnAlarmNotificationType |                            |
| CnFupReadRegisterRequestType  | CnFupReadRegisterConfirmType  |                         |                            |
| CnFupProgramBeginRequestType  | CnFupProgramBeginConfirmType  |                         |                            |
| CnFupProgramRequestType       | CnFupProgramConfirmType       |                         |                            |
| CnFupProgramEndRequestType    | CnFupProgramEndConfirmType    |                         |                            |
| CnFupReadRequestType          | CnFupReadConfirmType          |                         |                            |
| CnFupResetRequestType         | CnFupResetConfirmType         |                         |                            |

#### RegisterApp (`RegisterAppRequestType` and `RegisterAppConfirmType`)
Before you can login, you need to register your device, by sending a `type: RegisterAppRequestType`.
```javascript
type: RegisterAppRequestType
reference: 15

uuid: "\251\226\031\002 \004Mh\240}\205\242\343\206o\312"
pin: 0
devicename: "Computer"
```

The bridge will respond with a `type: RegisterAppConfirmType`. 
In case of success, it will respond like this:
```javascript
type: RegisterAppConfirmType
reference: 15
```

In case of a failure (invalid PIN), it will respond with a `result: NOT_ALLOWED`.
```javascript
type: RegisterAppConfirmType
result: NOT_ALLOWED
reference: 15
```

#### StartSession (`StartSessionRequestType` and `StartSessionConfirmType`)
The client logs in by sending a `type: StartSessionRequestType`. Only one client can be logged in at the same time.
```javascript
type: StartSessionRequestType
reference: 16
```

In case of a success, it will respond with a `type: StartSessionRequestType` with `result: OK`.
```javascript
type: StartSessionConfirmType
result: OK
reference: 16
```

If another client is already logged in, the bridge will respond with a `result` of `OTHER_SESSION`. The name of the
other device will be in `devicename`.  
```javascript
type: StartSessionConfirmType
result: OTHER_SESSION
reference: 16

devicename: "Google Nexus 5X"
```

You can force the takeover of this session by specifying a `takeover: 1`.
```javascript
type: StartSessionConfirmType
result: OK
reference: 17

takeover: 1
```

Next, we see a few notifications. They seem to be messages to let the client know what nodes are available. We only
send messages to `nodeId: 1`.
```javascript
type: CnNodeNotificationType

nodeId: 1
productId: 1
zoneId: 1
mode: NODE_NORMAL
```

```javascript
type: CnNodeNotificationType

nodeId: 48
productId: 5
zoneId: 255
mode: NODE_NORMAL
```

These are the known `productId`s.

| productId | type           | description                                                                             |
|-----------|----------------|-----------------------------------------------------------------------------------------|
| 1         | ComfoAirQ      | The ComfoAirQ ventilation unit.                                                         |
| 2         | ComfoSense     | ComfoSense C                                                                            |
| 3         | ComfoSwitch    | ComfoSwitch C                                                                           |
| 4         | OptionBox      |                                                                                         |
| 5         | ZehnderGateway | ComfoConnect LAN C                                                                      |
| 6         | ComfoCool      | ComfoCool Q600                                                                          |
| 7         | KNXGateway     | ComfoConnect KNX C                                                                      |
| 8         | Service Tool   |                                                                                         |
| 9         | PT Tool        | Production test tool                                                                    |
| 10        | DVT Tool       | Design verification test tool                                                           |

#### CloseSession (`CloseSessionRequestType`)
The client logs out by sending a `type: CloseSessionRequestType`. The bridge doesn't seem to send a response on 
this.
```javascript
type: CloseSessionRequestType
```

When the session is closed by the bridge (because another client connects with `takeover: 1`), the bridge will also send
a `type: CloseSessionRequestType` message to the client.

#### CnRpdoRequest (`CnRpdoRequestType`, `CnRpdoConfirmType` and `CnRpdoNotificationType`)
To receive status updates, you need to register to a `pdid` by sending a `type: CnRpdoRequestType`. You also need
to specify a `pdid`, `zone`, `type` and `timeout`. The zone always seems to be `1`. The `type` seems to depend on the 
`pdid`.
```javascript
type: CnRpdoRequestType
reference: 104

pdid: 176
zone: 1
type: 1
timeout: 4294967295
```

The bridge will reply with a `type: CnRpdoConfirmType`.
```javascript
type: CnRpdoConfirmType
result: OK
reference: 104
```

Next, when an update is available, the bridge will send a `type: CnRpdoNotificationType`.
```javascript
type: CnRpdoNotificationType

pdid: 176
data: "\000"
```

For known PDOs, check [PROTOCOL-PDO.md](PROTOCOL-PDO.md)

#### CnRmiRequest (`CnRmiRequestType` and `CnRmiResponseType`)
You can execute a function on the device by invoking a `type: CnRmiRequestType`. You need to specify the `nodeId` and a
`message`. This can make a configuration change, or request data.

```javascript
type: CnRmiRequestType
reference: 122

nodeId: 1
message: "\001\001\001\020\010"
```

The bridge will respond with a `type: CnRmiResponseType`.
```javascript
type: CnRmiResponseType
reference: 122

message: "ComfoAir Q450 B R RF ST Quality\000"
```

For an overview about the known RMI Requests, check [PROTOCOL-RMI.md](PROTOCOL-RMI.md)