A Go-based gateway application that communicates with Drexel&Weiss Aerosmart M ventilation/heatpump devices via USB serial and integrates with Home Assistant via MQTT.
⚠️ Disclaimer: This software is provided "as is", without warranty of any kind. Use at your own risk. The authors accept no liability for any damages, data loss, or device malfunctions resulting from the use of this software. This project is not affiliated with or endorsed by Drexel und Weiss.
- Serial Communication: Reads and writes to Aerosmart M device via serial over USB located on Mainboard - refer to INSTALLATION.md. NOTE: It is NOT Modbus RTU, no modbus settings required.
- MQTT Integration: Publishes sensor data and subscribes to control commands
- MQTT Resilience:
- Persistent Session: Subscriptions preserved across reconnects (CleanSession=false)
- Auto-Recovery: Automatically re-subscribes to topics after connection loss
- Configurable QoS: Supports QoS 0, 1, or 2 (default: 1 for at-least-once delivery)
- KeepAlive Monitoring: Detects network failures within 30-60 seconds
- Connection State Visibility: Logs reconnection progress and state changes
- Publish Retry: Automatic retry with exponential backoff for failed publishes
- Home Assistant Discovery: Auto-discovers sensors and switches in Home Assistant
- Derived Calculations: Calculates derived values (e.g., zuluftabluftprozent, co2luefterstufe4)
- Configurable Logging: Supports debug, info, warn, and error log levels
- Graceful Shutdown: Handles SIGINT and SIGTERM signals for clean shutdown
- Reconnection: Automatic reconnection for both serial and MQTT connections
- Write Priority: Control commands preempt ongoing read operations for low-latency response (<1 second detection, 1-3 second write completion). Write priority auto-expires after 10 seconds to prevent blocking on serial errors.
- Message Deduplication: Prevents processing duplicate MQTT messages within 1 second window
- Timing Metrics: Logs message processing latency (receive → process → complete) for monitoring
The application follows a layered architecture with the following main components:
┌─────────────────────────────────────────────────────────────────┐
│ main.go │
│ - Initialization & Configuration │
│ - Connection Management │
│ - Main Loop (Periodic Reads) │
└─────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌─────────────────┐ ┌───────────────┐
│ serial/ │ │ mqtt/ │ │ registers/ │
│ serial.go │ │ client.go │ │ reader.go │
│ │ │ │ │ │
│ - Open/Close │ │ - Connect │ │ - ReadSingle │
│ - Write │ │ - Publish │ │ - ReadAll │
│ - Read │ │ - Subscribe │ │ - PublishAll │
│ - SendAnd │ │ - Discovery │ │ │
│ Receive │ │ │ │ │
└───────────────┘ └─────────────────┘ └───────────────┘
│ │ │
│ │ │
└─────────────────────┼─────────────────────┘
▼
┌─────────────────┐
│ registers/ │
│ reader.go │
│ (Writer) │
│ - HandleMessage │
│ - TriggerFull │
└─────────────────┘
- Serial Port (
internal/serial/serial.go): Handles all serial communication with the device, including retry logic and port management - MQTT Client (
internal/mqtt/client.go): Manages MQTT connections, publishing, and subscriptions - Register Reader (
internal/registers/reader.go): Reads register values from the device and publishes to MQTT - Register Writer (
internal/registers/reader.go): Handles write commands from MQTT and verifies after writing
- Go 1.25 or later
- A serial device connected to your Aerosmart M ventilation system
- MQTT broker (e.g., Mosquitto)
# Clone the repository
git clone https://github.com/nean/aerosmart-gateway.git
cd aerosmart-gateway
# Build the application
go build -o aerosmart-gateway ./cmd/main.go- Copy the example configuration file:
cp config/config.yaml.example config.yaml- Edit
config.yamlwith your settings:
serial:
port: "/dev/ttyUSB0"
baudrate: 115200
mqtt:
broker: "192.168.1.20"
port: 1883
username: "mqtt"
password: "your_password"
client_id: "aerosmart-gateway"
device_id: "aerosmart"
log_level: "info"
read_interval: 60
ha_discovery:
enabled: true
prefix: "homeassistant"
device_info:
name: "Aerosmart Gateway"
manufacturer: "Drexel und Weiss"
model: "aerosmartPI"
# sw_version: defaults to the application version if not set./aerosmart-gateway -config config.yaml -registers registers.yamlExample of full register readout:

