# de1 internal state live variables package provide de1_vars 1.2 package require lambda package require de1_device_scale 1.0 package require de1_event 1.0 package require de1_logging 1.0 package require de1_profile 2.0 package require de1_shot 2.0 ############################# # raw data from the DE1 proc clear_espresso_chart {} { espresso_elapsed length 0 espresso_pressure length 0 espresso_weight length 0 espresso_weight_chartable length 0 espresso_flow length 0 espresso_flow_weight length 0 espresso_flow_weight_raw length 0 espresso_flow_weight_2x length 0 espresso_water_dispensed length 0 espresso_flow_2x length 0 espresso_resistance length 0 espresso_resistance_weight length 0 espresso_pressure_delta length 0 espresso_flow_delta length 0 espresso_flow_delta_negative length 0 espresso_flow_delta_negative_2x length 0 espresso_temperature_mix length 0 espresso_temperature_basket length 0 espresso_state_change length 0 espresso_pressure_goal length 0 espresso_flow_goal length 0 espresso_flow_goal_2x length 0 espresso_temperature_goal length 0 espresso_de1_explanation_chart_elapsed length 0 espresso_de1_explanation_chart_elapsed_1 length 0 espresso_de1_explanation_chart_elapsed_2 length 0 espresso_de1_explanation_chart_elapsed_3 length 0 espresso_de1_explanation_chart_elapsed_flow length 0 espresso_de1_explanation_chart_elapsed_flow_1 length 0 espresso_de1_explanation_chart_elapsed_flow_2 length 0 espresso_de1_explanation_chart_elapsed_flow_3 length 0 espresso_elapsed append 0 espresso_pressure append 0 #god_espresso_pressure append 0 espresso_weight append 0 espresso_weight_chartable append 0 espresso_flow append 0 espresso_flow_weight append 0 espresso_flow_weight_2x append 0 espresso_flow_weight_raw append 0 espresso_water_dispensed append 0 espresso_flow_2x append 0 espresso_resistance append 0 espresso_resistance_weight append 0 espresso_pressure_delta append 0 espresso_flow_delta append 0 espresso_flow_delta_negative append 0 espresso_flow_delta_negative_2x append 0 espresso_temperature_mix append [return_temperature_number $::settings(espresso_temperature)] espresso_temperature_basket append [return_temperature_number $::settings(espresso_temperature)] espresso_state_change append 0 espresso_pressure_goal append -1 espresso_flow_goal append -1 espresso_flow_goal_2x append -1 espresso_temperature_goal append [return_temperature_number $::settings(espresso_temperature)] god_shot_reference_reset catch { # update the Y axis on the temperature chart, each time that we make an espresso, in case the goal temperature changed #update_temperature_charts_y_axis } } proc espresso_chart_structures {} { return [list espresso_elapsed espresso_pressure espresso_weight espresso_weight_chartable espresso_flow espresso_flow_weight espresso_flow_weight_raw espresso_water_dispensed espresso_flow_weight_2x espresso_flow_2x espresso_resistance espresso_resistance_weight espresso_pressure_delta espresso_flow_delta espresso_flow_delta_negative espresso_flow_delta_negative_2x espresso_temperature_mix espresso_temperature_basket espresso_state_change espresso_pressure_goal espresso_flow_goal espresso_flow_goal_2x espresso_temperature_goal espresso_de1_explanation_chart_flow espresso_de1_explanation_chart_elapsed_flow espresso_de1_explanation_chart_flow_2x espresso_de1_explanation_chart_flow_1_2x espresso_de1_explanation_chart_flow_2_2x espresso_de1_explanation_chart_flow_3_2x espresso_de1_explanation_chart_pressure espresso_de1_explanation_chart_temperature espresso_de1_explanation_chart_temperature_10 espresso_de1_explanation_chart_pressure_1 espresso_de1_explanation_chart_pressure_2 espresso_de1_explanation_chart_pressure_3 espresso_de1_explanation_chart_elapsed_flow espresso_de1_explanation_chart_elapsed_flow_1 espresso_de1_explanation_chart_elapsed_flow_2 espresso_de1_explanation_chart_elapsed_flow_3 espresso_de1_explanation_chart_elapsed espresso_de1_explanation_chart_elapsed_1 espresso_de1_explanation_chart_elapsed_2 espresso_de1_explanation_chart_elapsed_3] } proc backup_espresso_chart {} { unset -nocomplain ::chartbk foreach s [espresso_chart_structures] { if {[$s length] > 0} { set ::chartbk($s) [$s range 0 end] } else { set ::chartbk($s) {} } } } proc restore_espresso_chart {} { foreach s [espresso_chart_structures] { $s length 0 if {[info exists ::chartbk($s)] == 1} { $s append $::chartbk($s) } } } proc god_shot_reference_reset {} { ############################################################################################################ # god shot reference god_espresso_pressure length 0 god_espresso_temperature_basket length 0 god_espresso_flow length 0 god_espresso_flow_weight length 0 god_espresso_flow_2x length 0 god_espresso_flow_weight_2x length 0 if {[info exists ::settings(god_espresso_elapsed)] == 1} { espresso_elapsed length 0 espresso_elapsed append $::settings(god_espresso_elapsed) } god_espresso_pressure append $::settings(god_espresso_pressure) god_espresso_temperature_basket append $::settings(god_espresso_temperature_basket) if {$::settings(god_espresso_flow) != {} } { god_espresso_flow append $::settings(god_espresso_flow) # in zoomed flow/pressure mode we chart flow/weight at 2x the normal Y axis, so we need to populate those vectors by hand foreach flow $::settings(god_espresso_flow) { god_espresso_flow_2x append [expr {2.0 * $flow}] } } if {$::settings(god_espresso_flow_weight) != {} } { god_espresso_flow_weight append $::settings(god_espresso_flow_weight) foreach flow_weight $::settings(god_espresso_flow_weight) { god_espresso_flow_weight_2x append [expr {2.0 * $flow_weight}] } } ############################################################################################################ } proc espresso_frame_title {num} { if {$num == 1} { return "1) Ramp up pressure to 8.4 bar over 12 seconds" } elseif {$num == 2} { return "2) Hold pressure at 8.4 bars for 10 seconds" } elseif {$num == 3} { return "3) Maintain 1.2 mL/s flow rate for 30 seconds" } elseif {$num == 4} { return "" } elseif {$num == 5} { return "" } elseif {$num == 6} { return "" } } proc espresso_frame_description {num} { if {$num == 1} { return "Gently go to 8.4 bar of pressure with a water mix temperature of 92\u00B0C. Go to the next step after 10 seconds. temperature of 92\u00B0C. Gently go to 8.4 bar of pressure with a water mix temperature of 92\u00B0C." } elseif {$num == 2} { return "Quickly go to 8.4 bar of pressure with a basket temperature of 90\u00B0C. Go to the next step after 10 seconds." } elseif {$num == 3} { return "Automatically manage pressure to attain a flow rate of 1.2 mL/s at a water temperature of 88\u00B0C. End this step after 30 seconds." } elseif {$num == 4} { return "" } elseif {$num == 5} { return "" } elseif {$num == 6} { return "" } } proc set_alarms_for_de1_wake_sleep {} { # first clear existing timers if {[info exists ::alarms_for_de1_wake] == 1} { after cancel $::alarms_for_de1_wake unset ::alarms_for_de1_wake } if {[info exists ::alarms_for_de1_sleep] == 1} { after cancel $::alarms_for_de1_sleep unset ::alarms_for_de1_sleep } # if the timers are active, then find the next alarm time and set an alarm to wake in that many milliseconds from now if {$::settings(scheduler_enable) == 1} { set wake_seconds [expr {[next_alarm_time $::settings(scheduler_wake)] - [clock seconds]}] set ::alarms_for_de1_wake [after [expr {1000 * $wake_seconds}] scheduler_wake] # scheduled sleep is now an enforced awake time and this function should not be called # set sleep_seconds [expr {[next_alarm_time $::settings(scheduler_sleep)] - [clock seconds]}] # set ::alarms_for_de1_sleep [after [expr {1000 * $sleep_seconds}] scheduler_sleep] } } proc scheduler_wake {} { msg -NOTICE "Scheduled wake occured at [clock format [clock seconds]]" start_schedIdle # after alarm has occured go ahead and set the alarm for tommorrow after 2000 set_alarms_for_de1_wake_sleep } proc scheduler_sleep {} { msg -ERROR "OBSOLETE: scheduled sleep is now an enforced awake time and this function should not be called" msg -NOTICE "Scheduled sleep occured at [clock format [clock seconds]]" start_sleep # after alarm has occured go ahead and set the alarm for tommorrow after 2000 set_alarms_for_de1_wake_sleep } proc current_alarm_time { in } { set alarm [expr {[round_date_to_nearest_day [clock seconds]] + round($in)}] return $alarm } proc next_alarm_time { in } { set alarm [expr {[round_date_to_nearest_day [clock seconds]] + round($in)}] if {$alarm < [clock seconds] } { # if the alarm time has passed, set it for tomorrow #set alarm [expr {$alarm + 86400} ] set alarm [clock add $alarm 1 day] } return $alarm } proc time_format {seconds {crlf 0} {include_day 0} } { if {$seconds == ""} { # no time passed return "" } set crlftxt " " if {$crlf == 1} { set crlftxt \n } if {$include_day == 0} { # john decided to no longer include the day on the alarm, as it's more info that is needed for an alarm clock if {$::settings(enable_ampm) == 1} { return [subst {[string trim [clock format $seconds -format {%l:%M %p}]]}] } else { return [subst {[string trim [clock format $seconds -format {%H:%M}]]}] } } else { if {$::settings(enable_ampm) == 1} { return [subst {[translate [clock format $seconds -format {%A}]]$crlftxt[string trim [clock format $seconds -format {%l:%M %p}]]}] } else { return [subst {[translate [clock format $seconds -format {%A}]]$crlftxt[string trim [clock format $seconds -format {%H:%M}]]}] } } } proc stripzeros {value} { set retval [string trimleft $value 0] if { ![string length $retval] } { return 0 } return $retval } proc format_alarm_time { in } { return [time_format [next_alarm_time $in]] } proc start_timer_espresso_preinfusion {} { set ::timers(espresso_preinfusion_start) [clock milliseconds] } proc stop_timer_espresso_preinfusion {} { set ::timers(espresso_preinfusion_stop) [clock milliseconds] } proc start_timer_espresso_pour {} { set ::timers(espresso_pour_start) [clock milliseconds] } proc start_timer_water_pour {} { set ::timers(water_pour_stop) 0 set ::timers(water_pour_start) [clock milliseconds] } proc start_timer_steam_pour {} { set ::timers(steam_pour_stop) 0 set ::timers(steam_pour_start) [clock milliseconds] } proc start_timer_flush_pour {} { set ::timers(flush_pour_stop) 0 set ::timers(flush_pour_start) [clock milliseconds] } proc stop_timer_espresso_pour {} { set ::timers(espresso_pour_stop) [clock milliseconds] } proc stop_timer_water_pour {} { msg -DEBUG "stop_timer_water_pour" set ::timers(water_pour_stop) [clock milliseconds] } proc stop_timer_steam_pour {} { msg -DEBUG "stop_timer_steam_pour" set ::timers(steam_pour_stop) [clock milliseconds] } proc stop_timer_flush_pour {} { msg -DEBUG "stop_timer_flush_pour" set ::timers(flush_pour_stop) [clock milliseconds] } proc stop_espresso_timers {} { if {$::timer_running != 1} { return } set ::timer_running 0 set ::timers(espresso_stop) [clock milliseconds] } proc start_espresso_timers {} { clear_espresso_timers set ::timer_running 1 set ::timers(espresso_start) [clock milliseconds] } proc clear_espresso_timers {} { unset -nocomplain ::timers set ::timers(espresso_start) 0 set ::timers(espresso_stop) 0 unset -nocomplain ::substate_timers set ::substate_timers(espresso_start) 0 set ::substate_timers(espresso_stop) 0 set ::timers(espresso_preinfusion_start) 0 set ::timers(espresso_preinfusion_stop) 0 set ::timers(espresso_pour_start) 0 set ::timers(espresso_pour_stop) 0 set ::timer_running 0 } clear_espresso_timers #stop_timers # amount of time that we've been on this page #set ::timer [clock seconds] proc espresso_timer {} { #global start_timer #if {$::timer_running == 1} { # set ::timer [clock seconds] #} #return $timer return [expr {([clock milliseconds] - $::timers(espresso_start) )/1000}] } proc espresso_millitimer {{time_reference 0}} { # Accept seconds or ms, always returns ms; 10000000000 is Sat Nov 20 09:46:40 PST 2286 if { $time_reference == 0 } { set time_reference [clock milliseconds] } elseif { $time_reference < 10000000000 } { set time_reference [expr { $time_reference * 1000. }] } return [expr { $time_reference - $::timers(espresso_start) }] } proc espresso_elapsed_timer {} { if {$::timers(espresso_start) == 0} { return 0 } elseif {$::timers(espresso_stop) == 0} { # no stop, so show current elapsed time return [expr {([clock milliseconds] - $::timers(espresso_start))/1000}] } else { # stop occured, so show that. return [expr {($::timers(espresso_stop) - $::timers(espresso_start))/1000}] } } proc espresso_preinfusion_timer {} { if {$::timers(espresso_preinfusion_start) == 0} { return 0 } elseif {$::timers(espresso_preinfusion_stop) == 0} { # no stop, so show current elapsed time return [expr {([clock milliseconds] - $::timers(espresso_preinfusion_start))/1000}] } else { # stop occured, so show that. return [expr {($::timers(espresso_preinfusion_stop) - $::timers(espresso_preinfusion_start))/1000}] } } proc espresso_pour_timer {} { if {[info exists ::timers(espresso_pour_start)] != 1} { return 0 } if {$::timers(espresso_pour_start) == 0} { return 0 } elseif {$::timers(espresso_pour_stop) == 0} { # no stop, so show current elapsed time return [expr {([clock milliseconds] - $::timers(espresso_pour_start))/1000}] } else { # stop occured, so show that. return [expr {($::timers(espresso_pour_stop) - $::timers(espresso_pour_start))/1000}] } } proc water_pour_timer {} { if {[info exists ::timers(water_pour_start)] != 1} { return 0 } if {$::timers(water_pour_start) == 0} { return 0 } elseif {$::timers(water_pour_stop) == 0} { # no stop, so show current elapsed time return [expr {([clock milliseconds] - $::timers(water_pour_start))/1000}] } else { # stop occured, so show that. return [expr {($::timers(water_pour_stop) - $::timers(water_pour_start))/1000}] } } proc steam_pour_timer {} { if {[info exists ::timers(steam_pour_start)] != 1} { return 0 } if {$::timers(steam_pour_start) == 0} { return 0 } elseif {$::timers(steam_pour_stop) == 0} { # no stop, so show current elapsed time return [expr {([clock milliseconds] - $::timers(steam_pour_start))/1000}] } else { # stop occured, so show that. return [expr {($::timers(steam_pour_stop) - $::timers(steam_pour_start))/1000}] } } proc steam_pour_millitimer {{time_reference 0}} { # Accept seconds or ms, always returns ms; 10000000000 is Sat Nov 20 09:46:40 PST 2286 if { $time_reference == 0 } { set time_reference [clock milliseconds] } elseif { $time_reference < 10000000000 } { set time_reference [expr { $time_reference * 1000. }] } if {[info exists ::timers(steam_pour_start)] != 1} { return 0 } if {$::timers(steam_pour_start) == 0} { return 0 } elseif {$::timers(steam_pour_stop) == 0} { # no stop, so show current elapsed time return [expr {$time_reference - $::timers(steam_pour_start)}] } else { # stop occured, so show that. return [expr {$::timers(steam_pour_stop) - $::timers(steam_pour_start)}] } } proc flush_pour_timer {} { set t "" set c 0 if {[info exists ::timers(flush_pour_start)] != 1} { set t "-0" set c 1 } elseif {$::timers(flush_pour_start) == 0} { set t "-1" set c 2 } elseif {$::timers(flush_pour_stop) == 0} { # no stop, so show current elapsed time set t [expr {([clock milliseconds] - $::timers(flush_pour_start))/1000}] set c 3 } else { # stop occured, so show that. set t [expr {($::timers(flush_pour_stop) - $::timers(flush_pour_start))/1000}] set c 4 } return $t } proc done_timer {} { if {$::timers(stop) == 0} { return 0 } else { # no stop, so show current elapsed time return [expr {([clock milliseconds] - $::timers(stop))/1000}] } } proc espresso_done_timer {} { if {[info exists ::timers(espresso_stop)] != 1} { return 0 } if {$::timers(espresso_stop) == 0} { return 0 } else { # no stop, so show current elapsed time return [expr {([clock milliseconds] - $::timers(espresso_stop))/1000}] } } proc water_done_timer {} { if {[info exists ::timers(water_pour_stop)] != 1} { return 0 } if {$::timers(water_pour_stop) == 0} { return 0 } else { # no stop, so show current elapsed time return [expr {([clock milliseconds] - $::timers(water_pour_stop))/1000}] } } proc steam_done_timer {} { if {[info exists ::timers(steam_pour_stop)] != 1} { return 0 } if {$::timers(steam_pour_stop) == 0} { return 0 } else { # no stop, so show current elapsed time return [expr {([clock milliseconds] - $::timers(steam_pour_stop))/1000}] } } proc flush_done_timer {} { if {[info exists ::timers(flush_pour_stop)] != 1} { return 0 } if {$::timers(flush_pour_stop) == 0} { return 0 } else { # no stop, so show current elapsed time return [expr {([clock milliseconds] - $::timers(flush_pour_stop))/1000}] } } proc waterflow {} { if {$::de1(substate) != $::de1_substate_types_reversed(pouring) && $::de1(substate) != $::de1_substate_types_reversed(preinfusion)} { return 0 } if {$::android == 0} { if {[ifexists ::de1(flow)] == ""} { set ::de1(flow) 3 } if {$::de1(flow) > 5} { set ::de1(flow) 4.5 } if {$::de1(flow) < 1} { set ::de1(flow) 1.5 } set ::de1(flow) [expr {(.1 * (rand() - 0.5)) + $::de1(flow)}] if {$::de1_num_state($::de1(state)) == "Espresso"} { if {[espresso_millitimer] < 5000} { set ::de1(preinfusion_volume) [expr {$::de1(preinfusion_volume) + ($::de1(flow) * .1) }] } else { set ::de1(pour_volume) [expr {$::de1(pour_volume) + ($::de1(flow) * .1) }] } } #set ::de1(flow) [expr {rand() + $::de1(flow) - 0.5}] } return $::de1(flow) } set start_timer [clock seconds] set start_millitimer [clock milliseconds] proc watervolume {} { if {$::de1(substate) != $::de1_substate_types_reversed(pouring) && $::de1(substate) != $::de1_substate_types_reversed(preinfusion)} { return 0 } if {$::android == 1} { return $::de1(volume) } global start_timer return [expr {[clock seconds] - $start_timer}] } proc steamtemp {} { if {$::android == 0} { set ::de1(steam_heater_temperature) [expr {(160+(rand() * 5))}] } return $::de1(steam_heater_temperature) } proc watertemp {} { if {$::android == 0} { #set ::de1(head_temperature) [expr {$::settings(espresso_temperature) - 2.0 + (rand() * 4)}] set ::de1(goal_temperature) $::settings(espresso_temperature) if {[ifexists ::de1(head_temperature)] == ""} { set ::de1(head_temperature) $::de1(goal_temperature) } if {$::de1(head_temperature) < 80} { set ::de1(head_temperature) $::de1(goal_temperature) } if {$::de1(head_temperature) > 95} { set ::de1(head_temperature) $::de1(goal_temperature) } #set ::de1(head_temperature) [expr {rand() + $::de1(head_temperature) - 0.5}] set ::de1(head_temperature) [expr {(.2 * (rand() - 0.6)) + $::de1(head_temperature)}] #set ::de1(head_temperature) 90 } return $::de1(head_temperature) } proc pressure {} { if {$::de1(substate) != $::de1_substate_types_reversed(pouring) && $::de1(substate) != $::de1_substate_types_reversed(preinfusion)} { return 0 } if {$::android == 0} { if {$::de1(state) == 4} { #espresso if {[ifexists ::de1(pressure)] == ""} { set ::de1(pressure) 5 } if {$::de1(pressure) > 10} { set ::de1(pressure) 9 } if {$::de1(pressure) < 1} { set ::de1(pressure) 5 } set ::de1(pressure) [expr {(.5 * (rand() - 0.5)) + $::de1(pressure)}] } elseif {$::de1(state) == 5} { #steam if {[ifexists ::de1(pressure)] == ""} { set ::de1(pressure) 2 } if {$::de1(pressure) > 3} { set ::de1(pressure) 2 } if {$::de1(pressure) < .2} { set ::de1(pressure) 2 } set ::de1(pressure) [expr {(.2 * (rand() - 0.5)) + $::de1(pressure)}] } } return $::de1(pressure) #if {$::android == 1} { #} #return [expr {(rand() * 3.5)}] } proc accelerometer_angle {} { if {$::android == 0} { set ::settings(accelerometer_angle) [expr {(rand() + $::settings(accelerometer_angle)) - 0.5}] } return [round_to_one_digits [expr {abs($::settings(accelerometer_angle))}]] } set since_last_acc [clock milliseconds] set last_acc_count 0 proc accelerometer_angle_text {} { global accelerometer_read_count global since_last_acc global last_acc_count set rate 0 set delta 0 catch { set delta [expr {$accelerometer_read_count - $last_acc_count}] set rate [expr {1000* ([clock milliseconds] - $since_last_acc}] } set since_last_acc [clock milliseconds] set last_acc_count $accelerometer_read_count return "$::settings(accelerometer_angle)\u00B0 ($accelerometer_read_count) $rate events/second $delta events $rate" } proc group_head_heater_temperature {} { if {$::android == 0} { # slowly have the water level drift set ::de1(water_level) [expr {$::de1(water_level) + (.1*(rand() - 0.5))}] } return $::de1(head_temperature) } proc steam_heater_temperature {} { if {$::android == 0} { set ::de1(mix_temperature) [expr {140 + (rand() * 20.0)}] } return $::de1(steam_heater_temperature) } proc water_mix_temperature {} { if {$::android == 0} { if {$::de1(substate) == $::de1_substate_types_reversed(pouring) || $::de1(substate) == $::de1_substate_types_reversed(preinfusion)} { if {$::de1(mix_temperature) == "" || $::de1(mix_temperature) < 85 || $::de1(mix_temperature) > 99} { set ::de1(mix_temperature) 94 } set ::de1(mix_temperature) [expr {$::de1(head_temperature) + ((rand() - 0.5) * 2) }] } #return [return_flow_weight_measurement [expr {(rand() * 6)}]] } return $::de1(mix_temperature) } ################# # formatting DE1 numbers into pretty text proc steam_heater_action_text {} { set delta [expr {int([steam_heater_temperature] - [setting_steam_temperature])}] if {$delta < -2} { return [translate "(Heating):"] } elseif {$delta > 2} { return [translate "(Cooling):"] } else { return [translate "Ready:"] } } proc group_head_heater_action_text {} { set delta [expr {int([group_head_heater_temperature] - [setting_espresso_temperature])}] if {$delta < -5} { return [translate "Heating:"] } elseif {$delta > 5} { return [translate "Cooling:"] } else { return [translate "Ready:"] } } proc group_head_heating_text {} { set delta [expr {int([group_head_heater_temperature] - [setting_espresso_temperature])}] if {$delta < -5} { return [translate "(heating)"] } } proc return_seconds_divided_by_ten {in} { if {$in == ""} {return ""} set t [expr {$in / 10.0}] return "[round_to_one_digits $t] [translate "seconds"]" } proc timer_text {} { return [subst {[timer] [translate "seconds"]}] } proc return_liquid_measurement {in} { if {$::de1(language_rtl) == 1} { return [subst {[translate "mL"] [round_to_integer $in] }] } if {$::settings(enable_fluid_ounces) != 1} { return [subst {[round_to_integer $in] [translate "mL"]}] } else { return [subst {[round_to_integer [ml_to_oz $in]] oz}] } } proc return_flow_calibration_measurement {in} { if {$::de1(language_rtl) == 1} { return [subst {[translate "mL/s"] [round_to_one_digits [expr {0.1 * $in}]]}] } return [subst {[round_to_one_digits [expr {0.1 * $in}]] [translate "mL/s"]}] } proc return_flow_measurement {in} { if {$::de1(language_rtl) == 1} { return [subst {[translate "mL/s"] [round_to_one_digits $in]}] } if {$::settings(enable_fluid_ounces) != 1} { return [subst {[round_to_one_digits $in] [translate "mL/s"]}] } else { return [subst {[round_to_one_digits [ml_to_oz $in]] oz/s}] } } proc return_pressure_measurement {in} { if {$::de1(language_rtl) == 1} { return [subst {[translate "bar"] [commify [round_to_one_digits $in]]}] } return [subst {[commify [round_to_one_digits $in]] [translate "bar"]}] } proc return_flow_weight_measurement {in} { if {$::de1(language_rtl) == 1} { return [subst {[translate "g/s"] [round_to_one_digits $in]}] } if {$::settings(enable_fluid_ounces) != 1} { return [subst {[round_to_one_digits $in] [translate "g/s"]}] } else { return [subst {[round_to_one_digits [ml_to_oz $in]] oz/s}] } } proc return_weight_measurement {in} { if {$::de1(language_rtl) == 1} { return [subst {[translate "g"][round_to_one_digits $in]}] } if {$::settings(enable_fluid_ounces) != 1} { return [subst {[round_to_one_digits $in][translate "g"]}] } else { return [subst {[round_to_one_digits [ml_to_oz $in]] oz}] } } proc return_percent {in} { return [subst {[round_to_one_digits $in]%}] } proc return_percent_off_if_zero {in} { if {$in == 0} { return [translate "off"] } return [subst {[round_to_one_digits $in]%}] } proc return_off_if_zero {in} { if {$in == 0} { return [translate "off"] } return $in } proc return_zero_if_blank {in} { if {$in == ""} { return 0 } return $in } proc return_stop_at_volume_measurement {in} { if {$in == 0} { return [translate "off"] } else { return [return_liquid_measurement [round_to_integer $in]] } } proc return_off_or_temperature {in} { if {$in == 0} { return [translate "off"] } else { return [return_temperature_measurement [round_to_integer $in]] } } proc return_stop_at_weight_measurement {in} { if {$in == 0 || $in == ""} { return [translate "off"] } else { if {$::de1(language_rtl) == 1} { return [subst {[translate "g"][round_to_one_digits $in]}] } if {$::settings(enable_fluid_ounces) != 1} { return [subst {[round_to_one_digits $in][translate "g"]}] } else { return [subst {[round_to_one_digits [ml_to_oz $in]] oz}] } } } proc return_stop_at_weight_measurement_precise {in} { if {$in == 0} { return [translate "off"] } else { if {$::de1(language_rtl) == 1} { return [subst {[translate "g"][round_to_one_digits $in]}] } if {$::settings(enable_fluid_ounces) != 1} { return [subst {[round_to_one_digits $in][translate "g"]}] } else { return [subst {[round_to_half_integer [ml_to_oz $in]] oz}] } } } proc return_shot_weight_measurement {in} { if {$in == 0} { return [translate "off"] } else { if {$::de1(language_rtl) == 1} { return [subst {[translate "g"][round_to_one_digits $in]}] } if {$::settings(enable_fluid_ounces) != 1} { return [subst {[round_to_one_digits $in][translate "g"]}] } else { return [subst {[round_to_one_digits [ml_to_oz $in]] oz}] } } } proc preinfusion_pour_timer_text {} { if {$::de1(language_rtl) == 1} { return [subst {[translate "s"][espresso_preinfusion_timer] [translate "preinfusion"] }] } return [subst {[espresso_preinfusion_timer][translate "s"] [translate "preinfusion"]}] } proc total_pour_timer_text {} { if {$::de1(language_rtl) == 1} { return "[translate {s}][espresso_elapsed_timer] [translate {total}] " } return "[espresso_elapsed_timer][translate {s}] [translate {total}]" } proc espresso_done_timer_text {} { if {[espresso_done_timer] < $::settings(seconds_to_display_done_espresso)} { if {$::de1(language_rtl) == 1} { return "[translate s][espresso_done_timer] [translate done]" } return "[espresso_done_timer][translate s] [translate done]" } else { return "" } } proc pouring_timer_text {} { if {$::de1(language_rtl) == 1} { if {$::settings(final_desired_shot_volume_advanced) > 0 && $::settings(settings_profile_type) == "settings_2c"} { return "[return_liquid_measurement [round_to_integer $::settings(final_desired_shot_volume_advanced)]] < [translate {pouring}] [translate {s}][espresso_elapsed_timer]" } if {$::settings(scale_bluetooth_address) == "" && $::settings(final_desired_shot_volume) > 0 && ($::settings(settings_profile_type) == "settings_2a" || $::settings(settings_profile_type) == "settings_2b")} { return "[translate {s}][espresso_pour_timer] [translate {pouring}] < [return_liquid_measurement [round_to_integer $::settings(final_desired_shot_volume)]]" } else { return "[translate {s}][espresso_pour_timer] [translate {pouring}]" } } if {$::settings(final_desired_shot_volume_advanced) > 0 && $::settings(settings_profile_type) == "settings_2c"} { return "[espresso_elapsed_timer][translate {s}] [translate {pouring}] < [return_liquid_measurement [round_to_integer $::settings(final_desired_shot_volume_advanced)]]" } if {$::settings(scale_bluetooth_address) == "" && $::settings(final_desired_shot_volume) > 0 && ($::settings(settings_profile_type) == "settings_2a" || $::settings(settings_profile_type) == "settings_2b")} { return "[espresso_pour_timer][translate {s}] [translate {pouring}] < [return_liquid_measurement [round_to_integer $::settings(final_desired_shot_volume)]]" } return "[espresso_pour_timer][translate {s}] [translate {pouring}]" } proc pouring_timer_text_2 {} { OBSOLETE if {$::de1(language_rtl) == 1} { if {$::settings(scale_bluetooth_address) == "" && $::settings(final_desired_shot_volume) > 0 && ($::settings(settings_profile_type) == "settings_2a" || $::settings(settings_profile_type) == "settings_2b")} { return "[translate {s}][espresso_pour_timer] [translate {pouring}] < [return_liquid_measurement [round_to_integer $::settings(final_desired_shot_volume)]]" } else { return "[translate {pouring}] [translate {s}][espresso_pour_timer]" } } if {$::settings(scale_bluetooth_address) == "" && $::settings(final_desired_shot_volume) > 0 && ($::settings(settings_profile_type) == "settings_2a" || $::settings(settings_profile_type) == "settings_2b")} { return "[espresso_pour_timer][translate {s}] [translate {pouring}] < [return_liquid_measurement [round_to_integer $::settings(final_desired_shot_volume)]]" } else { return "[espresso_pour_timer][translate {s}] [translate {pouring}]" } } proc preinfusion_volume {} { return_liquid_measurement [round_to_integer $::de1(preinfusion_volume)] } proc pour_volume {} { return_liquid_measurement [round_to_integer $::de1(pour_volume)] } proc waterflow_text {} { return [return_flow_measurement [waterflow]] } proc watervolume_text {} { if {$::android == 0} { if {$::de1(substate) == $::de1_substate_types_reversed(pouring) || $::de1(substate) == $::de1_substate_types_reversed(preinfusion)} { if {$::de1(volume) == ""} { set ::de1(volume) 0 } set ::de1(volume) [expr {$::de1(volume) + (rand() * .27) }] } } # we sum these two numbers so that we don't have a rounding error that a user might find offensive (ie, "preinfusion + pour = total" ALWAYS) return [return_liquid_measurement [expr {[round_to_integer $::de1(preinfusion_volume)] + [round_to_integer $::de1(pour_volume)]}]] } proc waterweightflow_text {} { if {$::android == 0} { if {$::de1(substate) == $::de1_substate_types_reversed(pouring) || $::de1(substate) == $::de1_substate_types_reversed(preinfusion)} { if {[espresso_millitimer] > 5000} { # no weight increase for 5s due to preinfusion if {$::de1(scale_weight_rate) == ""} { set ::de1(scale_weight_rate) 3 } set ::de1(scale_weight_rate) [expr {$::de1(scale_weight_rate) + ((rand() - 0.5) * .3) }] if {$::de1(scale_weight_rate) < 0} { set ::de1(scale_weight_rate) 1 } set ::de1(scale_weight_rate_raw) [expr {$::de1(scale_weight_rate) + ((rand() - 0.5) * 1) }] } } #return [return_flow_weight_measurement [expr {(rand() * 6)}]] } if {$::de1(scale_weight) == "" || [ifexists ::settings(scale_bluetooth_address)] == ""} { return "" } return [return_flow_weight_measurement $::de1(scale_weight_rate)] } proc finalwaterweight_text {} { if {$::de1(scale_weight) == "" || [ifexists ::settings(scale_bluetooth_address)] == ""} { return "" } if {[ifexists ::blink_water_weight] == 1} { return "" } return [return_weight_measurement $::de1(final_water_weight)] } # drink_weight is present for both espresso and hot water proc drink_weight_text {} { if {$::de1(scale_weight) == "" || [ifexists ::settings(scale_bluetooth_address)] == ""} { return "" } if {[ifexists ::blink_water_weight] == 1} { return "" } return [return_weight_measurement $::settings(running_weight)] } proc dump_stack {args} { msg -INFO [stacktrace] } #trace add variable de1(final_water_weight) write dump_stack proc waterweight_text {} { if {$::de1(scale_weight) == "" || [ifexists ::settings(scale_bluetooth_address)] == ""} { return "" } if {$::android == 0} { if {[espresso_millitimer] < 5000} { # no weight increase for 5s due to preinfusion set ::de1(scale_weight) 0 } else { if {$::de1(substate) == $::de1_substate_types_reversed(pouring) || $::de1(substate) == $::de1_substate_types_reversed(preinfusion)} { if {$::de1(scale_weight) == ""} { set ::de1(scale_weight) 3 } set ::de1(scale_weight) [expr {$::de1(scale_weight) + (rand() * .3) }] set ::de1(final_water_weight) $::de1(scale_weight) } else { set ::de1(scale_weight) 0 } } #return [return_weight_measurement [expr {round((rand() * 20))}]] } if {$::de1(scale_device_handle) == "0"} { return [translate "Disconnected"] } return [return_weight_measurement $::de1(scale_sensor_weight)] #return [return_weight_measurement $::de1(scale_weight)] } proc waterweight_label_text {} { if {[ifexists ::settings(scale_bluetooth_address)] == ""} { return "" } if {$::de1(scale_device_handle) == "0"} { if {[ifexists ::blink_water_weight] != 1} { if {$::currently_connecting_scale_handle == 0} { set ::blink_water_weight 1 return {} } else { return [translate "Wait"] } } else { set ::blink_water_weight 0 return [translate "Weight"] } } return [translate "Weight"] } proc espresso_goal_temp_text {} { return [return_temperature_measurement $::de1(goal_temperature)] } proc espresso_goal_temp_text_rtl_aware {} { return [subst {[return_temperature_measurement $::de1(goal_temperature)] [translate "goal"]}] } set ::diff_brew_temp_from_goal 0 set ::positive_diff_brew_temp_from_goal 0 proc diff_brew_temp_from_goal {} { set ::diff_brew_temp_from_goal [expr {[water_mix_temperature] - $::de1(goal_temperature)}] set ::positive_diff_brew_temp_from_goal [expr {abs($::diff_brew_temp_from_goal)}] return $::diff_brew_temp_from_goal } proc diff_brew_temp_from_goal_text {} { set diff [expr {[water_mix_temperature] - $::de1(goal_temperature)}] return [return_delta_temperature_measurement $diff] } proc diff_espresso_temp_from_goal {} { set diff [expr {[watertemp] - $::de1(goal_temperature)}] return $diff } proc diff_espresso_temp_from_goal_text {} { set diff [expr {[watertemp] - $::de1(goal_temperature)}] return [return_delta_temperature_measurement $diff] } proc diff_group_temp_from_goal {} { set diff [expr {[group_head_heater_temperature] - $::de1(goal_temperature)}] return $diff } proc diff_group_temp_from_goal_text {} { set diff [expr {[group_head_heater_temperature] - $::de1(goal_temperature)}] return [return_delta_temperature_measurement $diff] } proc diff_pressure {} { if {$::android == 0} { return [expr {3 - (rand() * 6)}] } return $::gui::state::_delta_pressure } proc diff_flow_rate {} { if {$::android == 0} { return [expr {3 - (rand() * 6)}] } return $::gui::state::_delta_flow } proc diff_flow_rate_text {} { return [return_flow_measurement [round_to_one_digits [diff_flow_rate]]] } proc mixtemp_text {} { return [return_temperature_measurement [water_mix_temperature]] } proc watertemp_text {} { return [return_temperature_measurement [watertemp]] } proc steamtemp_text {} { return [return_temperature_measurement [steamtemp]] } proc pressure_text {} { if {$::de1(language_rtl) == 1} { return [subst {[translate "bar"] [commify [round_to_one_digits [pressure]]]}] } return [subst {[commify [round_to_one_digits [pressure]]] [translate "bar"]}] } proc commify {number} { while {[regsub {^([-+]?\d+)(\d\d\d)} $number {\1,\2} number]} {} if {[ifexists ::settings(enable_commanumbers)] == 1} { set number [string map {. , , .} $number] } return $number } ####################### # settings proc setting_steam_max_time {} { return [expr {round( $::settings(steam_max_time) )}] } proc setting_water_max_time {} { return [expr {round( $::settings(water_time_max) )}] } proc setting_espresso_max_time {} { return [expr {round( $::settings(espresso_max_time) )}] } proc setting_steam_max_time_text {} { return [subst {[setting_steam_max_time] [translate "seconds"]}] } proc setting_water_max_time_text {} { return [subst {[setting_water_max_time] [translate "seconds"]}] } proc setting_espresso_max_time_text {} { return [subst {[setting_espresso_max_time] [translate "seconds"]}] } proc setting_steam_temperature {} { return $::settings(steam_temperature) } proc setting_espresso_temperature {} { return $::settings(espresso_temperature) } proc setting_water_temperature {} { return $::settings(water_temperature) } proc return_html_temperature_units {} { if {$::settings(enable_fahrenheit) == 1} { return "[encoding convertfrom utf-8 °]F" } else { return "[encoding convertfrom utf-8 °]C" } } proc return_temp_offset {in} { if {$::settings(enable_fahrenheit) == 1} { return [expr {$in * 1.8}] } else { return $in } } proc return_temperature_number {in} { if {$::settings(enable_fahrenheit) == 1} { return [round_to_two_digits [celsius_to_fahrenheit $in]] } else { return [round_to_two_digits $in] } } # john 25-1-2020 fix # we were using the wrong unicode symbol for the degrees sign (should be \u00B0 not \u00BA). # http://www.fileformat.info/info/unicode/char/b0/index.htm # http://www.fileformat.info/info/unicode/char/ba/index.htm proc return_temperature_measurement {in} { if {$::settings(enable_fahrenheit) == 1} { return [subst {[round_to_integer [celsius_to_fahrenheit $in]]\u00B0F}] } else { return [subst {[round_to_one_digits $in]\u00B0C}] } } proc round_and_return_temperature_setting {varname} { upvar $varname in set out [round_temperature_number $in] if {$in != $out} { set $varname $out } return_temperature_setting $in } proc round_temperature_number {in} { return [round_to_half_integer $in] } proc return_temperature_setting_or_off {in} { if {$in == 0} { return [translate "off"] } else { return [return_temperature_setting $in] } } proc return_temperature_setting {in} { if {$::settings(enable_fahrenheit) == 1} { return [subst {[round_to_integer [celsius_to_fahrenheit $in]]\u00B0F}] } else { if {[round_to_half_integer $in] == [round_to_integer $in]} { # don't display a .0 on the number if it's not needed return [subst {[round_to_integer $in]\u00B0C}] } else { return [subst {[round_to_half_integer $in]\u00B0C}] } } } proc return_plus_or_minus_number {in} { if {$in > 0.0} { return "+$in" } return $in } proc return_delta_temperature_measurement {in} { if {$::settings(enable_fahrenheit) == 1} { set label "\u00B0F" #set num [celsius_to_fahrenheit $in] # set num $in } else { set label "\u00B0C" # set num $in } # handle ºC vs ºF deltas set in [return_temp_offset $in] set num [round_to_one_digits $in] set t {} if {$num > 0.0} { set t "+$t" } set s "$t$num$label" return $s } proc setting_steam_temperature_text {} { return [return_temperature_measurement [setting_steam_temperature]] } proc setting_water_temperature_text {} { return [return_temperature_measurement [setting_water_temperature]] } proc steam_heater_temperature_text {} { return [return_temperature_measurement [steam_heater_temperature]] } proc group_head_heater_temperature_text {} { return [return_temperature_measurement [group_head_heater_temperature]] } proc group_head_heater_temperature_text_rtl_aware {} { return [subst {[return_temperature_measurement [group_head_heater_temperature]] [translate "metal"]}] } proc coffee_temp_text_rtl_aware {} { return "[watertemp_text] [translate {coffee}]" } proc setting_espresso_temperature_text {} { return [return_temperature_measurement [setting_espresso_temperature]] } proc setting_espresso_pressure {} { return $::settings(espresso_pressure) } proc setting_espresso_pressure_text {} { return [subst {[commify [round_to_one_digits [setting_espresso_pressure]]] [translate "bar"]}] } proc setting_espresso_stop_pressure_text {} { if {$::settings(preinfusion_stop_pressure) == 0} { return "" } return [subst {[commify [round_to_one_digits $::settings(preinfusion_stop_pressure)]] [translate "bar"]}] } proc setting_espresso_stop_flow_text {} { if {$::settings(preinfusion_stop_flow_rate) == 0} { return "" } return [subst {[return_flow_measurement $::settings(preinfusion_stop_flow_rate)]}] } proc graph_seconds_axis_format {nm val} { if {$val == 0} { return [translate "start"] } return "$val [translate {seconds}]" } ####################### # conversion functions proc round_to_tens {in} { set x 0 catch { set x [expr {round($in / 10.0)*10}] } return $x } proc round_to_two_digits {in} { set x 0 catch { set x [format "%.2f" $in] } return $x # obsolete below set x 0 catch { set x [expr {round($in * 100.0)/100.0}] } return $x } proc round_to_one_digits {in} { set x 0 catch { set x [expr {round($in * 10.0)/10.0}] } return $x } proc round_to_integer {in} { set x 0 catch { set x [expr {round($in)}] } return $x } proc celsius_to_fahrenheit {in} { set x 0 catch { set x [expr {32 + ($in * 1.8)}] } return $x } proc ml_to_oz {in} { set x 0 catch { set x [expr {$in * 0.033814}] } return $x } proc backup_settings {} { unset -nocomplain ::settings_backup; array set ::settings_backup [array get ::settings] backup_espresso_chart #update_de1_explanation_chart } proc refresh_skin_directories {} { unset -nocomplain ::skin_directories_cache } proc skin_directories {} { if {[info exists ::skin_directories_cache] == 1} { return $::skin_directories_cache } set dirs [lsort -dictionary [glob -nocomplain -tails -directory "[homedir]/skins/" *]] set dd {} # overriding settings to include Insight Dark now set ::settings(most_popular_skins) [list Insight "Insight Dark" MimojaCafe Metric DSx SWDark4 MiniMetric] foreach d $dirs { if {$d == "CVS" || $d == "example"} { continue } if {[ifexists ::settings(show_only_most_popular_skins)] == 1 && [ifexists ::settings(most_popular_skins)] != ""} { #puts "'$d' '[ifexists ::settings(most_popular_skins)]'" if {[lsearch -exact [string toupper [ifexists ::settings(most_popular_skins)]] [string toupper $d] ] == -1} { continue } } set fn "[homedir]/skins/$d/skin.tcl" set skintcl [read_file $fn] #set skintcl "" if {[string first "package require de1plus" $skintcl] != -1} { # keep track of which skins are DE1PLUS so we can display them differently in the listbox set ::de1plus_skins($d) 1 } lappend dd $d } set ::skin_directories_cache [lsort -dictionary -increasing $dd] return $::skin_directories_cache } proc fill_history_listbox {} { set widget $::globals(history_listbox) $widget delete 0 99999 set cnt 0 set current_skin_number 0 foreach d [history_directories] { $widget insert $cnt [clock format $d] incr cnt } #$widget selection set $current_skin_number bind $widget <<ListboxSelect>> [list ::preview_history %W] preview_history } proc fill_skin_listbox {} { set widget $::globals(tablet_styles_listbox) $widget delete 0 99999 set cnt 0 set ::current_skin_number 0 foreach d [skin_directories] { if {$d == "CVS" || $d == "example"} { continue } $widget insert $cnt [translate $d] if {$::settings(skin) == $d} { set ::current_skin_number $cnt } if {[ifexists ::de1plus_skins($d)] == 1} { # mark skins that require the DE1PLUS model with a different color to highlight them $widget itemconfigure $cnt -background #F0F0FF } incr cnt } #$widget itemconfigure $current_skin_number -foreground blue $widget selection set $::current_skin_number make_current_listbox_item_blue $widget preview_tablet_skin $widget yview $::current_skin_number } proc make_current_listbox_item_blue { widget} { if {[$widget index end] == 0} { # empty listbox return } set found_one 0 for {set x 0} {$x < [$widget index end]} {incr x} { if {$x == [$widget curselection]} { #if {$x < [$widget index end]} { $widget itemconfigure $x -foreground #000000 -selectforeground #000000 -background #c0c4e1 set found_one 1 #} } else { $widget itemconfigure $x -foreground #b2bad0 -background #fbfaff } } if {$found_one != 1} { # handle the case where nothing has been selected $widget selection set 0 $widget itemconfigure 0 -foreground #000000 -selectforeground #000000 -background #c0c4e1 } } proc history_directories {} { set dirs [lsort -dictionary [glob -nocomplain -tails -directory "[homedir]/history/" *.shot]] set dd {} foreach d $dirs { lappend dd [file rootname $d] } return [lsort -dictionary -increasing $dd] } proc profile_directories {} { set show_hidden 0 if {[ifexists ::profiles_hide_mode] == 1} { set show_hidden 1 } set dirs [lsort -dictionary [glob -nocomplain -tails -directory "[homedir]/profiles/" *.tcl]] set dd {} foreach d $dirs { #if {$d == "CVS" || $d == "example"} { # continue #} set dflow_test [string tolower [string range $d 0 5]] if {$dflow_test == "d-flow"} { if {[plugin_enabled "D_Flow_Espresso_Profile"] != true} { continue } } set filecontents [encoding convertfrom utf-8 [read_binary_file "[homedir]/profiles/$d"]] if {[string first "settings_profile_type settings_2b" $filecontents] != -1 || [string first "settings_profile_type settings_2c" $filecontents] != -1 || [string first "settings_profile_type settings_profile_flow" $filecontents] != -1 || [string first "settings_profile_type settings_profile_advanced" $filecontents] != -1} { # keep track of which skins are DE1PLUS so we can display them differently in the listbox set ::de1plus_profile([file rootname $d]) 1 } unset -nocomplain profile catch { array set profile $filecontents } if {[info exists profile(profile_title)] != 1} { msg -WARNING "Corrupt profile file in profile_directories: '$d'" #continue } if {[ifexists profile(profile_hide)] == 1} { if {$show_hidden != 1} { continue } } set rootname [file rootname $d] if {$rootname == "CVS" || $rootname == "example"} { continue } lappend dd $rootname } return [lsort -dictionary -increasing $dd] } proc delete_selected_profile {} { set w $::globals(profiles_listbox) if {[$w curselection] == ""} { msg -NOTICE "No profile has yet been tapped by the user to delete, so doing nothing" return } set profile $::profile_number_to_directory([$w curselection]) set fn "[homedir]/profiles/${profile}.tcl" msg -NOTICE "About to delete profile: '$fn'" if {$profile == "default"} { msg -NOTICE "cannot delete default profile" return } #return file delete $fn set ::settings(profile) "default" fill_profiles_listbox #preview_profile } # the checkbox character is not available in all fonts, so we use an X instead then proc checkboxchar {} { if {[language] == "ar" || [language] == "he"} { return "X" } return "\u2713" } proc bluetooth_character {} { if {[language] == "ar" || [language] == "he"} { return "BLE:" } return "\uE018" } proc thermometer_character {} { if {[language] == "ar" || [language] == "he"} { return "T:" } return "\uF2C9" } proc scale_character {} { if {[language] == "ar" || [language] == "he"} { return "SCALE:" } return "\uF515" } proc usb_character {} { if {[language] == "ar" || [language] == "he"} { return "USB:" } return "\uE01A" } proc wifi_character {} { if {[language] == "ar" || [language] == "he"} { return "WIFI:" } return "\uE019" } #set de1_device_list {} proc fill_ble_listbox {} { set widget $::ble_listbox_widget $widget delete 0 99999 set cnt 0 set current_ble_number 0 set one_selected 0 foreach d [lsort -dictionary -increasing $::de1_device_list] { set addr_raw [dict get $d address] set name [dict get $d name] set type [dict get $d type] set display_addr $addr_raw if { $type == "ble" } { set icon [bluetooth_character] set display_addr [string range $addr_raw 9 13] } elseif { $type == "usb" } { set icon [usb_character] } elseif { $type == "wifi" } { set icon [wifi_character] } else { set icon "?${type}?" } if {$addr_raw == [ifexists ::settings(bluetooth_address)]} { $widget insert $cnt " \[[checkboxchar]\] $icon $name ($display_addr)" set one_selected 1 } else { $widget insert $cnt " \[ \] $icon $name ($display_addr)" } if {[ifexists ::settings(bluetooth_address)] == $addr_raw} { set current_ble_number $cnt } incr cnt } set ::de1_needs_to_be_selected 0 if {[llength $::de1_device_list] > 0 && $one_selected == 0} { set ::de1_needs_to_be_selected 1 } # john - probably makes sense for "pair" to occur on item tap make_current_listbox_item_blue $widget } proc remove_peripheral {address} { set newdict {} foreach d $::peripheral_device_list { if {[dict get $d address] != $address} { lappend newdict $d } } set ::peripheral_device_list $newdict } proc fill_peripheral_listbox {} { set widget $::ble_scale_listbox_widget $widget delete 0 99999 set cnt 0 set current_ble_number 0 # count peripherals with the same name, so we can differentiate them in the listbox if needed foreach d $::peripheral_device_list { set name [dict get $d name] if {[info exists peripherals_seen($name)] == 1} { incr peripherals_seen($name) } else { set peripherals_seen($name) 1 } } set one_selected 0 foreach d $::peripheral_device_list { set addr [dict get $d address] set name [dict get $d name] set connectiontype [dict get $d connectiontype] set devicetype [dict get $d devicetype] set family [dict get $d devicefamily] set icon "UNKN:" if {$devicetype eq "thermometer"} { set icon [thermometer_character] } elseif {$devicetype eq "scale"} { set icon [scale_character] } elseif {$connectiontype eq "ble"} { set icon [bluetooth_character] } if { $name eq "" } { set name $family } if {$peripherals_seen($name) > 1} { # this peripheral appears twice in a ble scan, so give the last two digits of the ble address to differentiate it set name "[string range $addr end-1 end]-$name" } if {$addr == [ifexists ::settings(scale_bluetooth_address)]} { $widget insert $cnt " \[[checkboxchar]\] $icon $name" set one_selected 1 } else { $widget insert $cnt " \[ \] $icon $name" } if {[ifexists ::settings(scale_bluetooth_address)] == $addr} { set current_ble_number $cnt } incr cnt } $widget selection set $current_ble_number; set ::peripheral_needs_to_be_selected 0 if {[llength $::de1_device_list] > 0 && $one_selected == 0} { set ::peripheral_needs_to_be_selected 1 } make_current_listbox_item_blue $widget } proc profile_type_text {} { set in $::settings(settings_profile_type) if {$in == "settings_2a"} { return [translate "Pressure profile"] } elseif {$in == "settings_2b"} { return [translate "Flow profile"] } elseif {$in == "settings_2c"} { return [translate "Advanced profile"] } else { return [translate "Profile"] } } proc array_keys_sorted_by_val {arrname {sort_order -increasing}} { upvar $arrname arr foreach k [array names arr] { set k2 "$arr($k) $k" #set k2 "[format {"%0.12i"} $arr($k)] $k" set t($k2) $k } set toreturn {} set keys [lsort $sort_order -dictionary [array names t]] foreach k $keys { set v $t($k) lappend toreturn $v } return $toreturn } proc fill_specific_profiles_listbox { widget selected_profile_name hide_mode} { $widget delete 0 99999 set selected_profile_number 0 set cnt 0 set grouping "" unset -nocomplain ::profile_number_to_directory foreach d [profile_directories] { unset -nocomplain profile catch { set fn "[homedir]/profiles/$d.tcl" array set profile [encoding convertfrom utf-8 [read_binary_file $fn]] } set profile_to_title($d) [translate [ifexists profile(profile_title)]] } set profiles [array_keys_sorted_by_val profile_to_title] foreach d $profiles { set fn "[homedir]/profiles/${d}.tcl" unset -nocomplain profile catch { array set profile [encoding convertfrom utf-8 [read_binary_file $fn]] } if {[info exists profile(profile_title)] != 1} { msg -WARNING "Corrupt profile file in choices: '$d'" #continue set profile(profile_title) "$d \u2639 \u2639 \u2639" } # experimental feature to also load god shots with profiles # if {$profile(profile_title) == "Default" || $profile(profile_title) == "Gentle and sweet"} { # set profile(profile_title) "$d \u2665" # } set ptitle $profile(profile_title) set pcnt [ifexists ::profile_shot_count($d)] if {[language] != "en" && [ifexists profile(profile_language)] == "en" && [ifexists profile(author)] == "Decent"} { set p [translate $ptitle] } else { set p $ptitle } if {$hide_mode != 1} { if {[ifexists profile(profile_hide)] == 1} { # hide this profile if it's marked to be hidden, unless we're un the profile_hide edit mode, in which case we show all profiles continue } if {$pcnt != ""} { # mark the most frequently used profiles with a special symbol, to attract the eye to them set p "$p \u25C0" } set parts [split $p /] if {[llength $parts] > 1} { set this_group [lindex $parts 0] if {$this_group != $grouping} { set grouping $this_group $widget insert $cnt $this_group set ::profile_number_to_directory($cnt) $d incr cnt } set p " - [lindex $parts 1]" } else { } } else { # if editing what profiles to show, then use a check or empty box, to indicate which profiles will be shown if {[ifexists profile(profile_hide)] == 1} { if {[language] == "he"} { set p "\[ \] $p" } else { set p "\u2610 $p" } } else { if {[language] == "he"} { set p "\[X\] $p" } else { set p "\u2612 $p" } } } $widget insert $cnt $p set ::profile_number_to_directory($cnt) $d if {[string tolower $selected_profile_name] == [string tolower [ifexists profile(profile_title)]]} { set selected_profile_number $cnt } elseif {[language] != "en"} { if {[string tolower $selected_profile_name] == [string tolower [translate [ifexists profile(profile_title)]]]} { set selected_profile_number $cnt } } incr cnt } return $selected_profile_number } proc fill_profiles_listbox {} { # use this variable to prevent triggering preview_profile for each profile as it gets added to the listbox. Tk would otherwise do this for each profile as its added to the listbox set ::filling_profiles 1 set widget $::globals(profiles_listbox) set ::settings(profile_to_save) $::settings(profile) set ::current_profile_number [fill_specific_profiles_listbox $widget $::settings(profile) [ifexists ::profiles_hide_mode]] $widget selection set $::current_profile_number; #set ::globals(profiles_listbox) $widget make_current_listbox_item_blue $widget preview_profile $widget yview $::current_profile_number unset -nocomplain ::filling_profiles } proc fill_languages_listbox {} { set widget $::languages_widget $widget delete 0 99999 set cnt 0 set current_profile_number 0 # on android we can automatically detect the language from the OS setting, and this is the preferred way to go $widget insert $cnt [translate Automatic] incr cnt set current 0 foreach {code desc} [translation_langs_array] { if {$::settings(language) == $code} { set current $cnt } $widget insert $cnt "$desc" incr cnt } $widget selection set $current; make_current_listbox_item_blue $::languages_widget $::languages_widget yview $current } proc highlight_extension {} { set stepnum [$::extensions_widget curselection] if {$stepnum == ""} { set ::extension_highlighted -1 return } if { [info exists ::extension_highlighted] } { if { $::extension_highlighted == $stepnum } { set plugin [lindex [available_plugins] $stepnum] plugins toggle $plugin fill_extensions_listbox $::extensions_widget selection set $stepnum make_current_listbox_item_blue $::extensions_widget } else { set ::extension_highlighted $stepnum } } else { set ::extension_highlighted $stepnum } set plugin [lindex [available_plugins] $stepnum ] if {[info exists ::plugins::${plugin}::ui_entry] && [set ::plugins::${plugin}::ui_entry] != ""} { canvas_show "$::extensions_settings $::extensions_settings_button" } else { canvas_hide "$::extensions_settings $::extensions_settings_button" msg -WARNING "Plugin" $plugin "Does not have a uientry" } set description "" foreach {name value} { "Version:" version "Author:" author "Contact:" contact "\n" description} { set conf [set ::plugins::${plugin}::${value}] if { $conf != {} } { append description "[translate $name] $conf\n" } } .can itemconfigure $::extensions_metadata -text $description fill_extensions_listbox $::extensions_widget selection set $stepnum; make_current_listbox_item_blue $::extensions_widget } proc fill_plugin_settings {} { set stepnum [$::extensions_widget curselection] if {$stepnum == ""} { borg toast [translate "No extension selected"] return } set plugin [lindex [available_plugins] $stepnum] if {[info exists ::plugins::${plugin}::ui_entry] && [set ::plugins::${plugin}::ui_entry] != ""} { set next_page [set ::plugins::${plugin}::ui_entry] page_to_show_when_off $next_page } } proc fill_extensions_listbox {} { set widget $::extensions_widget set stepnum [$::extensions_widget curselection] $widget delete 0 99999 set cnt 0 set current 0 foreach {plugin} [available_plugins] { if {[plugin_enabled $plugin]} { if {[language] == "he"} { set p "\[X\] " } else { set p "\u2612 " } } else { if {[language] == "he"} { set p "\[ \] " } else { set p "\u2610 " } } if { [info exists ::plugins::${plugin}::name ] && [subst \$::plugins::${plugin}::name] ne "" } { set plugin_name [subst \$::plugins::${plugin}::name] } else { set plugin_name $plugin } $widget insert $cnt "$p $plugin_name" incr cnt } $::extensions_widget yview $current if {$stepnum == ""} { canvas_hide "$::extensions_settings $::extensions_settings_button" } else { $::extensions_widget selection set $stepnum; make_current_listbox_item_blue $::extensions_widget } } proc fill_advanced_profile_steps_listbox {} { set widget $::advanced_shot_steps_widget set cs [ifexists ::current_step_number] $widget delete 0 99999 set cnt 0 set current_profile_number 0 foreach step $::settings(advanced_shot) { unset -nocomplain props array set props $step set name $props(name) $widget insert $cnt "[expr {1 + $cnt}]. $name" incr cnt } if {$cs == "" || $cs > [$::advanced_shot_steps_widget index end]} { $::advanced_shot_steps_widget selection set 0; set ::current_step_number 0 } else { $::advanced_shot_steps_widget selection set $cs; } load_advanced_profile_step 1 make_current_listbox_item_blue $::advanced_shot_steps_widget update idletasks } proc load_language {} { set stepnum [$::languages_widget curselection] if {$stepnum == ""} { return } if {$stepnum == 0} { set ::settings(language) "" } else { set ::settings(language) [lindex [translation_langs_array] [expr {($stepnum * 2) - 2}] ] } make_current_listbox_item_blue $::languages_widget } proc load_advanced_profile_step {{force 0}} { if {$::de1(current_context) != "settings_2c" && $force == 0} { msg -DEBUG "returning load_advanced_profile_step" return } set stepnum [$::advanced_shot_steps_widget curselection] if {$stepnum == ""} { #set stepnum return } set ::current_step_number $stepnum #set stepnum [current_adv_step] unset -nocomplain ::current_adv_step # max flow / max pressure are not always set, so we set it now array set ::current_adv_step {max_flow_or_pressure 0 max_flow_or_pressure_range 0.6} # Max weight is not always set, so we set it here array set ::current_adv_step {weight 0.0} array set ::current_adv_step [lindex $::settings(advanced_shot) $stepnum] make_current_listbox_item_blue $::advanced_shot_steps_widget set ::profile_step_name_to_add $::current_adv_step(name) set ::current_step_number $stepnum } proc current_adv_step {} { return $::current_step_number set stepnum [$::advanced_shot_steps_widget curselection] if {$stepnum == ""} { msg -DEBUG "current_adv_step: blank seleted" set stepnum 0 } msg -DEBUG "current_adv_step: stepnum: $stepnum" return $stepnum } proc change_current_adv_shot_step_name {} { set ::current_adv_step(name) "$::profile_step_name_to_add" save_current_adv_shot_step fill_advanced_profile_steps_listbox } proc save_current_adv_shot_step {} { set ::settings(advanced_shot) [lreplace $::settings(advanced_shot) [current_adv_step] [current_adv_step] [array get ::current_adv_step]] profile_has_changed_set # for display purposes, make the espresso temperature be equal to the temperature of the first step in the advanced shot array set first_step [lindex $::settings(advanced_shot) 0] set ::settings(espresso_temperature) [ifexists first_step(temperature)] } proc delete_current_adv_step {} { if {[$::advanced_shot_steps_widget index end] == 1} { # we don't allow deleting the only step, because that leads to weird UI issues. msg -NOTICE "not deleting step because there is only one advanced step at the moment" return } set ::settings(advanced_shot) [lreplace $::settings(advanced_shot) [current_adv_step] [current_adv_step]] msg -DEBUG "delete_current_adv_step: deleting" set ::current_step_number 0 $::advanced_shot_steps_widget selection set $::current_step_number; $::advanced_shot_steps_widget activate $::current_step_number; fill_advanced_profile_steps_listbox #make_current_listbox_item_blue $::advanced_shot_steps_widget } # inserts a new step immediately after the currently seleted one, with all the same settings except for a different name proc add_to_current_adv_step {} { # don't add more than the maximum number of steps that the espresso machine can handle if {[llength $::settings(advanced_shot)] >= 20} { return } set newlist {} set cnt 0 set stepnum [current_adv_step] set name $::profile_step_name_to_add if {$name == ""} { set name [translate "step"] } foreach s $::settings(advanced_shot) { lappend newlist $s if {$cnt == $stepnum} { array set x $s set x(name) $name lappend newlist [array get x] } incr cnt } if {$newlist == {}} { set newlist [list [list name $name]] } set ::settings(advanced_shot) $newlist fill_advanced_profile_steps_listbox #$::advanced_shot_steps_widget selection set $stepnum; $::advanced_shot_steps_widget selection clear $::current_step_number; incr ::current_step_number $::advanced_shot_steps_widget selection set $::current_step_number; $::advanced_shot_steps_widget activate $::current_step_number; # $::advanced_shot_steps_widget activate $stepnum; make_current_listbox_item_blue $::advanced_shot_steps_widget } proc save_new_tablet_skin_setting {} { set ::settings(skin) [$::globals(tablet_styles_listbox) get [$::globals(tablet_styles_listbox) curselection]] } proc preview_tablet_skin {} { if {$::de1(current_context) != "tabletstyles"} { return } msg -DEBUG "preview_tablet_skin (entry)" set w $::globals(tablet_styles_listbox) if {[$w curselection] == ""} { msg -DEBUG "preview_tablet_skin: no current skin selection" #set w #set skindir [$w get $::current_skin_number] #return msg -DEBUG "preview_tablet_skin ::current_skin_number: $::current_skin_number" $w selection set $::current_skin_number } set skindir [lindex [skin_directories] [$w curselection]] set ::settings(skin) $skindir set fn "[homedir]/skins/$skindir/${::screen_size_width}x${::screen_size_height}/icon.jpg" if {[file exists $fn] != 1} { catch { file mkdir "[homedir]/skins/$skindir/${::screen_size_width}x${::screen_size_height}/" } msg -DEBUG "preview_tablet_skin: creating $fn" set rescale_images_x_ratio [expr {$::screen_size_height / 1600.0}] set rescale_images_y_ratio [expr {$::screen_size_width / 2560.0}] set src "[homedir]/skins/$skindir/2560x1600/icon.jpg" catch { $::table_style_preview_image read $src photoscale $::table_style_preview_image $rescale_images_y_ratio $rescale_images_x_ratio $::table_style_preview_image write $fn -format {jpeg -quality 90} } } else { set fn "[homedir]/skins/$skindir/${::screen_size_width}x${::screen_size_height}/icon.jpg" $::table_style_preview_image read $fn } make_current_listbox_item_blue $::globals(tablet_styles_listbox) } proc preview_history {w args} { catch { set profile [lindex [history_directories] [$w curselection] [$w curselection]] msg -DEBUG "preview_history: history item: $profile [$w curselection]" set fn "[homedir]/history/${profile}.tcl" # need to code this array set props [encoding convertfrom utf-8 [read_binary_file $fn]] array set ::settings $props(settings) espresso_elapsed length 0; espresso_elapsed append $props(espresso_elapsed) espresso_pressure length 0; espresso_pressure append $props(espresso_pressure) espresso_flow length 0; espresso_flow append $props(espresso_flow) espresso_flow_weight length 0; espresso_flow_weight append $props(espresso_flow_weight) espresso_temperature_basket length 0; espresso_temperature_basket append $props(espresso_temperature_basket) espresso_temperature_mix length 0; espresso_temperature_mix append $props(espresso_temperature_mix) make_current_listbox_item_blue $::globals(history_listbox) } } proc save_settings_and_ask_to_restart_app {} { save_settings; message_page [translate "Please quit and restart this app to apply your changes."] [translate "Quit"]; } proc message_page {msg buttonmsg {longertxt {}} } { if {[catch { if {$longertxt == ""} { .can coords $::message_label [list [rescale_x_skin 1280] [rescale_y_skin 800]] } else { # if there is a longer message, then move the larger font label up, to make room for it .can coords $::message_label [list [rescale_x_skin 1280] [rescale_y_skin 550]] } .can itemconfigure $::message_label -text $msg .can itemconfigure $::message_button_label -text $buttonmsg .can itemconfigure $::message_longertxt -text $longertxt set_next_page off message; page_show message } err] != 0} { msg -ERROR "message_page failed because: '$err'" } } #set ::infopage_label [add_de1_text "infopage" 1280 750 -text "" -font Helv_15_bold -fill "#2d3046" -justify "center" -anchor "center" -width 900] #set ::infopage_button_label [add_de1_text "infopage" 1280 1090 -text [translate "Ok"] -font Helv_10_bold -fill "#fAfBff" -anchor "center"] #set ::infopage_button [add_de1_button "infopage" {say [translate {Ok}] $::settings(sound_button_in); set_next_page off off} 980 990 1580 1190 ""] proc info_page {msg buttonmsg {nextpage {}}} { if {[catch { .can itemconfigure $::infopage_label -text $msg .can itemconfigure $::infopage_button_label -text $buttonmsg set_next_page off infopage page_show off if {$nextpage != ""} { msg -INFO "Setting next page after info to: '$nextpage'" set_next_page off $nextpage } else { set_next_page off off } } err] != 0} { msg -ERROR "info_page failed because: '$err'" } } proc version_page {msg buttonmsg} { if {[catch { .can itemconfigure $::versionpage_label -text $msg .can itemconfigure $::versionpage_button_label -text $buttonmsg set_next_page off versionpage; page_show off } err] != 0} { msg -ERROR "info_page failed because: '$err'" } } proc change_bluetooth_device {} { ################################################################################################################ set w $::ble_listbox_widget #set ::settings(profile) [$::globals(profiles_listbox) get [$::globals(profiles_listbox) curselection]] if {[$w curselection] == ""} { # no current selection msg -DEBUG "change_bluetooth_device: no BLE selection" return "" } set selection_index [$w curselection] set dic [lindex $::de1_device_list $selection_index] set addr [dict get $dic address] if {$addr == $::settings(bluetooth_address)} { # if no change in setting, then disconnect/reconnect. #return ################################################################################################################ # prevent rapid changing of DE1 bluetooth setting, because that can cause multiple connections to be made to the same DE1 if {[ifexists ::globals(changing_bluetooth_device)] == 1} { msg -DEBUG "change_bluetooth_device: already changing_bluetooth_device" return } msg -NOTICE "change_bluetooth_device: reconnecting to DE1" } set ::globals(changing_bluetooth_device) 1 after 5000 {set ::globals(changing_bluetooth_device) 0} if {$addr != $::settings(bluetooth_address)} { # if no change in setting, then disconnect/reconnect. set ::settings(bluetooth_address) $addr save_settings } # disconnect (if necessary) and reconnect to the DE1 now ble_connect_to_de1 fill_ble_listbox } proc change_scale_bluetooth_device {} { set w $::ble_scale_listbox_widget if {$w == ""} { return } set selection_index [$w curselection] if {$selection_index == ""} { return } puts "selection_index: $selection_index" msg "selected item" set dic [lindex $::peripheral_device_list $selection_index] set addr [dict get $dic address] set name [dict get $dic name] set connectiontyte [dict get $dic connectiontype] set devicetype [dict get $dic devicetype] set devicefamily [dict get $dic devicefamily] if { $name == "" } { set name $devicefamily } if {$connectiontyte ne "ble"} { msg -WARNING "Non BLE peripheral requested for connect. Damn!" return } msg -INFO "selected $devicetype $name @ $addr" if {$devicetype eq "scale"} { set ::settings(scale_bluetooth_address) $addr set ::settings(scale_bluetooth_name) $name set ::settings(scale_type) $devicefamily msg "set scale type to: '$::settings(scale_type)' $addr" set handle $::de1(scale_device_handle) if {$handle != "" && $handle != 0} { set ::de1(scale_device_handle) 0 #set ::de1(cmdstack) {}; #set ::currently_connecting_scale_handle 0 ble close $handle ble_connect_to_scale } else { ble_connect_to_scale } #after 500 } else { msg -WARNING "Non scale peripheral requested for connect. Damn!" } save_settings fill_peripheral_listbox } proc select_profile { profile } { set fn "[homedir]/profiles/${profile}.tcl" set ::settings(profile) $profile set ::settings(profile_notes) "" # for importing De1 profiles that don't have this feature. set ::settings(preinfusion_flow_rate) 4 # Disable limits by default set ::settings(maximum_pressure) 0 set ::settings(maximum_flow) 0 # unset -nocomplain ::settings(profile_video_help) load_settings_vars $fn set ::settings(profile_filename) $profile if {[language] != "en" && $::settings(profile_language) == "en" && [ifexists ::settings(author)] == "Decent"} { # the first time this profile is loaded into another language, we should try to translate the # title and notes to the local language set ::settings(profile_notes) [translate $::settings(profile_notes)] set ::settings(profile_title) [translate $::settings(profile_title)] set ::settings(profile_language) [language] } set ::settings(original_profile_title) $::settings(profile_title) set ::settings(settings_profile_type) [::profile::fix_profile_type $::settings(settings_profile_type)] set ::settings(profile) $::settings(profile_title) ::profile::sync_from_legacy update_onscreen_variables profile_has_not_changed_set # as of v1.3 people can start an espresso from the group head, which means their currently selected # profile needs to sent right away to the DE1, in case the person taps the GH button to start espresso w/o leaving settings send_de1_settings_soon } set preview_profile_counter 0 proc preview_profile {} { if {$::de1(current_context) != "settings_1"} { return } if {[ifexists ::filling_profiles] == 1} { # use this variable to prevent triggering preview_profile for each profile as it gets added to the listbox. Tk would otherwise do this for each profile as its added to the listbox return } incr ::preview_profile_counter set w $::globals(profiles_listbox) #$w selection set active #set ::settings(profile) [$::globals(profiles_listbox) get [$::globals(profiles_listbox) curselection]] if {[$w curselection] == ""} { $w selection set $::current_profile_number msg -DEBUG "preview_profile: setting profile to $::current_profile_number" #set profile $::current_profile_number } #set profile [$w get [$w curselection]] #set profile [lindex [profile_directories] [$w curselection]] set profile $::profile_number_to_directory([$w curselection]) set fn "[homedir]/profiles/${profile}.tcl" if {[ifexists ::profiles_hide_mode] == 1} { catch { array set thisprofile [encoding convertfrom utf-8 [read_binary_file $fn]] } if {[info exists thisprofile(profile_title)] != 1} { msg -WARNING "Corrupt profile file to preview: '$d'" return } if {[ifexists thisprofile(profile_hide)] == 1} { set thisprofile(profile_hide) 0 } else { set thisprofile(profile_hide) 1 } save_array_to_file thisprofile $fn set ::filling_profiles 1 # need to save and restore the scrollbar value, because we're refilling the listbox to show hide/show state change set oldscrollbarbalue [$::profiles_scrollbar get] fill_profiles_listbox unset -nocomplain ::filling_profiles $::profiles_scrollbar set $oldscrollbarbalue listbox_moveto $::globals(profiles_listbox) $::profiles_slider $oldscrollbarbalue return } select_profile $profile make_current_listbox_item_blue $::globals(profiles_listbox) #set ::settings(profile_notes) [clock seconds] } proc send_de1_settings_soon {} { # if they have a GHC then they can press it while in the settings page, and expect the profile they just select to be in place. Thus we bluetooth send # profiles as they tap on them, but only if they have a GHC. No point in sending bluetooth commands that are not needed on non-GHC machines. if {[ghc_required] == 1} { if {[info exists ::save_settings_to_de1_id] == 1} { after cancel $::save_settings_to_de1_id; unset -nocomplain ::save_settings_to_de1_id msg -NOTICE "send_de1_settings_soon: cancelled extra de1_send" } set ::save_settings_to_de1_id [after 500 save_settings_to_de1] } } proc profile_has_changed_set_colors {} { if {$::settings(profile_has_changed) == 1} { update_de1_explanation_chart if {[info exists ::globals(widget_profile_name_to_save)] == 1} { catch { $::globals(widget_profile_name_to_save) configure -bg #ffe3e3 } } catch { .can itemconfigure $::globals(widget_current_profile_name) -fill $::de1(widget_current_profile_name_color_normal) } catch { .can itemconfigure $::globals(widget_current_profile_name1) -fill $::de1(widget_current_profile_name_color_normal) } catch { .can itemconfigure $::globals(widget_current_profile_name2) -fill $::de1(widget_current_profile_name_color_normal) } catch { .can itemconfigure $::globals(widget_current_profile_name_espresso) -fill $::de1(widget_current_profile_name_color_normal) } catch { .can itemconfigure $::globals(widget_current_profile_name_espresso1) -fill $::de1(widget_current_profile_name_color_normal) } catch { .can itemconfigure $::globals(widget_current_profile_name_espresso2) -fill $::de1(widget_current_profile_name_color_normal) } } else { if {[info exists ::globals(widget_profile_name_to_save)] == 1} { # this indicates to the user that the profile has changed or not catch { $::globals(widget_profile_name_to_save) configure -bg #fbfaff } } # this is displayed on the main Insight skin page catch { .can itemconfigure $::globals(widget_current_profile_name) -fill $::de1(widget_current_profile_name_color_changed) } catch { .can itemconfigure $::globals(widget_current_profile_name1) -fill $::de1(widget_current_profile_name_color_changed) } catch { .can itemconfigure $::globals(widget_current_profile_name2) -fill $::de1(widget_current_profile_name_color_changed) } catch { .can itemconfigure $::globals(widget_current_profile_name_espresso) -fill $::de1(widget_current_profile_name_color_changed) } catch { .can itemconfigure $::globals(widget_current_profile_name_espresso1) -fill $::de1(widget_current_profile_name_color_changed) } catch { .can itemconfigure $::globals(widget_current_profile_name_espresso2) -fill $::de1(widget_current_profile_name_color_changed) } } } proc profile_has_changed_set args { # if one the scroll bars has been touched by a human (not by the page display code) then mark the profile as having been changed if {[lsearch -exact [stackprocs] "page_show"] == -1 && [lsearch -exact [stackprocs] "update_onscreen_variables"] == -1} { set ::settings(profile_has_changed) 1 } else { # pass } #profile_has_changed_set_colors } proc profile_has_not_changed_set args { set ::settings(profile_has_changed) 0 } proc load_settings_vars {fn} { msg -NOTICE "load_settings_vars $fn" # default to no temp steps, so as to migrate older profiles that did not have this setting, and not accidentally enble this feature on them unset -nocomplain ::settings(espresso_temperature_steps_enabled) # default value set ::settings(final_desired_shot_volume_advanced_count_start) 0 #error "load_settings_vars" # set the default profile type to use, this can be over-ridden by the saved profile set ::settings(settings_profile_type) "settings_2a" catch { foreach {k v} [encoding convertfrom utf-8 [read_binary_file $fn]] { #set ::settings($k) $v set temp_settings($k) $v } } if {[ifexists temp_settings(settings_profile_type)] == "settings_2c" && [ifexists temp_settings(final_desired_shot_weight)] != "" && [ifexists temp_settings(final_desired_shot_weight_advanced)] == "" } { msg -NOTICE "load_settings_vars: Using a default" \ "for final_desired_shot_weight_advanced" \ "from final_desired_shot_weight of" \ [ifexists temp_settings(final_desired_shot_weight)] set temp_settings(final_desired_shot_weight_advanced) [ifexists temp_settings(final_desired_shot_weight)] } # pre-set the shot volume, to the shot weight, if importing an old shot definition that doesn't have a an end volume if {[ifexists temp_settings(final_desired_shot_volume)] == ""} { msg -NOTICE "load_settings_vars: pre-set the shot volume,"\ "to the shot weight, if importing an old shot definition" \ "that doesn't have a an end volume" set temp_settings(final_desired_shot_volume) [ifexists temp_settings(final_desired_shot_weight)] } # properly reset beverage_type if not provided in the profile, o/w the beverage_type of the last profile with it # defined will be persisted throughout all subsequent shots. if { ![info exists temp_settings(beverage_type)] } { set temp_settings(beverage_type) {} } if {[ifexists temp_settings(black_screen_saver)] == 1} { # we've moved the "black screen saver" feature from its own dedicated variable to now be a setting of "0 minutes" on the screen saver change timer # this line is simply for backward compatiblity, moving the old setting to a new one set temp_settings(final_desired_shot_volume) 0 set temp_settings(saver_brightness) 0 } # we accidentally saved water temp in profiles for about a year and then decided we didn't want that, so remove it from profiles if it's there unset -nocomplain temp_settings(water_temperature) catch { array set ::settings [array get temp_settings] } # john disabling LONG PRESS support as it appears to be buggy on tablets https://3.basecamp.com/3671212/buckets/7351439/messages/2566269076#__recording_2595312790 set ::setting(disable_long_press) 1 update_de1_explanation_chart } proc save_settings_vars {fn varlist} { set txt "" foreach k $varlist { if {[info exists ::settings($k)] == 1} { set v $::settings($k) append txt "[list $k] [list $v]\n" } } set success 0 catch { write_file $fn $txt set success 1 } return $success } proc profile_vars {} { return { advanced_shot espresso_temperature_steps_enabled author espresso_hold_time preinfusion_time espresso_pressure espresso_decline_time pressure_end espresso_temperature espresso_temperature_0 espresso_temperature_1 espresso_temperature_2 espresso_temperature_3 settings_profile_type flow_profile_preinfusion flow_profile_preinfusion_time flow_profile_hold flow_profile_hold_time flow_profile_decline flow_profile_decline_time flow_profile_minimum_pressure preinfusion_flow_rate profile_notes final_desired_shot_volume final_desired_shot_weight final_desired_shot_weight_advanced tank_desired_water_temperature final_desired_shot_volume_advanced profile_title profile_language preinfusion_stop_pressure profile_hide final_desired_shot_volume_advanced_count_start beverage_type maximum_pressure maximum_pressure_range_advanced maximum_flow_range_advanced maximum_flow maximum_pressure_range_default maximum_flow_range_default} } proc set_profile_title_untitled {} { # if no name then give it a name which is just a number if {$::settings(profile_title) == ""} { incr ::settings(preset_counter) save_settings set ::settings(profile_title) [subst {[translate "Untitled"] $::settings(preset_counter)}] } } proc save_profile {} { if {$::settings(profile_title) == [translate "Saved"]} { return } # if no name then give it a name which is just a number set_profile_title_untitled #set profile_vars { advanced_shot author espresso_hold_time preinfusion_time espresso_pressure espresso_decline_time pressure_end espresso_temperature settings_profile_type flow_profile_preinfusion flow_profile_preinfusion_time flow_profile_hold flow_profile_hold_time flow_profile_decline flow_profile_decline_time flow_profile_minimum_pressure preinfusion_flow_rate profile_notes water_temperature final_desired_shot_volume final_desired_shot_weight final_desired_shot_weight_advanced tank_desired_water_temperature final_desired_shot_volume_advanced profile_title profile_language preinfusion_stop_pressure} #set profile_name_to_save $::settings(profile_to_save) if {$::settings(settings_profile_type) == "settings_2c2"} { # if on the LIMITS tab, indicate that this is settings_2c (aka "advanced") shot as part of the OK button process set ::settings(settings_profile_type) "settings_2c" } if {[ifexists ::settings(original_profile_title)] == $::settings(profile_title)} { set profile_filename $::settings(profile_filename) } else { # if they change the description of the profile, then save it to a new name # replace prior usage of unformatted seconds with sanitized profile name and append with formatted time if file exists set profile_filename [::profile::filename_from_title $::settings(profile_title)] set profile_timestamp [clock format [clock seconds] -format %Y%m%d_%H%M%S] if {[file exists "[homedir]/profiles/${profile_filename}.tcl"] == 1} { append profile_filename "_" $profile_timestamp } } set fn "[homedir]/profiles/${profile_filename}.tcl" if {[write_file $fn ""] == 0} { set fn "[homedir]/profiles/${profile_timestamp}.tcl" } # set the title back to its title, after we display SAVED for a second # moves the cursor to the end of the seletion after showing the "saved" message. after 1000 "set ::settings(profile_title) \{$::settings(profile_title)\}; $::globals(widget_profile_name_to_save) icursor 999" # Save V2 of profiles in parallel ::profile::save "[homedir]/profiles_v2/${profile_filename}.json" if {[save_settings_vars $fn [profile_vars]] == 1} { set ::settings(profile) $::settings(profile_title) fill_profiles_listbox update_de1_explanation_chart set ::settings(profile_title) [translate "Saved"] profile_has_not_changed_set } else { set ::settings(profile_title) [translate "Invalid name"] } profile_has_changed_set_colors } proc save_espresso_rating_to_history {} { #unset -nocomplain ::settings(history_saved) set ::settings(history_saved) 0 save_this_espresso_to_history {} {} } ::de1::event::listener::after_flow_complete_add \ [lambda {event_dict} { save_this_espresso_to_history \ [dict get $event_dict previous_state] \ [dict get $event_dict this_state] \ } ] proc save_this_espresso_to_history {unused_old_state unused_new_state} { msg -DEBUG "save_this_espresso_to_history (entry)" # only save shots that have at least 5 data points if {!$::settings(history_saved) && [espresso_elapsed length] > 5 && [espresso_pressure length] > 5 && $::settings(should_save_history) == 1} { #TODO disable once v2 shotfiles are stable #if {$::settings(create_legacy_shotfiles) == 1} { set espresso_data [::shot::create_legacy] set fn "[homedir]/history/[clock format $::settings(espresso_clock) -format "%Y%m%dT%H%M%S"].shot" write_file $fn $espresso_data #} set espresso_data [::shot::create] set fn "[homedir]/history_v2/[clock format $::settings(espresso_clock) -format "%Y%m%dT%H%M%S"].json" write_file $fn $espresso_data msg -NOTICE "Saved this espresso to history" set ::settings(history_saved) 1 } else { msg -NOTICE "Not saved to history:" \ "history_saved: $::settings(history_saved)" \ "Time samples: [espresso_elapsed length]" \ "Pres samples: [espresso_pressure length]" \ "should_save_history: $::settings(should_save_history)" } } proc ghc_required {} { if {$::settings(ghc_is_installed) != 0 && $::settings(ghc_is_installed) != 1 && $::settings(ghc_is_installed) != 2 && $::settings(ghc_is_installed) != 4} { return 1 } if {$::undroid == 1 && $::android == 0} { return 0 } return 0 } proc start_text_if_espresso_ready {} { set num $::de1(substate) set substate_txt $::de1_substate_types($num) if {$substate_txt == "ready" && $::de1(device_handle) != 0} { if {[ghc_required]} { # display READY instead of START, because they have to tap the group head to start, they cannot tap the tablet, due to UL compliance limits return [translate "READY"] } return [translate "START"] } return [translate "WAIT"] } proc restart_text_if_espresso_ready {} { set num $::de1(substate) set substate_txt $::de1_substate_types($num) if {$substate_txt == "ready" && $::de1(device_handle) != 0} { if {[ghc_required]} { # display READY instead of START, because they have to tap the group head to start, they cannot tap the tablet, due to UL compliance limits return [translate "READY"] } return [translate "RESTART"] } return [translate "WAIT"] } proc stop_text_if_espresso_stoppable {} { set num $::de1(substate) set substate_txt $::de1_substate_types($num) if {$substate_txt != "ending"} { return [translate "STOP"] } return [translate "WAIT"] } # TODO: this should probably be renamed. proc espresso_history_save_from_gui {} { set num $::de1(substate) set substate_txt $::de1_substate_types($num) if {$substate_txt != "ready"} { set state [translate "WAIT"] } else { # if {$::settings(history_saved) != 1} { # set state [translate "SAVING"] #} else { #}; if {[ghc_required]} { # display READY instead of START, because they have to tap the group head to start, they cannot tap the tablet, due to UL compliance limits set state [translate "READY"] } else { set state [translate "RESTART"] } } #set state [translate "WAIT"] #save_this_espresso_to_history; return $state } proc bar_or_off_text {num} { if {$num == 0} { return [translate "off"] } else { return [subst {$num [translate "bar"]}] } } proc preinfusion_seconds_text {num} { if {$num == 0} { return [translate "off"] } elseif {$num == 1} { return [subst {< $num [translate "second"]}] } else { return [subst {< $num [translate "seconds"]}] } } proc seconds_text {num} { if {$num == 0} { return [translate "off"] } elseif {$num == 1} { return [subst {$num [translate "second"]}] } elseif {$num == 60} { return [translate "1 minute"] } else { return [subst {$num [translate "seconds"]}] } } proc seconds_text_abbreviated {num} { if {$num == 0} { return [translate "off"] } elseif {$num == 1} { return [subst {$num [translate "sec"]}] } elseif {$num == 60} { return [translate "1 min"] } else { return [subst {$num [translate "sec"]}] } } proc screen_saver_change_minutes {num} { if {$num == 0} { set ::settings(saver_brightness) 0 return [translate "Always black"] } else { return "[translate {Change image every:}] [minutes_text $num]" } } proc minutes_text {num} { if {$num == 0} { return [translate "off"] } elseif {$num == 60} { return [translate "1 hour"] } elseif {$num == 120} { return [translate "2 hours"] } elseif {$num == 1} { return [subst {$num [translate "minute"]}] } else { return [subst {$num [translate "minutes"]}] } } proc days_text {num} { if {$num == 0} { return [translate "immediately"] } elseif {$num == 30} { return [translate "One month"] } else { return [subst {$num [translate "days"]}] } } proc scentone_choice {english_aroma} { if {[lsearch -exact $::settings(scentone) $english_aroma] == -1} { return [translate $english_aroma] } else { return [subst {\[ \[ \[ [translate $english_aroma] \] \] \]}] } } proc scentone_toggle {english_aroma} { if {[lsearch -exact $::settings(scentone) $english_aroma] == -1} { lappend ::settings(scentone) $english_aroma set ::settings(scentone) [lsort $::settings(scentone)] } else { set ::settings(scentone) [lsort -unique [list_remove_element $::settings(scentone) $english_aroma]] } update_onscreen_variables } proc scentone_category {english_category} { set english_aroma_list $::scentone($english_category) foreach english_aroma $english_aroma_list { if {[lsearch -exact $::settings(scentone) $english_aroma] != -1} { return [subst {\[ \[ \[ [translate $english_category] \] \] \]}] } } return [translate $english_category] } proc scentone_selected { {category {}} } { set returnlist {} foreach selected $::settings(scentone) { if {$category == ""} { # if this is a complete list of all selected aromas lappend returnlist [translate $selected] } else { # if this is only the selected aromas for a subcategory if {[lsearch -exact $::scentone($category) $selected] != -1} { lappend returnlist [translate $selected] } } } if {$returnlist == "" } { if {$category == ""} { return [translate "Categories"] } else { return [subst {[translate $category]}] } } if {$category != ""} { return [subst {[translate $category] : [join [lsort $returnlist] ", "].}] } else { return [subst {[translate "Selected:"] [join [lsort $returnlist] ", "].}] } } proc scentone_translated_selection { } { set returnlist {} foreach selected $::settings(scentone) { lappend returnlist [translate $selected] } if {$returnlist == ""} { return "" } return [join [lsort $returnlist] ", "]. } proc round_to_half_integer {in} { set r 0 catch { set r [expr {int($in * 2) / 2.0}] } return $r } proc check_firmware_update_is_available {} { #if {$::settings(ghc_is_installed) != 0} { # ok to do v1.3 fw update #if {$::settings(force_fw_update) != 1} { #set ::de1(firmware_update_button_label) "Up to date" #return "" #} #} else { #if {$::settings(force_fw_update) != 1} { # set ::de1(firmware_update_button_label) "Up to date" # return "" #} #} if {[ifexists ::de1(firmware_crc)] == ""} { set ::de1(firmware_crc) [crc::crc32 -filename [fwfile]] } # obsolete method, comparing settings-saved CRC of last fw upload, to what DE1 reports as CRC if {($::de1(firmware_crc) != [ifexists ::settings(firmware_crc)]) && $::de1(currently_updating_firmware) == ""} { ##obsolete - set ::de1(firmware_update_button_label) "Firmware update available" } else { # pass } set ::de1(firmware_update_button_label) "Up to date" # new method, directly comparing the incremented version number if {[ifexists ::de1(Firmware_file_Version)] != "" && [ifexists ::settings(firmware_version_number)] != ""} { if {[ifexists ::de1(Firmware_file_Version)] > [ifexists ::settings(firmware_version_number)]} { set ::de1(firmware_update_button_label) "Firmware update available" } elseif {[ifexists ::de1(Firmware_file_Version)] < [ifexists ::settings(firmware_version_number)]} { set ::de1(firmware_update_button_label) "Firmware downgrade available" } } return "" } proc firmware_update_eta_label {} { if {[info exists ::de1(firmware_update_start_time)] != 1} { return } set elapsed [expr {[clock milliseconds] - $::de1(firmware_update_start_time)}] set ::de1(firmware_update_eta) 0 set percentage [expr {1.0 * ($::de1(firmware_bytes_uploaded)) / $::de1(firmware_update_size)}] if {$percentage >= 1 && $::de1(currently_updating_firmware) == 0} { #return "[translate {Turn your machine off and on again}]" return "" } elseif {$percentage < 0.003 } { # not enough data to give a good estimate yet return "" } else { set etamin [expr {round((($elapsed / $percentage) - $elapsed) / 60000)}] set etasec [expr {1 + round((($elapsed / $percentage) - $elapsed) / 1000)}] if {$etasec >=120} { return "$etamin [translate minutes]" } else { return "$etasec [translate seconds]" } } } proc firmware_uploaded_label {} { if {($::de1(firmware_bytes_uploaded) == 0 || $::de1(firmware_update_size) == 0) && $::de1(currently_updating_firmware) != "1" && $::de1(currently_erasing_firmware) != "1"} { if {$::de1(firmware_crc) == [ifexists ::settings(firmware_crc)]} { return [translate "No update necessary"] } return "" } if {$::de1(firmware_update_size) == 0 || $::de1(firmware_bytes_uploaded) == 0} { return "0.0%" } set percentage [expr {(100.0 * $::de1(firmware_bytes_uploaded)) / $::de1(firmware_update_size)}] if {$percentage >= 100 && $::de1(currently_updating_firmware) == 0} { #return "[translate {Turn your machine off and on again}]" return [translate "Done"] } else { return "[round_to_one_digits $percentage]%" } } proc de1_version_bleapi {} { array set ver $::de1(version) set c [ifexists ver(BLE_APIVersion)] if {$c == ""} { set c 0 } return $c } proc de1_version_string {} { array set v $::de1(version) #set v(BLE_Sha) [clock seconds] #, APP=[package version de1app] set version "BLE v[ifexists v(BLE_Release)].[ifexists v(BLE_Changes)].[ifexists v(BLE_Commits)], API v[ifexists v(BLE_APIVersion)], SHA=[ifexists v(BLE_Sha)]" if {[ifexists v(FW_Sha)] != [ifexists v(BLE_Sha)] && [ifexists v(FW_Sha)] != 0} { append version "\nFW v[ifexists v(FW_Release)].[ifexists v(FW_Changes)].[ifexists v(FW_Commits)], API v[ifexists v(FW_APIVersion)], SHA=[ifexists v(FW_Sha)]" } if {$::settings(firmware_sha) != "" && [ifexists v(BLE_Sha)] != "" && $::settings(firmware_sha) != [ifexists v(BLE_Sha)] } { after 5000 [list info_page "[translate {Your DE1 firmware has been upgraded}]\n\n$version" [translate "Ok"]] } array set modelarr [list 0 [translate "unknown"] 1 DE1 2 DE1+ 3 DE1PRO 4 DE1XL 5 DE1CAFE 6 DE1XXL 7 DE1XXXL] set brev "" if {[ifexists ::settings(cpu_board_model)] != ""} { set brev [expr {$::settings(cpu_board_model) / 1000.0}] } if {$brev != ""} { append version ", [translate pcb]=$brev" } if {[ifexists ::settings(machine_model)] != "" && [ifexists ::settings(machine_model)] != "0"} { append version ", [translate model]=[ifexists modelarr([ifexists ::settings(machine_model)])]" } if {[ifexists ::settings(firmware_version_number)] != ""} { append version ", [translate current]=[ifexists ::settings(firmware_version_number)]" } if {[ifexists ::de1(Firmware_file_Version)] != "" && [ifexists ::settings(firmware_version_number)] != "" && [ifexists ::de1(Firmware_file_Version)] != [ifexists ::settings(firmware_version_number)] } { append version ", [translate available]=[ifexists ::de1(Firmware_file_Version)]" } if { [package version de1app] ne "" } { append version ", [translate app]=v[package version de1app] [app_updates_policy_as_text]" } #if { [app_updates_policy_as_text] ne "" } { # append version ", [translate {branch}]=[app_updates_policy_as_text]" #} if {[ifexists v(BLE_Sha)] != "" && $::settings(firmware_sha) != [ifexists v(BLE_Sha)] } { set ::settings(firmware_sha) $v(BLE_Sha) save_settings } return $version #return "HW=[ifexists v(BLEFWMajor)].[ifexists v(BLEFWMinor)].[ifexists v(P0BLECommits)].[ifexists v(Dirty)] API=[ifexists v(APIVersion)] SHA=[ifexists v(BLESha)]" } # spreadsheet to calculate this calculated from CAD, from Mark Kelly, and is in /d/admin/src/ proc water_tank_level_to_milliliters {mm} { set mm_to_ml [list \ 0 \ 16 \ 43 \ 70 \ 97 \ 124 \ 151 \ 179 \ 206 \ 233 \ 261 \ 288 \ 316 \ 343 \ 371 \ 398 \ 426 \ 453 \ 481 \ 509 \ 537 \ 564 \ 592 \ 620 \ 648 \ 676 \ 704 \ 732 \ 760 \ 788 \ 816 \ 844 \ 872 \ 900 \ 929 \ 957 \ 985 \ 1013 \ 1042 \ 1070 \ 1104 \ 1138 \ 1172 \ 1207 \ 1242 \ 1277 \ 1312 \ 1347 \ 1382 \ 1417 \ 1453 \ 1488 \ 1523 \ 1559 \ 1594 \ 1630 \ 1665 \ 1701 \ 1736 \ 1772 \ 1808 \ 1843 \ 1879 \ 1915 \ 1951 \ 1986 \ 2022 \ 2058 \ ] set index [expr {int($mm)}] set mm 2058 catch { set mm [lindex $mm_to_ml $index ] } if {$mm == ""} { set mm 2058 } return $mm } proc refill_kit_retry_button {} { if {$::de1(substate) != 0} { return [translate "Touch screen to retry"] } else { return "" } } proc return_fan_threshold_calibration {temperature} { if {$temperature == 0} { return [translate "always on"] } return [return_temperature_setting $temperature] } proc return_steam_flow_calibration {steam_flow} { set in [expr {$steam_flow / 100.0}] if {$::settings(enable_fluid_ounces) != 1} { return [subst {[round_to_one_digits $in] [translate "mL/s"]}] } else { return [subst {[round_to_two_digits [ml_to_oz $in]] oz/s}] } } proc return_steam_heater_calibration {steam_temperature} { if {$steam_temperature < 130} { return [translate "off"] } return [return_temperature_setting $steam_temperature] } # obsolete - does not work reliably proc Restart_app {} { #foreach w [winfo children .] { ## destroy $w #} #source [info script] setup_images_for_first_page setup_images_for_other_pages .can itemconfigure splash -state hidden #after $::settings(timer_interval) update_onscreen_variables delay_screen_saver change_screen_saver_img #de1_ui_startup } foreach p [info procs] { set kpv(p,$p) 1 } foreach p [info vars] { set kpv(v,$p) 1 } set kpv(p,bgerror) 1 ####################################################################### # # Restart # # Deletes all non-root widgets and all bindings This allows us to # reload the source file without getting any errors. # proc Restart_app {{f ""}} { # global kpv tk_version ;#catch {auto_reset} ;# Undo all autoload stuff if [info exists tk_version] { ;# In wish not tclsh . config -cursor {} eval destroy [winfo children .] ;# Kill all children windows foreach v [bind .] { ;# Remove all bindings also bind . $v {} } if {$tk_version >= 4.0} { ;# Kill after events catch {foreach v [after info] {after cancel $v}} } if {$tk_version >= 8} { ;# Font stuff catch { eval font delete [font names] } eval image delete [image names] ;# Image stuff } wm geom . {} ;# Reset main window geometry . config -width 200 -height 200 raise . } foreach v [info procs] { ;# Remove unwanted procs if {[info exists kpv(p,$v)] == 0} { catch {rename $v {}} } } # foreach v [namespace children] { # if {[info exists kpv(n,$v)] == 0} { # catch {namespace delete $v} # } # } uplevel { ;# Get at global variables foreach _v [info vars] { ;# Remove unwanted variables if {[info exists kpv(v,$_v)] == 0} {;# Part of the core? catch {unset $_v} } } catch { unset _v } } if [file exists $f] { # uplevel source $f } source "de1plus.tcl" } proc toggle_espresso_steps_option {} { if {[ifexists ::settings(espresso_temperature_steps_enabled)] != 1} { set ::settings(espresso_temperature_steps_enabled) 1 } else { set ::settings(espresso_temperature_steps_enabled) 0 } msg -DEBUG "Clearing default step temps" set ::settings(espresso_temperature_0) $::settings(espresso_temperature) set ::settings(espresso_temperature_1) $::settings(espresso_temperature) set ::settings(espresso_temperature_2) $::settings(espresso_temperature) set ::settings(espresso_temperature_3) $::settings(espresso_temperature) msg -DEBUG "toggle_espresso_steps_option $::settings(espresso_temperature_steps_enabled)" } proc round_and_return_step_temperature_setting {varname} { if {[ifexists ::settings(espresso_temperature_steps_enabled)] != 1} { return "" } set v [ifexists $varname] if {$v == ""} { msg -ERROR "can't find variable $varname in round_and_return_step_temperature_setting" return "" } return [round_and_return_temperature_setting $varname] } proc range_check_variable {varname low high} { upvar $varname var if {$var < $low} { msg -DEBUG "range_check_variable: variable $varname was under $low" set var $low } if {$var > $high} { msg -DEBUG "range_check_variable: variable $varname was over $high" set var $high } } proc range_check_shot_variables {} { range_check_variable ::settings(espresso_temperature) 0 105 range_check_variable ::settings(espresso_temperature_0) 0 105 range_check_variable ::settings(espresso_temperature_1) 0 105 range_check_variable ::settings(espresso_temperature_2) 0 105 range_check_variable ::settings(espresso_temperature_3) 0 105 range_check_variable ::settings(preinfusion_time) 0 60 range_check_variable ::settings(espresso_hold_time) 0 60 range_check_variable ::settings(preinfusion_flow_rate) 0 $::de1(max_flowrate_v11) range_check_variable ::settings(flow_profile_hold) 0 $::de1(max_flowrate_v11) range_check_variable ::settings(flow_profile_decline) 0 $::de1(max_flowrate_v11) range_check_variable ::settings(preinfusion_stop_pressure) 0 $::de1(maxpressure) range_check_variable ::settings(espresso_pressure) 0 $::de1(maxpressure) range_check_variable ::settings(espresso_decline_time) 0 60 range_check_variable ::settings(pressure_end) 0 $::de1(maxpressure) range_check_variable ::settings(final_desired_shot_volume) 0 100 range_check_variable ::settings(final_desired_shot_weight) 0 100 range_check_variable ::settings(final_desired_shot_weight_advanced) 0 2000 range_check_variable ::settings(final_desired_shot_volume) 0 2000 range_check_variable ::settings(final_desired_shot_volume_advanced) 0 2000 range_check_variable ::settings(final_desired_shot_volume_advanced_count_start) 0 20 range_check_variable ::settings(tank_desired_water_temperature) 0 45 } proc change_espresso_temperature {amount} { if {$::settings(settings_profile_type) == "settings_2a" || $::settings(settings_profile_type) == "settings_2b"} { # pressure or flow profile if {[ifexists ::settings(espresso_temperature_steps_enabled)] == 1} { # if step temps are enabled then set the preinfusion start temp to the global temp # and then apply the relative change desired to each subsequent step set ::settings(espresso_temperature) [expr {$::settings(espresso_temperature) + $amount}] set ::settings(espresso_temperature_0) $::settings(espresso_temperature) set ::settings(espresso_temperature_1) [expr {$::settings(espresso_temperature_1) + $amount}] set ::settings(espresso_temperature_2) [expr {$::settings(espresso_temperature_2) + $amount}] set ::settings(espresso_temperature_3) [expr {$::settings(espresso_temperature_3) + $amount}] } else { set ::settings(espresso_temperature) [expr {$::settings(espresso_temperature) + $amount}] } } else { # advanced shots need every step changed set newshot {} set cnt 0 foreach step $::settings(advanced_shot) { incr cnt array unset -nocomplain props array set props $step set temp [ifexists props(temperature)] set newtemp [expr {$temp + $amount}] if {$cnt == 1} { set ::settings(espresso_temperature) $newtemp } set props(temperature) $newtemp lappend newshot [array get props] } set ::settings(advanced_shot) $newshot } range_check_shot_variables } proc when_to_start_pour_tracking_advanced {} { if {$::settings(final_desired_shot_volume_advanced_count_start) > 0} { set stepdesc "" catch { # this should all work, but in case there's something wrong with this logic or the advanced shot, wrap it all in a catch{} so as to not cause an error for such a minor feature if {$::settings(final_desired_shot_volume_advanced_count_start) > [llength [ifexists ::settings(advanced_shot)]] } { set ::settings(final_desired_shot_volume_advanced_count_start) [llength [ifexists ::settings(advanced_shot)]] } array set steparr [lindex [ifexists ::settings(advanced_shot)] [expr {-1 + $::settings(final_desired_shot_volume_advanced_count_start)}]] set stepdesc [ifexists steparr(name)] } return [subst {[translate "After step"] $::settings(final_desired_shot_volume_advanced_count_start) - $stepdesc}] } else { return [translate "Immediately"] } } proc app_updates_policy_as_text {} { set progname "stable" if {[ifexists ::settings(app_updates_beta_enabled)] == 1} { set progname "beta" } elseif {[ifexists ::settings(app_updates_beta_enabled)] == 2} { set progname "nightly" } return [translate $progname] } proc set_resolution_height_from_width { {discard {}} } { set ::settings(screen_size_height) [expr {int($::settings(screen_size_width)/1.6)}] # check the width and make sure it is a multiple of 160. If not, pick the nearest setting. for {set x [expr {$::settings(screen_size_width) - 0}]} {$x <= 2800} {incr x} { set ratio [expr {$x / 160.0}] if {$ratio == int($ratio)} { set ::settings(screen_size_width) $x set ::settings(screen_size_height) [expr {int($::settings(screen_size_width)/1.6)}] break } } }