Skip to content

Commit 6a811c1

Browse files
authored
Merge pull request #3 from phettberg/adapt_lss_handling
Adapt lss handling
2 parents 9ab9a64 + b7446ae commit 6a811c1

File tree

4 files changed

+121
-16
lines changed

4 files changed

+121
-16
lines changed

.github/workflows/workflow.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
password: ${{ secrets.PYPI_API_TOKEN }}
3535

3636
test:
37-
runs-on: ubuntu-latest
37+
runs-on: ubuntu-22.04
3838
strategy:
3939
matrix:
4040
python-version: ['3.7', '3.8', '3.9', '3.10']
@@ -51,4 +51,4 @@ jobs:
5151
pip install -r requirements_dev.txt -e .
5252
- name: Test with pytest
5353
run: |
54-
pytest -v
54+
pytest -v

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# 0.5.0
2+
3+
* fix LSS baudrate configuration handling
4+
* provide LSS store configuration functionality (bitrate and node id)
5+
16
# 0.4.12
27

38
* update object dictionary on type change in PDOs

src/durand/services/lss.py

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import TYPE_CHECKING, List, Optional
1+
from typing import TYPE_CHECKING, Callable, List, Optional
22
from enum import IntEnum
33
import logging
44
import struct
@@ -18,24 +18,49 @@ class LSSState(IntEnum):
1818

1919

2020
class LSSSlave:
21+
CiA_bit_timing_table = {
22+
0: 1000000,
23+
1: 800000,
24+
2: 500000,
25+
3: 250000,
26+
4: 125000,
27+
6: 50000,
28+
7: 20000,
29+
8: 10000,
30+
}
31+
2132
def __init__(self, node: "Node"):
2233
self._node = node
2334

2435
self._state = LSSState.WAITING
2536

2637
self._received_selective_address: List[Optional[int]] = [None] * 4
2738
self._remote_responder_address: List[Optional[int]] = [None] * 6
28-
self._fastscan_state = 0
39+
self._fastscan_state: int = 0
2940

30-
self._pending_baudrate = None
31-
self._change_baudrate_cb = None
41+
self._pending_baudrate: int | None = None
42+
self._change_baudrate_cb: Callable[[int, float], None] | None = None
43+
44+
self._store_configuration_cb: Callable[[int, int], None] | None = None
3245

3346
node.network.add_subscription(cob_id=0x7E5, callback=self.handle_msg)
3447
node.nmt.state_callbacks.add(self.on_nmt_state_update)
3548

36-
def set_baudrate_change_callback(self, cb):
49+
def set_baudrate_change_callback(self, cb: Callable[[int, float], None]):
50+
"""Defines a callback function to change bitrate. The delay is obtained from
51+
the `activate_bit_timing` command.
52+
This callback is called after the delay specified in the command and should
53+
change the bitrate of the network after waiting for the delay again.
54+
:param cb: callback function with signature (baudrate, delay)
55+
"""
3756
self._change_baudrate_cb = cb
3857

58+
def set_store_configuration_callback(self, cb: Callable[[int, int], None]):
59+
"""Defines a callback function to store new bitrate and node id persistently.
60+
:param cb: callback function with signature (baudrate, node_id)
61+
"""
62+
self._store_configuration_cb = cb
63+
3964
def on_nmt_state_update(self, state: StateEnum):
4065
if state == StateEnum.INITIALISATION:
4166
self._state = LSSState.WAITING
@@ -124,18 +149,17 @@ def cmd_configure_node_id(self, msg: bytes):
124149
)
125150

126151
def cmd_configure_bit_timing(self, msg: bytes):
127-
valid_table_entries = (0, 1, 2, 3, 4, 6, 7, 8)
128152
selector, index = msg[1:3]
129153

130154
if (
131155
selector != 0
132-
or index not in valid_table_entries
156+
or index not in self.CiA_bit_timing_table
133157
or self._change_baudrate_cb is None
134158
):
135159
self._node.network.send(0x7E4, b"\x13\x01" + bytes(6))
136160
return
137161

138-
self._pending_baudrate = index
162+
self._pending_baudrate = self.CiA_bit_timing_table[index]
139163
self._node.network.send(0x7E4, b"\x13\x00" + bytes(6))
140164

141165
def cmd_activate_bit_timing(self, msg: bytes):
@@ -146,13 +170,19 @@ def cmd_activate_bit_timing(self, msg: bytes):
146170

