Skip to content

Commit fef3fa9

Browse files
committed
tries improving
1 parent 00dffa6 commit fef3fa9

File tree

5 files changed

+181
-36
lines changed

5 files changed

+181
-36
lines changed

ngwidgets/mbus_viewer.py

Lines changed: 94 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,80 @@
33
44
@author: wf
55
"""
6+
import os
67
import re
78
import json
89
import meterbus
10+
from dataclasses import field
911
from nicegui import ui
12+
from ngwidgets.widgets import Link
13+
from ngwidgets.yamlable import lod_storable
14+
from typing import Dict
15+
16+
@lod_storable
17+
class Manufacturer:
18+
"""
19+
A manufacturer of M-Bus devices
20+
"""
21+
name: str
22+
url: str
23+
country: str = "Germany" # Most M-Bus manufacturers are German
24+
25+
def as_html(self) -> str:
26+
return Link.create(url=self.url, text=self.name, target="_blank") if self.url else self.name
27+
28+
@lod_storable
29+
class Device:
30+
"""
31+
A device class for M-Bus devices
32+
"""
33+
manufacturer: Manufacturer
34+
model: str
35+
title: str = "" # Optional full product title
36+
doc_url: str = "" # Documentation URL
37+
38+
def as_html(self) -> str:
39+
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+
42+
@lod_storable
43+
class MBusExample:
44+
device: Device
45+
name: str
46+
title: str
47+
hex: str
48+
49+
def as_html(self) -> str:
50+
return self.device.as_html()
51+
52+
@lod_storable
53+
class MBusExamples:
54+
"""
55+
Manages M-Bus devices and their examples
56+
"""
57+
examples: Dict[str, MBusExample] = field(default_factory=dict)
58+
59+
@classmethod
60+
def get(cls, yaml_path:str=None) -> Dict[str, MBusExample]:
61+
if yaml_path is None:
62+
yaml_path=cls.examples_path()+"/mbus_examples.yaml"
63+
mbus_examples=cls.load_from_yaml_file(yaml_path)
64+
return mbus_examples
65+
66+
@classmethod
67+
def examples_path(cls) -> str:
68+
# the root directory (default: examples)
69+
path = os.path.join(os.path.dirname(__file__), "../ngwidgets_examples")
70+
path = os.path.abspath(path)
71+
return path
1072

1173
class MBusParser:
1274
"""
1375
parse MBus data
1476
"""
1577
def __init__(self):
1678
# Define example messages
17-
self.examples = {
18-
"Basic Reading": "684d4d680800722654832277040904360000000c78265483220406493500000c14490508000b2d0000000b3b0000000a5a18060a5e89020b61883200046d0d0c2c310227c80309fd0e2209fd0f470f00008d16",
19-
"CF Echo II": "68 03 03 68 73 fe a6 17 16"
20-
}
79+
self.examples = MBusExamples.get().examples
2180

2281
def fromhex(self, x, base=16):
2382
"""Convert hex string to integer"""
@@ -41,12 +100,12 @@ def get_frame_json(self, frame):
41100
}
42101
return json.dumps(data)
43102

44-
def parse_mbus_data(self, hex_data):
103+
def parse_mbus_frame(self, hex_data):
45104
"""
46-
Parse M-Bus hex data and return JSON result
47-
Returns tuple of (error_msg, json_str)
105+
Parse M-Bus hex data and return mbus frame
106+
Returns tuple of (error_msg, mbus_frame)
48107
"""
49-
json_str = None
108+
frame = None
50109
error_msg = None
51110
try:
52111
# Allow flexible whitespace in input
@@ -55,10 +114,9 @@ def parse_mbus_data(self, hex_data):
55114
data = list(map(self.fromhex, re.findall("..", filtered_data)))
56115
# Parse using meterbus
57116
frame = meterbus.load(data)
58-
json_str = self.get_frame_json(frame)
59117
except Exception as e:
60118
error_msg = f"Error parsing M-Bus data: {str(e)}"
61-
return error_msg, json_str
119+
return error_msg, frame
62120

63121
class MBusViewer(MBusParser):
64122
"""
@@ -70,7 +128,7 @@ def __init__(self, solution=None):
70128
self.hex_input = None
71129
self.json_code = None
72130
self.example_select = None
73-
self.error_label = None
131+
self.error_html = None
74132

