Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
12 changes: 11 additions & 1 deletion .github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,20 @@
"fileFormat": "yaml",
"customType": "jsonata",
"matchStrings": [
"jobs.build.steps[id=\"esphome_build\"].{\"currentValue\":with.version}"
"{\"currentValue\": env.ESPHOME_VERSION}"
],
"packageNameTemplate": "esphome/esphome",
"datasourceTemplate": "github-releases"
},
{
"managerFilePatterns": [".github/workflows/build.yaml"],
"fileFormat": "yaml",
"customType": "jsonata",
"matchStrings": [
"{\"currentValue\": env.PYTHON_VERSION}"
],
"depNameTemplate": "python",
"datasourceTemplate": "python-version"
}
]
}
24 changes: 23 additions & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ name: Compilation check
on:
pull_request:

env:
ESPHOME_VERSION: "2026.9.1"
PYTHON_VERSION: "3.14"

jobs:
build:
runs-on: ubuntu-latest
Expand All @@ -23,5 +27,23 @@ jobs:
uses: esphome/build-action@v8.1.0
with:
yaml-file: tests/${{ matrix.config }}.yaml
version: "2026.9.1"
version: ${{ env.ESPHOME_VERSION }}
substitutions: variant=${{ matrix.variant }}

telegrams:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install esphome
run: pip install "esphome==$ESPHOME_VERSION" pytest
- name: Build the telegram runner
run: |
esphome compile tests/wmbus/host/test.yaml
install -D "$(esphome idedata tests/wmbus/host/test.yaml | jq -r .prog_path)" ~/.local/bin/wmbus-test-runner
- name: Decode the upstream telegrams
run: pytest tests/wmbus/host -q
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
__pycache__
.vscode
.esphome
secrets.yaml
tests/**/.gitignore
.pytest_cache
21 changes: 9 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ESPHome Components

