#include "Marlin.h"
#include "language.h"
#include "planner.h"
#include "temperature.h"
#include "ultralcd.h"
#include "ConfigurationStore.h"

void _EEPROM_writeData(int &pos, uint8_t* value, uint8_t size)
{
  uint8_t c;
  while(size--)
  {
    eeprom_write_byte((unsigned char*)pos, *value);
    c = eeprom_read_byte((unsigned char*)pos);
    if (c != *value)
    {
      SERIAL_ECHO_START;
      SERIAL_ECHOLNPGM(MSG_ERR_EEPROM_WRITE);
    }
    pos++;
    value++;
  }
}
void _EEPROM_readData(int &pos, uint8_t* value, uint8_t size)
{
  do
  {
    *value = eeprom_read_byte((unsigned char*)pos);
    pos++;
    value++;
  } while (--size);
}

#define EEPROM_WRITE_VAR(pos, value) _EEPROM_writeData(pos, (uint8_t*)&value, sizeof(value))
#define EEPROM_READ_VAR(pos, value) _EEPROM_readData(pos, (uint8_t*)&value, sizeof(value))

//======================================================================================
#define DUMMY_PID_VALUE 3000.0f
#define EEPROM_OFFSET 100

// IMPORTANT:  Whenever there are changes made to the variables stored in EEPROM
// in the functions below, also increment the version number. This makes sure that
// the default values are used whenever there is a change to the data, to prevent
// wrong data being written to the variables.
// ALSO:  always make sure the variables in the Store and retrieve sections are in the same order.

#define EEPROM_VERSION "V13"

#ifdef EEPROM_SETTINGS
  void Config_StoreSettings()
  {
    float dummy = 0.0f;
    char ver[4] = "000";
    int i = EEPROM_OFFSET;
    EEPROM_WRITE_VAR(i, ver); // invalidate data first
    EEPROM_WRITE_VAR(i, baudrate);
    EEPROM_WRITE_VAR(i, axis_steps_per_unit);
    EEPROM_WRITE_VAR(i, max_feedrate);
    EEPROM_WRITE_VAR(i, max_retraction_feedrate);
    EEPROM_WRITE_VAR(i, max_acceleration_units_per_sq_second);
    EEPROM_WRITE_VAR(i, acceleration);
    EEPROM_WRITE_VAR(i, retract_acceleration);
    EEPROM_WRITE_VAR(i, minimumfeedrate);
    EEPROM_WRITE_VAR(i, mintravelfeedrate);
    EEPROM_WRITE_VAR(i, minsegmenttime);
    EEPROM_WRITE_VAR(i, max_xy_jerk);
    EEPROM_WRITE_VAR(i, max_z_jerk);
    EEPROM_WRITE_VAR(i, max_e_jerk);
    EEPROM_WRITE_VAR(i, add_homing);
    EEPROM_WRITE_VAR(i, zprobe_zoffset);

    #if EXTRUDERS > 1 && !defined SINGLENOZZLE
      EEPROM_WRITE_VAR(i, hotend_offset);
    #endif

    #ifdef DELTA
      EEPROM_WRITE_VAR(i, delta_radius);
      EEPROM_WRITE_VAR(i, delta_diagonal_rod);
      EEPROM_WRITE_VAR(i, max_pos);
      EEPROM_WRITE_VAR(i, endstop_adj);
      EEPROM_WRITE_VAR(i, tower_adj);
      EEPROM_WRITE_VAR(i, z_probe_offset);
    #endif

    #ifndef ULTIPANEL
      int plaPreheatHotendTemp = PLA_PREHEAT_HOTEND_TEMP, plaPreheatHPBTemp = PLA_PREHEAT_HPB_TEMP, plaPreheatFanSpeed = PLA_PREHEAT_FAN_SPEED;
      int absPreheatHotendTemp = ABS_PREHEAT_HOTEND_TEMP, absPreheatHPBTemp = ABS_PREHEAT_HPB_TEMP, absPreheatFanSpeed = ABS_PREHEAT_FAN_SPEED;
      int gumPreheatHotendTemp = GUM_PREHEAT_HOTEND_TEMP, gumPreheatHPBTemp = GUM_PREHEAT_HPB_TEMP, gumPreheatFanSpeed = GUM_PREHEAT_FAN_SPEED;
    #endif

    EEPROM_WRITE_VAR(i, plaPreheatHotendTemp);
    EEPROM_WRITE_VAR(i, plaPreheatHPBTemp);
    EEPROM_WRITE_VAR(i, plaPreheatFanSpeed);
    EEPROM_WRITE_VAR(i, absPreheatHotendTemp);
    EEPROM_WRITE_VAR(i, absPreheatHPBTemp);
    EEPROM_WRITE_VAR(i, absPreheatFanSpeed);
    EEPROM_WRITE_VAR(i, gumPreheatHotendTemp);
    EEPROM_WRITE_VAR(i, gumPreheatHPBTemp);
    EEPROM_WRITE_VAR(i, gumPreheatFanSpeed);

    #ifdef PIDTEMP
      EEPROM_WRITE_VAR(i, Kp);
      EEPROM_WRITE_VAR(i, Ki);
      EEPROM_WRITE_VAR(i, Kd);
    #endif //PIDTEMP

    #ifndef DOGLCD
      int lcd_contrast = 32;
    #endif
      EEPROM_WRITE_VAR(i, lcd_contrast);
    #ifdef SCARA
      EEPROM_WRITE_VAR(i, axis_scaling);        // Add scaling for SCARA
    #endif //SCARA

    #ifdef FWRETRACT
      EEPROM_WRITE_VAR(i, autoretract_enabled);
      EEPROM_WRITE_VAR(i, retract_length);
      #if EXTRUDERS > 1
        EEPROM_WRITE_VAR(i, retract_length_swap);
      #endif //EXTRUDERS > 1
      EEPROM_WRITE_VAR(i, retract_feedrate);
      EEPROM_WRITE_VAR(i, retract_zlift);
      EEPROM_WRITE_VAR(i, retract_recover_length);
      #if EXTRUDERS > 1
        EEPROM_WRITE_VAR(i, retract_recover_length_swap);
      #endif //EXTRUDERS > 1
      EEPROM_WRITE_VAR(i, retract_recover_feedrate);
    #endif // FWRETRACT

    EEPROM_WRITE_VAR(i, volumetric_enabled);

    // Save filament sizes
    for (int e = 0; e < EXTRUDERS; e++) EEPROM_WRITE_VAR(i, filament_size[e]);

    int storageSize = i;

    char ver2[4] = EEPROM_VERSION;
    int j = EEPROM_OFFSET;
    EEPROM_WRITE_VAR(j, ver2); // validate data

    // Report storage size
    SERIAL_ECHO_START;
    SERIAL_ECHOPAIR("Settings Stored (", (unsigned long)i);
    SERIAL_ECHOLNPGM(" bytes)");
  }

  void Config_RetrieveSettings()
  {
    int i=EEPROM_OFFSET;
    char stored_ver[4];
    char ver[4]=EEPROM_VERSION;
    EEPROM_READ_VAR(i,stored_ver); //read stored version
    //SERIAL_ECHOLN("Version: [" << ver << "] Stored version: [" << stored_ver << "]");
    if (strncmp(ver,stored_ver,3) == 0)
    {
      // version number match
      EEPROM_READ_VAR(i,baudrate);
      if(baudrate!=9600 && baudrate!=14400 && baudrate!=19200 && baudrate!=28800 && baudrate!=38400 && baudrate!=56000 && baudrate!=115200 && baudrate!=250000) baudrate=BAUDRATE;
      EEPROM_READ_VAR(i,axis_steps_per_unit);
      EEPROM_READ_VAR(i,max_feedrate);
      EEPROM_READ_VAR(i,max_retraction_feedrate);
      EEPROM_READ_VAR(i,max_acceleration_units_per_sq_second);

      // steps per sq second need to be updated to agree with the units per sq second (as they are what is used in the planner)
      reset_acceleration_rates();

      EEPROM_READ_VAR(i,acceleration);
      EEPROM_READ_VAR(i,retract_acceleration);
      EEPROM_READ_VAR(i,minimumfeedrate);
      EEPROM_READ_VAR(i,mintravelfeedrate);
      EEPROM_READ_VAR(i,minsegmenttime);
      EEPROM_READ_VAR(i,max_xy_jerk);
      EEPROM_READ_VAR(i,max_z_jerk);
      EEPROM_READ_VAR(i,max_e_jerk);
      EEPROM_READ_VAR(i,add_homing);
      EEPROM_READ_VAR(i,zprobe_zoffset);

      #if EXTRUDERS > 1 && !defined SINGLENOZZLE
        EEPROM_READ_VAR(i, hotend_offset);
      #endif

      #ifdef DELTA
        EEPROM_READ_VAR(i,delta_radius);
        EEPROM_READ_VAR(i,delta_diagonal_rod);
        EEPROM_READ_VAR(i,max_pos);
        EEPROM_READ_VAR(i,endstop_adj);
        EEPROM_READ_VAR(i,tower_adj);
        EEPROM_READ_VAR(i,z_probe_offset);
        // Update delta constants for updated delta_radius & tower_adj values
        set_delta_constants();
      #endif //DELTA

      #ifndef ULTIPANEL
        int plaPreheatHotendTemp, plaPreheatHPBTemp, plaPreheatFanSpeed;
        int absPreheatHotendTemp, absPreheatHPBTemp, absPreheatFanSpeed;
        int gumPreheatHotendTemp, gumPreheatHPBTemp, gumPreheatFanSpeed;
      #endif

      EEPROM_READ_VAR(i,plaPreheatHotendTemp);
      EEPROM_READ_VAR(i,plaPreheatHPBTemp);
      EEPROM_READ_VAR(i,plaPreheatFanSpeed);
      EEPROM_READ_VAR(i,absPreheatHotendTemp);
      EEPROM_READ_VAR(i,absPreheatHPBTemp);
      EEPROM_READ_VAR(i,absPreheatFanSpeed);
      EEPROM_READ_VAR(i,gumPreheatHotendTemp);
      EEPROM_READ_VAR(i,gumPreheatHPBTemp);
      EEPROM_READ_VAR(i,gumPreheatFanSpeed);

      #ifdef PIDTEMP
        // do not need to scale PID values as the values in EEPROM are already scaled		
        EEPROM_READ_VAR(i,Kp);
        EEPROM_READ_VAR(i,Ki);
        EEPROM_READ_VAR(i,Kd);
      #endif // PIDTEMP

      #ifndef DOGLCD
        int lcd_contrast;
      #endif //DOGLCD

      EEPROM_READ_VAR(i,lcd_contrast);

      #ifdef SCARA
        EEPROM_READ_VAR(i,axis_scaling);
      #endif //SCARA

      #ifdef FWRETRACT
        EEPROM_READ_VAR(i,autoretract_enabled);
        EEPROM_READ_VAR(i,retract_length);
        #if EXTRUDERS > 1
          EEPROM_READ_VAR(i,retract_length_swap);
        #endif //EXTRUDERS > 1
        EEPROM_READ_VAR(i,retract_feedrate);
        EEPROM_READ_VAR(i,retract_zlift);
        EEPROM_READ_VAR(i,retract_recover_length);
        #if EXTRUDERS > 1
          EEPROM_READ_VAR(i,retract_recover_length_swap);
        #endif //EXTRUDERS > 1
        EEPROM_READ_VAR(i,retract_recover_feedrate);
      #endif //FWRETRACT

      EEPROM_READ_VAR(i, volumetric_enabled);
      EEPROM_READ_VAR(i, filament_size[0]);
      #if EXTRUDERS > 1
        EEPROM_READ_VAR(i, filament_size[1]);
        #if EXTRUDERS > 2
          EEPROM_READ_VAR(i, filament_size[2]);
          #if EXTRUDERS > 3
            EEPROM_READ_VAR(i, filament_size[3]);
          #endif //EXTRUDERS > 3
        #endif //EXTRUDERS > 2
      #endif //EXTRUDERS > 1
      calculate_volumetric_multipliers();
      // Call updatePID (similar to when we have processed M301)
      updatePID();
      SERIAL_ECHO_START;
      SERIAL_ECHOLNPGM("Stored settings retrieved");
    }
    else
    {
      Config_ResetDefault();
    }

    #ifdef EEPROM_CHITCHAT
      Config_PrintSettings();
    #endif //EEPROM_CHITCHAT
  }
#endif //EEPROM_SETTINGS

void Config_ResetDefault()
{
  //Setting default baudrate for serial
  baudrate=BAUDRATE;

  const static float tmp1[] = DEFAULT_AXIS_STEPS_PER_UNIT;
  const static float tmp2[] = DEFAULT_MAX_FEEDRATE;
  const static float tmp3[] = DEFAULT_RETRACTION_MAX_FEEDRATE;
  const static long tmp4[]  = DEFAULT_MAX_ACCELERATION;
  #ifdef PIDTEMP
    const static float tmp5[] = DEFAULT_Kp;
    const static float tmp6[] = DEFAULT_Ki;
    const static float tmp7[] = DEFAULT_Kd;
  #endif // PIDTEMP

  #if defined(EXTRUDER_OFFSET_X) && defined(EXTRUDER_OFFSET_Y)
    const static float tmp8[] = EXTRUDER_OFFSET_X;
    const static float tmp9[] = EXTRUDER_OFFSET_Y;
  #else
    const static float tmp8[] = {0,0,0,0};
    const static float tmp9[] = {0,0,0,0};
  #endif

  for (short i=0;i<3+EXTRUDERS;i++) 
  {
    axis_steps_per_unit[i] = tmp1[i];
    max_feedrate[i] = tmp2[i];
    max_acceleration_units_per_sq_second[i] = tmp4[i];
  }

  for (short i=0;i<EXTRUDERS;i++)
  {
    max_retraction_feedrate[i] = tmp3[i];
    #if EXTRUDERS > 1 && !defined SINGLENOZZLE
      hotend_offset[X_AXIS][i] = tmp8[i];
      hotend_offset[Y_AXIS][i] = tmp9[i];
    #endif
    #ifdef SCARA
      axis_scaling[i] = 1;
    #endif //SCARA
  }

  // steps per sq second need to be updated to agree with the units per sq second
  reset_acceleration_rates();

  acceleration=DEFAULT_ACCELERATION;
  retract_acceleration=DEFAULT_RETRACT_ACCELERATION;
  minimumfeedrate=DEFAULT_MINIMUMFEEDRATE;
  minsegmenttime=DEFAULT_MINSEGMENTTIME;       
  mintravelfeedrate=DEFAULT_MINTRAVELFEEDRATE;
  max_xy_jerk=DEFAULT_XYJERK;
  max_z_jerk=DEFAULT_ZJERK;
  max_e_jerk=DEFAULT_EJERK;
  add_homing[0] = add_homing[1] = add_homing[2] = 0;

  #ifdef ENABLE_AUTO_BED_LEVELING
    zprobe_zoffset = -Z_PROBE_OFFSET_FROM_EXTRUDER;
  #else
    zprobe_zoffset = 0;
  #endif //ENABLE_AUTO_BED_LEVELING

  #ifdef DELTA
    delta_radius = DEFAULT_DELTA_RADIUS;
    delta_diagonal_rod = DEFAULT_DELTA_DIAGONAL_ROD;
    endstop_adj[0] = endstop_adj[1] = endstop_adj[2] = 0;
    tower_adj[0] = tower_adj[1] = tower_adj[2] = tower_adj[3] = tower_adj[4] = tower_adj[5] = 0;
    max_pos[2] = MANUAL_Z_HOME_POS;
    set_default_z_probe_offset();
    set_delta_constants();
  #endif //DELTA

  #ifdef ULTIPANEL
    plaPreheatHotendTemp = PLA_PREHEAT_HOTEND_TEMP;
    plaPreheatHPBTemp = PLA_PREHEAT_HPB_TEMP;
    plaPreheatFanSpeed = PLA_PREHEAT_FAN_SPEED;
    absPreheatHotendTemp = ABS_PREHEAT_HOTEND_TEMP;
    absPreheatHPBTemp = ABS_PREHEAT_HPB_TEMP;
    absPreheatFanSpeed = ABS_PREHEAT_FAN_SPEED;
    gumPreheatHotendTemp = GUM_PREHEAT_HOTEND_TEMP;
    gumPreheatHPBTemp = GUM_PREHEAT_HPB_TEMP;
    gumPreheatFanSpeed = GUM_PREHEAT_FAN_SPEED;
  #endif

  #ifdef DOGLCD
    lcd_contrast = DEFAULT_LCD_CONTRAST;
  #endif //DOGLCD

  #ifdef PIDTEMP
    #ifndef SINGLENOZZLE
      for (int e=0;e<EXTRUDERS;e++) 
    #else
      int e = 0; // only need to write once
    #endif //SINGLENOZZLE
    {
      Kp[e] = tmp5[e];
      Ki[e] = scalePID_i(tmp6[e]);
      Kd[e] = scalePID_d(tmp7[e]);
    }
    // call updatePID (similar to when we have processed M301)
    updatePID();
  #endif//PIDTEMP

  #ifdef FWRETRACT
    autoretract_enabled = false;
    retract_length = RETRACT_LENGTH;
    #if EXTRUDERS > 1
      retract_length_swap = RETRACT_LENGTH_SWAP;
    #endif //EXTRUDERS > 1
    retract_feedrate = RETRACT_FEEDRATE;
    retract_zlift = RETRACT_ZLIFT;
    retract_recover_length = RETRACT_RECOVER_LENGTH;
    #if EXTRUDERS > 1
      retract_recover_length_swap = RETRACT_RECOVER_LENGTH_SWAP;
    #endif //EXTRUDERS > 1
    retract_recover_feedrate = RETRACT_RECOVER_FEEDRATE;
  #endif //FWRETRACT

  volumetric_enabled = false;
  filament_size[0] = DEFAULT_NOMINAL_FILAMENT_DIA;
  #if EXTRUDERS > 1
    filament_size[1] = DEFAULT_NOMINAL_FILAMENT_DIA;
    #if EXTRUDERS > 2
      filament_size[2] = DEFAULT_NOMINAL_FILAMENT_DIA;
      #if EXTRUDERS > 3
        filament_size[3] = DEFAULT_NOMINAL_FILAMENT_DIA;
      #endif //EXTRUDERS > 3
    #endif //EXTRUDERS > 2
  #endif //EXTRUDERS > 1
  calculate_volumetric_multipliers();

  SERIAL_ECHO_START;
  SERIAL_ECHOLNPGM("Hardcoded Default Settings Loaded");
}

