scanner_state_machine_def.h
Go to the documentation of this file.
1 // Copyright (c) 2020-2021 Pilz GmbH & Co. KG
2 //
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU Lesser General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU Lesser General Public License for more details.
12 //
13 // You should have received a copy of the GNU Lesser General Public License
14 // along with this program. If not, see <https://www.gnu.org/licenses/>.
15 
20 {
21 namespace protocol_layer
22 {
24  const communication_layer::NewMessageCallback& control_msg_callback,
25  const communication_layer::ErrorCallback& control_error_callback,
26  const communication_layer::ErrorCallback& start_error_callback,
27  const communication_layer::ErrorCallback& stop_error_callback,
28  const communication_layer::NewMessageCallback& data_msg_callback,
29  const communication_layer::ErrorCallback& data_error_callback,
30  const ScannerStartedCallback& scanner_started_callback,
31  const ScannerStoppedCallback& scanner_stopped_callback,
32  const InformUserAboutLaserScanCallback& laser_scan_callback,
33  const TimeoutCallback& start_timeout_callback,
34  const TimeoutCallback& monitoring_frame_timeout_callback)
35  : config_(config)
36  , control_client_(control_msg_callback,
37  control_error_callback,
38  config_.hostUDPPortControl(), // LCOV_EXCL_LINE Lcov bug?
39  config_.clientIp(),
40  config_.scannerControlPort())
41  , data_client_(data_msg_callback,
42  data_error_callback,
43  config_.hostUDPPortData(), // LCOV_EXCL_LINE Lcov bug?
44  config_.clientIp(),
45  config_.scannerDataPort())
46  , scanner_started_callback_(scanner_started_callback)
47  , scanner_stopped_callback_(scanner_stopped_callback)
48  , start_error_callback_(start_error_callback)
49  , stop_error_callback_(stop_error_callback)
50  , inform_user_about_laser_scan_callback_(laser_scan_callback)
51  , start_timeout_callback_(start_timeout_callback)
52  , monitoring_frame_timeout_callback_(monitoring_frame_timeout_callback)
53 {
54 }
55 
56 //+++++++++++++++++++++++++++++++++ States ++++++++++++++++++++++++++++++++++++
57 
58 // clang-format off
59 #define DEFAULT_ON_ENTRY_IMPL(state_name)\
60  template <class Event, class FSM>\
61  void ScannerProtocolDef::state_name::on_entry(Event const&, FSM& fsm)\
62  {\
63  PSENSCAN_DEBUG("StateMachine", "Entering state: " #state_name);\
64  }\
65 
66 #define DEFAULT_ON_EXIT_IMPL(state_name)\
67  template <class Event, class FSM>\
68  void ScannerProtocolDef::state_name::on_exit(Event const&, FSM& fsm)\
69  {\
70  PSENSCAN_DEBUG("StateMachine", "Exiting state: " #state_name);\
71  }
72 
73 #define DEFAULT_STATE_IMPL(state_name)\
74  DEFAULT_ON_ENTRY_IMPL(state_name)\
75  DEFAULT_ON_EXIT_IMPL(state_name)
76 // clang-format on
77 
78 DEFAULT_STATE_IMPL(WaitForStopReply)
79 
81 
82 // \cond Ignore "was not declared or defined" warnings from doxygen
83 template <class Event, class FSM>
84 void ScannerProtocolDef::Idle::on_exit(Event const& /*unused*/, FSM& fsm) // NOLINT
85 {
86  PSENSCAN_DEBUG("StateMachine", "Exiting state: Idle");
87  fsm.control_client_.startAsyncReceiving();
88  fsm.data_client_.startAsyncReceiving();
89 }
90 
91 template <class Event, class FSM>
92 void ScannerProtocolDef::WaitForStartReply::on_entry(Event const& /*unused*/, FSM& fsm) // NOLINT
93 {
94  PSENSCAN_DEBUG("StateMachine", "Entering state: WaitForStartReply");
95  // Start watchdog...
96  fsm.start_reply_watchdog_ = fsm.watchdog_factory_.create(WATCHDOG_TIMEOUT, fsm.start_timeout_callback_);
97 }
98 
99 template <class Event, class FSM>
100 void ScannerProtocolDef::WaitForStartReply::on_exit(Event const& /*unused*/, FSM& fsm) // NOLINT
101 {
102  PSENSCAN_DEBUG("StateMachine", "Exiting state: WaitForStartReply");
103  // Stops the watchdog by resetting the pointer
104  fsm.start_reply_watchdog_.reset();
105 }
106 
107 template <class Event, class FSM>
108 void ScannerProtocolDef::WaitForMonitoringFrame::on_entry(Event const& /*unused*/, FSM& fsm) // NOLINT
109 {
110  PSENSCAN_DEBUG("StateMachine", "Entering state: WaitForMonitoringFrame");
111  fsm.scan_buffer_.reset();
112  // Start watchdog...
113  fsm.monitoring_frame_watchdog_ =
114  fsm.watchdog_factory_.create(WATCHDOG_TIMEOUT, fsm.monitoring_frame_timeout_callback_);
115 }
116 
117 template <class Event, class FSM>
118 void ScannerProtocolDef::WaitForMonitoringFrame::on_exit(Event const& /*unused*/, FSM& fsm) // NOLINT
119 {
120  PSENSCAN_DEBUG("StateMachine", "Exiting state: WaitForMonitoringFrame");
121  // Stops the watchdog by resetting the pointer
122  fsm.monitoring_frame_watchdog_.reset();
123 }
124 
125 template <class Event, class FSM>
126 void ScannerProtocolDef::Stopped::on_entry(Event const& /*unused*/, FSM& /*unused*/) // NOLINT
127 {
128  PSENSCAN_DEBUG("StateMachine", "Entering state: Stopped");
129 }
130 
131 DEFAULT_ON_EXIT_IMPL(Stopped)
132 
135 
136 // \endcond
137 //+++++++++++++++++++++++++++++++++ Actions +++++++++++++++++++++++++++++++++++
138 
139 template <class T>
140 inline void ScannerProtocolDef::sendStartRequest(const T& event)
141 {
142  PSENSCAN_DEBUG("StateMachine", "Action: sendStartRequest");
143 
144  if (!config_.hostIp())
145  {
146  auto host_ip{ control_client_.hostIp() };
147  config_.hostIp(host_ip.to_ulong());
148  PSENSCAN_INFO("StateMachine", "No host ip set! Using local ip: {}", host_ip.to_string());
149  }
152 }
153 
155 {
156  PSENSCAN_DEBUG("StateMachine", "Action: handleStartRequestTimeout");
157  PSENSCAN_ERROR("StateMachine",
158  "Timeout while waiting for the scanner to start! Retrying... "
159  "(Please check the ethernet connection or contact PILZ support if the error persists.)");
160  sendStartRequest(event);
161 }
162 
163 template <class T>
164 inline void ScannerProtocolDef::sendStopRequest(const T& event)
165 {
166  PSENSCAN_DEBUG("StateMachine", "Action: sendStopRequest");
167  data_client_.stop();
169 }
170 
172 {
173  PSENSCAN_DEBUG("StateMachine", "Action: handleMonitoringFrame");
175 
176  try
177  {
179  *(event.data_), event.num_bytes_) };
182  const data_conversion_layer::monitoring_frame::MessageStamped stamped_msg{ msg, event.timestamp_ };
183  informUserAboutTheScanData(stamped_msg);
184  }
185  // LCOV_EXCL_START
187  {
188  PSENSCAN_ERROR("StateMachine", e.what());
189  }
190  // LCOV_EXCL_STOP
191 }
192 
194 {
196 }
197 
199 {
201 }
202 
204 {
206  *(reply_event.data_)) };
208  fmt::format("Unknown result code {:#04x} in start reply.", static_cast<uint32_t>(msg.result())));
209 }
210 
212 {
213  start_error_callback_("Start Request refused by device.");
214 }
215 
217 {
219  *(reply_event.data_)) };
220  stop_error_callback_(fmt::format("Unknown result code {:#04x} in stop reply.", static_cast<uint32_t>(msg.result())));
221 }
222 
224 {
225  stop_error_callback_("Stop Request refused by device.");
226 }
227 
229 {
230  if (msg.hasDiagnosticMessagesField() && !msg.diagnosticMessages().empty())
231  {
233  1 /* sec */, "StateMachine", "The scanner reports an error: {}", util::formatRange(msg.diagnosticMessages()));
234  }
235 }
236 
237 inline void
239 {
240  if (!zoneset_reference_msg_.is_initialized() || (msg.scanCounter() >= zoneset_reference_msg_->scanCounter() &&
241  msg.activeZoneset() != zoneset_reference_msg_->activeZoneset()))
242  {
243  PSENSCAN_INFO("Scanner", "The scanner switched to active zoneset {}", msg.activeZoneset());
245  }
246 }
247 
250 {
251  try
252  {
253  scan_buffer_.add(stamped_msg);
255  {
257  }
258  }
259  catch (const ScanRoundError& ex)
260  {
261  PSENSCAN_WARN("ScanBuffer", ex.what());
262  }
263  if (config_.fragmentedScansEnabled()) // Send the scan fragment in any case.
264  {
265  sendMessageWithMeasurements({ stamped_msg });
266  }
267 }
268 
270  const std::vector<data_conversion_layer::monitoring_frame::MessageStamped>& stamped_msgs)
271 {
272  if (framesContainMeasurements(stamped_msgs))
273  {
274  try
275  {
277  }
278  // LCOV_EXCL_START
280  {
281  PSENSCAN_ERROR("StateMachine", ex.what());
282  }
283  // LCOV_EXCL_STOP
284  }
285 }
286 
288  const std::vector<data_conversion_layer::monitoring_frame::MessageStamped>& stamped_msgs)
289 {
290  if (std::all_of(stamped_msgs.begin(), stamped_msgs.end(), [](const auto& stamped_msg) {
291  return stamped_msg.msg_.measurements().empty();
292  }))
293  {
294  PSENSCAN_DEBUG("StateMachine", "No measurement data in current monitoring frame(s), skipping laser scan callback.");
295  return false;
296  }
297  return true;
298 }
299 
301 {
302  PSENSCAN_DEBUG("StateMachine", "Action: handleMonitoringFrameTimeout");
303 
304  PSENSCAN_WARN("StateMachine",
305  "Timeout while waiting for MonitoringFrame message."
306  " (Please check the ethernet connection or contact PILZ support if the error persists.)");
307 }
308 
309 //+++++++++++++++++++++++++++++++++ Guards ++++++++++++++++++++++++++++++++++++
310 
311 // LCOV_EXCL_START
313  : std::runtime_error(error_msg)
314 {
315 }
316 // LCOV_EXCL_STOP
317 
319 {
320  // LCOV_EXCL_START
322  {
323  throw InternalScannerReplyError("Unexpected code in reply");
324  }
326  {
328  {
329  throw InternalScannerReplyError("Request refused by device.");
330  }
331  else
332  {
333  throw InternalScannerReplyError("Unknown operation result code.");
334  }
335  }
336  // LCOV_EXCL_STOP
337 }
338 
340 {
342  *(reply_event.data_)) };
343  return isStartReply(msg) && isAcceptedReply(msg);
344 }
345 
347 {
349  *(reply_event.data_)) };
350  return isStartReply(msg) && isUnknownReply(msg);
351 }
352 
354 {
356  *(reply_event.data_)) };
357  return isStartReply(msg) && isRefusedReply(msg);
358 }
359 
361 {
363  *(reply_event.data_)) };
364  return isStopReply(msg) && isAcceptedReply(msg);
365 }
366 
368 {
370  *(reply_event.data_)) };
371  return isStopReply(msg) && isUnknownReply(msg);
372 }
373 
375 {
377  *(reply_event.data_)) };
378  return isStopReply(msg) && isRefusedReply(msg);
379 }
380 
382 {
384 }
385 
387 {
389 }
390 
392 {
394 }
395 
397 {
399 }
400 
402 {
404 }
405 
406 //++++++++++++++++++++ Special transitions ++++++++++++++++++++++++++++++++++++
407 
408 template <class FSM>
409 static std::string getStateName(const int& state_id)
410 {
411  using recursive_transition_table = typename boost::msm::back::recursive_get_transition_table<FSM>::type;
412  using states = typename boost::msm::back::generate_state_set<recursive_transition_table>::type;
413 
414  std::string mangle_state_name;
415  boost::mpl::for_each<states, boost::msm::wrap<boost::mpl::placeholders::_1> >(
416  boost::msm::back::get_state_name<recursive_transition_table>(mangle_state_name, state_id));
417  const auto full_name{ boost::core::demangle(mangle_state_name.c_str()) };
418  return full_name.substr(full_name.rfind("::") + 2);
419 }
420 
421 template <class T>
422 static std::string classNameShort(const T& t)
423 {
424  const auto full_name{ boost::core::demangle(typeid(t).name()) };
425  return full_name.substr(full_name.rfind("::") + 2);
426 }
427 
428 // LCOV_EXCL_START
429 template <class FSM, class Event>
430 void ScannerProtocolDef::exception_caught(Event const& event, FSM& /*unused*/, std::exception& exception) // NOLINT
431 {
432  PSENSCAN_ERROR("StateMachine", "Received error \"{}\". Shutting down now.", exception.what());
433  sendStopRequest(event);
434  throw exception;
435 }
436 // LCOV_EXCL_STOP
437 
438 template <class FSM, class Event>
439 void ScannerProtocolDef::no_transition(Event const& event, FSM& /*unused*/, int state) // NOLINT
440 {
441  PSENSCAN_WARN("StateMachine",
442  "No transition in state \"{}\" for event \"{}\".",
443  getStateName<FSM>(state),
444  classNameShort(event));
445 }
446 
447 template <class FSM>
449  FSM& /*unused*/,
450  int state)
451 {
452  PSENSCAN_WARN("StateMachine", "Received monitoring frame despite not waiting for it");
453 }
454 
455 } // namespace protocol_layer
456 } // namespace psen_scan_v2_standalone
Error
#define DEFAULT_ON_EXIT_IMPL(state_name)
bool isRefusedStartReply(scanner_events::RawReplyReceived const &reply_event)
static LaserScan toLaserScan(const std::vector< data_conversion_layer::monitoring_frame::MessageStamped > &stamped_msgs)
Converts monitoring_frames of a scan_round to the user friendly LaserScan type sent by the IScanner::...
void notifyUserAboutRefusedStartReply(scanner_events::RawReplyReceived const &reply_event)
std::function< void(const std::string &)> ErrorCallback
Definition: udp_client.h:50
bool isStartReply(data_conversion_layer::scanner_reply::Message const &msg)
Higher level data type representing a single monitoring frame.
void checkForDiagnosticErrors(const data_conversion_layer::monitoring_frame::Message &msg)
#define PSENSCAN_WARN(name,...)
Definition: logging.h:62
std::function< void(const data_conversion_layer::RawDataConstPtr &, const std::size_t &, const int64_t &timestamp)> NewMessageCallback
Definition: udp_client.h:49
Exception thrown if an additional field was missing during deserialization of a Message.
bool isAcceptedStartReply(scanner_events::RawReplyReceived const &reply_event)
void informUserAboutTheScanData(const data_conversion_layer::monitoring_frame::MessageStamped &stamped_msg)
std::function< void(const LaserScan &)> InformUserAboutLaserScanCallback
monitoring_frame::Message deserialize(const data_conversion_layer::RawData &data, const std::size_t &num_bytes)
bool isRefusedReply(data_conversion_layer::scanner_reply::Message const &msg)
void checkForChangedActiveZoneset(const data_conversion_layer::monitoring_frame::Message &msg)
void checkForInternalErrors(const data_conversion_layer::scanner_reply::Message &msg)
bool isUnknownReply(data_conversion_layer::scanner_reply::Message const &msg)
Contains the events needed to define and implement the scanner protocol.
RawData serialize(const data_conversion_layer::start_request::Message &start_request, const uint32_t &seq_number=DEFAULT_SEQ_NUMBER)
static std::string getStateName(const int &state_id)
#define PSENSCAN_WARN_THROTTLE(period, name,...)
Definition: logging.h:78
void stop()
Stops the underlying io_service so that no messages are received anymore.
Definition: udp_client.h:212
#define DEFAULT_ON_ENTRY_IMPL(state_name)
#define PSENSCAN_DEBUG(name,...)
Definition: logging.h:63
Higher level data type storing the configuration details of the scanner like scanner IP...
void handleMonitoringFrame(const scanner_events::RawMonitoringFrameReceived &event)
Wrapping class for a Message and its corresponding timestamp.
#define PSENSCAN_INFO(name,...)
Definition: logging.h:61
const InformUserAboutLaserScanCallback inform_user_about_laser_scan_callback_
void handleStartRequestTimeout(const scanner_events::StartTimeout &event)
void write(const data_conversion_layer::RawData &data)
Asynchronously sends the specified data to the other endpoint.
Definition: udp_client.h:275
Root namespace in which the software components to communicate with the scanner (firmware-version: 2)...
Definition: udp_client.h:41
Received Start- or Stop-Reply message from scanner device.
bool isUnknownStopReply(scanner_events::RawReplyReceived const &reply_event)
boost::asio::ip::address_v4 hostIp()
Returns local ip address of current socket connection.
Definition: udp_client.h:242
#define PSENSCAN_ERROR(name,...)
Definition: logging.h:60
bool isAcceptedStopReply(scanner_events::RawReplyReceived const &reply_event)
void add(const data_conversion_layer::monitoring_frame::MessageStamped &stamped_msg)
Adds the message to the current scan round.
Definition: scan_buffer.h:134
boost::optional< uint32_t > hostIp() const
std::vector< data_conversion_layer::monitoring_frame::MessageStamped > currentRound()
Definition: scan_buffer.h:124
Timeout while waiting for scanner device to start.
static constexpr std::chrono::milliseconds WATCHDOG_TIMEOUT
bool framesContainMeasurements(const std::vector< data_conversion_layer::monitoring_frame::MessageStamped > &stamped_msg)
: Exception thrown if data received from the scanner hardware could not be processed according to pro...
void notifyUserAboutUnknownStopReply(scanner_events::RawReplyReceived const &reply_event)
void notifyUserAboutStart(scanner_events::RawReplyReceived const &reply_event)
void notifyUserAboutUnknownStartReply(scanner_events::RawReplyReceived const &reply_event)
void handleMonitoringFrameTimeout(const scanner_events::MonitoringFrameTimeout &event)
Exception indicating problems with the monitoring frames of a scan round.
Definition: scan_buffer.h:31
bool isStopReply(data_conversion_layer::scanner_reply::Message const &msg)
void sendMessageWithMeasurements(const std::vector< data_conversion_layer::monitoring_frame::MessageStamped > &stamped_msg)
Higher level data type representing a scanner start request.
Definition: start_request.h:42
std::string formatRange(const T &range)
Definition: format_range.h:38
bool isUnknownStartReply(scanner_events::RawReplyReceived const &reply_event)
ScannerProtocolDef(const ScannerConfiguration &config, const communication_layer::NewMessageCallback &control_msg_callback, const communication_layer::ErrorCallback &control_error_callback, const communication_layer::ErrorCallback &start_error_callback, const communication_layer::ErrorCallback &stop_error_callback, const communication_layer::NewMessageCallback &data_msg_callback, const communication_layer::ErrorCallback &data_error_callback, const ScannerStartedCallback &scanner_started_callback, const ScannerStoppedCallback &scanner_stopped_callback, const InformUserAboutLaserScanCallback &laser_scan_callback, const TimeoutCallback &start_timeout_callback, const TimeoutCallback &monitoring_frame_timeout_callback)
boost::optional< data_conversion_layer::monitoring_frame::Message > zoneset_reference_msg_
void exception_caught(Event const &event, FSM &fsm, std::exception &exception)
void notifyUserAboutStop(scanner_events::RawReplyReceived const &reply_event)
Higher level data type representing a reply message from the scanner.
bool isRefusedStopReply(scanner_events::RawReplyReceived const &reply_event)
void notifyUserAboutRefusedStopReply(scanner_events::RawReplyReceived const &reply_event)
bool isAcceptedReply(data_conversion_layer::scanner_reply::Message const &msg)
#define DEFAULT_STATE_IMPL(state_name)


psen_scan_v2
Author(s): Pilz GmbH + Co. KG
autogenerated on Sat Nov 5 2022 02:13:36