Skip to content

Commit 8455cd2

Browse files
authored
Merge pull request #23 from IoTLabs-pl/wmbusmeters-1.20.0
wmbusmeters 1.20.0
2 parents 8fa8697 + 1a5bfb3 commit 8455cd2

File tree

133 files changed

+5832
-1002
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

133 files changed

+5832
-1002
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,22 @@ wmbus_common:
3636
drivers:
3737
- apator162
3838
- amiplus
39+
# or specify drivers and their fields to be loaded
40+
drivers:
41+
- name: apator162
42+
fields:
43+
- total_m3
44+
- amiplus
45+
- name: sensostar
46+
fields:
47+
- total_kwh
48+
- waterstarm
3949
```
4050

51+
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.
52+
53+
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.
54+
4155
`wmbusmeters` is included as a git subtree. To sync version from upstream repository, run:
4256

4357
```bash
@@ -69,6 +83,9 @@ wmbus_meter:
6983
`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.
7084
`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.
7185

86+
**Attention!**
87+
By default, component will not load any fields to reduce memory footprint. If you need specific fields (or all fields) you can specify them in `wmbus_common` component configuration. Only specified fields will be available in `meter` variable in `on_telegram` trigger, will be serialized with `as_json` method etc.
88+
7289
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.
7390
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.
7491

