Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Network is mixing roles of network access and canopen services #574

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

Closed
sveinse opened this issue Apr 26, 2025 · 4 comments
Closed

Network is mixing roles of network access and canopen services #574

sveinse opened this issue Apr 26, 2025 · 4 comments

Comments

@sveinse
Copy link
Collaborator

sveinse commented Apr 26, 2025

The class Network is an abstraction that provides access to the CAN Bus. The current implementation is also assuming additional canopen roles in the same class. The user have no way to avoid that. It instantiates NmtMaster, LssMaster, SyncProducer, TimeProducer. All of them are in the current implementation passive, as in, they do not initiate any traffic on their own. This implementation is mixing the role of being a network interface class and providing canopen master functions. As an example, if network is going to be used for passive listening or slave nodes, one would not want NmtMaster and LssMaster.

I propose moving the initializations of SyncProducer, TimeProducer, NmtMaster and LssMaster to a separate method Network.create_manager() (manager is the new official name for master in canopen) with a function argument to __init__() to prevent creation of them. With this the user can opt out of the additional services.

Something like this:

    # From class Network:
    def __init__(self, bus: Optional[can.BusABC] = None, create_manager = True):
        if create_manager:
            self.create_manager()

    def create_manager(self):
        self.sync = SyncProducer(self)
        self.time = TimeProducer(self)
        self.nmt = NmtMaster(0)
        self.nmt.network = self
        self.lss = LssMaster()
        self.lss.network = self
        self.subscribe(self.lss.LSS_RX_COBID, self.lss.on_message_received)

def __init__(self, bus: Optional[can.BusABC] = None):
"""
:param can.BusABC bus:
A python-can bus instance to re-use.
"""
#: A python-can :class:`can.BusABC` instance which is set after
#: :meth:`canopen.Network.connect` is called
self.bus = bus
#: A :class:`~canopen.network.NodeScanner` for detecting nodes
self.scanner = NodeScanner(self)
#: List of :class:`can.Listener` objects.
#: Includes at least MessageListener.
self.listeners = [MessageListener(self)]
self.notifier: Optional[can.Notifier] = None
self.nodes: Dict[int, Union[RemoteNode, LocalNode]] = {}
self.subscribers: Dict[int, List[Callback]] = {}
self.send_lock = threading.Lock()
self.sync = SyncProducer(self)
self.time = TimeProducer(self)
self.nmt = NmtMaster(0)
self.nmt.network = self
self.lss = LssMaster()
self.lss.network = self
self.subscribe(self.lss.LSS_RX_COBID, self.lss.on_message_received)

@acolomb
Copy link
Member

acolomb commented Apr 26, 2025

I don't see how it hurts to have these services instantiated in any case. A CANopen network simply has these services. Whoever uses them assumes the role of a manager / NMT master / whatever. I don't understand the Network object to represent one single device's portal to the network. It represents the network as a whole, and be it as it may, these services do exist for every node.

CANopen is a bit special in this regard because of the promiscuous nature of the bus. Most other protocols have defined messages whose format is fixed, but each instantiation is different because it specifies a sender and possibly a receiver. But in CAN, an object (explicitly not using the term message) simply exists on the bus. Any node can create it, and there is no way to tell which one actually caused the object to appear on the bus. (The extreme case is an RTR frame which can be started by the requesting device, but finished by the responder.) That also allows other nodes to track the state of an SDO server for example, simply by listening to the SDO protocol exchanges. In that regard the SDO server is also something that simply exists on the network, not actually some state explicitly bound to one node. For the client, that is more obvious, since different nodes can query the same SDO server. They need to take care not to step on each other's toes, as it would destroy the SDO server and client internal state (that's why they should listen to the SDO messages even when not sending actively). That's why the spec calls it an "SDO channel" by the way, as the client and server are just roles that any node can assume at a given time.

Therefore I find the current object hierarchy in the library mirrors that architecture quite well. Just because an object is instantiated as a member of the Network doesn't mean the node using this library is assuming any role. As long as it doesn't use the objects to act on the bus. Or is there more to it than I thought of, why it is cumbersome to have these objects in some cases?

@acolomb
Copy link
Member

acolomb commented Apr 26, 2025

Regarding terminology, I think the "network interface class" that "provides access to the CAN bus" is actually the Network.bus member. Quite precisely named.

@sveinse
Copy link
Collaborator Author

sveinse commented Apr 26, 2025

... Just because an object is instantiated as a member of the Network doesn't mean the node using this library is assuming any role. As long as it doesn't use the objects to act on the bus. Or is there more to it than I thought of, why it is cumbersome to have these objects in some cases?

Its ok. It's fine, but not perfect, to have them there as long as they are never actively transmitting on the bus on their own.

Network is a combination of network management roles (connect, subscribe, notify, etc.) and a collection of CANopen master-like roles (NmtMaster, LssMaster, etc.). My point remains that it's a bit unstructured software architecture to mix these different roles. However, the library is what it is, and we have to keep them there for the sake of the API.

Not all uses of Network is relates to a manager-like role. E.g. when implementing LocalNodes or passive listeners. When this is running on reduced systems with limited memory, its unfortunate that it creates unused objects.

I found this while writing a test case and I noticed that one gets a LSS subscription callback either if you want it or not when Network is created. The LssMaster.responses gueue will fill up with CAN messages if this library is connected to a CAN bus with an external LSS master.

@acolomb
Copy link
Member

acolomb commented Apr 27, 2025

Network is a combination of network management roles (connect, subscribe, notify, etc.) and a collection of CANopen master-like roles

I don't see that as an error or as being "unstructured". The Network object is a software-internal representation of the real, physical CANopen network. These methods you mention are necessary for linking the two together. The member objects represent services and nodes that are part of the real network, as such they exist whenever the network exists.

This may be a matter of taste, but I guess we agree that there is no real need for changing it. Thus let's focus on actual improvements and end this discussion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants