Been using the roaster for the last few years with overall good results. One thing that's definitely become obvious though, is the heating element is simply undersized for my needs. I've had to lower the fan speed a fair bit to even achieve my roast temps (and when it's cold, I have to lower the fan speed even further). This results in less agitation of the beans, which makes it more likely that some of them sit in one place too long and get slightly burnt. If we were dealing with more 'normal' temperatures, I could redesign the heating component of the roaster, but since the temps are so hot I really lack the tools or ability to make modifications to that part of the design (hence why a popcorn popper was used to begin with).
I'm still glad that I did this project and will continue to use it indefinitely, but when it eventually dies, I think I'll look into possibly just purchasing a roaster, or at the very least revisit the drawing board and see if I can find a way to increase the heating capacity (even if it's just finding a popcorn popper with a higher-wattage heating element). Having measured the power draw, even when keeping things below 15 amps I still have some room for more, not to mention if allowed myself to go all the way to 20 amps. There's also the possibility of adding some recirculation of the roasting air, but that adds a need to properly filter the air not to mention the fire hazard if some chaff makes it's way into the inlet.
I also had someone ask about the ESPHome YAML. While it's still a bit of a work-in-progress, it's mostly done. If I were starting from scratch, I'd definitely do things differently, but this was my first 'real' ESPHome project so I was still learning the nuances and best approaches to things. It's LONG as I have 3 separate roast profiles and essentially just copy-pasted things. Here's where it stands currently (with some redaction, as-needed):
esphome:
name: coffee-roaster
friendly_name: Coffee Roaster
# Added because of issues with the Neopixel turning blue on startup and not working properly afterwards
on_boot:
priority: 500
then:
- climate.control:
id: pid_controller
mode: "OFF"
target_temperature: 15.5556°C
- text_sensor.template.publish:
id: roast_status
state: "On"
- repeat:
count: 5
then:
- light.turn_off:
id: neopixel_light
- delay: 1s
esp32:
board: esp32dev
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API. Changed reboot_timeout to 0s to keep it from rebooting every 15min when using away from home.
api:
reboot_timeout: 0s
encryption:
key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
ota:
- platform: esphome
password: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
wifi:
networks:
- ssid: !secret wifi_ssid
password: !secret wifi_password
- ssid: !secret trailer_wifi_ssid
password: !secret wifi_password
- ssid: !secret chucks_wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Coffee-Roaster Fallback Hotspot"
password: !secret wifi_password
#Removed so that when it can't connect to WiFi, the local AP it creates is for the web server and not setting up a different WiFi connection (i.e. allows the web server to be accessed without a WiFi network)
#captive_portal:
web_server:
port: 80
local: true
interval:
- interval: 1s
then:
#Safety check to make sure that if the heater is on, the fan is also on
- if:
condition:
and:
- lambda: 'return id(heat_value).state > 0;'
- fan.is_off: fan_speed
then:
- script.stop: roast_1
- script.stop: roast_2
- script.stop: roast_3
# - script.stop: fan_mixer
- climate.control:
id: pid_controller
mode: "OFF"
- fan.turn_on:
id: fan_speed
speed: 100
- text_sensor.template.publish:
id: roast_status
state: "ERROR: Heater On, Fan Not On"
- light.turn_on:
id: neopixel_light
brightness: 100%
red: 100%
green: 0%
blue: 0%
effect: "Blink"
#Safety check to make sure that if the heater is 100% on for a while, the temperature actually increases.
- if:
condition:
and:
- lambda: 'return id(heat_value).state == 100;'
- lambda: 'return id(heater_counter) == 0;'
- lambda: 'return id(temp_sensor).state < 100;'
then:
- lambda: |-
id(heater_start_temp) = id(temp_sensor).state;
- lambda: |-
id(heater_counter) = (id(heater_counter) + 1);
else:
- if:
condition:
and:
- lambda: 'return id(heat_value).state == 100;'
- lambda: 'return id(heater_counter) > 0;'
- lambda: 'return id(temp_sensor).state < 100;'
then:
- lambda: |-
id(heater_counter) = (id(heater_counter) + 1);
else:
- lambda: |-
id(heater_counter) = 0;
- if:
condition:
and:
- lambda: 'return id(heater_counter) > 60;'
- lambda: 'return id(temp_sensor).state <= id(heater_start_temp);'
- lambda: 'return id(temp_sensor).state < 100;'
then:
- script.stop: roast_1
- script.stop: roast_2
- script.stop: roast_3
# - script.stop: fan_mixer
- climate.control:
id: pid_controller
mode: "OFF"
- fan.turn_on:
id: fan_speed
speed: 100
- text_sensor.template.publish:
id: roast_status
state: "ERROR: Heater On, Temperature Not Increasing"
- light.turn_on:
id: neopixel_light
brightness: 100%
red: 100%
green: 0%
blue: 0%
effect: "Blink"
else:
- if:
condition:
- lambda: 'return id(heater_counter) > 60;'
then:
- lambda: |-
id(heater_counter) = 0;
number:
#Roast #1
- platform: template
name: Roast 1 Preheat Temp
optimistic: true
id: roast_1_preheat_temp
device_class: temperature
unit_of_measurement: °C
max_value: 270
min_value: 15
step: 1
initial_value: 120
mode: box
- platform: template
name: Roast 1 Temp
optimistic: true
id: roast_1_temp
device_class: temperature
unit_of_measurement: °C
max_value: 270
min_value: 15
step: 1
initial_value: 221
mode: box
- platform: template
name: Roast 1 Ramp
optimistic: true
id: roast_1_ramp
icon: "mdi:thermometer-chevron-up"
unit_of_measurement: °C/min
max_value: 120
min_value: 1
step: 1
initial_value: 10
mode: box
- platform: template
name: Roast 1 Fan Speed
optimistic: true
id: roast_1_fan_speed
icon: "mdi:fan-speed-1"
unit_of_measurement: "%"
max_value: 100
min_value: 1
step: 1
initial_value: 75
mode: box
#Roast #2
- platform: template
name: Roast 2 Preheat Temp
optimistic: true
id: roast_2_preheat_temp
device_class: temperature
unit_of_measurement: °C
max_value: 270
min_value: 15
step: 1
initial_value: 120
mode: box
- platform: template
name: Roast 2 Temp
optimistic: true
id: roast_2_temp
device_class: temperature
unit_of_measurement: °C
max_value: 270
min_value: 15
step: 1
initial_value: 234
mode: box
- platform: template
name: Roast 2 Ramp
optimistic: true
id: roast_2_ramp
icon: "mdi:thermometer-chevron-up"
unit_of_measurement: °C/min
max_value: 120
min_value: 1
step: 1
initial_value: 8
mode: box
- platform: template
name: Roast 2 Fan Speed
optimistic: true
id: roast_2_fan_speed
icon: "mdi:fan-speed-2"
unit_of_measurement: "%"
max_value: 100
min_value: 1
step: 1
initial_value: 75
mode: box
#Roast #3
- platform: template
name: Roast 3 Preheat Temp
optimistic: true
id: roast_3_preheat_temp
device_class: temperature
unit_of_measurement: °C
max_value: 270
min_value: 15
step: 1
initial_value: 120
mode: box
- platform: template
name: Roast 3 Temp
optimistic: true
id: roast_3_temp
device_class: temperature
unit_of_measurement: °C
max_value: 270
min_value: 15
step: 1
initial_value: 221
mode: box
- platform: template
name: Roast 3 Ramp
optimistic: true
id: roast_3_ramp
icon: "mdi:thermometer-chevron-up"
unit_of_measurement: °C/min
max_value: 120
min_value: 1
step: 1
initial_value: 10
mode: box
- platform: template
name: Roast 3 Fan Speed
optimistic: true
id: roast_3_fan_speed
icon: "mdi:fan-speed-3"
unit_of_measurement: "%"
max_value: 100
min_value: 1
step: 1
initial_value: 50
mode: box
button:
#Roast #1
- platform: template
name: "Start Roast #1"
id: start_roast_1
icon: "mdi:coffee"
on_press:
then:
- if:
condition:
lambda: 'return (id(currently_roasting) == 1);'
then:
else:
- script.execute: roast_1
#Roast #2
- platform: template
name: "Start Roast #2"
id: start_roast_2
icon: "mdi:coffee"
on_press:
then:
- if:
condition:
lambda: 'return (id(currently_roasting) == 1);'
then:
else:
- script.execute: roast_2
#Roast #3
- platform: template
name: "Start Roast #3"
id: start_roast_3
icon: "mdi:coffee"
on_press:
then:
- if:
condition:
lambda: 'return (id(currently_roasting) == 1);'
then:
else:
- script.execute: roast_3
globals:
- id: currently_roasting
type: int
restore_value: no
initial_value: '0'
- id: current_temp_setpoint
type: int
restore_value: no
initial_value: '0'
#Used for heater safety interval check
- id: heater_counter
type: int
restore_value: no
initial_value: '0'
- id: heater_start_temp
type: int
restore_value: no
initial_value: '0'
script:
- id: reduce_fan_speed
mode: restart
then:
- delay: 30s
- fan.turn_on:
id: fan_speed
speed: 50
- delay: 20s
- fan.turn_on:
id: fan_speed
speed: 40
- delay: 20s
- fan.turn_on:
id: fan_speed
speed: 30
- id: roast_timer
mode: restart
then:
- delay: 2700s
- script.stop: roast_1
- script.stop: roast_2
- script.stop: roast_3
# - script.stop: fan_mixer
- climate.control:
id: pid_controller
mode: "OFF"
- fan.turn_on:
id: fan_speed
speed: 100
- text_sensor.template.publish:
id: roast_status
state: "ERROR: Roast Timeout"
- light.turn_on:
id: neopixel_light
brightness: 100%
red: 100%
green: 0%
blue: 0%
effect: "Blink"
- id: roast_1
then:
- globals.set:
id: currently_roasting
value: '1'
# - globals.set:
# id: current_fan_speed
# value: !lambda |-
# return id(roast_1_fan_speed).state;
- light.turn_on:
id: button_light_switch_1
- light.turn_off:
id: button_light_switch_2
- light.turn_off:
id: button_light_switch_3
- light.turn_on:
id: neopixel_light
brightness: 50%
red: 100%
green: 100%
blue: 0%
- fan.turn_on:
id: fan_speed
speed: !lambda |-
return id(roast_1_fan_speed).state;
- delay: 5s
- climate.control:
id: pid_controller
mode: HEAT
target_temperature: !lambda |-
return id(roast_1_preheat_temp).state;
- text_sensor.template.publish:
id: roast_status
state: "Roast #1 Preheat"
- wait_until:
condition:
lambda: |-
return id(temp_sensor).state > (id(roast_1_preheat_temp).state - 1);
timeout: 300s
- if:
condition:
- lambda: |-
return id(temp_sensor).state < (id(roast_1_preheat_temp).state - 1);
then:
- script.stop: roast_1
- climate.control:
id: pid_controller
mode: "OFF"
- fan.turn_on:
id: fan_speed
speed: 100
- text_sensor.template.publish:
id: roast_status
state: "ERROR: Preheat Timeout"
- light.turn_on:
id: neopixel_light
brightness: 100%
red: 100%
green: 0%
blue: 0%
effect: "Blink"
else:
- delay: 120s
# - script.execute: fan_mixer
- light.turn_on:
id: neopixel_light
brightness: 50%
red: 100%
green: 0%
blue: 0%
- globals.set:
id: current_temp_setpoint
value: !lambda |-
return id(roast_1_preheat_temp).state;
- script.execute: roast_timer
- text_sensor.template.publish:
id: roast_status
state: "Roast #1 Ramp-up"
- while:
condition:
or:
- lambda: |-
return id(temp_sensor).state < id(roast_1_temp).state;
#Was having issues with the roast ending early but not throwing errors. Changed from AND to OR above and added this to try at catch a situation where the temp sensor doesn't read a number (i.e. so it ISN'T less than the roast temp).
- lambda: |-
return isnan(id(temp_sensor).state);
then:
- climate.control:
id: pid_controller
mode: HEAT
target_temperature: !lambda |-
return id(current_temp_setpoint);
# delay will be calculated by dividing 60 by C/min value
- delay: !lambda |-
return 60000 / id(roast_1_ramp).state;
- if:
condition:
- lambda: |-
return id(current_temp_setpoint) == id(roast_1_temp).state;
then:
- script.execute: reduce_fan_speed
else:
- if:
condition:
- lambda: |-
return id(current_temp_setpoint) < id(roast_1_temp).state;
then:
- lambda: |-
id(current_temp_setpoint) = (id(current_temp_setpoint) + 1);
else:
- script.stop: roast_timer
- script.stop: reduce_fan_speed
# - script.stop: fan_mixer
- light.turn_on:
id: neopixel_light
brightness: 50%
red: 100%
green: 65%
blue: 0%
- climate.control:
id: pid_controller
mode: "OFF"
target_temperature: 15.5556°C
- fan.turn_on:
id: fan_speed
speed: 100
- text_sensor.template.publish:
id: roast_status
state: "Roast #1 Cooldown"
- wait_until:
condition:
sensor.in_range:
id: temp_sensor
below: 40
timeout: 1200s
- delay: 60s
- fan.turn_off: fan_speed
- globals.set:
id: currently_roasting
value: '0'
- text_sensor.template.publish:
id: roast_status
state: "Roast #1 Done"
- light.turn_on:
id: neopixel_light
brightness: 50%
red: 0%
green: 100%
blue: 0%
- id: roast_2
then:
- globals.set:
id: currently_roasting
value: '1'
- light.turn_on:
id: button_light_switch_2
- light.turn_off:
id: button_light_switch_1
- light.turn_off:
id: button_light_switch_3
- light.turn_on:
id: neopixel_light
brightness: 50%
red: 100%
green: 100%
blue: 0%
- fan.turn_on:
id: fan_speed
speed: !lambda |-
return id(roast_2_fan_speed).state;
- delay: 5s
- climate.control:
id: pid_controller
mode: HEAT
target_temperature: !lambda |-
return id(roast_2_preheat_temp).state;
- text_sensor.template.publish:
id: roast_status
state: "Roast #2 Preheat"
- wait_until:
condition:
lambda: |-
return id(temp_sensor).state > (id(roast_2_preheat_temp).state - 1);
timeout: 300s
- if:
condition:
- lambda: |-
return id(temp_sensor).state < (id(roast_2_preheat_temp).state - 1);
then:
- script.stop: roast_2
- climate.control:
id: pid_controller
mode: "OFF"
- fan.turn_on:
id: fan_speed
speed: 100
- text_sensor.template.publish:
id: roast_status
state: "ERROR: Preheat Timeout"
- light.turn_on:
id: neopixel_light
brightness: 100%
red: 100%
green: 0%
blue: 0%
effect: "Blink"
else:
- delay: 120s
- light.turn_on:
id: neopixel_light
brightness: 50%
red: 100%
green: 0%
blue: 0%
- globals.set:
id: current_temp_setpoint
value: !lambda |-
return id(roast_2_preheat_temp).state;
- script.execute: roast_timer
- text_sensor.template.publish:
id: roast_status
state: "Roast #2 Ramp-up"
- while:
condition:
or:
- lambda: |-
return id(temp_sensor).state < id(roast_2_temp).state;
#Was having issues with the roast ending early but not throwing errors. Changed from AND to OR above and added this to try at catch a situation where the temp sensor doesn't read a number (i.e. so it ISN'T less than the roast temp).
- lambda: |-
return isnan(id(temp_sensor).state);
then:
- climate.control:
id: pid_controller
mode: HEAT
target_temperature: !lambda |-
return id(current_temp_setpoint);
# delay will be calculated by dividing 60 by C/min value
- delay: !lambda |-
return 60000 / id(roast_2_ramp).state;
- if:
condition:
- lambda: |-
return id(current_temp_setpoint) == id(roast_2_temp).state;
then:
- script.execute: reduce_fan_speed
else:
- if:
condition:
- lambda: |-
return id(current_temp_setpoint) < id(roast_2_temp).state;
then:
- lambda: |-
id(current_temp_setpoint) = (id(current_temp_setpoint) + 1);
else:
- script.stop: roast_timer
- script.stop: reduce_fan_speed
- light.turn_on:
id: neopixel_light
brightness: 50%
red: 100%
green: 65%
blue: 0%
- climate.control:
id: pid_controller
mode: "OFF"
target_temperature: 15.5556°C
- fan.turn_on:
id: fan_speed
speed: 100
- text_sensor.template.publish:
id: roast_status
state: "Roast #2 Cooldown"
- wait_until:
condition:
sensor.in_range:
id: temp_sensor
below: 40
timeout: 1200s
- delay: 60s
- fan.turn_off: fan_speed
- globals.set:
id: currently_roasting
value: '0'
- text_sensor.template.publish:
id: roast_status
state: "Roast #2 Done"
- light.turn_on:
id: neopixel_light
brightness: 50%
red: 0%
green: 100%
blue: 0%
- id: roast_3
then:
- globals.set:
id: currently_roasting
value: '1'
- light.turn_on:
id: button_light_switch_3
- light.turn_off:
id: button_light_switch_2
- light.turn_off:
id: button_light_switch_1
- light.turn_on:
id: neopixel_light
brightness: 50%
red: 100%
green: 100%
blue: 0%
- fan.turn_on:
id: fan_speed
speed: !lambda |-
return id(roast_3_fan_speed).state;
- delay: 5s
- climate.control:
id: pid_controller
mode: HEAT
target_temperature: !lambda |-
return id(roast_3_preheat_temp).state;
- text_sensor.template.publish:
id: roast_status
state: "Roast #3 Preheat"
- wait_until:
condition:
lambda: |-
return id(temp_sensor).state > (id(roast_3_preheat_temp).state - 1);
timeout: 300s
- if:
condition:
- lambda: |-
return id(temp_sensor).state < (id(roast_3_preheat_temp).state - 1);
then:
- script.stop: roast_3
- climate.control:
id: pid_controller
mode: "OFF"
- fan.turn_on:
id: fan_speed
speed: 100
- text_sensor.template.publish:
id: roast_status
state: "ERROR: Preheat Timeout"
- light.turn_on:
id: neopixel_light
brightness: 100%
red: 100%
green: 0%
blue: 0%
effect: "Blink"
else:
#Shortened from 120s to 30s to reduce energy usage while in trailer
- delay: 30s
- light.turn_on:
id: neopixel_light
brightness: 50%
red: 100%
green: 0%
blue: 0%
- globals.set:
id: current_temp_setpoint
value: !lambda |-
return id(roast_3_preheat_temp).state;
- script.execute: roast_timer
- text_sensor.template.publish:
id: roast_status
state: "Roast #3 Ramp-up"
- while:
condition:
or:
- lambda: |-
return id(temp_sensor).state < id(roast_3_temp).state;
#Was having issues with the roast ending early but not throwing errors. Changed from AND to OR above and added this to try at catch a situation where the temp sensor doesn't read a number (i.e. so it ISN'T less than the roast temp).
- lambda: |-
return isnan(id(temp_sensor).state);
then:
- climate.control:
id: pid_controller
mode: HEAT
target_temperature: !lambda |-
return id(current_temp_setpoint);
# delay will be calculated by dividing 60 by C/min value
- delay: !lambda |-
return 60000 / id(roast_3_ramp).state;
- if:
condition:
- lambda: |-
return id(current_temp_setpoint) == id(roast_3_temp).state;
then:
- script.execute: reduce_fan_speed
else:
- if:
condition:
- lambda: |-
return id(current_temp_setpoint) < id(roast_3_temp).state;
then:
- lambda: |-
id(current_temp_setpoint) = (id(current_temp_setpoint) + 1);
else:
- script.stop: roast_timer
- script.stop: reduce_fan_speed
- light.turn_on:
id: neopixel_light
brightness: 50%
red: 100%
green: 65%
blue: 0%
- climate.control:
id: pid_controller
mode: "OFF"
target_temperature: 15.5556°C
- fan.turn_on:
id: fan_speed
speed: 100
- text_sensor.template.publish:
id: roast_status
state: "Roast #3 Cooldown"
- wait_until:
condition:
sensor.in_range:
id: temp_sensor
below: 40
timeout: 1200s
- delay: 60s
- fan.turn_off: fan_speed
- globals.set:
id: currently_roasting
value: '0'
- text_sensor.template.publish:
id: roast_status
state: "Roast #3 Done"
- light.turn_on:
id: neopixel_light
brightness: 50%
red: 0%
green: 100%
blue: 0%
- id: blank_script
then:
light:
- platform: binary
name: Button Light 1
output: button_light_1
id: button_light_switch_1
restore_mode: ALWAYS_OFF
- platform: binary
name: Button Light 2
output: button_light_2
id: button_light_switch_2
restore_mode: ALWAYS_OFF
- platform: binary
name: Button Light 3
output: button_light_3
id: button_light_switch_3
restore_mode: ALWAYS_OFF
- platform: neopixelbus
type: RGB
variant: SK6812
pin: GPIO22
num_leds: 1
name: NeoPixel
id: neopixel_light
restore_mode: ALWAYS_OFF
default_transition_length: 0s
effects:
- pulse:
name: "Blink"
transition_length: 0s
update_interval: 1s
binary_sensor:
- platform: gpio
pin:
number: GPIO27
inverted: true
mode:
input: true
pullup: true
name: Button 1
on_press:
then:
- if:
condition:
lambda: 'return (id(currently_roasting) == 1);'
then:
else:
- script.execute: roast_1
- platform: gpio
pin:
number: GPIO32
inverted: true
mode:
input: true
pullup: true
name: Button 2
on_press:
then:
- if:
condition:
lambda: 'return (id(currently_roasting) == 1);'
then:
else:
- script.execute: roast_2
- platform: gpio
pin:
number: GPIO33
inverted: true
mode:
input: true
pullup: true
name: Button 3
on_press:
then:
- if:
condition:
lambda: 'return (id(currently_roasting) == 1);'
then:
else:
- script.execute: roast_3
output:
- id: button_light_1
platform: gpio
pin: GPIO4
- id: button_light_2
platform: gpio
pin: GPIO25
- id: button_light_3
platform: gpio
pin: GPIO26
- platform: ledc
pin: GPIO16
frequency: 1000 Hz #might need to change to get it to work?
id: fan_pwm
min_power: 0.2
zero_means_zero: true
- platform: slow_pwm
pin: GPIO17
id: heater_pwm
period: 15s
turn_on_action:
- fan.turn_on:
id: fan_speed
#Required this action and leaving it blank threw errors, so I just made a script to call that does nothing.
turn_off_action:
- script.execute: blank_script
fan:
- platform: speed
output: fan_pwm
name: Fan
id: fan_speed
restore_mode: ALWAYS_OFF
climate:
- platform: pid
name: PID Controller
id: pid_controller
sensor: temp_sensor
default_target_temperature: 15.5556°C
heat_output: heater_pwm
control_parameters:
kp: 0.07276 #0.05009
ki: 0.00364 #0.00230
kd: 0.36376 #0.27302
visual:
min_temperature: 15.5556°C
max_temperature: 270°C
temperature_step: 1°C
switch:
- platform: template
name: PID Controller Autotune
turn_on_action:
- climate.pid.autotune: pid_controller
spi:
clk_pin: GPIO14 #14 #18
mosi_pin: GPIO13 #13 #23
miso_pin: GPIO12 #12 #19
sensor:
- platform: max31855
name: Temperature
cs_pin: GPIO15 #15 #5
update_interval: 1s
#filters:
# - lambda: return x * (9.0/5.0) + 32.0;
#unit_of_measurement: "°F"
id: temp_sensor
on_value_range:
- above: 280
then:
- script.stop: roast_1
- script.stop: roast_2
- script.stop: roast_3
# - script.stop: fan_mixer
- climate.control:
id: pid_controller
mode: "OFF"
- fan.turn_on:
id: fan_speed
speed: 100
- text_sensor.template.publish:
id: roast_status
state: "ERROR: OVERHEAT PROTECTION"
- light.turn_on:
id: neopixel_light
brightness: 100%
red: 100%
green: 0%
blue: 0%
effect: "Blink"
- platform: wifi_signal
name: WiFi Strength
update_interval: 60s
disabled_by_default: true
- platform: uptime
name: Uptime
disabled_by_default: true
- platform: pid
name: PID Kp
type: KP
- platform: pid
name: PID Ki
type: KI
- platform: pid
name: PID Kd
type: KD
- platform: pid
name: PID Heat
type: HEAT
id: heat_value
#heat_value is a percent but the state reports back 0-100, not 0-1
#Used a power meter to get the power draw at the various settings
- platform: template
name: Power
unit_of_measurement: "W"
device_class: "power"
lambda: |-
if (id(fan_speed).state) {
return 1 + 57.5 + (id(heat_value).state)*13.805;
} else {
return 1;
}
update_interval: 1s
# Progress indicator sensors
- platform: template
name: "Roast Progress"
id: roast_progress
lambda: |-
if (id(roast_timer).is_running() and id(roast_1).is_running()) {
return (id(roast_1_percent_complete).state);
} else if (id(roast_timer).is_running() and id(roast_2).is_running()){
return (id(roast_2_percent_complete).state);
} else if (id(roast_timer).is_running() and id(roast_3).is_running()){
return (id(roast_3_percent_complete).state);
} else if ((id(roast_status).state == "Roast #1 Done") or (id(roast_status).state == "Roast #2 Done") or (id(roast_status).state == "Roast #3 Done") or (id(roast_status).state == "Roast #1 Cooldown") or (id(roast_status).state == "Roast #2 Cooldown") or (id(roast_status).state == "Roast #3 Cooldown") ){
return 100;
} else {
return 0;
}
update_interval: 5s
unit_of_measurement: "%"
icon: mdi:timer-sand
accuracy_decimals: 0
- platform: template
name: "Roast #1 Percent Complete"
id: roast_1_percent_complete
lambda: return (id(temp_sensor).state);
update_interval: 5s
unit_of_measurement: "%"
icon: mdi:timer-sand
accuracy_decimals: 0
internal: true
filters:
- calibrate_linear:
method: least_squares
datapoints:
- 120 -> 0
- 221 -> 100
- clamp:
min_value: 0
max_value: 100
- platform: template
name: "Roast #2 Percent Complete"
id: roast_2_percent_complete
lambda: return (id(temp_sensor).state);
update_interval: 5s
unit_of_measurement: "%"
icon: mdi:timer-sand
accuracy_decimals: 0
internal: true
filters:
- calibrate_linear:
method: least_squares
datapoints:
- 120 -> 0
- 234 -> 100
- clamp:
min_value: 0
max_value: 100
- platform: template
name: "Roast #3 Percent Complete"
id: roast_3_percent_complete
lambda: return (id(temp_sensor).state);
update_interval: 5s
unit_of_measurement: "%"
icon: mdi:timer-sand
accuracy_decimals: 0
internal: true
filters:
- calibrate_linear:
method: least_squares
datapoints:
- 120 -> 0
- 221 -> 100
- clamp:
min_value: 0
max_value: 100
text_sensor:
- platform: wifi_info
ip_address:
name: IP Address
- platform: template
name: Status
id: roast_status
icon: "mdi:card-text"
status_led:
pin:
number: GPIO2
inverted: false
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.