Reverse engineering a portable fridge
My previous fridge in the car finally kicked the bucket (it was already quite old when I picked it up second-hand). So I replaced it with a new fridge - A Dometic / Waeco CFX40 - which just so happens to have WiFi. As far as I can tell the intended purpose of having wifi is to enable control and status reporting from a mobile app.
I don't really use the feature, usually I just control and monitor the fridge from the back of the car when I'm actually using it, however, it seemed like a fun project to reverse engineer the protocol and build a custom Python interface.
The communications protocol and associate functionality was determined primarily through analysis of the mobile application, as well as analysis of network traffic between the mobile and the fridge and a custom Python interface developed to communicate with the fridge. Data from the fridge (Fridge status, temp and a rough battery voltage) can be collected when the vehicle is in range. Interestingly, the SoC in the fridge can also be configured to work as a client, so in theory it could connect to an existing infrastructure network - however I was unable to get this to work.
The particular device that I'm interacting with is a Dometic / Waeco CFX 40L single-compartment portable fridge/freezer, though the mobile application will work with all supported Dometic / Waeco CFX models.
One of these:
Connecting to the device's network and running some scans/probes indicates that the device runs what appears to be a TI SimpleLink Wi-Fi CC3200 - A single-chip wireless MCU from Texas Instruments, that combines an ARM Cortex-M4 with wireless connectivity.
There are two TCP ports listening on the device - TCP 80 and TCP 6378 - The configuration interface for the TI interface is on port 80 which is unencrypted and unauthenticated. This is the standard configuration interface, and there is nothing special here, It's possible to change some of the WiFi configuration of the device through this interface.
Whilst the CC3200 does support station mode, and can be configured to use Station mode via the web interface I was unable to get this to work - From reading the documentation, this could be because device is either set to AP mode programmatically on startup, or is in 'Force AP' Mode, which is set via a GPIO pin.
By default, the device is set in AP mode with an IP address of 192.168.1.1 (the mobile application is hardcoded to use this address).
On port 6378 we have the Dometic / CFX application protocol. The wire protocol used on port 6378 is a custom binary protocol, which is unencrypted, unauthenticated and implements basic data validation through a simple checksum. It's a fixed-length protocol, with each field corresponding to a specific parameter relevant to the operation of the fridge.
Packets from the app to the fridge are fixed at a length of 15 bytes. Not all fields need to be set, and the
command_type will determine which fields need to be set when sending a packet to the fridge.
Responses from the fridge are fixed at 20 bytes. This can contain the full configuration string including all set parameters. There are 5 additional bytes that are not used, as far as I can tell.
Attributes of the wire protocol are:
- Entirely static - positions of each data element fixed in the data structure
- Payloads are fixed-length, but the protocol still implements a payload length field.
- Can get current/set temp, errors, alarms, etc.
- Can send commands to check info, set alarms, set temp, turn off/on
A simple checksum is calculated and sent at the end of each packet. If this checksum does not validate, the packet is ignored. The checksum is the sum of the ordinal value of each byte in the byte array.
The exception to all of the above is the command to change the WiFi credentials. This command is non-standard, and communicates directly with the fridge without using the same structure as the rest of the protocol.
Binary protocol structure
The protocol structure for a request and response is detailed below:
|Request data structure (app -> fridge)||Response data structure (fridge -> app)|
There are some fields that have multiple (somewhat unrelated) uses:
- Temperature Unit / Compartment enable (CF_COMPONOFF) - Dual use to set the temp unit or enable/disable a compartment. Depends on command sent. Flags in response.
- Door Status / AC power Status (STATUS_OF_DOORS_AND_AC) - Flags indicating status of the doors, and whether or not AC Power is connected.
Magic: This is either 0x33 in the request, or 0xCC in the response
Command: Command is only valid in the request. There are 9 possible commands, which are:
-  COMMAND_CHECK_INFO: Request current status from the fridge
-  COMMAND_SET_TEMP_COMP1: Set the desired temperature of compartment 1 (main compartment if only one available)
-  COMMAND_SET_TEMP_COMP2: Set the desired temperature of compartment 2 (Not applicable on single compartment models)
-  COMMAND_SET_ABS: Set the battery protection mode
-  COMMAND_SET_CF: Set the temperature unit - Celcius or Fahrenheit
-  COMMAND_TURN_ON_OFF: Turn the fridge on or off
-  COMMAND_SET_TEMP_CALIB: Set the temperature calibration
-  COMMAND_SET_ALARM: Set the alarm mode
-  COMMAND_RESET_WIFI: This is not actually implemented in the Android application.
Payload Size: The length of the payload following this byte. Essentially, it's length-3. It's a fixed-length protocol so this is always 12 in the request and 17 in the response.
Set Temp C1: Compartment 1 Set Temperature. Valid values are -127 to 127, but I suspect this is due to the data type on the device itself. I've not yet tested setting to an out-of-range value (I.E a value that the fridge itself does not support).
Set Temp C2: Compartment 2 Set Temperature. Only valid if this is one of the larger, dual-compartment models.
Battery Protection Level: Also known as ABS (My best guess is - Auto Battery Shutdown, but I could be making that up). Sets the sensitivity of the low-battery shut off to either low, medium or high. The voltage thresholds are unclear.
Temp Unit / Compartment Enable: Dual use to set the temp unit or enable/disable a compartment (if a dual-compartment model).
Power: Fridge on or off. 0 for off, 1 for On
Temp Calibration: Calibration setting - Haven't looked into what this does.
Alarm Set: The fridge can alarm (via the phone) if the temperature is +3 or +5 above the set temperature. 0 - 2 are valid values (0 = Off, 1= +3, 2=+5)
DC Voltage: Current DC Supply voltage. Unclear What this is if on AC only
C1 Temp: Current temperature of compartment 1
C2 Temp: Current temperature of compartment 2, if a dual-compartment model
Compressor Status: Indicates if the compressor is currently running, or not running
Door / AC Status: Flags indicating status of the doors, and whether or not AC Power is connected
Error Flags: Flags byte indicating potential errors/alarms. The possible error codes are:
There is also functionality to change the WiFi password, which is not implemented using the standard command structure. Instead, this function creates a packet containing two magic bytes (the first of which is the same as all CFX commands. The second may be a command), the length of the new SSID and password and the actual SSID and password.
|1||Magic or command|
On success the fridge will send the response
b'\xcc\xee\x02\x01\xbd' and immediately disconnect and restart with the new SSID and passphrase. On failure, no response will be sent and no action will be taken.
I've written a python library that implements most functionality. Almost all of the functionality included in the original mobile app is implemented in Python, however, given I only have a single-compartment model it's written with this in mind and is not tested with dual-compartment models.
Using this library, one can now interact with the fridge via a Python script. The device will need to be connected to the Fridge's WiFi network, and the address is normally hardcoded to 192.168.1.1 - this could be changed, if the fridge is connected to an existing network.
Example demonstrating an active door open alarm:
This article will be updated with a link to the code soon.