A lightweight HTTP proxy for Carrier/Bryant Infinity HVAC systems that logs XML traffic and exposes Prometheus-compatible metrics.
- π Traffic Inspection - Intercepts and logs all HTTP requests/responses between your thermostat and HVAC system
- π Prometheus Metrics - Exposes temperature, humidity, fan speed, and system status as Prometheus gauges
- πΎ XML Logging - Saves prettified XML payloads to disk for analysis
- π‘ MQTT Support - Optionally publish status to MQTT topic
- π Transparent Proxy - Forwards all traffic unmodified to maintain system functionality
- π³ Docker Ready - Minimal image size (~2MB) with multi-stage builds
%%{ init: { 'flowchart': { 'curve': 'linear' }, 'theme': 'neutral' } }%%
flowchart LR
%% --- Main Flow ---
Thermostat --> proxy
proxy --> upstream
%% --- Docker Container Subgraph ---
subgraph dc["Docker Container"]
direction TB
subgraph proxy["hvac-proxy"]
metrics_service["http://<YOUR_HOST_IP>:8080/metrics"]
disk["/data"]
end
end
Tested with:
- Bryant Evolution systems
Expected to work with:
- Carrier Infinity systems
- Systems using Proteus AC outdoor units
- Multi-zone systems with up to 8 zones
# Pull from your registry
docker pull kwv4/hvac-proxy:latest
# Run the proxy (with optional BLOCK_UPDATES environment variable to block updates)
docker run -d \
-p 8080:8080 \
-v /var/log/hvac:/data \
-e BLOCK_UPDATES="true" \
--name hvacproxy \
kwv4/hvac-proxy:latest# Clone the repository
git clone https://github.com/kwv/hvac-proxy
cd hvac-proxy
# Initialize and build
go mod tidy
go build -o hvac-proxy
# Run
./hvac-proxyYour thermostat needs to connect to the machine running the proxy:
# On Linux
ip addr show | grep "inet " | grep -v 127.0.0.1
# On macOS
ifconfig | grep "inet " | grep -v 127.0.0.1
# Or check your router's DHCP client listLook for an IP like 192.168.1.100 on your local network.
Point your HVAC thermostat to the proxy:
- Host: Your Docker host IP (e.g.,
192.168.1.100) - Port:
8080
The exact configuration method depends on your thermostat model. Consult your thermostat's network settings or API configuration.
- View metrics:
http://YOUR_HOST_IP:8080/metrics - Check logs:
docker logs -f hvacproxy - Verify XML files are being created in
/var/log/hvac/(or your mounted volume)
The /metrics endpoint exposes Prometheus-compatible gauges:
| Metric | Description | Unit |
|---|---|---|
outdoorAirTemp |
Outdoor temperature | Β°F |
fanSpeed |
Fan speed | CFM |
filter |
Filter life remaining | % |
temperature |
Indoor temperature | Β°F |
relativeHumidity |
Indoor relative humidity | % |
heatSetPoint |
Heating setpoint | Β°F |
coolingSetPoint |
Cooling setpoint | Β°F |
localtime |
Last update timestamp | Unix time |
Example output:
# HELP outdoorAirTemp degrees in F
# TYPE outdoorAirTemp gauge
outdoorAirTemp 63.0
# HELP fanSpeed cubic feet minute
# TYPE fanSpeed gauge
fanSpeed 437
All requests and responses are logged to /data (or your mounted volume path):
POST-systems_SERIALNUMBER_status.xml- Status updates from thermostatGET-config-response.xml- Configuration responses from upstream
XML files are automatically prettified with 2-space indentation. Only the latest file for each type is kept (files are overwritten on each request).
The proxy listens on port 8080 by default. To change this, set the PORT environment variable or modify main.go.
BLOCK_UPDATES: If set to"true", all<update>blocks in the XML response will be removed. This is useful for scenarios where updates should be conditionally blocked.
Authentication is optional (leave user/password blank if not needed). To enable MQTT, you MUST set MQTT_BROKER.
MQTT_BROKER: Broker URL (e.g.,tcp://localhost:1883). Required to enable MQTT.MQTT_TOPIC: Topic to publish to (default:hvac/).MQTT_USER: MQTT username.MQTT_PASSWORD: MQTT password.MQTT_QOS: Quality of Service level (0, 1, or 2). Default is 0.MQTT_RETAINED: Whether to retain the message (true or false). Default is false.
The payload published to the MQTT topic is a JSON object containing the current system status:
{
"localTime": "2024-04-05T14:30:00Z",
"outdoorAirTemp": 63.5,
"filterLevel": 40,
"idu": {
"cfm": 437,
"opstat": "off"
},
"zones": {
"zones": [
{
"id": 1,
"currentTemp": 72.3,
"relativeHumidity": 45,
"heatSetPoint": 68,
"coolSetPoint": 75
}
]
}
}Example usage:
BLOCK_UPDATES="true" go run main.goBy default, the application will include all <update> blocks unless this variable is explicitly set.
- Check that the
Hostheader is being passed correctly - Verify network connectivity to the upstream HVAC system
- Ensure the thermostat can reach the proxy IP and port
- Ensure the thermostat is sending status updates
- Check your mounted volume (e.g.,
/var/log/hvac/) for saved XML files - Verify the XML contains a
<status>root element - Check logs for parsing errors:
docker logs hvacproxy
- Verify port 8080 is not already in use:
netstat -tuln | grep 8080 - Check container logs:
docker logs hvacproxy - Ensure the volume mount path exists and is writable
- Verify the volume mount in your Docker run command
- Check permissions on the host directory
- Look for error messages in the logs
The proxy logs all activity:
2025/11/15 18:52:45 hvac-proxy listening on :8080
2025/11/15 18:52:45 Saving XML files to /data/
[REQ] 192.168.1.100 β POST /status (1234 bytes)
[RESP] POST /status β 200 (elapsed: 45ms)
Files are saved silently without log messages for cleaner output.
MIT License - see LICENSE file for details.
- Built with Go's standard library
- XML formatting using Go's
encoding/xml - Prometheus metrics format compatible
- Docker multi-stage builds for minimal image size
- Developed with assistance from Claude (Anthropic)
For issues, questions, or contributions, please open an issue on the repository.