Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into sdo-client-fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
samsamfire committed Dec 3, 2023
2 parents 19ed1b3 + ba4fa0c commit ba1484d
Show file tree
Hide file tree
Showing 32 changed files with 479 additions and 363 deletions.
12 changes: 6 additions & 6 deletions canopen/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from .network import Network, NodeScanner
from .node import RemoteNode, LocalNode
from .sdo import SdoCommunicationError, SdoAbortedError
from .objectdictionary import import_od, export_od, ObjectDictionary, ObjectDictionaryError
from .profiles.p402 import BaseNode402
from canopen.network import Network, NodeScanner
from canopen.node import RemoteNode, LocalNode
from canopen.sdo import SdoCommunicationError, SdoAbortedError
from canopen.objectdictionary import import_od, export_od, ObjectDictionary, ObjectDictionaryError
from canopen.profiles.p402 import BaseNode402
try:
from ._version import version as __version__
from canopen._version import version as __version__
except ImportError:
# package is not installed
__version__ = "unknown"
Expand Down
4 changes: 2 additions & 2 deletions canopen/emcy.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
logger = logging.getLogger(__name__)


class EmcyConsumer(object):
class EmcyConsumer:

def __init__(self):
#: Log of all received EMCYs for this node
Expand Down Expand Up @@ -79,7 +79,7 @@ def wait(
return emcy


class EmcyProducer(object):
class EmcyProducer:

def __init__(self, cob_id: int):
self.network = None
Expand Down
2 changes: 1 addition & 1 deletion canopen/lss.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
]


class LssMaster(object):
class LssMaster:
"""The Master of Layer Setting Services"""

LSS_TX_COBID = 0x7E5
Expand Down
29 changes: 17 additions & 12 deletions canopen/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@
except ImportError:
# Do not fail if python-can is not installed
can = None
Listener = object
CanError = Exception
class Listener:
""" Dummy listener """

from .node import RemoteNode, LocalNode
from .sync import SyncProducer
from .timestamp import TimeProducer
from .nmt import NmtMaster
from .lss import LssMaster
from .objectdictionary.eds import import_from_node
from .objectdictionary import ObjectDictionary
from canopen.node import RemoteNode, LocalNode
from canopen.sync import SyncProducer
from canopen.timestamp import TimeProducer
from canopen.nmt import NmtMaster
from canopen.lss import LssMaster
from canopen.objectdictionary.eds import import_from_node
from canopen.objectdictionary import ObjectDictionary

logger = logging.getLogger(__name__)

Expand All @@ -32,7 +33,7 @@
class Network(MutableMapping):
"""Representation of one CAN bus containing one or more nodes."""

def __init__(self, bus=None):
def __init__(self, bus: can.BusABC | None = None):
"""
:param can.BusABC bus:
A python-can bus instance to re-use.
Expand Down Expand Up @@ -109,7 +110,8 @@ def connect(self, *args, **kwargs) -> "Network":
if node.object_dictionary.bitrate:
kwargs["bitrate"] = node.object_dictionary.bitrate
break
self.bus = can.interface.Bus(*args, **kwargs)
if self.bus is None:
self.bus = can.Bus(*args, **kwargs)
logger.info("Connected to '%s'", self.bus.channel_info)
self.notifier = can.Notifier(self.bus, self.listeners, 1)
return self
Expand Down Expand Up @@ -268,6 +270,9 @@ def __getitem__(self, node_id: int) -> Union[RemoteNode, LocalNode]:

def __setitem__(self, node_id: int, node: Union[RemoteNode, LocalNode]):
assert node_id == node.id
if node_id in self.nodes:
# Remove old callbacks
self.nodes[node_id].remove_network()
self.nodes[node_id] = node
node.associate_network(self)

Expand All @@ -282,7 +287,7 @@ def __len__(self) -> int:
return len(self.nodes)


class PeriodicMessageTask(object):
class PeriodicMessageTask:
"""
Task object to transmit a message periodically using python-can's
CyclicSendTask
Expand Down Expand Up @@ -359,7 +364,7 @@ def on_message_received(self, msg):
logger.error(str(e))


