diff --git a/CMakeLists.txt b/CMakeLists.txt index a05354f..62b662d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,5 +23,5 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_VISIBILITY_PRESET hidden) -add_executable(sender main.cpp grbl.h grbl.cpp grbl_test.cpp grbl_communication.h grbl_communication.cpp machine.h grbl_machine.cpp string_utils.h) +add_executable(sender main.cpp grbl.h grbl.cpp grbl_test.cpp grbl_communication.h grbl_communication.cpp grbl_machine.h grbl_machine.cpp string_utils.h) target_link_libraries(sender nanogui gtest gtest_main) \ No newline at end of file diff --git a/grbl.cpp b/grbl.cpp index 4340e0e..71737bb 100644 --- a/grbl.cpp +++ b/grbl.cpp @@ -1,4 +1,5 @@ #include "grbl.h" +#include "string_utils.h" #include #include @@ -31,13 +32,15 @@ grbl::instruction grbl::instruction::new_comment(size_t line, std::string commen } grbl::program::program(std::string filename) { - load(std::move(filename)); + load_from_file(std::move(filename)); } static auto comment_re = std::regex(R"(\(([^)]*)\))"); static auto gcode_re = std::regex(R"(([a-zA-Z0-9\s.]+).*(\(([^)]*)\))?)"); -bool grbl::program::load(std::istream& in) { +bool grbl::program::load_from_stream(std::istream& in) { + instructions.clear(); + is_loaded = true; size_t line_number = 0; for (std::string line; std::getline(in, line);) { @@ -49,8 +52,17 @@ bool grbl::program::load(std::istream& in) { auto comment = sm.str(1); instructions.emplace_back(instruction::new_comment(line_number, comment)); } else if (std::regex_match(line, sm, gcode_re)) { - auto command = sm.str(1); - auto comment = sm.str(3); + auto command = trim(sm.str(1)); + auto comment = trim(sm.str(3)); + +// // grblHAL does not support feed rates with decimal points, +// // so we remove them if possible (only one feed command in this line) +// static auto decimal_feed_re = std::regex(R"(^(F\d+)\.\d+$)"); +// std::smatch feed_matches{}; +// if (std::regex_match(command, feed_matches, decimal_feed_re)) { +// command = feed_matches.str(1); +// } + instructions.emplace_back(instruction::new_gcode(line_number, command, comment)); } else { std::cerr << "Failed to parse line " << line << std::endl; @@ -67,11 +79,11 @@ bool grbl::program::load_from_string(const std::string& content) { in_stream << content; filename = ""; - is_loaded = load(in_stream); + is_loaded = load_from_stream(in_stream); return is_loaded; } -bool grbl::program::load(std::string path) { +bool grbl::program::load_from_file(std::string path) { filename = std::move(path); is_loaded = false; @@ -79,7 +91,7 @@ bool grbl::program::load(std::string path) { if (!in_file) { return false; } - is_loaded = load(in_file); + is_loaded = load_from_stream(in_file); return is_loaded; } diff --git a/grbl.h b/grbl.h index 7b05822..797d38d 100644 --- a/grbl.h +++ b/grbl.h @@ -29,8 +29,8 @@ struct program { program() = default; explicit program(std::string filename); - bool load(std::string filename); - bool load(std::istream& in); + bool load_from_file(std::string filename); + bool load_from_stream(std::istream& in); bool load_from_string(const std::string& content); void dump(std::ostream& out); diff --git a/grbl_machine.cpp b/grbl_machine.cpp index 730518b..36cc674 100644 --- a/grbl_machine.cpp +++ b/grbl_machine.cpp @@ -1,6 +1,6 @@ #include #include -#include "machine.h" +#include "grbl_machine.h" #include "string_utils.h" static bool starts_with(const std::string& line, const std::string& prefix) { @@ -19,15 +19,6 @@ void grbl::machine::connect() { pipe->open(*this); } -void grbl::machine::run_program(const grbl::program& pgm) { - std::cout << "running program 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::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 @@ -40,7 +31,6 @@ 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; @@ -76,6 +66,7 @@ grbl::realtime_status_report grbl::parse_status_report(std::string line, grbl::r 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; @@ -90,6 +81,25 @@ grbl::machine_status grbl::status_from_string(const std::string& status) { 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: @@ -150,18 +160,31 @@ void grbl::machine::on_line_received(std::string line, grbl::transport *transpor std::cout << ">> " << line << std::endl; if (starts_with(line, "ok")) { -// on_ok(); - if (state == grbl_machine_state::run_program) { + 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); @@ -185,6 +208,7 @@ void grbl::machine::on_line_received(std::string line, grbl::transport *transpor } void grbl::machine::continue_program() { + bool program_ended = false; if (executed_instructions < running_program.number_of_instructions()) { instruction to_send; do { @@ -194,9 +218,19 @@ void grbl::machine::continue_program() { if (to_send.type == instruction_type::gcode) { pipe->send(to_send.command); } else { - state = grbl_machine_state::idle; + 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; } } @@ -265,6 +299,11 @@ 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); } diff --git a/machine.h b/grbl_machine.h similarity index 83% rename from machine.h rename to grbl_machine.h index f0091f5..9480e72 100644 --- a/machine.h +++ b/grbl_machine.h @@ -47,16 +47,14 @@ struct realtime_status_report { }; inline bool operator==(const realtime_status_report& a, const realtime_status_report& b) { - return (a.status == b.status && - a.sub_status == b.sub_status && - a.machine_pos[0] == b.machine_pos[0] && - a.machine_pos[1] == b.machine_pos[1] && - a.machine_pos[2] == b.machine_pos[2] && - a.work_pos[0] == b.work_pos[0] && - a.work_pos[1] == b.work_pos[1] && - a.work_pos[2] == b.work_pos[2] && - true - ); + return a.status == b.status && + a.sub_status == b.sub_status && + a.machine_pos[0] == b.machine_pos[0] && + a.machine_pos[1] == b.machine_pos[1] && + a.machine_pos[2] == b.machine_pos[2] && + a.work_pos[0] == b.work_pos[0] && + a.work_pos[1] == b.work_pos[1] && + a.work_pos[2] == b.work_pos[2]; } realtime_status_report parse_status_report(std::string line, grbl::realtime_status_report& result); @@ -64,6 +62,7 @@ realtime_status_report parse_status_report(std::string line, grbl::realtime_stat enum class grbl_machine_state { init, run_program, + check_program, idle, }; @@ -105,6 +104,8 @@ struct machine_listener { virtual void on_banner(std::string line) = 0; virtual void on_message(std::string message) = 0; virtual void on_alarm(int alarm) = 0; + virtual void on_run_completed(bool success, size_t failed_index, int error) = 0; + virtual void on_check_completed(bool success, size_t failed_index, int error) = 0; }; struct machine : public transport_callbacks { @@ -114,6 +115,7 @@ struct machine : public transport_callbacks { void set_listener(grbl::machine_listener *listener); void connect(); void run_program(const grbl::program& pgm); + void check_program(const grbl::program& pgm); void request_jog(jog_state jog) const; void cancel_jog() const; @@ -137,7 +139,9 @@ protected: grbl_machine_state state = grbl_machine_state::init; program running_program; size_t executed_instructions = 0; + bool entered_check_mode = false; void continue_program(); + void reset_machine_state(); }; diff --git a/grbl_test.cpp b/grbl_test.cpp index 2dbf48d..57dd300 100644 --- a/grbl_test.cpp +++ b/grbl_test.cpp @@ -1,7 +1,7 @@ #include #include "grbl.h" -#include "machine.h" +#include "grbl_machine.h" TEST(grbl_program, default_state) { grbl::program pgm; diff --git a/main.cpp b/main.cpp index 6529d9a..e354b65 100644 --- a/main.cpp +++ b/main.cpp @@ -52,7 +52,7 @@ #include "grbl.h" #include #include "grbl_communication.h" -#include "machine.h" +#include "grbl_machine.h" #include "string_utils.h" using namespace nanogui; @@ -67,8 +67,12 @@ public: grbl::jog_state jog; Label *m_pos_x, *m_pos_y, *m_pos_z; TextArea *lblStatus, *lblSubstatus; - nanogui::Color red = nanogui::Color(255, 0, 0, 255); + nanogui::Color colRed = nanogui::Color(255, 0, 0, 255); + nanogui::Color colGreen = nanogui::Color(0, 255, 0, 255); + nanogui::Color colBg; int last_alarm = 0; + grbl::program pgm; + Button *btnLoadProgram, *btnCheckProgram, *btnRunProgram; SenderApp() : Screen(Vector2i(1024, 768), "GRBL Sender") { inc_ref(); @@ -78,6 +82,12 @@ public: window->set_layout(new GroupLayout()); // window->set_size(Screen::size()); + // save regular button color + auto b = new Button(this); + colBg = b->background_color(); + b->set_visible(false); + + new Label(window, "Status", "sans-bold"); Widget *status_holder = new Widget(window); status_holder->set_layout(new GridLayout()); @@ -116,7 +126,7 @@ public: cnc.request_home(); }); Button *btnReset = new Button(actions, "Reset"); - btnReset->set_background_color(red); + btnReset->set_background_color(colRed); btnReset->set_callback([&] { cnc.request_reset(); }); @@ -133,17 +143,42 @@ public: // freed when the parent window is deleted new Label(window, "Program", "sans-bold"); - Button *btnLoadProgram = new Button(window, "Load"); + Widget *pgm_actions = new Widget(window); + pgm_actions->set_layout(new BoxLayout(Orientation::Horizontal)); + + + btnLoadProgram = new Button(pgm_actions, "Load"); btnLoadProgram->set_callback([&] { auto path = file_dialog( {{"nc", "G-Code files"}, {"ngc", "G-Code files"}}, false); - grbl::program pgm{path}; - if (pgm.is_loaded) { - cnc.run_program(pgm); + + btnCheckProgram->set_background_color(colBg); + btnRunProgram->set_background_color(colBg); + + if (pgm.load_from_file(path)) { + btnCheckProgram->set_enabled(true); +// btnRunProgram->set_enabled(true); + } else { + btnCheckProgram->set_enabled(false); + btnRunProgram->set_enabled(false); } }); - btnLoadProgram->set_tooltip("short tooltip"); + btnLoadProgram->set_tooltip("Load program"); + + btnCheckProgram = new Button(pgm_actions, "Check"); + btnCheckProgram->set_enabled(false); + btnCheckProgram->set_callback([&] { + cnc.check_program(pgm); + }); + btnCheckProgram->set_tooltip("Check program"); + + btnRunProgram = new Button(pgm_actions, "Run"); + btnRunProgram->set_enabled(false); + btnRunProgram->set_callback([&] { + cnc.run_program(pgm); + }); + btnRunProgram->set_tooltip("Execute program"); // Alternative construction notation using variadic template btnLoadProgram = window->add