Skip to content

Commit cb6638c

Browse files
Even more documentation.
1 parent 4f36fb7 commit cb6638c

File tree

13 files changed

+1637
-24
lines changed

13 files changed

+1637
-24
lines changed

canopen/common.py

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ class Variable(object):
88

99
def __init__(self, od):
1010
self.od = od
11-
#: Bits as a dictionary
12-
self.bits = Bits(self)
11+
self._bits = Bits(self)
1312

1413
def get_data(self):
1514
raise NotImplementedError()
@@ -19,7 +18,7 @@ def set_data(self, data):
1918

2019
@property
2120
def data(self):
22-
"""Bytes."""
21+
"""Byte representation of the object (:class:`bytes`)."""
2322
if self.od.access_type == "wo":
2423
logger.warning("Variable is write only")
2524
return self.get_data()
@@ -32,6 +31,27 @@ def data(self, data):
3231

3332
@property
3433
def raw(self):
34+
"""Raw representation of the object.
35+
36+
This table lists the translations between object dictionary data types
37+
and Python native data types.
38+
39+
+---------------------------+----------------------------+
40+
| Data type | Python type |
41+
+===========================+============================+
42+
| UNSIGNEDxx | :class:`int` |
43+
+---------------------------+ |
44+
| INTEGERxx | |
45+
+---------------------------+----------------------------+
46+
| BOOLEAN | :class:`bool` |
47+
+---------------------------+----------------------------+
48+
| REALxx | :class:`float` |
49+
+---------------------------+----------------------------+
50+
| VISIBLE_STRING | :class:`str` |
51+
| +----------------------------+
52+
| | ``unicode`` (Python 2) |
53+
+---------------------------+----------------------------+
54+
"""
3555
value = self.od.decode_raw(self.data)
3656
text = "Value of %s (0x%X:%d) is %s" % (
3757
self.od.name, self.od.index,
@@ -50,6 +70,12 @@ def raw(self, value):
5070

5171
@property
5272
def phys(self):
73+
"""Physical value scaled with some factor (defaults to 1).
74+
75+
On object dictionaries that support specifying a factor, this can be
76+
either a :class:`float` or an :class:`int`.
77+
Strings will be passed as is.
78+
"""
5379
value = self.od.decode_phys(self.data)
5480
logger.debug("Value of %s (0x%X:%d) is %s %s",
5581
self.od.name, self.od.index,
@@ -65,6 +91,7 @@ def phys(self, value):
6591

6692
@property
6793
def desc(self):
94+
"""Converts to and from a description of the value as a string."""
6895
value = self.od.decode_desc(self.data)
6996
logger.debug("Description of %s (0x%X:%d) is %s",
7097
self.od.name, self.od.index,
@@ -78,6 +105,11 @@ def desc(self, desc):
78105
self.od.subindex, desc)
79106
self.data = self.od.encode_desc(desc)
80107

108+
@property
109+
def bits(self):
110+
"""Access bits using integers, slices, or bit descriptions."""
111+
return self._bits
112+
81113

82114
class Bits(object):
83115

canopen/nmt.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,15 @@
3838

3939

4040
class NmtMaster(object):
41+
"""
42+
Can set the state of the node it controls using NMT commands and monitor
43+
the current state using the heartbeat protocol.
44+
"""
4145

4246
def __init__(self, parent):
4347
self._state = 0
4448
self._state_received = False
49+
#: Timestamp of last heartbeat message
4550
self.timestamp = 0
4651
self.state_change = threading.Condition()
4752
self.parent = parent
@@ -54,6 +59,7 @@ def on_heartbeat(self, can_id, data, timestamp):
5459
self.state_change.notify_all()
5560

5661
def send_command(self, code):
62+
"""Send an NMT command code to the node."""
5763
logger.info("Sending NMT command 0x%X to node %d", code, self.parent.id)
5864
self.parent.network.send_message(0, [code, self.parent.id])
5965
if code in COMMAND_TO_STATE:
@@ -62,6 +68,19 @@ def send_command(self, code):
6268

6369
@property
6470
def state(self):
71+
"""Attribute to get or set current state as a string.
72+
73+
Can be one of:
74+
75+
- INIT
76+
- PRE OPERATIONAL
77+
- STOPPED
78+
- OPERATIONAL
79+
- SLEEP
80+
- STANDBY
81+
- RESET (for setting only)
82+
- RESET COMMUNICATION (for setting only)
83+
"""
6584
if self._state in NMT_STATES:
6685
return NMT_STATES[self._state]
6786
else:

canopen/node.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,21 @@
1111

1212

1313
class Node(object):
14+
"""A CANopen slave node.
15+
16+
:param int node_id:
17+
Node ID
18+
:param object_dictionary:
19+
Object dictionary as either a path to a file or an object.
20+
:type object_dictionary: :class:`str`, :class:`canopen.ObjectDictionary`
21+
"""
1422

1523
def __init__(self, node_id=1, object_dictionary=None):
24+
#: Node ID
1625
self.id = node_id
26+
#: :class:`canopen.Network` owning the node
1727
self.network = None
28+
#: :class:`canopen.ObjectDictionary` associated with the node
1829
self.object_dictionary = objectdictionary.ObjectDictionary()
1930
self.service_callbacks = {}
2031
self.message_callbacks = []
@@ -42,6 +53,12 @@ def set_node_id(self, node_id):
4253
self.sdo.id = node_id
4354

4455
def set_object_dictionary(self, object_dictionary):
56+
"""Sets the object dictionary for the node.
57+
58+
:param object_dictionary:
59+
Object dictionary as either a path to a file or an object.
60+
:type object_dictionary: :class:`str`, :class:`canopen.ObjectDictionary`
61+
"""
4562
assert object_dictionary, "An Object Dictionary file has not been specified"
4663
if not isinstance(object_dictionary, objectdictionary.ObjectDictionary):
4764
object_dictionary = objectdictionary.import_od(object_dictionary)

canopen/objectdictionary/__init__.py

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@
2626

2727

2828
def import_od(filename):
29+
"""Parse an EDS or EPF file.
30+
31+
:param str filename:
32+
Path to object dictionary file.
33+
34+
:return:
35+
A :class:`canopen.ObjectDictionary` object.
36+
"""
2937
if filename.endswith(".eds"):
3038
from . import eds
3139
return eds.import_eds(filename)
@@ -35,18 +43,19 @@ def import_od(filename):
3543

3644

3745
class ObjectDictionary(collections.Mapping):
46+
"""Representation of the object dictionary as a Python dictionary."""
3847

3948
def __init__(self):
4049
self.indexes = collections.OrderedDict()
4150
self.names = collections.OrderedDict()
51+
#: Default bitrate if specified by file
4252
self.bitrate = None
4353

4454
def __getitem__(self, index):
45-
"""Get Object Dictionary index."""
55+
"""Get object from object dictionary by name or index."""
4656
return self.names.get(index) or self.indexes[index]
4757

4858
def __iter__(self):
49-
"""Iterates over all Object Dictionary indexes."""
5059
return iter(self.names)
5160

5261
def __len__(self):
@@ -56,14 +65,26 @@ def __contains__(self, index):
5665
return index in self.names or index in self.indexes
5766

5867
def add_object(self, obj):
68+
"""Add object to the object dictionary.
69+
70+
:param obj:
71+
Should be either one of
72+
:class:`canopen.objectdictionary.Variable`,
73+
:class:`canopen.objectdictionary.Record`, or
74+
:class:`canopen.objectdictionary.Array`.
75+
"""
5976
obj.parent = self
6077
self.indexes[obj.index] = obj
6178
self.names[obj.name] = obj
6279

6380

6481
class Record(collections.Mapping):
82+
"""Groups multiple :class:`canopen.objectdictionary.Variable` objects using
83+
subindexes.
84+
"""
6585

6686
def __init__(self, name, index):
87+
#: The :class:`canopen.ObjectDictionary` owning the record.
6788
self.parent = None
6889
self.index = index
6990
self.name = name
@@ -86,22 +107,31 @@ def __eq__(self, other):
86107
return self.index == other.index
87108

88109
def add_member(self, variable):
110+
"""Adds a :class:`canopen.objectdictionary.Variable` to the record."""
89111
variable.parent = self
90112
self.subindexes[variable.subindex] = variable
91113
self.names[variable.name] = variable
92114

93115

94116
class Array(collections.Sequence):
117+
"""An array of :class:`canopen.objectdictionary.Variable` objects using
118+
subindexes.
119+
120+
Actual length of array must be read from the node using SDO.
121+
"""
95122

96123
def __init__(self, name, index):
124+
#: The :class:`canopen.ObjectDictionary` owning the array.
97125
self.parent = None
98126
self.index = index
99127
self.name = name
100128
self.length = 255
129+
#: Variable to read to get length of array
101130
self.last_subindex = Variable(
102131
"Number of entries", index, 0)
103132
self.last_subindex.data_type = UNSIGNED8
104133
self.last_subindex.parent = self
134+
#: Each variable will be based on this with unique subindexes
105135
self.template = None
106136

107137
def __getitem__(self, subindex):
@@ -122,7 +152,7 @@ def __len__(self):
122152

123153

124154
class Variable(object):
125-
"""Object Dictionary VAR."""
155+
"""Simple variable."""
126156

127157
STRUCT_TYPES = {
128158
BOOLEAN: struct.Struct("?"),
@@ -139,16 +169,29 @@ class Variable(object):
139169
}
140170

141171
def __init__(self, name, index, subindex=0):
172+
#: The :class:`canopen.ObjectDictionary`,
173+
#: :class:`canopen.objectdictionary.Record` or
174+
#: :class:`canopen.objectdictionary.Array` owning the variable
142175
self.parent = None
176+
#: 16-bit address of the object in the dictionary
143177
self.index = index
178+
#: 8-bit sub-index of the object in the dictionary
144179
self.subindex = subindex
180+
#: String representation of the variable
145181
self.name = name
182+
#: Data type according to the standard as an :class:`int`
146183
self.data_type = UNSIGNED32
184+
#: Access type, should be "rw", "ro", "wo", or "const"
147185
self.access_type = "rw"
186+
#: Physical unit
148187
self.unit = ""
188+
#: Factor between physical unit and integer value
149189
self.factor = 1
190+
#: Minimum allowed value
150191
self.min = None
192+
#: Maximum allowed value
151193
self.max = None
194+
#: Dictionary of value descriptions
152195
self.value_descriptions = {}
153196
self.bit_definitions = {}
154197

canopen/sdo.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@
3030

3131

3232
class SdoClient(collections.Mapping):
33+
"""Handles communication with an SDO server."""
3334

3435
def __init__(self, parent, node_id):
36+
#: Node ID
3537
self.id = node_id
3638
self.parent = parent
3739
self.response = None
@@ -66,6 +68,21 @@ def send_request(self, sdo_request):
6668
return self.response
6769

6870
def upload(self, index, subindex):
71+
"""May be called to manually make a read operation.
72+
73+
:param int index:
74+
Index of object to read.
75+
:param int subindex:
76+
Sub-index of object to read.
77+
78+
:return: A data object.
79+
:rtype: bytes
80+
81+
:raises canopen.SdoCommunicationError:
82+
On unexpected response or timeout.
83+
:raises canopen.SdoAbortedError:
84+
When node responds with an error.
85+
"""
6986
request = SDO_STRUCT.pack(REQUEST_UPLOAD, index, subindex, b'')
7087
response = self.send_request(request)
7188
res_command, res_index, res_subindex, res_data = SDO_STRUCT.unpack(response)
@@ -108,6 +125,20 @@ def upload(self, index, subindex):
108125
return res_data[:length] if length is not None else res_data
109126

110127
def download(self, index, subindex, data):
128+
"""May be called to manually make a write operation.
129+
130+
:param int index:
131+
Index of object to write.
132+
:param int subindex:
133+
Sub-index of object to write.
134+
:param bytes data:
135+
Data to be written.
136+
137+
:raises canopen.SdoCommunicationError:
138+
On unexpected response or timeout.
139+
:raises canopen.SdoAbortedError:
140+
When node responds with an error.
141+
"""
111142
length = len(data)
112143
command = REQUEST_DOWNLOAD | SIZE_SPECIFIED
113144

@@ -196,6 +227,7 @@ def __contains__(self, subindex):
196227

197228

198229
class Variable(common.Variable):
230+
"""Access object dictionary variable values using SDO protocol."""
199231

200232
def __init__(self, sdo_node, od):
201233
self.sdo_node = sdo_node

0 commit comments

Comments
 (0)