75133
def createTextArea(self, label, placeholder=None, classes: str = "h-64"):
76134
"""Create a standardized textarea with common styling"""
@@ -85,34 +143,47 @@ def createTextArea(self, label, placeholder=None, classes: str = "h-64"):
85143
def on_parse(self):
86144
"""Handle parse button click"""
87145
try:
88-
error_msg, json_str = self.parse_mbus_data(self.hex_input.value)
89-
if error_msg:
90-
self.error_label.text = error_msg
91-
self.error_label.classes("text-red-500")
92-
else:
93-
self.json_code.content=json_str
146+
with self.result_row:
147+
self.json_code.content=""
148+
self.error_view.content=""
149+
mbus_hex_str=self.hex_input.value
150+
error_msg, frame = self.parse_mbus_frame(mbus_hex_str)
151+
if error_msg:
152+
self.error_view.content = f"{error_msg}"
153+
else:
154+
json_str=self.get_frame_json(frame)
155+
self.json_code.content=json_str
94156
except Exception as ex:
95157
self.solution.handle_exception(ex)
96158

97159
def on_example_change(self):
98160
"""Handle example selection change"""
161+
99162
selected = self.example_select.value
100163
if selected in self.examples:
101-
self.hex_input.value = self.examples[selected]
164+
example = self.examples[selected]
165+
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
102173
self.on_parse()
103174

104175
def setup_ui(self):
105176
"""Create the NiceGUI user interface"""
106177
ui.label("M-Bus Message Parser").classes("text-h4 q-mb-md")
107178

108-
self.error_label = ui.label().classes("text-red-500 hidden")
109-
110179
self.example_select = ui.select(
111180
label="Select Example",
112181
options=list(self.examples.keys()),
113182
on_change=self.on_example_change,
114183
).classes("w-full q-mb-md")
115184

