Skip to content

Commit 65ac211

Browse files
author
Christian Sandberg
committed
Add PDO support.
1 parent 9903755 commit 65ac211

File tree

9 files changed

+437
-106
lines changed

9 files changed

+437
-106
lines changed

README.rst

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
A Python implementation of the CANopen standard. The application will act as a master.
22

33

4+
Hardware support
5+
================
6+
7+
This library support multiple hardware and drivers through the
8+
`python-can <https://pypi.python.org/pypi/python-can/>`_ package.
9+
At the time of writing this includes:
10+
11+
* Kvaser
12+
* Peak CAN
13+
* USB2CAN
14+
* Anything supported by socketcan on Linux
15+
16+
It is also possible to integrate this library with a custom backend.
17+
18+
419
Examples
5-
--------
20+
========
621

722
Here are some quick examples:
823

@@ -45,7 +60,7 @@ Here are some quick examples:
4560
4661
4762
TODO
48-
----
63+
====
4964

5065
There are a lot of things that still needs implementing and fixing.
5166
Pull requests are most welcome!

canopen/common.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import logging
2+
3+
4+
logger = logging.getLogger(__name__)
5+
6+
7+
class Variable(object):
8+
9+
def __init__(self, od):
10+
self.od = od
11+
self.bits = Bits(self)
12+
13+
def get_data(self):
14+
raise NotImplementedError()
15+
16+
def set_data(self, data):
17+
raise NotImplementedError()
18+
19+
@property
20+
def data(self):
21+
if self.od.access_type == "wo":
22+
logger.warning("Variable is write only")
23+
return self.get_data()
24+
25+
@data.setter
26+
def data(self, data):
27+
if "w" not in self.od.access_type:
28+
logger.warning("Variable is read only")
29+
self.set_data(data)
30+
31+
@property
32+
def raw(self):
33+
value = self.od.decode_raw(self.data)
34+
text = "Value of %s (0x%X:%d) is %s" % (
35+
self.od.name, self.od.index,
36+
self.od.subindex, value)
37+
if value in self.od.value_descriptions:
38+
text += " (%s)" % self.od.value_descriptions[value]
39+
logger.debug(text)
40+
return value
41+
42+
@raw.setter
43+
def raw(self, value):
44+
logger.debug("Writing %s (0x%X:%d) = %s",
45+
self.od.name, self.od.index,
46+
self.od.subindex, value)
47+
self.data = self.od.encode_raw(value)
48+
49+
@property
50+
def phys(self):
51+
value = self.od.decode_phys(self.data)
52+
logger.debug("Value of %s (0x%X:%d) is %s %s",
53+
self.od.name, self.od.index,
54+
self.od.subindex, value, self.od.unit)
55+
return value
56+
57+
@phys.setter
58+
def phys(self, value):
59+
logger.debug("Writing %s (0x%X:%d) = %s",
60+
self.od.name, self.od.index,
61+
self.od.subindex, value)
62+
self.data = self.od.encode_phys(value)
63+
64+
@property
65+
def desc(self):
66+
value = self.od.decode_desc(self.data)
67+
logger.debug("Description of %s (0x%X:%d) is %s",
68+
self.od.name, self.od.index,
69+
self.od.subindex, value)
70+
return value
71+
72+
@desc.setter
73+
def desc(self, desc):
74+
logger.debug("Setting description of %s (0x%X:%d) to %s",
75+
self.od.name, self.od.index,
76+
self.od.subindex, desc)
77+
self.data = self.od.encode_desc(desc)
78+
79+
80+
class Bits(object):
81+
82+
def __init__(self, variable):
83+
self.variable = variable
84+
85+
def _get_bits(self, key):
86+
if isinstance(key, slice):
87+
bits = range(key.start, key.stop, key.step)
88+
elif isinstance(key, int):
89+
bits = [key]
90+
else:
91+
bits = key
92+
return bits
93+
94+
def __getitem__(self, key):
95+
return self.variable.od.decode_bits(self.variable.data,
96+
self._get_bits(key))
97+
98+
def __setitem__(self, key, value):
99+
self.variable.data = self.variable.od.encode_bits(
100+
self.variable.data, self._get_bits(key), value)

canopen/network.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import collections
22
import logging
3+
import threading
4+
35
import can
6+
47
from .node import Node
58

69

