33
44@author: wf
55"""
6+
7+ import json
68import os
79import re
8- import json
9- import meterbus
1010from dataclasses import field
11+ from typing import Dict
12+
13+ import meterbus
1114from nicegui import ui
15+
1216from ngwidgets .widgets import Link
1317from ngwidgets .yamlable import lod_storable
14- from typing import Dict
18+ from reportlab .lib .textsplit import dumbSplit
19+
1520
1621@lod_storable
1722class 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
2940class 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
4357class 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
5375class 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
73131class 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+
121179class 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" )
0 commit comments