class NodeScanner(object):
class NodeScanner:
"""Observes which nodes are present on the bus.
Listens for the following messages:
Expand Down
4 changes: 1 addition & 3 deletions canopen/nmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import time
from typing import Callable, Optional

from .network import CanError

logger = logging.getLogger(__name__)

NMT_STATES = {
Expand Down Expand Up @@ -39,7 +37,7 @@
}


class NmtBase(object):
class NmtBase:
"""
Can set the state of the node it controls using NMT commands and monitor
the current state using the heartbeat protocol.
Expand Down
4 changes: 2 additions & 2 deletions canopen/node/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .remote import RemoteNode
from .local import LocalNode
from canopen.node.remote import RemoteNode
from canopen.node.local import LocalNode
12 changes: 5 additions & 7 deletions canopen/node/base.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from typing import TextIO, Union
from .. import objectdictionary
from canopen.objectdictionary import ObjectDictionary, import_od


class BaseNode(object):
class BaseNode:
"""A CANopen node.
:param node_id:
Expand All @@ -15,14 +15,12 @@ class BaseNode(object):
def __init__(
self,
node_id: int,
object_dictionary: Union[objectdictionary.ObjectDictionary, str, TextIO],
object_dictionary: Union[ObjectDictionary, str, TextIO],
):
self.network = None

if not isinstance(object_dictionary,
objectdictionary.ObjectDictionary):
object_dictionary = objectdictionary.import_od(
object_dictionary, node_id)
if not isinstance(object_dictionary, ObjectDictionary):
object_dictionary = import_od(object_dictionary, node_id)
self.object_dictionary = object_dictionary

self.id = node_id or self.object_dictionary.node_id
47 changes: 10 additions & 37 deletions canopen/node/local.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import logging
from typing import Dict, Union

from .base import BaseNode
from ..sdo import SdoServer, SdoAbortedError
from ..pdo import PDO, TPDO, RPDO, Map
from ..nmt import NmtSlave
from ..emcy import EmcyProducer
from ..sync import SyncProducer
from .. import objectdictionary
from canopen.node.base import BaseNode
from canopen.sdo import SdoServer, SdoAbortedError
from canopen.pdo import PDO, TPDO, RPDO
from canopen.nmt import NmtSlave
from canopen.emcy import EmcyProducer
from canopen.objectdictionary import ObjectDictionary
from canopen import objectdictionary

logger = logging.getLogger(__name__)


class LocalNode(BaseNode):

def __init__(
self,
node_id: int,
object_dictionary: Union[objectdictionary.ObjectDictionary, str],
object_dictionary: Union[ObjectDictionary, str],
):
super(LocalNode, self).__init__(node_id, object_dictionary)

Expand All @@ -31,7 +32,6 @@ def __init__(
self.nmt = NmtSlave(self.id, self)
# Let self.nmt handle writes for 0x1017
self.add_write_callback(self.nmt.on_write)
self.add_write_callback(self._pdo_update_callback)
self.emcy = EmcyProducer(0x80 + self.id)

def associate_network(self, network):
Expand All @@ -43,7 +43,6 @@ def associate_network(self, network):
self.emcy.network = network
network.subscribe(self.sdo.rx_cobid, self.sdo.on_request)
network.subscribe(0, self.nmt.on_command)
network.subscribe(SyncProducer.cob_id,self._on_sync)

def remove_network(self):
self.network.unsubscribe(self.sdo.rx_cobid, self.sdo.on_request)
Expand Down Expand Up @@ -121,36 +120,10 @@ def _find_object(self, index, subindex):
# Index does not exist
raise SdoAbortedError(0x06020000)
obj = self.object_dictionary[index]
if not isinstance(obj, objectdictionary.Variable):
if not isinstance(obj, objectdictionary.ODVariable):
# Group or array
if subindex not in obj:
# Subindex does not exist
raise SdoAbortedError(0x06090011)
obj = obj[subindex]
return obj

def _pdo_update_callback(self, index: int, subindex: int, od, data):
"""Update internal PDO data if the variable is mapped"""
try:
self.pdo[index].raw = data
except KeyError:
try:
self.pdo[index][subindex].raw = data
except KeyError:
pass


def _on_sync(self, can_id, data, timestamp) -> None:
"""Send TPDOs on sync, node should be in OPERATIONAL state"""
if not self.nmt.state == "OPERATIONAL":
logger.debug("Sync received but nothing will be sent because not in OPERATIONAL")
return
for tpdo in self.tpdo.map.values():
tpdo : Map
if tpdo.enabled:
tpdo._internal_sync_count += 1
if tpdo.trans_type <= tpdo._internal_sync_count and tpdo.trans_type <= 0xF0:
# Transmit the PDO once
tpdo.transmit()
# Reset internal sync count
tpdo._internal_sync_count = 0
28 changes: 12 additions & 16 deletions canopen/node/remote.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import logging
from typing import Union, TextIO

from ..sdo import SdoClient
from ..nmt import NmtMaster
from ..emcy import EmcyConsumer
from ..pdo import TPDO, RPDO, PDO
from ..objectdictionary import Record, Array, Variable
from .base import BaseNode

import canopen

from canopen import objectdictionary
from canopen.sdo import SdoClient, SdoCommunicationError, SdoAbortedError
from canopen.nmt import NmtMaster
from canopen.emcy import EmcyConsumer
from canopen.pdo import TPDO, RPDO, PDO
from canopen.objectdictionary import ODRecord, ODArray, ODVariable, ObjectDictionary
from canopen.node.base import BaseNode

logger = logging.getLogger(__name__)

Expand All @@ -31,7 +27,7 @@ class RemoteNode(BaseNode):
def __init__(
self,
node_id: int,
object_dictionary: Union[objectdictionary.ObjectDictionary, str, TextIO],
object_dictionary: Union[ObjectDictionary, str, TextIO],
load_od: bool = False,
):
super(RemoteNode, self).__init__(node_id, object_dictionary)
Expand Down Expand Up @@ -138,9 +134,9 @@ def __load_configuration_helper(self, index, subindex, name, value):
index=index,
name=name,
value=value)))
except canopen.SdoCommunicationError as e:
except SdoCommunicationError as e:
logger.warning(str(e))
except canopen.SdoAbortedError as e:
except SdoAbortedError as e:
# WORKAROUND for broken implementations: the SDO is set but the error
# "Attempt to write a read-only object" is raised any way.
if e.code != 0x06010002:
Expand All @@ -152,10 +148,10 @@ def __load_configuration_helper(self, index, subindex, name, value):
def load_configuration(self):
''' Load the configuration of the node from the object dictionary.'''
for obj in self.object_dictionary.values():
if isinstance(obj, Record) or isinstance(obj, Array):
if isinstance(obj, ODRecord) or isinstance(obj, ODArray):
for subobj in obj.values():
if isinstance(subobj, Variable) and subobj.writable and (subobj.value is not None):
if isinstance(subobj, ODVariable) and subobj.writable and (subobj.value is not None):
self.__load_configuration_helper(subobj.index, subobj.subindex, subobj.name, subobj.value)
elif isinstance(obj, Variable) and obj.writable and (obj.value is not None):
elif isinstance(obj, ODVariable) and obj.writable and (obj.value is not None):
self.__load_configuration_helper(obj.index, None, obj.name, obj.value)
self.pdo.read() # reads the new configuration from the driver
Loading

0 comments on commit ba1484d

Please sign in to comment.