185+
self.example_details = ui.html().classes("w-full mb-4")
186+
116187
self.hex_input = self.createTextArea(
117188
label="Enter M-Bus hex message",
118189
placeholder="e.g. 68 4d 4d 68 08 00 72 26 54 83 22 77...",
@@ -121,5 +192,6 @@ def setup_ui(self):
121192

122193
with ui.row() as self.button_row:
123194
ui.button("Parse Message", on_click=self.on_parse).classes("q-mt-md")
124-
125-
self.json_code = ui.code(language='json').classes("w-full h-96 q-mt-md")
195+
with ui.row() as self.result_row:
196+
self.error_view = ui.html()
197+
self.json_code = ui.code(language='json').classes("w-full h-96 q-mt-md")

ngwidgets/mbus_viewer_cmd.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,28 @@
33
44
@author: wf
55
"""
6-
76
import sys
8-
7+
from argparse import ArgumentParser
98
from ngwidgets.cmd import WebserverCmd
109
from ngwidgets.mbus_viewer_server import NiceMBusWebserver
11-
10+
from ngwidgets.mbus_viewer import MBusExamples
1211

1312
class NiceMBusCmd(WebserverCmd):
1413
"""
1514
command line handling for ngwidgets
1615
"""
16+
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
1728

1829

1930
def main(argv: list = None):

ngwidgets/mbus_viewer_server.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
44
@author: wf
55
"""
6+
import os
67
from nicegui import Client
78
import ngwidgets
89
from ngwidgets.input_webserver import InputWebserver, WebserverConfig, InputWebSolution
910
from ngwidgets.yamlable import lod_storable
10-
from ngwidgets.mbus_viewer import MBusViewer
11+
from ngwidgets.mbus_viewer import MBusViewer, MBusExamples
12+
1113

1214
@lod_storable
1315
class Version:
@@ -65,6 +67,20 @@ def __init__(self):
6567
InputWebserver.__init__(self, config=NiceMBusWebserver.get_config())
6668
pass
6769

70+
def configure_run(self):
71+
root_path = (
72+
self.args.root_path
73+
if self.args.root_path
74+
else MBusExamples.examples_path()
75+
)
76+
self.root_path = os.path.abspath(root_path)
77+
self.allowed_urls = [
78+
"https://raw.githubusercontent.com/WolfgangFahl/nicescad/main/examples/",
79+
"https://raw.githubusercontent.com/openscad/openscad/master/examples/",
80+
self.root_path,
81+
]
82+
83+
6884
class NiceMBus(InputWebSolution):
6985
"""
7086
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Example definitions for mbus viewer tool
2+
# WF 2025-01-22
3+
4+
manufacturers:
5+
allmess:
6+
name: Allmess
7+
url: https://www.allmess.de
8+
country: Germany
9+
10+
devices:
11+
cf_echo_ii:
12+
manufacturer: allmess
13+
model: CF Echo II
14+
doc_url: https://www.allmess.de/fileadmin/multimedia/alle_Dateien/MA/MA_BA_12088-AC%20CF%20Echo%20II%20D%20TS1021_KL.pdf
15+
16+
ultramaxx:
17+
manufacturer: allmess
18+
model: UltraMaXX
19+
title: Integral-MK UltraMaXX
20+
doc_url: https://www.allmess.de/fileadmin/multimedia/alle_Dateien/DB/DB_P0012%20UltraMaXX_TS0219.pdf
21+
22+
examples:
23+
cf_echo_ii_basic:
24+
device: cf_echo_ii
25+
name: Basic Reading
26+
title: Standard M-Bus reading
27+
hex: 684d4d680800722654832277040904360000000c78265483220406493500000c14490508000b2d0000000b3b0000000a5a18060a5e89020b61883200046d0d0c2c310227c80309fd0e2209fd0f470f00008d16
28+
29+
cf_echo_ii_init:
30+
device: cf_echo_ii
31+
name: CF Echo II init write
32+
title: CF Echo II init
33+
hex: 6803036873fea61716
34+
35+
ultramaxx_init:
36+
device: ultramaxx
37+
name: CFUM init
38+
title: CFUM init
39+
hex: 6803036853fea6f716

tests/test_mbusparser.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66
import json
77
from ngwidgets.basetest import Basetest
8-
from ngwidgets.mbus_viewer import MBusParser
8+
from ngwidgets.mbus_viewer import MBusParser,MBusExample
99

1010
class TestMBusParser(Basetest):
1111
"""
@@ -20,16 +20,23 @@ def test_mbus_parser(self):
2020
Test examples from MBusParser class
2121
"""
2222
mbus_parser = MBusParser()
23-
for name, hex_data in mbus_parser.examples.items():
24-
error_msg,json_str = mbus_parser.parse_mbus_data(hex_data)
23+
for name, example in MBusExample.get().items():
24+
if not example.hex:
25+
if self.debug:
26+
print(f"{example.name}: ⚪ no hex data")
27+
continue
28+
29+
error_msg, frame = mbus_parser.parse_mbus_frame(example.hex)
2530
if self.debug:
26-
marker="✗" if error_msg else "✓"
27-
print(f"{name}{marker}")
31+
marker = "✗" if error_msg else "✓"
32+
print(f"{example.name} {marker}")
2833
if error_msg:
29-
print(error_msg)
34+
print(f" {error_msg}")
3035
else:
31-
json_data=json.loads(json_str)
32-
print(json.dumps(json_data,indent=2))
36+
json_str = mbus_parser.get_frame_json(frame)
37+
json_data = json.loads(json_str)
38+
print(json.dumps(json_data, indent=2))
3339

34-
self.assertIsNone(error_msg, f"Failed to parse {name}")
35-
self.assertIsInstance(json_data, dict)
40+
if example.hex: # only assert for examples with hex data
41+
self.assertIsNone(error_msg, f"Failed to parse {example.name}")
42+
self.assertIsInstance(json_data, dict)

0 commit comments

Comments
 (0)