This has been a long project with some reverse engineering but it’s finally done! I have created a way for Home Assistant to control my Anova cooker using their undocumented API. My config is based around this, though there is a PR pending for additional functionality: https://github.com/bmedicke/anova.py
Anyway, make sure you have either my version loaded or that the PR is accepted. Once you do that you need the script located here: https://github.com/danodemano/anova.py/blob/master/anova_control.py
Then you need 3 shell commands:
anova_start: '/scripts/anova_control.py -m start -t "{{ states.input_number.anova_temp.state | int }}" -d "{{ states.input_number.anova_duration.state | int }}"'
anova_stop: '/scripts/anova_control.py -m stop'
anova_icebath: '/scripts/anova_control.py -m icebath'
And a CLI sensor:
- platform: command_line
command: /scripts/anova_control.py -m status -o h
name: Anova Status
json_attributes:
- current_temp
- is_running
- is_timer_running
- speaker_mode
- target_temp
- temp_unit
- timer_length
- alarm_active
- job_type
- job_stage
- duration
- job_start_time
value_template: '{{value_json.current_temp}}'
scan_interval: 15
And some template sensors:
anova_is_timer_running:
friendly_name: 'Is Timer Running'
value_template: '{{ states.sensor.anova_status.attributes.is_timer_running }}'
anova_job_type:
friendly_name: 'Job Type'
value_template: '{{ states.sensor.anova_status.attributes.job_type }}'
anova_job_stage:
friendly_name: 'Job Stage'
value_template: '{{ states.sensor.anova_status.attributes.job_stage }}'
anova_timer_length:
friendly_name: 'Cook Time Remaining'
value_template: '{{ (states.sensor.anova_status.attributes.timer_length/60) | round(0) }}'
unit_of_measurement: 'min'
anova_job_start_time:
friendly_name: 'Job Start Time'
value_template: '{{ as_timestamp(states.sensor.anova_status.attributes.job_start_time) | timestamp_custom("%x %X") }}'
anova_is_running:
friendly_name: 'Is Running'
value_template: '{{ states.sensor.anova_status.attributes.is_running }}'
anova_duration:
friendly_name: 'Cook Duration'
value_template: '{{ (states.sensor.anova_status.attributes.duration/60) | round(0) }}'
unit_of_measurement: 'min'
anova_alarm_active:
friendly_name: 'Alarm Active'
value_template: '{{ states.sensor.anova_status.attributes.alarm_active }}'
anova_target_temp:
friendly_name: 'Target Temp'
value_template: '{{ states.sensor.anova_status.attributes.target_temp }}'
unit_of_measurement: '°F'
anova_speaker_mode:
friendly_name: 'Speaker Mode'
value_template: '{{ states.sensor.anova_status.attributes.speaker_mode }}'
anova_current_temp:
friendly_name: 'Current Temp'
value_template: '{{ states.sensor.anova_status.attributes.current_temp }}'
unit_of_measurement: '°F'
anova_temp_unit:
friendly_name: 'Temp Unit'
value_template: '{{ states.sensor.anova_status.attributes.temp_unit }}'
And binary template sensors:
anova_is_timer_running:
friendly_name: 'Is Timer Running'
value_template: >-
{%- if is_state("sensor.anova_is_timer_running", "True") -%}
True
{%- elif is_state("sensor.anova_is_timer_running", "False") -%}
False
#This accounts for no state if cooker is offline
{%- else -%}
False
{%- endif -%}
entity_id: sensor.anova_is_timer_running
anova_is_running:
friendly_name: 'Is Running'
value_template: >-
{%- if is_state("sensor.anova_is_running", "True") -%}
True
{%- elif is_state("sensor.anova_is_running", "False") -%}
False
#This accounts for no state if cooker is offline
{%- else -%}
False
{%- endif -%}
entity_id: sensor.anova_is_running
anova_speaker_mode:
friendly_name: 'Speaker Mode'
value_template: >-
{%- if is_state("sensor.anova_speaker_mode", "True") -%}
True
{%- elif is_state("sensor.anova_speaker_mode", "False") -%}
False
#This accounts for no state if cooker is offline
{%- else -%}
True
{%- endif -%}
entity_id: sensor.anova_speaker_mode
anova_alarm_active:
friendly_name: 'Alarm Active'
value_template: >-
{%- if is_state("sensor.anova_alarm_active", "True") -%}
True
{%- elif is_state("sensor.anova_alarm_active", "False") -%}
False
#This accounts for no state if cooker is offline
{%- else -%}
False
{%- endif -%}
entity_id: sensor.anova_alarm_active
I customized the entities and used a custom card but you are left with this:
And expanded:
Then running:
Hope that all makes sense! You will need to get your cooker ID and secret for this to work. Enjoy!!!