@@ -14,6 +17,7 @@ def __init__(self):
1417
self.listeners = [MessageDispatcher(self)]
1518
self.notifier = None
1619
self.nodes = []
20+
self.send_lock = threading.Lock()
1721
# NMT to all nodes
1822
#self.nmt = NmtNode(0)
1923

@@ -30,7 +34,8 @@ def connect(self, *args, **kwargs):
3034
self.notifier = can.Notifier(self.bus, self.listeners, 1)
3135

3236
def disconnect(self):
33-
self.notifier.running.clear()
37+
self.notifier.stop()
38+
self.bus.shutdown()
3439

3540
def add_listener(self, listener):
3641
self.listeners.append(listener)
@@ -47,14 +52,15 @@ def send_message(self, can_id, data):
4752
msg = can.Message(extended_id=False,
4853
arbitration_id=can_id,
4954
data=data)
50-
self.bus.send(msg)
55+
with self.send_lock:
56+
self.bus.send(msg)
5157

5258
def put_message(self, can_id, data, timestamp):
5359
node_id = can_id & 0x7F
5460
for node in self.nodes:
5561
if node.id == node_id or node_id == 0:
5662
node.on_message(can_id, data, timestamp)
57-
for callback in node.callbacks:
63+
for callback in node.message_callbacks:
5864
callback(can_id, data, timestamp)
5965

6066
def __getitem__(self, node_id):

canopen/nmt.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@
3939

4040
class NmtNode(object):
4141

42-
def __init__(self):
42+
def __init__(self, parent):
4343
self._state = 0
4444
self._state_received = False
4545
self.timestamp = 0
4646
self.state_change = threading.Condition()
47-
self.parent = None
47+
self.parent = parent
4848

4949
def on_heartbeat(self, can_id, data, timestamp):
5050
self.timestamp = timestamp

canopen/node.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .sdo import SdoNode
22
from .nmt import NmtNode
33
from .emcy import EmcyNode
4+
from .pdo import PdoNode
45
from . import objectdictionary
56

67

@@ -16,24 +17,25 @@ def __init__(self, node_id=1, object_dictionary=None):
1617
self.network = None
1718
self.object_dictionary = objectdictionary.ObjectDictionary()
1819
self.service_callbacks = {}
19-
self.callbacks = []
20+
self.message_callbacks = []
2021

2122
if object_dictionary:
2223
self.set_object_dictionary(object_dictionary)
2324

24-
self.sdo = SdoNode(node_id)
25-
self.sdo.parent = self
25+
self.sdo = SdoNode(self, node_id)
2626
self.register_service(SDO_RESPONSE, self.sdo.on_response)
2727

28-
self.nmt = NmtNode()
29-
self.nmt.parent = self
28+
self.pdo = PdoNode(self)
29+
self.add_callback(self.pdo.on_message)
30+
31+
self.nmt = NmtNode(self)
3032
self.register_service(HEARTBEAT, self.nmt.on_heartbeat)
3133

3234
self.emcy = EmcyNode()
3335
self.register_service(EMCY, self.emcy.on_emcy)
3436

3537
def add_callback(self, callback):
36-
self.callbacks.append(callback)
38+
self.message_callbacks.append(callback)
3739

3840
def set_node_id(self, node_id):
3941
self.id = node_id

canopen/objectdictionary/__init__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
INTEGER64 = 21
2020
UNSIGNED64 = 27
2121

22+
SIGNED_TYPES = (INTEGER8, INTEGER16, INTEGER32, INTEGER64)
23+
UNSIGNED_TYPES = (BOOLEAN, UNSIGNED8, UNSIGNED16, UNSIGNED32, UNSIGNED64)
24+
2225

2326
def import_any(filename):
2427
if filename.endswith(".eds"):
@@ -110,7 +113,7 @@ def __getitem__(self, subindex):
110113
return var
111114

112115
def __len__(self):
113-
return 255
116+
return 256
114117

115118

116119
class Variable(object):
@@ -150,9 +153,9 @@ def __eq__(self, other):
150153

151154
def __len__(self):
152155
if self.data_type in self.STRUCT_TYPES:
153-
return self.STRUCT_TYPES[self.data_type].size
156+
return self.STRUCT_TYPES[self.data_type].size * 8
154157
else:
155-
return 1
158+
return 8
156159

157160
def add_value_description(self, value, descr):
158161
self.value_descriptions[value] = descr

0 commit comments

Comments
 (0)