99
1010Contributors: Poikilos
1111"""
12+ from functools import partial
1213import logging
1314import os
1415import sys
2324from xml .etree import ElementTree as ET
2425
2526from openlcb .cdivar import CLASSNAME_TYPES , CDIVar
27+ from openlcb .memoryservice import MemoryReadMemo
28+ from openlcb .nodeid import NodeID
29+ from openlcb .openlcbnetwork import OpenLCBNetwork
2630
2731
2832if __name__ == "__main__" :
@@ -67,22 +71,30 @@ class CDIForm(ttk.Frame, XMLDataProcessor):
6771 Args:
6872 parent (TkWidget): Typically a ttk.Frame or tk.Frame with "root"
6973 attribute set.
74+ linkLayer (LinkLayer): Typically a CanLink instance.
7075 """
7176 def __init__ (self , * args , ** kwargs ):
72- assert isinstance ( args [0 ] , LinkLayer ), \
77+ assert issubclass ( type ( args [1 ]) , LinkLayer ), \
7378 "Expected LinkLayer/subclass got {}" .format (type (args [0 ]).__name__ )
74- linkLayer = args [0 ]
75- args = args [1 :] # remove first argument (only for GUI )
79+ linkLayer = args [1 ]
80+ assert issubclass ( type ( args [0 ]), tk . Widget )
7681 XMLDataProcessor .__init__ (self , linkLayer , MemorySpace .CDI )
77- ttk .Frame .__init__ (self , * args , ** kwargs )
82+ ttk .Frame .__init__ (self , * args [: 1 ] , ** kwargs )
7883 self ._top_widgets = []
7984 if len (args ) < 1 :
8085 raise ValueError ("at least one argument (parent) is required" )
8186 self .parent = args [0 ]
82- self .root = args [0 ]
87+ self .mainform = args [2 ]
88+ self .root = args [2 ]
89+ assert hasattr (self .mainform , 'network' ), \
90+ "mainform must have 'network' OpenLCBNetwork"
91+ assert hasattr (self .mainform , 'settings' ), \
92+ "mainform must have 'settings' dictionary with at least 'farNodeID'"
8393 self ._status_callback = None
8494 if hasattr (self .parent , 'root' ):
8595 self .root = self .parent .root
96+ elif hasattr (self .mainform , 'root' ):
97+ self .root = self .mainform .root
8698 self ._container = self # where to put visible widgets
8799 self ._treeview = None # type: ttk.Treeview|None
88100 self ._treeMemos = {} # type: Dict[str, CDIMemo]
@@ -178,7 +190,7 @@ def onTreeSelect(self, event: tk.Event):
178190 cm = self ._treeMemos [iid ]
179191 # print(f"type(item)={type(item)}")
180192 # raise NotImplementedError(item)
181- # print(f"cm={cm}")
193+ print (f"cm={ cm } " )
182194 self .clearSettingWidgets ()
183195 if cm .tag not in CLASSNAME_TYPES :
184196 # Non-value (such as segment or group)
@@ -199,7 +211,7 @@ def onTreeSelect(self, event: tk.Event):
199211 tkvar = None
200212 v_widget = None
201213 mapToValue = cm .valueMap ()
202- self .mapToValue = mapToValue
214+ # self.mapToValue = mapToValue
203215 if mapToValue :
204216 tkvar = tk .StringVar (self .root )
205217 v_widget = ttk .Combobox (self .cdiSettingFrame ,
@@ -215,22 +227,58 @@ def onTreeSelect(self, event: tk.Event):
215227 raise TypeError ("Device should not specify max for {}"
216228 .format (cdivar .className ))
217229 v_widget = ttk .LabeledScale (self .cdiSettingFrame ,
218- variable = tkvar )
230+ variable = tkvar ,
231+ from_ = cdivar .min ,
232+ to = cdivar .max )
219233 # ^ widget.scale is ttk.Scale, widget.label is ttk.Label
220234 # ^ a.k.a. Slider (if not using Tk)
221235 v_widget .scale .cdivar = cdivar
222236 v_widget .scale .tip = nameLabel .tip
237+
238+ def update_label_width (* args ):
239+ # Fix label being too small to show value:
240+ v_widget .label .configure (width = len (str (int (tkvar .get ()))) + 2 ) # noqa: E501
241+ tkvar .trace_add ('write' , update_label_width )
223242 else :
224243 tkvar = tk .StringVar (self .root )
225244 v_widget = ttk .Entry (self .cdiSettingFrame , textvariable = tkvar )
226245 v_widget .grid (column = 1 , row = self .cdiSettingRow )
227246 self .cdiSettingRow += 1
228247 self .cdiSettingWidgets .append (v_widget )
229248 if cdivar .default is not None :
230- tkvar .set (cdivar .default )
249+ defaultStr = cdivar .default
250+ if mapToValue :
251+ mapToStr = cm .keyMap ()
252+ assert mapToStr
253+ defaultStr = mapToStr [str (cdivar .default .value ())]
254+ tkvar .set (defaultStr )
231255 v_widget .cdivar = cdivar
232256 v_widget .tip = nameLabel .tip
257+ try :
258+ v_widget .configure (state = 'readonly' ) # readonly until refresh
259+ except tk .TclError :
260+ pass # N/A such as for Scale
261+ # so that previous value is known.
262+
263+ # Write and Read buttons
264+ writeButton = ttk .Button (
265+ self .cdiSettingFrame ,
266+ text = "Write" ,
267+ command = partial (self .onWriteValueClicked , v_widget , tkvar ,
268+ cdivar , mapToValue = mapToValue ),
269+ )
270+ # writeButton.grid(column=0, row=self.cdiSettingRow)
271+
272+ readButton = ttk .Button (
273+ self .cdiSettingFrame ,
274+ text = "Read" ,
275+ command = partial (self .onReadValueClicked , v_widget , tkvar ,
276+ cdivar , mapToValue = mapToValue ),
277+ )
278+ readButton .grid (column = 1 , row = self .cdiSettingRow )
279+ self .cdiSettingRow += 1
233280
281+ # Show the address
234282 address_str = ""
235283 if address_str is not None :
236284 address_str = str (cm .address )
@@ -241,9 +289,96 @@ def onTreeSelect(self, event: tk.Event):
241289 self .cdiSettingWidgets .append (a_widget )
242290 self .cdiSettingWidgets .append (av_widget )
243291 self .cdiSettingRow += 1
244-
245292 break
246293
294+ def onReadValueClicked (self , v_widget : tk .Widget ,
295+ tkvar : Union [tk .StringVar , tk .IntVar , tk .DoubleVar ],
296+ cdivar : CDIVar , mapToValue = None ):
297+ print ("read:"
298+ f" space={ cdivar .space } ={ hex (cdivar .space )} "
299+ f" address={ cdivar .address } ={ hex (cdivar .address )} "
300+ f" size={ cdivar .size } ={ hex (cdivar .size )} " )
301+ # read 64 bytes from the CDI space starting at address zero
302+ assert hasattr (self .mainform , 'settings' ), \
303+ "mainform must have 'settings' dictionary"
304+ assert 'farNodeID' in self .mainform .settings , \
305+ "mainform 'settings' dictionary is missing 'farNodeID'"
306+ farNodeIDStr = self .mainform .settings ['farNodeID' ]
307+
308+ memMemo = MemoryReadMemo (NodeID (farNodeIDStr ),
309+ cdivar .size , cdivar .space ,
310+ cdivar .address , self .memoryReadFail ,
311+ self .memoryReadSuccess )
312+ memMemo .widget = v_widget
313+ memMemo .tkvar = tkvar
314+ memMemo .cdivar = cdivar
315+ memMemo .mapToValue = mapToValue
316+ network = self .mainform .network # type: OpenLCBNetwork
317+ network ._memoryService .requestMemoryRead (memMemo )
318+
319+ def memoryReadFail (self , memo : MemoryReadMemo ):
320+ self .setStatus ("Memory read...error." )
321+
322+ def memoryReadSuccess (self , memo : MemoryReadMemo ):
323+ self .setStatus ("Memory read...success." )
324+ widget = None # type: tk.Widget | None
325+ tkvar = None # type: tk.StringVar | tk.IntVar | tk.DoubleVar | None
326+ cdivar = None # type: CDIVar | None
327+ if hasattr (memo , 'widget' ):
328+ widget = memo .widget
329+ try :
330+ # widget['state'] = tk.NORMAL
331+ widget .configure (state = tk .NORMAL )
332+ except tk .TclError :
333+ pass # N/A (such as Scale)
334+ value = None
335+ mapToValue = None
336+ found = None
337+ if hasattr (memo , 'cdivar' ):
338+ cdivar = memo .cdivar
339+ assert cdivar is not None
340+ cdivar .setData (memo .data )
341+ value = cdivar .value ()
342+ if (value is not None ) and hasattr (memo , 'mapToValue' ):
343+ mapToValue = memo .mapToValue
344+ if mapToValue :
345+ for k , v in mapToValue .items ():
346+ # if str(v.value()) == str(value):
347+ if v .value () == value :
348+ value = k # key is the user-facing string
349+ found = value
350+ break
351+ if found is None :
352+ logger .warning (
353+ f"Found no matching value for { repr (value )} in { mapToValue } " )
354+ else :
355+ logger .warning (
356+ f"Found matching value { repr (value )} for { found } " )
357+ else :
358+ logger .debug (
359+ f"There is no value map. Using { repr (value )} directly" )
360+ else :
361+ logger .debug (
362+ f"There is no value map. Using { repr (value )} directly" )
363+ else :
364+ logger .warning ("There is no cdivar." )
365+ if hasattr (memo , 'tkvar' ):
366+ tkvar = memo .tkvar
367+ if value is not None :
368+ if isinstance (tkvar , tk .StringVar ):
369+ print (f"Set StringVar to { repr (value )} " )
370+ tkvar .set (str (value ))
371+ else :
372+ print (f"Set { type (tkvar ).__name__ } to { repr (value )} "
373+ f" ({ cdivar .data } )" )
374+ # Assume input already matches type of tk var
375+ tkvar .set (value )
376+
377+ def onWriteValueClicked (self , v_widget : tk .Widget ,
378+ tkvar : Union [tk .StringVar , tk .IntVar , tk .DoubleVar ], # noqa: E501
379+ cdivar : CDIVar , mapToValue = None ):
380+ print (f"TODO: write: { cdivar .className } ({ repr (tkvar .get ())} )" )
381+
247382 def clearSettingWidgets (self ):
248383 for widget in self .cdiSettingWidgets :
249384 widget .grid_forget ()
0 commit comments