Skip to content
Open
47 changes: 27 additions & 20 deletions canopen/sdo/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,20 +316,21 @@ def read(self, size=-1):
self.pos += length
return response[1:length + 1]

def readinto(self, b):
"""
Read bytes into a pre-allocated, writable bytes-like object b,
and return the number of bytes read.
"""
data = self.read(7)
b[:len(data)] = data
return len(data)
# For now not used, but if needed, a test is also required:
# def readinto(self, b):
# """
# Read bytes into a pre-allocated, writable bytes-like object b,
# and return the number of bytes read.
# """
# data = self.read(7)
# b[:len(data)] = data
# return len(data)

def readable(self):
return True
# def readable(self):
# return True

def tell(self):
return self.pos
# def tell(self):
# return self.pos


class WritableStream(io.RawIOBase):
Expand Down Expand Up @@ -366,6 +367,7 @@ def __init__(self, sdo_client, index, subindex=0, size=None, force_segment=False
response = sdo_client.request_response(request)
res_command, = struct.unpack_from("B", response)
if res_command != RESPONSE_DOWNLOAD:
self._done = True # prevent close() from sending a stray close segment
self.sdo_client.abort(ABORT_INVALID_COMMAND_SPECIFIER)
raise SdoCommunicationError(
f"Unexpected response 0x{res_command:02X}")
Expand Down Expand Up @@ -420,6 +422,7 @@ def write(self, b):
response = self.sdo_client.request_response(request)
res_command, = struct.unpack("B", response[0:1])
if res_command & 0xE0 != RESPONSE_SEGMENT_DOWNLOAD:
self._done = True # prevent close() from sending a stray close segment
self.sdo_client.abort(ABORT_INVALID_COMMAND_SPECIFIER)
raise SdoCommunicationError(
f"Unexpected response 0x{res_command:02X} "
Expand Down Expand Up @@ -535,6 +538,7 @@ def read(self, size=-1):
if seqno == self._ackseq + 1:
self._ackseq = seqno
else:
logger.debug('Wrong seqno')
# Wrong sequence number
response = self._retransmit()
res_command, = struct.unpack_from("B", response)
Expand Down Expand Up @@ -612,14 +616,15 @@ def close(self):
def tell(self):
return self.pos

def readinto(self, b):
"""
Read bytes into a pre-allocated, writable bytes-like object b,
and return the number of bytes read.
"""
data = self.read(7)
b[:len(data)] = data
return len(data)
# For now not used, but if needed, a test is also required:
# def readinto(self, b):
# """
# Read bytes into a pre-allocated, writable bytes-like object b,
# and return the number of bytes read.
# """
# data = self.read(7)
# b[:len(data)] = data
# return len(data)

def readable(self):
return True
Expand Down Expand Up @@ -787,6 +792,8 @@ def _retransmit(self, ackseq, blksize):
# Reset _seqno and update blksize
self._seqno = 0
self._blksize = blksize
# Reset _done so the last segment can be re-sent
self._done = False
# We are retransmitting
self._retransmitting = True
# Resend the block
Expand Down
31 changes: 24 additions & 7 deletions canopen/sdo/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

# Command, index, subindex
SDO_STRUCT = struct.Struct("<BHB")
SDO_BLOCKACK_STRUCT = struct.Struct("<BBB") # c + ackseq + new blocksize
SDO_BLOCKEND_STRUCT = struct.Struct("<BH") # c + CRC
SDO_ABORT_STRUCT = struct.Struct("<BHBI") # c +i + si + Abort code

# Command[5-7]
REQUEST_SEGMENT_DOWNLOAD = 0 << 5
REQUEST_DOWNLOAD = 1 << 5
REQUEST_UPLOAD = 2 << 5
Expand All @@ -21,18 +25,31 @@
RESPONSE_BLOCK_DOWNLOAD = 5 << 5
RESPONSE_BLOCK_UPLOAD = 6 << 5

# Block transfer sub-commands, Command[0-1]
SUB_COMMAND_MASK = 0x3
INITIATE_BLOCK_TRANSFER = 0
END_BLOCK_TRANSFER = 1
BLOCK_TRANSFER_RESPONSE = 2
START_BLOCK_UPLOAD = 3

EXPEDITED = 0x2
SIZE_SPECIFIED = 0x1
BLOCK_SIZE_SPECIFIED = 0x2
CRC_SUPPORTED = 0x4
NO_MORE_DATA = 0x1
NO_MORE_BLOCKS = 0x80
TOGGLE_BIT = 0x10
EXPEDITED = 0x2 # Expedited and segmented
SIZE_SPECIFIED = 0x1 # All transfers
BLOCK_SIZE_SPECIFIED = 0x2 # Block transfer: size specified in message
CRC_SUPPORTED = 0x4 # client/server CRC capable
NO_MORE_DATA = 0x1 # Segmented: last segment
NO_MORE_BLOCKS = 0x80 # Block transfer: last segment
TOGGLE_BIT = 0x10 # segmented toggle mask

# Block states
BLOCK_STATE_NONE = -1
BLOCK_STATE_INIT = 0 # state when entering
BLOCK_STATE_UPLOAD = 0x10 # delimiter, used for block type check
BLOCK_STATE_UP_INIT_RESP = 0x11 # state when entering, response during upload
BLOCK_STATE_UP_DATA = 0x12 # Upload Data transfer state
BLOCK_STATE_UP_END = 0x13 # End of Upload block transfers
BLOCK_STATE_DOWNLOAD = 0x20 # delimiter, used for block type check
BLOCK_STATE_DL_DATA = 0x24 # Download Data transfer state
BLOCK_STATE_DL_END = 0x25 # End of Download block transfers

# Abort codes
ABORT_TOGGLE_NOT_ALTERNATED: Final[int] = 0x0503_0000
Expand Down
26 changes: 22 additions & 4 deletions canopen/sdo/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

from typing import Union

class SdoError(Exception):
pass
Expand Down Expand Up @@ -44,20 +44,38 @@ class SdoAbortedError(SdoError):
0x08000024: "No data available",
}

def __init__(self, code: int):
def __init__(self, code: Union[int, str]):
#: Abort code
self.code = code
if isinstance(code, str):
try:
self.code = self.from_string(code)
except ValueError as e:
raise ValueError(f"Unknown SDO abort description: {code}") from e
else:
self.code = code

def __str__(self):
text = f"Code 0x{self.code:08X}"
if self.code in self.CODES:
text = text + ", " + self.CODES[self.code]
return text

def __eq__(self, other):
def __eq__(self, other:Union['SdoAbortedError', int, str]):
"""Compare two exception objects based on SDO abort code."""
if isinstance(other, int):
other = SdoAbortedError(other)
elif isinstance(other, str):
other = SdoAbortedError(other)
elif not isinstance(other, SdoAbortedError):
raise TypeError(f"Cannot compare SdoAbortedError with {type(other)}")
return self.code == other.code

def from_string(self, text: str) -> int:
code = list(SdoAbortedError.CODES.keys())[
list(SdoAbortedError.CODES.values()).index(text)
]
return SdoAbortedError(code)


class SdoCommunicationError(SdoError):
"""No or unexpected response from slave."""
Loading