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/py.typed ADDED
File without changes
@@ -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
+ [![version](https://img.shields.io/badge/version-0.10.0-brightgreen.svg)](CHANGELOG.md)
31
+ [![license](https://img.shields.io/badge/license-GPL--3.0-orange.svg)](LICENSE)
32
+ [![python](https://img.shields.io/badge/python-%E2%89%A53.9-blue.svg)](https://www.python.org/)
33
+ [![dependencies](https://img.shields.io/badge/runtime%20deps-0-brightgreen.svg)](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,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+