Skip to content

Commit 75fc9e7

Browse files
committed
Separate MetadataHandler (XML parser) from OpenLCBNetwork.
1 parent 01a5b81 commit 75fc9e7

File tree

7 files changed

+543
-430
lines changed

7 files changed

+543
-430
lines changed

examples/examples_gui.py

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,12 @@
2020

2121
from logging import getLogger
2222

23+
from openlcb.memoryservice import MemorySpace
2324
from openlcb.message import Message
25+
from openlcb.metadataprocessor import MetadataProcessor
2426
from openlcb.mti import MTI
27+
from openlcb.nodeid import NodeID
28+
from openlcb.openlcbnetwork import OpenLCBNetwork
2529

2630
try:
2731
import tkinter as tk
@@ -41,7 +45,7 @@
4145
from openlcb import emit_cast, formatted_ex
4246
from openlcb.tcplink.mdnsconventions import id_from_tcp_service_name
4347

44-
from typing import OrderedDict as TypingOrderedDict
48+
from typing import Callable, OrderedDict as TypingOrderedDict
4549

4650
zeroconf_enabled = False
4751
try:
@@ -454,11 +458,8 @@ def _gui(self, parent):
454458
self.cdi_refresh_button.grid(row=self.cdi_row, column=1)
455459

456460
self.cdi_row += 1
457-
self.cdi_form = CDIForm(self.cdi_tab) # OpenLCBNetwork() subclass
458-
# ^ CDIForm has ttk.Treeview etc.
459-
self.cdi_form.canLink.registerMessageReceivedListener(
460-
self.handleMessage)
461-
self.cdi_form.grid(row=self.cdi_row)
461+
self.network = None
462+
self.cdi_form = None
462463

463464
self.example_tab = ttk.Frame(self.notebook)
464465
self.example_tab.columnconfigure(index=0, weight=1)
@@ -486,6 +487,15 @@ def _gui(self, parent):
486487
# self.rowconfigure(row, weight=1)
487488
# self.rowconfigure(self.row_count-1, weight=1) # make last row expand
488489

