Skip to content

Commit 41837c4

Browse files
authored
Merge pull request #54 from Hierosoft/node-id-detection
Node id detection
2 parents 4a9f669 + 4b94c25 commit 41837c4

26 files changed

Lines changed: 571 additions & 82 deletions

examples/example_cdi_access.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,10 @@ def printDatagram(memo):
9898
# accumulate the CDI information
9999
resultingCDI = []
100100

101+
# callbacks to get results of memory read
101102

102103
def memoryReadSuccess(memo):
103-
"""createcallbacks to get results of memory read
104+
"""Handle a successful read
104105
Invoked when the memory read successfully returns,
105106
this queues a new read until the entire CDI has been
106107
returned. At that point, it invokes the XML processing below.
@@ -162,7 +163,7 @@ def __init__(self):
162163
def startElement(self, name, attrs):
163164
print("Start: ", name)
164165
if attrs is not None and attrs :
165-
print(" Atributes: ", attrs.getNames())
166+
print(" Attributes: ", attrs.getNames())
166167

167168
def endElement(self, name):
168169
print(name, "content:", self._flushCharBuffer())

examples/example_memory_length_query.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,17 @@ def printDatagram(memo):
9494

9595
memoryService = MemoryService(datagramService)
9696

97+
# callbacks to get results of memory read
9798

9899
# def memoryReadSuccess(memo):
99-
# """createcallbacks to get results of memory read
100-
#
100+
# """Handle a successful read
101+
#
101102
# Args:
102103
# memo (MemoryReadMemo): Event that was generated.
103104
# """
104105
# print("successful memory read: {}".format(memo.data))
105-
#
106-
#
106+
#
107+
#
107108
# def memoryReadFail(memo):
108109
# print("memory read failed: {}".format(memo.data))
109110

examples/example_memory_transfer.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,10 @@ def printDatagram(memo):
9494

9595
memoryService = MemoryService(datagramService)
9696

97+
# callbacks to get results of memory read
9798

9899
def memoryReadSuccess(memo):
99-
"""createcallbacks to get results of memory read
100+
"""Handle a successful read
100101
101102
Args:
102103
memo (MemoryReadMemo): Event that was generated.

examples/example_node_implementation.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
the address and port). Defaults to a hard-coded test
1010
address and port.
1111
'''
12+
import socket
13+
1214
# region same code as other examples
1315
from examples_settings import Settings # do 1st to fix path if no pip install
1416
settings = Settings()
@@ -44,7 +46,12 @@
4446

4547
s = TcpSocket()
4648
# s.settimeout(30)
47-
s.connect(settings['host'], settings['port'])
49+
try:
50+
s.connect(settings['host'], settings['port'])
51+
except socket.gaierror:
52+
print("Failure accessing {}:{}"
53+
.format(settings.get('host'), settings.get('port')))
54+
raise
4855

4956
print("RR, SR are raw socket interface receive and send;"
5057
" RL, SL are link interface; RM, SM are message interface")
@@ -94,7 +101,8 @@ def printDatagram(memo):
94101
memoryService = MemoryService(datagramService)
95102

96103

97-
# createcallbacks to get results of memory read
104+
# callbacks to get results of memory read
105+
98106
def memoryReadSuccess(memo):
99107
print("successful memory read: {}".format(memo.data))
100108

examples/example_string_serial_interface.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'''
22
Example of raw socket communications over the physical connection, in this case
3-
a socket.
3+
a serial port.
44
55
Usage:
66
python3 example_string_interface.py [host|host:port]

examples/example_tcp_message_interface.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,25 +57,25 @@ def printMessage(msg):
5757
print("RM: {} from {}".format(msg, msg.source))
5858

5959

60-
tcpLinklayer = TcpLink(NodeID(100))
61-
tcpLinklayer.registerMessageReceivedListener(printMessage)
62-
tcpLinklayer.linkPhysicalLayer(sendToSocket)
60+
tcpLinkLayer = TcpLink(NodeID(100))
61+
tcpLinkLayer.registerMessageReceivedListener(printMessage)
62+
tcpLinkLayer.linkPhysicalLayer(sendToSocket)
6363

6464
#######################
6565

6666
# have the socket layer report up to bring the link layer up and get an alias
6767
print(" SL : link up")
68-
tcpLinklayer.linkUp()
68+
tcpLinkLayer.linkUp()
6969

7070
# send an VerifyNodes message to provoke response
7171
message = Message(MTI.Verify_NodeID_Number_Global,
7272
NodeID(settings['localNodeID']), None)
7373
print("SM: {}".format(message))
74-
tcpLinklayer.sendMessage(message)
74+
tcpLinkLayer.sendMessage(message)
7575

