Skip to content

Commit e82de20

Browse files
committed
improves
1 parent fef3fa9 commit e82de20

File tree

5 files changed

+204
-71
lines changed

5 files changed

+204
-71
lines changed

ngwidgets/mbus_viewer.py

Lines changed: 85 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,77 +3,136 @@
33
44
@author: wf
55
"""
6+
7+
import json
68
import os
79
import re
8-
import json
9-
import meterbus
1010
from dataclasses import field
11+
from typing import Dict
12+
13+
import meterbus
1114
from nicegui import ui
15+
1216
from ngwidgets.widgets import Link
1317
from ngwidgets.yamlable import lod_storable
14-
from typing import Dict
18+
from reportlab.lib.textsplit import dumbSplit
19+
1520

1621
@lod_storable
1722
class Manufacturer:
1823
"""
1924
A manufacturer of M-Bus devices
2025
"""
26+
2127
name: str
2228
url: str
2329
country: str = "Germany" # Most M-Bus manufacturers are German
2430

2531
def as_html(self) -> str:
26-
return Link.create(url=self.url, text=self.name, target="_blank") if self.url else self.name
32+
return (
33+
Link.create(url=self.url, text=self.name, target="_blank")
34+
if self.url
35+
else self.name
36+
)
37+
2738

2839
@lod_storable
2940
class Device:
3041
"""
31-
A device class for M-Bus devices
42+
A device class for M-Bus devices storing manufacturer reference
3243
"""
33-
manufacturer: Manufacturer
44+
mid: str # manufacturer id reference
3445
model: str
3546
title: str = "" # Optional full product title
3647
doc_url: str = "" # Documentation URL
48+
# manufacturer: Manufacturer - set on relink
3749

3850
def as_html(self) -> str:
3951
title = self.title if self.title else self.model
40-
return Link.create(url=self.doc_url, text=title, target="_blank") if self.doc_url else title
41-
52+
device_link = Link.create(url=self.doc_url, text=title, target="_blank") if self.doc_url else title
53+
mfr_html = self.manufacturer.as_html() if hasattr(self, 'manufacturer') else self.mid
54+
return f"{mfr_html}{device_link}"
55+
4256
@lod_storable
4357
class MBusExample:
44-
device: Device
58+
"""
59+
An M-Bus example storing device reference
60+
"""
61+
did: str # device id reference
4562
name: str
4663
title: str
4764
hex: str
65+
# device: Device - set on relink
4866

4967
def as_html(self) -> str:
50-
return self.device.as_html()
68+
device_html = self.device.as_html() if hasattr(self, 'device') else self.did
69+
example_text = f"{self.name}: {self.title}" if self.title else self.name
70+
return f"{device_html}{example_text}"
71+
72+
5173

5274
@lod_storable
5375
class MBusExamples:
5476
"""
55-
Manages M-Bus devices and their examples
77+
Manages M-Bus devices and their examples with separate dictionaries for
78+
manufacturers, devices and examples
5679
"""
80+
manufacturers: Dict[str, Manufacturer] = field(default_factory=dict)
81+
devices: Dict[str, Device] = field(default_factory=dict)
5782
examples: Dict[str, MBusExample] = field(default_factory=dict)
5883

5984
@classmethod
60-
def get(cls, yaml_path:str=None) -> Dict[str, MBusExample]:
85+
def get(cls, yaml_path: str = None) -> 'MBusExamples':
86+
"""
87+
Load and dereference M-Bus examples from YAML
88+
89+
Args:
90+
yaml_path: Path to YAML file (defaults to examples_path/mbus_examples.yaml)
91+
92+
Returns:
93+
MBusExamples instance with dereferenced objects
94+
"""
6195
if yaml_path is None:
62-
yaml_path=cls.examples_path()+"/mbus_examples.yaml"
63-
mbus_examples=cls.load_from_yaml_file(yaml_path)
96+
yaml_path = cls.examples_path() + "/mbus_examples.yaml"
97+
98+
# Load raw YAML data
99+
mbus_examples = cls.load_from_yaml_file(yaml_path)
100+
mbus_examples.relink()
64101
return mbus_examples
102+
103+
def relink(self):
104+
"""
105+
Reestablish object references between manufacturers, devices and examples
106+
"""
107+
# Dereference manufacturers in devices
108+
for device_id, device in self.devices.items():
109+
if device.mid in self.manufacturers:
110+
device.manufacturer = self.manufacturers[device.mid]
111+
else:
112+
raise KeyError(f"Manufacturer '{device.mid}' not found for device '{device_id}'")
113+
114+
# Dereference devices in examples
115+
for example_id, example in self.examples.items():
116+
if example.did in self.devices:
117+
example.device = self.devices[example.did]
118+
else:
119+
raise KeyError(f"Device '{example.did}' not found for example '{example_id}'")
120+
65121

66122
@classmethod
67123
def examples_path(cls) -> str:
68124
# the root directory (default: examples)
69125
path = os.path.join(os.path.dirname(__file__), "../ngwidgets_examples")
70126
path = os.path.abspath(path)
71127
return path
128+
129+
72130

73131
class MBusParser:
74132
"""
75133
parse MBus data
76134
"""
135+
77136
def __init__(self):
78137
# Define example messages
79138
self.examples = MBusExamples.get().examples
@@ -84,19 +143,17 @@ def fromhex(self, x, base=16):
84143

85144
def get_frame_json(self, frame):
86145
"""Fallback JSON generation if to_JSON not available"""
87-
if hasattr(frame, 'to_JSON'):
146+
if hasattr(frame, "to_JSON"):
88147
return frame.to_JSON()
89148
# Fallback to basic frame info
90149
data = {
91150
"header": {
92151
"start": frame.header.startField.parts[0],
93152
"length": len(frame.body.bodyHeader.ci_field.parts) + 2,
94153
"control": frame.header.cField.parts[0],
95-
"address": frame.header.aField.parts[0]
154+
"address": frame.header.aField.parts[0],
96155
},
97-
"body": {
98-
"ci_field": frame.body.bodyHeader.ci_field.parts[0]
99-
}
156+
"body": {"ci_field": frame.body.bodyHeader.ci_field.parts[0]},
100157
}
101158
return json.dumps(data)
102159

@@ -118,10 +175,12 @@ def parse_mbus_frame(self, hex_data):
118175
error_msg = f"Error parsing M-Bus data: {str(e)}"
119176
return error_msg, frame
120177

178+
121179
class MBusViewer(MBusParser):
122180
"""
123181
Meterbus message viewer with JSON editor support
124182
"""
183+
125184
def __init__(self, solution=None):
126185
super().__init__()
127186
self.solution = solution
@@ -144,15 +203,15 @@ def on_parse(self):
144203
"""Handle parse button click"""
145204
try:
146205
with self.result_row:
147-
self.json_code.content=""
148-
self.error_view.content=""
149-
mbus_hex_str=self.hex_input.value
206+
self.json_code.content = ""
207+
self.error_view.content = ""
208+
mbus_hex_str = self.hex_input.value
150209
error_msg, frame = self.parse_mbus_frame(mbus_hex_str)
151210
if error_msg:
152211
self.error_view.content = f"{error_msg}"
153212
else:
154-
json_str=self.get_frame_json(frame)
155-
self.json_code.content=json_str
213+
json_str = self.get_frame_json(frame)
214+
self.json_code.content = json_str
156215
except Exception as ex:
157216
self.solution.handle_exception(ex)
158217

@@ -163,13 +222,7 @@ def on_example_change(self):
163222
if selected in self.examples:
164223
example = self.examples[selected]
165224
self.hex_input.value = example.hex
166-
markup = Link.create(
167-
url=example.url,
168-
text=example.device,
169-
tooltip=example.title,
170-
target="_blank") if example.url else example.device
171-
self.example_details.content = markup
172-
self.example_details.content=markup
225+
self.example_details.content = example.as_html()
173226
self.on_parse()
174227

175228
def setup_ui(self):
@@ -194,4 +247,4 @@ def setup_ui(self):
194247
ui.button("Parse Message", on_click=self.on_parse).classes("q-mt-md")
195248
with ui.row() as self.result_row:
196249
self.error_view = ui.html()
197-
self.json_code = ui.code(language='json').classes("w-full h-96 q-mt-md")
250+
self.json_code = ui.code(language="json").classes("w-full h-96 q-mt-md")

ngwidgets/mbus_viewer_cmd.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,32 @@
33
44
@author: wf
55
"""
6+
67
import sys
78
from argparse import ArgumentParser
9+
810
from ngwidgets.cmd import WebserverCmd
9-
from ngwidgets.mbus_viewer_server import NiceMBusWebserver
1011
from ngwidgets.mbus_viewer import MBusExamples
12+
from ngwidgets.mbus_viewer_server import NiceMBusWebserver
13+
1114