![ESPHome](https://img.shields.io/badge/dynamic/yaml?url=https%3A%2F%2Fraw.githubusercontent.com%2FIoTLabs-pl%2FESPHome-Components%2Frefs%2Fheads%2Fmaster%2F.github%2Fworkflows%2Fbuild.yaml&query=%24.jobs.build.steps...with.version&label=ESPHome&style=for-the-badge&color=f3922d&labelColor=2b4c5a)
![ESPHome](https://img.shields.io/badge/dynamic/yaml?url=https%3A%2F%2Fraw.githubusercontent.com%2FIoTLabs-pl%2FESPHome-Components%2Frefs%2Fheads%2Fmaster%2F.github%2Fworkflows%2Fbuild.yaml&query=%24.env.ESPHOME_VERSION&label=ESPHome&style=for-the-badge&color=f3922d&labelColor=2b4c5a)
![GitHub License](https://img.shields.io/github/license/IoTLabs-pl/esphome-components?style=for-the-badge&color=f3922d&labelColor=2b4c5a)

This repository contains custom [ESPHome](https://esphome.io/) components for devices developed by [IoTLabs](https://iotlabs.pl).
Expand Down Expand Up @@ -74,7 +74,7 @@ interval:

## `wmbus_common`

This component is a port of [wmbusmeters](https://wmbusmeters.org/) library to ESPHome. It provides common functionality for working with wM-Bus devices, including parsing and handling wM-Bus packets.
This component decodes wM-Bus telegrams with the driver definitions of [wmbusmeters](https://wmbusmeters.org/), compiled into the firmware at build time. See [components/wmbus_common/README.md](components/wmbus_common/README.md) for details.

**Example configuration:**

Expand All @@ -99,13 +99,7 @@ wmbus_common:

If you use `wmbus_meter` component, you may not need to configure `wmbus_common` component, as it will be automatically included as a dependency. Dependent components will automatically load required drivers and fields based on their configuration, so specifying them on this level is redundant.

However, if you want to load specific drivers or fields that are not required by any dependent component, you should specify them in `wmbus_common` configuration.

`wmbusmeters` is included as a git subtree. To sync version from upstream repository, run:

```bash
./scripts/pull_wmbusmeters.py [GIT_REF]
```
However, if you want to load specific drivers or fields that are not required by any dependent component, you should specify them in `wmbus_common` configuration. These are also the drivers a meter with `type: auto` chooses from.

## `wmbus_meter`

Expand All @@ -120,7 +114,6 @@ wmbus_meter:
meter_id: 12345678
type: apator162
key: "00000000000000000000000000000000"
mode: T1
on_telegram:
- socket_transmitter.send:
id: transmitter
Expand All @@ -129,7 +122,7 @@ wmbus_meter:
topic: test/topic
```

`mode` parameter is optional and allows to filter received packets by mode. It can be set to `T1` or `C1`. If not set, all packets will be processed.
`type` is the driver name, or `auto` to detect the driver from the first telegram among the drivers listed in `wmbus_common`.
`key` parameter is optional and allows to decrypt packets using AES-128-CBC encryption. It should be provided as hexadecimal or ASCII encoded string. If not set, packets will be processed as unencrypted.

**Attention!**
Expand All @@ -138,6 +131,8 @@ By default, component will not load any fields to reduce memory footprint. If yo
Component provides `on_telegram` trigger that can be used to send data to a remote server or process it in any other way. It can be used to send data to MQTT broker, HTTP server, or any other service. `meter` variable is available in the following lambdas.
Additionally, `wmbus_meter.send_telegram_with_mqtt` action can be used to send JSON-encoded meter data to MQTT broker. It requires `mqtt` component to be configured in ESPHome.

`meter->as_json()` returns the media and the meter's fields: `{"media":"water","total_m3":123.456,"status":"OK"}`.

On the `wmbus_meter` platform, you can use the following sensors to provide data to Home Assistant/MQTT:

- **sensor**: as abstraction for numerical values from the meter, such as consumption, volume, etc.
Expand All @@ -151,7 +146,7 @@ On the `wmbus_meter` platform, you can use the following sensors to provide data
field: total_m3
```

By default, unit of measurement is picked from the field name, but you can override it by specifying `unit_of_measurement` parameter. Especially, if you want to do this, ESPHome's `multiply` filter may be useful to change numerical value to the desired unit.
By default, unit of measurement and accuracy decimals are picked from the field name, but you can override them. To publish the value in another unit, ESPHome's `multiply` filter may be useful.

- **text_sensor**
```yaml
Expand All @@ -163,6 +158,8 @@ On the `wmbus_meter` platform, you can use the following sensors to provide data
field: timestamp
```

Besides the driver's fields, `timestamp` and `media` are available.

When you use `sensor` and `text_sensor` platforms, required fields will be automatically loaded for corresponding driver, so you don't need to specify them in `wmbus_common` component configuration.

For both `sensor` and `text_sensor`, all config from generic [Sensor](https://esphome.io/components/sensor/index.html) and [Text Sensor](https://esphome.io/components/text_sensor/index.html) components is available, so you can use filters, icons, etc.
Expand Down
2 changes: 1 addition & 1 deletion components/wmbus_common/.wmbusmeters_tag
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.20.0
3.0.0-188-gac3d579
22 changes: 22 additions & 0 deletions components/wmbus_common/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# `wmbus_common`

The wM-Bus decoder behind `wmbus_radio` and `wmbus_meter`, built on [wmbusmeters](https://github.com/wmbusmeters/wmbusmeters).

`drivers/src/*.xmq` are wmbusmeters' driver definitions and `tables/*.yaml` its unit and VIF tables, both verbatim from the ref in `.wmbusmeters_tag`. `drivers/loader/` turns the drivers into C++ tables at codegen time; no upstream C++ is compiled.

## Licence

wmbusmeters is GPL-3.0, and so is this repository (see [LICENSE](../../LICENSE)). Please credit [wmbusmeters](https://wmbusmeters.org/) when you use this.

## Updating upstream

```bash
./scripts/wmbus_common/fetch_upstream.py --diff master # upstream's C++ changes since the recorded ref
./scripts/wmbus_common/fetch_upstream.py master # a tag, branch or commit of wmbusmeters
python3 scripts/wmbus_common/check_drivers.py # which drivers the generator handles
esphome compile tests/wmbus/host/test.yaml # upstream's test telegrams still decode:
install -D "$(esphome idedata tests/wmbus/host/test.yaml | jq -r .prog_path)" ~/.local/bin/wmbus-test-runner
pytest tests/wmbus/host
```

The fetch commits nothing: review `git diff`, run the checks, then commit or revert.
55 changes: 31 additions & 24 deletions components/wmbus_common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,25 @@
from esphome.core import CORE
from esphome.yaml_util import ESPHomeDumper

from .driver_loader import CppDriver, Driver, DriverManager
from .drivers.loader import GENERATED_DIR, AutoDriver, Driver, DriverManager, validate_driver_name

CODEOWNERS = ["@kubasaw"]
# to_json renders through ArduinoJson.
AUTO_LOAD = ["json"]

UPSTREAM_REF = (Path(__file__).parent / ".wmbusmeters_tag").read_text().strip()

CONF_ALL = "all"
CONF_DRIVERS = "drivers"
CONF_FIELDS = "fields"

CURRENT_DIR = Path(__file__).parent

wmbus_common_ns = cg.esphome_ns.namespace("wmbus_common")
WMBusCommon = wmbus_common_ns.class_("WMBusCommon", cg.Component)

ESPHomeDumper.add_multi_representer(
Driver, lambda s, v: s.represent_stringify(v.name)
)
for driver_class in (Driver, AutoDriver):
ESPHomeDumper.add_multi_representer(
driver_class, lambda s, v: s.represent_stringify(v.name)
)


def maybe_all(replacements):
Expand All @@ -37,7 +41,7 @@ def driver_field_validator(conf):
driver = conf[CONF_NAME]

conf[CONF_FIELDS] = cv.All(
maybe_all(driver.available_fields),
maybe_all(driver.available_fields()),
[driver.request_field],
)(conf[CONF_FIELDS])

Expand All @@ -46,9 +50,7 @@ def driver_field_validator(conf):

DRIVER_ENTRY_SCHEMA = cv.maybe_simple_value(
{
cv.Required(CONF_NAME): cv.All(
cv.one_of(*DriverManager.available_drivers), DriverManager.request_driver
),
cv.Required(CONF_NAME): validate_driver_name,
cv.Optional(CONF_FIELDS, default=CONF_ALL): cv.valid,
},
driver_field_validator,
Expand All @@ -65,22 +67,27 @@ def driver_field_validator(conf):
)


def validate_auto_has_candidates(config):
if DriverManager.auto_requested and not DriverManager.registered_drivers:
raise cv.Invalid(
"a meter with 'type: auto' picks among the drivers this firmware "
"compiles, and this one compiles none -- name them under "
"'wmbus_common: drivers:'"
)

return config


FINAL_VALIDATE_SCHEMA = validate_auto_has_candidates


async def to_code(config):
cg.add_define(
"WMBUSMETERS_TAG",
CURRENT_DIR.joinpath(".wmbusmeters_tag").read_text(),
)
# Host AES comes from OpenSSL; ESP32 uses mbedTLS.
if CORE.is_host:
cg.add_build_flag("-lcrypto")

target_dir = CORE.relative_src_path("wmbusmeters_drivers")
DriverManager.sync_to_directory(target_dir)
cg.add_define("WMBUSMETERS_VERSION", UPSTREAM_REF)
DriverManager.sync_to_directory(CORE.relative_src_path(GENERATED_DIR))

var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)


def FILTER_SOURCE_FILES() -> list[str]:
return [
d.source_path.name
for d in DriverManager._all_drivers.values()
if isinstance(d, CppDriver)
]
Loading
Loading