490+
def setupNetwork(self):
491+
self.network = OpenLCBNetwork(self.getValue('localNodeID'))
492+
self.cdi_form = CDIForm(self.network.canLink, self.cdi_tab) # MetadataProcessor subclass
493+
# ^ formerly OpenLCBNetwork() subclass
494+
# ^ CDIForm has ttk.Treeview etc.
495+
self.cdi_form.canLink.registerMessageReceivedListener(
496+
self.handleMessage)
497+
self.cdi_form.grid(row=self.cdi_row)
498+
489499
def handleMessage(self, message: Message):
490500
"""Off-thread message handler.
491501
This is called by the OpenLCB network stack which is controlled
@@ -527,6 +537,12 @@ def _handleConnect(self):
527537
print(ready_message)
528538

529539
def _connect(self):
540+
userNodeID = NodeID(self.getValue('localNodeID')) # assert good NodeID
541+
if self.network is None:
542+
self.setupNetwork()
543+
elif self.network.canLink.localNodeID != userNodeID:
544+
self.network.physicalLayer.physicalLayerDown()
545+
self.setupNetwork()
530546
host_var = self.fields.get('host')
531547
host = host_var.get()
532548
port_var = self.fields.get('port')
@@ -535,8 +551,6 @@ def _connect(self):
535551
port = int(port)
536552
else:
537553
raise TypeError("Expected int, got {}".format(emit_cast(port)))
538-
localNodeID_var = self.fields.get('localNodeID')
539-
localNodeID = localNodeID_var.get()
540554
# self.cdi_form.connect(host, port, localNodeID)
541555
self.saveSettings()
542556
self.cdi_connect_button.configure(state=tk.DISABLED)
@@ -553,9 +567,8 @@ def _connect(self):
553567
self._tcp_socket.connect(host, port)
554568
# self.cdi_form.setConnectHandler(self.connectStateChanged)
555569
# ^ See message.mti == MTI Link_Layer_Down instead.
556-
result = self.cdi_form.startListening(
570+
result = self.network.startListening(
557571
self._tcp_socket,
558-
localNodeID,
559572
)
560573
self._connect_thread = None
561574
except Exception as ex:
@@ -587,12 +600,19 @@ def cdiRefreshClicked(self):
587600
print("Querying farNodeID={}".format(repr(farNodeID)))
588601
self.setStatus("Downloading CDI...")
589602
threading.Thread(
590-
target=self.cdi_form.downloadCDI,
591-
args=(farNodeID,),
603+
target=self.downloadCDI,
604+
args=(farNodeID, MemorySpace.CDI),
592605
kwargs={'callback': self.cdi_form.on_cdi_element},
593606
daemon=True,
594607
).start()
595608

609+
def downloadCDI(self, farNodeID: str, space: MemorySpace,
610+
callback: Callable[[dict], None] = None):
611+
self.setStatus("Downloading CDI...")
612+
self.cdi_form.onStartDownload()
613+
assert isinstance(space, MemorySpace)
614+
self.network.download(farNodeID, self.cdi_form, callback=callback)
615+
596616
def getValue(self, key):
597617
field = self.fields.get(key)
598618
if not field:
@@ -728,7 +748,12 @@ def fillDefaultPort(self):
728748
def fillDefault(self, key):
729749
self.fields[key].set(self.settings.getDefault(key))
730750

751+
def getStatus(self):
752+
# See also CDIForm
753+
return self.statusLabel.get()
754+
731755
def setStatus(self, msg):
756+
# See also CDIForm
732757
self.statusLabel.configure(text=msg)
733758

734759
def setTooltip(self, key, msg):

examples/tkexamples/cdiform.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717
from collections import deque
1818
from logging import getLogger
1919
from typing import Callable
20+
21+
from openlcb.linklayer import LinkLayer
22+
from openlcb.memoryservice import MemorySpace
23+
from openlcb.metadataprocessor import element_to_dict
2024
# from xml.etree import ElementTree as ET
2125

22-
from openlcb.openlcbnetwork import element_to_dict
2326

2427
if __name__ == "__main__":
2528
logger = getLogger(__file__)
@@ -37,7 +40,7 @@
3740
" since test running from repo but could not find openlcb in {}."
3841
.format(repr(REPO_DIR)))
3942
try:
40-
from openlcb.openlcbnetwork import OpenLCBNetwork
43+
from openlcb.metadataprocessor import MetadataProcessor
4144
except ImportError as ex:
4245
print("{}: {}".format(type(ex).__name__, ex), file=sys.stderr)
4346
print("* You must run this from a venv that has openlcb installed"
@@ -46,15 +49,19 @@
4649
raise # sys.exit(1)
4750

4851

49-
class CDIForm(ttk.Frame, OpenLCBNetwork):
52+
class CDIForm(ttk.Frame, MetadataProcessor):
5053
"""A GUI frame to represent the CDI visually as a tree.
5154
5255
Args:
5356
parent (TkWidget): Typically a ttk.Frame or tk.Frame with "root"
5457
attribute set.
5558
"""
5659
def __init__(self, *args, **kwargs):
57-
OpenLCBNetwork.__init__(self, *args, **kwargs)
60+
assert isinstance(args[0], LinkLayer), \
61+
"Expected LinkLayer/subclass got {}".format(type(args[0]).__name__)
62+
linkLayer = args[0]
63+
args = args[1:] # remove first argument (only for GUI)
64+
MetadataProcessor.__init__(self, linkLayer, MemorySpace.CDI)
5865
ttk.Frame.__init__(self, *args, **kwargs)
5966
self._top_widgets = []
6067
if len(args) < 1:
@@ -97,20 +104,25 @@ def clear(self):
97104
# return OpenLCBNetwork.connect(self, new_socket, localNodeID,
98105
# callback=callback)
99106

100-
def downloadCDI(self, farNodeID: str,
101-
callback: Callable[[dict], None] = None):
102-
self.setStatus("Downloading CDI...")
103-
self.ignore_non_gui_tags = deque()
104-
self._populating_stack = deque()
105-
super().downloadCDI(farNodeID, callback=callback)
106-
107107
def setStatus(self, message: str):
108+
# See also MainForm
108109
self._status_var.set(message)
109110

111+
def getStatus(self):
112+
# See also MainForm
113+
return self._status_var.get()
114+
115+
def onStartDownload(self):
116+
"""Initialize variables used by element handler(s)."""
117+
self.onStart()
118+
self._resetTree()
119+
self.ignore_non_gui_tags = deque()
120+
self._populating_stack = deque()
121+
110122
def on_cdi_element(self, event_d: dict):
111123
"""Handler for incoming CDI tag
112-
(Use this for callback in downloadCDI, which sets parser's
113-
_onElement)
124+
Use this for callback in downloadCDI, which sets parser
125+
(_dataListener)'s _onElement.
114126
115127
Args:
116128
event_d (dict): Document parsing state info:

openlcb/memoryservice.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
- Wait for either dataReply or rejectedReply call back.
2222
'''
2323

24+
from enum import Enum
2425
import logging
2526

2627
from typing import (
@@ -36,6 +37,23 @@
3637
)
3738

3839

40+
class MemorySpace(Enum):
41+
"""The memory space to read.
42+
In practice, MetadataProcessor (or a non-XML parser if necessary)
43+
uses this to track what data type and format is to be assumed in a
44+
received Message. It is assumed to have the same space as the
45+
request (MemoryReadMemo).
46+
47+
Attributes:
48+
Uninitialized: No data (memory read request response) is expected.
49+
CDI: The data expected from the memory read is CDI XML.
50+
FDI: The data expected from the memory read is FDI XML.
51+
"""
52+
Uninitialized = -1
53+
CDI = 0xFF # decodes to 0x03
54+
FDI = 0xFA
55+
56+
3957
class MemoryReadMemo:
4058
"""Memo carries request and reply.
4159
@@ -45,7 +63,9 @@ class MemoryReadMemo:
4563
space (int): Encoded memory space identifier, where values:
4664
- 0xFF to 0xFD are special spaces, and only the least significant
4765
2 bits are relevant.
66+
- 0xFF is CDI (decodes to 0x03)
4867
- 0x00 to 0xFC represent standard memory spaces directly.
68+
- 0xFA is FDI
4969
address (int): The address in memory where the read operation
5070
should be performed.
5171
rejectedReply (Callable[MemoryReadMemo]): Callback function to handle

0 commit comments

Comments
 (0)