33
44@author: wf
55"""
6+ import os
67import re
78import json
89import meterbus
10+ from dataclasses import field
911from 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
1173class 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
63121class 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" )
0 commit comments