Skip to content

Commit 95d551e

Browse files
committed
Improve output. Assert valid XML. Remove dup def of prDim. Use keyword args explicitly to avoid incorrect usage. Assert CDI is loaded. Reuse NodeID instance for explicitness (and to avoid mutation). Show localNodeID on socket loop failure.
1 parent 5c2759c commit 95d551e

4 files changed

Lines changed: 94 additions & 82 deletions

File tree

examples/example_node_implementation.py

Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,20 @@
1111
the address and port). Defaults to a hard-coded test
1212
address and port.
1313
'''
14+
import os
1415
import socket
16+
from typing import Set, Union
1517

1618
# region same code as other examples
17-
from examples_settings import Settings # do 1st to fix path if no pip install
19+
from examples_settings import Settings
20+
from openlcb.convert import Convert
1821
settings = Settings()
1922

2023
if __name__ == "__main__":
2124
settings.load_cli_args(docstring=__doc__)
2225
# endregion same code as other examples
2326

24-
from openlcb import precise_sleep # noqa: E402
27+
from openlcb import prDim, precise_sleep # noqa: E402
2528
from openlcb.tcplink.tcpsocket import TcpSocket # noqa: E402
2629

2730
from openlcb.canbus.canphysicallayergridconnect import ( # noqa: E402
@@ -66,18 +69,7 @@
6669
# sock.sendString(string)
6770
# physicalLayer.onFrameSent(frame)
6871

69-
70-
def prDim(s: str):
71-
"""Show extra debugging information in darker text so special cases
72-
are readable (resets to 00 [default colors] for text after prDim).
73-
Note: 0;30;40 is non-bold dim, but that's invisible in cmd.exe.
74-
"""
75-
print("\033[1;30;40m {}\033[00m".format(s))
76-
# - `\033[` (or `\e[`, see
77-
# https://gist.github.com/JBlond/2fea43a3049b38287e5e9cefc87b2124)
78-
# starts ansi escape sequence
79-
# - 30 for gray
80-
# - 00 to reset to white
72+
me = os.path.basename(__file__)
8173

8274

8375
def printFrame(frame):
@@ -89,25 +81,18 @@ def printFrame(frame):
8981

9082

9183
def printMessage(message: Message):
92-
prDim("RM: {} from {}".format(message, message.source))
93-
if message.mti is MTI.Verified_NodeID:
94-
# REPLY
95-
# Use source as destination (Get more info about verified node)
96-
message = Message(MTI.Simple_Node_Ident_Info_Request,
97-
localNodeID, message.source)
98-
print(f"[{__file__}] Sending {message.mti} to {message.destination}...")
99-
canLink.sendMessage(message)
84+
"""Received message (RM) handler."""
85+
prDim(f"RM: {message.mti} from {message.source}")
10086

10187

10288
localNodeID = NodeID(settings['localNodeID'])
10389
print()
10490
print(f"[example_node_memory_implementation] localNodeID: {localNodeID}")
10591

10692
canLink = CanLink(physicalLayer, localNodeID)
107-
canLink.registerMessageReceivedListener(printMessage)
108-
10993
datagramService = DatagramService(canLink)
11094
canLink.registerMessageReceivedListener(datagramService.process)
95+
canLink.registerMessageReceivedListener(printMessage)
11196

11297

11398
def printDatagram(memo):
@@ -143,15 +128,22 @@ def memoryReadFail(memo):
143128
# This is a very minimal node, which just takes part in the low-level common
144129
# protocols
145130
localNode = Node(
146-
NodeID(settings['localNodeID']),
147-
SNIP("python-openlcb", "example_node_implementation",
148-
"0.1", "0.2", "Example Node Implementation",
149-
"Example from python-openlcb"),
150-
set([PIP.SIMPLE_NODE_IDENTIFICATION_PROTOCOL, PIP.DATAGRAM_PROTOCOL])
131+
localNodeID,
132+
snip=SNIP("python-openlcb", "example_node_implementation",
133+
"0.1", "0.2", "Example Node Implementation",
134+
"Example from python-openlcb"),
135+
pipSet=set([
136+
PIP.SIMPLE_NODE_IDENTIFICATION_PROTOCOL,
137+
PIP.DATAGRAM_PROTOCOL,
138+
])
151139
)
152140

153141
localNodeProcessor = LocalNodeProcessor(canLink, localNode)
154142
canLink.registerMessageReceivedListener(localNodeProcessor.process)
143+
# ^ Must be registered after CanPhysicalLayer constructor
144+
# since that registers canLink.handleFrameReceived which
145+
# maps aliases and allows us to reply if request was the
146+
# first message to supply the far NodeID.
155147

156148

157149
def displayOtherNodeIds(message: Message) :
@@ -185,19 +177,23 @@ def displayOtherNodeIds(message: Message) :
185177
print(" SL : link up")
186178
# request that nodes identify themselves so that we can print their node IDs
187179
message = Message(MTI.Verify_NodeID_Number_Global,
188-
NodeID(settings['localNodeID']), None)
180+
localNodeID, None)
189181
canLink.sendMessage(message)
190182

191183
# process resulting activity
192184
while True:
193185
count = 0
194-
count += physicalLayer.sendAll(sock, verbose=True,
195-
verbose_fn=prDim)
196-
count += physicalLayer.receiveAll(sock, verbose=settings['trace'],
197-
verbose_fn=prDim)
198-
if count < 1:
199-
precise_sleep(.01)
200-
# else skip sleep to avoid latency (port already delayed)
186+
try:
187+
count += physicalLayer.sendAll(sock, verbose=True,
188+
verbose_fn=prDim)
189+
count += physicalLayer.receiveAll(sock, verbose=settings['trace'],
190+
verbose_fn=prDim)
191+
if count < 1:
192+
precise_sleep(.01)
193+
# else skip sleep to avoid latency (port already delayed)
194+
except Exception:
195+
print(f"localNodeID={localNodeID}")
196+
raise
201197

202198
print("Calling physicalLayerDown...")
203199
physicalLayer.physicalLayerDown()

examples/example_node_memory_implementation.py

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,32 @@
2121
import os
2222
import socket
2323
import struct
24+
import xml.etree.ElementTree as ET
25+
26+
from logging import getLogger
27+
from typing import Set
2428

2529
# region same code as other examples
2630
from examples_settings import Settings
31+
from openlcb.convert import Convert
2732
from openlcb.localnode import LocalNode
2833
from openlcb.memoryspace import MemorySpace
29-
from openlcb.memorymanager import Segment # do 1st to fix path if no pip install
34+
from openlcb.memorymanager import Segment
3035
settings = Settings()
3136

3237
if __name__ == "__main__":
3338
settings.load_cli_args(docstring=__doc__)
3439
# endregion same code as other examples
3540

36-
from openlcb import emit_cast, get_config_dir, precise_sleep # noqa: E402
41+
from openlcb import assert_xml, emit_cast, formatted_ex, get_config_dir, prDim, precise_sleep # noqa: E402, E501
3742
from openlcb.tcplink.tcpsocket import TcpSocket # noqa: E402
3843

3944
from openlcb.canbus.canphysicallayergridconnect import ( # noqa: E402
4045
CanPhysicalLayerGridConnect,
4146
)
4247
from openlcb.canbus.canlink import CanLink # noqa: E402
4348
from openlcb.nodeid import NodeID # noqa: E402
44-
from openlcb.datagramservice import DatagramService # noqa: E402
49+
from openlcb.datagramservice import DatagramReadMemo, DatagramService # noqa: E402, E501
4550
from openlcb.memoryservice import MemoryService # noqa: E402
4651
from openlcb.message import Message # noqa: E402
4752
from openlcb.mti import MTI # noqa: E402
@@ -71,23 +76,15 @@
7176
print("RR, SR are raw socket interface receive and send;"
7277
" RL, SL are link interface; RM, SM are message interface")
7378

79+
me = os.path.basename(__file__)
80+
logger = getLogger(__name__)
7481

7582
# def sendToSocket(frame: CanFrame):
7683
# string = frame.encodeAsString()
7784
# print(" SR: {}".format(string.strip()))
7885
# sock.sendString(string)
7986
# physicalLayer.onFrameSent(frame)
8087

81-
def prDim(s: str):
82-
"""Show extra debugging information in darker text so special cases
83-
are readable (resets to 00 [default colors] for text after prDim).
84-
Note: 0;30;40 is non-bold dim, but that's invisible in cmd.exe.
85-
"""
86-
print("\033[1;30;40m {}\033[00m".format(s))
87-
# - `\033` starts ansi escape sequence
88-
# - 30 for gray
89-
# - 00 to reset to white
90-
9188

9289
def printFrame(frame):
9390
prDim(" RL: {}".format(frame))
@@ -98,7 +95,11 @@ def printFrame(frame):
9895

9996

10097
def printMessage(message: Message):
101-
prDim("RM: {} from {}".format(message.mti, message.source))
98+
prDim(f"RM: {message.mti} from {message.source}")
99+
# state = canLink.pollState()
100+
# if state is not CanLink.State.Permitted:
101+
# print(f"RM: - SKIPPED (state={state})")
102+
# return
102103

103104

104105
localNodeID = NodeID(settings['localNodeID'])
@@ -159,7 +160,10 @@ def printMessage(message: Message):
159160
""" # noqa: E501
160161

161162

162-
def handleDatagram(memo):
163+
assert_xml(cdi)
164+
165+
166+
def handleDatagram(memo: DatagramReadMemo):
163167
"""create a call-back to print datagram contents when received
164168
165169
Args:
@@ -169,7 +173,7 @@ def handleDatagram(memo):
169173
bool: Always False (True would mean we sent a reply to the datagram,
170174
but let the MemoryService do that).
171175
"""
172-
print(f"Datagram receive call back: {emit_cast(memo)}")
176+
print(f"Datagram receive call back: {Convert.toHex(memo.data)}")
173177
return False
174178

175179

@@ -189,24 +193,24 @@ def memoryReadFail(memo):
189193

190194

191195
# create a node and connect it update
192-
# This is a very minimal node, which just takes part in the low-level common
193-
# protocols
194-
localNodeID = NodeID(settings['localNodeID'])
196+
# This node takes part in high level protocols.
197+
# See docstring.
195198
localNode = LocalNode(
196199
localNodeID,
197-
SNIP("python-openlcb example authors",
198-
"example_node_memory_implementation",
199-
"1.0", "1.0", "example_node_memory_implementation",
200-
"python-openlcb example node with memory"),
201-
set([
200+
canLink,
201+
snip=SNIP("python-openlcb example authors",
202+
"example_node_memory_implementation",
203+
"1.0", "1.0", "example_node_memory_implementation",
204+
"python-openlcb example node with memory"),
205+
pipSet=set([
202206
PIP.SIMPLE_NODE_IDENTIFICATION_PROTOCOL,
203207
PIP.DATAGRAM_PROTOCOL,
204208
PIP.CONFIGURATION_DESCRIPTION_INFORMATION,
205209
PIP.ADCDI_PROTOCOL,
206210
PIP.MEMORY_CONFIGURATION_PROTOCOL,
207211
]),
208-
canLink
209212
)
213+
210214
memoryService.memory = localNode
211215
my_conf_dir = os.path.join(get_config_dir("python-openlcb"))
212216
backup_name = "example_node_memory_implementation.cdi.xml"
@@ -218,6 +222,10 @@ def memoryReadFail(memo):
218222
segment = localNode.getSegment(MemorySpace.CDI.value)
219223
assert isinstance(segment, Segment)
220224
assert isinstance(segment._data, (bytearray, bytes))
225+
last = localNode.getLast(0xFF)
226+
assert last is not None
227+
last = localNode.getLast(MemorySpace.CDI)
228+
assert last is not None
221229
# localNodeProcessor = LocalNodeProcessor(canLink, localNode)
222230
# canLink.registerMessageReceivedListener(localNodeProcessor.process)
223231
localNodeProcessor = localNode.localNodeProcessor
@@ -231,8 +239,7 @@ def displayOtherNodeIds(message: Message) :
231239
"""
232240
if message.mti == MTI.Verified_NodeID :
233241
print(f"[displayOtherNodeIds] Detected farNodeID {message.source}")
234-
else:
235-
print(f"[displayOtherNodeIds] {message.mti} from {message.source}")
242+
# For others, see printMessage
236243

237244

238245
canLink.registerMessageReceivedListener(displayOtherNodeIds)
@@ -252,19 +259,22 @@ def displayOtherNodeIds(message: Message) :
252259
precise_sleep(.02)
253260
print(" SL : link up")
254261
# request that nodes identify themselves so that we can print their node IDs
255-
message = Message(MTI.Verify_NodeID_Number_Global,
256-
NodeID(settings['localNodeID']), None)
262+
message = Message(MTI.Verify_NodeID_Number_Global, localNodeID, None)
257263
canLink.sendMessage(message)
258264

259265
# process resulting activity
260266
while True:
261267
count = 0
262-
count += physicalLayer.sendAll(sock, verbose=True,
263-
verbose_fn=prDim)
264-
count += physicalLayer.receiveAll(sock, verbose=settings['trace'],
265-
verbose_fn=prDim)
266-
if count < 1:
267-
precise_sleep(.01)
268-
# else skip sleep to avoid latency (port already delayed)
268+
try:
269+
count += physicalLayer.sendAll(sock, verbose=True,
270+
verbose_fn=prDim)
271+
count += physicalLayer.receiveAll(sock, verbose=settings['trace'],
272+
verbose_fn=prDim)
273+
if count < 1:
274+
precise_sleep(.01)
275+
# else skip sleep to avoid latency (port already delayed)
276+
except Exception:
277+
print(f"localNodeID={localNodeID}")
278+
raise
269279

270280
physicalLayer.physicalLayerDown()

openlcb/localnode.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from logging import getLogger
44
from typing import Union
55
from openlcb import emit_cast
6+
from openlcb.linklayer import LinkLayer
67
from openlcb.memoryspace import MemorySpace
78
from openlcb.memorymanager import MemoryManager, Segment
89
from openlcb.node import PIP, SNIP, Node
@@ -28,9 +29,14 @@ class LocalNode(Node, MemoryManager):
2829
"""A Node with its own virtual memory
2930
(emulate memory spaces such as for creating a virtual
3031
signal node with settings)"""
31-
def __init__(self, id: NodeID, snip: SNIP, pipSet: set,
32-
linkLayer: CanLink):
33-
Node.__init__(self, id, snip, pipSet)
32+
def __init__(self, id: NodeID, linkLayer: CanLink,
33+
snip: Union[SNIP, None] = None,
34+
pipSet: Union[set, None] = None):
35+
if not issubclass(type(linkLayer), LinkLayer):
36+
raise TypeError("Expected LinkLayer/subclass,"
37+
f" got a {type(linkLayer).__name__}")
38+
Node.__init__(self, id, snip=snip, pipSet=pipSet)
39+
pipSet = self.pipSet # ensured non-None by Node init.
3440
MemoryManager.__init__(self)
3541
self.cdiBackupDir = None # type: str|None
3642
self.cdi = None # type: XMLDataProcessor|None

tests/test_memorymanager.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -330,18 +330,18 @@ def virtual_send(data):
330330

331331
virtualNode = LocalNode(
332332
virtualNodeID,
333-
SNIP("python-openlcb authors",
334-
"test_memorymanager VN",
335-
"1.0", "1.0", "test_memorymanager VN",
336-
"python-openlcb virtual node with memory for test_memorymanager"), # noqa: E501
337-
set([
333+
virtualCanLink,
334+
snip=SNIP("python-openlcb authors",
335+
"test_memorymanager VN",
336+
"1.0", "1.0", "test_memorymanager VN",
337+
"python-openlcb virtual node with memory for test_memorymanager"), # noqa: E501
338+
pipSet=set([
338339
PIP.SIMPLE_NODE_IDENTIFICATION_PROTOCOL,
339340
PIP.DATAGRAM_PROTOCOL,
340341
PIP.CONFIGURATION_DESCRIPTION_INFORMATION,
341342
PIP.ADCDI_PROTOCOL,
342343
PIP.MEMORY_CONFIGURATION_PROTOCOL,
343344
]),
344-
virtualCanLink
345345
)
346346
# dgService = DatagramService(canLink)
347347
# localMemoryService = MemoryService(dgService)

0 commit comments

Comments
 (0)