Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
e71d50f
flow velocity fix
mplogas Sep 20, 2024
b7ee42d
ac1100 reports in mA :/
mplogas Sep 20, 2024
f37b128
reverted calculation for current and rather switch units mA / A
mplogas Sep 20, 2024
7432ec5
solar radiation is a double
mplogas Sep 20, 2024
28787f4
collecting refactoring ideas
mplogas Nov 10, 2024
3617728
added statemachine.cs
mplogas Mar 27, 2025
ee287f9
gitignore
mplogas Jun 4, 2025
3e592ff
hakone
mplogas May 15, 2025
724af44
shinkansen-kyoto: removed consumer, moved to statemachine
mplogas May 17, 2025
b689aed
shinkansen kyoto-hiroshima: updated architecture diagram
mplogas May 21, 2025
6a417d2
naming fixes in diagrams.drawio and message stubs in project
mplogas May 21, 2025
ce57a61
tokyo hotel lobby mass commit. summarizes stuff from 7 days of refact…
mplogas May 28, 2025
47453b8
fixes and cleanup for new mqtt service class. mqttoptions & controlle…
mplogas Jun 4, 2025
66ecdc7
updated diagrams.drawio
mplogas Jul 15, 2025
92220d6
some more refactoring of the new MqttService.cs and the StateMachine.cs
mplogas Jul 15, 2025
5ff783a
updated diagram
mplogas Jul 17, 2025
c04b62b
docs
mplogas Jul 17, 2025
0d1f655
updated gitignore
mplogas Jul 17, 2025
1676105
communication statemachine <-> mqttservice
mplogas Jul 17, 2025
66e2620
mqtt topic doc
mplogas Jul 22, 2025
9dca38c
new mqttservice!
mplogas Jul 22, 2025
54230dd
improved readability/maintainability via partial classes
mplogas Jul 22, 2025
ff864a5
removed guid that was used to test-log instances
mplogas Jul 22, 2025
a68172a
renaming and cleanup of unused classes
mplogas Jul 29, 2025
f70cc22
moved to partial classes and added httppublishingservice (shitty name…
mplogas Jul 29, 2025
8b51b8e
forgot to move the old mqtt stuff to the old folder
mplogas Jul 29, 2025
81889d0
consolidated the model.
mplogas Jul 29, 2025
a286694
added wfc02 compatibility
mplogas Jul 31, 2025
d250794
updated readme
mplogas Jul 31, 2025
85f6be1
fixed ns for mapping
mplogas Aug 1, 2025
39b3051
added storage logging for debugging purposes
mplogas Aug 26, 2025
cabccfe
a delta is emitted to the mqtt service only. in theory.
mplogas Aug 26, 2025
f1d20bc
end-to-end and then a random newtonsoft jsonparsing error appeared
mplogas Aug 27, 2025
735d87a
data is now emitted to mqtt again. after inital full emit, only chang…
mplogas Sep 4, 2025
25bf9cb
HA discovery added, ha restart triggers re-emit of sensor data. ha di…
mplogas Sep 5, 2025
1311388
Phase 1: dead code cleanup, docs refresh, unit tests
mplogas Mar 2, 2026
6f991c7
Phase 2: fix wiring gaps, add stale discovery removal
mplogas Mar 2, 2026
fb69e97
Phase 3: implement subdevice commands, fix WFC02 battery mapping
mplogas Mar 2, 2026
e193e43
config changes
mplogas Mar 2, 2026
87999dd
Update Dockerfile to .NET 10.0 base images
mplogas Mar 2, 2026
a0df2ee
Address PR review findings
mplogas Mar 3, 2026
d20bee4
removed empty, legacy folder
mplogas Mar 3, 2026
adf4d58
Add semver tagging to Docker build workflow
mplogas Mar 3, 2026
2e8e013
v2.0 release.
mplogas Mar 3, 2026
f846439
Update README to reflect .NET 10
mplogas Mar 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name: Docker Image Build and Push to DockerHub
on:
push:
branches: [ "main" ]
tags: [ "v*" ]
pull_request:
branches: [ "main" ]
workflow_dispatch:
Expand All @@ -22,19 +23,18 @@ jobs:
id: meta
uses: docker/metadata-action@v5
with:
# list of Docker images to use as base name for tags
images: |
${{ vars.DOCKERHUB_USERNAME }}/ecowitt-controller
# Docker tags to generate
tags: |
type=schedule,enable=true,pattern={{date 'YYYYMMDD-hhmmss' tz='Europe/Berlin'}}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,enable={{is_default_branch}},value=latest
type=sha,enable=true,format=short
type=sha,enable=true,format=short
- name: Build and push
uses: docker/build-push-action@v5
with:
context: "{{defaultContext}}:src"
#dockerfile: ./Dockerfile
push: true
push: ${{ github.event_name != 'pull_request' }}
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta.outputs.tags }}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
*.bkp