147171
def _change_baudrate(self, delay: float):
148172
if self._change_baudrate_cb:
149-
self._change_baudrate_cb(self._pending_baudrate)
173+
self._change_baudrate_cb(self._pending_baudrate, delay)
150174
self._pending_baudrate = None
151-
get_scheduler().add(delay, self._node.nmt.reset)
152175

153176
def cmd_store_configuration(self, _msg: bytes):
154-
# store configuration is not supported
155-
self._node.network.send(0x7E4, b"\x17\x01" + bytes(6))
177+
if self._store_configuration_cb is not None:
178+
self._store_configuration_cb(self._pending_baudrate, self._node.nmt.pending_node_id)
179+
result = 0 # 0x00 = successfully completed
180+
else:
181+
result = 1 # 0x01 = not supported
182+
183+
self._node.network.send(
184+
0x7E4, b"\x17" + result.to_bytes(1, "little") + bytes(6)
185+
)
156186

157187
def cmd_identify_remote_responders(self, msg: bytes):
158188
index = msg[0] - 0x46

tests/example_based/test_lss.py

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
""" Testing LSS service """
22

3-
from durand import Node
3+
from durand import Node, scheduler
44

5-
from ..mock_network import MockNetwork, TxMsg, RxMsg
5+
from ..mock_network import MockNetwork, RxMsg, TxMsg
6+
7+
8+
class MockScheduler(scheduler.AbstractScheduler):
9+
def add(self, delay: float, callback, args=(), kwargs=None) -> scheduler.TEntry:
10+
if kwargs is None:
11+
kwargs = {}
12+
callback(*args, **kwargs)
13+
14+
def cancel(self, entry: scheduler.TEntry):
15+
...
16+
17+
@property
18+
def lock(self):
19+
...
620

721

822
def test_global_selection():
@@ -38,3 +52,59 @@ def test_global_selection():
3852
TxMsg(0x581, "43 00 10 00 00 00 00 00"), # receive the acknowledge
3953
]
4054
)
55+
56+
57+
def test_configuration():
58+
"""Test example starts with a node with node id 0x01.
59+
After the "switch state global" request to set every responder into configuration mode,
60+
the node id is set to 2.
61+
62+
Furthermore, the baudrate is set to 500 kbit/s and the configuration is stored.
63+
When the configuration is stored, the node sends an acknowledge message.
64+
65+
At last, activate bit timing is sent to the node. It is the responsibility of its callback
66+
to reinitialize the network with the new baudrate after a delay.
67+
The callback is tested with the baudrate and delay parameters.
68+
"""
69+
70+
scheduler_ = MockScheduler()
71+
scheduler.set_scheduler(scheduler_)
72+
73+
network = MockNetwork()
74+
75+
# create the node
76+
node = Node(network, node_id=0x01)
77+
78+
def baudrate_change_callback(baudrate: int, delay: float):
79+
# This callback is supposed to reinitialize the network with the new baudrate
80+
# after the delay
81+
# In this test, we just check if the callback is called with the correct parameters
82+
assert baudrate == 500_000
83+
assert delay == 1
84+
85+
node.lss.set_baudrate_change_callback(baudrate_change_callback)
86+
87+
def store_configuration_callback(baudrate: int, node_id: int):
88+
assert baudrate == 500_000
89+
assert node_id == 2
90+
91+
node.lss.set_store_configuration_callback(store_configuration_callback)
92+
93+
network.test(
94+
[
95+
RxMsg(
96+
0x7E5, "04 01 00 00 00 00 00 00"
97+
), # switch state global to configuration state
98+
99+
RxMsg(0x7E5, "11 02 00 00 00 00 00 00"), # set node id to 2
100+
TxMsg(0x7E4, "11 00 00 00 00 00 00 00"), # receive the acknowledge (success)
101+
102+
RxMsg(0x7E5, "13 00 02 00 00 00 00 00"), # set baudrate to 500 kbit/s
103+
TxMsg(0x7E4, "13 00 00 00 00 00 00 00"), # receive the acknowledge (success)
104+
105+
RxMsg(0x7E5, "17 00 00 00 00 00 00 00"), # store configuration
106+
TxMsg(0x7E4, "17 00 00 00 00 00 00 00"), # receive the acknowledge (success)
107+
108+
RxMsg(0x7E5, "15 E8 03 00 00 00 00 00"), # activate bit timing
109+
]
110+
)

0 commit comments

Comments
 (0)