@ -36,10 +36,12 @@
# define SOFT_PWM_SCALE 0
# endif
# if HOTENDS = = 1
# if HOTENDS < = 1
# define HOTEND_INDEX 0
# define E_UNUSED() UNUSED(e)
# else
# define HOTEND_INDEX e
# define E_UNUSED()
# endif
// PID storage
@ -47,6 +49,11 @@ typedef struct { float Kp, Ki, Kd; } PID_t;
typedef struct { float Kp , Ki , Kd , Kc ; } PIDC_t ;
# if ENABLED(PID_EXTRUSION_SCALING)
typedef PIDC_t hotend_pid_t ;
# if LPQ_MAX_LEN > 255
typedef uint16_t lpq_ptr_t ;
# else
typedef uint8_t lpq_ptr_t ;
# endif
# else
typedef PID_t hotend_pid_t ;
# endif
@ -54,11 +61,11 @@ typedef struct { float Kp, Ki, Kd, Kc; } PIDC_t;
# define DUMMY_PID_VALUE 3000.0f
# if ENABLED(PIDTEMP)
# define _PID_Kp(H) Temperature:: pid[H] .Kp
# define _PID_Ki(H) Temperature:: pid[H] .Ki
# define _PID_Kd(H) Temperature:: pid[H] .Kd
# define _PID_Kp(H) Temperature:: temp_hotend[H].pid .Kp
# define _PID_Ki(H) Temperature:: temp_hotend[H].pid .Ki
# define _PID_Kd(H) Temperature:: temp_hotend[H].pid .Kd
# if ENABLED(PID_EXTRUSION_SCALING)
# define _PID_Kc(H) Temperature:: pid[H] .Kc
# define _PID_Kc(H) Temperature:: temp_hotend[H].pid .Kc
# else
# define _PID_Kc(H) 1
# endif
@ -80,6 +87,14 @@ enum ADCSensorState : char {
PrepareTemp_0 ,
MeasureTemp_0 ,
# endif
# if HAS_HEATED_BED
PrepareTemp_BED ,
MeasureTemp_BED ,
# endif
# if HAS_TEMP_CHAMBER
PrepareTemp_CHAMBER ,
MeasureTemp_CHAMBER ,
# endif
# if HAS_TEMP_ADC_1
PrepareTemp_1 ,
MeasureTemp_1 ,
@ -96,13 +111,9 @@ enum ADCSensorState : char {
PrepareTemp_4 ,
MeasureTemp_4 ,
# endif
# if HAS_HEATED_BED
PrepareTemp_BED ,
MeasureTemp_BED ,
# endif
# if HAS_TEMP_CHAMBER
PrepareTemp_CHAMBER ,
MeasureTemp_CHAMBER ,
# if HAS_TEMP_ADC_5
PrepareTemp_5 ,
MeasureTemp_5 ,
# endif
# if ENABLED(FILAMENT_WIDTH_SENSOR)
Prepare_FILWIDTH ,
@ -136,16 +147,115 @@ enum ADCSensorState : char {
# define G26_CLICK_CAN_CANCEL (HAS_LCD_MENU && ENABLED(G26_MESH_VALIDATION))
enum TempIndex : uint8_t {
# if HOTENDS > 0
TEMP_E0 ,
# if HOTENDS > 1
TEMP_E1 ,
# if HOTENDS > 2
TEMP_E2 ,
# if HOTENDS > 3
TEMP_E3 ,
# if HOTENDS > 4
TEMP_E4 ,
# if HOTENDS > 5
TEMP_E5 ,
# endif
# endif
# endif
# endif
# endif
# endif
# if HAS_HEATED_BED
TEMP_BED ,
# endif
# if HAS_HEATED_CHAMBER
TEMP_CHAMBER ,
# endif
tempCOUNT
} ;
// A temperature sensor
typedef struct TempInfo {
uint16_t acc ;
int16_t raw ;
float current ;
} temp_info_t ;
// A PWM heater with temperature sensor
typedef struct HeaterInfo : public TempInfo {
int16_t target ;
uint8_t soft_pwm_amount ;
} heater_info_t ;
// A heater with PID stabilization
template < typename T >
struct PIDHeaterInfo : public HeaterInfo {
T pid ; // Initialized by settings.load()
} ;
# if ENABLED(PIDTEMP)
typedef struct PIDHeaterInfo < hotend_pid_t > hotend_info_t ;
# else
typedef heater_info_t hotend_info_t ;
# endif
# if HAS_HEATED_BED
# if ENABLED(PIDTEMPBED)
typedef struct PIDHeaterInfo < PID_t > bed_info_t ;
# else
typedef heater_info_t bed_info_t ;
# endif
# endif
# if HAS_TEMP_CHAMBER
# if HAS_HEATED_CHAMBER
# if ENABLED(PIDTEMPCHAMBER)
typedef struct PIDHeaterInfo < PID_t > chamber_info_t ;
# else
typedef heater_info_t chamber_info_t ;
# endif
# else
typedef temp_info_t chamber_info_t ;
# endif
# endif
// Heater idle handling
typedef struct {
millis_t timeout_ms ;
bool timed_out ;
inline void update ( const millis_t & ms ) { if ( ! timed_out & & timeout_ms & & ELAPSED ( ms , timeout_ms ) ) timed_out = true ; }
inline void start ( const millis_t & ms ) { timeout_ms = millis ( ) + ms ; timed_out = false ; }
inline void reset ( ) { timeout_ms = 0 ; timed_out = false ; }
inline void expire ( ) { start ( 0 ) ; }
} heater_idle_t ;
// Heater watch handling
typedef struct {
uint16_t target ;
millis_t next_ms ;
inline bool elapsed ( const millis_t & ms ) { return next_ms & & ELAPSED ( ms , next_ms ) ; }
inline bool elapsed ( ) { return elapsed ( millis ( ) ) ; }
} heater_watch_t ;
// Temperature sensor read value ranges
typedef struct { int16_t raw_min , raw_max ; } raw_range_t ;
typedef struct { int16_t mintemp , maxtemp ; } celsius_range_t ;
typedef struct { int16_t raw_min , raw_max , mintemp , maxtemp ; } temp_range_t ;
class Temperature {
public :
static volatile bool in_temp_isr ;
static float current_temperature [ HOTENDS ] ;
static int16_t current_temperature_raw [ HOTENDS ] ,
target_temperature [ HOTENDS ] ;
static uint8_t soft_pwm_amount [ HOTENDS ] ;
static hotend_info_t temp_hotend [ HOTENDS ] ;
# if HAS_HEATED_BED
static bed_info_t temp_bed ;
# endif
# if HAS_TEMP_CHAMBER
static chamber_info_t temp_chamber ;
# endif
# if ENABLED(AUTO_POWER_E_FANS)
static uint8_t autofan_speed [ HOTENDS ] ;
@ -156,19 +266,6 @@ class Temperature {
soft_pwm_count_fan [ FAN_COUNT ] ;
# endif
# if ENABLED(PIDTEMP)
static hotend_pid_t pid [ HOTENDS ] ;
# endif
# if HAS_HEATED_BED
static float current_temperature_bed ;
static int16_t current_temperature_bed_raw , target_temperature_bed ;
static uint8_t soft_pwm_amount_bed ;
# if ENABLED(PIDTEMPBED)
static PID_t bed_pid ;
# endif
# endif
# if ENABLED(BABYSTEPPING)
static volatile int16_t babystepsTodo [ 3 ] ;
# endif
@ -178,15 +275,11 @@ class Temperature {
static int16_t extrude_min_temp ;
FORCE_INLINE static bool tooCold ( const int16_t temp ) { return allow_cold_extrude ? false : temp < extrude_min_temp ; }
FORCE_INLINE static bool tooColdToExtrude ( const uint8_t e ) {
# if HOTENDS == 1
UNUSED ( e ) ;
# endif
E_UNUSED ( ) ;
return tooCold ( degHotend ( HOTEND_INDEX ) ) ;
}
FORCE_INLINE static bool targetTooColdToExtrude ( const uint8_t e ) {
# if HOTENDS == 1
UNUSED ( e ) ;
# endif
E_UNUSED ( ) ;
return tooCold ( degTargetHotend ( HOTEND_INDEX ) ) ;
}
# else
@ -197,6 +290,16 @@ class Temperature {
FORCE_INLINE static bool hotEnoughToExtrude ( const uint8_t e ) { return ! tooColdToExtrude ( e ) ; }
FORCE_INLINE static bool targetHotEnoughToExtrude ( const uint8_t e ) { return ! targetTooColdToExtrude ( e ) ; }
# if HEATER_IDLE_HANDLER
static heater_idle_t hotend_idle [ HOTENDS ] ;
# if HAS_HEATED_BED
static heater_idle_t bed_idle ;
# endif
# if HAS_HEATED_CHAMBER
static heater_idle_t chamber_idle ;
# endif
# endif
private :
# if EARLY_WATCHDOG
@ -204,11 +307,9 @@ class Temperature {
# endif
static volatile bool temp_meas_ready ;
static uint16_t raw_temp_value [ MAX_EXTRUDERS ] ;
# if WATCH_HOTENDS
static uint16_t watch_target_temp [ HOTENDS ] ;
static millis_t watch_heater_next_ms [ HOTENDS ] ;
static heater_watch_t watch_hotend [ HOTENDS ] ;
# endif
# if ENABLED(TEMP_SENSOR_1_AS_REDUNDANT)
@ -217,42 +318,40 @@ class Temperature {
# endif
# if ENABLED(PID_EXTRUSION_SCALING)
static long last_e_position ;
static long lpq [ LPQ_MAX_LEN ] ;
static int lpq_ptr ;
static int32_t last_e_position , lpq [ LPQ_MAX_LEN ] ;
static lpq_ptr_t lpq_ptr ;
# endif
// Init min and max temp with extreme values to prevent false errors during startup
static int16_t minttemp_raw [ HOTENDS ] ,
maxttemp_raw [ HOTENDS ] ,
minttemp [ HOTENDS ] ,
maxttemp [ HOTENDS ] ;
static temp_range_t temp_range [ HOTENDS ] ;
# if HAS_HEATED_BED
static uint16_t raw_temp_bed_value ;
# if WATCH_THE_BED
static uint16_t watch_target_bed_temp ;
static millis_t watch_bed_next_ms ;
# if WATCH_BED
static heater_watch_t watch_bed ;
# endif
# if DISABLED(PIDTEMPBED)
static millis_t next_bed_check_ms ;
# endif
# if HEATER_IDLE_HANDLER
static millis_t bed_idle_timeout_ms ;
static bool bed_idle_timeout_exceeded ;
# endif
# ifdef BED_MINTEMP
static int16_t bed_ mint temp_raw;
static int16_t mintemp_raw_BED ;
# endif
# ifdef BED_MAXTEMP
static int16_t bed_ maxt temp_raw;
static int16_t maxtemp_raw_BED ;
# endif
# endif
# if HAS_TEMP_CHAMBER
static uint16_t raw_temp_chamber_value ;
static float current_temperature_chamber ;
static int16_t current_temperature_chamber_raw ;
# if HAS_HEATED_CHAMBER
# if WATCH_CHAMBER
static heater_watch_t watch_chamber ;
# endif
# if DISABLED(PIDTEMPCHAMBER)
static millis_t next_chamber_check_ms ;
# endif
# ifdef CHAMBER_MINTEMP
static int16_t mintemp_raw_CHAMBER ;
# endif
# ifdef CHAMBER_MAXTEMP
static int16_t maxtemp_raw_CHAMBER ;
# endif
# endif
# ifdef MAX_CONSECUTIVE_LOW_TEMPERATURE_ERROR_ALLOWED
@ -279,11 +378,6 @@ class Temperature {
static bool paused ;
# endif
# if HEATER_IDLE_HANDLER
static millis_t heater_idle_timeout_ms [ HOTENDS ] ;
static bool heater_idle_timeout_exceeded [ HOTENDS ] ;
# endif
public :
# if HAS_ADC_BUTTONS
static uint32_t current_ADCKey_raw ;
@ -311,7 +405,7 @@ class Temperature {
static float analog_to_celsius_bed ( const int raw ) ;
# endif
# if HAS_TEMP_CHAMBER
static float analog_to_celsius C hamber( const int raw ) ;
static float analog_to_celsius _c hamber( const int raw ) ;
# endif
# if FAN_COUNT > 0
@ -395,21 +489,15 @@ class Temperature {
*/
# ifdef MILLISECONDS_PREHEAT_TIME
static bool is_preheating ( const uint8_t e ) {
# if HOTENDS == 1
UNUSED ( e ) ;
# endif
E_UNUSED ( ) ;
return preheat_end_time [ HOTEND_INDEX ] & & PENDING ( millis ( ) , preheat_end_time [ HOTEND_INDEX ] ) ;
}
static void start_preheat_time ( const uint8_t e ) {
# if HOTENDS == 1
UNUSED ( e ) ;
# endif
E_UNUSED ( ) ;
preheat_end_time [ HOTEND_INDEX ] = millis ( ) + MILLISECONDS_PREHEAT_TIME ;
}
static void reset_preheat_time ( const uint8_t e ) {
# if HOTENDS == 1
UNUSED ( e ) ;
# endif
E_UNUSED ( ) ;
preheat_end_time [ HOTEND_INDEX ] = 0 ;
}
# else
@ -427,63 +515,81 @@ class Temperature {
//deg=degreeCelsius
FORCE_INLINE static float degHotend ( const uint8_t e ) {
# if HOTENDS == 1
UNUSED ( e ) ;
# endif
return current_temperature [ HOTEND_INDEX ] ;
E_UNUSED ( ) ;
return temp_hotend [ HOTEND_INDEX ] . current ;
}
# if ENABLED(SHOW_TEMP_ADC_VALUES)
FORCE_INLINE static int16_t rawHotendTemp ( const uint8_t e ) {
# if HOTENDS == 1
UNUSED ( e ) ;
# endif
return current_temperature_raw [ HOTEND_INDEX ] ;
E_UNUSED ( ) ;
return temp_hotend [ HOTEND_INDEX ] . raw ;
}
# endif
FORCE_INLINE static int16_t degTargetHotend ( const uint8_t e ) {
# if HOTENDS == 1
UNUSED ( e ) ;
# endif
return target_temperature [ HOTEND_INDEX ] ;
E_UNUSED ( ) ;
return temp_hotend [ HOTEND_INDEX ] . target ;
}
# if WATCH_HOTENDS
static void start_watching_heater ( const uint8_t e = 0 ) ;
static void start_watching_heater ( const uint8_t e = 0 ) ;
# else
static inline void start_watching_heater ( const uint8_t e = 0 ) { UNUSED ( e ) ; }
# endif
# if HAS_LCD_MENU
static inline void start_watching_E0 ( ) { start_watching_heater ( 0 ) ; }
static inline void start_watching_E1 ( ) { start_watching_heater ( 1 ) ; }
static inline void start_watching_E2 ( ) { start_watching_heater ( 2 ) ; }
static inline void start_watching_E3 ( ) { start_watching_heater ( 3 ) ; }
static inline void start_watching_E4 ( ) { start_watching_heater ( 4 ) ; }
static inline void start_watching_E5 ( ) { start_watching_heater ( 5 ) ; }
# endif
static void setTargetHotend ( const int16_t celsius , const uint8_t e ) {
# if HOTENDS == 1
UNUSED ( e ) ;
# endif
E_UNUSED ( ) ;
# ifdef MILLISECONDS_PREHEAT_TIME
if ( celsius = = 0 )
reset_preheat_time ( HOTEND_INDEX ) ;
else if ( target_temperature [ HOTEND_INDEX ] = = 0 )
else if ( t emp_hotend[ HOTEND_INDEX ] . target = = 0 )
start_preheat_time ( HOTEND_INDEX ) ;
# endif
# if ENABLED(AUTO_POWER_CONTROL)
powerManager . power_on ( ) ;
# endif
target_temperature [ HOTEND_INDEX ] = MIN ( celsius , maxttemp [ HOTEND_INDEX ] - 15 ) ;
# if WATCH_HOTENDS
start_watching_heater ( HOTEND_INDEX ) ;
# endif
temp_hotend [ HOTEND_INDEX ] . target = MIN ( celsius , temp_range [ HOTEND_INDEX ] . maxtemp - 15 ) ;
start_watching_heater ( HOTEND_INDEX ) ;
}
# if WATCH_CHAMBER
static void start_watching_chamber ( ) ;
# else
static inline void start_watching_chamber ( ) { }
# endif
# if HAS_TEMP_CHAMBER
static void setTargetChamber ( const int16_t celsius ) {
# if HAS_HEATED_CHAMBER
temp_chamber . target =
# ifdef CHAMBER_MAXTEMP
min ( celsius , CHAMBER_MAXTEMP )
# else
celsius
# endif
;
start_watching_chamber ( ) ;
# endif // HAS_HEATED_CHAMBER
}
# endif // HAS_TEMP_CHAMBER
FORCE_INLINE static bool isHeatingHotend ( const uint8_t e ) {
# if HOTENDS == 1
UNUSED ( e ) ;
# endif
return target_temperature [ HOTEND_INDEX ] > current_temperature [ HOTEND_INDEX ] ;
E_UNUSED ( ) ;
return temp_hotend [ HOTEND_INDEX ] . target > temp_hotend [ HOTEND_INDEX ] . current ;
}
FORCE_INLINE static bool isCoolingHotend ( const uint8_t e ) {
# if HOTENDS == 1
UNUSED ( e ) ;
# endif
return target_temperature [ HOTEND_INDEX ] < current_temperature [ HOTEND_INDEX ] ;
E_UNUSED ( ) ;
return temp_hotend [ HOTEND_INDEX ] . target < temp_hotend [ HOTEND_INDEX ] . current ;
}
# if HAS_TEMP_HOTEND
@ -497,33 +603,33 @@ class Temperature {
# if HAS_HEATED_BED
# if ENABLED(SHOW_TEMP_ADC_VALUES)
FORCE_INLINE static int16_t rawBedTemp ( ) { return current_temperature_bed_raw ; }
FORCE_INLINE static int16_t rawBedTemp ( ) { return temp_bed . raw ; }
# endif
FORCE_INLINE static float degBed ( ) { return temp_bed . current ; }
FORCE_INLINE static int16_t degTargetBed ( ) { return temp_bed . target ; }
FORCE_INLINE static bool isHeatingBed ( ) { return temp_bed . target > temp_bed . current ; }
FORCE_INLINE static bool isCoolingBed ( ) { return temp_bed . target < temp_bed . current ; }
# if WATCH_BED
static void start_watching_bed ( ) ;
# else
static inline void start_watching_bed ( ) { }
# endif
FORCE_INLINE static float degBed ( ) { return current_temperature_bed ; }
FORCE_INLINE static int16_t degTargetBed ( ) { return target_temperature_bed ; }
FORCE_INLINE static bool isHeatingBed ( ) { return target_temperature_bed > current_temperature_bed ; }
FORCE_INLINE static bool isCoolingBed ( ) { return target_temperature_bed < current_temperature_bed ; }
static void setTargetBed ( const int16_t celsius ) {
# if ENABLED(AUTO_POWER_CONTROL)
powerManager . power_on ( ) ;
# endif
t arget_temperature_bed =
t emp_bed. target =
# ifdef BED_MAXTEMP
MIN ( celsius , BED_MAXTEMP - 15 )
# else
celsius
# endif
;
# if WATCH_THE_BED
start_watching_bed ( ) ;
# endif
start_watching_bed ( ) ;
}
# if WATCH_THE_BED
static void start_watching_bed ( ) ;
# endif
static bool wait_for_bed ( const bool no_wait_for_cooling = true
# if G26_CLICK_CAN_CANCEL
, const bool click_to_cancel = false
@ -534,10 +640,15 @@ class Temperature {
# if HAS_TEMP_CHAMBER
# if ENABLED(SHOW_TEMP_ADC_VALUES)
FORCE_INLINE static int16_t rawChamberTemp ( ) { return current_temperature_chamber_ raw; }
FORCE_INLINE static int16_t rawChamberTemp ( ) { return temp_chamber. raw; }
# endif
FORCE_INLINE static float degChamber ( ) { return current_temperature_chamber ; }
# endif
FORCE_INLINE static float degChamber ( ) { return temp_chambercurrent ; }
# if HAS_HEATED_CHAMBER
FORCE_INLINE static bool isHeatingChamber ( ) { return temp_chamber . target > temp_chambercurrent ; }
FORCE_INLINE static bool isCoolingChamber ( ) { return temp_chamber . target < temp_chambercurrent ; }
FORCE_INLINE static int16_t degTargetChamber ( ) { return temp_chamber . target ; }
# endif
# endif // HAS_TEMP_CHAMBER
FORCE_INLINE static bool still_heating ( const uint8_t e ) {
return degTargetHotend ( e ) > TEMP_HYSTERESIS & & ABS ( degHotend ( e ) - degTargetHotend ( e ) ) > TEMP_HYSTERESIS ;
@ -589,47 +700,17 @@ class Temperature {
# if HEATER_IDLE_HANDLER
static void start_heater_idle_timer ( const uint8_t e , const millis_t timeout_ms ) {
# if HOTENDS == 1
UNUSED ( e ) ;
# endif
heater_idle_timeout_ms [ HOTEND_INDEX ] = millis ( ) + timeout_ms ;
heater_idle_timeout_exceeded [ HOTEND_INDEX ] = false ;
}
static void reset_heater_idle_timer ( const uint8_t e ) {
# if HOTENDS == 1
UNUSED ( e ) ;
# endif
heater_idle_timeout_ms [ HOTEND_INDEX ] = 0 ;
heater_idle_timeout_exceeded [ HOTEND_INDEX ] = false ;
# if WATCH_HOTENDS
start_watching_heater ( HOTEND_INDEX ) ;
# endif
}
FORCE_INLINE static bool is_heater_idle ( const uint8_t e ) {
# if HOTENDS == 1
UNUSED ( e ) ;
# endif
return heater_idle_timeout_exceeded [ HOTEND_INDEX ] ;
E_UNUSED ( ) ;
hotend_idle [ HOTEND_INDEX ] . reset ( ) ;
start_watching_heater ( HOTEND_INDEX ) ;
}
# if HAS_HEATED_BED
static void start_bed_idle_timer ( const millis_t timeout_ms ) {
bed_idle_timeout_ms = millis ( ) + timeout_ms ;
bed_idle_timeout_exceeded = false ;
}
static void reset_bed_idle_timer ( ) {
bed_idle_timeout_ms = 0 ;
bed_idle_timeout_exceeded = false ;
# if WATCH_THE_BED
start_watching_bed ( ) ;
# endif
bed_idle . reset ( ) ;
start_watching_bed ( ) ;
}
FORCE_INLINE static bool is_bed_idle ( ) { return bed_idle_timeout_exceeded ; }
# endif
# endif // HEATER_IDLE_HANDLER
@ -689,26 +770,38 @@ class Temperature {
static float get_pid_output_bed ( ) ;
# endif
# if HAS_HEATED_CHAMBER
static float get_pid_output_chamber ( ) ;
# endif
static void _temp_error ( const int8_t e , PGM_P const serial_msg , PGM_P const lcd_msg ) ;
static void min_temp_error ( const int8_t e ) ;
static void max_temp_error ( const int8_t e ) ;
# if HAS_TEMP_CHAMBER
static void chamber_temp_error ( const bool max ) ;
# endif
# if ENABLED(THERMAL_PROTECTION_HOTENDS) || HAS_THERMALLY_PROTECTED_BED
# if ENABLED(THERMAL_PROTECTION_HOTENDS) || HAS_THERMALLY_PROTECTED_BED || ENABLED(THERMAL_PROTECTION_CHAMBER)
enum TRState : char { TRInactive , TRFirstHeating , TRStable , TRRunaway } ;
static void thermal_runaway_protection ( TRState * const state , millis_t * const timer , const float & current , const float & target , const int8_t heater_id , const uint16_t period_seconds , const uint16_t hysteresis_degc ) ;
typedef struct {
millis_t timer = 0 ;
TRState state = TRInactive ;
} tr_state_machine_t ;
# if ENABLED(THERMAL_PROTECTION_HOTENDS)
static TRState thermal_runaway_state_machine [ HOTENDS ] ;
static millis_t thermal_runaway_timer [ HOTENDS ] ;
static tr_state_machine_t tr_state_machine [ HOTENDS ] ;
# endif
# if HAS_THERMALLY_PROTECTED_BED
static TRState thermal_runaway_bed_state_machine ;
static millis_t thermal_runaway_bed_timer ;
static tr_state_machine_t tr_state_machine_bed ;
# endif
# if ENABLED(THERMAL_PROTECTION_CHAMBER)
static tr_state_machine_t tr_state_machine_chamber ;
# endif
static void thermal_runaway_protection ( tr_state_machine_t & state , const float & current , const float & target , const int8_t heater_id , const uint16_t period_seconds , const uint16_t hysteresis_degc ) ;
# endif // THERMAL_PROTECTION
} ;