Skip to content

Commit 124feae

Browse files
Merge pull request #29 from ymkim92/LSS
LSS Master
2 parents fca01c6 + b085683 commit 124feae

File tree

4 files changed

+256
-0
lines changed

4 files changed

+256
-0
lines changed

canopen/lss.py

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import logging
2+
import struct
3+
import time
4+
5+
try:
6+
import queue
7+
except ImportError:
8+
import Queue as queue
9+
10+
11+
logger = logging.getLogger(__name__)
12+
13+
SWITCH_MODE_GLOBAL = 0x04
14+
CONFIGURE_NODE_ID = 0x11
15+
CONFIGURE_BIT_TIMING = 0x13
16+
STORE_CONFIGURATION = 0x17
17+
INQUIRE_NODE_ID = 0x5E
18+
19+
ERROR_NONE = 0
20+
ERROR_INADMISSIBLE = 1
21+
22+
ERROR_STORE_NONE = 0
23+
ERROR_STORE_NOT_SUPPORTED = 1
24+
ERROR_STORE_ACCESS_PROBLEM = 2
25+
26+
ERROR_VENDOR_SPECIFIC = 0xff
27+
28+
29+
class LssMaster(object):
30+
"""The Master of Layer Setting Services"""
31+
32+
LSS_TX_COBID = 0x7E5
33+
LSS_RX_COBID = 0x7E4
34+
35+
NORMAL_MODE = 0x00
36+
CONFIGURATION_MODE = 0x01
37+
38+
#: Max retries for any LSS request
39+
MAX_RETRIES = 3
40+
41+
#: Max time in seconds to wait for response from server
42+
RESPONSE_TIMEOUT = 0.5
43+
44+
def __init__(self):
45+
self.network = None
46+
self._node_id = 0
47+
self._data = None
48+
self._mode_state = self.NORMAL_MODE
49+
self.responses = queue.Queue()
50+
51+
def send_switch_mode_global(self, mode):
52+
"""switch mode to CONFIGURATION_MODE or NORMAL_MODE.
53+
There is no reply for this request
54+
55+
:param int mode:
56+
CONFIGURATION_MODE or NORMAL_MODE
57+
"""
58+
# LSS messages are always a full 8 bytes long.
59+
# Unused bytes are reserved and should be initialized with 0.
60+
61+
message = [0]*8
62+
63+
if self._mode_state != mode:
64+
message[0] = SWITCH_MODE_GLOBAL
65+
message[1] = mode
66+
self._mode_state = mode
67+
self.__send_command(message)
68+
69+
def __send_inquire_node_id(self):
70+
"""
71+
:return: int current node id
72+
"""
73+
message = [0]*8
74+
message[0] = INQUIRE_NODE_ID
75+
current_node_id, nothing = self.__send_command(message)
76+
77+
return current_node_id
78+
79+
def __send_configure(self, key, value1=0, value2=0):
80+
"""Send a message to set a key with values
81+
:return: int error_code, int error_extension
82+
"""
83+
message = [0]*8
84+
message[0] = key
85+
message[1] = value1
86+
message[2] = value2
87+
error_code, error_extension = self.__send_command(message)
88+
if error_code != ERROR_NONE:
89+
error_msg = "LSS Error: %d" %error_code
90+
raise LssError(error_msg)
91+
92+
def inquire_node_id(self):
93+
"""Read the node id.
94+
CANopen node id must be within the range from 1 to 127.
95+
96+
:return: int node id
97+
0 means it is not read by LSS protocol
98+
"""
99+
self.send_switch_mode_global(self.CONFIGURATION_MODE)
100+
return self.__send_inquire_node_id()
101+
102+
def configure_node_id(self, new_node_id):
103+
"""Set the node id
104+
105+
:param int new_node_id:
106+
new node id to set
107+
"""
108+
self.send_switch_mode_global(self.CONFIGURATION_MODE)
109+
self.__send_configure(CONFIGURE_NODE_ID, new_node_id)
110+
111+
def configure_bit_timing(self, new_bit_timing):
112+
"""Set the bit timing.
113+
114+
:param int new_bit_timing:
115+
bit timing index.
116+
0: 1 MBit/sec, 1: 800 kBit/sec,
117+
2: 500 kBit/sec, 3: 250 kBit/sec,
118+
4: 125 kBit/sec 5: 100 kBit/sec,
119+
6: 50 kBit/sec, 7: 20 kBit/sec,
120+
8: 10 kBit/sec
121+
"""
122+
self.send_switch_mode_global(self.CONFIGURATION_MODE)
123+
self.__send_configure(CONFIGURE_BIT_TIMING, 0, new_bit_timing)
124+
125+
def store_configuration(self):
126+
"""Store node id and baud rate.
127+
"""
128+
self.__send_configure(STORE_CONFIGURATION)
129+
130+
def __send_command(self, message):
131+
"""Send a LSS operation code to the network
132+
133+
:param byte list message:
134+
LSS request message.
135+
"""
136+
137+
retries_left = self.MAX_RETRIES
138+
139+
message_str = "".join(["{:02x} ".format(x) for x in message])
140+
logger.info(
141+
"Sending LSS message %s", message_str)
142+
143+
response = None
144+
if not self.responses.empty():
145+
# logger.warning("There were unexpected messages in the queue")
146+
self.responses = queue.Queue()
147+
148+
while retries_left:
149+
# Wait for node to respond
150+
self.network.send_message(self.LSS_TX_COBID, message)
151+
152+
# There is no response for SWITCH_MODE_GLOBAL message
153+
if message[0] == SWITCH_MODE_GLOBAL:
154+
return
155+
156+
try:
157+
response = self.responses.get(
158+
block=True, timeout=self.RESPONSE_TIMEOUT)
159+
except queue.Empty:
160+
retries_left -= 1
161+
else:
162+
break
163+
164+
if not response:
165+
raise LssError("No LSS response received")
166+
if retries_left < self.MAX_RETRIES:
167+
logger.warning("There were some issues while communicating with the node")
168+
res_command, = struct.unpack("B", response[0:1])
169+
if res_command != message[0]:
170+
raise LssError(abort_code)
171+
else:
172+
self._mode_state = self.CONFIGURATION_MODE
173+
message1, = struct.unpack("B", response[1:2])
174+
message2, = struct.unpack("B", response[2:3])
175+
return message1, message2
176+
177+
def on_message_received(self, can_id, data, timestamp):
178+
self.responses.put(bytes(data))
179+
180+
class LssError(Exception):
181+
"""Some LSS operation failed."""