@@ -97,6 +114,8 @@ On the `wmbus_meter` platform, you can use the following sensors to provide data
97114
field: timestamp
98115
```
99116

117+
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.
118+
100119
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.
101120

102121
## `wmbus_radio`
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.19.0
1+
1.20.0
Lines changed: 60 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,89 @@
1+
from pathlib import Path
2+
13
import esphome.config_validation as cv
2-
from esphome.const import SOURCE_FILE_EXTENSIONS, CONF_ID
3-
from esphome.loader import get_component, ComponentManifest
44
from esphome import codegen as cg
5-
from pathlib import Path
5+
from esphome.const import CONF_ID, CONF_NAME, SOURCE_FILE_EXTENSIONS
6+
from esphome.core import CORE
7+
from esphome.yaml_util import ESPHomeDumper
8+
9+
from .driver_loader import CppDriver, Driver, DriverManager
610

711
CODEOWNERS = ["@kubasaw"]
12+
CONF_ALL = "all"
813
CONF_DRIVERS = "drivers"
14+
CONF_FIELDS = "fields"
15+
16+
CURRENT_DIR = Path(__file__).parent
17+
SOURCE_FILE_EXTENSIONS.add(".cc")
18+
919

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

13-
SOURCE_FILE_EXTENSIONS.add(".cc")
14-
15-
_ALWAYS_EXCLUDED = {"auto", "unknown"}
1623

17-
AVAILABLE_DRIVERS = sorted(
18-
name
19-
for f in Path(__file__).parent.glob("driver_*.cc")
20-
if (name := f.stem.removeprefix("driver_")) not in _ALWAYS_EXCLUDED
24+
ESPHomeDumper.add_multi_representer(
25+
Driver, lambda s, v: s.represent_stringify(v.name)
2126
)
2227

23-
_registered_drivers = set()
2428

29+
def maybe_all(replacements):
30+
def validator(v):
31+
if v == CONF_ALL:
32+
return replacements
33+
else:
34+
return v
35+
36+
return validator
2537

26-
validate_driver = cv.All(
27-
cv.one_of(*AVAILABLE_DRIVERS, lower=True, space="_"),
28-
lambda driver: _registered_drivers.add(driver) or driver,
29-
)
3038

39+
def driver_field_validator(conf):
40+
driver = conf[CONF_NAME]
41+
42+
conf[CONF_FIELDS] = cv.All(
43+
maybe_all(driver.available_fields),
44+
[driver.request_field],
45+
)(conf[CONF_FIELDS])
46+
47+
return conf
48+
49+
50+
DRIVER_ENTRY_SCHEMA = cv.maybe_simple_value(
51+
{
52+
cv.Required(CONF_NAME): cv.All(
53+
cv.one_of(*DriverManager.available_drivers), DriverManager.request_driver
54+
),
55+
cv.Optional(CONF_FIELDS, default=CONF_ALL): cv.valid,
56+
},
57+
driver_field_validator,
58+
key=CONF_NAME,
59+
)
3160

3261
CONFIG_SCHEMA = cv.Schema(
3362
{
3463
cv.GenerateID(): cv.declare_id(WMBusCommon),
35-
cv.Optional(CONF_DRIVERS): cv.All(
36-
lambda x: AVAILABLE_DRIVERS if x == "all" else x,
37-
[validate_driver],
64+
cv.Optional(CONF_DRIVERS, default=[]): cv.All(
65+
maybe_all(DriverManager.available_drivers), [DRIVER_ENTRY_SCHEMA]
3866
),
3967
}
4068
)
4169

4270

43-
class WMBusComponentManifest(ComponentManifest):
44-
@property
45-
def resources(self):
46-
exclude_drivers = set(AVAILABLE_DRIVERS) - _registered_drivers
47-
if _registered_drivers:
48-
exclude_drivers |= _ALWAYS_EXCLUDED
49-
else:
50-
exclude_drivers |= _ALWAYS_EXCLUDED - {"unknown"}
51-
52-
exclude_files = {f"driver_{name}.cc" for name in exclude_drivers}
53-
resources = [fr for fr in super().resources if fr.resource not in exclude_files]
54-
return resources
55-
56-
5771
async def to_code(config):
58-
cg.add_define("WMBUSMETERS_TAG", Path(__file__).with_name('.wmbusmeters_tag').read_text())
72+
cg.add_define(
73+
"WMBUSMETERS_TAG",
74+
CURRENT_DIR.joinpath(".wmbusmeters_tag").read_text(),
75+
)
5976

60-
get_component("wmbus_common").__class__ = WMBusComponentManifest
77+
target_dir = CORE.relative_src_path("wmbusmeters_drivers")
78+
DriverManager.sync_to_directory(target_dir)
6179

6280
var = cg.new_Pvariable(config[CONF_ID])
6381
await cg.register_component(var, config)
82+
83+
84+
def FILTER_SOURCE_FILES() -> list[str]:
85+
return [
86+
d.source_path.name
87+
for d in DriverManager._all_drivers.values()
88+
if isinstance(d, CppDriver)
89+
]

components/wmbus_common/driver_abbb23.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ namespace
2424
Driver(MeterInfo &mi, DriverInfo &di);
2525
};
2626

27-
static bool ok = registerDriver([](DriverInfo&di)
27+
static bool ok = staticRegisterDriver([](DriverInfo&di)
2828
{
2929
di.setName("abbb23");
3030
di.setDefaultFields("name,id,total_energy_consumption_kwh,timestamp");
3131
di.setMeterType(MeterType::ElectricityMeter);
3232
di.addLinkMode(LinkMode::T1);
33-
di.addDetection(MANUFACTURER_ABB, 0x02, 0x20);
33+
di.addMVT(MANUFACTURER_ABB, 0x02, 0x20);
3434
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
3535
});
3636

components/wmbus_common/driver_aerius.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ namespace
2424
Driver(MeterInfo &mi, DriverInfo &di);
2525
};
2626

27-
static bool ok = registerDriver([](DriverInfo&di)
27+
static bool ok = staticRegisterDriver([](DriverInfo&di)
2828
{
2929
di.setName("aerius");
3030
di.setDefaultFields("name,id,total_m3,timestamp");
3131
di.setMeterType(MeterType::GasMeter);
3232
di.addLinkMode(LinkMode::T1);
33-
di.addDetection(MANUFACTURER_DME, 0x03, 0x30);
33+
di.addMVT(MANUFACTURER_DME, 0x03, 0x30);
3434
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
3535
});
3636

components/wmbus_common/driver_amiplus.cc

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright (C) 2019-2022 Fredrik Öhrström (gpl-3.0-or-later)
2+
Copyright (C) 2019-2025 Fredrik Öhrström (gpl-3.0-or-later)
33
44
This program is free software: you can redistribute it and/or modify
55
it under the terms of the GNU General Public License as published by
@@ -24,21 +24,22 @@ namespace
2424
Driver(MeterInfo &mi, DriverInfo &di);
2525
};
2626

27-
static bool ok = registerDriver([](DriverInfo&di)
27+
static bool ok = staticRegisterDriver([](DriverInfo&di)
2828
{
2929
di.setName("amiplus");
3030
di.setDefaultFields("name,id,total_energy_consumption_kwh,current_power_consumption_kw,total_energy_production_kwh,current_power_production_kw,voltage_at_phase_1_v,voltage_at_phase_2_v,voltage_at_phase_3_v,total_energy_consumption_tariff_1_kwh,total_energy_consumption_tariff_2_kwh,total_energy_consumption_tariff_3_kwh,total_energy_production_tariff_1_kwh,total_energy_production_tariff_2_kwh,total_energy_production_tariff_3_kwh,timestamp");
3131
di.setMeterType(MeterType::ElectricityMeter);
3232
di.addLinkMode(LinkMode::T1);
33-
di.addDetection(MANUFACTURER_APA, 0x02, 0x02);
34-
di.addDetection(MANUFACTURER_DEV, 0x37, 0x02);
35-
di.addDetection(MANUFACTURER_DEV, 0x02, 0x00);
36-
di.addDetection(MANUFACTURER_DEV, 0x02, 0x01);
33+
di.addMVT(MANUFACTURER_APA, 0x02, 0x02);
34+
di.addMVT(MANUFACTURER_DEV, 0x37, 0x02);
35+
di.addMVT(MANUFACTURER_DEV, 0x02, 0x00);
36+
di.addMVT(MANUFACTURER_DEV, 0x02, 0x01);
37+
di.addMVT(MANUFACTURER_NES, 0x02, 0x03);
3738
// Apator Otus 1/3 seems to use both, depending on a frame.
3839
// Frames with APA are successfully decoded by this driver
3940
// Frames with APT are not - and their content is unknown - perhaps it broadcasts two data formats?
40-
di.addDetection(MANUFACTURER_APA, 0x02, 0x01);
41-
//di.addDetection(MANUFACTURER_APT, 0x02, 0x01);
41+
di.addMVT(MANUFACTURER_APA, 0x02, 0x01);
42+
//di.addMVT(MANUFACTURER_APT, 0x02, 0x01);
4243
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
4344
});
4445

@@ -91,29 +92,35 @@ namespace
9192
"Voltage at phase L1.",
9293
DEFAULT_PRINT_PROPERTIES,
9394
Quantity::Voltage,
94-
VifScaling::None, DifSignedness::Signed,
95+
VifScaling::Auto, DifSignedness::Signed,
9596
FieldMatcher::build()
96-
.set(DifVifKey("0AFDC9FC01"))
97+
.set(MeasurementType::Instantaneous)
98+
.set(VIFRange::Voltage)
99+
.add(VIFCombinable::AtPhase1)
97100
);
98101

99102
addNumericFieldWithExtractor(
100103
"voltage_at_phase_2",
101104
"Voltage at phase L2.",
102105
DEFAULT_PRINT_PROPERTIES,
103106
Quantity::Voltage,
104-
VifScaling::None, DifSignedness::Signed,
107+
VifScaling::Auto, DifSignedness::Signed,
105108
FieldMatcher::build()
106-
.set(DifVifKey("0AFDC9FC02"))
109+
.set(MeasurementType::Instantaneous)
110+
.set(VIFRange::Voltage)
111+
.add(VIFCombinable::AtPhase2)
107112
);
108113

109114
addNumericFieldWithExtractor(
110115
"voltage_at_phase_3",
111116
"Voltage at phase L3.",
112117
DEFAULT_PRINT_PROPERTIES,
113118
Quantity::Voltage,
114-
VifScaling::None, DifSignedness::Signed,
119+
VifScaling::Auto, DifSignedness::Signed,
115120
FieldMatcher::build()
116-
.set(DifVifKey("0AFDC9FC03"))
121+
.set(MeasurementType::Instantaneous)
122+
.set(VIFRange::Voltage)
123+
.add(VIFCombinable::AtPhase3)
117124
);
118125

119126
addStringFieldWithExtractor(
@@ -232,3 +239,13 @@ namespace
232239
// telegram=|3e44b6108707320001027a380030052f2f0C7830253390066D6872141239400E031891690000000E833C9265010000000B2B2602000BAB3C0000002F2F2F2F|
233240
// {"_":"telegram","current_power_consumption_kw": 0.226,"current_power_production_kw": 0,"device_date_time": "2024-09-18 20:50:40","id": "00320787","media": "electricity","meter": "amiplus","name": "MyElectricity4","timestamp": "1111-11-11T11:11:11Z","total_energy_consumption_kwh": 699.118,"total_energy_production_kwh": 16.592}
234241
// |MyElectricity4;00320787;699.118;0.226;16.592;0;null;null;null;null;null;null;null;null;null;1111-11-11 11:11.11
242+
243+
// Test: MyElectricity5 amiplus 56914504 NOKEY
244+
// telegram=|9e4401060445915601027a3d0390052f2f066dc076091935800c78044591560e032088300000008e10032088300000008e20030000000000008e30030000000000008e8010030000000000000e833c2702000000008e10833c2702000000008e20833c0000000000008e30833c0000000000008e8010833c0000000000000afdc8fc0136240afdc8fc0262240afdc8fc0389222f2f2f2f2f2f2f2f2f2f2f2f|
245+
// {"_":"telegram","media":"electricity","meter":"amiplus","name":"MyElectricity5","id":"56914504","total_energy_consumption_kwh":308.82,"total_energy_consumption_tariff_1_kwh":308.82,"total_energy_consumption_tariff_2_kwh":0,"total_energy_consumption_tariff_3_kwh":0,"total_energy_production_kwh":0.227,"total_energy_production_tariff_1_kwh":0.227,"total_energy_production_tariff_2_kwh":0,"total_energy_production_tariff_3_kwh":0,"voltage_at_phase_1_v":243.6,"voltage_at_phase_2_v":246.2,"voltage_at_phase_3_v":228.9,"device_date_time":"2024-05-25 09:54:00","timestamp":"1111-11-11T11:11:11Z"}
246+
// |MyElectricity5;56914504;308.82;null;0.227;null;243.6;246.2;228.9;308.82;0;0;0.227;0;0;1111-11-11 11:11.11
247+
248+
// Test: MyElectricity6 amiplus 00086426 NOKEY
249+
// telegram=|8E44B3382664080003027A090080052F2F_066D37090E2232050C78266408000AFDC9FC0142020AFDC9FC0240020AFDC9FC0338028E30833C0000000000008E20833C0000000000008E10833C0000000000000BABC8FC100000008E10030750030000008E20035379060000008E30030000000000000B2B1307000BAB3C0000002F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F|
250+
// {"_": "telegram","current_power_consumption_kw": 0.713,"current_power_production_kw": 0,"device_date_time": "2025-02-02 14:09:55","id": "00086426","media": "electricity","meter": "amiplus","name": "MyElectricity6","timestamp": "1111-11-11T11:11:11Z","total_energy_consumption_tariff_1_kwh": 35.007,"total_energy_consumption_tariff_2_kwh": 67.953,"total_energy_consumption_tariff_3_kwh": 0,"total_energy_production_tariff_1_kwh": 0,"total_energy_production_tariff_2_kwh": 0,"total_energy_production_tariff_3_kwh": 0,"voltage_at_phase_1_v": 242,"voltage_at_phase_2_v": 240,"voltage_at_phase_3_v": 238}
251+
// |MyElectricity6;00086426;null;0.713;null;0;242;240;238;35.007;67.953;0;0;0;0;1111-11-11 11:11.11

components/wmbus_common/driver_apator08.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ namespace
2828
void processContent(Telegram *t);
2929
};
3030

31-
static bool ok = registerDriver([](DriverInfo&di)
31+
static bool ok = staticRegisterDriver([](DriverInfo&di)
3232
{
3333
di.setName("apator08");
3434
di.setDefaultFields("name,id,total_m3,timestamp");
3535
di.setMeterType(MeterType::WaterMeter);
3636
di.addLinkMode(LinkMode::T1);
37-
di.addDetection(MANUFACTURER_APT, 0x03, 0x03);
38-
di.addDetection(MANUFACTURER_APT, 0x0F, 0x0F);
37+
di.addMVT(MANUFACTURER_APT, 0x03, 0x03);
38+
di.addMVT(MANUFACTURER_APT, 0x0F, 0x0F);
3939
di.usesProcessContent();
4040
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
4141
});

components/wmbus_common/driver_apator162.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,16 @@ namespace
2828
int registerSize(int c);
2929
};
3030

31-
static bool ok = registerDriver([](DriverInfo&di)
31+
static bool ok = staticRegisterDriver([](DriverInfo&di)
3232
{
3333
di.setName("apator162");
3434
di.setDefaultFields("name,id,total_m3,timestamp");
3535
di.setMeterType(MeterType::WaterMeter);
3636
di.addLinkMode(LinkMode::T1);
3737
di.addLinkMode(LinkMode::C1);
38-
di.addDetection(MANUFACTURER_APA, 0x06, 0x05);
39-
di.addDetection(MANUFACTURER_APA, 0x07, 0x05);
40-
di.addDetection(0x8614 /*APT?*/, 0x07, 0x05); // Older version of telegram that is not understood!
38+
di.addMVT(MANUFACTURER_APA, 0x06, 0x05);
39+
di.addMVT(MANUFACTURER_APA, 0x07, 0x05);
40+
di.addMVT(0x8614 /*APT?*/, 0x07, 0x05); // Older version of telegram that is not understood!
4141
di.usesProcessContent();
4242
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
4343
});

components/wmbus_common/driver_apator172.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ namespace
2828
void processContent(Telegram *t);
2929
};
3030

31-
static bool ok = registerDriver([](DriverInfo&di)
31+
static bool ok = staticRegisterDriver([](DriverInfo&di)
3232
{
3333
di.setName("apator172");
3434
di.setDefaultFields("name,id,total_m3,timestamp");
3535
di.setMeterType(MeterType::WaterMeter);
36-
di.addDetection(0x8614 /*APT?*/, 0x11, 0x04);
36+
di.addMVT(0x8614 /*APT?*/, 0x11, 0x04);
3737
di.usesProcessContent();
3838
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
3939
});

components/wmbus_common/driver_apatoreitn.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,16 @@ namespace
2828
std::string dateToString(uchar date_lo, uchar date_hi);
2929
};
3030

31-
static bool ok = registerDriver([](DriverInfo&di)
31+
static bool ok = staticRegisterDriver([](DriverInfo&di)
3232
{
3333
// Note: this supports only E.ITN 30.51 at the moment.
3434
// E.ITN 30.60 should be similar, as it is covered via the same datasheet
3535
// http://www.apator.com/uploads/files/Produkty/Podzielnik_kosztow_ogrzewania/i-pl-021-2016-e-itn-30-51-30-6.pdf
3636
di.setName("apatoreitn");
3737
di.setDefaultFields("name,id,current_hca,previous_hca,current_date,season_start_date,esb_date,temp_room_avg_c,temp_room_prev_avg_c,timestamp");
3838
di.setMeterType(MeterType::HeatCostAllocationMeter);
39-
di.addDetection(MANUFACTURER_APA, 0x08, 0x04);
40-
di.addDetection(MANUFACTURER_APT, 0x08, 0x04);
39+
di.addMVT(MANUFACTURER_APA, 0x08, 0x04);
40+
di.addMVT(MANUFACTURER_APT, 0x08, 0x04);
4141
di.usesProcessContent();
4242
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
4343
});

0 commit comments

Comments
 (0)