KaCanOpen
 All Classes Functions Variables Typedefs Enumerations Pages
sdo.cpp
1 /*
2  * Copyright (c) 2015, Thomas Keh
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright
12  * notice, this list of conditions and the following disclaimer in the
13  * documentation and/or other materials provided with the distribution.
14  *
15  * 3. Neither the name of the copyright holder nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include "sdo.h"
33 #include "core.h"
34 #include "logger.h"
35 #include "sdo_error.h"
36 #include "global_config.h"
37 
38 #include <iostream>
39 #include <chrono>
40 #include <cassert>
41 #include <future>
42 
43 namespace kaco {
44 
45 SDO::SDO(Core& core)
46  : m_core(core) {
47  m_send_and_wait_receivers.fill(received_unassigned_sdo);
48 }
49 
50 void SDO::download(uint8_t node_id, uint16_t index, uint8_t subindex, uint32_t size, const std::vector<uint8_t>& data) {
51 
52  assert(size>0);
53  assert(data.size()>=size);
54 
55  if (size<=4) {
56 
57  // expedited transfer
58 
59  uint8_t command = Flag::initiate_download_request
60  | size_flag(size)
61  | Flag::size_indicated
62  | Flag::expedited_transfer;
63 
64  SDOResponse response = send_sdo_and_wait(command, node_id, index, subindex, {{
65  data[0],
66  (size>1) ? data[1] : (uint8_t) 0,
67  (size>2) ? data[2] : (uint8_t) 0,
68  (size>3) ? data[3] : (uint8_t) 0
69  }});
70 
71  if (response.failed()) {
72  throw sdo_error(response.get_data());
73  }
74 
75  } else {
76 
77  // segmented transfer
78  throw sdo_error(sdo_error::type::segmented_download);
79 
80  }
81 
82 }
83 
84 std::vector<uint8_t> SDO::upload(uint8_t node_id, uint16_t index, uint8_t subindex) {
85 
86  std::vector<uint8_t> result;
87 
88  uint8_t command = Flag::initiate_upload_request;
89  SDOResponse response = send_sdo_and_wait(command, node_id, index, subindex, {{0,0,0,0}});
90 
91  if (response.failed()) {
92  throw sdo_error(response.get_data());
93  }
94 
95  if (response.command & Flag::expedited_transfer) {
96 
97  for (unsigned i=0; i<response.get_length(); ++i) {
98  result.push_back(response.data[3+i]);
99  }
100 
101  } else {
102 
103  if ((response.command & Flag::size_indicated)==0) {
104  throw sdo_error(sdo_error::type::response_command,"Command "+std::to_string(response.command)+" is reserved for further use.");
105  }
106 
107  // TODO: the &0xFF is necessary (tested with sysWOORXX IO-X1) but I haven't found this restriction in CiA301...
108  uint32_t original_size = response.get_data() & 0xFFFF;
109  uint32_t size = original_size;
110  bool more_segments = true;
111  uint8_t toggle_bit = 0;
112 
113  // request data
114  while (more_segments) {
115 
116  if (size==0) {
117  WARN("[SDO::upload] [Restrictive] Uploaded already all "<<original_size<<" bytes but there are still more segments. Ignore...");
118  return result;
119  }
120 
121  uint8_t command = Flag::upload_segment_request
122  | toggle_bit;
123  SDOResponse response = send_sdo_and_wait(command, node_id, 0, 0, {{0,0,0,0}});
124 
125  if (response.failed()) {
126  throw sdo_error(response.get_data());
127  }
128 
129  if (toggle_bit != (response.command & Flag::toggle_bit)) {
130  throw sdo_error(sdo_error::type::response_toggle_bit);
131  }
132 
133  unsigned i=0;
134  while(i<7 && size>0) {
135  result.push_back(response.data[i]);
136  ++i;
137  --size;
138  }
139 
140  toggle_bit = (toggle_bit>0) ? 0 : Flag::toggle_bit;
141  more_segments = (response.command & Flag::no_more_segments)==0;
142 
143  }
144 
145  if (size>0) {
146  WARN("[SDO::upload] [Restrictive] Uploaded just "<<(original_size-size)<<" of "<<original_size<<" bytes but there are no more segments. Ignore...");
147  return result;
148  }
149 
150  }
151 
152  return result;
153 
154 }
155 
157 
158  SDOResponse response;
159  response.node_id = message.get_node_id();
160  response.command = message.data[0];
161 
162  for (unsigned i=0; i<7; ++i) {
163  response.data[i] = message.data[1+i];
164  }
165 
166  DEBUG_LOG("Received SDO (transmit/server) from node "<<(unsigned)response.node_id<<". thread=" << std::this_thread::get_id()) ;
167 
168  // This calls either a callback which was registered by send_sdo_and_wait or received_unassigned_sdo().
169  {
170  std::lock_guard<std::mutex> scoped_lock(m_send_and_wait_receiver_mutexes[response.node_id]);
171  m_send_and_wait_receivers[response.node_id](response);
172  }
173 
174  // TODO: Also call custom callbacks, not only internal ones.
175 
176 }
177 
178 SDOResponse SDO::send_sdo_and_wait(uint8_t command, uint8_t node_id, uint16_t index, uint8_t subindex, const std::array<uint8_t,4>& data) {
179 
180  DEBUG_LOG_EXHAUSTIVE("SDO::send_sdo_and_wait: thread=" << std::this_thread::get_id() << " node_id=" << node_id
181  << " command=" << command << " index=" << index << " subindex=" << subindex << " START.");
182 
183  // We lock this method so requests and responses to/from the same node are not mixed up
184  // and m_send_and_wait_receivers[node_id] manipulation is safe.
185  // SDO callbacks are specific to their node id.
186  // assert: m_send_and_wait_mutex.size() == 256 > std::numeric_limits<uint8_t>::max()
187  std::lock_guard<std::mutex> scoped_lock(m_send_and_wait_mutex[node_id]);
188 
189  std::promise<SDOResponse> received_promise;
190  std::future<SDOResponse> received_future = received_promise.get_future();
191 
192  {
193  std::lock_guard<std::mutex> scoped_lock(m_send_and_wait_receiver_mutexes[node_id]);
194  m_send_and_wait_receivers[node_id] = [&] (SDOResponse response) {
195  // This is only called if the response comes from the correct node
196  // (process_incoming_message takes care of this).
197  // The reference to received_promise is assured to be alive
198  // since the receiver is removed before this method returns.
199  received_promise.set_value(response);
200  };
201  }
202 
203  // send message
204  Message message;
205  message.cob_id = 0x600+node_id;
206  message.rtr = false;
207  message.len = 8;
208  message.data[0] = command;
209  message.data[1] = index & 0xFF;
210  message.data[2] = (index >> 8) & 0xFF;
211  message.data[3] = subindex;
212  message.data[4] = data[0];
213  message.data[5] = data[1];
214  message.data[6] = data[2];
215  message.data[7] = data[3];
216  m_core.send(message);
217 
218  DEBUG_LOG_EXHAUSTIVE("SDO::send_sdo_and_wait: thread=" << std::this_thread::get_id() << " node_id=" << node_id
219  << " command=" << command << " index=" << index << " subindex=" << subindex << " WAIT.");
220 
221  const auto timeout = std::chrono::milliseconds(Config::sdo_response_timeout_ms);
222  const auto status = received_future.wait_for(timeout);
223 
224  // remove receiver
225  {
226  std::lock_guard<std::mutex> scoped_lock(m_send_and_wait_receiver_mutexes[node_id]);
227  m_send_and_wait_receivers[node_id] = received_unassigned_sdo;
228  }
229 
230  if (status == std::future_status::timeout) {
231  DEBUG_LOG_EXHAUSTIVE("SDO::send_sdo_and_wait: thread=" << std::this_thread::get_id() << " node_id=" << node_id
232  << " command=" << command << " index=" << index << " subindex=" << subindex << " TIMEOUT.");
233  throw sdo_error(sdo_error::type::response_timeout, "Timeout was "+std::to_string(timeout.count())+"ms.");
234  }
235 
236  DEBUG_LOG_EXHAUSTIVE("SDO::send_sdo_and_wait: thread=" << std::this_thread::get_id() << " node_id=" << node_id
237  << " command=" << command << " index=" << index << " subindex=" << subindex << " DONE.");
238 
239  return received_future.get();
240 
241 }
242 
243 void SDO::received_unassigned_sdo(SDOResponse response) {
244  DEBUG_LOG("Received unassigned SDO (transmit/server):");
245  DEBUG(response.print();)
246 }
247 
248 
249 uint8_t SDO::size_flag(uint8_t size) {
250  assert(size>0);
251  assert(size<=4);
252  switch(size) {
253  case 1: return 0x0C;
254  case 2: return 0x08;
255  case 3: return 0x04;
256  case 4: return 0x00;
257  }
258  assert(false && "Dead code!");
259  return 0;
260 }
261 
262 }
uint16_t cob_id
Message ID aka COB-ID.
Definition: message.h:42
uint8_t len
Message's length (0 to 8)
Definition: message.h:48
SDO(Core &core)
Constructor.
Definition: sdo.cpp:45
void print() const
Prints the response to command line.
void send(const Message &message)
Sends a message.
Definition: core.cpp:243
uint8_t get_length() const
Returns the number of data bytes.
uint8_t failed() const
Check if the transfer failed.
void download(uint8_t node_id, uint16_t index, uint8_t subindex, uint32_t size, const std::vector< uint8_t > &bytes)
SDO download: Write value into remote device's object dictionary.
Definition: sdo.cpp:50
This type of exception is thrown when there are problems while accessing devices via SDO...
Definition: sdo_error.h:48
This struct contains the response of a SDO request. Note that there are two types of response - segme...
Definition: sdo_response.h:43
SDOResponse send_sdo_and_wait(uint8_t command, uint8_t node_id, uint16_t index, uint8_t subindex, const std::array< uint8_t, 4 > &data)
Sends an SDO message and waits for the response.
Definition: sdo.cpp:178
This class implements the Core of KaCanOpen It communicates with the CAN driver, sends CAN messages a...
Definition: core.h:59
static size_t sdo_response_timeout_ms
Timeout in milliseconds when waiting for an SDO response.
Definition: global_config.h:44
void process_incoming_message(const Message &message)
Process incoming SDO message.
Definition: sdo.cpp:156
uint8_t get_node_id() const
Extracts the node id from the COB-ID.
Definition: message.cpp:40
uint8_t command
SDO command specifier.
Definition: sdo_response.h:49
uint8_t rtr
Remote transmission request (0 if it's not an RTR message, 1 if it is an RTR message) ...
Definition: message.h:45
This struct represents a CANOpen message.
Definition: message.h:39
std::vector< uint8_t > upload(uint8_t node_id, uint16_t index, uint8_t subindex)
SDO download: Get value from remote device's object dictionary.
Definition: sdo.cpp:84
uint8_t data[8]
Data bytes.
Definition: message.h:51
uint32_t get_data() const
Returns the data as a single 4-byte value (only for expedited transfer).
uint8_t node_id
Node id.
Definition: sdo_response.h:46
uint8_t data[7]
Data bytes.
Definition: sdo_response.h:62