# SQL Server files
*.mdf
Expand Down Expand Up @@ -396,3 +397,7 @@ FodyWeavers.xsd

# JetBrains Rider
*.sln.iml
.idea/
/.$diagrams.drawio.bkp
/.continue/mcpServers
/.vscode
81 changes: 81 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Ecowitt Controller is a .NET 8 / ASP.NET Core application that bridges Ecowitt weather stations and IoT subdevices (AC1100 smart plugs, WFC01/WFC02 water valves) to MQTT, with Home Assistant auto-discovery support. It receives weather data via HTTP POST from Ecowitt gateways and polls subdevices via their HTTP API, then publishes everything over MQTT.

## Build & Run Commands

All commands run from `src/`:

```bash
# Build
dotnet build Ecowitt.Controller.sln

# Run
dotnet run --project Ecowitt.Controller/Ecowitt.Controller.csproj

# Run release
dotnet run -c Release --project Ecowitt.Controller/Ecowitt.Controller.csproj

# Run tests (NUnit)
dotnet test EcoWitt.Controller.Tests/EcoWitt.Controller.Tests.csproj

# Run a single test
dotnet test EcoWitt.Controller.Tests/EcoWitt.Controller.Tests.csproj --filter "FullyQualifiedName~TestMethodName"

# Docker build (from src/)
docker build -t ecowitt-controller .
```

Note: the test project casing is `EcoWitt.Controller.Tests` (capital W) while the main project is `Ecowitt.Controller`.

## Architecture

The system uses three BackgroundServices communicating through an **in-memory message bus** (SlimMessageBus):

### Data Flow

1. **DataController** (`Controller/DataController.cs`) — ASP.NET endpoint at `POST /data/report` receives form-encoded weather data from Ecowitt gateways, publishes `GatewayApiData` onto the bus.

2. **Dispatcher** (`Service/Orchestrator/Dispatcher*.cs`) — Central orchestrator (partial class split across files). Consumes messages from both HTTP and MQTT services, manages the `DeviceStore`, performs change detection via `DeNoiserHelper`, and emits data/discovery messages. The `ConsumerHttp` partial handles gateway and subdevice API data; the `ConsumerMqtt` partial handles MQTT lifecycle and Home Assistant status events.

3. **MqttService** (`Service/Mqtt/MqttService*.cs`) — Partial class split across files for consumer, publisher, discovery, and events. Connects to MQTT broker, publishes sensor data and Home Assistant discovery payloads, subscribes to HA status topic for re-emission on HA restart.

4. **HttpPublishingService** (`Service/Http/HttpPublishingService*.cs`) — Polls Ecowitt gateway HTTP APIs for subdevice data on a configurable interval, publishes `SubdeviceApiAggregate` onto the bus.

### Message Bus Topics (SlimMessageBus)

Messages are defined in `Model/Message/` (Config, Data, Event subdirs). The bus wiring is in `Program.cs`. Key flows:
- Dispatcher → MqttService: `MqttConfig`, `DeviceData`, `DeviceDataFull`, `SubdeviceData`, `SubdeviceDataFull`, `HomeAssistantDiscoveryEvent`
- MqttService → Dispatcher: `MqttServiceEvent`, `MqttConnectionEvent`, `HomeAssistantStatusEvent`
- Dispatcher → HttpPublishingService: `HttpConfig`
- HttpPublishingService → Dispatcher: `SubdeviceApiAggregate`, `HttpServiceEvent`

### Key Model Layer

- **Device/Subdevice/Sensor** (`Model/Device.cs`, `Model/Subdevice.cs`, `Model/Sensor.cs`) — Domain model. `ISensor` has typed value accessors and change tracking via hash comparison.
- **SensorBuilder** (`Model/Mapping/SensorBuilder*.cs`) — Partial class that maps raw Ecowitt property names (e.g., `tempinf`, `baromrelin`) to typed `Sensor` objects with unit conversion (imperial→metric). This is the main mapping to extend when adding new sensor types.
- **ApiDataExtension** (`Model/Mapping/ApiDataExtension.cs`) — Extension methods that convert API DTOs to domain objects.
- **DiscoveryBuilder** (`Model/Discovery/`) — Builds Home Assistant MQTT discovery payloads.
- **DeNoiserHelper** (`Service/Orchestrator/DeNoiser.cs`) — Filters insignificant sensor value changes using configurable tolerances per sensor type.
- **DeviceStore** (`Service/Orchestrator/DeviceStore.cs`) — Thread-safe in-memory store (`ConcurrentDictionary`) for gateway/subdevice state.

