Lots of work on status representation

This commit is contained in:
2023-04-28 14:50:58 +03:00
parent 796fd57ac3
commit 30ccb20846
8 changed files with 430 additions and 457 deletions
+1 -1
View File
@@ -23,5 +23,5 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_VISIBILITY_PRESET hidden) 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) 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)
target_link_libraries(sender nanogui gtest gtest_main) target_link_libraries(sender nanogui gtest gtest_main)
+16 -5
View File
@@ -74,6 +74,8 @@ void grbl::tcp_transport::worker() {
std::string received; std::string received;
uint8_t buffer[200]; uint8_t buffer[200];
auto last_report = std::chrono::high_resolution_clock::now();
while (!should_quit) { while (!should_quit) {
if (is_connected) { if (is_connected) {
// anything to write? // anything to write?
@@ -103,12 +105,12 @@ void grbl::tcp_transport::worker() {
if (is_eol) { if (is_eol) {
if (!is_empty_line(received)) { if (!is_empty_line(received)) {
// starts with? https://stackoverflow.com/questions/1878001/how-do-i-check-if-a-c-stdstring-starts-with-a-certain-string-and-convert-a // starts with? https://stackoverflow.com/questions/1878001/how-do-i-check-if-a-c-stdstring-starts-with-a-certain-string-and-convert-a
if (received.rfind("Grbl", 0) == 0) { // if (received.rfind("Grbl", 0) == 0) {
listener->on_banner(received, this); // listener->on_banner(received, this);
//
} else { // } else {
listener->on_line_received(received, this); listener->on_line_received(received, this);
} // }
} }
received.clear(); received.clear();
} else { } else {
@@ -117,6 +119,15 @@ void grbl::tcp_transport::worker() {
} }
} }
auto now = std::chrono::high_resolution_clock::now();
auto ms_since_last_report = std::chrono::duration_cast<std::chrono::milliseconds>(now - last_report);
// maintain a 5Hz rate
if (ms_since_last_report.count() >= 200) {
request_realtime_report();
last_report = now;
}
// give some time to others // give some time to others
std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(1));
} }
+1 -1
View File
@@ -12,10 +12,10 @@ struct transport_callbacks {
virtual void on_connected(transport *) = 0; virtual void on_connected(transport *) = 0;
virtual void on_disconnected(transport *) = 0; virtual void on_disconnected(transport *) = 0;
virtual void on_line_received(std::string line, transport *) = 0; virtual void on_line_received(std::string line, transport *) = 0;
virtual void on_banner(std::string version, transport *) = 0;
}; };
struct transport { struct transport {
virtual ~transport() = default;
virtual void open(transport_callbacks& cb) = 0; virtual void open(transport_callbacks& cb) = 0;
virtual void close() = 0; virtual void close() = 0;
virtual void send(std::string line) = 0; virtual void send(std::string line) = 0;
+165 -13
View File
@@ -1,6 +1,11 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include "machine.h" #include "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() { grbl::machine::machine() {
pipe = new tcp_transport("192.168.5.39", 23); pipe = new tcp_transport("192.168.5.39", 23);
@@ -14,7 +19,7 @@ void grbl::machine::connect() {
pipe->open(*this); pipe->open(*this);
} }
void grbl::machine::run_program(grbl::program pgm) { void grbl::machine::run_program(const grbl::program& pgm) {
std::cout << "running program with " << pgm.number_of_instructions() << " instructions" << std::endl; std::cout << "running program with " << pgm.number_of_instructions() << " instructions" << std::endl;
running_program = pgm; running_program = pgm;
state = grbl_machine_state::run_program; state = grbl_machine_state::run_program;
@@ -35,23 +40,148 @@ void grbl::machine::on_disconnected(grbl::transport *transport) {
std::cout << "grbl machine disconnected" << std::endl; std::cout << "grbl machine disconnected" << std::endl;
} }
void grbl::machine::on_line_received(std::string line, grbl::transport *transport) {
std::cout << ">> " << line << std::endl;
if (state == grbl_machine_state::run_program) { grbl::realtime_status_report grbl::parse_status_report(std::string line, grbl::realtime_status_report& result) {
if (line.rfind("ok", 0) == 0) { // grbl::realtime_status_report result;
continue_program();
} else if (line.rfind("error", 0) == 0) { auto l = line.substr(1, -1);
std::cerr << "Received error" << std::endl; 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 { } 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
} }
} else { }
// evaluate responses when not running a program }
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;
}
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";
} }
} }
void grbl::machine::on_banner(std::string version, grbl::transport *transport) { std::string grbl::alarm_to_string(int alarm) {
std::cout << "grbl machine received banner" << std::endl; switch (alarm) {
case 1:
return "Hard limit has been triggered. Machine position is likely lost due to sudden halt. Re-homing is highly recommended.";
case 2:
return "Soft limit alarm. G-code motion target exceeds machine travel. Machine position retained. Alarm 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";
}
}
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")) {
// on_ok();
if (state == grbl_machine_state::run_program) {
continue_program();
}
} else if (starts_with(line, "error")) {
size_t error = std::stoi(line.substr(6));
// on_error(error);
} else {
// we have a push message
if (starts_with(line, "Grbl")) {
listener->on_banner(line);
} 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() { void grbl::machine::continue_program() {
@@ -72,9 +202,9 @@ void grbl::machine::continue_program() {
} }
void grbl::machine::request_jog(jog_state jog) const { void grbl::machine::request_jog(jog_state jog) const {
cancel_jog(); cancel_jog();
if (jog.no_jogging()) { if (jog.no_jogging()) {
// cancel_jog();
return; return;
} }
@@ -109,8 +239,30 @@ void grbl::machine::request_jog(jog_state jog) const {
void grbl::machine::cancel_jog() const { void grbl::machine::cancel_jog() const {
pipe->send_single_char_command(0x85); pipe->send_single_char_command(0x85);
}
void grbl::machine::set_listener(grbl::machine_listener *listener) {
machine::listener = listener;
}
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);
} }
bool grbl::jog_state::no_jogging() const { bool grbl::jog_state::no_jogging() const {
+23 -1
View File
@@ -1,6 +1,8 @@
#include "grbl.h"
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "grbl.h"
#include "machine.h"
TEST(grbl_program, default_state) { TEST(grbl_program, default_state) {
grbl::program pgm; grbl::program pgm;
@@ -26,3 +28,23 @@ X3.87739 Y78.52820
EXPECT_EQ(6, pgm.number_of_instructions()); EXPECT_EQ(6, pgm.number_of_instructions());
EXPECT_EQ(true, pgm.is_loaded); EXPECT_EQ(true, pgm.is_loaded);
} }
TEST(grbl_status_report, parse) {
auto content = "<Idle|MPos:0.000,1.000,2.000|Bf:35,1022|FS:0,0|Pn:Z|Ov:100,100,100>";
grbl::realtime_status_report r;
grbl::parse_status_report(content, r);
EXPECT_EQ(grbl::machine_status::idle, r.status);
EXPECT_EQ("", r.sub_status);
EXPECT_EQ(0, r.machine_pos[0]);
EXPECT_EQ(1, r.machine_pos[1]);
EXPECT_EQ(2, r.machine_pos[2]);
EXPECT_EQ(35, r.buffers_free);
//
grbl::parse_status_report("<Home|MPos:0.000,0.000,0.000|Bf:35,1022|FS:0,0|Pn:Z|Ov:100,100,100>", r);
EXPECT_EQ(grbl::machine_status::home, r.status);
EXPECT_EQ(1022, r.rx_chars_free);
}
+66 -15
View File
@@ -5,6 +5,48 @@
namespace grbl { namespace grbl {
enum class machine_status {
idle,
run,
hold,
jog,
alarm,
door,
check,
home,
sleep,
tool,
unknown
};
machine_status status_from_string(const std::string& status);
std::string status_to_string(const machine_status& status);
std::string alarm_to_string(int alarm);
struct realtime_status_report {
machine_status status;
std::string sub_status;
float work_pos[3] = {0};
float machine_pos[3] = {0};
size_t buffers_free = 0;
size_t rx_chars_free = 0;
size_t line_number = 0;
float feed_rate = 0;
float programmed_rpm = 0;
float actual_rpm = 0;
std::string signals;
float axis_offsets[3] = {0};
std::string coordinate_system;
std::string overrides;
std::string accessory_status;
bool mpg = false;
bool homing_complete = false;
std::string scaled_axis;
bool tool_length_reference_offset_set = false;
std::string firmware;
};
realtime_status_report parse_status_report(std::string line, grbl::realtime_status_report& result);
enum class grbl_machine_state { enum class grbl_machine_state {
init, init,
@@ -12,17 +54,6 @@ enum class grbl_machine_state {
idle, idle,
}; };
enum class jog_direction {
up,
down,
left,
right,
up_left,
up_right,
down_left,
down_right
};
struct jog_state { struct jog_state {
bool left_pressed = false; bool left_pressed = false;
bool right_pressed = false; bool right_pressed = false;
@@ -33,7 +64,7 @@ struct jog_state {
bool speed_fast_pressed = false; bool speed_fast_pressed = false;
bool speed_slow_pressed = false; bool speed_slow_pressed = false;
bool no_jogging() const; [[nodiscard]] bool no_jogging() const;
}; };
static bool operator==(const jog_state& a, const jog_state& b) { static bool operator==(const jog_state& a, const jog_state& b) {
@@ -54,21 +85,41 @@ static bool operator!=(const jog_state& a, const jog_state& b) {
return !(a == b); return !(a == b);
} }
struct machine_listener {
virtual void on_connected() = 0;
virtual void on_disconnected() = 0;
virtual void on_realtime_status_report(realtime_status_report) = 0;
virtual void on_banner(std::string line) = 0;
virtual void on_message(std::string message) = 0;
virtual void on_alarm(int alarm) = 0;
};
struct machine : public transport_callbacks { struct machine : public transport_callbacks {
machine(); machine();
~machine(); ~machine();
void set_listener(grbl::machine_listener *listener);
void connect(); void connect();
void run_program(grbl::program pgm); void run_program(const grbl::program& pgm);
void request_jog(jog_state jog) const; void request_jog(jog_state jog) const;
void cancel_jog() const; void cancel_jog() const;
realtime_status_report get_status() const { return last_report; };
void request_unlock();
void request_home();
void request_reset();
void request_cycle_start();
void request_feed_hold();
protected:
void on_connected(transport *transport) override; void on_connected(transport *transport) override;
void on_disconnected(transport *transport) override; void on_disconnected(transport *transport) override;
void on_line_received(std::string line, transport *transport) override; void on_line_received(std::string line, transport *transport) override;
realtime_status_report last_report{};
void on_banner(std::string version, transport *transport) override; machine_listener* listener = nullptr;
transport *pipe = nullptr; transport *pipe = nullptr;
grbl_machine_state state = grbl_machine_state::init; grbl_machine_state state = grbl_machine_state::init;
program running_program; program running_program;
+113 -419
View File
@@ -58,36 +58,76 @@ using namespace nanogui;
grbl::machine cnc{}; grbl::machine cnc{};
class SenderApp : public Screen { class SenderApp : public Screen, public grbl::machine_listener {
public: public:
Window *window; Window *window;
Window *left_window;
Window *right_window;
grbl::jog_state jog; grbl::jog_state jog;
Label *m_pos_x, *m_pos_y, *m_pos_z;
TextBox *lblStatus, *lblSubstatus;
nanogui::Color red = nanogui::Color(255, 0, 0, 255);
int last_alarm = 0;
SenderApp() : Screen(Vector2i(1024, 768), "GRBL Sender") { SenderApp() : Screen(Vector2i(1024, 768), "GRBL Sender") {
inc_ref(); inc_ref();
window = new Window(this, "Button demo"); window = new Window(this, "Machine status");
// window->set_fixed_height(Screen::size().y()); // window->set_fixed_height(Screen::size().y());
window->set_position(Vector2i(0, 0)); window->set_position(Vector2i(0, 0));
window->set_layout(new GridLayout(nanogui::Orientation::Horizontal, 2, Alignment::Fill, 0, 0)); window->set_layout(new GroupLayout());
window->set_size(Screen::size()); // window->set_size(Screen::size());
left_window = new Window(window, "Left sidebar"); new Label(window, "Status", "sans-bold");
left_window->set_modal(true); Widget *status_holder = new Widget(window);
status_holder->set_layout(new GridLayout());
right_window = new Window(window, "Right sidebar"); lblStatus = new TextBox(window, grbl::status_to_string(cnc.get_status().status));
right_window->set_modal(true); lblSubstatus = new TextBox(window, cnc.get_status().sub_status);
left_window->set_layout(new GroupLayout()); // Machine pos
new Label(window, "Machine pos", "sans-bold");
Widget *mpos = new Widget(window);
mpos->set_layout(new GridLayout());
new Label(mpos, "X");
m_pos_x = new Label(mpos, std::to_string(cnc.get_status().machine_pos[0]));
new Label(mpos, "Y");
m_pos_y = new Label(mpos, std::to_string(cnc.get_status().machine_pos[1]));
new Label(mpos, "Z");
m_pos_z = new Label(mpos, std::to_string(cnc.get_status().machine_pos[2]));
// buttons to change state
new Label(window, "Actions", "sans-bold");
Widget *actions = new Widget(window);
actions->set_layout(new BoxLayout(Orientation::Horizontal));
Button *btnUnlock = new Button(actions, "Unlock");
btnUnlock->set_callback([&] {
cnc.request_unlock();
});
Button *btnHome = new Button(actions, "Home");
btnHome->set_callback([&] {
cnc.request_home();
});
Button *btnReset = new Button(actions, "Reset");
btnReset->set_background_color(red);
btnReset->set_callback([&] {
cnc.request_reset();
});
Button *btnCycleStart = new Button(actions, "Cycle Start");
btnCycleStart->set_callback([&] {
cnc.request_cycle_start();
});
Button *btnFeedHold = new Button(actions, "Feed Hold");
btnFeedHold->set_callback([&] {
cnc.request_feed_hold();
});
// No need to store a pointer, the data structure will be automatically // No need to store a pointer, the data structure will be automatically
// freed when the parent window is deleted // freed when the parent window is deleted
new Label(left_window, "Push buttons", "sans-bold"); new Label(window, "Program", "sans-bold");
Button *b = new Button(left_window, "Plain button"); Button *btnLoadProgram = new Button(window, "Load");
b->set_callback([&] { btnLoadProgram->set_callback([&] {
auto path = file_dialog( auto path = file_dialog(
{{"nc", "G-Code files"}, {{"nc", "G-Code files"},
{"ngc", "G-Code files"}}, false); {"ngc", "G-Code files"}}, false);
@@ -96,38 +136,38 @@ public:
cnc.run_program(pgm); cnc.run_program(pgm);
} }
}); });
b->set_tooltip("short tooltip"); btnLoadProgram->set_tooltip("short tooltip");
/* Alternative construction notation using variadic template */ // Alternative construction notation using variadic template
b = left_window->add<Button>("Styled", FA_ROCKET); btnLoadProgram = window->add<Button>("Styled", FA_ROCKET);
b->set_background_color(Color(0, 0, 255, 25)); btnLoadProgram->set_background_color(Color(0, 0, 255, 25));
b->set_callback([] { std::cout << "pushed!" << std::endl; }); btnLoadProgram->set_callback([] { std::cout << "pushed!" << std::endl; });
b->set_tooltip("This button has a fairly long tooltip. It is so long, in " btnLoadProgram->set_tooltip("This button has a fairly long tooltip. It is so long, in "
"fact, that the shown text will span several lines."); "fact, that the shown text will span several lines.");
new Label(left_window, "Toggle buttons", "sans-bold"); new Label(window, "Toggle buttons", "sans-bold");
b = new Button(left_window, "Toggle me"); btnLoadProgram = new Button(window, "Toggle me");
b->set_flags(Button::ToggleButton); btnLoadProgram->set_flags(Button::ToggleButton);
b->set_change_callback([](bool state) { std::cout << "Toggle button state: " << state << std::endl; }); btnLoadProgram->set_change_callback([](bool state) { std::cout << "Toggle button state: " << state << std::endl; });
new Label(left_window, "Radio buttons", "sans-bold"); new Label(window, "Radio buttons", "sans-bold");
b = new Button(left_window, "Radio button 1"); btnLoadProgram = new Button(window, "Radio button 1");
b->set_flags(Button::RadioButton); btnLoadProgram->set_flags(Button::RadioButton);
b = new Button(left_window, "Radio button 2"); btnLoadProgram = new Button(window, "Radio button 2");
b->set_flags(Button::RadioButton); btnLoadProgram->set_flags(Button::RadioButton);
new Label(left_window, "A tool palette", "sans-bold"); new Label(window, "A tool palette", "sans-bold");
Widget *tools = new Widget(left_window); Widget *tools = new Widget(window);
tools->set_layout(new BoxLayout(Orientation::Horizontal, tools->set_layout(new BoxLayout(Orientation::Horizontal,
Alignment::Middle, 0, 6)); Alignment::Middle, 0, 6));
b = new ToolButton(tools, FA_CLOUD); btnLoadProgram = new ToolButton(tools, FA_CLOUD);
b = new ToolButton(tools, FA_FAST_FORWARD); btnLoadProgram = new ToolButton(tools, FA_FAST_FORWARD);
b = new ToolButton(tools, FA_COMPASS); btnLoadProgram = new ToolButton(tools, FA_COMPASS);
b = new ToolButton(tools, FA_UTENSILS); btnLoadProgram = new ToolButton(tools, FA_UTENSILS);
new Label(left_window, "Popup buttons", "sans-bold"); new Label(window, "Popup buttons", "sans-bold");
PopupButton *popup_btn = new PopupButton(left_window, "Popup", FA_FLASK); PopupButton *popup_btn = new PopupButton(window, "Popup", FA_FLASK);
Popup *popup = popup_btn->popup(); Popup *popup = popup_btn->popup();
popup->set_layout(new GroupLayout()); popup->set_layout(new GroupLayout());
new Label(popup, "Arbitrary widgets can be placed here"); new Label(popup, "Arbitrary widgets can be placed here");
@@ -144,340 +184,6 @@ public:
popup_left->set_layout(new GroupLayout()); popup_left->set_layout(new GroupLayout());
new CheckBox(popup_left, "Another check box"); new CheckBox(popup_left, "Another check box");
/*
window = new Window(this, "Basic widgets");
window->set_position(Vector2i(200, 15));
window->set_layout(new GroupLayout());
new Label(window, "Message dialog", "sans-bold");
tools = new Widget(window);
tools->set_layout(new BoxLayout(Orientation::Horizontal,
Alignment::Middle, 0, 6));
b = new Button(tools, "Info");
b->set_callback([&] {
auto dlg = new MessageDialog(this, MessageDialog::Type::Information, "Title", "This is an information message");
dlg->set_callback([](int result) { std::cout << "Dialog result: " << result << std::endl; });
});
b = new Button(tools, "Warn");
b->set_callback([&] {
auto dlg = new MessageDialog(this, MessageDialog::Type::Warning, "Title", "This is a warning message");
dlg->set_callback([](int result) { std::cout << "Dialog result: " << result << std::endl; });
});
b = new Button(tools, "Ask");
b->set_callback([&] {
auto dlg = new MessageDialog(this, MessageDialog::Type::Question, "Title", "This is a question message", "Yes", "No", true);
dlg->set_callback([](int result) { std::cout << "Dialog result: " << result << std::endl; });
});
#if defined(_WIN32)
/// Executable is in the Debug/Release/.. subdirectory
std::string resources_folder_path("../icons");
#else
std::string resources_folder_path("./icons");
#endif
std::vector<std::pair<int, std::string>> icons;
#if !defined(EMSCRIPTEN)
try {
icons = load_image_directory(m_nvg_context, resources_folder_path);
} catch (const std::exception& e) {
std::cerr << "Warning: " << e.what() << std::endl;
}
#endif
new Label(window, "Image panel & scroll panel", "sans-bold");
PopupButton *image_panel_btn = new PopupButton(window, "Image Panel");
image_panel_btn->set_icon(FA_IMAGES);
popup = image_panel_btn->popup();
VScrollPanel *vscroll = new VScrollPanel(popup);
ImagePanel *img_panel = new ImagePanel(vscroll);
img_panel->set_images(icons);
popup->set_fixed_size(Vector2i(245, 150));
auto image_window = new Window(this, "Selected image");
image_window->set_position(Vector2i(710, 15));
image_window->set_layout(new GroupLayout(3));
// Create a Texture instance for each object
for (auto& icon: icons) {
Vector2i size;
int n = 0;
ImageHolder texture_data(
stbi_load((icon.second + ".png").c_str(), &size.x(), &size.y(), &n, 0),
stbi_image_free);
assert(n == 4);
Texture *tex = new Texture(
Texture::PixelFormat::RGBA,
Texture::ComponentFormat::UInt8,
size,
Texture::InterpolationMode::Trilinear,
Texture::InterpolationMode::Nearest);
tex->upload(texture_data.get());
m_images.emplace_back(tex, std::move(texture_data));
}
ImageView *image_view = new ImageView(image_window);
if (!m_images.empty())
image_view->set_image(m_images[0].first);
image_view->center();
m_current_image = 0;
img_panel->set_callback([this, image_view](int i) {
std::cout << "Selected item " << i << std::endl;
image_view->set_image(m_images[i].first);
m_current_image = i;
});
image_view->set_pixel_callback(
[this](const Vector2i& index, char **out, size_t size) {
const Texture *texture = m_images[m_current_image].first.get();
uint8_t *data = m_images[m_current_image].second.get();
for (int ch = 0; ch < 4; ++ch) {
uint8_t value = data[(index.x() + index.y() * texture->size().x()) * 4 + ch];
snprintf(out[ch], size, "%i", (int) value);
}
}
);
new Label(window, "File dialog", "sans-bold");
tools = new Widget(window);
tools->set_layout(new BoxLayout(Orientation::Horizontal,
Alignment::Middle, 0, 6));
b = new Button(tools, "Open");
b->set_callback([&] {
std::cout << "File dialog result: " << file_dialog(
{{"png", "Portable Network Graphics"},
{"txt", "Text file"}}, false) << std::endl;
});
b = new Button(tools, "Save");
b->set_callback([&] {
std::cout << "File dialog result: " << file_dialog(
{{"png", "Portable Network Graphics"},
{"txt", "Text file"}}, true) << std::endl;
});
new Label(window, "Combo box", "sans-bold");
new ComboBox(window, {"Combo box item 1", "Combo box item 2", "Combo box item 3"});
new Label(window, "Check box", "sans-bold");
CheckBox *cb = new CheckBox(window, "Flag 1",
[](bool state) { std::cout << "Check box 1 state: " << state << std::endl; }
);
cb->set_checked(true);
cb = new CheckBox(window, "Flag 2",
[](bool state) { std::cout << "Check box 2 state: " << state << std::endl; }
);
new Label(window, "Progress bar", "sans-bold");
m_progress = new ProgressBar(window);
new Label(window, "Slider and text box", "sans-bold");
Widget *panel = new Widget(window);
panel->set_layout(new BoxLayout(Orientation::Horizontal,
Alignment::Middle, 0, 20));
Slider *slider = new Slider(panel);
slider->set_value(0.5f);
slider->set_fixed_width(80);
TextBox *text_box = new TextBox(panel);
text_box->set_fixed_size(Vector2i(60, 25));
text_box->set_value("50");
text_box->set_units("%");
slider->set_callback([text_box](float value) {
text_box->set_value(std::to_string((int) (value * 100)));
});
slider->set_final_callback([&](float value) {
std::cout << "Final slider value: " << (int) (value * 100) << std::endl;
});
text_box->set_fixed_size(Vector2i(60, 25));
text_box->set_font_size(20);
text_box->set_alignment(TextBox::Alignment::Right);
window = new Window(this, "Misc. widgets");
window->set_position(Vector2i(425, 15));
window->set_layout(new GroupLayout());
TabWidget *tab_widget = window->add<TabWidget>();
Widget *layer = new Widget(tab_widget);
layer->set_layout(new GroupLayout());
tab_widget->append_tab("Color Wheel", layer);
// Use overloaded variadic add to fill the tab widget with Different tabs.
layer->add<Label>("Color wheel widget", "sans-bold");
layer->add<ColorWheel>();
layer = new Widget(tab_widget);
layer->set_layout(new GroupLayout());
tab_widget->append_tab("Function Graph", layer);
layer->add<Label>("Function graph widget", "sans-bold");
Graph *graph = layer->add<Graph>("Some Function");
graph->set_header("E = 2.35e-3");
graph->set_footer("Iteration 89");
std::vector<float>& func = graph->values();
func.resize(100);
for (int i = 0; i < 100; ++i)
func[i] = 0.5f * (0.5f * std::sin(i / 10.f) +
0.5f * std::cos(i / 23.f) + 1);
// Dummy tab used to represent the last tab button.
int plus_id = tab_widget->append_tab("+", new Widget(tab_widget));
// A simple counter.
int counter = 1;
tab_widget->set_callback([tab_widget, this, counter, plus_id](int id) mutable {
if (id == plus_id) {
// When the "+" tab has been clicked, simply add a new tab.
std::string tab_name = "Dynamic " + std::to_string(counter);
Widget *layer_dyn = new Widget(tab_widget);
int new_id = tab_widget->insert_tab(tab_widget->tab_count() - 1,
tab_name, layer_dyn);
layer_dyn->set_layout(new GroupLayout());
layer_dyn->add<Label>("Function graph widget", "sans-bold");
Graph *graph_dyn = layer_dyn->add<Graph>("Dynamic function");
graph_dyn->set_header("E = 2.35e-3");
graph_dyn->set_footer("Iteration " + std::to_string(new_id * counter));
std::vector<float>& func_dyn = graph_dyn->values();
func_dyn.resize(100);
for (int i = 0; i < 100; ++i)
func_dyn[i] = 0.5f *
std::abs((0.5f * std::sin(i / 10.f + counter) +
0.5f * std::cos(i / 23.f + 1 + counter)));
++counter;
tab_widget->set_selected_id(new_id);
// We must invoke the layout manager after adding tabs dynamically
perform_layout();
}
});
// A button to go back to the first tab and scroll the window.
panel = window->add<Widget>();
panel->add<Label>("Jump to tab: ");
panel->set_layout(new BoxLayout(Orientation::Horizontal,
Alignment::Middle, 0, 6));
auto ib = panel->add<IntBox<int>>();
ib->set_editable(true);
b = panel->add<Button>("", FA_FORWARD);
b->set_fixed_size(Vector2i(22, 22));
ib->set_fixed_height(22);
b->set_callback([tab_widget, ib] {
int value = ib->value();
if (value >= 0 && value < tab_widget->tab_count())
tab_widget->set_selected_index(value);
});
window = new Window(this, "Grid of small widgets");
window->set_position(Vector2i(425, 300));
GridLayout *layout =
new GridLayout(Orientation::Horizontal, 2,
Alignment::Middle, 15, 5);
layout->set_col_alignment(
{Alignment::Maximum, Alignment::Fill});
layout->set_spacing(0, 10);
window->set_layout(layout);
// FP widget
{
new Label(window, "Floating point :", "sans-bold");
text_box = new TextBox(window);
text_box->set_editable(true);
text_box->set_fixed_size(Vector2i(100, 20));
text_box->set_value("50");
text_box->set_units("GiB");
text_box->set_default_value("0.0");
text_box->set_font_size(16);
text_box->set_format("[-]?[0-9]*\\.?[0-9]+");
}
// Positive integer widget
{
new Label(window, "Positive integer :", "sans-bold");
auto int_box = new IntBox<int>(window);
int_box->set_editable(true);
int_box->set_fixed_size(Vector2i(100, 20));
int_box->set_value(50);
int_box->set_units("Mhz");
int_box->set_default_value("0");
int_box->set_font_size(16);
int_box->set_format("[1-9][0-9]*");
int_box->set_spinnable(true);
int_box->set_min_value(1);
int_box->set_value_increment(2);
}
// Checkbox widget
{
new Label(window, "Checkbox :", "sans-bold");
cb = new CheckBox(window, "Check me");
cb->set_font_size(16);
cb->set_checked(true);
}
new Label(window, "Combo box :", "sans-bold");
ComboBox *cobo =
new ComboBox(window, {"Item 1", "Item 2", "Item 3"});
cobo->set_font_size(16);
cobo->set_fixed_size(Vector2i(100, 20));
new Label(window, "Color picker :", "sans-bold");
auto cp = new ColorPicker(window, {255, 120, 0, 255});
cp->set_fixed_size({100, 20});
cp->set_final_callback([](const Color& c) {
std::cout << "ColorPicker final callback: ["
<< c.r() << ", "
<< c.g() << ", "
<< c.b() << ", "
<< c.w() << "]" << std::endl;
});
// setup a fast callback for the color picker widget on a new window
// for demonstrative purposes
window = new Window(this, "Color Picker Fast Callback");
layout = new GridLayout(Orientation::Horizontal, 2,
Alignment::Middle, 15, 5);
layout->set_col_alignment(
{Alignment::Maximum, Alignment::Fill});
layout->set_spacing(0, 10);
window->set_layout(layout);
window->set_position(Vector2i(425, 500));
new Label(window, "Combined: ");
b = new Button(window, "ColorWheel", FA_INFINITY);
new Label(window, "Red: ");
auto red_int_box = new IntBox<int>(window);
red_int_box->set_editable(false);
new Label(window, "Green: ");
auto green_int_box = new IntBox<int>(window);
green_int_box->set_editable(false);
new Label(window, "Blue: ");
auto blue_int_box = new IntBox<int>(window);
blue_int_box->set_editable(false);
new Label(window, "Alpha: ");
auto alpha_int_box = new IntBox<int>(window);
cp->set_callback([b, red_int_box, blue_int_box, green_int_box, alpha_int_box](const Color& c) {
b->set_background_color(c);
b->set_text_color(c.contrasting_color());
int red = (int) (c.r() * 255.0f);
red_int_box->set_value(red);
int green = (int) (c.g() * 255.0f);
green_int_box->set_value(green);
int blue = (int) (c.b() * 255.0f);
blue_int_box->set_value(blue);
int alpha = (int) (c.w() * 255.0f);
alpha_int_box->set_value(alpha);
});
*/
perform_layout(); perform_layout();
// All NanoGUI widgets are initialized at this point. Now // All NanoGUI widgets are initialized at this point. Now
@@ -567,13 +273,41 @@ public:
} }
bool resize_event(const Vector2i& size) override { bool resize_event(const Vector2i& size) override {
window->set_size(size); return Screen::resize_event(size);
Screen::resize_event(size); }
return true;
void on_connected() override {
}
void on_disconnected() override {
}
void on_realtime_status_report(grbl::realtime_status_report report) override {
lblStatus->set_value(grbl::status_to_string(cnc.get_status().status) + " ");
if (cnc.get_status().status == grbl::machine_status::alarm) {
lblSubstatus->set_value(grbl::alarm_to_string(last_alarm));
} else {
lblSubstatus->set_value(cnc.get_status().sub_status);
}
m_pos_x->set_caption(std::to_string(cnc.get_status().machine_pos[0]));
m_pos_y->set_caption(std::to_string(cnc.get_status().machine_pos[1]));
m_pos_z->set_caption(std::to_string(cnc.get_status().machine_pos[2]));
}
void on_banner(std::string line) override {
}
void on_message(std::string message) override {
set_caption(message);
}
void on_alarm(int alarm) override {
last_alarm = alarm;
} }
virtual bool keyboard_event(int key, int scancode, int action, int modifiers) { bool keyboard_event(int key, int scancode, int action, int modifiers) override {
if (Screen::keyboard_event(key, scancode, action, modifiers)) if (Screen::keyboard_event(key, scancode, action, modifiers))
return true; return true;
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
@@ -691,59 +425,15 @@ private:
}; };
struct grbl_listener : public grbl::transport_callbacks {
void on_connected(grbl::transport *t) override {
std::cout << "Listener: 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");
t->send("\xff\xfd\x18");
}
void on_disconnected(grbl::transport *t) override {
std::cout << "Listener: disconnected!" << std::endl;
}
void on_banner(std::string version, grbl::transport *t) override {
std::cout << "Banner: " << version << std::endl;
t->send("$$");
}
void on_line_received(std::string line, grbl::transport *t) override {
std::cout << "Listener: -> " << line << std::endl;
}
};
int main(int argc, char **argv) { int main(int argc, char **argv) {
// grbl_listener listener;
//
// grbl::tcp_transport transport("192.168.5.39", 23);
// transport.open(listener);
testing::InitGoogleTest(&argc, argv); testing::InitGoogleTest(&argc, argv);
auto result = RUN_ALL_TESTS(); auto result = RUN_ALL_TESTS();
if (result) { if (result) {
exit(result); exit(result);
} }
cnc.connect();
// grbl::program pgm{"./program.nc"};
//// pgm.dump(std::cout);
//
// cnc.run_program(pgm);
// transport.request_realtime_report();
// transport.request_cycle_start();
// transport.request_feed_hold();
// transport.parser_state_report();
try { try {
// grbl::program pgm;
// pgm.load("./program.nc");
// pgm.dump(std::cout);
nanogui::init(); nanogui::init();
// scoped variables // scoped variables
@@ -752,6 +442,10 @@ int main(int argc, char **argv) {
app->dec_ref(); app->dec_ref();
app->draw_all(); app->draw_all();
app->set_visible(true); app->set_visible(true);
cnc.set_listener(app);
cnc.connect();
nanogui::mainloop(1 / 60.f * 1000); nanogui::mainloop(1 / 60.f * 1000);
} }
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <vector>
#include <string>
std::vector<std::string> split_string(const std::string& in, const std::string& delimiter) {
std::vector<std::string> result{};
size_t last{0}, next;
while ((next = in.find(delimiter, last)) != std::string::npos) {
result.push_back(in.substr(last, next - last));
last = next + delimiter.size();
}
result.push_back(in.substr(last));
return result;
}
std::string& ltrim(std::string& str, const std::string& chars = "\t\n\v\f\r ") {
str.erase(0, str.find_first_not_of(chars));
return str;
}
std::string ltrim(std::string&& str, const std::string& chars = "\t\n\v\f\r ") {
str.erase(0, str.find_first_not_of(chars));
return str;
}
std::string& rtrim(std::string& str, const std::string& chars = "\t\n\v\f\r ") {
str.erase(str.find_last_not_of(chars) + 1);
return str;
}
std::string rtrim(std::string&& str, const std::string& chars = "\t\n\v\f\r ") {
str.erase(str.find_last_not_of(chars) + 1);
return str;
}
std::string& trim(std::string& str, const std::string& chars = "\t\n\v\f\r ") {
return ltrim(rtrim(str, chars), chars);
}
std::string trim(std::string&& str, const std::string& chars = "\t\n\v\f\r ") {
return ltrim(rtrim(str, chars), chars);
}