Skip to content

Commit

Permalink
Rework socket interface (#99)
Browse files Browse the repository at this point in the history
* Reworked sockets
  • Loading branch information
pylessard authored Oct 29, 2023
1 parent e470dc4 commit 1fff9e7
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 124 deletions.
2 changes: 2 additions & 0 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@
# Output file base name for HTML help builder.
htmlhelp_basename = 'canisotpdoc'

autodoc_typehints = 'description'

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
Expand Down
54 changes: 3 additions & 51 deletions doc/source/isotp/socket.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,58 +82,10 @@ Usage

To configure a socket, few methods are available

.. py:method:: socket.set_opts(optflag=None, frame_txtime=None, ext_address=None, txpad=None, rxpad=None, rx_ext_address=None, tx_stmin=None)
.. automethod:: isotp.socket.set_opts
.. automethod:: isotp.socket.set_fc_opts
.. automethod:: isotp.socket.set_ll_opts

Sets the general options of the socket

:param optflag: A list of flags modifying the protocol behaviour. Refer to :class:`socket.flags<isotp.socket.flags>`
:type optflag: int

:param frame_txtime: Frame transmission time (N_As/N_Ar) in nanoseconds.
:type frame_txtime: int

:param ext_address: The extended address to use. If not None, flags.EXTEND_ADDR will be set.
:type ext_address: int

:param txpad: The byte to use to pad the transmitted CAN messages. If not None, flags.TX_PADDING will be set
:type txpad: int

:param rxpad: The byte to use to pad the transmitted CAN messages. If not None, flags.RX_PADDING will be set
:type rxpad: int

:param rx_ext_address: The extended address to use in reception. If not None, flags.RX_EXT_ADDR will be set
:type rx_ext_address: int

:param tx_stmin: Sets the transmit separation time (time between consecutive frame) in nanoseconds. This value will override the value received through FlowControl frame. If not None, flags.FORCE_TXSTMIN will be set
:type tx_stmin: int


.. py:method:: socket.set_fc_opts(bs=None, stmin=None, wftmax=None)
Sets the flow control options of the socket

:param bs: The block size sent in the flow control message. Indicates the number of consecutive frame a sender can send before the socket sends a new flow control. A block size of 0 means that no additional flow control message will be sent (block size of infinity)
:type bs: int

:param stmin: The minimum separation time sent in the flow control message. Indicates the amount of time to wait between 2 consecutive frame. This value will be sent as is over CAN. Values from 1 to 127 means milliseconds. Values from 0xF1 to 0xF9 means 100us to 900us. 0 Means no timing requirements
:type stmin: int

:param wftmax: Maximum number of wait frame (flow control message with flow status=1) allowed before dropping a message. 0 means that wait frame are not allowed
:type wftmax: int

.. py:method:: socket.set_ll_opts(mtu=None, tx_dl=None, tx_flags=None)
Sets the link layer options. Default values are set to work with CAN 2.0. Link layer may be configure to work in CAN FD.

:param mtu: The internal CAN frame structure size. Possible values are defined in :class:`isotp.socket.LinkLayerProtocol<isotp.socket.LinkLayerProtocol>`
:type mtu: int

:param tx_dl: The CAN message payload length. For CAN 2.0, this value should be 8. For CAN FD, possible values are 8,12,16,20,24,32,48,64
:type tx_dl: int

:param tx_flags: Link layer flags.
:type tx_flags: int

-----

.. autoclass:: isotp.socket.flags
Expand Down
14 changes: 7 additions & 7 deletions isotp/address.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,25 @@ class Address:
:type addressing_mode: int
:param txid: The CAN ID for transmission. Used for these addressing mode: ``Normal_11bits``, ``Normal_29bits``, ``Extended_11bits``, ``Extended_29bits``, ``Mixed_11bits``
:type txid: int or None
:type txid: int | None
:param rxid: The CAN ID for reception. Used for these addressing mode: ``Normal_11bits``, ``Normal_29bits``, ``Extended_11bits``, ``Extended_29bits``, ``Mixed_11bits``
:type rxid: int or None
:type rxid: int | None
:param target_address: Target address (N_TA) used in ``NormalFixed_29bits`` and ``Mixed_29bits`` addressing mode.
:type target_address: int or None
:type target_address: int | None
:param source_address: Source address (N_SA) used in ``NormalFixed_29bits`` and ``Mixed_29bits`` addressing mode.
:type source_address: int or None
:type source_address: int | None
:param physical_id: The CAN ID for physical (unicast) messages. Only bits 28-16 are used. Used for these addressing modes: ``NormalFixed_29bits``, ``Mixed_29bits``. Set to standard mandated value if None.
:type: int or None
:type physical_id: int | None
:param functional_id: The CAN ID for functional (multicast) messages. Only bits 28-16 are used. Used for these addressing modes: ``NormalFixed_29bits``, ``Mixed_29bits``. Set to standard mandated value if None.
:type: int or None
:type functional_id: int | None
:param address_extension: Address extension (N_AE) used in ``Mixed_11bits``, ``Mixed_29bits`` addressing mode
:type address_extension: int or None
:type address_extension: int | None
"""

addressing_mode: AddressingMode
Expand Down
169 changes: 126 additions & 43 deletions isotp/tpsock/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ class socket:
"""
A IsoTP socket wrapper for easy configuration
:param timeout: The underlying socket timeout set with ``settimeout``. Makes the reception thread sleep
:type timeout: int
:param timeout: Passed down to the socket ``settimeout`` method. Control the blocking/non-blocking behavior of the socket
:type timeout: int | None
"""

Expand All @@ -84,7 +84,7 @@ class socket:
closed: bool
_socket: socket_module.socket

def __init__(self, timeout=0.1):
def __init__(self, timeout=None):
check_support()
from . import opts # import only if required.
self.interface = None
Expand All @@ -93,73 +93,150 @@ def __init__(self, timeout=0.1):
self.closed = False
self._socket = socket_module.socket(socket_module.AF_CAN, socket_module.SOCK_DGRAM, socket_module.CAN_ISOTP)
if timeout is not None and timeout > 0:
self._socket.settimeout(timeout)
self.settimeout(timeout)

def send(self, *args, **kwargs):
def settimeout(self, value: Optional[float]) -> None:
self._socket.settimeout(value)

def gettimeout(self) -> Optional[float]:
return self._socket.gettimeout()

def send(self, data: bytes, flags: int = 0) -> int:
if not self.bound:
raise RuntimeError("bind() must be called before using the socket")
return self._socket.send(*args, **kwargs)

def recv(self, n=mtu) -> Optional[bytes]:
return self._socket.send(data, flags)

def recv(self, bufsize: int = mtu, flags: int = 0) -> bytes:
if not self.bound:
raise RuntimeError("bind() must be called before using the socket")
try:
return self._socket.recv(n)
except socket_module.timeout:
return None
except:
raise

def set_ll_opts(self, *args, **kwargs) -> "opts.linklayer":
return self._socket.recv(bufsize, flags)

def set_ll_opts(self,
mtu: Optional[int] = None,
tx_dl: Optional[int] = None,
tx_flags: Optional[int] = None
) -> "opts.LinkLayerOpts":
"""
Sets the link layer options. Default values are set to work with CAN 2.0. Link layer may be configure to work in CAN FD.
Values of `None` will leave the parameter unchanged
:param mtu: The internal CAN frame structure size. Possible values are defined in :class:`isotp.socket.LinkLayerProtocol<isotp.socket.LinkLayerProtocol>`
:type mtu: int
:param tx_dl: The CAN message payload length. For CAN 2.0, this value should be 8. For CAN FD, possible values are 8,12,16,20,24,32,48,64
:type tx_dl: int
:param tx_flags: Link layer flags.
:type tx_flags: int
:rtype: :class:`isotp.opts.LinkLayerOpts<isotp.opts.LinkLayerOpts>`
"""
if self.bound:
raise RuntimeError("Options must be set before calling bind()")
return opts.linklayer.write(self._socket, *args, **kwargs)

def set_opts(self, *args, **kwargs) -> "opts.general":
return opts.LinkLayerOpts.write(self._socket,
mtu=mtu,
tx_dl=tx_dl,
tx_flags=tx_flags)

def set_opts(self,
optflag: Optional[int] = None,
frame_txtime: Optional[int] = None,
ext_address: Optional[int] = None,
txpad: Optional[int] = None,
rxpad: Optional[int] = None,
rx_ext_address: Optional[int] = None,
tx_stmin: Optional[int] = None) -> "opts.GeneralOpts":
"""
Sets the general options of the socket. Values of `None` will leave the parameter unchanged
:param optflag: A list of flags modifying the protocol behaviour. Refer to :class:`socket.flags<isotp.socket.flags>`
:type optflag: int
:param frame_txtime: Frame transmission time (N_As/N_Ar) in nanoseconds.
:type frame_txtime: int
:param ext_address: The extended address to use. If not None, flags.EXTEND_ADDR will be set.
:type ext_address: int
:param txpad: The byte to use to pad the transmitted CAN messages. If not None, flags.TX_PADDING will be set
:type txpad: int
:param rxpad: The byte to use to pad the transmitted CAN messages. If not None, flags.RX_PADDING will be set
:type rxpad: int
:param rx_ext_address: The extended address to use in reception. If not None, flags.RX_EXT_ADDR will be set
:type rx_ext_address: int
:param tx_stmin: Sets the transmit separation time (time between consecutive frame) in nanoseconds. This value will override the value received through FlowControl frame. If not None, flags.FORCE_TXSTMIN will be set
:type tx_stmin: int
:rtype: :class:`isotp.opts.GeneralOpts<isotp.opts.GeneralOpts>`
"""

if self.bound:
raise RuntimeError("Options must be set before calling bind()")
return opts.general.write(self._socket, *args, **kwargs)

def set_fc_opts(self, *args, **kwargs) -> "opts.flowcontrol":
return opts.GeneralOpts.write(self._socket,
optflag=optflag,
frame_txtime=frame_txtime,
ext_address=ext_address,
txpad=txpad,
rxpad=rxpad,
rx_ext_address=rx_ext_address,
tx_stmin=tx_stmin
)

def set_fc_opts(self, bs: Optional[int] = None, stmin: Optional[int] = None, wftmax: Optional[int] = None) -> "opts.FlowControlOpts":
"""
Sets the flow control options of the socket. Values of `None` will leave the parameter unchanged
:param bs: The block size sent in the flow control message. Indicates the number of consecutive frame a sender can send before the socket sends a new flow control. A block size of 0 means that no additional flow control message will be sent (block size of infinity)
:type bs: int
:param stmin: The minimum separation time sent in the flow control message. Indicates the amount of time to wait between 2 consecutive frame. This value will be sent as is over CAN. Values from 1 to 127 means milliseconds. Values from 0xF1 to 0xF9 means 100us to 900us. 0 Means no timing requirements
:type stmin: int
:param wftmax: Maximum number of wait frame (flow control message with flow status=1) allowed before dropping a message. 0 means that wait frame are not allowed
:type wftmax: int
:rtype: :class:`isotp.opts.FlowControlOpts<isotp.opts.FlowControlOpts>`
"""
if self.bound:
raise RuntimeError("Options must be set before calling bind()")
return opts.flowcontrol.write(self._socket, *args, **kwargs)
return opts.FlowControlOpts.write(self._socket, bs=bs, stmin=stmin, wftmax=wftmax)

def get_ll_opts(self, *args, **kwargs) -> "opts.linklayer":
return opts.linklayer.read(self._socket, *args, **kwargs)
def get_ll_opts(self) -> "opts.LinkLayerOpts":
return opts.LinkLayerOpts.read(self._socket)

def get_opts(self, *args, **kwargs) -> "opts.general":
return opts.general.read(self._socket, *args, **kwargs)
def get_opts(self) -> "opts.GeneralOpts":
return opts.GeneralOpts.read(self._socket)

def get_fc_opts(self, *args, **kwargs) -> "opts.flowcontrol":
return opts.flowcontrol.read(self._socket, *args, **kwargs)
def get_fc_opts(self) -> "opts.FlowControlOpts":
return opts.FlowControlOpts.read(self._socket)

def bind(self, interface: str, *args, **kwargs) -> None:
def bind(self, interface: str, address: isotp.Address) -> None:
"""
Binds the socket to an address.
If no address is provided, all additional parameters will be used to create an address. This is mainly to allow a syntax such as ``sock.bind('vcan0', rxid=0x123, txid=0x456)`` for backward compatibility.
:param interface: The network interface to use
:type interface: string
:param address: The address to bind to.
:type: :class:`isotp.Address<isotp.Address>`
:type address: :class:`isotp.Address<isotp.Address>`
"""
self.interface = interface

# == This is for syntax flexibility and also backward compatibility
address = None
if 'address' in kwargs:
address = kwargs['address']
if not isinstance(interface, str):
raise ValueError("interface must be a string")

for arg in args:
if isinstance(arg, isotp.address.Address) and address is None:
address = arg
break
if not isinstance(address, isotp.Address):
raise ValueError("address and instance of isotp.Address")

if address is None:
address = isotp.address.Address(*args, **kwargs)
# ==
self.interface = interface
self.address = address

# IsoTP sockets doesn't provide an interface to modify the target address type. We asusme physical.
Expand Down Expand Up @@ -187,10 +264,16 @@ def bind(self, interface: str, *args, **kwargs) -> None:
self.bound = True

def fileno(self) -> int:
"""Returns the socket file descriptor"""
return self._socket.fileno()

def close(self, *args, **kwargs) -> None:
self._socket.close(*args, **kwargs)
def real_socket(self) -> socket_module.socket:
"""Return the real socket object hidden by the fake isotp socket object"""
return self._socket

def close(self) -> None:
"""Closes the socket"""
self._socket.close()
self.bound = False
self.closed = True
self.address = None
Expand Down
Loading

0 comments on commit 1fff9e7

Please sign in to comment.