7676
# process resulting activity
7777
while True:
7878
received = s.receive()
7979
print(" RR: {}".format(received))
8080
# pass to link processor
81-
tcpLinklayer.receiveListener(received)
81+
tcpLinkLayer.receiveListener(received)

examples/examples_gui.py

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from collections import OrderedDict
2121

2222
from examples_settings import Settings
23+
from openlcb.tcplink.mdnsconventions import id_from_tcp_service_name
2324

2425
zeroconf_enabled = False
2526
try:
@@ -90,7 +91,7 @@ class MainForm(ttk.Frame):
9091
key and the Button instance is the value.
9192
example_modules (OrderedDict[str]): The example
9293
module name is the key, and the full path is the value. If
93-
examples are made modular, the value will not be nessary, but
94+
examples are made modular, the value will not be necessary, but
9495
for now just run the file in another Python instance (See
9596
run_example method).
9697
@@ -291,7 +292,10 @@ def gui(self, parent):
291292
self.row = 0
292293
self.add_field("service_name",
293294
"TCP Service name (optional, sets host&port)",
294-
gui_class=ttk.Combobox, tooltip="")
295+
gui_class=ttk.Combobox, tooltip="",
296+
command=self.set_id_from_name,
297+
command_text="Copy digits to Far Node ID")
298+
self.fields["service_name"].button.configure(state=tk.DISABLED)
295299
self.fields["service_name"].var.trace('w', self.on_service_name_change)
296300
self.add_field("host", "IP address/hostname",
297301
command=self.detect_hosts,
@@ -334,8 +338,8 @@ def gui(self, parent):
334338
self.add_field(
335339
"farNodeID", "Far Node ID",
336340
gui_class=ttk.Combobox,
337-
# command=self.detect_nodes, # TODO: finish detect_nodes & use
338-
# command_text="Detect", # TODO: finish detect_nodes & use
341+
command=self.detect_nodes, # TODO: finish detect_nodes & use
342+
command_text="Detect", # TODO: finish detect_nodes & use
339343
)
340344

341345
self.add_field(
@@ -345,6 +349,17 @@ def gui(self, parent):
345349
command_text="Default",
346350
)
347351

352+
self.add_field(
353+
"timeout", "Remote nodes timeout (seconds)",
354+
gui_class=ttk.Entry,
355+
)
356+
357+
self.add_field(
358+
"trace", "Remote nodes logging",
359+
gui_class=ttk.Checkbutton,
360+
text="Trace",
361+
)
362+
348363
# The status widget is the only widget other than self which
349364
# is directly inside the parent widget (forces it to bottom):
350365
self.statusLabel = ttk.Label(self.parent)
@@ -362,28 +377,45 @@ def gui(self, parent):
362377
# self.rowconfigure(row, weight=1)
363378
# self.rowconfigure(self.row_count-1, weight=1) # make last row expand
364379

380+
def set_id_from_name(self):
381+
id = self.get_id_from_name(update_button=True)
382+
if not id:
383+
return
384+
self.fields['farNodeID'].var.set(id)
385+
386+
def get_id_from_name(self, update_button=False):
387+
lcc_id = id_from_tcp_service_name(self.fields['service_name'].var.get())
388+
if update_button:
389+
if not lcc_id:
390+
self.fields["service_name"].button.configure(state=tk.DISABLED)
391+
else:
392+
self.fields["service_name"].button.configure(state=tk.NORMAL)
393+
return lcc_id
394+
365395
def on_service_name_change(self, index, value, op):
366396
key = self.fields['service_name'].get()
397+
_ = self.get_id_from_name(update_button=True)
367398
info = self.detected_services.get(key)
368399
if not info:
369400
# The user may be typing, so don't spam screen with messages,
370401
# just ignore incomplete entries.
371402
return
372403
# We got info, so use the info to set *other* fields:
373-
self.fields['host'].set(info['server'])
404+
self.fields['host'].set(info['server'].rstrip("."))
405+
# ^ Remove trailing "." to prevent getaddrinfo failed.
374406
self.fields['port'].set(info['port'])
375407
self.set_status("Hostname & Port have been set ({server}:{port})"
376408
.format(**info))
377409

378-
def add_field(self, key, text, gui_class=ttk.Entry, command=None,
379-
command_text=None, tooltip=None):
410+
def add_field(self, key, caption, gui_class=ttk.Entry, command=None,
411+
command_text=None, tooltip=None, text=None):
380412
"""Generate a uniform data field that may or may not affect a setting.
381413
382414
The row(s) for the data field will start at self.row, and self.row will
383415
be incremented for (each) row added by this function.
384416
385417
Args:
386-
text (str): Text for the label.
418+
caption (str): Text for the label.
387419
key (str): Key to store the widget.
388420
gui_class (Misc): The ttk widget class or function to use to create
389421
the data entry widget (field.widget).
@@ -392,6 +424,8 @@ def add_field(self, key, text, gui_class=ttk.Entry, command=None,
392424
tooltip (str, optional): Add a tooltip tk.Label as field.tooltip
393425
with this text. Added even if "". Defaults to None (not added
394426
in that case).
427+
text (str, optional): Text on the input widget itself (only
428+
applies to gui_class Checkbutton).
395429
"""
396430
# self.row should already be set to an empty row.
397431
self.column = 0 # Return to beginning of row
@@ -404,16 +438,27 @@ def add_field(self, key, text, gui_class=ttk.Entry, command=None,
404438
raise ValueError("command is required for command_caption.")
405439

406440
field = DataField()
407-
field.label = ttk.Label(self, text=text)
441+
field.label = ttk.Label(self, text=caption)
408442
field.label.grid(row=self.row, column=self.column, **self.grid_args)
409443
self.host_column = self.column
410444
self.column += 1
411445
self.fields[key] = field
412-
field.var = tk.StringVar(self.w1)
413-
field.widget = gui_class(
414-
self,
415-
textvariable=field.var,
416-
)
446+
if gui_class in (ttk.Checkbutton, tk.Checkbutton):
447+
field.var = tk.BooleanVar(self.w1)
448+
# field.var.set(True)
449+
field.widget = gui_class(
450+
self,
451+
# onvalue=True,
452+
# offvalue=False,
453+
variable=field.var,
454+
text=text,
455+
)
456+
else:
457+
field.var = tk.StringVar(self.w1)
458+
field.widget = gui_class(
459+
self,
460+
textvariable=field.var,
461+
)
417462
field.widget.grid(row=self.row, column=self.column, **self.grid_args)
418463
self.column += 1
419464

@@ -550,14 +595,14 @@ def main():
550595
window_h,
551596
)) # WxH+X+Y format
552597
root.minsize = (window_w, window_h)
553-
mainform = MainForm(root)
554-
mainform.master.title("Python OpenLCB Examples")
598+
main_form = MainForm(root)
599+
main_form.master.title("Python OpenLCB Examples")
555600
try:
556-
mainform.mainloop()
601+
main_form.mainloop()
557602
finally:
558-
if mainform.zeroconf:
559-
mainform.zeroconf.close()
560-
mainform.zeroconf = None
603+
if main_form.zeroconf:
604+
main_form.zeroconf.close()
605+
main_form.zeroconf = None
561606
return 0
562607

