ecowitt-modbus Python library
ecowitt-modbus is an asynchronous, transport-independent Python library for communicating with Fine Offset / Ecowitt weather sensors over Modbus RTU (including RTU-over-TCP, the common way these sensors are bridged onto a network).
The library was developed as the backend for a corresponding Home Assistant integration. As it is kept independent of Home Assistant, it can also be used by other Python applications and projects.
Supported devices
| Device | Specification | Default address | Comments |
|---|---|---|---|
| Fine Offset / Ecowitt WN90LP | Modbus RTU V1.0.6 | 0x90 (144) |
All-in-one weather sensor array with no moving parts (light, UV, temperature, humidity, wind, rain, pressure) |
| Fine Offset / Ecowitt WN69LP | Modbus RTU V1.0.2 | 0x24 (36) |
7-in-1 sensor array with a mechanical anemometer and tipping-bucket rain gauge |
Both are the wired RS-485 members of the Ecowitt range. The LP models are the ones with a Modbus interface; their wireless counterparts (the WS90 and WS69, which report to an Ecowitt gateway instead) have none and are not addressable here.
If you are looking for the WS90: the WN90LP is its wired version — the same sensor with the solar panel replaced by an RS-485 connection. Ecowitt originally published its Modbus specification as WS90ModbusRTU and has since renamed it to WN90LP ModbusRTU, with the register map unchanged.
The two supported devices share almost no register layout. They are modelled separately and only present a common interface, not common internals.
Usage
Construct the model you have with a modbus_connection.ModbusUnit, confirm the right device answered, then poll it:
from ecowitt_modbus import WN90LP
device = WN90LP(unit)
await device.async_probe()
await device.async_update()
print(device.sensors.temperature) # 26.2
print(device.sensors.wind_speed) # 1.2
An application that lets a user pick the model can look the class up by name instead of branching:
from ecowitt_modbus import SUPPORTED_MODELS
device = SUPPORTED_MODELS["WN69LP"](unit)
async_probe() raises NotThisDeviceError when something answers but is not the model asked for. How much it can confirm depends on the device — see Identifying a device below.
Data provided by the library
Every supported model reports light, UV index, temperature, humidity, wind speed, gust speed, wind direction, rainfall, and absolute pressure. Beyond that they differ:
| WN90LP | WN69LP | |
|---|---|---|
| Rainfall resolution | 0.1mm, plus a 0.01mm counter | 0.254mm (0.01in tipping bucket) |
| UV index encoding | tenths | whole numbers |
| Battery / supply voltage | archived history only | live registers |
| 30-minute rolling history | yes, 11 parameters at one sample per minute | no |
| Recent-rainfall register | no | yes |
| Firmware version | not reported | reported (specification V1.0.2 and later) |
Both models also expose their RS-485 settings, and accept validated writes to the two settable identity registers (baud rate and device address).
The WN90LP's history block is 330 registers wide and changes once a minute, so it is polled separately from the live readings:
await device.async_update_history()
print(device.history.battery_voltage)
Identifying a device
The two models differ in how confidently a caller can tell what it is talking to, which matters for any application that needs to recognise a device again later:
- The WN90LP reports a fixed device code and a 32-bit device ID.
async_probe()is a positive identification, anddevice.serial_numberis stable across changes of host, port, and device address. - The WN69LP reports neither.
device.serial_numberis alwaysNone, andasync_probe()can only reject a responder whose readings fall outside the physical ranges the specification documents (for example over 100% humidity). That rules out most unrelated devices, but it cannot distinguish a WN69LP from another device whose registers happen to decode plausibly at the same addresses.
Scope
The library reads live weather readings, RS-485 communication settings, and — on the WN90LP — the 30-minute rolling history the sensor archives internally.
It does not implement:
- the WN90LP's "on-demand measurement" command registers (
0x9C92-0x9C9A), which duplicate the live readings with tighter timing at the cost of a write-then-wait-then-read sequence, - the WN69LP's automatic reporting mode (register
0x162), which has the device push readings unprompted rather than answer polls, - either device's write-only command registers (rainfall reset, software reset),
- the recovery command that reads or sets the baud rate and address when they are unknown, which is a non-Modbus framed exchange.
The library does not create or own the Modbus transport. Applications provide a modbus_connection.ModbusUnit and may use any backend modbus-connection supports (pymodbus, tmodbus, ...).
Testing and validation
Decode logic (scale, offset, and invalid-value handling) is cross-checked against the manufacturers' own worked examples in the WS90 Modbus RTU V1.0.6 and WN69LP Modbus RTU V1.0.2 specifications. The WN90LP model is additionally verified against a live sensor.
Software tests run against an in-memory mock Modbus backend (via modbus-connection's pytest plugin) — no real device or server is needed to run the test suite. The register images those tests use are exported as ecowitt_modbus.testing so consuming applications can reuse them.
Documentation, development and contribution guidelines
Run script/run_checks.sh before opening a pull request — it mirrors the CI workflow (formatting, lint, compile, tests, build) in one command. script/format_code.sh applies formatting and safe lint fixes only. script/query.py is a standalone CLI for querying a real sensor over TCP or serial; run it with --help for usage.
Contributions target the develop branch; main only ever receives fast-forwards from develop (enforced in CI).
Related projects
This library is one of three repositories for Ecowitt Modbus support in Home Assistant:
ha-ecowitt-modbus— a HACS-installable custom integration, vendoring this library.ha-core-ws90— ahome-assistant/corefork adding this as a built-in integration (branchecowitt-ws90-integration).
Metadata
Release files for ecowitt-modbus 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ecowitt_modbus-0.3.0.tar.gz | 24.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ecowitt_modbus-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 47.2 kB
Release files / ecowitt_modbus-0.3.0.tar.gz
| Download URL | ecowitt_modbus-0.3.0.tar.gz |
|---|---|
| Size | 24.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7852f808469ebd573cb1bf38c1f4ac44408d81b54c401787fae6bf5589f96d0e
|
|
BLAKE2b-256 checksum How to use checksums |
c989c9afb78faf94e6298b15dfe0b7a837f0f59342ecc0b1a5fa2c53de500728
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.
Transparency logRelease files / ecowitt_modbus-0.3.0-py3-none-any.whl
| Download URL | ecowitt_modbus-0.3.0-py3-none-any.whl |
|---|---|
| Size | 22.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
42c9455b59d024700f0a63c7c1602acced37502fb915d652bfb37f7230f7734e
|
|
BLAKE2b-256 checksum How to use checksums |
994460a7a721406b8970aaf9e810e6a425cccdd6001058f96aa413824af0df73
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.
Transparency log