christiansandberg / canopen

CANopen for Python
http://canopen.readthedocs.io/
MIT License
442 stars 196 forks source link

Read PDO configuration from EDS OD #273

Closed sveinse closed 5 months ago

sveinse commented 3 years ago

In my setup I read the PDO configuration from the remote node. To save bus traffic, how can I read the same PDO configuration from the EDS file? This remote note has static PDO configuration, so there no need to run SDOs to the remote to get it. I believe the EDS file contain the information.

    # Create the node object and load the OD
    node: canopen.RemoteNode = network.add_node(nodeid, 'PulseOD.eds')

    # Read the PDO configuration from the target
    await node.tpdo.aread()
    await node.rpdo.aread()
christiansandberg commented 2 years ago

I think it is already supported. See subscribe().

sveinse commented 2 years ago

Maybe I'm misunderstanding the API or doing something wrong, but if I call node.rpdo.subscribe() instead of node.rpdo.read() and vice versa for the tpdo, I still cannot access the rpdo by name, e.g. node.rpdo[1]['MotorSpeed']. If I do I get KeyError: 'MotorSpeed not found in map. Valid entries are '. But if I do the sdo access via node.rpdo.read() it sets up the proper mapping by name. I had hoped I could use the EDS to configure the mapping on the pdos without needing to ask the remote by sdo.

edit I shouldn't rule out that the EDS is not correct. I'm coming from CanFestival, which has their own OD format and we haven't validated the EDS contents. But the pdo mapping seems to be present in the EDS at least.

acolomb commented 2 years ago

Does your EDS actually contain values for the PDO configuration objects? It usually just describes the object dictionary, possibly with default values (like a class definition). It doesn't contain the actual values of the objects (as in a specific class instance for a specific node). Can you show an example of how that data is described in the EDS?

Usually you can just declare your PDOs in Python code using .add_variable() and such, then use .subscribe() to start listening using that fixed configuration. I'm doing the same to save lots of SDO traffic on startup.

If you have a different type of OD definition file (non-EDS), maybe you could implement an importer for it?

sveinse commented 2 years ago

Yes, the EDS contains the RPDO mapping in 1600, and TPDO mapping in 1A00. The DefaultValue contains an encode of the OD parameter. In this example DefaultValue = 538050576 which equals to 0x20120010, where the upper 16-bits corresponds to the 2012 parameter. Hence the first parameter in the PDO mapping would be rpdo[1]['MotorSpeed']

[1600]
ParameterName=Receive PDO 1 Mapping
ObjectType=0x8
SubNumber=7

[1600sub0]
ParameterName=Number of Entries
ObjectType=0x7
DataType=0x0005
AccessType=rw
DefaultValue=6
PDOMapping=0

[1600sub1]
ParameterName=PDO 1 Mapping for an application object 1
ObjectType=0x7
DataType=0x0007
AccessType=rw
DefaultValue=538050576
PDOMapping=0

[2012]
ParameterName=MotorSpeed
ObjectType=0x7
DataType=0x0006
AccessType=rw
DefaultValue=0
PDOMapping=1

I'm not technically looking for the values, only the mapping of the object names. Which is what the above is providing. If using .add_variable() there might be a discrepancy between what is declared in the py code as to what the remote is actually using. The EDS however, is authoritative.

sveinse commented 2 years ago

I think I've found a solution, https://github.com/sveinse/canopen-asyncio/commit/5631a03a5ff2575638e5e33cbbd6eed125896caf.

Basically added an from_od option to pdo/base.py Map.read():

    def read(self, from_od=False) -> None:
        """Read PDO configuration for this map using SDO or from OD."""

If from_od is True, the values are read from var.od.default instead of doing the normal var.raw SDO read. This works on our devices, and I've verified the ODs defaults with the remote values and it matches completely. This saves lots of SDOs and I don't need to use .add_variable() with the potential risk of getting the py code of sync with the OD.

dominikfigueroa commented 2 years ago

@sveinse Thanks so much for your helpful input on this thread! I was also confused as to why subscribe() wouldn't populate pdo object info like name (therefore not allowing us to access objects by name). It seems that is just a limitation of the subscribe() function as of now.

However, I merged in the changes you showed on https://github.com/sveinse/canopen-asyncio/commit/5631a03a5ff2575638e5e33cbbd6eed125896caf into my local copy of python-canopen and it works perfectly! Now, by using node.rpdo.read(from_od=True) I am still able to objects in the OD by name -- just the same as if I used the original read() function. But now, there is no SDO flood during the boot process.

Highly appreciate you sharing your work. I personally think this work is worth opening a PR for in this repo.