From b7b9fed0dd39bdc95668e30e59314831828d93ac Mon Sep 17 00:00:00 2001 From: Adrian Scripca Date: Tue, 23 May 2023 14:28:08 +0300 Subject: [PATCH] Exposed exaggeration factor when rendering heightmap. Added checkbox to select whether to run autoleveled program or not. Heightmap can handle non integer from_x and to_x. --- grbl_machine.cpp | 62 ++++++++++++++++++++--------------- heightmap.cpp | 36 +++++++++++--------- heightmap.h | 21 ++++++------ heightmap_test.cpp | 23 +++++++++++++ render.cpp | 82 +--------------------------------------------- render.h | 2 +- sender_app.cpp | 31 ++++++++++++++---- sender_app.h | 4 ++- 8 files changed, 118 insertions(+), 143 deletions(-) diff --git a/grbl_machine.cpp b/grbl_machine.cpp index c5a6444..1b6846c 100644 --- a/grbl_machine.cpp +++ b/grbl_machine.cpp @@ -38,7 +38,7 @@ void grbl::machine::on_disconnected(grbl::transport *transport) { switch_to_state(grbl_machine_state::disconnected); } -grbl::realtime_status_report grbl::parse_status_report(std::string line, grbl::realtime_status_report& result) { +grbl::realtime_status_report grbl::parse_status_report(std::string line, grbl::realtime_status_report &result) { // grbl::realtime_status_report result; // pin values are always reset when a report arrives @@ -71,7 +71,7 @@ grbl::realtime_status_report grbl::parse_status_report(std::string line, grbl::r result.buffers_free = std::stoi(p[0]); result.rx_chars_free = std::stoi(p[1]); } else if (elements[0] == "Pn") { - for (auto& c: elements[1]) { + for (auto &c: elements[1]) { switch (c) { case 'P': result.signals.bit.probe = true; @@ -111,7 +111,7 @@ grbl::realtime_status_report grbl::parse_status_report(std::string line, grbl::r } -grbl::machine_status grbl::status_from_string(const std::string& status) { +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; @@ -125,9 +125,10 @@ grbl::machine_status grbl::status_from_string(const std::string& status) { return machine_status::unknown; } -void grbl::machine::check_program(const grbl::program& pgm) { +void grbl::machine::check_program(const grbl::program &pgm) { running_program = pgm; - std::cout << "checking program (" << running_program.filename << ") with " << running_program.number_of_instructions() + std::cout << "checking program (" << running_program.filename << ") with " + << running_program.number_of_instructions() << " instructions" << std::endl; switch_to_state(grbl_machine_state::check_program); } @@ -149,9 +150,10 @@ void grbl::machine::set_work_offset(std::string work_offset) { } -void grbl::machine::run_program(const grbl::program& pgm, const std::string& work_offset) { +void grbl::machine::run_program(const grbl::program &pgm, const std::string &work_offset) { running_program = pgm; - std::cout << "running program (" << running_program.filename << ") with " << running_program.number_of_instructions() << " instructions" + std::cout << "running program (" << running_program.filename << ") with " + << running_program.number_of_instructions() << " instructions" << " on work offset " << work_offset << std::endl; set_work_offset(work_offset); @@ -159,7 +161,7 @@ void grbl::machine::run_program(const grbl::program& pgm, const std::string& wor switch_to_state(grbl_machine_state::run_program); } -std::string grbl::status_to_string(const grbl::machine_status& status) { +std::string grbl::status_to_string(const grbl::machine_status &status) { switch (status) { case machine_status::idle: return "Idle"; @@ -590,11 +592,11 @@ void grbl::machine::switch_to_state(grbl::grbl_machine_state new_state) { states[state]->on_enter(this); } -const std::map& grbl::machine::get_settings() const { +const std::map &grbl::machine::get_settings() const { return settings; } -const std::map& grbl::machine::get_parameters() const { +const std::map &grbl::machine::get_parameters() const { return parameters; } @@ -683,7 +685,7 @@ void grbl::machine::start_z_probe(float min_z, float feed_rate) { awaiting_responses++; } -void grbl::machine::probe_heightmap(grbl::heightmap& grid) { +void grbl::machine::probe_heightmap(grbl::heightmap &grid) { std::cout << "probing heightmap" << std::endl; dynamic_cast(states[grbl_machine_state::heightmap_probing])->grid = &grid; switch_to_state(grbl_machine_state::heightmap_probing); @@ -756,7 +758,8 @@ void grbl::machine_state_init::on_line_received(std::string line) { if (starts_with(line, "$")) { auto pieces = split_string(line, "="); cnc->settings[pieces[0]] = pieces[1]; - } else if (starts_with(line, "[G") || starts_with(line, "[H") || starts_with(line, "[T") || starts_with(line, "[P")) { + } else if (starts_with(line, "[G") || starts_with(line, "[H") || starts_with(line, "[T") || + starts_with(line, "[P")) { line = line.substr(1, line.size() - 2); // TODO: some parameters have more than two : auto pieces = split_string(line, ":"); @@ -833,7 +836,8 @@ void grbl::machine_state_idle::on_line_received(std::string line) { } else if (starts_with(line, "$")) { auto pieces = split_string(line, "="); cnc->settings[pieces[0]] = pieces[1]; - } else if (starts_with(line, "[G") || starts_with(line, "[H") || starts_with(line, "[T") || starts_with(line, "[P")) { + } else if (starts_with(line, "[G") || starts_with(line, "[H") || starts_with(line, "[T") || + starts_with(line, "[P")) { line = line.substr(1, line.size() - 2); // TODO: some parameters have more than two : auto pieces = split_string(line, ":"); @@ -948,7 +952,8 @@ bool grbl::machine_state_check_program::continue_program() { instruction to_send; do { to_send = cnc->running_program.instruction_at(cnc->executed_instructions++); - } while (to_send.type != instruction_type::gcode && cnc->executed_instructions < cnc->running_program.number_of_instructions()); + } while (to_send.type != instruction_type::gcode && + cnc->executed_instructions < cnc->running_program.number_of_instructions()); if (to_send.type == instruction_type::gcode) { cnc->pipe->send(to_send.command); @@ -1014,7 +1019,8 @@ bool grbl::machine_state_run_program::continue_program() { instruction to_send; do { to_send = cnc->running_program.instruction_at(cnc->executed_instructions++); - } while (to_send.type != instruction_type::gcode && cnc->executed_instructions < cnc->running_program.number_of_instructions()); + } while (to_send.type != instruction_type::gcode && + cnc->executed_instructions < cnc->running_program.number_of_instructions()); if (to_send.type == instruction_type::gcode) { cnc->pipe->send(to_send.command); @@ -1116,21 +1122,23 @@ bool grbl::machine_state_heightmap_probing::continue_program() { void grbl::machine_state_heightmap_probing::move_to_next_stage() { switch (stage) { case heightmap_probing_stage::start: - cnc->pipe->send("G0X0Y0Z15"); + cnc->pipe->send("G0 X" + std::to_string(grid->vertices[0].x) + " Y" + + std::to_string(grid->vertices[0].y) + "Z0.5"); + stage = heightmap_probing_stage::goto_home; break; case heightmap_probing_stage::goto_home: - cnc->pipe->send("G38.2 Z-65 F100"); + cnc->pipe->send("G38.2 Z-65 F5"); stage = heightmap_probing_stage::initial_probe_step_back; break; case heightmap_probing_stage::initial_probe_step_back: // step back a bit (1mm, relative) - cnc->pipe->send("G91 G0 Z1"); + cnc->pipe->send("G91 G0 Z0.3"); stage = heightmap_probing_stage::initial_probe_fine_seek; break; case heightmap_probing_stage::initial_probe_fine_seek: // probe again but finer - cnc->pipe->send(" G38.2 Z-5 F5"); + cnc->pipe->send("G38.2 Z-1 F5"); stage = heightmap_probing_stage::initial_probe; break; case heightmap_probing_stage::initial_probe: @@ -1147,7 +1155,6 @@ void grbl::machine_state_heightmap_probing::move_to_next_stage() { grid->vertices[probed_locations].z = 0; } - stage = heightmap_probing_stage::goto_next_location; break; case heightmap_probing_stage::goto_next_location: { @@ -1172,7 +1179,7 @@ void grbl::machine_state_heightmap_probing::move_to_next_stage() { cnc->pipe->send("G0Z15"); // safe height stage = heightmap_probing_stage::done; } else { - cnc->pipe->send("G90 G0 Z1.5"); + cnc->pipe->send("G90 G0 Z1"); stage = heightmap_probing_stage::goto_next_location_move; } break; @@ -1182,13 +1189,13 @@ void grbl::machine_state_heightmap_probing::move_to_next_stage() { stage = heightmap_probing_stage::goto_start_probing_at; break; case heightmap_probing_stage::goto_start_probing_at: - cnc->pipe->send("G0 Z0.5"); // this appears to move Z upwards instead of downwards. why? + cnc->pipe->send("G0 Z0.3"); // this appears to move Z upwards instead of downwards. why? // faking the G0 Z0.5 // cnc->pipe->send("G91 G0 Z-1"); // 1.5 - 1 = 0.5.// stage = heightmap_probing_stage::probing; break; case heightmap_probing_stage::probing: - cnc->pipe->send("G38.2 Z-5 F5"); + cnc->pipe->send("G38.2 Z-1 F5"); stage = heightmap_probing_stage::goto_next_location; break; case heightmap_probing_stage::done: @@ -1208,17 +1215,17 @@ grbl::machine_event_disconnect::machine_event_disconnect() { machine_event::type = machine_event_type::disconnected; } -grbl::machine_event_report_received::machine_event_report_received(const grbl::realtime_status_report& r) +grbl::machine_event_report_received::machine_event_report_received(const grbl::realtime_status_report &r) : report(r) { machine_event::type = machine_event_type::report_received; } -grbl::machine_event_banner::machine_event_banner(const std::string& b) +grbl::machine_event_banner::machine_event_banner(const std::string &b) : banner{b} { machine_event::type = machine_event_type::banner; } -grbl::machine_event_message::machine_event_message(const std::string& m) +grbl::machine_event_message::machine_event_message(const std::string &m) : message{m} { machine_event::type = machine_event_type::message; } @@ -1260,7 +1267,8 @@ grbl::machine_event_probe_result::machine_event_probe_result(bool touched, const machine_event::type = machine_event_type::probe_result; } -grbl::machine_event_heightmap_probe_acquired::machine_event_heightmap_probe_acquired(grbl::heightmap *g, size_t location) +grbl::machine_event_heightmap_probe_acquired::machine_event_heightmap_probe_acquired(grbl::heightmap *g, + size_t location) : grid(g), probed_location(location) { machine_event::type = machine_event_type::heightmap_probe_acquired; diff --git a/heightmap.cpp b/heightmap.cpp index b3f57eb..6e0bee6 100644 --- a/heightmap.cpp +++ b/heightmap.cpp @@ -30,8 +30,8 @@ grbl::heightmap grbl::heightmap::from_params(float from_x, float from_y, float t size_t grbl::heightmap::index_from_coords(float x, float y) const { size_t result = 0; - for (auto& v: vertices) { - if (v.x == x && v.y == y) { + for (auto &v: vertices) { + if (float_equal(v.x, x) && float_equal(v.y, y)) { return result; } result++; @@ -44,21 +44,25 @@ float grbl::heightmap::get_z_at(float x, float y) const { // TODO: make faster by precalculating indices - float x1 = x - fmodf(x, resolution); - float x2 = x1 + resolution; - float y1 = y - fmodf(y, resolution); - float y2 = y1 + resolution; - float z11 = vertices[index_from_coords(x1, y1)].z; - float z12 = vertices[index_from_coords(x1, y2)].z; - float z21 = vertices[index_from_coords(x2, y1)].z; - float z22 = vertices[index_from_coords(x2, y2)].z; + double x1 = x - fmod(x - from_x, resolution); + double x2 = x1 + resolution; + double y1 = y - fmod(y - from_y, resolution); + double y2 = y1 + resolution; + double z11 = vertices[index_from_coords(x1, y1)].z; + double z12 = vertices[index_from_coords(x1, y2)].z; + double z21 = vertices[index_from_coords(x2, y1)].z; + double z22 = vertices[index_from_coords(x2, y2)].z; - float alpha_x = (x - x1) / resolution; - float a = z11 + (z21 - z11) * alpha_x; - float b = z12 + (z22 - z12) * alpha_x; + double alpha_x = (x - x1) / (double) resolution; + double a = z11 + (z21 - z11) * alpha_x; + double b = z12 + (z22 - z12) * alpha_x; - float alpha_y = (y - y1) / resolution; - float c = a + (b - a) * alpha_y; + double alpha_y = (y - y1) / (double) resolution; + double c = a + (b - a) * alpha_y; - return c; + return (float) c; +} + +bool grbl::float_equal(float a, float b) { + return fabs(a - b) < 0.0001; } diff --git a/heightmap.h b/heightmap.h index 7e659c5..a659232 100644 --- a/heightmap.h +++ b/heightmap.h @@ -7,17 +7,18 @@ namespace grbl { -struct heightmap { - static heightmap from_params(float from_x, float from_y, float to_x, float to_y, float resolution); - float get_z_at(float x, float y) const; - size_t index_from_coords(float x, float y) const; + bool float_equal(float a, float b); - float from_x, from_y; - float to_x, to_y; - float resolution; - size_t x_segments, y_segments; - std::vector vertices; -}; + struct heightmap { + static heightmap from_params(float from_x, float from_y, float to_x, float to_y, float resolution); + float get_z_at(float x, float y) const; + size_t index_from_coords(float x, float y) const; + float from_x, from_y; + float to_x, to_y; + float resolution; + size_t x_segments, y_segments; + std::vector vertices; + }; } diff --git a/heightmap_test.cpp b/heightmap_test.cpp index be5bb56..a0c6fbc 100644 --- a/heightmap_test.cpp +++ b/heightmap_test.cpp @@ -23,4 +23,27 @@ TEST(heightmap, get_z_at) { z = grid.get_z_at(1, 0.5f); EXPECT_EQ(2, z); +} + +TEST(heightmap, get_z_at_non_zero_origin) { + auto grid = grbl::heightmap::from_params(0 - 0.123, 0 + 0.234, 1 - 0.123, 1 + 0.234, 1); + grid.vertices[0].z = 0; + grid.vertices[1].z = 1; + grid.vertices[2].z = 2; + grid.vertices[3].z = 3; + + auto z = grid.get_z_at(0.5f - 0.123f, 0.5f + 0.234f); + EXPECT_EQ(true, grbl::float_equal(1.5, z)); + + z = grid.get_z_at(0.5f - 0.123f, 0 + 0.234f); + EXPECT_EQ(true, grbl::float_equal(0.5, z)); + + z = grid.get_z_at(0.5f - 0.123f, 1 + 0.234f); + EXPECT_EQ(true, grbl::float_equal(2.5, z)); + + z = grid.get_z_at(0 - 0.123f, 0.5f + 0.234f); + EXPECT_EQ(true, grbl::float_equal(1, z)); + + z = grid.get_z_at(1 - 0.123f, 0.5f + 0.234f); + EXPECT_EQ(true, grbl::float_equal(2, z)); } \ No newline at end of file diff --git a/render.cpp b/render.cpp index 2fdfc09..38afd62 100644 --- a/render.cpp +++ b/render.cpp @@ -349,85 +349,6 @@ GLsizei grbl::program_renderer::update_model_vbo(const grbl::program &pgm) { min_pos = glm::vec3(std::numeric_limits::max()); max_pos = glm::vec3(std::numeric_limits::min()); - min_pos = max_pos = glm::vec3(0); - /* - static auto movement_re = std::regex(R"(([gG]0*1?\s+|[xXyYzZ]\s*[0-9\.\-]+))"); - bool is_tool_on = false; - - glm::vec3 tool_pos; - min_pos = max_pos = tool_pos = glm::vec3(0); - - std::vector buffer_data; - for (auto& i: pgm.instructions) { - if (i.type == grbl::instruction_type::gcode) { - - std::vector tokens; - std::smatch res; - std::string::const_iterator start(i.command.cbegin()); - while (std::regex_search(start, i.command.cend(), res, movement_re)) { - auto str = res[0].str(); - - // make upper case - std::transform(str.begin(), str.end(), str.begin(), ::toupper); - - // remove whitespace from things like "X 123.1234" - str.erase(remove_if(str.begin(), str.end(), isspace), str.end()); - - tokens.push_back(str); - start = res.suffix().first; - } - - if (!tokens.empty()) { - auto new_pos = tool_pos; - - for (auto& t: tokens) { - if (t[0] == 'X') { - new_pos.x = std::stof(t.substr(1)); - } else if (t[0] == 'Y') { - new_pos.y = std::stof(t.substr(1)); - } else if (t[0] == 'Z') { - new_pos.z = std::stof(t.substr(1)); - } - } - - bool has_g1 = tokens[0][0] == 'G' && std::stoi(tokens[0].substr(1)) == 1; - bool has_g0 = tokens[0][0] == 'G' && std::stoi(tokens[0].substr(1)) == 0; - bool is_plunge = has_g1 && tokens.size() > 1 && tokens[1][0] == 'Z'; - bool is_retract = has_g0 && tokens.size() > 1 && tokens[1][0] == 'Z'; - - is_tool_on = has_g1 || (is_retract ? false : is_tool_on); - - auto from_color = glm::vec4(1.0f); - auto to_color = glm::vec4(1.0f); - if (is_tool_on) { - from_color = glm::vec4(1, 0.6, 0.6, 1); - to_color = glm::vec4(1, 0.6, 0.6, 1); - } - - if (is_plunge) { - to_color = glm::vec4(1, 0.6, 0.6, 1); - } else if (is_retract) { - to_color = glm::vec4(0.6, 1, 0.6, 1); - } - - add_line(buffer_data, tool_pos, from_color, new_pos, to_color); - - // calculate extents - min_pos.x = std::min(min_pos.x, new_pos.x); - min_pos.y = std::min(min_pos.y, new_pos.y); - min_pos.z = std::min(min_pos.z, new_pos.z); - - max_pos.x = std::max(max_pos.x, new_pos.x); - max_pos.y = std::max(max_pos.y, new_pos.y); - max_pos.z = std::max(max_pos.z, new_pos.z); - - tool_pos = new_pos; - } - } - } - */ - - std::vector buffer_data; grbl::grbl_parser parser; @@ -475,7 +396,7 @@ GLsizei grbl::program_renderer::update_model_vbo(const grbl::program &pgm) { } -void grbl::program_renderer::update_grid(const grbl::heightmap &grid) { +void grbl::program_renderer::update_grid(const grbl::heightmap &grid, float exaggeration_factor) { glm::vec4 color = {0.5, 0.3, 0, 1}; @@ -496,7 +417,6 @@ void grbl::program_renderer::update_grid(const grbl::heightmap &grid) { auto p3 = grid.vertices[next]; // exaggerate Z - auto exaggeration_factor = 100.0f; p1.z *= exaggeration_factor; p2.z *= exaggeration_factor; p3.z *= exaggeration_factor; diff --git a/render.h b/render.h index 5d3814b..03fefb2 100644 --- a/render.h +++ b/render.h @@ -19,7 +19,7 @@ public: glm::vec3 get_extents_max() const { return max_pos; }; - void update_grid(const grbl::heightmap& grid); + void update_grid(const grbl::heightmap& grid, float exaggeration_factor); private: GLsizei update_model_vbo(const grbl::program& pgm); void update_model_extents(glm::vec3 point); diff --git a/sender_app.cpp b/sender_app.cpp index e78b3b0..6ddcda8 100644 --- a/sender_app.cpp +++ b/sender_app.cpp @@ -102,7 +102,7 @@ void SenderApp::add_work_parameters_markup() { auto work_btn_holder = work_holder->add(); work_btn_holder->set_layout( - new nanogui::GridLayout(nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Fill, 0, 6)); + new nanogui::GridLayout(nanogui::Orientation::Horizontal, 4, nanogui::Alignment::Fill, 0, 6)); btn_load_program = work_btn_holder->add("Load"); @@ -132,12 +132,16 @@ void SenderApp::add_work_parameters_markup() { btn_run_program = work_btn_holder->add("Run"); // btnRunProgram->set_enabled(false); btn_run_program->set_callback([&] { -// auto leveled_pgm = pgm.apply_heightmap(heightmap_grid); -// leveled_pgm.save("output_corrected6.nc"); - cnc.run_program(pgm, "G" + std::to_string(cbo_work_offset->selected_index() + 54)); + auto leveled_pgm = pgm.apply_heightmap(heightmap_grid); + leveled_pgm.save("last-run-autoleveled.nc"); + cnc.run_program(btn_run_autoleveled->pushed() ? leveled_pgm : pgm, + "G" + std::to_string(cbo_work_offset->selected_index() + 54)); }); btn_run_program->set_tooltip("Execute program"); + btn_run_autoleveled = work_btn_holder->add("", FA_TABLE); + btn_run_autoleveled->set_flags(nanogui::Button::ToggleButton); + btn_run_autoleveled->set_tooltip("Run auto-leveled program version, using the current heightmap"); } void SenderApp::add_status_markup() { @@ -490,7 +494,7 @@ void SenderApp::add_heightmap_markup() { if (!path.empty()) { heightmap_grid = load_heightmap(path); fill_heightmap_controls_from_grid(heightmap_grid); - renderer.update_grid(heightmap_grid); + renderer.update_grid(heightmap_grid, std::stof(exaggeration_factors[cbo_exaggeration->selected_index()])); } }); @@ -566,6 +570,19 @@ void SenderApp::add_heightmap_markup() { save_heightmap(heightmap_grid, path); } }); + + heightmap_layer->add("Grid rendering", "sans-bold", 20); + auto heightmap_render_holder = heightmap_layer->add(); + heightmap_render_holder->set_layout( + new nanogui::GridLayout(nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill, 4, 4)); + + + heightmap_render_holder->add("Exaggeration factor"); + cbo_exaggeration = heightmap_render_holder->add(exaggeration_factors); + cbo_exaggeration->set_callback([&](int idx) { + renderer.update_grid(heightmap_grid, std::stof(exaggeration_factors[cbo_exaggeration->selected_index()])); + }); + } void SenderApp::fill_heightmap_from_model() const { @@ -590,7 +607,7 @@ void SenderApp::update_grid() { to_y != heightmap_grid.to_y || res != heightmap_grid.resolution) { heightmap_grid = std::move(grbl::heightmap::from_params(from_x, from_y, to_x, to_y, res)); - renderer.update_grid(heightmap_grid); + renderer.update_grid(heightmap_grid, std::stof(exaggeration_factors[cbo_exaggeration->selected_index()])); } } @@ -996,7 +1013,7 @@ void SenderApp::draw(NVGcontext *ctx) { case grbl::machine_event_type::heightmap_probe_acquired: { std::cout << "Updating grid" << std::endl; auto ev = dynamic_cast(event.get()); - renderer.update_grid(*ev->grid); + renderer.update_grid(*ev->grid, std::stof(exaggeration_factors[cbo_exaggeration->selected_index()])); break; } } diff --git a/sender_app.h b/sender_app.h index 0243aaf..93492bf 100644 --- a/sender_app.h +++ b/sender_app.h @@ -115,12 +115,13 @@ private: nanogui::Widget *parameters_layer = nullptr; nanogui::TextBox *mpos_x_text = nullptr, *mpos_y_text = nullptr, *mpos_z_text = nullptr; - nanogui::ComboBox *cbo_work_offset = nullptr, *cbo_tool = nullptr, *cbo_jog_feed_rates = nullptr, *cbo_jog_distance = nullptr; + nanogui::ComboBox *cbo_work_offset = nullptr, *cbo_tool = nullptr, *cbo_jog_feed_rates = nullptr, *cbo_jog_distance = nullptr, *cbo_exaggeration = nullptr; nanogui::ToolButton *btn_pin_door = nullptr, *btn_pin_hold = nullptr, *btn_pin_reset = nullptr, *btn_pin_cycle_start = nullptr; nanogui::ToolButton *btn_pin_limit_x = nullptr, *btn_pin_limit_y = nullptr, *btn_pin_limit_z = nullptr, *btn_pin_probe = nullptr; nanogui::Button *btn_keyboard_jog = nullptr; + nanogui::Button *btn_run_autoleveled = nullptr; std::stringstream dro_ss; grbl::heightmap heightmap_grid; @@ -136,6 +137,7 @@ private: std::vector jog_distances = {"0.01", "0.1", "1", "10"}; std::vector jog_feed_rates = {"5", "100", "500", "1000"}; + std::vector exaggeration_factors = {"0", "1", "5", "10", "20", "50", "100"}; int last_alarm = 0;