diff --git a/README.md b/README.md index fca5fa9..5e356fb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## grblHAL ## -__New:__ The core now has support for up to 8 axes, and for 4-6 axes configurations ABC axis letters can be remapped to UVW. +__New:__ A web app for [building for some drivers](http://svn.io-engineering.com:8080/) is now in the works, feedback will be appreciated. grblHAL has [many extensions](https://github.com/grblHAL/core/wiki) that may cause issues with some senders. As a workaround for these a [compile time option](https://github.com/grblHAL/core/wiki/Changes-from-grbl-1.1#workaround) has been added that disables extensions selectively. @@ -13,7 +13,7 @@ It has been written to complement grblHAL and has features such as proper keyboa --- -Latest build date is 20221005, see the [changelog](changelog.md) for details. +Latest build date is 20221018, see the [changelog](changelog.md) for details. __NOTE:__ A settings reset will be performed on an update for versions earlier than 20211122. Backup and restore of settings is recommended. __IMPORTANT!__ A new setting has been introduced for ganged axes motors in version 20211121. I have only bench tested this for a couple of drivers, correct function should be verified after updating by those who have more than three motors configured. @@ -86,4 +86,4 @@ List of Supported G-Codes: Some [plugins](https://github.com/grblHAL/plugins) implements additional M-codes. --- -2022-10-07 +2022-10-21 diff --git a/changelog.md b/changelog.md index f3cdd52..f58b304 100644 --- a/changelog.md +++ b/changelog.md @@ -1,10 +1,41 @@ ## grblHAL changelog +Build 202210118 + +Core: + +* Added new setting `$346` for action to take after tool change: either return controlled point \(tool tip\) back to the same position as before the M6 command \(default\) or move spindle to Z home only. + +* Added spindle type property to HAL, "hardened" code. + +* Fix for issue #191, allow homing of rotary axes with infinite rotation \(max travel = 0\). + +Plugins: + +* Spindle: Added spindle type property to registration data. + +* Fans: Bug fix, added off delay option for fan 0 with setting `$480` specifying number of minutes to delay. Useful for allowing an exhaust fan to clear a laser cutter enclosure before turning it off. +__Note:__ The new setting may cause a reset of other plugin settings to default values, backup and restore. + +* Laser coolant: implemented off delay and coolant lost monitoring. + +Drivers: + +* STM32F4xx: Fix for [issue #99](https://github.com/grblHAL/STM32F4xx/issues/99). Updated spindle registration. + +* SAM3X8E: Fix for [issue #15](https://github.com/grblHAL/SAM3X8E/issues/15). + +* Many: Improved driver PWM spindle code. + +* Some: Updated for [Web Builder](http://svn.io-engineering.com:8080/) requirements. + +--- + Build 20221009 Core: -* Fix for bug preventing some hosts (Win7) querying SSDP information. +* Fix for bug preventing some hosts \(Win7\) querying SSDP information. Plugins: diff --git a/core_handlers.h b/core_handlers.h index 65ddd23..802f95f 100644 --- a/core_handlers.h +++ b/core_handlers.h @@ -68,6 +68,7 @@ typedef bool (*enqueue_gcode_ptr)(char *data); typedef bool (*protocol_enqueue_realtime_command_ptr)(char c); typedef void (*on_state_change_ptr)(sys_state_t state); +typedef void (*on_spindle_programmed_ptr)(spindle_state_t spindle, float rpm, spindle_rpm_mode_t mode); typedef void (*on_program_completed_ptr)(program_flow_t program_flow, bool check_mode); typedef void (*on_execute_realtime_ptr)(sys_state_t state); typedef void (*on_unknown_accessory_override_ptr)(uint8_t cmd); @@ -99,6 +100,7 @@ typedef struct { // grbl core events - may be subscribed to by drivers or by the core. on_state_change_ptr on_state_change; on_report_handlers_init_ptr on_report_handlers_init; + on_spindle_programmed_ptr on_spindle_programmed; on_program_completed_ptr on_program_completed; on_execute_realtime_ptr on_execute_realtime; on_execute_realtime_ptr on_execute_delay; diff --git a/driver_opts.h b/driver_opts.h index 89dd10d..3e2d919 100644 --- a/driver_opts.h +++ b/driver_opts.h @@ -372,12 +372,12 @@ #define NETWORK_AP_HOSTNAME "grblHAL_AP" #endif #ifndef NETWORK_AP_IPMODE -#define NETWORK_AP_IPMODE 0 // 0 = static, 1 = DHCP, 2 = AutoIP +#define NETWORK_AP_IPMODE 0 // static #endif #ifndef NETWORK_AP_IP #define NETWORK_AP_IP "192.168.5.1" #endif -#ifndef NETWORK_AP_GATEWAYs +#ifndef NETWORK_AP_GATEWAY #define NETWORK_AP_GATEWAY "192.168.5.1" #endif #ifndef NETWORK_AP_MASK diff --git a/gcode.c b/gcode.c index b3da2ac..e49747f 100644 --- a/gcode.c +++ b/gcode.c @@ -609,7 +609,7 @@ status_code_t gc_execute_block(char *block) memset(&gc_block, 0, sizeof(gc_block)); // Initialize the parser block struct. memcpy(&gc_block.modal, &gc_state.modal, sizeof(gc_state.modal)); // Copy current modes - bool set_tool = false; + bool set_tool = false, spindle_programmed = false; axis_command_t axis_command = AxisCommand_None; uint_fast8_t port_command = 0; plane_t plane; @@ -1437,6 +1437,8 @@ status_code_t gc_execute_block(char *block) gc_state.modal.spindle_rpm_mode = gc_block.modal.spindle_rpm_mode; } + spindle_programmed = gc_block.words.s && !user_words.s; + if (!gc_block.words.s) gc_block.values.s = gc_state.modal.spindle_rpm_mode == SpindleSpeedMode_RPM ? gc_state.spindle.rpm : gc_state.spindle.css.surface_speed; else if(!user_words.s && gc_state.modal.spindle_rpm_mode == SpindleSpeedMode_CSS) @@ -2698,10 +2700,13 @@ status_code_t gc_execute_block(char *block) // Update spindle control and apply spindle speed when enabling it in this block. // NOTE: All spindle state changes are synced, even in laser mode. Also, plan_data, // rather than gc_state, is used to manage laser state for non-laser motions. - if(spindle_sync(0, gc_block.modal.spindle, plan_data.spindle.rpm)) + if((spindle_programmed = spindle_sync(0, gc_block.modal.spindle, plan_data.spindle.rpm))) gc_state.modal.spindle = gc_block.modal.spindle; } + if(spindle_programmed && grbl.on_spindle_programmed) + grbl.on_spindle_programmed(gc_state.modal.spindle, gc_state.spindle.rpm, gc_state.modal.spindle_rpm_mode); + // TODO: Recheck spindle running in CCS mode (is_rpm_pos_adjusted = On)? plan_data.condition.spindle = gc_state.modal.spindle; // Set condition flag for planner use. diff --git a/grbl.h b/grbl.h index 42126f3..fc12f78 100644 --- a/grbl.h +++ b/grbl.h @@ -34,7 +34,7 @@ #else #define GRBL_VERSION "1.1f" #endif -#define GRBL_BUILD 20221009 +#define GRBL_BUILD 20221018 #define GRBL_URL "https://github.com/grblHAL" diff --git a/grbllib.c b/grbllib.c index 576a42a..f01b588 100644 --- a/grbllib.c +++ b/grbllib.c @@ -135,6 +135,7 @@ int grbl_enter (void) nvs_buffer_alloc(); // Allocate memory block for NVS buffer #endif + settings_clear(); report_init_fns(); #ifdef KINEMATICS_API diff --git a/machine_limits.c b/machine_limits.c index c6bf96d..3108e78 100644 --- a/machine_limits.c +++ b/machine_limits.c @@ -272,14 +272,23 @@ static bool limits_homing_cycle (axes_signals_t cycle, axes_signals_t auto_squar step_pin[idx] = bit(idx); #endif // Set target based on max_travel setting. Ensure homing switches engaged with search scalar. - // NOTE: settings.max_travel[] is stored as a negative value. - if (bit_istrue(cycle.mask, bit(idx))) { - max_travel = max(max_travel,(-HOMING_AXIS_SEARCH_SCALAR) * settings.axis[idx].max_travel); + // NOTE: settings.axis[].max_travel is stored as a negative value. + if(bit_istrue(cycle.mask, bit(idx))) { +#if N_AXIS > 3 + if(bit_istrue(settings.steppers.is_rotational.mask, bit(idx))) + max_travel = max(max_travel, (-HOMING_AXIS_SEARCH_SCALAR) * (settings.axis[idx].max_travel < -0.0f ? settings.axis[idx].max_travel : -360.0f)); + else +#endif + max_travel = max(max_travel, (-HOMING_AXIS_SEARCH_SCALAR) * settings.axis[idx].max_travel); + if(bit_istrue(auto_square.mask, bit(idx))) dual_motor_axis = idx; } } while(idx); + if(max_travel == 0.0f) + return true; + if(auto_square.mask) { float fail_distance = (-settings.homing.dual_axis.fail_length_percent / 100.0f) * settings.axis[dual_motor_axis].max_travel; fail_distance = min(fail_distance, settings.homing.dual_axis.fail_distance_max); diff --git a/plugins.h b/plugins.h index ed68b07..693128a 100644 --- a/plugins.h +++ b/plugins.h @@ -196,6 +196,5 @@ typedef struct { } nvs_transfer_t; extern nvs_transfer_result_t i2c_nvs_transfer (nvs_transfer_t *i2c, bool read); -extern void my_plugin_init (void); #endif diff --git a/plugins_init.h b/plugins_init.h index f04b51d..086c2b1 100644 --- a/plugins_init.h +++ b/plugins_init.h @@ -85,6 +85,7 @@ webui_init(); #endif + extern void my_plugin_init (void); my_plugin_init(); // Third party plugin definitions. diff --git a/protocol.c b/protocol.c index 44fd60f..259321b 100644 --- a/protocol.c +++ b/protocol.c @@ -407,7 +407,7 @@ bool protocol_exec_rt_system (void) if (sys.rt_exec_alarm && (rt_exec = system_clear_exec_alarm())) { // Enter only if any bit flag is true - if(sys.rt_exec_state & EXEC_RESET) { + if((sys.reset_pending = !!(sys.rt_exec_state & EXEC_RESET))) { // Kill spindle and coolant. killed = true; hal.spindle.set_state((spindle_state_t){0}, 0.0f); @@ -463,7 +463,7 @@ bool protocol_exec_rt_system (void) if (sys.rt_exec_state && (rt_exec = system_clear_exec_states())) { // Get and clear volatile sys.rt_exec_state atomically. // Execute system abort. - if (rt_exec & EXEC_RESET) { + if((sys.reset_pending = !!(rt_exec & EXEC_RESET))) { if(!killed) { // Kill spindle and coolant. diff --git a/settings.c b/settings.c index 2c7f24e..62d8a3f 100644 --- a/settings.c +++ b/settings.c @@ -122,7 +122,7 @@ PROGMEM const settings_t defaults = { #endif .steppers.deenergize.mask = ST_DEENERGIZE_MASK, #if N_AXIS > 3 - .steppers.is_rotational.mask = (ST_ROTATIONAL_MASK & AXES_BITMASK) >> 3, + .steppers.is_rotational.mask = (ST_ROTATIONAL_MASK & AXES_BITMASK) & 0b11111000, #endif #if DEFAULT_HOMING_ENABLE .homing.flags.enabled = DEFAULT_HOMING_ENABLE, @@ -378,6 +378,7 @@ static status_code_t set_force_initialization_alarm (setting_id_t id, uint_fast1 static status_code_t set_probe_allow_feed_override (setting_id_t id, uint_fast16_t int_value); static status_code_t set_tool_change_mode (setting_id_t id, uint_fast16_t int_value); static status_code_t set_tool_change_probing_distance (setting_id_t id, float value); +static status_code_t set_tool_restore_pos (setting_id_t id, uint_fast16_t int_value); static status_code_t set_ganged_dir_invert (setting_id_t id, uint_fast16_t int_value); static status_code_t set_stepper_deenergize_mask (setting_id_t id, uint_fast16_t int_value); #ifndef NO_SAFETY_DOOR_SUPPORT @@ -532,6 +533,7 @@ PROGMEM static const setting_detail_t setting_detail[] = { { Setting_ToolChangeFeedRate, Group_Toolchange, "Tool change locate feed rate", "mm/min", Format_Decimal, "#####0.0", NULL, NULL, Setting_IsExtended, &settings.tool_change.feed_rate, NULL, NULL }, { Setting_ToolChangeSeekRate, Group_Toolchange, "Tool change search seek rate", "mm/min", Format_Decimal, "#####0.0", NULL, NULL, Setting_IsExtended, &settings.tool_change.seek_rate, NULL, NULL }, { Setting_ToolChangePulloffRate, Group_Toolchange, "Tool change probe pull-off rate", "mm/min", Format_Decimal, "#####0.0", NULL, NULL, Setting_IsExtended, &settings.tool_change.pulloff_rate, NULL, NULL }, + { Setting_ToolChangeRestorePosition, Group_Toolchange, "Restore position after M6", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsExtendedFn, set_tool_restore_pos, get_int, NULL }, { Setting_DualAxisLengthFailPercent, Group_Limits_DualAxis, "Dual axis length fail", "percent", Format_Decimal, "##0.0", "0", "100", Setting_IsExtended, &settings.homing.dual_axis.fail_length_percent, NULL, is_setting_available }, { Setting_DualAxisLengthFailMin, Group_Limits_DualAxis, "Dual axis length fail min", "mm", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsExtended, &settings.homing.dual_axis.fail_distance_min, NULL, is_setting_available }, { Setting_DualAxisLengthFailMax, Group_Limits_DualAxis, "Dual axis length fail max", "mm", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsExtended, &settings.homing.dual_axis.fail_distance_max, NULL, is_setting_available }, @@ -703,6 +705,7 @@ PROGMEM static const setting_descr_t setting_descr[] = { { Setting_ToolChangeFeedRate, "Feed rate to slowly engage tool change sensor to determine the tool offset accurately." }, { Setting_ToolChangeSeekRate, "Seek rate to quickly find the tool change sensor before the slower locating phase." }, { Setting_ToolChangePulloffRate, "Pull-off rate for the retract move before the slower locating phase." }, + { Setting_ToolChangeRestorePosition, "When set the spindle is moved so that the controlled point (tool tip) is the same as before the M6 command, if not the spindle is only moved to the Z home position." }, { Setting_DualAxisLengthFailPercent, "Dual axis length fail in percent of axis max travel." }, { Setting_DualAxisLengthFailMin, "Dual axis length fail minimum distance." }, { Setting_DualAxisLengthFailMax, "Dual axis length fail minimum distance." }, @@ -1123,6 +1126,16 @@ static status_code_t set_tool_change_probing_distance (setting_id_t id, float va return Status_OK; } +static status_code_t set_tool_restore_pos (setting_id_t id, uint_fast16_t int_value) +{ + if(hal.driver_cap.atc) + return Status_InvalidStatement; + + settings.flags.no_restore_position_after_M6 = int_value == 0; + + return Status_OK; +} + #if N_AXIS > 3 static status_code_t set_rotational_axes (setting_id_t id, uint_fast16_t int_value) { @@ -1441,6 +1454,10 @@ static uint32_t get_int (setting_id_t id) value = settings.tool_change.mode; break; + case Setting_ToolChangeRestorePosition: + value = settings.flags.no_restore_position_after_M6 ? 0 : 1; + break; + case Setting_DisableG92Persistence: value = settings.flags.g92_is_volatile; break; @@ -2486,6 +2503,13 @@ bool settings_add_spindle_type (const char *type) return ok; } +// Clear settings chain +void settings_clear (void) +{ + setting_details.next = NULL; + settingsd = &setting_details; +} + // Initialize the config subsystem void settings_init (void) { diff --git a/settings.h b/settings.h index 7561620..3bfd268 100644 --- a/settings.h +++ b/settings.h @@ -212,6 +212,7 @@ typedef enum { Setting_ToolChangeFeedRate = 343, Setting_ToolChangeSeekRate = 344, Setting_ToolChangePulloffRate = 345, + Setting_ToolChangeRestorePosition = 346, Setting_DualAxisLengthFailPercent = 347, Setting_DualAxisLengthFailMin = 348, @@ -299,6 +300,8 @@ typedef enum { Setting_VFD_20 = 472, Setting_VFD_21 = 473, + Setting_Fan0OffDelay = 480, + Setting_SettingsMax, Setting_SettingsAll = Setting_SettingsMax, @@ -362,7 +365,8 @@ typedef union { unused1 :1, g92_is_volatile :1, compatibility_level :4, - unassigned :2; + no_restore_position_after_M6 :1, + unassigned :1; }; } settingflags_t; @@ -770,6 +774,9 @@ typedef setting_details_t *(*on_get_settings_ptr)(void); extern settings_t settings; +// Clear settings chain (unlinks plugin/driver settings from core settings) +void settings_clear (void); + // Initialize the configuration subsystem (load settings from persistent storage) void settings_init(); diff --git a/spindle_control.c b/spindle_control.c index 7cbf141..5813278 100644 --- a/spindle_control.c +++ b/spindle_control.c @@ -33,13 +33,14 @@ #endif static uint8_t n_spindle = 0; -static const spindle_ptrs_t *spindles[N_SPINDLE]; -static spindle_id_t current_spindle = 0; +static const spindle_ptrs_t *spindles[N_SPINDLE], *current_spindle = NULL; spindle_id_t spindle_register (const spindle_ptrs_t *spindle, const char *name) { - if(n_spindle == 0) + if(n_spindle == 0) { memcpy(&hal.spindle, spindle, sizeof(spindle_ptrs_t)); + current_spindle = spindle; + } if(n_spindle < N_SPINDLE && settings_add_spindle_type(name)) { spindles[n_spindle++] = spindle; @@ -56,39 +57,36 @@ bool spindle_select (spindle_id_t spindle_id) if(n_spindle == 0) { if(hal.spindle.set_state) - spindles[n_spindle++] = &hal.spindle; + spindles[n_spindle++] = current_spindle = &hal.spindle; else spindle_add_null(); } if((ok = spindle_id >= 0 && spindle_id < n_spindle)) { - if(hal.spindle.set_state && hal.spindle.set_state != spindles[spindle_id]->set_state) + spindle_ptrs_t spindle_org; + + if(hal.spindle.set_state && current_spindle != spindles[spindle_id]) gc_spindle_off(); - if(ok) { + memcpy(&spindle_org, &hal.spindle, offsetof(spindle_ptrs_t, get_data)); + memcpy(&hal.spindle, spindles[spindle_id], offsetof(spindle_ptrs_t, get_data)); - spindle_ptrs_t spindle_org; - - memcpy(&spindle_org, &hal.spindle, offsetof(spindle_ptrs_t, get_data)); - memcpy(&hal.spindle, spindles[spindle_id], offsetof(spindle_ptrs_t, get_data)); - - if(!hal.spindle.cap.rpm_range_locked) { - hal.spindle.rpm_min = settings.spindle.rpm_min; - hal.spindle.rpm_max = settings.spindle.rpm_max; - } - - if(hal.spindle.config) - ok = hal.spindle.config(); - - if(ok) { - current_spindle = spindle_id; - sys.mode = settings.mode == Mode_Laser && !hal.spindle.cap.laser ? Mode_Standard : settings.mode; - if(grbl.on_spindle_select) - grbl.on_spindle_select(spindle_id); - } else - memcpy(&spindle_org, &hal.spindle, offsetof(spindle_ptrs_t, get_data)); + if(!hal.spindle.cap.rpm_range_locked) { + hal.spindle.rpm_min = settings.spindle.rpm_min; + hal.spindle.rpm_max = settings.spindle.rpm_max; } + + if(hal.spindle.config) + ok = hal.spindle.config(); + + if(ok) { + current_spindle = spindles[spindle_id]; + sys.mode = settings.mode == Mode_Laser && !hal.spindle.cap.laser ? Mode_Standard : settings.mode; + if(grbl.on_spindle_select) + grbl.on_spindle_select(spindle_id); + } else + memcpy(&spindle_org, &hal.spindle, offsetof(spindle_ptrs_t, get_data)); } return ok; @@ -104,7 +102,14 @@ const spindle_ptrs_t *spindle_get (spindle_id_t spindle_id) spindle_id_t spindle_get_current (void) { - return current_spindle; + spindle_id_t spindle_id = spindle_get_count(); + + do { + if(spindles[--spindle_id] == current_spindle) + break; + } while(spindle_id); + + return spindle_id; } spindle_cap_t spindle_get_caps (void) @@ -123,6 +128,7 @@ spindle_cap_t spindle_get_caps (void) void spindle_update_caps (spindle_pwm_t *pwm_caps) { + hal.spindle.type = pwm_caps ? SpindleType_PWM : SpindleType_Basic; hal.spindle.cap.laser = !!pwm_caps && !!hal.spindle.update_pwm; hal.spindle.pwm_off_value = pwm_caps ? pwm_caps->off_value : 0; sys.mode = settings.mode == Mode_Laser && !hal.spindle.cap.laser ? Mode_Standard : settings.mode; @@ -172,6 +178,7 @@ static void null_update_rpm (float rpm) void spindle_add_null (void) { static const spindle_ptrs_t spindle = { + .type = SpindleType_Null, .cap.variable = Off, .cap.at_speed = Off, .cap.direction = Off, @@ -203,6 +210,8 @@ void spindle_set_override (uint_fast8_t speed_override) else sys.step_control.update_spindle_rpm = On; sys.report.overrides = On; // Set to report change immediately + if(grbl.on_spindle_programmed) + grbl.on_spindle_programmed(gc_state.modal.spindle, spindle_set_rpm(gc_state.spindle.rpm, sys.override.spindle_rpm), gc_state.modal.spindle_rpm_mode); } } diff --git a/spindle_control.h b/spindle_control.h index 8ffa2de..4d2721f 100644 --- a/spindle_control.h +++ b/spindle_control.h @@ -75,6 +75,14 @@ typedef enum { SpindleData_AngularPosition //!< 2 } spindle_data_request_t; +typedef enum { + SpindleType_PWM, //!< 0 + SpindleType_Basic, //!< 1 - on/off + optional direction + SpindleType_VFD, //!< 2 + SpindleType_Solenoid, //!< 3 + SpindleType_Null, //!< 4 +} spindle_type_t; + /*! \brief Pointer to function for configuring the spindle. \returns state in a \a spindle_state_t union variable. */ @@ -134,6 +142,7 @@ typedef void (*spindle_pulse_on_ptr)(uint_fast16_t pulse_length); //! Handlers for spindle support. typedef struct { + spindle_type_t type; //!< Spindle type. spindle_cap_t cap; //!< Spindle capabilities. uint_fast16_t pwm_off_value; //!< Value for switching PWM signal off. float rpm_min; //!< Minimum spindle RPM. diff --git a/system.h b/system.h index d3e43fe..b5282b2 100644 --- a/system.h +++ b/system.h @@ -227,6 +227,7 @@ typedef struct system { bool cancel; //!< System cancel flag. bool suspend; //!< System suspend state flag. bool position_lost; //!< Set when mc_reset is called when machine is moving. + bool reset_pending; //!< Set when reset processing is underway. volatile bool steppers_deenergize; //!< Set to true to deenergize stepperes axes_signals_t tlo_reference_set; //!< Axes with tool length reference offset set int32_t tlo_reference[N_AXIS]; //!< Tool length reference offset diff --git a/tool_change.c b/tool_change.c index 55d5b6f..ce5932d 100644 --- a/tool_change.c +++ b/tool_change.c @@ -117,9 +117,11 @@ static bool restore (void) target.values[plane.axis_linear] = tool_change_position; mc_line(target.values, &plan_data); - memcpy(&target, &previous, sizeof(coord_data_t)); - target.values[plane.axis_linear] = tool_change_position; - mc_line(target.values, &plan_data); + if(!settings.flags.no_restore_position_after_M6) { + memcpy(&target, &previous, sizeof(coord_data_t)); + target.values[plane.axis_linear] = tool_change_position; + mc_line(target.values, &plan_data); + } if(protocol_buffer_synchronize()) { @@ -128,8 +130,10 @@ static bool restore (void) coolant_sync(gc_state.modal.coolant); spindle_restore(gc_state.modal.spindle, gc_state.spindle.rpm); - previous.values[plane.axis_linear] += gc_get_offset(plane.axis_linear); - mc_line(previous.values, &plan_data); + if(!settings.flags.no_restore_position_after_M6) { + previous.values[plane.axis_linear] += gc_get_offset(plane.axis_linear); + mc_line(previous.values, &plan_data); + } } if(protocol_buffer_synchronize()) {