1215
class NiceMBusCmd(WebserverCmd):
1316
"""
1417
command line handling for ngwidgets
1518
"""
19+
1620
def getArgParser(self, description: str, version_msg) -> ArgumentParser:
17-
"""
18-
override the default argparser call
19-
"""
20-
parser = super().getArgParser(description, version_msg)
21-
parser.add_argument(
22-
"-rp",
23-
"--root_path",
24-
default=MBusExamples.examples_path(),
25-
help="path to mbux hex files [default: %(default)s]",
26-
)
27-
return parser
21+
"""
22+
override the default argparser call
23+
"""
24+
parser = super().getArgParser(description, version_msg)
25+
parser.add_argument(
26+
"-rp",
27+
"--root_path",
28+
default=MBusExamples.examples_path(),
29+
help="path to mbux hex files [default: %(default)s]",
30+
)
31+
return parser
2832

2933

3034
def main(argv: list = None):
@@ -43,4 +47,4 @@ def main(argv: list = None):
4347
if __name__ == "__main__":
4448
if DEBUG:
4549
sys.argv.append("-d")
46-
sys.exit(main())
50+
sys.exit(main())

ngwidgets/mbus_viewer_server.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,23 @@
33
44
@author: wf
55
"""
6+
67
import os
8+
79
from nicegui import Client
10+
811
import ngwidgets
9-
from ngwidgets.input_webserver import InputWebserver, WebserverConfig, InputWebSolution
12+
from ngwidgets.input_webserver import InputWebserver, InputWebSolution, WebserverConfig
13+
from ngwidgets.mbus_viewer import MBusExamples, MBusViewer
1014
from ngwidgets.yamlable import lod_storable
11-
from ngwidgets.mbus_viewer import MBusViewer, MBusExamples
1215

1316

1417
@lod_storable
1518
class Version:
1619
"""
1720
Version handling for nicegui widgets
1821
"""
22+
1923
name = "mbus-viewer"
2024
version = ngwidgets.__version__
2125
date = "2025-01-22"
@@ -41,6 +45,7 @@ class Version:
4145
4246
Created by {authors} on {date} last updated {updated}"""
4347

