dbus-maxxfan — a MaxxFan Deluxe on the Venus OS switch pane, over infrared
dbus-maxxfan — a MaxxFan Deluxe on the Venus OS switch pane, over infrared
The MaxxAir MaxxFan Deluxe is in a lot of camper vans, and it has no data
connection whatsoever — no VE.Direct, no Bluetooth, no wires beyond power. The
only way in is the infrared remote.
So that is the way in. An Arduino with an infrared LED hangs on a USB port of the
GX device and transmits; a driver publishes the fan on the D-Bus as
com.victronenergy.switch. The result is a proper card in the switch pane, on
the GX display, in the remote console and in VRM.
Repository: GitHub - mcgyver78/dbus-maxxfan · GitHub (MIT)
[screenshot: docs/switch-pane.png from the repository]
What you get
One card named MaxxFan with eight controls:
| Control | Type | What it does |
|---|---|---|
| Fan | toggle | on / off |
| Speed | slider | 10 … 100 %, in steps of ten |
| Direction | dropdown | Intake / Exhaust |
| Lid | toggle | open / closed |
| Mode | dropdown | Manual / Auto (thermostat) |
| Auto setpoint | temperature setpoint | −2 … 37 °C |
| Resend | button | transmit the current state again |
| Beep | button | make the fan beep twice |
Everything is also on /SwitchableOutput/<name>/…, so Node-RED and MQTT can read
and write the same values. Running the fan from a temperature sensor of your own,
or from PV surplus, is a small flow — the driver deliberately does not try to be
a thermostat itself.
Hardware
An ATmega328P board — classic Nano, Uno or Pro Mini — with an infrared LED on
pin 9 through a 180…220 Ω resistor, cathode to ground. That is the entire
circuit:
D9 (OC1A) --[ 180 ... 220 R ]--|>|-- GND
IR LED
Pin 9 is not a free choice: the 38 kHz carrier is generated by timer 1 in
hardware. That also means the sketch does not have to disable interrupts while
transmitting — a packet occupies the LED for about 150 ms, and sketches that
bit-bang the carrier lose every serial byte that arrives in that window.
The LED belongs next to the fan’s own receiver window; at ~20 mA straight off the
pin it carries a metre or two, which is plenty from there.
Installation
Install SetupHelper, then Settings →
Package manager → Inactive packages → new:
| Field | Value |
|---|---|
| Package name | dbus-maxxfan |
| GitHub user | mcgyver78 |
| GitHub branch or tag | latest |
Flash arduino/maxxfan_tx onto the board (no libraries needed), plug it into the
GX device, done. The driver finds the port through /dev/serial/by-id/ and asks
each candidate to identify itself before sending it anything, so it will never
talk to your VE.Direct cables by mistake.
Requires Venus OS v3.70 or newer with the new user interface — the switch pane is
a gui-v2 feature.
Infrared only goes one way
Worth being explicit about, because it shapes everything else: the fan never
answers. What the display shows is the state the driver last transmitted, not a
reading. Use the hand held remote and the two drift apart until the next command
from the GX — which immediately puts them back in step, because every packet
carries the complete state.
Two consequences:
- The driver does not transmit at startup. Re-sending the stored state on
every GX reboot would start a fan somebody deliberately switched off by hand. - A periodic refresh every 15 minutes re-sends the current state, so a fan
operated by hand comes back under GX control on its own. There is also a
Resend button.
About the protocol
The packet layout was published by
skypeachblue and
wingspinner, and implemented for ESPHome by
brown-studios. This
driver builds the packet from the requested state rather than replaying recorded
pulse trains — which is what makes the thermostat setpoint reachable at all, and
incidentally shrinks the sketch from 88 % of the flash to 10 %.
Checking the encoder against the 99 signals captured from an original remote
turned up two things that differ from the published implementations. Both are
documented and the comparison script ships in tools/:
- The symbol period is 834 µs, not the 800 µs usually quoted.
- The setpoint is stored in Fahrenheit, and the remote’s conversion from the
Celsius it displays truncates towards zero:degF = trunc(degC × 1.8) + 32.
A rounded conversion is off by one degree on 20 of the 38 setpoints — 21 °C
becomes 70 °F instead of the 69 °F the remote actually sends.
Both are tolerated by the fan, but matching the original leaves the widest margin
on a weak or off-axis path.
Why the switch service
Venus OS has no service class for a ventilation fan. com.victronenergy.switch
is the one the GUI scans for /SwitchableOutput/x/…, and it offers exactly what
this device needs: toggles, a value slider, dropdowns with labels, a temperature
setpoint and momentary buttons. The same API carries the GX IO extender, Node-RED
virtual switches and third party digital switches, so the fan sits on the switch
pane next to them with no GUI modification at all.
One thing to know if you build something similar: the stepped switch (type 4)
sends the number of the position it sits on, not the value, and ignores the
minimum, maximum and step size the driver publishes. It cost me an hour of
watching the fan beep at every step without changing speed. Use the basic slider
(7) or the dimmer (2) for anything with a real range.
Status
Running here on Venus OS v3.79 against a MaxxFan Deluxe. Not implemented yet, but
cheap to add: an infrared receiver (TSOP38238 or similar) on D2 so the driver can
see what the hand held remote sends and stay in step with it. The decoder already
exists in the verification tool; it only needs porting to C.
Feedback and pull requests welcome.

