389 lines
14 KiB
C++
389 lines
14 KiB
C++
#include <iostream>
|
|
#include <sstream>
|
|
#include "grbl_machine.h"
|
|
#include "string_utils.h"
|
|
|
|
static bool starts_with(const std::string& line, const std::string& prefix) {
|
|
return line.rfind(prefix, 0) == 0;
|
|
}
|
|
|
|
grbl::machine::machine() {
|
|
pipe = new tcp_transport("192.168.5.39", 23);
|
|
}
|
|
|
|
grbl::machine::~machine() {
|
|
delete pipe;
|
|
}
|
|
|
|
void grbl::machine::connect() {
|
|
pipe->open(*this);
|
|
}
|
|
|
|
void grbl::machine::on_connected(grbl::transport *transport) {
|
|
std::cout << "grbl machine connected" << std::endl;
|
|
// telnet handshake so that we get the banner. banner won't be coming otherwise
|
|
// t->send("\xff\xfd\x18\xff\xfd\x20\xff\xfd\x23\xff\xfd\x27");
|
|
pipe->send("\xff\xfd\x18");
|
|
|
|
}
|
|
|
|
void grbl::machine::on_disconnected(grbl::transport *transport) {
|
|
std::cout << "grbl machine disconnected" << std::endl;
|
|
}
|
|
|
|
grbl::realtime_status_report grbl::parse_status_report(std::string line, grbl::realtime_status_report& result) {
|
|
// grbl::realtime_status_report result;
|
|
|
|
auto l = line.substr(1, -1);
|
|
auto pieces = split_string(l, "|");
|
|
for (auto i = 0; i < pieces.size(); i++) {
|
|
if (i == 0) {
|
|
// status
|
|
auto elements = split_string(pieces[i], ":");
|
|
result.status = status_from_string(elements[0]);
|
|
result.sub_status = elements.size() > 1 ? elements[1] : "";
|
|
} else {
|
|
auto elements = split_string(pieces[i], ":");
|
|
if (elements[0] == "WPos") {
|
|
auto axis = split_string(elements[1], ",");
|
|
result.work_pos[0] = std::stof(axis[0]);
|
|
result.work_pos[1] = std::stof(axis[1]);
|
|
result.work_pos[2] = std::stof(axis[2]);
|
|
} else if (elements[0] == "MPos") {
|
|
auto axis = split_string(elements[1], ",");
|
|
result.machine_pos[0] = std::stof(axis[0]);
|
|
result.machine_pos[1] = std::stof(axis[1]);
|
|
result.machine_pos[2] = std::stof(axis[2]);
|
|
} else if (elements[0] == "Bf") {
|
|
auto p = split_string(elements[1], ",");
|
|
result.buffers_free = std::stoi(p[0]);
|
|
result.rx_chars_free = std::stoi(p[1]);
|
|
} else {
|
|
// not implemented
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
grbl::machine_status grbl::status_from_string(const std::string& status) {
|
|
if (status == "Idle") return machine_status::idle;
|
|
if (status == "Run") return machine_status::run;
|
|
if (status == "Hold") return machine_status::hold;
|
|
if (status == "Jog") return machine_status::jog;
|
|
if (status == "Alarm") return machine_status::alarm;
|
|
if (status == "Door") return machine_status::door;
|
|
if (status == "Check") return machine_status::check;
|
|
if (status == "Home") return machine_status::home;
|
|
if (status == "Sleep") return machine_status::sleep;
|
|
if (status == "Tool") return machine_status::tool;
|
|
return machine_status::unknown;
|
|
}
|
|
|
|
void grbl::machine::run_program(const grbl::program& pgm) {
|
|
std::cout << "running program (" << pgm.filename << ") with " << pgm.number_of_instructions() << " instructions" << std::endl;
|
|
running_program = pgm;
|
|
state = grbl_machine_state::run_program;
|
|
|
|
executed_instructions = 0;
|
|
continue_program();
|
|
}
|
|
|
|
void grbl::machine::check_program(const grbl::program& pgm) {
|
|
std::cout << "checking program (" << pgm.filename << ") with " << pgm.number_of_instructions() << " instructions" << std::endl;
|
|
|
|
running_program = pgm;
|
|
entered_check_mode = false;
|
|
executed_instructions = 0;
|
|
state = grbl_machine_state::check_program;
|
|
pipe->send("$C");
|
|
}
|
|
|
|
std::string grbl::status_to_string(const grbl::machine_status& status) {
|
|
switch (status) {
|
|
case machine_status::idle:
|
|
return "Idle";
|
|
case machine_status::run:
|
|
return "Run";
|
|
case machine_status::hold:
|
|
return "Hold";
|
|
case machine_status::jog:
|
|
return "Jog";
|
|
case machine_status::alarm:
|
|
return "Alarm";
|
|
case machine_status::door:
|
|
return "Door";
|
|
case machine_status::check:
|
|
return "Check";
|
|
case machine_status::home:
|
|
return "Home";
|
|
case machine_status::sleep:
|
|
return "Sleep";
|
|
case machine_status::tool:
|
|
return "Tool";
|
|
case machine_status::unknown:
|
|
default:
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
std::string grbl::alarm_to_string(int alarm) {
|
|
switch (alarm) {
|
|
case 1:
|
|
return "Hard limit has been triggered.\nMachine position is likely lost due to sudden halt.\nRe-homing is highly recommended.";
|
|
case 2:
|
|
return "Soft limit alarm. G-code motion target exceeds\nmachine travel. Machine position retained.\nAlarm may be safely unlocked.";
|
|
case 3:
|
|
return "Reset while in motion. Machine position is likely lost due to sudden halt. Re-homing is highly recommended.";
|
|
case 4:
|
|
return "Probe fail. Probe is not in the expected initial state before starting probe cycle when G38.2 and G38.3 is not triggered and G38.4 and G38.5 is triggered.";
|
|
case 5:
|
|
return "Probe fail. Probe did not contact the workpiece within the programmed travel for G38.2 and G38.4.";
|
|
case 6:
|
|
return "Homing fail. The active homing cycle was reset.";
|
|
case 7:
|
|
return "Homing fail. Safety door was opened during homing cycle.";
|
|
case 8:
|
|
return "Homing fail. Pull off travel failed to clear limit switch. Try increasing pull-off setting or check wiring.";
|
|
case 9:
|
|
return "Homing fail. Could not find limit switch within search distances. Try increasing max travel, decreasing pull-off distance, or check wiring.";
|
|
case 10:
|
|
return "Homing fail. Second dual axis limit switch failed to trigger within configured search distance after first. Try increasing trigger fail distance or check wiring.";
|
|
default:
|
|
return "unknown alarm code";
|
|
}
|
|
}
|
|
|
|
std::string grbl::error_to_string(size_t error) {
|
|
switch (error) {
|
|
case 1:
|
|
return "G-code words consist of a letter and a value. Letter was not found.";
|
|
case 2:
|
|
return "Missing the expected G-code word value or numeric value format is not valid.";
|
|
case 3:
|
|
return "Grbl '$' system command was not recognized or supported.";
|
|
case 4:
|
|
return "Negative value received for an expected positive value.";
|
|
case 5:
|
|
return "Homing cycle failure. Homing is not enabled via settings.";
|
|
case 6:
|
|
return "Minimum step pulse time must be greater than 3usec.";
|
|
case 7:
|
|
return "An EEPROM read failed. Auto-restoring affected EEPROM to default values.";
|
|
case 8:
|
|
return "Grbl '$' command cannot be used unless Grbl is IDLE. Ensures smooth operation during a job.";
|
|
case 9:
|
|
return "G-code commands are locked out during alarm or jog state.";
|
|
case 10:
|
|
return "Soft limits cannot be enabled without homing also enabled.";
|
|
case 11:
|
|
return "Max characters per line exceeded. Received command line was not executed.";
|
|
case 12:
|
|
return "Grbl '$' setting value cause the step rate to exceed the maximum supported.";
|
|
case 13:
|
|
return "Safety door detected as opened and door state initiated.";
|
|
case 14:
|
|
return "Build info or startup line exceeded EEPROM line length limit. Line not stored.";
|
|
case 15:
|
|
return "Jog target exceeds machine travel. Jog command has been ignored.";
|
|
case 16:
|
|
return "Jog command has no '=' or contains prohibited g-code.";
|
|
case 17:
|
|
return "Laser mode requires PWM output.";
|
|
case 20:
|
|
return "Unsupported or invalid g-code command found in block.";
|
|
case 21:
|
|
return "More than one g-code command from same modal group found in block.";
|
|
case 22:
|
|
return "Feed rate has not yet been set or is undefined.";
|
|
case 23:
|
|
return "G-code command in block requires an integer value.";
|
|
case 24:
|
|
return "More than one g-code command that requires axis words found in block.";
|
|
case 25:
|
|
return "Repeated g-code word found in block.";
|
|
case 26:
|
|
return "No axis words found in block for g-code command or current modal state which requires them.";
|
|
case 27:
|
|
return "Line number value is invalid.";
|
|
case 28:
|
|
return "G-code command is missing a required value word.";
|
|
case 29:
|
|
return "G59.x work coordinate systems are not supported.";
|
|
case 30:
|
|
return "G53 only allowed with G0 and G1 motion modes.";
|
|
case 31:
|
|
return "Axis words found in block when no command or current modal state uses them.";
|
|
case 32:
|
|
return "G2 and G3 arcs require at least one in-plane axis word.";
|
|
case 33:
|
|
return "Motion command target is invalid.";
|
|
case 34:
|
|
return "Arc radius value is invalid.";
|
|
case 35:
|
|
return "G2 and G3 arcs require at least one in-plane offset word.";
|
|
case 36:
|
|
return "Unused value words found in block.";
|
|
case 37:
|
|
return "G43.1 dynamic tool length offset is not assigned to configured tool length axis.";
|
|
case 38:
|
|
return "Tool number greater than max supported value.";
|
|
default:
|
|
return "unknown error";
|
|
}
|
|
}
|
|
|
|
void grbl::machine::on_line_received(std::string line, grbl::transport *transport) {
|
|
if (line.at(0) != '<')
|
|
std::cout << ">> " << line << std::endl;
|
|
|
|
if (starts_with(line, "ok")) {
|
|
if (state == grbl_machine_state::run_program || state == grbl_machine_state::check_program) {
|
|
if (!entered_check_mode) {
|
|
entered_check_mode = true;
|
|
}
|
|
continue_program();
|
|
} else if (state == grbl_machine_state::idle && entered_check_mode) {
|
|
entered_check_mode = false;
|
|
}
|
|
|
|
} else if (starts_with(line, "error")) {
|
|
size_t error = std::stoi(line.substr(6));
|
|
if (state == grbl_machine_state::run_program) {
|
|
listener->on_run_completed(false, executed_instructions - 1, error);
|
|
state = grbl_machine_state::idle;
|
|
} else if (state == grbl_machine_state::check_program) {
|
|
listener->on_check_completed(false, executed_instructions - 1, error);
|
|
state = grbl_machine_state::idle;
|
|
pipe->send("$C"); // exit check mode
|
|
}
|
|
// on_error(error);
|
|
} else {
|
|
// we have a push message
|
|
if (starts_with(line, "Grbl")) {
|
|
listener->on_banner(line);
|
|
reset_machine_state();
|
|
} else if (starts_with(line, "<")) {
|
|
last_report = parse_status_report(line, last_report);
|
|
listener->on_realtime_status_report(last_report);
|
|
} else if (starts_with(line, "[MSG:")) {
|
|
listener->on_message(line.substr(5, line.size() - 6));
|
|
} else if (starts_with(line, "ALARM:")) {
|
|
listener->on_alarm(std::stoi(line.substr(6)));
|
|
}
|
|
}
|
|
|
|
// if (state == grbl_machine_state::run_program) {
|
|
// if (line.rfind("ok", 0) == 0) {
|
|
// continue_program();
|
|
// } else if (line.rfind("error", 0) == 0) {
|
|
// std::cerr << "Received error" << std::endl;
|
|
// } else {
|
|
// }
|
|
// } else {
|
|
// // evaluate responses when not running a program
|
|
// }
|
|
}
|
|
|
|
void grbl::machine::continue_program() {
|
|
bool program_ended = false;
|
|
if (executed_instructions < running_program.number_of_instructions()) {
|
|
instruction to_send;
|
|
do {
|
|
to_send = running_program.instruction_at(executed_instructions++);
|
|
} while (to_send.type != instruction_type::gcode && executed_instructions < running_program.number_of_instructions());
|
|
|
|
if (to_send.type == instruction_type::gcode) {
|
|
pipe->send(to_send.command);
|
|
} else {
|
|
program_ended = true;
|
|
}
|
|
} else {
|
|
program_ended = true;
|
|
}
|
|
|
|
if (program_ended) {
|
|
if (state == grbl_machine_state::check_program) {
|
|
pipe->send("$C");
|
|
listener->on_check_completed(true, 0, 0);
|
|
} else if (state == grbl_machine_state::run_program) {
|
|
listener->on_run_completed(true, 0, 0);
|
|
}
|
|
state = grbl_machine_state::idle;
|
|
}
|
|
}
|
|
|
|
void grbl::machine::request_jog(jog_state jog) const {
|
|
|
|
cancel_jog();
|
|
if (jog.no_jogging()) {
|
|
return;
|
|
}
|
|
|
|
std::stringstream ss;
|
|
ss << "$J=G91 G21 ";
|
|
|
|
if (jog.left_pressed)
|
|
ss << " X-1000";
|
|
else if (jog.right_pressed)
|
|
ss << " X1000";
|
|
|
|
if (jog.up_pressed)
|
|
ss << " Y1000";
|
|
else if (jog.down_pressed)
|
|
ss << " Y-1000";
|
|
|
|
if (jog.z_up_pressed)
|
|
ss << " Z1000";
|
|
else if (jog.z_down_pressed)
|
|
ss << " Z-1000";
|
|
|
|
if (jog.speed_fast_pressed) {
|
|
ss << " F10000";
|
|
} else if (jog.speed_slow_pressed) {
|
|
ss << " F100";
|
|
} else {
|
|
ss << " F1000";
|
|
}
|
|
|
|
pipe->send(ss.str());
|
|
}
|
|
|
|
void grbl::machine::cancel_jog() const {
|
|
pipe->send_single_char_command(0x85);
|
|
}
|
|
|
|
void grbl::machine::set_listener(grbl::machine_listener *l) {
|
|
listener = l;
|
|
}
|
|
|
|
void grbl::machine::request_unlock() {
|
|
pipe->send("$X");
|
|
}
|
|
|
|
void grbl::machine::request_home() {
|
|
pipe->send("$H");
|
|
}
|
|
|
|
void grbl::machine::request_reset() {
|
|
pipe->send_single_char_command(0x18);
|
|
}
|
|
|
|
void grbl::machine::request_cycle_start() {
|
|
pipe->send_single_char_command(0x81);
|
|
}
|
|
|
|
void grbl::machine::request_feed_hold() {
|
|
pipe->send_single_char_command(0x82);
|
|
}
|
|
|
|
void grbl::machine::reset_machine_state() {
|
|
state = grbl_machine_state::idle;
|
|
executed_instructions = false;
|
|
}
|
|
|
|
bool grbl::jog_state::no_jogging() const {
|
|
return !(up_pressed || down_pressed || left_pressed || right_pressed || z_up_pressed || z_down_pressed);
|
|
}
|