48+
4449
class NiceMBusWebserver(InputWebserver):
4550
"""
4651
webserver to demonstrate ngwidgets capabilities
@@ -69,9 +74,7 @@ def __init__(self):
6974

7075
def configure_run(self):
7176
root_path = (
72-
self.args.root_path
73-
if self.args.root_path
74-
else MBusExamples.examples_path()
77+
self.args.root_path if self.args.root_path else MBusExamples.examples_path()
7578
)
7679
self.root_path = os.path.abspath(root_path)
7780
self.allowed_urls = [
@@ -82,9 +85,7 @@ def configure_run(self):
8285

8386

8487
class NiceMBus(InputWebSolution):
85-
"""
86-
87-
"""
88+
""" """
8889

8990
def __init__(self, webserver: "NiceMBusWebserver", client: Client):
9091
super().__init__(webserver, client)
@@ -95,9 +96,7 @@ async def home(self):
9596
"""
9697

9798
def setup_home():
98-
viewer=MBusViewer(solution=self)
99+
viewer = MBusViewer(solution=self)
99100
viewer.setup_ui()
100101

101-
102102
await self.setup_content_div(setup_home)
103-

ngwidgets_examples/mbus_examples.yaml

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,30 @@ manufacturers:
66
name: Allmess
77
url: https://www.allmess.de
88
country: Germany
9-
109
devices:
1110
cf_echo_ii:
12-
manufacturer: allmess
11+
mid: allmess
1312
model: CF Echo II
13+
title: ''
1414
doc_url: https://www.allmess.de/fileadmin/multimedia/alle_Dateien/MA/MA_BA_12088-AC%20CF%20Echo%20II%20D%20TS1021_KL.pdf
15-
1615
ultramaxx:
17-
manufacturer: allmess
16+
mid: allmess
1817
model: UltraMaXX
1918
title: Integral-MK UltraMaXX
2019
doc_url: https://www.allmess.de/fileadmin/multimedia/alle_Dateien/DB/DB_P0012%20UltraMaXX_TS0219.pdf
21-
2220
examples:
23-
cf_echo_ii_basic:
24-
device: cf_echo_ii
21+
cf_echo_basic:
22+
did: cf_echo_ii
2523
name: Basic Reading
2624
title: Standard M-Bus reading
2725
hex: 684d4d680800722654832277040904360000000c78265483220406493500000c14490508000b2d0000000b3b0000000a5a18060a5e89020b61883200046d0d0c2c310227c80309fd0e2209fd0f470f00008d16
28-
29-
cf_echo_ii_init:
30-
device: cf_echo_ii
26+
cf_echo_init:
27+
did: cf_echo_ii
3128
name: CF Echo II init write
3229
title: CF Echo II init
3330
hex: 6803036873fea61716
34-
3531
ultramaxx_init:
36-
device: ultramaxx
32+
did: ultramaxx
3733
name: CFUM init
3834
title: CFUM init
39-
hex: 6803036853fea6f716
35+
hex: 6803036853fea6f716

0 commit comments

Comments
 (0)