MotionLib  1.0.0
SamBuCa motion library
GrblAxis.cpp
1 /*
2 ** Copyright (C) 2025 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: 2025-04-16T15:43:07
21 ** Author: Sylvain Fargier <sylvain.fargier@cern.ch>
22 */
23 
24 #include "GrblAxis.hpp"
25 
26 #include <ccut/Regex.hpp>
27 #include <ccut/async.hpp>
28 
29 #include "GrblPlatform.hpp"
30 #include "util/grbl/GrblDeviceBase.hpp"
31 #include "util/grbl/GrblGenerator.hpp"
32 
33 using namespace logger;
34 using namespace smc;
35 using namespace smc::internal;
36 using namespace std::chrono;
37 using grbl::GrblParser;
38 
41 
42 GrblAxis::GrblAxis(const std::string &uid,
43  const std::shared_ptr<GrblPlatform> &grbl) :
44  Axis{uid},
45  m_grbl{grbl}
46 {}
47 
48 std::future<void> GrblAxis::moveTo(const units::Value &userPosition) const
49 {
50  try
51  {
52  std::shared_ptr<GrblPlatform> grbl(m_grbl.lock());
53  if (!grbl)
54  throw Exception(ErrorCode::Canceled, "platform stopped (deleted)");
55 
56  grbl::Axis grblAxis = grbl->getAxis(uid());
57  units::Value pos{userPosition};
58  if (!convert(pos, units::unit_t::millimeters))
59  throw Exception(ErrorCode::IncompatibleUnits,
60  "failed to convert in millimeters");
61 
62  grbl::Generator gen;
63  gen << grbl::gen::MotionMode::Rapid << grbl::gen::DistanceMode::Absolute
64  << grbl::gen::Position{grblAxis, grbl::grbl_float_t(pos.value)};
65 
66  MotionCmd cmd(
67  [grbl](MotionCmd &cmd, GrblPlatform::Context &ctx) {
68  cmd.line = ++ctx.line;
69  std::string msg = cmd.message;
70 
71  if (ctx.triggeredMotion)
72  {
73  uint8_t trigPin = grbl->getTrigger(ctx.prefix + ctx.id);
74  msg += grbl::gen::DigitalOut::on(trigPin, false).value;
75  }
76 
77  msg += "N" + std::to_string(cmd.line);
78  msg += "(MSG,Stepper:NewBlock)";
79 
80  grbl::ErrorCode code = ctx.device->send(msg);
81  ctx.checkReply(code);
82  },
83  std::set<grbl::Axis>{grblAxis}, gen.str());
84  return grbl->queue(std::move(cmd));
85  }
86  catch (const Exception &ex)
87  {
88  return ccut::make_future_error(ex);
89  }
90 }
91 
92 std::future<void> GrblAxis::moveBy(const units::Value &userPosition) const
93 {
94  try
95  {
96  std::shared_ptr<GrblPlatform> grbl(m_grbl.lock());
97  if (!grbl)
98  throw Exception(ErrorCode::Canceled, "platform stopped (deleted)");
99 
100  grbl::Axis grblAxis = grbl->getAxis(uid());
101  units::Value pos{userPosition};
102  if (!convert(pos, units::unit_t::millimeters))
103  throw Exception(ErrorCode::IncompatibleUnits,
104  "failed to convert in millimeters");
105 
106  grbl::Generator gen;
107  gen << grbl::gen::MotionMode::Rapid << grbl::gen::DistanceMode::Relative
108  << grbl::gen::Position{grblAxis, grbl::grbl_float_t(pos.value)};
109 
110  MotionCmd cmd(
111  [grbl](MotionCmd &cmd, GrblPlatform::Context &ctx) {
112  cmd.line = ++ctx.line;
113  std::string msg = cmd.message;
114 
115  if (ctx.triggeredMotion)
116  {
117  uint8_t trigPin = grbl->getTrigger(ctx.prefix + ctx.id);
118  msg += grbl::gen::DigitalOut::on(trigPin, false).value;
119  }
120 
121  msg += "N" + std::to_string(cmd.line);
122  msg += "(MSG,Stepper:NewBlock)";
123 
124  grbl::ErrorCode code = ctx.device->send(msg);
125  ctx.checkReply(code);
126  },
127  std::set<grbl::Axis>{grblAxis}, gen.str());
128  return grbl->queue(std::move(cmd));
129  }
130  catch (const Exception &ex)
131  {
132  return ccut::make_future_error(ex);
133  }
134 }
135 
136 void GrblPlatform::Context::sendHold()
137 {
138  this->checkReply(this->device->sendRealtime(grbl::gen::Realtime::FeedHold));
139  const steady_clock::time_point deadline = steady_clock::now() + seconds{30};
140 
141  while (steady_clock::now() <= deadline)
142  {
143  this->checkReply(this->device->sendRealtime(
144  grbl::gen::Realtime::StatusReport,
145  [this](const std::string &message) {
146  if (grbl::GrblParser::getLineType(message) ==
147  grbl::LineType::State)
148  {
149  this->device->dataSignal(message);
150  throw grbl::ErrorCode{0};
151  }
152  return false;
153  }));
154  const grbl::GrblParser::State &st = this->state;
155  if (st.runState == grbl::RunState::Hold &&
156  static_cast<grbl::HoldStateArg>(st.runStateArg) ==
157  grbl::HoldStateArg::Complete)
158  {
159  return;
160  }
161  else
162  {
163  std::this_thread::sleep_for(std::chrono::milliseconds{100});
164  }
165  }
166  std::ostringstream oss;
167  oss << "Failed to pause motion (state=" << grbl::to_string(state.runState)
168  << ":" << state.runStateArg << ")";
169  throw Exception(ErrorCode::Runtime, oss.str());
170 }
171 
172 std::future<void> GrblAxis::stop() const
173 {
174  try
175  {
176  std::shared_ptr<GrblPlatform> grbl(m_grbl.lock());
177  if (!grbl)
178  throw Exception(ErrorCode::Canceled, "platform stopped (deleted)");
179 
180  std::future<void> f = grbl->run<void>(
181  [grbl](ImmediateCmd &cmd, GrblPlatform::Context &ctx) {
182  ctx.sendHold();
183  grbl->clearQueues("stopped", false);
184  ctx.sendReset();
185  cmd.prom->get<void>().set_value();
186  });
187  return f;
188  }
189  catch (const Exception &ex)
190  {
191  return ccut::make_future_error<void>(ex);
192  }
193 }
194 
195 std::future<void> GrblAxis::pause() const
196 {
197  try
198  {
199  std::shared_ptr<GrblPlatform> grbl(m_grbl.lock());
200  if (!grbl)
201  throw Exception(ErrorCode::Canceled, "platform stopped (deleted)");
202 
203  std::future<void> f = grbl->run<void>(
204  [](ImmediateCmd &cmd, GrblPlatform::Context &ctx) {
205  ctx.sendHold();
206  cmd.prom->get<void>().set_value();
207  });
208  return f;
209  }
210  catch (const Exception &ex)
211  {
212  return ccut::make_future_error<void>(ex);
213  }
214 }
215 
216 std::future<void> GrblAxis::resume() const
217 {
218  try
219  {
220  std::shared_ptr<GrblPlatform> grbl(m_grbl.lock());
221  if (!grbl)
222  throw Exception(ErrorCode::Canceled, "platform stopped (deleted)");
223 
224  std::future<void> f = grbl->run<void>(
225  [](ImmediateCmd &cmd, GrblPlatform::Context &ctx) {
226  ctx.checkReply(
227  ctx.device->sendRealtime(grbl::gen::Realtime::CycleStart));
228  cmd.prom->get<void>().set_value();
229  });
230  return f;
231  }
232  catch (const Exception &ex)
233  {
234  return ccut::make_future_error<void>(ex);
235  }
236 }
237 
238 std::future<units::value_t> GrblAxis::getPosition(units::unit_t unit) const
239 {
240  try
241  {
242  std::shared_ptr<GrblPlatform> grbl(m_grbl.lock());
243  if (!grbl)
244  throw Exception(ErrorCode::Canceled, "platform stopped (deleted)");
245 
246  std::shared_ptr<const Axis> obj{
247  std::static_pointer_cast<const Axis>(shared_from_this())};
248 
249  std::future<units::value_t> f = grbl->run<units::value_t>(
250  [unit, obj](ImmediateCmd &cmd, GrblPlatform::Context &ctx) {
251  ctx.device->sendRealtime(
252  grbl::gen::Realtime::StatusReport,
253  [&ctx](const std::string &message) {
254  if (grbl::GrblParser::getLineType(message) ==
255  grbl::LineType::State)
256  {
257  ctx.device->dataSignal(message);
258  throw grbl::ErrorCode{0};
259  }
260  return false;
261  });
262  cmd.prom->get<units::value_t>().set_value(
263  obj->lastPosition(unit));
264  });
265  return f;
266  }
267  catch (const Exception &ex)
268  {
269  return ccut::make_future_error<units::value_t>(ex);
270  }
271 }
272 
273 std::future<void> GrblAxis::setActualPosition(
274  const units::Value &userPosition) const
275 {
276  try
277  {
278  std::shared_ptr<GrblPlatform> grbl(m_grbl.lock());
279  if (!grbl)
280  throw Exception(ErrorCode::Canceled, "platform stopped (deleted)");
281 
282  grbl::Axis grblAxis = grbl->getAxis(uid());
283  units::Value pos{userPosition};
284 
285  if (!convert(pos, units::unit_t::millimeters))
286  throw Exception(ErrorCode::IncompatibleUnits,
287  "failed to convert in millimeters");
288 
289  std::future<void> f = grbl->run<void>(
290  [grblAxis, pos](ImmediateCmd &cmd, GrblPlatform::Context &ctx) {
291  grbl::Generator gen;
292  gen << grbl::gen::NonModal::SetCoordinate(-1, true)
293  << grbl::gen::Position{grblAxis,
294  grbl::grbl_float_t(pos.value)};
295  grbl::ErrorCode code = ctx.device->send(gen.str());
296  ctx.checkReply(code);
297 
298  cmd.prom->get<void>().set_value();
299  });
300  return f;
301  }
302  catch (const Exception &ex)
303  {
304  return ccut::make_future_error(ex);
305  }
306 }
307 
308 units::value_t GrblAxis::lastPosition(units::unit_t unit) const
309 {
310  units::value_t ret;
311  {
312  std::shared_ptr<GrblPlatform> grbl(m_grbl.lock());
313  if (!grbl)
314  throw Exception(ErrorCode::Canceled, "platform stopped (deleted)");
315 
316  ret = grbl->getLastAxisPosition(uid());
317  }
318  if (ret.unit != unit)
319  {
320  if (!convert(ret, unit))
321  throw Exception(ErrorCode::IncompatibleUnits,
322  "failed to convert to " + to_string(unit));
323  }
324  return ret;
325 }
326 
327 static const ccut::Regex alarmCodeRegex{"ALARM:([[:digit:]]+)"};
328 
329 std::future<void> GrblAxis::takeReference(
330  const std::chrono::milliseconds &timeout) const
331 {
332  try
333  {
334  std::shared_ptr<GrblPlatform> grbl(m_grbl.lock());
335  if (!grbl)
336  throw Exception(ErrorCode::Canceled, "platform stopped (deleted)");
337 
338  // create an immediate command
339  std::future<void> f = grbl->run<void>(
340  [grbl, timeout](ImmediateCmd &cmd, GrblPlatform::Context &ctx) {
341  // send the homing command
342  grbl::ErrorCode code = ctx.device->send(
343  grbl::gen::System::Home,
344  [&ctx](const std::string &line) {
345  std::vector<std::string> matches;
346  if (alarmCodeRegex.match(line, matches))
347  {
348  int code = std::stoi(matches[1].c_str());
349  std::string alarm_msg = ctx.getAlarmMessage(
350  static_cast<grbl::AlarmCode>(code));
351  error(GrblPlatform::s_loggerCat) << alarm_msg;
352  throw grbl::CustomErrorCode::InternalError;
353  }
354  return true;
355  },
356  timeout);
357  // check the reply
358  if (code == grbl::CustomErrorCode::Timeout)
359  {
360  error(GrblPlatform::s_loggerCat)
361  << "homing request timeout";
362  grbl->clearQueues("homing failed", false);
363  ctx.sendReset();
364  }
365  ctx.checkReply(code);
366  // set the promise value
367  cmd.prom->get<void>().set_value();
368  });
369  return f;
370  }
371  catch (const Exception &ex)
372  {
373  return ccut::make_future_error<void>(ex);
374  }
375 }
376 
377 static Axis::State grblStateToAxisState(const grbl::RunState &state)
378 {
379  switch (state)
380  {
381  case grbl::RunState::Idle: return Axis::State::IDLE;
382  case grbl::RunState::Run:
383  case grbl::RunState::Hold:
384  case grbl::RunState::Jog:
385  case grbl::RunState::Home: return Axis::State::RUNNING;
386  case grbl::RunState::Alarm: return Axis::State::ERROR;
387  default: return Axis::State::UNKNOWN;
388  }
389 }
390 
391 std::future<Axis::State> GrblAxis::getState() const
392 {
393  try
394  {
395  std::shared_ptr<GrblPlatform> grbl(m_grbl.lock());
396  if (!grbl)
397  throw Exception(ErrorCode::Canceled, "platform stopped (deleted)");
398 
399  std::future<Axis::State> f = grbl->run<Axis::State>(
400  [](ImmediateCmd &cmd, const GrblPlatform::Context &ctx) {
401  grbl::RunState grblState = ctx.state.runState;
402  Axis::State runState = grblStateToAxisState(grblState);
403  cmd.prom->get<Axis::State>().set_value(runState);
404  });
405  return f;
406  }
407  catch (const Exception &ex)
408  {
409  return ccut::make_future_error<Axis::State>(ex);
410  }
411 }
412 
413 std::future<std::set<std::string>> GrblAxis::listConfig() const
414 {
415  try
416  {
417  std::shared_ptr<GrblPlatform> grbl(m_grbl.lock());
418  if (!grbl)
419  throw Exception(ErrorCode::Canceled, "platform stopped (deleted)");
420 
421  grbl::Axis grblAxis = grbl->getAxis(uid());
422  return grbl->run<std::set<std::string>>(
423  [grblAxis](ImmediateCmd &cmd, GrblPlatform::Context &ctx) {
424  cmd.prom->get<std::set<std::string>>().set_value(
425  ctx.listConfig(grblAxis));
426  });
427  }
428  catch (const Exception &ex)
429  {
430  return ccut::make_future_error<std::set<std::string>>(ex);
431  }
432 }
433 
434 std::future<std::string> GrblAxis::getConfig(const std::string &name) const
435 {
436  try
437  {
438  std::shared_ptr<GrblPlatform> grbl(m_grbl.lock());
439  if (!grbl)
440  throw Exception(ErrorCode::Canceled, "platform stopped (deleted)");
441 
442  const std::string uid = this->uid();
443 
444  grbl::Axis grblAxis = grbl->getAxis(uid);
445  return grbl->run<std::string>([name, grblAxis, uid,
446  grbl](ImmediateCmd &cmd,
447  GrblPlatform::Context &ctx) {
448  grbl::SettingId id = ctx.findSetting(name, grblAxis).id;
449  std::string ret;
450  grbl::ErrorCode rc = ctx.device->send(
451  grbl::gen::System::getSetting(id),
452  [id, uid, &ret, grbl](const std::string &line) {
453  if (GrblParser::getLineType(line) == grbl::LineType::Setting)
454  {
455  GrblParser::Setting setting;
456  if ((GrblParser::parseSetting(line, setting)) &&
457  (setting.first == id))
458  {
459  ret = setting.second;
460  grbl->updateAxisSetting(uid, setting.first,
461  setting.second);
462  return true;
463  }
464  }
465  return false;
466  });
467  ctx.checkReply(rc);
468 
469  cmd.prom->get<std::string>().set_value(ret);
470  });
471  }
472  catch (const Exception &ex)
473  {
474  return ccut::make_future_error<std::string>(ex);
475  }
476 }
477 
478 std::future<void> GrblAxis::setConfig(const std::string &name,
479  const std::string &value) const
480 {
481  try
482  {
483  std::shared_ptr<GrblPlatform> grbl(m_grbl.lock());
484  if (!grbl)
485  throw Exception(ErrorCode::Canceled, "platform stopped (deleted)");
486 
487  const std::string uid = this->uid();
488  grbl::Axis grblAxis = grbl->getAxis(uid);
489  return grbl->run<void>(
490  [name, value, grblAxis, uid, grbl](ImmediateCmd &cmd,
491  GrblPlatform::Context &ctx) {
492  grbl::SettingId id = ctx.findSetting(name, grblAxis).id;
493  grbl::ErrorCode rc = ctx.device->send(
494  grbl::gen::System::setSetting(id, value));
495  ctx.checkReply(rc);
496  grbl->updateAxisSetting(uid, id, value);
497  cmd.prom->get<void>().set_value();
498  });
499  }
500  catch (const Exception &ex)
501  {
502  return ccut::make_future_error(ex);
503  }
504 }
505 
506 bool GrblAxis::canConvert(const units::Value &value, units::unit_t unit) const
507 {
508  units::value_t temp{value};
509  return convert(temp, unit);
510 }
511 
512 bool GrblAxis::convert(units::Value &value, units::unit_t unit) const
513 {
514  std::shared_ptr<GrblPlatform> grbl(m_grbl.lock());
515 
516  if (unit != value.unit && grbl)
517  {
518  switch (unit)
519  {
520  case units::STEPS:
521  if (value.unit == units::MILLIMETERS)
522  {
523  units::Value resolution = grbl->getLastAxisResolution(uid());
524  if (resolution.value <= 0)
525  {
526  logger::warning(GrblPlatform::s_loggerCat)
527  << "axis resolution not set";
528  return false;
529  }
530  value = resolution * value;
531  return true;
532  }
533  break;
534  case units::MILLIMETERS:
535  if (value.unit == units::STEPS)
536  {
537  units::Value resolution = grbl->getLastAxisResolution(uid());
538  if (resolution.value <= 0)
539  {
540  logger::warning(GrblPlatform::s_loggerCat)
541  << "axis resolution not set";
542  return false;
543  }
544  value = value / resolution;
545  return true;
546  }
547  break;
548  default: break;
549  }
550  try
551  {
552  units::Value resolution = grbl->getLastAxisResolution(uid());
553  if (resolution.value <= 0)
554  {
555  logger::warning(GrblPlatform::s_loggerCat)
556  << "axis resolution not set";
557  return false;
558  }
559  switch (value.unit)
560  {
561  case units::STEPS: value = value * resolution; return true;
562  case units::MILLIMETERS: value = resolution / value; return true;
563  default: return false;
564  }
565  }
566  catch (const Exception &ex)
567  {
568  logger::warning(GrblPlatform::s_loggerCat)
569  << "failed to convert value: " << ex.what();
570  return false;
571  }
572  }
573  return DeviceBase::convert(value, unit);
574 }
static LineType getLineType(const std::string &line)
detect line type
Definition: GrblParser.cpp:51
State
Axis State.
Definition: Axis.hpp:54
virtual bool convert(units::Value &value, units::unit_t unit) const
Convert value in a manner specific to this device.
Definition: DeviceBase.cpp:86
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::future< State > getState() const override
Get the current axis state.
Definition: GrblAxis.cpp:391
bool canConvert(const units::Value &value, units::unit_t unit) const override
Check if unit conversion is possible in a manner specific to this device.
Definition: GrblAxis.cpp:506
std::future< void > resume() const override
Resume paused motion.
Definition: GrblAxis.cpp:216
std::future< void > pause() const override
Pause ongoing motion, prevent queued motions from running.
Definition: GrblAxis.cpp:195
std::future< void > setConfig(const std::string &name, const std::string &value) const override
set device configuration value
Definition: GrblAxis.cpp:478
std::future< void > takeReference(const std::chrono::milliseconds &timeout=std::chrono::seconds{ 120}) const override
Start a homing sequence.
Definition: GrblAxis.cpp:329
std::future< std::set< std::string > > listConfig() const override
list device configuration options
Definition: GrblAxis.cpp:413
std::future< units::value_t > getPosition(units::unit_t unit) const override
Get current position of this axis.
Definition: GrblAxis.cpp:238
units::value_t lastPosition(units::unit_t unit) const override
Get last known position of the axis.
Definition: GrblAxis.cpp:308
std::future< void > setActualPosition(const units::Value &position) const override
Set current axis position.
Definition: GrblAxis.cpp:273
std::future< void > stop() const override
Stop any ongoing motion, flush the motion-queue.
Definition: GrblAxis.cpp:172
std::future< std::string > getConfig(const std::string &name) const override
get device configuration value
Definition: GrblAxis.cpp:434
bool convert(units::Value &value, units::unit_t unit) const override
Convert value in a manner specific to this device.
Definition: GrblAxis.cpp:512
std::future< void > moveBy(const units::Value &distance) const override
Move this axis by given distance.
Definition: GrblAxis.cpp:92
const std::string & to_string(Axis::State state)
convert State to string
Definition: Axis.cpp:78
static Modal SetCoordinate(int8_t system=-1, bool current=false)
Set the Coordinate command.
HoldStateArg
Hold state argument.
Definition: GrblTypes.hpp:104
main motion-lib namespace
Definition: Client.cpp:30
Struct holding state of Grbl as returned by "?".
Definition: GrblParser.hpp:96
set given axis to position
void checkReply(grbl::ErrorCode code)
check command result code