MotionLib  1.0.0
SamBuCa motion library
MGrblPlatform.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-04-29T11:16:27+02:00
21 ** Author: Sylvain Fargier <sfargier> <sylvain.fargier@cern.ch>
22 **
23 */
24 
25 #include "MGrblPlatform.hpp"
26 
27 #include <ccut/Signal.hpp>
28 #include <ccut/async.hpp>
29 #include <ccut/utils.hpp>
30 #include <ccut/yml.hpp>
31 #include <logger/Logger.hpp>
32 
33 #include "MotionLibConfig.hpp"
34 #include "platform/specialized/GrblPlatform/GrblPlatform.hpp"
35 #include "util/grbl/MGrblDeviceWrapper.hpp"
36 #include "util/grbl/MGrblSim.hpp"
37 
38 #ifdef PLATFORM_SAMBUCA
39 #include "util/edge/MEdgeGrblDevice.hpp"
40 #endif
41 
42 using namespace logger;
43 using namespace std::chrono;
44 namespace yml = ccut::yml;
45 
46 namespace smc {
47 namespace internal {
48 
49 static const std::string s_mgrblPrefix{"grbl://"};
50 static const std::string s_loggerCat{"smc:platform:mgrbl"};
51 
52 MGrblPlatform::MGrblPlatform(const ccut::yml::NodeRef &ref, Context *data) :
53  Thread("MGrblPlatform"),
54  m_ctx(data ? data : new Context{s_mgrblPrefix, ref}),
55  m_mainTriggerUrl(s_mgrblPrefix + m_ctx->id + "/trigger")
56 {
57  std::string type;
58  yml::NodeRef deviceNode = yml::get(ref, "device");
59 
60  if (deviceNode.is_map())
61  yml::get(deviceNode, "type") >> yml::default_to(type, std::string());
62  else
63  deviceNode >> yml::default_to(type, std::string());
64 
65  if (type == "GrblSim")
66  {
67  m_ctx->device.reset(new mgrbl::MGrblSim());
68  }
69 #ifdef PLATFORM_SAMBUCA
70  else if (type == "EdgeSim")
71  {
72  int lun;
73  std::string path;
74  yml::get(deviceNode, "lun") >> yml::default_to(lun, int());
75  yml::get(deviceNode, "path") >> yml::default_to(path, std::string());
76  m_ctx->device.reset(new edge::MEdgeGrblDevice(lun, path));
77  }
78  else if (type == "Edge")
79  {
80  int lun = 0;
81  yml::get(deviceNode, "lun") >> yml::default_to(lun, int());
82  m_ctx->device.reset(new edge::MEdgeGrblDevice(lun));
83  }
84 #endif /* PLATFORM_SAMBUCA */
85  else
86  throw Exception(ErrorCode::InvalidArguments,
87  "unknown MGrblPlatform device type");
88 
89  m_ctx->device->dataSignal.connect(
90  m_dataConn, [this](const std::string &line) { processMessage(line); });
91 
92  createPlatforms(*m_ctx);
93  getBuildInfo(*m_ctx);
94  loadLayout(*m_ctx);
95 
96  /* not running their thread, but still running */
97  for (const GrblPlatform::Shared &p : m_platforms)
98  p->m_started.store(true);
99  start();
100 }
101 
102 MGrblPlatform::~MGrblPlatform()
103 {
104  MGrblPlatform::stop();
105  m_dataConn.disconnect();
106  m_ctx->device.reset();
107 }
108 
109 void MGrblPlatform::stop()
110 {
111  Thread::stop();
112 }
113 
114 GrblPlatform::Shared MGrblPlatform::getFirstInstance() const
115 {
116  std::lock_guard<std::mutex> guard(m_lock);
117  if (m_platforms.empty()) /* unlikely */
118  throw Exception(ErrorCode::InternalError,
119  "no sub-platforms in MGrblPlatform");
120 
121  return m_platforms[0];
122 }
123 
125 {
126  GrblPlatform::Shared platform{getFirstInstance()};
127 
128  size_t grblAxis = static_cast<size_t>(platform->getAxis(axis.uid()));
129 
130  std::lock_guard<std::mutex> guard(m_lock);
131  if (m_layout.size() < grblAxis)
132  throw Exception(ErrorCode::InternalError, "axis not in layout");
133  const mgrbl::Instance instance = m_layout[grblAxis];
134 
135  if (instance >= m_platforms.size())
136  throw Exception(ErrorCode::InternalError,
137  "axis not bound to an instance");
138 
139  axis.m_grbl = m_platforms[instance];
140 }
141 
143 {
144  std::lock_guard<std::mutex> guard(m_lock);
145  if (m_platforms.empty()) /* unlikely */
146  throw Exception(ErrorCode::InternalError,
147  "no sub-platforms in MGrblPlatform");
148 
149  const decltype(m_triggers)::const_iterator instanceId = m_triggers.find(
150  trigger.uid());
151  if (instanceId == m_triggers.end())
152  throw Exception(ErrorCode::InternalError, "wrong address of trigger");
153 
154  const mgrbl::Instance instance = instanceId->second;
155  if (instance >= m_platforms.size())
156  throw Exception(ErrorCode::InternalError,
157  "trigger not bound to an instance");
158 
159  trigger.m_grbl = m_platforms[instance];
160 }
161 
163 {
164  std::lock_guard<std::mutex> guard(m_lock);
165 
166  if (m_platforms.empty()) /* unlikely */
167  throw Exception(ErrorCode::InternalError,
168  "no sub-platforms in MGrblPlatform");
169 
170  const decltype(m_gpios)::const_iterator instanceId = m_gpios.find(
171  gpio.uid());
172  if (instanceId == m_gpios.end())
173  throw Exception(ErrorCode::InternalError, "wrong address of gpio");
174 
175  const mgrbl::Instance instance = instanceId->second;
176  if (instance >= m_platforms.size())
177  throw Exception(ErrorCode::InternalError,
178  "gpio not bound to an instance");
179 
180  gpio.m_grbl = m_platforms[instance];
181 }
182 
184  Context &,
185  std::function<void(GrblPlatform::Shared &platform,
186  GrblPlatform::Context &ctx)> fun,
187  bool onlyFirst) const
188 {
189  if (onlyFirst)
190  {
191  GrblPlatform::Shared platform{getFirstInstance()};
192  fun(platform, *platform->m_ctx);
193  }
194  else
195  {
196  std::vector<GrblPlatform::Shared> platforms{getPlatforms()};
197  for (GrblPlatform::Shared &platform : platforms)
198  {
199  fun(platform, *platform->m_ctx);
200  }
201  }
202 }
203 
204 void MGrblPlatform::loadLayout(Context &ctx)
205 {
206  const std::string cmd = mgrbl::ControlSymbol + mgrbl::gen::Control::Layout;
207  ctx.device->send(cmd, [this, &ctx, &cmd](const std::string &line) {
208  mgrbl::Layout l;
209  if (mgrbl::MGrblParser::parseLayout(line.substr(1), l))
210  {
211  ctx.used.clear();
212 
213  {
214  std::lock_guard<std::mutex> guard(m_lock);
215  m_layout = l;
216  }
217  for (mgrbl::Instance i : l)
218  ctx.used.insert(i);
219 
220  info(s_loggerCat) << "loaded layout: " << l;
221  return true;
222  }
223  return false;
224  });
225 }
226 
227 void MGrblPlatform::getBuildInfo(Context &ctx)
228 {
229  const std::string cmd = mgrbl::ControlSymbol +
230  mgrbl::gen::Control::BuildInfo;
231  ctx.device->send(cmd, [this, &ctx, &cmd](const std::string &line) {
232  info(s_loggerCat) << "sambuca-vitis build-info: " << line;
233  return true;
234  });
235 }
236 
237 void MGrblPlatform::createPlatforms(Context &ctx)
238 {
239  const std::string cmd = mgrbl::ControlSymbol +
240  mgrbl::gen::Control::GrblCount;
241 
242  ctx.device->send(cmd, [this, &cmd](const std::string &line) {
243  if (ccut::startsWith(line, cmd))
244  {
245  std::lock_guard<std::mutex> guard(m_lock);
246  m_platforms.reserve(std::stoi(line.substr(cmd.size() + 1)));
247  return true;
248  }
249  return false;
250  });
251 
252  size_t capacity;
253  {
254  std::lock_guard<std::mutex> guard(m_lock);
255  capacity = m_platforms.capacity();
256  }
257 
258  if (capacity == 0)
259  throw Exception(ErrorCode::InvalidArguments,
260  "failed to retrieve MGrblPlatform instances count");
261 
262  for (size_t i = 0; i < capacity; ++i)
263  {
264  GrblPlatform::Shared grbl{
265  new GrblPlatform(ctx.prefix, ctx.id,
266  std::unique_ptr<grbl::GrblDeviceBase>(
267  new grbl::MGrblDeviceWrapper{i, ctx.device}))};
268 
269  /* reset grbl before running the platform */
270  grbl->m_ctx->sendReset();
271 
272  std::lock_guard<std::mutex> guard(m_lock);
273  m_platforms.emplace_back(grbl);
274  }
275 }
276 
277 void MGrblPlatform::thread_func()
278 {
279  debug(s_loggerCat) << "started";
280  m_ctx->nextPoll = steady_clock::now();
281  const milliseconds interval = GrblPlatform::defaultInterval;
282 
283  std::vector<GrblPlatform::Shared> platforms = getPlatforms();
284 
285  try
286  {
287  if (platforms.empty()) /* unlikely */
288  throw Exception(ErrorCode::InternalError,
289  "no sub-platforms in MGrblPlatform");
290 
291  { /* load error and setting descriptions */
292  GrblPlatform::Shared first = platforms.front();
293  first->loadErrors();
294  first->loadAlarms();
295  first->loadSettingsDesc();
296  for (const GrblPlatform::Shared &p : platforms)
297  {
298  if (p != first)
299  {
300  p->m_ctx->errors = first->m_ctx->errors;
301  p->m_ctx->alarms = first->m_ctx->alarms;
302  p->m_ctx->setting = first->m_ctx->setting;
303  p->m_ctx->settingGroup = first->m_ctx->settingGroup;
304  }
305  p->setup();
306  }
307  }
308 
309  while (m_started.load())
310  {
311  steady_clock::time_point now = steady_clock::now();
312  const milliseconds timeout = (now < m_ctx->nextPoll) ?
313  duration_cast<milliseconds>(m_ctx->nextPoll - now) :
314  milliseconds::zero();
315 
316  m_ctx->device->fetch(timeout);
317 
318  while (processImmediate(*m_ctx))
319  ;
320 
321  for (const GrblPlatform::Shared &p : platforms)
322  {
323  p->handleAlarm();
324  while (p->processImmediate())
325  ;
326 
327  p->processMotion();
328  }
329 
330  now = steady_clock::now();
331  if (m_ctx->nextPoll <= now)
332  {
333  processUpdate(*m_ctx);
334  m_ctx->nextPoll = now + interval;
335  }
336  }
337  }
338  catch (Exception &ex)
339  {
340  crit(s_loggerCat) << ex.what();
341  crit(s_loggerCat) << "stopping thread";
342  m_started.store(false);
343  }
344 
345  for (const GrblPlatform::Shared &p : platforms)
346  {
347  p->m_started.store(false);
348  p->clearQueues("platform stopped");
349  }
350  clearQueue("platform stopped");
351 }
352 
353 void MGrblPlatform::clearQueue(const std::string &msg)
354 {
355  info(s_loggerCat) << "clearing run-queues";
356  std::unique_lock<std::mutex> lock(m_lock);
357  while (!m_immediate.empty())
358  {
359  ImmediateCmd cmd = std::move(m_immediate.front());
360  m_immediate.pop();
361  cmd.prom->set_exception(Exception(ErrorCode::Canceled, msg));
362  }
363 }
364 
366 {
367  std::string msg("0?");
368  for (mgrbl::Instance i : ctx.used)
369  {
370  msg[0] = static_cast<char>(i + '0');
371  ctx.device->sendRealtime(msg);
372  }
373 }
374 
375 bool MGrblPlatform::processImmediate(Context &ctx)
376 {
377  std::unique_lock<std::mutex> lock(m_lock);
378  if (m_immediate.empty())
379  return false;
380  ImmediateCmd cmd = std::move(m_immediate.front());
381  m_immediate.pop();
382  lock.unlock();
383 
384  try
385  {
386  cmd.run(cmd, ctx);
387  }
388  catch (Exception &ex)
389  {
390  cmd.prom->set_exception(ex);
391  }
392  catch (std::exception &ex)
393  {
394  cmd.prom->set_exception(Exception(ErrorCode::Runtime, ex.what()));
395  }
396  return true;
397 }
398 
399 void MGrblPlatform::wake()
400 {
401  std::lock_guard<std::mutex> lock(m_mutex);
402  m_waked = true;
403  if (m_ctx->device)
404  m_ctx->device->wake();
405 }
406 
407 void MGrblPlatform::processMessage(const std::string &message)
408 {
409  try
410  {
411  if (mgrbl::MGrblParser::isControl(message))
412  {
413  notice(s_loggerCat) << "<~~ " << message;
414  return;
415  }
416  mgrbl::Instance instance = mgrbl::MGrblParser::getInstance(message);
417  const std::string instanceMessage{message.substr(1)};
418 
419  if (GrblPlatform::Context::isStepperMessage(instanceMessage))
420  m_ctx->nextPoll = steady_clock::now();
421 
422  GrblPlatform::Shared p = getInstance(instance);
423  p->m_ctx->device->dataSignal(instanceMessage);
424  }
425  catch (Exception &ex)
426  {
427  error(s_loggerCat) << "failed to processMessage: " << ex.what();
428  }
429 }
430 
431 MGrblAxis::Shared MGrblPlatform::createAxis(
432  const std::string &uid,
433  const std::shared_ptr<GrblPlatform> &grbl)
434 {
435  return std::make_shared<MGrblAxis>(uid, shared_from_this(), grbl);
436 }
437 
438 } // namespace internal
439 } // namespace smc
Class for implementing Multi-Grbl functionality on top of GrblDeviceBase.
static Instance getInstance(const std::string &line)
return line instance
Definition: MGrblParser.hpp:71
static bool isControl(const std::string &line)
check if given line is control
Definition: MGrblParser.hpp:63
static bool parseLayout(const std::string &line, Layout &layout)
parse layout reply
Definition: MGrblParser.cpp:47
DeviceId uid() const
Get the address of device.
Definition: DeviceBase.cpp:76
Exception thrown by MotionController in case of issues with command.
Definition: Exception.hpp:61
std::shared_ptr< GrblPlatform > getFirstInstance() const
return first instance
void runOnInstance(Context &ctx, std::function< void(GrblPlatform::Shared &platform, GrblPlatform::Context &ctx)> fun, bool onlyFirst=false) const
run the given lambda on all grbl sub-platforms
virtual void processUpdate(Context &ctx)
device status update function
std::shared_ptr< GrblPlatform > getInstance(mgrbl::Instance instance) const
get instance related to the given axis
void updateInstance(MGrblGpio &gpio) const
update instance ptr related to the given gpio
main motion-lib namespace
Definition: Client.cpp:30