### Configuration

Three option classes bound from `appsettings.json` sections in `Model/Configuration/`:
- `ecowitt` → `EcowittOptions` (gateways, polling interval, autodiscovery)
- `mqtt` → `MqttOptions` (broker connection)
- `controller` → `ControllerOptions` (units, precision, publishing interval, HA discovery toggle)

Config is loaded from `/config/appsettings.json` (Docker) or the content root (bare-metal).

## Conventions

- Partial classes are used extensively for service separation (consumers, publishers, events in separate files).
- Logging uses Serilog with structured logging templates (not string interpolation).
- HTTP retry policy uses Polly with decorrelated jitter backoff.
- The Ecowitt gateway HTTP API endpoints used: `get_iot_device_list`, `parse_quick_cmd_iot`.
- SensorType/SensorState/SensorCategory enums align with Home Assistant device classes.
186 changes: 99 additions & 87 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,52 +1,49 @@
## Supported Devices

### Gateways
- **GW2000**: The GW2000 is a displayless console/gateway available since April 2022. It offers an extended range of functions, including a browser interface (WebUI) for display and configuration. It supports both Ethernet and WLAN connections, though it's recommended not to use both interfaces simultaneously to avoid blocking the web interface. The GW2000 firmware supports various sensors, including the WFC01 IoT sensor.
- **GW1200**: This gateway supports bidirectional communication necessary for controlling intelligent components like switches and water valves. It supports a WebUI and also supports the WS View Plus app for configuration .
- **GW1x00**: The GW1100 is similar to the GW1200 but does not support a WebUI and no subdevices. It is configured and displayed via the WS View Plus app. The GW1100 includes a temperature/humidity sensor for indoor measurements and supports local data display through applications like PWT (Personal Weather Tablet).
- **Other Compatible Gateways**: WN1980, WS3800, and WS39x0 also support IoT sensors and bidirectional communication .

### Subdevices
- **AC1100 Smart Plug**: The AC1100 is a switchable socket that can be controlled manually, time-controlled, or based on measured values from the weather station. It requires an IoT-enabled console (e.g., GW2000, GW1200) and the Ecowitt app for automatic operation. It supports different regions with corresponding plugs and maximum wattages .
- **WFC01 Intelligent Water Timer (WittFlow)**: The WFC01 is a timer-controlled or sensor-measurement-dependent water valve. It features a built-in liquid flow sensor and a temperature sensor for the liquid. The valve can be controlled via the Ecowitt app, and it supports various operating modes, including manual, plan, and smart modes . The device is waterproof and dustproof to IP66 standards and built from corrosion-resistant materials .