563608

examples/examples_settings.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"timeout": 0.5, # Such as for example_remote_nodes.py
2828
"device": "/dev/cu.usbmodemCC570001B1",
2929
# ^ serial device such as for example_string_serial_interface.py
30-
# "service_name": "", # mdns service name (maybe more than 1 on host)
30+
# "service_name": "", # MDNS service name (maybe more than 1 on host)
3131
# ^ service_name isn't saved here, since it is not used by LCC
3232
# examples (See examples_gui instead, which finds it dynamically,
3333
# and where it is associated with a host and port).
@@ -140,6 +140,11 @@ def __next__(self):
140140
self._iterate_i += 1
141141
return key
142142

143+
def get(self, key):
144+
if key not in self:
145+
return None
146+
return self[key]
147+
143148
def keys(self):
144149
"""Return the keys iterator from the settings dictionary.
145150

openlcb/__init__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import re
2+
3+
4+
hex_pairs_rc = re.compile(r"^([0-9A-Fa-f]{2})+$")
5+
# {2}: Exactly two characters found (only match if pair)
6+
# +: at least one match plus 0 or more additional matches
7+
8+
9+
def only_hex_pairs(value):
10+
"""Check if string contains only machine-readable hex pairs.
11+
See openlcb.conventions submodule for LCC ID dot notation
12+
functions (less restrictive).
13+
"""
14+
return hex_pairs_rc.fullmatch(value)
15+
16+
17+
def emit_cast(value):
18+
"""Show type and value, such as for debug output."""
19+
return "{}({})".format(type(value).__name__, repr(value))

0 commit comments

Comments
 (0)