#ifndef DISABLE_M503
  void Config_PrintSettings()
  {  // Always have this function, even with EEPROM_SETTINGS disabled, the current values will be shown
    SERIAL_ECHO_START;
    SERIAL_ECHOPAIR("Baudrate: ", baudrate);
    SERIAL_EOL;
    SERIAL_ECHOLNPGM("Steps per unit:");
    SERIAL_ECHO_START;
    SERIAL_ECHOPAIR("  M92 X",axis_steps_per_unit[X_AXIS]);
    SERIAL_ECHOPAIR(" Y",axis_steps_per_unit[Y_AXIS]);
    SERIAL_ECHOPAIR(" Z",axis_steps_per_unit[Z_AXIS]);
    SERIAL_ECHOPAIR(" E0 S",axis_steps_per_unit[E_AXIS + 0]);
    #if EXTRUDERS > 1
      SERIAL_ECHOPAIR(" E1 S",axis_steps_per_unit[E_AXIS + 1]);
      #if EXTRUDERS > 2
        SERIAL_ECHOPAIR(" E2 S",axis_steps_per_unit[E_AXIS + 2]);
        #if EXTRUDERS > 3
          SERIAL_ECHOPAIR(" E3 S",axis_steps_per_unit[E_AXIS + 3]);
        #endif //EXTRUDERS > 3
      #endif //EXTRUDERS > 2
    #endif //EXTRUDERS > 1
    SERIAL_EOL;
    SERIAL_ECHO_START;

    #ifdef SCARA
      SERIAL_ECHOLNPGM("Scaling factors:");
      SERIAL_ECHO_START;
      SERIAL_ECHOPAIR("  M365 X",axis_scaling[X_AXIS]);
      SERIAL_ECHOPAIR(" Y",axis_scaling[Y_AXIS]);
      SERIAL_ECHOPAIR(" Z",axis_scaling[Z_AXIS]);
      SERIAL_EOL;
      SERIAL_ECHO_START;
    #endif

    SERIAL_ECHOLNPGM("Maximum feedrates (mm/s):");
    SERIAL_ECHO_START;
    SERIAL_ECHOPAIR("  M203 X ",max_feedrate[X_AXIS]);
    SERIAL_ECHOPAIR(" Y ",max_feedrate[Y_AXIS] ); 
    SERIAL_ECHOPAIR(" Z ", max_feedrate[Z_AXIS] ); 
    SERIAL_ECHOPAIR(" E0 ", max_feedrate[E_AXIS + 0]);
    #if EXTRUDERS > 1
      SERIAL_ECHOPAIR(" E1 ", max_feedrate[E_AXIS + 1]);
      #if EXTRUDERS > 2
        SERIAL_ECHOPAIR(" E2 ", max_feedrate[E_AXIS + 2]);
        #if EXTRUDERS > 3
          SERIAL_ECHOPAIR(" E3 ", max_feedrate[E_AXIS + 3]);
        #endif //EXTRUDERS > 3
      #endif //EXTRUDERS > 2
    #endif //EXTRUDERS > 1
    SERIAL_EOL;
    SERIAL_ECHO_START;
    SERIAL_ECHOLNPGM("Retraction Steps per unit:");
    SERIAL_ECHO_START;
    SERIAL_ECHOPAIR("  E0 ",max_retraction_feedrate[0]);
    #if EXTRUDERS > 1
      SERIAL_ECHOPAIR(" E1 ",max_retraction_feedrate[1]);
      #if EXTRUDERS > 2
        SERIAL_ECHOPAIR(" E2 ",max_retraction_feedrate[2]);
        #if EXTRUDERS > 3
          SERIAL_ECHOPAIR(" E3 ",max_retraction_feedrate[3]);
        #endif //EXTRUDERS > 3
      #endif //EXTRUDERS > 2
    #endif //EXTRUDERS > 1
    SERIAL_EOL;
    SERIAL_ECHO_START;
    SERIAL_ECHOLNPGM("Maximum Acceleration (mm/s2):");
    SERIAL_ECHO_START;
    SERIAL_ECHOPAIR("  M201 X " ,max_acceleration_units_per_sq_second[X_AXIS] ); 
    SERIAL_ECHOPAIR(" Y " , max_acceleration_units_per_sq_second[Y_AXIS] ); 
    SERIAL_ECHOPAIR(" Z " ,max_acceleration_units_per_sq_second[Z_AXIS] );
    SERIAL_ECHOPAIR(" E0 " ,max_acceleration_units_per_sq_second[E_AXIS]);
    #if EXTRUDERS > 1
      SERIAL_ECHOPAIR(" E1 " ,max_acceleration_units_per_sq_second[E_AXIS+1]);
      #if EXTRUDERS > 2
        SERIAL_ECHOPAIR(" E2 " ,max_acceleration_units_per_sq_second[E_AXIS+2]);
        #if EXTRUDERS > 3
          SERIAL_ECHOPAIR(" E3 " ,max_acceleration_units_per_sq_second[E_AXIS+3]);
        #endif //EXTRUDERS > 3
      #endif //EXTRUDERS > 2
    #endif //EXTRUDERS > 1
    SERIAL_EOL;
    SERIAL_ECHO_START;
    SERIAL_ECHOLNPGM("Acceleration: S=acceleration, T=retract acceleration");
    SERIAL_ECHO_START;
    SERIAL_ECHOPAIR("  M204 S",acceleration ); 
    SERIAL_ECHOPAIR(" T" ,retract_acceleration);
    SERIAL_EOL;

    SERIAL_ECHO_START;
    SERIAL_ECHOLNPGM("Advanced variables: S=Min feedrate (mm/s), T=Min travel feedrate (mm/s), B=minimum segment time (ms), X=maximum XY jerk (mm/s),  Z=maximum Z jerk (mm/s),  E=maximum E jerk (mm/s)");
    SERIAL_ECHO_START;
    SERIAL_ECHOPAIR("  M205 S",minimumfeedrate ); 
    SERIAL_ECHOPAIR(" T" ,mintravelfeedrate ); 
    SERIAL_ECHOPAIR(" B" ,minsegmenttime ); 
    SERIAL_ECHOPAIR(" X" ,max_xy_jerk ); 
    SERIAL_ECHOPAIR(" Z" ,max_z_jerk);
    SERIAL_ECHOPAIR(" E" ,max_e_jerk);
    SERIAL_EOL; 

    SERIAL_ECHO_START;
    SERIAL_ECHOLNPGM("Home offset (mm):");
    SERIAL_ECHO_START;
    SERIAL_ECHOPAIR("  M206 X",add_homing[X_AXIS] );
    SERIAL_ECHOPAIR(" Y" ,add_homing[Y_AXIS] );
    SERIAL_ECHOPAIR(" Z" ,add_homing[Z_AXIS] );
    SERIAL_EOL;

    #if EXTRUDERS > 1 && !defined SINGLENOZZLE
      SERIAL_ECHO_START;
      SERIAL_ECHOLNPGM("Hotend offset (mm):");
      for (int e = 0; e < EXTRUDERS; e++) {
        SERIAL_ECHO_START;
        SERIAL_ECHOPAIR("  M218 T", (long unsigned int)e);
        SERIAL_ECHOPAIR(" X", hotend_offset[X_AXIS][e]);
        SERIAL_ECHOPAIR(" Y" ,hotend_offset[Y_AXIS][e]);
        SERIAL_EOL;
      }
    #endif //EXTRUDERS > 1 && !defined SINGLENOZZLE
    
    #ifdef DELTA
      SERIAL_ECHO_START;
      SERIAL_ECHOLNPGM("Endstop adjustment (mm):");
      SERIAL_ECHO_START;
      SERIAL_ECHOPAIR("  M666 X",endstop_adj[0]);
      SERIAL_ECHOPAIR(" Y" ,endstop_adj[1]);
      SERIAL_ECHOPAIR(" Z" ,endstop_adj[2]);
      SERIAL_EOL;
      SERIAL_ECHO_START;
      SERIAL_ECHOLNPGM("Delta Geometry adjustment:");
      SERIAL_ECHO_START;
      SERIAL_ECHOPAIR("  M666 A",tower_adj[0]);
      SERIAL_ECHOPAIR(" B" ,tower_adj[1]);
      SERIAL_ECHOPAIR(" C" ,tower_adj[2]);
      SERIAL_ECHOPAIR(" E" ,tower_adj[3]);
      SERIAL_ECHOPAIR(" F" ,tower_adj[4]);
      SERIAL_ECHOPAIR(" G" ,tower_adj[5]);
      SERIAL_ECHOPAIR(" R" ,delta_radius);
      SERIAL_ECHOPAIR(" D" ,delta_diagonal_rod);
      SERIAL_ECHOPAIR(" H" ,max_pos[2]);
      SERIAL_ECHOPAIR(" P" ,z_probe_offset[3]);
      SERIAL_EOL;
      SERIAL_ECHOLN("Tower Positions");
      SERIAL_ECHOPAIR("Tower1 X:",delta_tower1_x);
      SERIAL_ECHOPAIR(" Y:",delta_tower1_y);
      SERIAL_EOL;
      SERIAL_ECHOPAIR("Tower2 X:",delta_tower2_x);
      SERIAL_ECHOPAIR(" Y:",delta_tower2_y);
      SERIAL_EOL;
      SERIAL_ECHOPAIR("Tower3 X:",delta_tower3_x);
      SERIAL_ECHOPAIR(" Y:",delta_tower3_y);
      SERIAL_EOL;
    #endif // DELTA

    #ifdef ENABLE_AUTO_BED_LEVELING
      SERIAL_ECHO_START;
      SERIAL_ECHOLNPGM("Z Probe offset (mm)");
      SERIAL_ECHO_START;
      SERIAL_ECHOPAIR("  M666 P",zprobe_zoffset);
      SERIAL_EOL;
    #endif // ENABLE_AUTO_BED_LEVELING

    #ifdef PIDTEMP
      SERIAL_ECHO_START;
      SERIAL_ECHOLNPGM("PID settings:");
      #ifndef SINGLENOZZLE
        for (int e = 0; e < EXTRUDERS; e++)
      #else
        int e = 0;
      #endif
      {
        SERIAL_ECHO_START;
        SERIAL_ECHOPAIR("  M301 E", (long unsigned int)e);
        SERIAL_ECHOPAIR(" P", Kp[e]);
        SERIAL_ECHOPAIR(" I" ,unscalePID_i(Ki[e])); 
        SERIAL_ECHOPAIR(" D" ,unscalePID_d(Kd[e]));
        SERIAL_EOL; 
      }
    #endif // PIDTEMP

    #ifdef FWRETRACT
      SERIAL_ECHO_START;
      SERIAL_ECHOLNPGM("Retract: S=Length (mm) F:Speed (mm/m) Z: ZLift (mm)");
      SERIAL_ECHO_START;
      SERIAL_ECHOPAIR("  M207 S",retract_length); 
      SERIAL_ECHOPAIR(" F" ,retract_feedrate*60); 
      SERIAL_ECHOPAIR(" Z" ,retract_zlift);
      SERIAL_EOL; 
      SERIAL_ECHO_START;
      SERIAL_ECHOLNPGM("Recover: S=Extra length (mm) F:Speed (mm/m)");
      SERIAL_ECHO_START;
      SERIAL_ECHOPAIR("  M208 S",retract_recover_length); 
      SERIAL_ECHOPAIR(" F" ,retract_recover_feedrate*60); 
      SERIAL_EOL;
      SERIAL_ECHO_START;
      SERIAL_ECHOLNPGM("Auto-Retract: S=0 to disable, 1 to interpret extrude-only moves as retracts or recoveries");
      SERIAL_ECHO_START;
      SERIAL_ECHOPAIR("  M209 S", (unsigned long)(autoretract_enabled ? 1 : 0));
      SERIAL_EOL;
      #if EXTRUDERS > 1
        SERIAL_ECHO_START;
        SERIAL_ECHOLNPGM("Multi-extruder settings:");
        SERIAL_ECHO_START;
        SERIAL_ECHOPAIR("  Swap retract length (mm):    ", retract_length_swap);
        SERIAL_EOL;
        SERIAL_ECHO_START;
        SERIAL_ECHOPAIR("  Swap rec. addl. length (mm): ", retract_recover_length_swap);
        SERIAL_EOL;
      #endif //EXTRUDERS > 1
      SERIAL_ECHO_START;
      if (volumetric_enabled)
      {
        SERIAL_ECHOLNPGM("Filament settings:");
        SERIAL_ECHO_START;
        SERIAL_ECHOPAIR("  M200 D", filament_size[0]);
        SERIAL_EOL; 
        #if EXTRUDERS > 1
          SERIAL_ECHO_START;
          SERIAL_ECHOPAIR("  M200 T1 D", filament_size[1]);
          SERIAL_EOL; 
          #if EXTRUDERS > 2
            SERIAL_ECHO_START;
            SERIAL_ECHOPAIR("  M200 T2 D", filament_size[2]);
            SERIAL_EOL;
            #if EXTRUDERS > 3
              SERIAL_ECHO_START;
              SERIAL_ECHOPAIR("  M200 T3 D", filament_size[3]);
              SERIAL_EOL;
            #endif //EXTRUDERS > 3
          #endif //EXTRUDERS > 2
        #endif //EXTRUDERS > 1
      } else
      {
        SERIAL_ECHOLNPGM("Filament settings: Disabled");
      }
    #endif //FWRETRACT
  }
#endif //!DISABLE_M503