### Compatibility
- **Weather Stations**: Fine Offset and its clones (Ecowitt, Froggit, Ambient Weather...) are generally supported if your gateway/weather station supports custom weather station uploads in Ecowitt format.
- **Web API Devices**: If your device offers a web API (e.g., GW2000, GW1200), this tool offers bidirectional communication, allowing you to control actors like the AC1100 smart plug or the WFC01 water valve .
## Why
Ecowitt offers great and reliable weather stations that can be run entirely locally. As a Home Assistant user, I used Ecowitt2Mqtt to collect my weather data. Unfortunately, it doesn't support subdevices (yet), and my Python skills aren't sufficient to extend it. However, I have experience with .NET, IoT, and I enjoy challenges. This project was created to fill that gap and provide a solution for controlling Ecowitt subdevices from Home Assistant. Additionally, the decoupled architecture can serve as a blueprint for future similar projects.
## Features
- **Multi-Gateway, Multi-Subdevices Support:** The system supports multiple gateways and subdevices, allowing for extensive scalability and flexibility .
- **Automatic Discovery:** Newly added sensors and subdevices are automatically detected. This feature can be turned off to allow for manual gateway definitions .
- **Bidirectional Communication:** Enables data retrieval such as battery state and sensor status from subdevices, and also allows for control commands to be sent .
- **MQTT-Based Integration:** Weather data is exposed via MQTT, facilitating integration with other IoT systems .
- **Home Assistant Compatibility:** Supports device discovery messages to the Home Assistant topic, enabling seamless integration with Home Assistant through MQTT .
- **Highly Configurable:** Offers extensive configuration options including discovery settings, polling schedules, HTTP timeouts, and alternative discovery topics .
- **Metric and Freedom Units Support:** Transforms data points to metric units by default, with the option to turn this off if imperial units are preferred .
- **Dynamic Property Discovery:** The system flexibly discovers and publishes new sensor properties by default, ensuring up-to-date data availability .
## Roadmap
- **InfluxDB Storage:** Store states directly in InfluxDB without going through Home Assistant first. This will enable more efficient data storage and retrieval, allowing for advanced data analysis and visualization using tools like Grafana.
- **Calculated Properties:** Implement calculated properties such as dewpoint, wind direction (NESW), windchill, etc. These calculated metrics will provide more comprehensive weather data insights, enhancing the utility of the collected data for various applications.
## Getting started
To begin, you will need a running MQTT broker along with its IP address or hostname and port number. While TLS and credentials are optional, they are highly recommended for enhanced security.
## Ecowitt Controller (this tool)
If you intend to run the Ecowitt Controller in a Docker container, you will need to create a new `.json` file named `appsettings.json`. For a minimal setup, you only need to specify the IP address of your MQTT broker. All other settings will be automatically populated with default values.
### Minimal Configuration
``` json
# Ecowitt Controller

A .NET 10 bridge that connects Ecowitt weather stations and IoT subdevices to MQTT, with native Home Assistant auto-discovery.
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README states this is a ".NET 8 bridge", but the project file and Dockerfile in this PR target .NET 10 (net10.0, mcr.microsoft.com/dotnet/*:10.0). This inconsistency will confuse users and can lead to build/runtime issues if they follow the docs.

Update the README to match the actual target/runtime (or revert the target framework/runtime versions to .NET 8 if that was the intent).

Suggested change
A .NET 10 bridge that connects Ecowitt weather stations and IoT subdevices to MQTT, with native Home Assistant auto-discovery.
A .NET 10 (net10.0) bridge that connects Ecowitt weather stations and IoT subdevices to MQTT, with native Home Assistant auto-discovery.

Copilot uses AI. Check for mistakes.

## Features

- Multi-gateway, multi-subdevice support
- Automatic discovery of new sensors and subdevices
- Bidirectional communication with subdevices (AC1100, WFC01, WFC02)
- Home Assistant MQTT discovery (devices, sensors, switches)
- Metric/imperial unit conversion
- Change-detection filtering to reduce MQTT noise

## Supported Devices

**Gateways:** GW3000, GW2000, GW1200, GW1100, WN1980, WS3800, WS39x0

**Subdevices** (require IoT-capable gateway like GW2000/GW1200):
- **AC1100** — Smart plug with power monitoring
- **WFC01** — Water timer with flow sensor and temperature
- **WFC02** — Water valve with optional flow sensor

**Weather Stations:** Any Fine Offset compatible station (Ecowitt, Froggit, Ambient Weather, ...) that supports custom Ecowitt protocol uploads.

## Getting Started

**Prerequisites:** A running MQTT broker (e.g. Mosquitto).

### Configuration

Create an `appsettings.json`. Minimal setup — just point it at your MQTT broker:

```json
{
"mqtt": {
"host": ""
"host": "192.168.1.50"
}
}
```
```

For a more detailed configuration, including MQTT credentials and polling intervals, see the full configuration example below. This example also enables debug logging.
### Full Config
Full configuration with all options and their defaults:

``` json
```json
{
"Serilog": {
"MinimumLevel": "Debug"
"MinimumLevel": "Warning"
},
"mqtt": {
"host": "",
Expand All @@ -56,72 +53,87 @@ For a more detailed configuration, including MQTT credentials and polling interv
"basetopic": "ecowitt",
"clientId": "ecowitt-controller",
"reconnect": true,
"reconnectAttemps": 5
"reconnectAttempts": 2
},
"ecowitt": {
"pollingInterval": 30, //seconds
"pollingInterval": 30,
"autodiscovery": true,
"calculateValues": true,
"retries": 2,
"gateways": [
{
"name": "weatherstation_01",
"ip": "192.168.1.101"
},
{
"name": "weatherstation_02",
"ip": "192.168.1.102",
"username": "",
"password": ""
}
]
},
"controller": {
"precision": 2, //math.round to x digits
"unit": "metric", // or "imperial"
"publishingInterval": 10, //seconds
"precision": 2,
"unit": "metric",
"homeassistantdiscovery": true
}
}
```

| Section | Key | Default | Description |
|---------|-----|---------|-------------|
| `mqtt` | `host` | — | MQTT broker address (required) |
| `mqtt` | `port` | `1883` | MQTT broker port |
| `mqtt` | `basetopic` | `ecowitt` | Root MQTT topic prefix |
| `mqtt` | `reconnect` | `true` | Auto-reconnect on disconnect |
| `mqtt` | `reconnectAttempts` | `2` | Reconnect retry count |
| `ecowitt` | `pollingInterval` | `30` | Subdevice polling interval (seconds) |
| `ecowitt` | `autodiscovery` | `false` | Auto-discover gateways from incoming data |
| `ecowitt` | `calculateValues` | `true` | Generate calculated sensor values |
| `ecowitt` | `gateways` | `[]` | Manual gateway definitions (name, ip, credentials) |
| `controller` | `precision` | `2` | Decimal places for floating-point values |
| `controller` | `unit` | `metric` | `metric` or `imperial` |
| `controller` | `homeassistantdiscovery` | `true` | Publish HA MQTT discovery messages |

### Run with Docker

```bash
docker run -d --name ecowitt-controller \
-v /path/to/appsettings.json:/config/appsettings.json:ro \
-p 8080:8080 \
mplogas/ecowitt-controller:latest
```
### Run as Docker
To run Ecowitt Controller in a Docker container, mount your config file and forward port 8080:

`docker run -d --name ecowitt-controller -v path/to/appsettings.json:/config/appsettings.json:ro -p 8080:8080 mplogas/ecowitt-controller:latest`
### Run on Bare-Metal
To run Ecowitt Controller on bare-metal, clone the repository and modify the `appsettings.json`. Then, change into the `src` directory and run:
### Run from Source

```bash
cd src
dotnet run --project Ecowitt.Controller/Ecowitt.Controller.csproj -c Release
```

`dotnet run -C Release Ecowitt.Controller.csproj`
### Configure Your Weather Station
To configure your Ecowitt weather station to send data to the Ecowitt Controller, follow these steps:

1. **Access the WebUI or WS View Plus App:**
- For the GW2000, you can use the built-in WebUI available at the device's IP address .
- Alternatively, use the WS View Plus app to configure the station.

2. **Set Up Custom Server:**
- Navigate to the weather services configuration page.
- Enable the 'customized' data posting option.
- Choose the Ecowitt protocol.
- Enter the IP address or hostname of your Ecowitt-Controller instance.
- Specify the path as `/data/report`.
- Set the port number (default is 8080).
- Define the posting interval (e.g., 30 seconds).

3. **Save and Apply Settings:**
- Save the configuration and ensure the weather station starts sending data to the specified Ecowitt-Controller instance.

1. Open your gateway's WebUI or the WS View Plus app
2. Go to weather services and enable the **Customized** upload
3. Set protocol to **Ecowitt**, enter the controller's IP, path `/data/report`, port `8080`
4. Set the posting interval (e.g. 30 seconds)

### Home Assistant
Ensure your Home Assistant has permission set up to listen for MQTT messages from the Ecowitt Controller topic. The Home Assistant Discovery should automatically pick up new devices and sensors if `homeassistantdiscovery` is enabled in your `appsettings.json`.
## Implementation

This project is developed using C# and .NET 8, utilizing ASP.NET Core Web API to create an endpoint for the Ecowitt custom weather station. The main components of the implementation include:
- **ASP.NET Core Web API**: Acts as the endpoint to receive data from the weather station.
- **[MQTTnet](https://github.com/dotnet/MQTTnet)**: Facilitates MQTT background services for communication with the weather station.
- **Polling Background Service**: Periodically retrieves updates from subdevices.
- **[SlimMessageBus](https://github.com/zarusz/SlimMessageBus)**: An in-memory message bus that integrates all components for efficient communication.`

With `homeassistantdiscovery` enabled (default), devices and sensors appear automatically in HA via MQTT discovery. Make sure your HA instance is connected to the same MQTT broker.

## Documentation

- [HTTP API](docs/api.md) — Inbound weather data endpoint and outbound gateway polling
- [MQTT Topics](docs/mqtt.md) — Topic structure, payloads, and Home Assistant discovery

## Tech Stack

- **ASP.NET Core Web API** — HTTP endpoint for Ecowitt weather data
- **[MQTTnet](https://github.com/dotnet/MQTTnet)** — MQTT client
- **[SlimMessageBus](https://github.com/zarusz/SlimMessageBus)** — In-memory message bus connecting the services
- **[Serilog](https://serilog.net/)** — Structured logging
- **[Polly](https://github.com/App-vNext/Polly)** — HTTP retry policies

## Contributing
We welcome contributions from the community! Please read our [CONTRIBUTING.md](CONTRIBUTING.md) file for guidelines on how to get involved.

Contributions welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

## License
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
## Contact
For any questions or feedback, please open an issue on GitHub.

MIT — see [LICENSE](LICENSE).
Loading