For detailed information about the application flow and timing diagrams, see:
- Application Flow Analysis - Detailed analysis of the application flow
- Timing Diagrams - Visual timing diagrams of component interactions
- Installation Guide - Detailed setup instructions
| Option | Description | Default |
|---|---|---|
-config |
Path to config file | config.yaml |
-registers |
Path to registers file | registers.yaml |
-version |
Show version information | false |
| Option | Description | Default |
|---|---|---|
serial.port |
Serial device path | /dev/ttyUSB0 |
serial.baudrate |
Baud rate | 115200 |
serial.read_timeout |
Read timeout (ms, 0=200ms fallback) | 2 |
serial.device_response_delay |
Wait after write (ms) | 40 |
serial.max_retries |
Max operation retries | 10 |
serial.max_reopens |
Max port reopen attempts | 10 |
| Option | Description | Default |
|---|---|---|
mqtt.broker |
MQTT broker address | (required) |
mqtt.port |
MQTT broker port | 1883 |
mqtt.username |
MQTT username | - |
mqtt.password |
MQTT password | - |
mqtt.client_id |
Client identifier | aerosmart-gateway |
mqtt.qos |
Quality of Service (0, 1, or 2) | 1 |
mqtt.retain |
Retain messages | true |
mqtt.publish_retry_count |
Number of retries for failed publishes | 3 |
mqtt.connect_retry_initial_delay_ms |
Initial delay for exponential backoff | 500ms |
mqtt.connect_retry_max_delay_ms |
Maximum delay for exponential backoff | 30000ms |
mqtt.connect_retry_jitter_percent |
Jitter percentage for backoff | 25% |
MQTT Resilience Note: The gateway uses the configured QoS level (default: 1, at-least-once delivery) for subscriptions and publishes to ensure message reliability. Persistent sessions (CleanSession=false) preserve subscriptions across reconnects. KeepAlive is set to 30 seconds for fast disconnect detection.
| Option | Description | Default |
|---|---|---|
device_id |
Device identifier (used as HA identifiers) | aerosmart |
log_level |
Logging level | info |
read_interval |
Read interval (seconds) | 60 |
ha_discovery.enabled |
Enable HA discovery | true |
ha_discovery.prefix |
HA discovery topic prefix | homeassistant |
ha_discovery.device_info.name |
Device display name | Aerosmart Gateway |
ha_discovery.device_info.manufacturer |
Device manufacturer | Drexel und Weiss |
ha_discovery.device_info.model |
Device model | aerosmartPI |
ha_discovery.device_info.sw_version |
Software version (defaults to app version) | - |
The gateway publishes sensor values to the following topics:
aerosmart/luefterstatus- Fan status (0-5)aerosmart/lueftermode- Fan mode (0-5)aerosmart/co2- CO2 level (ppm)aerosmart/innentemp- Indoor temperature (°C)aerosmart/aussentemp- Outdoor temperature (°C)aerosmart/raumsolltemp- Room setpoint temperature (°C)aerosmart/zuluftumin- Supply air RPMaerosmart/abluftumin- Exhaust air RPMaerosmart/zuluftprozent- Supply air percentage (%)aerosmart/abluftprozent- Exhaust air percentage (%)aerosmart/zuluftsollvolumenstrom- Supply air setpoint (m³/h)aerosmart/abluftsollvolumenstrom- Exhaust air setpoint (m³/h)- And more... (see registers.yaml)
The gateway subscribes to the following topics for device control:
dw/aerosmart/luefterstufe- Set fan stage (0-5) - Topic changes detected and processed within 1-3 secondsdw/aerosmart/boilerheizstab- Set boiler heating element (0-1) - Topic changes detected and processed within 1-3 seconds
Note: The gateway implements immediate message detection with timestamp-based write priority signaling (auto-expires after 10 seconds), ensuring control commands are processed with minimal latency.
The gateway calculates and publishes derived values:
aerosmart/zuluftabluftprozent- Supply/Exhaust percentageaerosmart/co2luefterstufe4- CO2 fan stage 4 (calculated from stage 3)aerosmart/beschattungtemp_adjusted- Adjusted shading temperature
When ha_discovery.enabled is set to true, the gateway automatically publishes discovery configurations to Home Assistant. This creates:
- Sensors: For all read registers (temperature, CO2, fan speed, etc.)
- Switches: For write registers (fan stage, boiler heating element)
The devices will appear in Home Assistant with the name "Aerosmart Gateway".
Example of Home Assistant dashboard:

- Load configuration from config.yaml
- Load register definitions from registers.yaml
- Initialize logger
- Connect to serial device (with retry)
- Connect to MQTT broker (with retry)
- Publish Home Assistant discovery configs
- Subscribe to write register topics
- Start periodic read loop
- Timer fires every N seconds (configurable)
- Writer checks for write priority (if write pending, skip read)
- Reader reads all configured registers sequentially
- Each register: Send command → Read response → Parse → Validate → Publish to MQTT
- Wait for next timer interval
- MQTT message received on write topic
- Writer signals write priority, preempting the periodic read cycle (the in-flight single-register read finishes first; remaining registers are skipped)
- Parse and validate value
- Send command to device
- Read response and verify
- Read verify registers (if configured)
- Publish verified values to MQTT
The application implements comprehensive retry logic:
- Connection Retry: Exponential backoff with jitter for both serial and MQTT
- Serial Retry: Write and read retries with port reopening on failure
- Max Retries: Configurable maximum attempts before giving up
- MQTT Publish Retry: Failed publishes are retried with exponential backoff (1s, 2s, 4s)
- MQTT KeepAlive: Detects network failures within 30-60 seconds for fast reconnection
- Subscription Recovery: Automatically re-subscribes to all topics after connection is restored
The repository ships a ready-to-use, hardened unit at docs/aerosmart.service and an installer (docs/install.sh) that deploys the binary, config, and service. See the Installation Guide for the full walkthrough.
To install the service manually:
sudo cp docs/aerosmart.service /etc/systemd/system/aerosmart.service
sudo systemctl daemon-reload
sudo systemctl enable --now aerosmartThe unit expects the binary and config under /opt/aerosmart/ (aerosmart-gateway, config.yaml, registers.yaml) and gates startup on the serial device via ConditionPathExists=/dev/ttyUSB0.
go test ./...FROM golang:1.25-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o aerosmart-gateway ./cmd/main.go
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /app/aerosmart-gateway .
COPY config.yaml .
COPY registers.yaml .
CMD ["./aerosmart-gateway", "-config", "config.yaml", "-registers", "registers.yaml"]golangci-lint runThe registers.yaml file defines:
- Read Registers: Values to read from the device
- Write Registers: Values that can be written to the device
- Derived Registers: Calculated values based on read registers
Each register has:
name: Unique identifiercommand: Serial command to sendtopic: MQTT topic for publishing/subscribingdivisor: Value divisor (for float conversion)type: Value type (integer or float)min_value/max_value: Valid rangeha: Home Assistant configuration
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
- Serial communication library: tarm/serial
- MQTT client library: eclipse/paho.mqtt.golang