MBusinoLibPy 0.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- MBusinoLibPy/__init__.py +68 -0
- MBusinoLibPy/enums.py +170 -0
- MBusinoLibPy/lookup_tables.py +782 -0
- MBusinoLibPy/mbusino_lib.py +1148 -0
- MBusinoLibPy/py.typed +0 -0
- MBusinoLibPy/vif_defs.py +174 -0
- mbusinolibpy-0.10.0.dist-info/METADATA +284 -0
- mbusinolibpy-0.10.0.dist-info/RECORD +11 -0
- mbusinolibpy-0.10.0.dist-info/WHEEL +5 -0
- mbusinolibpy-0.10.0.dist-info/licenses/LICENSE +674 -0
- mbusinolibpy-0.10.0.dist-info/top_level.txt +1 -0
MBusinoLibPy/py.typed
ADDED
|
File without changes
|
MBusinoLibPy/vif_defs.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""vif_defs table - 1:1 port from MBusinoLib.h line 179-345.
|
|
3
|
+
|
|
4
|
+
Struktur pro Eintrag wie im C++ (`typedef struct { uint8_t code; uint32_t base;
|
|
5
|
+
uint8_t size; int8_t scalar; } vif_def_type`):
|
|
6
|
+
|
|
7
|
+
(code, base, size, scalar)
|
|
8
|
+
| | | |
|
|
9
|
+
| | | +-- scaling exponent: value = raw * 10^scalar
|
|
10
|
+
| | +-------- number of consecutive valid values
|
|
11
|
+
| +-------------- first byte/extension byte this definition belongs to
|
|
12
|
+
+---------------------- MBUS_CODE enum value delivered for this VIF combination
|
|
13
|
+
|
|
14
|
+
The lookup `_findDefinition(vif)` checks `base <= vif < base + size` and returns the
|
|
15
|
+
FIRST match - therefore the order here matters just as much as in C++.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from .enums import MBUS_CODE
|
|
19
|
+
|
|
20
|
+
# Number of entries, in C++: #define MBUS_VIF_DEF_NUM 132 (MBusinoLib.h line 177)
|
|
21
|
+
MBUS_VIF_DEF_NUM = 132
|
|
22
|
+
|
|
23
|
+
# Order and values taken over unchanged from MBusinoLib.h.
|
|
24
|
+
VIF_DEFS = (
|
|
25
|
+
# C++-Kommentar: "0x7E is not used in direction Slave --> Master and
|
|
26
|
+
# therefore misused as library failure hint." -> UNKNOWN_VIF
|
|
27
|
+
(MBUS_CODE.UNKNOWN_VIF , 0x7E , 1, 0), # Fehlerhinweis-Eintrag
|
|
28
|
+
|
|
29
|
+
# --- without VIFE (direct codes) ---
|
|
30
|
+
(MBUS_CODE.ENERGY_WH , 0x0 , 8, -3),
|
|
31
|
+
(MBUS_CODE.ENERGY_J , 0x8 , 8, 0),
|
|
32
|
+
(MBUS_CODE.VOLUME_M3 , 0x10 , 8, -6),
|
|
33
|
+
(MBUS_CODE.MASS_KG , 0x18 , 8, -3),
|
|
34
|
+
(MBUS_CODE.ON_TIME_S , 0x20 , 1, 0),
|
|
35
|
+
(MBUS_CODE.ON_TIME_MIN , 0x21 , 1, 0),
|
|
36
|
+
(MBUS_CODE.ON_TIME_H , 0x22 , 1, 0),
|
|
37
|
+
(MBUS_CODE.ON_TIME_DAYS , 0x23 , 1, 0),
|
|
38
|
+
(MBUS_CODE.OPERATING_TIME_S , 0x24 , 1, 0),
|
|
39
|
+
(MBUS_CODE.OPERATING_TIME_MIN , 0x25 , 1, 0),
|
|
40
|
+
(MBUS_CODE.OPERATING_TIME_H , 0x26 , 1, 0),
|
|
41
|
+
(MBUS_CODE.OPERATING_TIME_DAYS , 0x27 , 1, 0),
|
|
42
|
+
(MBUS_CODE.POWER_W , 0x28 , 8, -3),
|
|
43
|
+
(MBUS_CODE.POWER_J_H , 0x30 , 8, 0),
|
|
44
|
+
(MBUS_CODE.VOLUME_FLOW_M3_H , 0x38 , 8, -6),
|
|
45
|
+
(MBUS_CODE.VOLUME_FLOW_M3_MIN , 0x40 , 8, -7),
|
|
46
|
+
(MBUS_CODE.VOLUME_FLOW_M3_S , 0x48 , 8, -9),
|
|
47
|
+
(MBUS_CODE.MASS_FLOW_KG_H , 0x50 , 8, -3),
|
|
48
|
+
(MBUS_CODE.FLOW_TEMPERATURE_C , 0x58 , 4, -3),
|
|
49
|
+
(MBUS_CODE.RETURN_TEMPERATURE_C , 0x5C , 4, -3),
|
|
50
|
+
(MBUS_CODE.TEMPERATURE_DIFF_K , 0x60 , 4, -3),
|
|
51
|
+
(MBUS_CODE.EXTERNAL_TEMPERATURE_C , 0x64 , 4, -3),
|
|
52
|
+
(MBUS_CODE.PRESSURE_BAR , 0x68 , 4, -3),
|
|
53
|
+
(MBUS_CODE.TIME_POINT_DATE , 0x6C , 1, 0),
|
|
54
|
+
(MBUS_CODE.TIME_POINT_DATETIME , 0x6D , 1, 0),
|
|
55
|
+
(MBUS_CODE.AVG_DURATION_S , 0x70 , 1, 0),
|
|
56
|
+
(MBUS_CODE.AVG_DURATION_MIN , 0x71 , 1, 0),
|
|
57
|
+
(MBUS_CODE.AVG_DURATION_H , 0x72 , 1, 0),
|
|
58
|
+
(MBUS_CODE.AVG_DURATION_DAYS , 0x73 , 1, 0),
|
|
59
|
+
(MBUS_CODE.ACTUAL_DURATION_S , 0x74 , 1, 0),
|
|
60
|
+
(MBUS_CODE.ACTUAL_DURATION_MIN , 0x75 , 1, 0),
|
|
61
|
+
(MBUS_CODE.ACTUAL_DURATION_H , 0x76 , 1, 0),
|
|
62
|
+
(MBUS_CODE.ACTUAL_DURATION_DAYS , 0x77 , 1, 0),
|
|
63
|
+
(MBUS_CODE.FABRICATION_NUMBER , 0x78 , 1, 0),
|
|
64
|
+
(MBUS_CODE.BUS_ADDRESS , 0x7A , 1, 0),
|
|
65
|
+
(MBUS_CODE.VOLUME_M3 , 0x933A , 1, -3),
|
|
66
|
+
(MBUS_CODE.VOLUME_M3 , 0x943A , 1, -2),
|
|
67
|
+
|
|
68
|
+
# --- VIFE 0xFD ---
|
|
69
|
+
(MBUS_CODE.CREDIT , 0xFD00 , 4, -3),
|
|
70
|
+
(MBUS_CODE.DEBIT , 0xFD04 , 4, -3),
|
|
71
|
+
(MBUS_CODE.ACCESS_NUMBER , 0xFD08 , 1, 0),
|
|
72
|
+
(MBUS_CODE.MANUFACTURER , 0xFD0A , 1, 0),
|
|
73
|
+
(MBUS_CODE.PARAMETER_SET_ID , 0xFD0B , 1, 0),
|
|
74
|
+
(MBUS_CODE.MODEL_VERSION , 0xFD0C , 1, 0),
|
|
75
|
+
(MBUS_CODE.HARDWARE_VERSION , 0xFD0D , 1, 0),
|
|
76
|
+
(MBUS_CODE.FIRMWARE_VERSION , 0xFD0E , 1, 0),
|
|
77
|
+
(MBUS_CODE.SOFTWARE_VERSION , 0xFD0F , 1, 0), # C++: "//neu"
|
|
78
|
+
(MBUS_CODE.CUSTOMER_LOCATION , 0xFD10 , 1, 0),
|
|
79
|
+
(MBUS_CODE.CUSTOMER , 0xFD11 , 1, 0),
|
|
80
|
+
(MBUS_CODE.ERROR_FLAGS , 0xFD17 , 1, 0),
|
|
81
|
+
(MBUS_CODE.ERROR_MASK , 0xFD18 , 1, 0),
|
|
82
|
+
(MBUS_CODE.DIGITAL_OUTPUT , 0xFD1A , 1, 0),
|
|
83
|
+
(MBUS_CODE.DIGITAL_INPUT , 0xFD1B , 1, 0),
|
|
84
|
+
(MBUS_CODE.BAUDRATE_BPS , 0xFD1C , 1, 0),
|
|
85
|
+
(MBUS_CODE.RESPONSE_DELAY_TIME , 0xFD1D , 1, 0),
|
|
86
|
+
(MBUS_CODE.RETRY , 0xFD1E , 1, 0),
|
|
87
|
+
(MBUS_CODE.SIZE_OF_STORAGE_BLOCK , 0xFD22 , 1, 0),
|
|
88
|
+
(MBUS_CODE.STORAGE_INTERVAL_MONTH , 0xFD28 , 1, 0),
|
|
89
|
+
(MBUS_CODE.GENERIC , 0xFD3A , 1, 0),
|
|
90
|
+
(MBUS_CODE.VOLTS , 0xFD40 , 16, -9),
|
|
91
|
+
(MBUS_CODE.AMPERES , 0xFD50 , 16, -12),
|
|
92
|
+
(MBUS_CODE.RESET_COUNTER , 0xFD60 , 1, 0),
|
|
93
|
+
(MBUS_CODE.CUMULATION_COUNTER , 0xFD61 , 1, 0),
|
|
94
|
+
(MBUS_CODE.SPECIAL_SUPPLIER_INFO , 0xFD67 , 1, 0),
|
|
95
|
+
|
|
96
|
+
# --- VIFE 0xFDFD ---
|
|
97
|
+
(MBUS_CODE.CURRENT_SELECTED_APL , 0xFDFD00, 1, 0),
|
|
98
|
+
(MBUS_CODE.SUB_DEVICES , 0xFDFD01, 1, 0),
|
|
99
|
+
(MBUS_CODE.REMAIN_BAT_LIFE_MONTH , 0xFDFD02, 1, 0),
|
|
100
|
+
(MBUS_CODE.CARBON_DIOXIDE_PPM , 0xFDFD10, 2, 0),
|
|
101
|
+
(MBUS_CODE.CARBON_MONOXIDE_PPM , 0xFDFD12, 2, 0),
|
|
102
|
+
(MBUS_CODE.VOLATILE_ORG_COMP_ppb , 0xFDFD14, 2, 0),
|
|
103
|
+
(MBUS_CODE.VOLATILE_ORG_COMP_UG_M3 , 0xFDFD16, 1, 0),
|
|
104
|
+
(MBUS_CODE.PARTICLES_UNSPEC_UG_M3 , 0xFDFD17, 1, 0),
|
|
105
|
+
(MBUS_CODE.PARTICLES_PM1_UG_M3 , 0xFDFD18, 1, 0),
|
|
106
|
+
(MBUS_CODE.PARTICLES_PM2_5_UG_M3 , 0xFDFD19, 1, 0),
|
|
107
|
+
(MBUS_CODE.PARTICLES_PM10_UG_M3 , 0xFDFD1A, 1, 0),
|
|
108
|
+
(MBUS_CODE.PARTICLES_UNSPEC_1M3 , 0xFDFD1B, 1, 5),
|
|
109
|
+
(MBUS_CODE.PARTICLES_PM1_1M3 , 0xFDFD1C, 1, 5),
|
|
110
|
+
(MBUS_CODE.PARTICLES_PM2_5_1M3 , 0xFDFD1D, 1, 5),
|
|
111
|
+
(MBUS_CODE.PARTICLES_PM10_1M3 , 0xFDFD1E, 1, 5),
|
|
112
|
+
(MBUS_CODE.ILLUMINANCE_LUX , 0xFDFD1F, 1, 0),
|
|
113
|
+
(MBUS_CODE.LUMINOUS_INTENSITY_CD , 0xFDFD20, 1, 0),
|
|
114
|
+
(MBUS_CODE.RADIANT_FLUX_DENS , 0xFDFD21, 1, 0),
|
|
115
|
+
(MBUS_CODE.WIND_SPEED_M_S , 0xFDFD22, 1, 0),
|
|
116
|
+
(MBUS_CODE.RAINFALL_L_MM , 0xFDFD23, 1, 0),
|
|
117
|
+
(MBUS_CODE.FORMAZIN_NEPHELOMETER_U , 0xFDFD25, 1, 0),
|
|
118
|
+
(MBUS_CODE.POTENTIAL_HYDROGEN_PH , 0xFDFD27, 1, 0),
|
|
119
|
+
(MBUS_CODE.DISMOUNTS_COUNTER , 0xFDFD2C, 1, 0),
|
|
120
|
+
(MBUS_CODE.TEST_BUTTON_COUNTER , 0xFDFD2D, 1, 0),
|
|
121
|
+
(MBUS_CODE.ALARM_COUNTER , 0xFDFD2E, 1, 0),
|
|
122
|
+
(MBUS_CODE.ALARM_MUTE_COUNTER , 0xFDFD2F, 1, 0),
|
|
123
|
+
(MBUS_CODE.OBSTACLE_DETECT_COUNTER , 0xFDFD30, 1, 0),
|
|
124
|
+
(MBUS_CODE.SMOKE_ENTRIES_COUNTER , 0xFDFD31, 1, 0),
|
|
125
|
+
(MBUS_CODE.SMOKE_CHAMBER_DEFECTS , 0xFDFD32, 1, 0),
|
|
126
|
+
(MBUS_CODE.SELF_TEST_COUNTER , 0xFDFD33, 1, 0),
|
|
127
|
+
(MBUS_CODE.SOUNDER_DEFECT_COUNTER , 0xFDFD34, 1, 0),
|
|
128
|
+
(MBUS_CODE.DECIBEL_A , 0xFDFD36, 1, 0),
|
|
129
|
+
(MBUS_CODE.BATTERY_PERCENTAGE , 0xFDFD38, 1, 0),
|
|
130
|
+
(MBUS_CODE.CHAMBER_POLLUTION_LEVEL , 0xFDFD39, 1, 0),
|
|
131
|
+
(MBUS_CODE.DISTANCE_MM , 0xFDFD3A, 2, 0),
|
|
132
|
+
(MBUS_CODE.MOISTURE_LEVEL_PERCENT , 0xFDFD3E, 1, 0),
|
|
133
|
+
(MBUS_CODE.PRESSURE_SENS_STATUS , 0xFDFD40, 1, 0),
|
|
134
|
+
(MBUS_CODE.SMOKE_ALARM_STATUS , 0xFDFD41, 1, 0),
|
|
135
|
+
(MBUS_CODE.CO_ALARM_STATUS , 0xFDFD42, 1, 0),
|
|
136
|
+
(MBUS_CODE.HEAT_ALARM_STATUS , 0xFDFD43, 1, 0),
|
|
137
|
+
(MBUS_CODE.DOOR_WINDOW_SENS_STATUS , 0xFDFD44, 1, 0),
|
|
138
|
+
|
|
139
|
+
# --- VIFE 0xFB ---
|
|
140
|
+
(MBUS_CODE.ENERGY_WH , 0xFB00 , 2, 5),
|
|
141
|
+
(MBUS_CODE.REACTIVE_ENERGY , 0xFB02 , 2, 0),
|
|
142
|
+
(MBUS_CODE.ENERGY_J , 0xFB08 , 2, 8),
|
|
143
|
+
(MBUS_CODE.VOLUME_M3 , 0xFB10 , 2, 2),
|
|
144
|
+
(MBUS_CODE.REACTIVE_POWER , 0xFB14 , 4, -3),
|
|
145
|
+
(MBUS_CODE.MASS_KG , 0xFB18 , 2, 5),
|
|
146
|
+
(MBUS_CODE.RELATIVE_HUMIDITY , 0xFB1A , 2, -1),
|
|
147
|
+
(MBUS_CODE.VOLUME_FT3 , 0xFB21 , 1, -1),
|
|
148
|
+
(MBUS_CODE.VOLUME_GAL , 0xFB22 , 2, -1),
|
|
149
|
+
(MBUS_CODE.VOLUME_FLOW_GAL_M , 0xFB24 , 1, -3),
|
|
150
|
+
(MBUS_CODE.VOLUME_FLOW_GAL_M , 0xFB25 , 1, 0),
|
|
151
|
+
(MBUS_CODE.VOLUME_FLOW_GAL_H , 0xFB26 , 1, 0),
|
|
152
|
+
(MBUS_CODE.POWER_W , 0xFB28 , 2, 5),
|
|
153
|
+
(MBUS_CODE.PHASE_VOLT_DEG , 0xFB2A , 1, -1),
|
|
154
|
+
(MBUS_CODE.PHASE_CURR_DEG , 0xFB2B , 1, -1),
|
|
155
|
+
(MBUS_CODE.FREQUENCY , 0xFB2C , 4, -3),
|
|
156
|
+
(MBUS_CODE.POWER_J_H , 0xFB30 , 2, 8),
|
|
157
|
+
(MBUS_CODE.APPARENT_POWER , 0xFB34 , 4, -3),
|
|
158
|
+
(MBUS_CODE.FLOW_TEMPERATURE_F , 0xFB58 , 4, -3),
|
|
159
|
+
(MBUS_CODE.RETURN_TEMPERATURE_F , 0xFB5C , 4, -3),
|
|
160
|
+
(MBUS_CODE.TEMPERATURE_DIFF_F , 0xFB60 , 4, -3),
|
|
161
|
+
(MBUS_CODE.EXTERNAL_TEMPERATURE_F , 0xFB64 , 4, -3),
|
|
162
|
+
(MBUS_CODE.TEMPERATURE_LIMIT_F , 0xFB70 , 4, -3),
|
|
163
|
+
(MBUS_CODE.TEMPERATURE_LIMIT_C , 0xFB74 , 4, -3),
|
|
164
|
+
(MBUS_CODE.MAX_POWER_W , 0xFB78 , 8, -3),
|
|
165
|
+
|
|
166
|
+
# --- VIFE 0xFC ---
|
|
167
|
+
(MBUS_CODE.CUSTOMIZED_VIF , 0xFC00 , 254, 0),
|
|
168
|
+
|
|
169
|
+
# --- VIFE 0xFF ---
|
|
170
|
+
(MBUS_CODE.MANUFACTURER_SPECIFIC , 0xFF00 , 254, 0),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Safety net: must match the C++ constant exactly.
|
|
174
|
+
assert len(VIF_DEFS) == MBUS_VIF_DEF_NUM, len(VIF_DEFS)
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: MBusinoLibPy
|
|
3
|
+
Version: 0.10.0
|
|
4
|
+
Summary: Python port of MBusinoLib, the Arduino M-Bus (Meterbus) RSP_UD decoder library
|
|
5
|
+
Author-email: Zeppelin500 <mbusinolib@gmail.com>
|
|
6
|
+
License: GPL-3.0-or-later
|
|
7
|
+
Project-URL: Homepage, https://github.com/Zeppelin500/MBusinoLibPy
|
|
8
|
+
Project-URL: Source, https://github.com/Zeppelin500/MBusinoLibPy
|
|
9
|
+
Project-URL: Original, https://github.com/Zeppelin500/MBusinoLib
|
|
10
|
+
Keywords: m-bus,mbus,meterbus,mbusino,decoder,dif,vif,en13757,smart-meter,heat-meter
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Home Automation
|
|
21
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# MBusinoLibPy - a Python M-Bus Decoder
|
|
29
|
+
|
|
30
|
+
[](CHANGELOG.md)
|
|
31
|
+
[](LICENSE)
|
|
32
|
+
[](https://www.python.org/)
|
|
33
|
+
[](pyproject.toml)
|
|
34
|
+
|
|
35
|
+
**MBusinoLibPy** decodes M-Bus (Meterbus) **RSP_UD** telegrams - the answer a
|
|
36
|
+
slave sends to the master - into plain Python dicts. It uses nothing but the
|
|
37
|
+
Python standard library and is the Python port of the Arduino library
|
|
38
|
+
[**MBusinoLib**](https://github.com/Zeppelin500/MBusinoLib) (v0.10.0,
|
|
39
|
+
GPL-3.0).
|
|
40
|
+
|
|
41
|
+
* **No runtime dependencies** - Python ≥ 3.9, standard library only.
|
|
42
|
+
* Validates a *complete* frame (start/stop byte, declared length, checksum)
|
|
43
|
+
before decoding header and records.
|
|
44
|
+
* Known record types come with readable names, units and Home Assistant
|
|
45
|
+
metadata.
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
There is no PyPI package - install straight from GitHub:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install git+https://github.com/Zeppelin500/MBusinoLibPy.git
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Working on the code itself? Use a development checkout:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
git clone https://github.com/Zeppelin500/MBusinoLibPy.git
|
|
59
|
+
cd MBusinoLibPy
|
|
60
|
+
pip install -e .
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Quick start
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from MBusinoLibPy import MBusinoLib
|
|
67
|
+
|
|
68
|
+
# A complete M-Bus long frame: 68 LL LL 68 C A CI ... CHK 16
|
|
69
|
+
telegram = bytes.fromhex(
|
|
70
|
+
"68 C1 C1 68 08 00 72 09 34 75 73 C5 14 00 0D 43 00 00 00 04"
|
|
71
|
+
"78 41 63 65 04 04 06 AA 29 00 00 04 13 40 A1 75 00 04 2B 00"
|
|
72
|
+
"00 00 00 14 2B 3C F3 00 00 ... 75 16" # shortened for readability
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
decode = MBusinoLib()
|
|
76
|
+
result = decode.decode_telegram(telegram)
|
|
77
|
+
|
|
78
|
+
print(result["frame"]["ok"], result["error_message"]) # True No error
|
|
79
|
+
print(result["header"]["manufacturer"], result["header"]["id"]) # EFE 73753409
|
|
80
|
+
print(result["header"]["medium"]) # Heat (Mass)
|
|
81
|
+
|
|
82
|
+
for record in result["records"]:
|
|
83
|
+
print(record["name"], record.get("value_scaled"), record.get("units"))
|
|
84
|
+
# fab_number 73753409.0 None
|
|
85
|
+
# volume 7708.992 m³
|
|
86
|
+
# ...
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
A hex string works too (spaces, commas and `0x` prefixes are accepted):
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
result = MBusinoLib().decode_telegram("68 1F 1F 68 08 00 72 78 56 34 ... CC 16")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
More runnable examples: [examples/](examples/).
|
|
96
|
+
|
|
97
|
+
## API reference
|
|
98
|
+
|
|
99
|
+
All decoding entry points are methods of the `MBusinoLib` class.
|
|
100
|
+
|
|
101
|
+
### `decode_telegram(raw)`
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
def decode_telegram(raw: bytes) -> dict
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Validates a complete frame and then decodes header and records.
|
|
108
|
+
|
|
109
|
+
* `raw` - complete telegram as `bytes`/`bytearray`, a list of byte values, or
|
|
110
|
+
a hex string.
|
|
111
|
+
* returns
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
{
|
|
115
|
+
"frame": { # frame checks
|
|
116
|
+
"byte_count", "packet_size", "slave_address", "c_field", "ci_field",
|
|
117
|
+
"start_ok", "length_ok", "fits", "stop_ok",
|
|
118
|
+
"checksum", "checksum_received", "checksum_ok", "quartet_checksum_ok",
|
|
119
|
+
"ok", "error_message",
|
|
120
|
+
},
|
|
121
|
+
"header": {...}, # decode_header() output
|
|
122
|
+
"records": [...], # decode_records() output
|
|
123
|
+
"error": 0, # error code of the decoding step (see Error handling)
|
|
124
|
+
"error_message": "...", # message for the first problem found
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`frame["ok"]` is `True` only when everything matched: start bytes, equal
|
|
129
|
+
length fields, declared length vs. actual size, stop byte and checksum.
|
|
130
|
+
The checksum is verified both as a plain byte sum and as an alternative
|
|
131
|
+
*quartet* (half-byte) sum - either match counts, and both results are
|
|
132
|
+
reported individually.
|
|
133
|
+
|
|
134
|
+
### `decode_records(buffer, size=None)`
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
def decode_records(buffer, size=None) -> list[dict]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
* `buffer` - record bytes, normally `telegram[0x13:packet_size]`.
|
|
141
|
+
* `size` - how many bytes to read. Default: `len(buffer)`.
|
|
142
|
+
* returns the list of decoded records.
|
|
143
|
+
|
|
144
|
+
Each record contains these keys (a key is only present when the telegram
|
|
145
|
+
carries it):
|
|
146
|
+
|
|
147
|
+
| key | content |
|
|
148
|
+
|-----|---------|
|
|
149
|
+
| `vif` | VIF/VIFE as lower-case hex string, e.g. `"0xfd1b"` |
|
|
150
|
+
| `code` | `MBUS_CODE` value (the record type, *not* the VIF byte) |
|
|
151
|
+
| `value_scaled` | numeric value, already scaled - absent for pure ASCII records |
|
|
152
|
+
| `value_string` | formatted value (dates, ASCII) |
|
|
153
|
+
| `units` | unit such as `"Wh"`, `"m³"`, `"°C"` - absent when none applies |
|
|
154
|
+
| `name` | record name plus VIFE/function suffix, e.g. `"power_L1"` |
|
|
155
|
+
| `subUnit` | > 0 only |
|
|
156
|
+
| `storage` | > 0 only |
|
|
157
|
+
| `tariff` | > 0 only |
|
|
158
|
+
| `telegramFollow` | `1` when the frame ends with the `0x1F` marker |
|
|
159
|
+
|
|
160
|
+
> **Error semantics:** when a record would run past the end of the buffer,
|
|
161
|
+
> decoding stops, `BUFFER_OVERFLOW` is set, and the records decoded so far
|
|
162
|
+
> are returned - so a partial result is always usable.
|
|
163
|
+
|
|
164
|
+
### `decode_header(buffer, length=None)`
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
def decode_header(buffer, length=None) -> dict
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
* `buffer` - the **complete** telegram including start/stop bytes.
|
|
171
|
+
* `length` - usable length of the telegram. Default: `len(buffer)`.
|
|
172
|
+
* returns the header fields; an empty/partial dict signals failure:
|
|
173
|
+
|
|
174
|
+
| Python return | meaning |
|
|
175
|
+
|---------------|---------|
|
|
176
|
+
| `{}` | failed before writing (too short, wrong start byte, L1 ≠ L2) |
|
|
177
|
+
| dict without `"id"` | failed after writing (CI is not `0x72`/`0x76`) |
|
|
178
|
+
| dict with `"id"` | success |
|
|
179
|
+
|
|
180
|
+
Possible header fields:
|
|
181
|
+
|
|
182
|
+
* `len`, `c_field`, `address`, `ci_field` - basic frame header
|
|
183
|
+
* `id` - meter identification number as HEX string (4 byte BCD, LSB first)
|
|
184
|
+
* `manufacturer` - 3-letter manufacturer code
|
|
185
|
+
* `version`, `medium_code`, `medium` - meter version and medium as text
|
|
186
|
+
* `access_counter`, `status`
|
|
187
|
+
* `status_details` - nested dict: `battery_low`, `permanent_error`,
|
|
188
|
+
`temporary_error`, `app_status`
|
|
189
|
+
* `signature`
|
|
190
|
+
|
|
191
|
+
### `get_code_name`, `get_code_units`, `get_device_class`, `get_state_class`
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
get_code_name(code) -> str # "" when unknown
|
|
195
|
+
get_code_units(code) -> str|None # None == no unit
|
|
196
|
+
get_device_class(code, medium=0xFF) -> str # "" when Home Assistant has no class
|
|
197
|
+
get_state_class(code) -> str # "" when not applicable
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Four lookup methods for `MBUS_CODE` values, implemented as complete lookup
|
|
201
|
+
tables in `MBusinoLibPy/lookup_tables.py`. For the cumulative volume codes
|
|
202
|
+
(`VOLUME_M3`/`FT3`/`GAL`) the `medium` argument selects the Home Assistant
|
|
203
|
+
`device_class` (`water` / `gas` / `volume`).
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
from MBusinoLibPy import MBUS_CODE, MBusinoLib
|
|
207
|
+
|
|
208
|
+
decode = MBusinoLib()
|
|
209
|
+
decode.get_code_name(MBUS_CODE.ENERGY_WH) # 'energy'
|
|
210
|
+
decode.get_code_units(MBUS_CODE.VOLUME_M3) # 'm³'
|
|
211
|
+
decode.get_device_class(MBUS_CODE.VOLUME_M3, 0x07) # 'water' (medium = Water)
|
|
212
|
+
decode.get_device_class(MBUS_CODE.VOLUME_M3, 0x03) # 'gas' (medium = Gas)
|
|
213
|
+
decode.get_state_class(MBUS_CODE.ENERGY_WH) # 'total_increasing'
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### `get_error` / `get_error_message`
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
decode.get_error() -> int # error code, then reset to NO_ERROR
|
|
220
|
+
decode.get_error_message(code=None) -> str # readable message, does NOT reset
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`get_error()` returns the stored error code **and** clears it, so the next
|
|
224
|
+
call starts fresh. `get_error_message()` only reads - with no argument it
|
|
225
|
+
reports the currently stored error, with a code argument it translates that
|
|
226
|
+
code. Call `get_error_message()` before `get_error()` if you need both.
|
|
227
|
+
|
|
228
|
+
## Error handling
|
|
229
|
+
|
|
230
|
+
Problems show up in two places:
|
|
231
|
+
|
|
232
|
+
* **frame level** - `result["frame"]["ok"]` is `False` and
|
|
233
|
+
`result["frame"]["error_message"]` says what was wrong (start byte, length
|
|
234
|
+
fields, truncation, stop byte, checksum).
|
|
235
|
+
* **decoding level** - `result["error"]` carries an error code and
|
|
236
|
+
`result["error_message"]` the matching text.
|
|
237
|
+
|
|
238
|
+
The decoder itself can produce these codes:
|
|
239
|
+
|
|
240
|
+
| value | name | meaning |
|
|
241
|
+
|-------|------|---------|
|
|
242
|
+
| 0 | `NO_ERROR` | no error |
|
|
243
|
+
| 1 | `BUFFER_OVERFLOW` | declared length does not fit, or a record reaches past the buffer |
|
|
244
|
+
| 4 | `UNSUPPORTED_VIF` | VIF is not known; the record falls back to `unknown_vif` |
|
|
245
|
+
|
|
246
|
+
How to check errors:
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
result = MBusinoLib().decode_telegram(telegram)
|
|
250
|
+
|
|
251
|
+
if not result["frame"]["ok"]:
|
|
252
|
+
print(result["frame"]["error_message"]) # e.g. "Checksum mismatch: ..."
|
|
253
|
+
if result["error"] != 0:
|
|
254
|
+
print(result["error_message"]) # e.g. "Unknown or unsupported VIF - ..."
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
When decoding records directly, use the instance methods (note that
|
|
258
|
+
`get_error()` clears the value):
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
decode = MBusinoLib()
|
|
262
|
+
records = decode.decode_records(buf)
|
|
263
|
+
code = decode.get_error() # int, reset to 0 afterwards
|
|
264
|
+
if code != 0:
|
|
265
|
+
print(decode.get_error_message(code))
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Enums
|
|
269
|
+
|
|
270
|
+
`MBUS_CODE` (123 members) and `MBUS_ERROR` (6 members) are `IntEnum`s.
|
|
271
|
+
`VIF_DEFS` is the VIF definition table (132 entries: `(code, base, size,
|
|
272
|
+
scalar)`).
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
MBusinoLibPy is free software under the GNU General Public License v3 (or
|
|
277
|
+
later) - see [LICENSE](LICENSE).
|
|
278
|
+
|
|
279
|
+
## Development
|
|
280
|
+
|
|
281
|
+
Run the test suite from the repository root: `python3 -m unittest`.
|
|
282
|
+
|
|
283
|
+
Internal development notes and open points from the implementation live in
|
|
284
|
+
[dev_issues/](dev_issues/README.md).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
MBusinoLibPy/__init__.py,sha256=WYwzWan95VgeRapuoyy6ytlMg3oOi3VTnE0IpfW4uPY,1955
|
|
2
|
+
MBusinoLibPy/enums.py,sha256=QeqEhClnhEVVTvAlACvGm1IeqN9PirF6PwJOFnnZY58,5003
|
|
3
|
+
MBusinoLibPy/lookup_tables.py,sha256=iL0SpECEbkVpQEljPZJTEib3Esbiq8-rvpfZDWm9Ip8,38058
|
|
4
|
+
MBusinoLibPy/mbusino_lib.py,sha256=YCafJukdlYo6rpH1UwDOiOsjTv2zb689k9HxOdAJlnE,56168
|
|
5
|
+
MBusinoLibPy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
MBusinoLibPy/vif_defs.py,sha256=nBUXrSRf1ooj0hVjpvJXJCvKcLawN6bvk5udAcBnFuw,9985
|
|
7
|
+
mbusinolibpy-0.10.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
8
|
+
mbusinolibpy-0.10.0.dist-info/METADATA,sha256=tgbSMFtLeJefZfNgiMAI5NzuDf_6kKggRmxXmE4hqt4,10291
|
|
9
|
+
mbusinolibpy-0.10.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
10
|
+
mbusinolibpy-0.10.0.dist-info/top_level.txt,sha256=BYSSt2wqI6vm2kWEc_1kAuzPjkC-giji4vipsmcs_cc,13
|
|
11
|
+
mbusinolibpy-0.10.0.dist-info/RECORD,,
|