Skip to content

DRAFT: Delegate state to link and flow to port (do not merge here, but to upstream) #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 100 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
066403c
Rename _charBuffer to _chunks since it is a list, and utilize the lis…
Poikilos Feb 5, 2025
e776630
Use bytearray more thoroughly in examples as required by new module c…
Poikilos Feb 5, 2025
6f622ac
Add the pyserial requirement (not same as serial package).
Poikilos Feb 5, 2025
5acb7d9
Fix Checkbutton handling.
Poikilos Feb 8, 2025
2dba727
Use a more modern interface theme when using Linux.
Poikilos Feb 8, 2025
1433105
Place examples in a tab (to prepare for other tabs).
Poikilos Feb 8, 2025
fc19615
Rename s so sock for clarity.
Poikilos Feb 8, 2025
282ed18
Add preliminary CDIFrame that shows tags (TODO: create a GUI element …
Poikilos Feb 8, 2025
2ac3655
Rename CDIFrame to CDIForm to match non-Tk (more common) naming conve…
Poikilos Feb 10, 2025
6af5a86
Rename submodule and instance to match new class name.
Poikilos Feb 10, 2025
fc09eea
Add CDIHandler and CDIForm as example subclass (FIXME: downloadCDI Us…
Poikilos Feb 14, 2025
6673e30
Show the exception and re-add sleep (from code in cdihandler.py based…
Poikilos Feb 26, 2025
35e6a27
Rename s to sock for clarity.
Poikilos Feb 27, 2025
fa8a2c0
Fix import order to take advantage of sys.path tweak (run examples fr…
Poikilos Feb 27, 2025
b9b4513
Remove lint.
Poikilos Feb 27, 2025
19ec558
Update cSpell dict.
Poikilos Feb 27, 2025
c9035ec
Improve Window position.
Poikilos Feb 27, 2025
5318b21
Add grep-able "setting nodeIdToAlias" messages (only shown if logging…
Poikilos Mar 4, 2025
e3e5fc9
Clarify a warning. Save settings on "Connect" click.
Poikilos Mar 4, 2025
76c3cc2
Improve the docstring for the ControlFrame enum.
Poikilos Mar 4, 2025
33e7973
Wait 200 ms as per Standard before allowing message sending (Wait for…
Poikilos Mar 4, 2025
5cb128a
Handle incoming branches of the CDI tree in real time.
Poikilos Mar 4, 2025
a20dd68
Update cSpell dict.
Poikilos Mar 4, 2025
4623303
Add a ValueError before getting too far (prevent obscure out-of-range…
Poikilos Mar 6, 2025
2d3a79b
Rename lastByte to processedCount since it is exclusive (last value i…
Poikilos Mar 28, 2025
97be5ec
Add precise_sleep. Remove lint (re-order imports).
Poikilos Mar 29, 2025
49cd438
Wait 200ms before finishing sendAliasAllocationSequence *and stop* cu…
Poikilos Mar 29, 2025
9c652f6
Handle connection state robustly according to standard, and check if …
Poikilos Mar 31, 2025
a008e31
Move a comment to a new docstring. Remove lint.
Poikilos Mar 31, 2025
3537a05
Add isInternal for ControlFrame analysis (to determine if frame is fr…
Poikilos Apr 1, 2025
da00ce7
Use threads to ensure receieve is occurring during alias reservation …
Poikilos Apr 1, 2025
3a367b0
Create a PortInterface to prevent threads reading and writing at the …
Poikilos Apr 2, 2025
1365ad5
Add tests for precise_sleep and formatted_ex functions added in recen…
Poikilos Apr 3, 2025
1babdf0
Add some type hints. Make GridConnectObserver more like Java Scanner …
Poikilos Apr 3, 2025
ee96089
Fix the Scanner test data.
Poikilos Apr 3, 2025
0a87a39
Move initialization out of TcpSocket constructor. Conform subclasses …
Poikilos Apr 3, 2025
741cfc6
Rename openlcb stack's own receive* methods to push* for clarity (per…
Poikilos Apr 3, 2025
b7b08ed
Fix #62: sendCanFrame adds CanFrame to deque so the thread that also …
Poikilos Apr 4, 2025
8edc8a8
Rename module according to new class name.
Poikilos Apr 4, 2025
f22e756
Fix GC test to use CanFrame send handler. Add Comments.
Poikilos Apr 8, 2025
af9ddc9
Fix test: Rename duplicate method definition. Remove "Code Spell Chec…
Poikilos Apr 8, 2025
f25a5d3
Remove placeholder test file since PhysicalLayer is an interface. Rem…
Poikilos Apr 8, 2025
5acc312
Add more states to CanLink to process alias reservation in steps (thr…
Poikilos Apr 24, 2025
88dd0c0
Improve docstring and comments for new CanLink State code.
Poikilos Apr 24, 2025
7d92188
Move some construction to PhysicalLayer (no more callback, it is the …
Poikilos Apr 26, 2025
d394479
Move some construction to PhysicalLayer (no more callback, it is the …
Poikilos Apr 26, 2025
212c5a7
Conform examples and some tests to new LinkLayer state machine. FIXME…
Poikilos Apr 27, 2025
62aff80
Represent value of enum with emit_cast for more explicit debug output.
Poikilos Apr 30, 2025
ca08834
Make a single and required frame handler in physicalLayer for enforci…
Poikilos Apr 30, 2025
8da2517
Handle the new non-blocking paradigm correctly in Dispatcher and exam…
Poikilos Apr 30, 2025
1499f4c
Fix: Run the (new) required frame handler (fix new code added for iss…
Poikilos May 5, 2025
a1cfb2d
Add debugging to diagnose port timing/flushing issues (https://github…
Poikilos May 5, 2025
8e66035
Show nodeIdToAlias after CID sequence is sent (for diagnosing port ti…
Poikilos May 5, 2025
34cf62a
Fix example_datagram_transfer: use farNodeId & localNodeId from setti…
Poikilos May 8, 2025
82018c1
Fix tests to use the new CanLink states (states created for issue #62…
Poikilos May 8, 2025
b479259
Improve a docstring and commment.
Poikilos May 8, 2025
1a597a1
Restart alias reservation if error occurs during related states as pe…
Poikilos May 15, 2025
3f7c85e
Ensure all tests are discovered (Fix invalid test left by f25a5d3 "Re…
Poikilos May 16, 2025
5727f6d
Rename Dispatcher to OpenLCBNetwork.
Poikilos May 16, 2025
f05b024
Rename a test to reflect the new class name.
Poikilos May 16, 2025
9720bdd
Rename submodule to reflect new classname.
Poikilos May 16, 2025
b163a2b
Add NotImplementedError to inform developer subclass was misused.
Poikilos May 16, 2025
c3412b7
Fix remaining CanLink tests not passing after adding new states (chec…
Poikilos May 16, 2025
894310d
Fix test_all.py.
Poikilos May 16, 2025
629b5f7
Merge branch 'delegate-state-to-link-and-flow-to-port' of https://git…
Poikilos May 19, 2025
f955a58
Rename each listener list and method to denote the specific purpose.
Poikilos May 19, 2025
7d4c6fe
Rename test class to match class being tested.
Poikilos May 19, 2025
eea8edd
Fix: undefined variable (use self._physicalLayer).
Poikilos May 19, 2025
d765d26
For consistency rename receiveListener to handleFrameReceived and cha…
Poikilos May 19, 2025
3a3c088
Rename a test to match the new method name.
Poikilos May 19, 2025
b93aa26
Add a type hint.
Poikilos May 19, 2025
71a8e54
Improve comments related to name changes etc, and remove lint.
Poikilos May 19, 2025
f0d80ae
Fix type checking for Processor. Add more type hints.
Poikilos May 19, 2025
16b95de
Add more type hints. Remove lint.
Poikilos May 19, 2025
5e842e9
Fix: Use backward-compatible typing class for List type hints.
Poikilos May 20, 2025
4fb2051
Remove lint.
Poikilos May 20, 2025
cae5664
Add more type hints.
Poikilos May 20, 2025
64fee90
Show a message during latency for clarity.
Poikilos May 20, 2025
7fbc441
Remove lint.
Poikilos May 20, 2025
a52db24
Call pollState automatically (reduces extra looping in client code su…
Poikilos May 20, 2025
c789eed
Clarify a comment.
Poikilos May 20, 2025
0daf89a
Ignore cached XML in example. Add a comment regarding caching.
Poikilos May 21, 2025
56d1fcf
Add high-level send and receive (and thereby reduce examples to simil…
Poikilos May 21, 2025
f9a9be5
Fix type-o.
Poikilos May 21, 2025
087075e
Model the network layer order consistently (socket <-> [PhysicalLayer…
Poikilos May 22, 2025
1a32b3d
Move onDisconnect to PhysicalLayer since it should be managed by sock…
Poikilos May 22, 2025
3f40bd9
Fix: check if isCanceled reservation frame (rename isBadReservation t…
Poikilos May 22, 2025
8d52f27
Remove dead code (GridConnectObserver is replaced by verbose=True in …
Poikilos May 23, 2025
6e9b1da
Fix: handle "None" return from _receive properly (and never return No…
Poikilos May 23, 2025
6bffdad
Rename an attribute for clarity (already renamed in Simulation class).
Poikilos May 23, 2025
cb3810f
Fix: Call sendAll in example_remode_nodes since not using Realtime su…
Poikilos May 23, 2025
466c369
Add more type hints.
Poikilos May 23, 2025
71544c8
Count the number of frames sent such as for testing issue #70 (needs …
Poikilos May 23, 2025
f6c6361
Fix #71: Wait for all SNIP info to arrive; Do not use socket on two d…
Poikilos May 24, 2025
6dce107
Fix docstring for example_node_implementation.py.
Poikilos May 28, 2025
6b3058f
Add more type hints.
Poikilos May 29, 2025
988f906
Use Pythonic underscore convention for private LocalNodeProcessor met…
Poikilos May 29, 2025
f8f96a5
Add registration methods for SNIP and Producer/Consumer identified. U…
Poikilos May 29, 2025
4317cd2
Use camelCase since used elsewhere. Rename GUI field fill methods to …
Poikilos May 29, 2025
01a5b81
Fix: Eliminate redundant onDisconnect (use physicalLayerDown to trigg…
Poikilos May 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ jobs:
- uses: actions/checkout@v3
- name: check tests run
run: |
python3 test_all.py
python3 -m unittest discover -s tests -p 'test_*.py'
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,5 @@ cython_debug/
/build
/doc/_autosummary
/examples/settings.json
/.venv-3.12/
/cached-cdi.xml
1 change: 1 addition & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import os
import sys

sys.path.insert(0, os.path.abspath('..'))

project = 'python-openlcb'
Expand Down
157 changes: 110 additions & 47 deletions examples/example_cdi_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,26 @@
address and port.
'''
# region same code as other examples
import copy
# from xml.sax.expatreader import AttributesImpl # only for IDE autocomplete
from examples_settings import Settings # do 1st to fix path if no pip install
from openlcb import precise_sleep
from openlcb.tcplink.tcpsocket import TcpSocket
settings = Settings()

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

from openlcb.canbus.tcpsocket import TcpSocket

from openlcb.canbus.canphysicallayergridconnect import (
from openlcb.canbus.canphysicallayergridconnect import ( # noqa:E402
CanPhysicalLayerGridConnect,
)
from openlcb.canbus.canlink import CanLink
from openlcb.nodeid import NodeID
from openlcb.datagramservice import (
from openlcb.canbus.canlink import CanLink # noqa:E402
from openlcb.nodeid import NodeID # noqa:E402
from openlcb.datagramservice import ( # noqa:E402
DatagramService,
)
from openlcb.memoryservice import (
from openlcb.memoryservice import ( # noqa:E402
MemoryReadMemo,
MemoryService,
)
Expand All @@ -42,18 +44,20 @@
# farNodeID = "02.01.57.00.04.9C"
# endregion moved to settings

s = TcpSocket()
sock = TcpSocket()
# s.settimeout(30)
s.connect(settings['host'], settings['port'])
sock.connect(settings['host'], settings['port'])


# print("RR, SR are raw socket interface receive and send;"
# " RL, SL are link interface; RM, SM are message interface")


def sendToSocket(string):
# print(" SR: {}".format(string.strip()))
s.send(string)
# def sendToSocket(frame: CanFrame):
# string = frame.encodeAsString()
# # print(" SR: {}".format(string.strip()))
# sock.sendString(string)
# physicalLayer.onFrameSent(frame)


def printFrame(frame):
Expand All @@ -80,11 +84,10 @@ def printDatagram(memo):
return False


canPhysicalLayerGridConnect = CanPhysicalLayerGridConnect(sendToSocket)
canPhysicalLayerGridConnect.registerFrameReceivedListener(printFrame)
physicalLayer = CanPhysicalLayerGridConnect()
physicalLayer.registerFrameReceivedListener(printFrame)

canLink = CanLink(NodeID(settings['localNodeID']))
canLink.linkPhysicalLayer(canPhysicalLayerGridConnect)
canLink = CanLink(physicalLayer, NodeID(settings['localNodeID']))
canLink.registerMessageReceivedListener(printMessage)

datagramService = DatagramService(canLink)
Expand All @@ -100,6 +103,9 @@ def printDatagram(memo):

# callbacks to get results of memory read

complete_data = False
read_failed = False


def memoryReadSuccess(memo):
"""Handle a successful read
Expand All @@ -113,6 +119,7 @@ def memoryReadSuccess(memo):
# print("successful memory read: {}".format(memo.data))

global resultingCDI
global complete_data

# is this done?
if len(memo.data) == 64 and 0 not in memo.data:
Expand All @@ -139,12 +146,15 @@ def memoryReadSuccess(memo):

# and process that
processXML(cdiString)
complete_data = True

# done


def memoryReadFail(memo):
global read_failed
print("memory read failed: {}".format(memo.data))
read_failed = True


#######################
Expand All @@ -161,54 +171,57 @@ def memoryReadFail(memo):


class MyHandler(xml.sax.handler.ContentHandler):
"""XML SAX callbacks in a handler object"""
def __init__(self):
self._charBuffer = bytearray()
"""XML SAX callbacks in a handler object

def startElement(self, name, attrs):
"""_summary_
Attributes:
_chunks (list[str]): Collects chunks of data.
This is implementation-specific, and not
required if streaming (parser.feed).
"""

Args:
name (_type_): _description_
attrs (_type_): _description_
"""
def __init__(self):
self._chunks = []

def startElement(self, name: str, attrs):
"""See xml.sax.handler.ContentHandler documentation."""
print("Start: ", name)
if attrs is not None and attrs :
print(" Attributes: ", attrs.getNames())

def endElement(self, name):
"""_summary_

Args:
name (_type_): _description_
"""
def endElement(self, name: str):
"""See xml.sax.handler.ContentHandler documentation."""
print(name, "content:", self._flushCharBuffer())
print("End: ", name)
pass

def _flushCharBuffer(self):
"""Decode the buffer, clear it, and return all content.
See xml.sax.handler.ContentHandler documentation.

Returns:
str: The content of the bytes buffer decoded as utf-8.
"""
s = self._charBuffer.decode("utf-8")
self._charBuffer.clear()
s = ''.join(self._chunks)
self._chunks.clear()
return s

def characters(self, data):
"""Received characters handler
def characters(self, data: str):
"""Received characters handler.
See xml.sax.handler.ContentHandler documentation.

Args:
data (Union[bytearray, bytes, list[int]]): any
data (any type accepted by bytearray extend).
"""
self._charBuffer.extend(data)
if not isinstance(data, str):
raise TypeError("Expected str, got {}".format(type(data).__name__))
self._chunks.append(data)


handler = MyHandler()


def processXML(content) :
def processXML(content: str) :
"""process the XML and invoke callbacks

Args:
Expand All @@ -218,15 +231,37 @@ def processXML(content) :
# only called when there is a null terminator, which indicates the
# last packet was reached for the requested read.
# - See memoryReadSuccess comments for details.
with open("cached-cdi.xml", 'w') as stream:
# NOTE: Actual caching should key by all SNIP info that could
# affect CDI/FDI: manufacturer, model, and version. Without
# all 3 being present in SNIP, the cache may be incorrect.
stream.write(content)
xml.sax.parseString(content, handler)
print("\nParser done")


#######################

# have the socket layer report up to bring the link layer up and get an alias
# print(" SL : link up")
canPhysicalLayerGridConnect.physicalLayerUp()
print(" QUEUE frames : link up...")
physicalLayer.physicalLayerUp()
print(" QUEUED frames : link up...waiting...")
while canLink.pollState() != CanLink.State.Permitted:
# provides incoming data to physicalLayer & sends queued:
physicalLayer.receiveAll(sock, verbose=True)
physicalLayer.sendAll(sock)

if canLink.getState() == CanLink.State.WaitForAliases:
# physicalLayer.receiveAll(sock, verbose=True)
physicalLayer.sendAll(sock)
# ^ prevent assertion error below, proceed to send.
if canLink.pollState() == CanLink.State.Permitted:
break
assert canLink.getWaitForAliasResponseStart() is not None, \
("openlcb didn't send the 7,6,5,4 CID frames (state={})"
.format(canLink.getState()))
precise_sleep(.02)
print(" SENT frames : link up")


def memoryRead():
Expand All @@ -236,8 +271,16 @@ def memoryRead():
to AME
"""
import time
time.sleep(1)

time.sleep(.21)
# ^ 200ms: See section 6.2.1 of CAN Frame Transfer Standard
# (CanLink.State.Permitted will only occur after that, but waiting
# now will reduce output & delays below in this example).
while canLink.getState() != CanLink.State.Permitted:
print("Waiting for connection sequence to complete...")
# This delay could be .2 (per alias collision), but longer to
# reduce console messages:
time.sleep(.5)
print("Requesting memory read. Please wait...")
# read 64 bytes from the CDI space starting at address zero
memMemo = MemoryReadMemo(NodeID(settings['farNodeID']), 64, 0xFF, 0,
memoryReadFail, memoryReadSuccess)
Expand All @@ -247,10 +290,30 @@ def memoryRead():
import threading # noqa E402
thread = threading.Thread(target=memoryRead)
thread.start()

previous_nodes = copy.deepcopy(canLink.nodeIdToAlias)
# process resulting activity
while True:
received = s.receive()
# print(" RR: {}".format(received.strip()))
# pass to link processor
canPhysicalLayerGridConnect.receiveString(received)
print()
print("This example will exit on failure or complete data.")
while not complete_data and not read_failed:
# In this example, requests are initiate by the
# memoryRead thread, and receiveAll actually
# receives the data from the requested memory space (CDI in this
# case) and offset (incremental position in the file/data,
# incremented by this example's memoryReadSuccess handler).
count = 0
count += physicalLayer.receiveAll(sock)
count += physicalLayer.sendAll(sock)
if canLink.nodeIdToAlias != previous_nodes:
print("nodeIdToAlias updated: {}".format(canLink.nodeIdToAlias))
if count < 1:
precise_sleep(.01)
# else skip sleep to avoid latency (port already delayed)
if canLink.nodeIdToAlias != previous_nodes:
previous_nodes = copy.deepcopy(canLink.nodeIdToAlias)

physicalLayer.physicalLayerDown()

if read_failed:
print("Read complete (FAILED)")
else:
print("Read complete (OK)")
Loading