MotionLib  1.0.0
SamBuCa motion library
GrblPlatform.cpp
1 /*
2 ** Copyright (C) 2021 CERN
3 **
4 ** This software is provided 'as-is', without any express or implied
5 ** warranty. In no event will the authors be held liable for any damages
6 ** arising from the use of this software.
7 **
8 ** Permission is granted to anyone to use this software for any purpose,
9 ** including commercial applications, and to alter it and redistribute it
10 ** freely, subject to the following restrictions:
11 **
12 ** 1. The origin of this software must not be misrepresented; you must not
13 ** claim that you wrote the original software. If you use this software
14 ** in a product, an acknowledgment in the product documentation would be
15 ** appreciated but is not required.
16 ** 2. Altered source versions must be plainly marked as such, and must not be
17 ** misrepresented as being the original software.
18 ** 3. This notice may not be removed or altered from any source distribution.
19 **
20 ** Created on: 2022-02-11T15:44:13+01:00
21 ** Author: Sylvain Fargier <sfargier> <sylvain.fargier@cern.ch>
22 **
23 */
24 
25 #include "GrblPlatform.hpp"
26 
27 #include <algorithm>
28 #include <cerrno>
29 #include <chrono>
30 #include <cmath>
31 #include <cstdlib>
32 #include <cstring>
33 #include <exception>
34 
35 #include <ccut/Regex.hpp>
36 #include <ccut/async.hpp>
37 #include <ccut/utils.hpp>
38 #include <fcntl.h>
39 #include <logger/Logger.hpp>
40 #include <poll.h>
41 #include <unistd.h>
42 
43 #include "GrblPlatformData.hpp"
44 #include "MotionLibConfig.hpp"
45 #include "util/Promise.hpp"
46 #include "util/grbl/GrblGenerator.hpp"
47 #include "util/grbl/GrblParser.hpp"
48 #include "util/grbl/GrblSim.hpp"
49 #include "util/grbl/GrblTypes.hpp"
50 
51 #ifdef PLATFORM_SAMBUCA
52 #include "util/edge/EdgeGrblDevice.hpp"
53 #endif
54 
55 using namespace logger;
56 using namespace std::chrono;
57 namespace yml = ccut::yml;
58 using grbl::GrblParser;
59 
60 #define GRBL_MIN_POS_CHANGE 0.001
61 
62 static const ccut::Regex s_errorCode("^\\[ERRORCODE:([[:digit:]]+)\\|\\|(.+)"
63  "\\]");
64 static const ccut::Regex s_alarmCode("^\\[ALARMCODE:([[:digit:]]+)\\|\\|(.+)"
65  "\\]");
66 
67 namespace smc {
68 namespace internal {
69 
70 const std::string GrblPlatform::s_loggerCat{"smc:platform:grbl"};
71 
72 const milliseconds GrblPlatform::defaultInterval(1000);
73 
74 GrblPlatform::GrblPlatform(const ccut::yml::NodeRef &ref) :
75  Thread("GrblPlatform")
76 {
77  std::string type;
78  std::string id;
79  yml::NodeRef deviceNode = yml::get(ref, "device");
80  yml::get(ref, "id") >> yml::default_to(id, "0");
81 
82  m_ctx.reset(new GrblPlatform::Context("grbl://", id));
83 
84  if (deviceNode.is_map())
85  yml::get(deviceNode, "type") >> yml::default_to(type, std::string());
86  else
87  deviceNode >> yml::default_to(type, std::string());
88 
89  if (type == "GrblSim")
90  {
91  m_ctx->device.reset(new grbl::GrblSim());
92  }
93 #ifdef PLATFORM_SAMBUCA
94  else if (type == "EdgeSim")
95  {
96  int lun;
97  std::string path;
98  yml::get(deviceNode, "lun") >> yml::default_to(lun, int());
99  yml::get(deviceNode, "path") >> yml::default_to(path, std::string());
100  m_ctx->device.reset(new edge::EdgeGrblDevice(lun, path));
101  }
102  else if (type == "Edge")
103  {
104  int lun;
105  yml::get(deviceNode, "lun") >> yml::default_to(lun, int());
106  m_ctx->device.reset(new edge::EdgeGrblDevice(lun));
107  }
108 #endif /* PLATFORM_SAMBUCA */
109  else
110  throw Exception(ErrorCode::InvalidArguments,
111  "unknown GrblPlatform device type");
112 
113  m_ctx->device->dataSignal.connect(
114  m_dataConn, [this](const std::string &line) { processMessage(line); });
115 
116  start();
117 }
118 
119 GrblPlatform::GrblPlatform(const std::string &prefix,
120  const std::string &id,
121  std::unique_ptr<grbl::GrblDeviceBase> &&device) :
122  Thread("GrblPlatform"),
123  m_ctx(new GrblPlatform::Context(prefix, id))
124 {
125  m_ctx->device.swap(device);
126  m_ctx->device->dataSignal.connect(
127  m_dataConn, [this](const std::string &line) { processMessage(line); });
128 }
129 
130 GrblPlatform::~GrblPlatform()
131 {
132  GrblPlatform::stop();
133  m_dataConn.disconnect();
134  m_ctx->device.reset();
135 }
136 
138 {
139  decltype(m_axisMap)::iterator it = m_axisMap.find(uid);
140  if (it == m_axisMap.end())
141  {
142  error(GrblPlatform::s_loggerCat) << "invalid uid for platform: " << uid;
143  throw Exception(ErrorCode::InvalidArguments,
144  "invalid uid for platform: " + uid);
145  }
146  return it->second;
147 }
148 
149 grbl::Axis GrblPlatform::getAxis(const std::string &uid)
150 {
151  std::lock_guard<std::mutex> l(m_lock);
152  return _getAxisData(uid).grblAxis;
153 }
154 
155 void GrblPlatform::updateAxisSetting(const std::string &axis,
156  grbl::SettingId settingId,
157  const std::string &value)
158 {
159  std::lock_guard<std::mutex> l(m_lock);
160  AxisData &data = _getAxisData(axis);
161  if (settingId == data.resolutionSettingId)
162  {
163  info(s_loggerCat)
164  << "updating axis " << axis << " resolution: " << value << " "
165  << to_string(data.resolution.unit);
166  data.resolution.value = std::stof(value);
167  }
168 }
169 
170 units::Value GrblPlatform::getLastAxisPosition(const std::string &axis) const
171 {
172  std::lock_guard<std::mutex> l(m_lock);
173  decltype(GrblPlatform::m_axisMap)::const_iterator it = m_axisMap.find(axis);
174  if (it == m_axisMap.end())
175  {
176  error(s_loggerCat) << "invalid uid for platform: " << axis;
177  throw Exception(ErrorCode::InvalidArguments,
178  "invalid uid for platform: " + axis);
179  }
180  return it->second.position;
181 }
182 
183 units::Value GrblPlatform::getLastAxisResolution(const std::string &axis) const
184 {
185  std::lock_guard<std::mutex> l(m_lock);
186  decltype(GrblPlatform::m_axisMap)::const_iterator it = m_axisMap.find(axis);
187  if (it == m_axisMap.end())
188  {
189  error(s_loggerCat) << "invalid uid for platform: " << axis;
190  throw Exception(ErrorCode::InvalidArguments,
191  "invalid uid for platform: " + axis);
192  }
193  return it->second.resolution;
194 }
195 
196 uint8_t GrblPlatform::getTrigger(const std::string &uid)
197 {
198  if (!ccut::startsWith(uid, m_trigger.first))
199  {
200  error(s_loggerCat) << "invalid uid for platform: " << uid;
201  throw Exception(ErrorCode::InvalidArguments,
202  "invalid uid for platform: " + uid);
203  }
204  return m_trigger.second;
205 }
206 
207 uint8_t GrblPlatform::getGpio(const std::string &uid)
208 {
209  std::lock_guard<std::mutex> l(m_lock);
210  decltype(m_gpioMap)::const_iterator it = std::find_if(
211  m_gpioMap.begin(), m_gpioMap.end(),
212  [&uid](const std::pair<std::string, uint8_t> &p) {
213  return ccut::startsWith(uid, p.first);
214  });
215  if (it == m_gpioMap.end())
216  {
217  error(s_loggerCat) << "invalid uid for platform: " << uid;
218  throw Exception(ErrorCode::InvalidArguments,
219  "invalid uid for platform: " + uid);
220  }
221  return it->second;
222 }
223 
224 void GrblPlatform::clearQueues(const std::string &msg, bool clearImmediates)
225 {
226  info(s_loggerCat) << "clearing run-queues";
227  std::unique_lock<std::mutex> lock(m_lock);
228  if (clearImmediates)
229  {
230  while (!m_immediate.empty())
231  {
232  ImmediateCmd cmd = std::move(m_immediate.front());
233  m_immediate.pop();
234  cmd.prom->set_exception(Exception(ErrorCode::Canceled, msg));
235  }
236  }
237  while (!m_queue.empty())
238  {
239  MotionCmd cmd = std::move(m_queue.front());
240  m_queue.pop();
241  cmd.prom.set_exception(
242  std::make_exception_ptr(Exception(ErrorCode::Canceled, msg)));
243  }
244  while (!m_pending.empty())
245  {
246  MotionCmd cmd = std::move(m_pending.front());
247  m_pending.pop();
248  cmd.prom.set_exception(
249  std::make_exception_ptr(Exception(ErrorCode::Runtime, msg)));
250  }
251 }
252 
253 void GrblPlatform::wake()
254 {
255  std::lock_guard<std::mutex> lock(m_mutex);
256  m_waked = true;
257  if (m_ctx->device)
258  m_ctx->device->wake();
259 }
260 
261 void GrblPlatform::stop()
262 {
263  Thread::stop();
264 }
265 
266 void GrblPlatform::processMessage(const std::string &message)
267 {
268  try
269  {
270  bool handled = false;
271  switch (GrblParser::getLineType(message))
272  {
273  case grbl::LineType::State:
274  if (GrblParser::parseState(message, m_ctx->state))
275  {
276  onStateMessage();
277  handled = true;
278  }
279  break;
280  case grbl::LineType::Message:
281  if (GrblPlatform::Context::isStepperMessage(message))
282  {
283  m_ctx->nextPoll = steady_clock::now();
284  handled = true;
285  }
286  break;
287  default: break;
288  }
289 
290  if (!handled)
291  {
292  debug(s_loggerCat) << "dropping message: " << message;
293  }
294  }
295  catch (std::exception &ex)
296  {
297  error(s_loggerCat) << "failed to processMessage: " << ex.what();
298  }
299 }
300 
301 std::future<void> GrblPlatform::queue(MotionCmd &&motion)
302 {
303  std::future<void> ret = motion.prom.get_future();
304  std::unique_lock<std::mutex> lock(m_lock);
305  if (m_started)
306  {
307  m_queue.push(std::move(motion));
308  lock.unlock();
309  wake();
310  }
311  else
312  motion.prom.set_exception(std::make_exception_ptr(
313  Exception(ErrorCode::Canceled, "platform stopped")));
314  return ret;
315 }
316 
317 void GrblPlatform::thread_func()
318 {
319  debug(s_loggerCat) << "started";
320  m_ctx->nextPoll = steady_clock::now();
321  const milliseconds interval = defaultInterval;
322 
323  try
324  {
325  loadErrors();
326  loadAlarms();
327  loadSettingsDesc();
328  // Kill any ongoing actions
329  m_ctx->sendReset();
330  // test in hard limits and homing
331  // make error grbl level then start server obj
332  setup();
333 
334  while (m_started.load())
335  {
336  steady_clock::time_point now = steady_clock::now();
337  const milliseconds timeout = (now < m_ctx->nextPoll) ?
338  duration_cast<milliseconds>(m_ctx->nextPoll - now) :
339  milliseconds::zero();
340 
341  m_ctx->device->fetch(timeout);
342 
343  this->handleAlarm();
344 
345  while (processImmediate())
346  ;
347 
348  processMotion();
349 
350  now = steady_clock::now();
351  if (m_ctx->nextPoll <= now)
352  {
353  m_ctx->device->sendRealtime("?");
354  m_ctx->nextPoll = now + interval;
355  }
356  }
357  }
358  catch (Exception &ex)
359  {
360  crit(s_loggerCat) << ex.what();
361  crit(s_loggerCat) << "stopping thread";
362  m_started.store(false);
363  }
364  clearQueues("platform stopped");
365 }
366 
367 void GrblPlatform::loadErrors()
368 {
369  /* process errors list */
370  m_ctx->checkReply(m_ctx->device->send(
371  grbl::gen::System::EnumerateErrors, [this](const std::string &line) {
372  std::vector<std::string> matches(3);
373  if (s_errorCode.match(line, matches))
374  {
375  int code = std::atoi(matches[1].c_str());
376  if (code == 0)
377  error(s_loggerCat) << "invalid error-code: " << line;
378  else
379  m_ctx->errors[code] = matches[2];
380  return true;
381  }
382  return false;
383  }));
384  info(s_loggerCat) << "loaded error-codes: " << m_ctx->errors.size();
385  m_ctx->errors[grbl::CustomErrorCode::Timeout] = "request timeout";
386  m_ctx->errors[grbl::CustomErrorCode::NoPayload] = "no payload";
387  m_ctx->errors[grbl::CustomErrorCode::InternalError] = "internal error";
388 }
389 
390 void GrblPlatform::loadAlarms()
391 {
392  /* process alarms list */
393  m_ctx->checkReply(m_ctx->device->send(
394  grbl::gen::System::EnumerateAlarms, [this](const std::string &line) {
395  std::vector<std::string> matches(3);
396  if (s_alarmCode.match(line, matches))
397  {
398  int code = std::atoi(matches[1].c_str());
399  if (code == 0)
400  error(s_loggerCat) << "invalid alarm-code: " << line;
401  else
402  m_ctx->alarms[code] = matches[2];
403  return true;
404  }
405  return false;
406  }));
407  info(s_loggerCat) << "loaded alarm-codes: " << m_ctx->alarms.size();
408 }
409 
410 void GrblPlatform::loadSettingsDesc()
411 {
412  /* retrieve settingGroup */
413  m_ctx->checkReply(m_ctx->device->send(
414  grbl::gen::System::EnumerateGroups, [this](const std::string &line) {
415  GrblParser::SettingGroup group;
416  if (GrblParser::parseSettingGroup(line, group))
417  {
418  m_ctx->settingGroup[group.name] = group.id;
419  return true;
420  }
421  return false;
422  }));
423  info(s_loggerCat) << "loaded settingGroups: " << m_ctx->settingGroup.size();
424 
425  m_ctx->checkReply(m_ctx->device->send(
426  grbl::gen::System::EnumerateSettings,
427  [this](const std::string &line) {
428  GrblParser::SettingDesc desc;
429  if (GrblParser::parseSettingDesc(line, desc))
430  {
431  m_ctx->setting[desc.name] = desc;
432  return true;
433  }
434  return false;
435  },
436  std::chrono::milliseconds(5000)));
437  info(s_loggerCat) << "loaded settings: " << m_ctx->setting.size();
438 }
439 
440 void GrblPlatform::setup()
441 {
442  m_ctx->_sendSetSetting(
443  m_ctx->findSetting("Status report options").id,
444  std::to_string((grbl::gen::Setting::StatusReportBit::BufferState |
445  grbl::gen::Setting::StatusReportBit::LineNumbers |
446  grbl::gen::Setting::StatusReportBit::FeedSpeed |
447  grbl::gen::Setting::StatusReportBit::PinState |
448  grbl::gen::Setting::StatusReportBit::ParserState |
449  grbl::gen::Setting::StatusReportBit::AlarmSubState)
450  .bits()));
451  /* enable hard limits */
452  m_ctx->_sendSetSetting(m_ctx->findSetting("Hard limits enable").id, "3");
453  m_ctx->checkReply(
454  m_ctx->device->send(grbl::gen::CoordinateSystem::set(0).value));
455  m_ctx->checkReply(m_ctx->device->send(
456  grbl::gen::System::BuildInfo, [this](const std::string &line) {
457  return GrblParser::parseBuildInfo(line, m_ctx->binfo);
458  }));
459 }
460 
461 bool GrblPlatform::processImmediate()
462 {
463  std::unique_lock<std::mutex> lock(m_lock);
464  if (m_immediate.empty())
465  return false;
466  ImmediateCmd cmd = std::move(m_immediate.front());
467  m_immediate.pop();
468  lock.unlock();
469 
470  try
471  {
472  cmd.run(cmd, *m_ctx);
473  }
474  catch (Exception &ex)
475  {
476  cmd.prom->set_exception(ex);
477  }
478  catch (std::exception &ex)
479  {
480  cmd.prom->set_exception(Exception(ErrorCode::Runtime, ex.what()));
481  }
482  return true;
483 }
484 
485 bool GrblPlatform::processMotion()
486 {
487  /* do not queue motions if Grbl is not ready for it */
488  if (m_ctx->state.motionBufferFree <= 0 ||
489  (m_ctx->state.runState != grbl::RunState::Idle &&
490  m_ctx->state.runState != grbl::RunState::Run))
491  return false;
492 
493  std::unique_lock<std::mutex> lock(m_lock);
494  if (m_queue.empty())
495  return false;
496  MotionCmd cmd = std::move(m_queue.front());
497  m_queue.pop();
498  lock.unlock();
499  try
500  {
501  cmd.run(cmd, *m_ctx);
502  }
503  catch (Exception &ex)
504  {
505  cmd.prom.set_exception(std::make_exception_ptr(ex));
506  return false;
507  }
508  catch (std::exception &ex)
509  {
510  cmd.prom.set_exception(
511  std::make_exception_ptr(Exception(ErrorCode::Runtime, ex.what())));
512  return false;
513  }
514 
515  m_pending.push(std::move(cmd));
516  --m_ctx->state.motionBufferFree;
517  return true;
518 }
519 
520 void GrblPlatform::onStateMessage()
521 {
522  /* this may be received in a middle of a transaction, so no commands allowed
523  */
524  if (m_ctx->state.runState == grbl::RunState::Alarm)
525  {
526  wake();
527  }
528  else
529  {
530  /* update positions, prepare notifications */
531  std::set<DeviceId> notifications;
532  {
533  std::lock_guard<std::mutex> lock(m_lock);
534  for (auto &it : m_axisMap)
535  {
536  const auto &newPosition = m_ctx->state.motorPositions.find(
537  it.second.grblAxis);
538  if (newPosition == m_ctx->state.motorPositions.end())
539  warning(s_loggerCat) << "no status for axis " << it.first;
540  else if (std::isnan(it.second.position.value) ||
541  fabs(it.second.position.value - newPosition->second) >=
542  GRBL_MIN_POS_CHANGE)
543  {
544  it.second.position.value = newPosition->second;
545  notifications.insert(it.first);
546  }
547  };
548  }
549 
550  /* update tasks */
551  size_t motionQueued = m_ctx->binfo.motionBufferSize -
552  m_ctx->state.motionBufferFree;
553  /*
554  * block is freed before motion is stopped (last segment remains)
555  * let's preserve last queued motion until everything is properly parked.
556  */
557  if (!motionQueued && (m_ctx->state.runState == grbl::RunState::Run))
558  motionQueued = 1;
559  while (m_pending.size() > motionQueued)
560  {
561  m_pending.front().prom.set_value();
562  m_pending.pop();
563  }
564 
565  /* send notifications */
566  notify(std::move(notifications));
567  }
568 }
569 
570 void GrblPlatform::handleAlarm()
571 {
572  if (m_ctx->state.runState != grbl::RunState::Alarm)
573  return;
574 
575  const std::string msg = m_ctx->getAlarmMessage(
576  static_cast<grbl::AlarmCode>(m_ctx->state.runStateArg));
577  warning(s_loggerCat) << "alarm detected, resetting: " << msg;
578  clearQueues(msg, false);
579  info(s_loggerCat) << "motion-queue flushed";
580 
581  m_ctx->sendReset();
582 }
583 
584 } // namespace internal
585 } // namespace smc
Exception thrown by MotionController in case of issues with command.
Definition: Exception.hpp:61
AxisData & _getAxisData(const std::string &uid)
const std::string & to_string(Axis::State state)
convert State to string
Definition: Axis.cpp:78
static Modal set(uint8_t system)
construct modal to set coordinate system
main motion-lib namespace
Definition: Client.cpp:30