canopen/network.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from .sync import SyncProducer
1717
from .timestamp import TimeProducer
1818
from .nmt import NmtMaster
19+
from .lss import LssMaster
1920
from .objectdictionary.eds import import_from_node
2021

2122

@@ -47,6 +48,11 @@ def __init__(self, bus=None):
4748
self.nmt = NmtMaster(0)
4849
self.nmt.network = self
4950

51+
self.lss = LssMaster()
52+
self.lss.network = self
53+
self.subscribe(self.lss.LSS_RX_COBID, self.lss.on_message_received)
54+
55+
5056
def subscribe(self, can_id, callback):
5157
"""Listen for messages with a specific CAN ID.
5258
@@ -256,3 +262,4 @@ def search(self, limit=127):
256262
sdo_req = b"\x40\x00\x10\x00\x00\x00\x00\x00"
257263
for node_id in range(1, limit + 1):
258264
self.network.send_message(0x600 + node_id, sdo_req)
265+

doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Easiest way to install is to use pip_::
3838
timestamp
3939
integration
4040
profiles
41+
lss
4142

4243

4344
.. _CANopen: https://en.wikipedia.org/wiki/CANopen

doc/lss.rst

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
Layer Setting Services (LSS)
2+
================================
3+
4+
The LSS protocol is used to change the node id and baud rate
5+
of the target CANOpen device. To change these values, configuration mode should be set
6+
first. Then modify the node id and the baud rate.
7+
Once you finished the setting, the values should be saved to non-volatile memory.
8+
Finally, you can switch to normal mode.
9+
10+
To use this protocol, only one LSS slave should be connected in CAN bus.
11+
12+
.. note::
13+
Only the node id and baud rate are supported in :class:`canopen.LssMaster`
14+
15+
Examples
16+
--------
17+
18+
Switch the target device into CONFIGURATION mode::
19+
20+
network.lss.send_switch_mode_global(network.lss.CONFIGURATION_MODE)
21+
22+
You can read the current node id of the LSS slave::
23+
24+
node_id = network.lss.inquire_node_id()
25+
26+
Change the node id and baud rate::
27+
28+
network.lss.configure_node_id(node_id+1)
29+
network.lss.configure_bit_timing(2)
30+
31+
This is the table for converting the argument index of bit timing into baud rate.
32+
33+
==== ===============
34+
idx Baud rate
35+
==== ===============
36+
0 1 MBit/sec
37+
1 800 kBit/sec
38+
2 500 kBit/sec
39+
3 250 kBit/sec
40+
4 125 kBit/sec
41+
5 100 kBit/sec
42+
6 50 kBit/sec
43+
7 20 kBit/sec
44+
8 10 kBit/sec
45+
==== ===============
46+
47+
Save the configuration::
48+
49+
network.lss.store_configuration()
50+
51+
Finally, you can switch the state of target device from CONFIGURATION mode to NORMAL mode::
52+
53+
network.lss.send_switch_mode_global(network.lss.NORMAL_MODE)
54+
55+
56+
API
57+
---
58+
59+
.. autoclass:: canopen.lss.LssMaster
60+
:members:
61+
62+
63+
.. autoclass:: canopen.lss.LssError
64+
:show-inheritance:
65+
:members:
66+
67+
.. _python-can: https://python-can.readthedocs.org/en/stable/

0 commit comments

Comments
 (0)