├── README.md └── anova_control.py /README.md: -------------------------------------------------------------------------------- 1 | # Anova_controller.py 2 | A Python CLI wrapper for the [pyanova-api](https://github.com/ammarzuberi/pyanova-api) module to interface with the [Anova](https://anovaculinary.com/) private API used by the [Anova Precision Cooker Pro](https://anovaculinary.com/anova-precision-cooker/pro/). Particularly intended for integrating an [Anova Precision Cooker Pro](https://anovaculinary.com/anova-precision-cooker/pro/) with [Home Assistant](https://www.home-assistant.io/). 3 | 4 | This may work with other [Anova](https://anovaculinary.com/) cookers but has only been tested with the [Anova Precision Cooker Pro](https://anovaculinary.com/anova-precision-cooker/pro/). 5 | 6 | # Acknowledgements 7 | This wouldn't have been possible without: 8 | 9 | [danodemano's](https://github.com/danodemano/) [anova_control.py](https://github.com/danodemano/anova.py/blob/master/anova_control.py) script which works with older Anova cooker models and provided a starting point plus a lot of the inspiration for this CLI wrapper 10 | 11 | [ammarzuberi's](https://github.com/ammarzuberi/) [pyanova-api](https://github.com/ammarzuberi/pyanova-api) Python module which this wrapper relies on to operate. 12 | 13 | # How to use it 14 | 15 | ## Prerequisites 16 | [pyanova-api](https://github.com/ammarzuberi/pyanova-api) must be installed for this script to work: 17 | 18 | ### Install pyanova-api Python module 19 | 20 | #### Quick install 21 | From the command line, run 22 | ```pip install pyanova-api``` 23 | 24 | #### Manual install 25 | - Clone the GitHub repository: 26 | ```git clone https://github.com/ammarzuberi/pyanova-api.git``` 27 | - Enter the newly created `pyanova-api` directory and run: 28 | ```pip install .``` 29 | 30 | #### Home Assistant Docker container install 31 | If running [Home Assistant](https://www.home-assistant.io/) within a [Docker](https://www.docker.com/) container, the above installation methods will see the [pyanova-api](https://github.com/ammarzuberi/pyanova-api) module removed on each reboot or upgrade of the container. 32 | 33 | To ensure the installation persists: 34 | 35 | - First, add the [hassio addons development](https://github.com/home-assistant/hassio-addons-development) repo to your HA installation 36 | - Install the 'Custom deps deployment' add-on 37 | - In the add-on configuration, add '- pyanova-api' under the 'pypi' heading 38 | ``` 39 | pypi: 40 | - pyanova-api 41 | ``` 42 | - Save the configuration 43 | - Start the add-on 44 | - Ensure the add-on is set to start on boot. 45 | 46 | ### Find your cooker_id 47 | You can find your cooker's device ID from the [Anova](https://anovaculinary.com/) mobile phone app: 48 | 49 | Profile > Settings cog > Cooker Details 50 | 51 | ### Obtain authentication credentials 52 | The [pyanova-api](https://github.com/ammarzuberi/pyanova-api) can not authenticate using Google, Facebook or Apple authentication tokens, you must sign up to anovaculincary.io by e-mail and connect the Anova Cooker to this account for this script to function as expected. 53 | 54 | ## Installation 55 | Once all pre-requisites are met, clone this repository or copy anova_control.py to the machine that has [pyanova-api](https://github.com/ammarzuberi/pyanova-api) installed. 56 | 57 | If using Home Assistant, cloning this repo into the \config\scripts folder is recommended or copy anova_control.py to \config\scripts\anova_control\anova_control.py. 58 | 59 | ## Script usage 60 | anova_control.py requires the following arguments to run: 61 | 62 | -o/--output [r/j/raw/json/c/cook] Set whether the output is a raw text string or JSON output of the cookers current state, or new cook settings applied to your Anova cooker! JSON output is recommended for Home Assistant sensor integration 63 | 64 | -i/--id/--cooker_id [Anova Device ID] 65 | 66 | -u/--username/-e/--email [email address used to login to anovaculinary.io account] 67 | 68 | -p/--password [password used to login to anovaculinary.io account] 69 | 70 | --time [(only required if o = c/cook) cook time in minutes] 71 | 72 | --temp [(only required if o = c/cook) cook temp in C] 73 | 74 | This will either output the full suite of information available from [pyanova-api](https://github.com/ammarzuberi/pyanova-api) in the format requested, plain text or JSON, or start/update a cook on your Anova cooker. 75 | 76 | ## Use with Home Assistant 77 | ### Command line sensor 78 | Create a [Home Assistant command line sensor](https://www.home-assistant.io/integrations/sensor.command_line/) to pull the JSON output into [Home Assistant](https://www.home-assistant.io/) as the entity 'sensor.anova_status': 79 | ``` 80 | platform: command_line 81 | command: 'python ./scripts/anova_control/anova_control.py -o j -i -u -p ' 82 | name: Anova Status 83 | json_attributes: 84 | - job_status 85 | - job_time_remaining 86 | - heater_duty_cycle 87 | - motor_duty_cycle 88 | - wifi_connected 89 | - wifi_ssid 90 | - water_leak 91 | - water_level_low 92 | - water_level_critical 93 | - heater_temp 94 | - triac_temp 95 | - water_temp 96 | value_template: '{{value_json.current_temp}}' 97 | scan_interval: 1 98 | ``` 99 | While the Anova cooker is not powered on, the status will be reported as 'unknown' in Home Assistant and only the 'friendly_name: Anova Status' attribute will be reported. Once the device is powered on, all attributes should be visible. 100 | 101 | ### Template Sensors 102 | The following template sensors pull the individual values from the command line sensor to read each value as a specific entity: 103 | ``` 104 | platform: template 105 | sensors: 106 | anova_job_status: 107 | friendly_name: 'Anova job status' 108 | value_template: 109 | '{{ states.sensor.anova_status.attributes.job_status }}' 110 | anova_job_time_remaining: 111 | friendly_name: 'Anova job time remaining' 112 | unit_of_measurement: 'min' 113 | value_template: 114 | '{{ states.sensor.anova_status.attributes.job_time_remaining/60 | round(0) }}' 115 | anova_heater_duty_cycle: 116 | friendly_name: 'Anova heater duty cycle' 117 | unit_of_measurement: '°C' 118 | value_template: 119 | '{{ states.sensor.anova_status.attributes.heater_duty_cycle }}' 120 | anova_motor_duty_cycle: 121 | friendly_name: 'Anova motor duty cycle' 122 | unit_of_measurement: '°C' 123 | value_template: 124 | '{{ states.sensor.anova_status.attributes.motor_duty_cycle }}' 125 | anova_wifi_connected: 126 | friendly_name: 'Anova wifi connected' 127 | value_template: 128 | '{{ states.sensor.anova_status.attributes.wifi_connected }}' 129 | anova_wifi_ssid: 130 | friendly_name: 'Anova wifi ssid' 131 | value_template: 132 | '{{ states.sensor.anova_status.attributes.wifi_ssid }}' 133 | anova_water_leak: 134 | friendly_name: 'Anova water leak' 135 | value_template: 136 | '{{ states.sensor.anova_status.attributes.water_leak }}' 137 | anova_water_level_low: 138 | friendly_name: 'Anova water level low' 139 | value_template: 140 | '{{ states.sensor.anova_status.attributes.water_level_low }}' 141 | anova_water_level_critical: 142 | friendly_name: 'Anova water level critical' 143 | value_template: 144 | '{{ states.sensor.anova_status.attributes.water_level_critical }}' 145 | anova_heater_temp: 146 | friendly_name: 'Anova heater_temp' 147 | unit_of_measurement: '°C' 148 | value_template: 149 | '{{ states.sensor.anova_status.attributes.heater_temp }}' 150 | anova_triac_temp: 151 | friendly_name: 'Anova triac temp' 152 | unit_of_measurement: '°C' 153 | value_template: 154 | '{{ states.sensor.anova_status.attributes.triac_temp }}' 155 | anova_water_temp: 156 | friendly_name: 'Anova water temp' 157 | unit_of_measurement: '°C' 158 | value_template: 159 | '{{ states.sensor.anova_status.attributes.water_temp }}' 160 | ``` 161 | 162 | # To Do 163 | Document other potential sensors to allow better integration into HA. -------------------------------------------------------------------------------- /anova_control.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | sys.path.append( '/config/deps/lib/python3.9/site-packages/anova/' ) 6 | import AnovaCooker 7 | import argparse 8 | import json 9 | 10 | #Check arguments function 11 | def check_arg(args=None): 12 | #Set up command line arguments 13 | parser = argparse.ArgumentParser(description='Script to control Anova cooker. For use with pyanova-api (https://github.com/ammarzuberi/pyanova-api)') 14 | parser.add_argument('-o', '--output', 15 | type=str, 16 | help='Output type, text string or JSON.', 17 | choices=['r', 'j', 'raw', 'json','c','cook'],) 18 | parser.add_argument('-i', '--id', '--cooker_id', 19 | type=str, 20 | help='Cooker ID, taken from Anova App.') 21 | parser.add_argument('-u', '--username', '-e', '--email', 22 | type=str, 23 | help='email address to authenticate Anova account.',) 24 | parser.add_argument('-p', '--password', 25 | type=str, 26 | help='Password to authenticate Anova account.',) 27 | parser.add_argument('--time', 28 | type=str, 29 | help='Cook time in minutes.',) 30 | parser.add_argument('--temp', 31 | type=str, 32 | help='Cook temp.',) 33 | results = parser.parse_args(args) 34 | return (results.output, 35 | results.id, 36 | results.username, 37 | results.password, 38 | results.time, 39 | results.temp) 40 | 41 | if __name__ == '__main__': 42 | if check_arg(sys.argv[1:]) is not None : 43 | try: 44 | #Connect to Anova, authenticate and collect cooker status 45 | o, i, u, p, time, temp = check_arg(sys.argv[1:]) 46 | cooker = AnovaCooker.AnovaCooker(i) 47 | cooker.authenticate(u, p) 48 | cooker.update_state 49 | if (o == "r") or (o == "raw") : 50 | #Return cooker status values in raw text from 51 | print('job_status = ', (cooker.job_status), ', job_time_remaining = ', (cooker.job_time_remaining), ', heater_duty_cycle = ', (cooker.heater_duty_cycle), \ 52 | ', motor_duty_cycle = ', (cooker.motor_duty_cycle), ', wifi_connected = ', (cooker.wifi_connected), ', wifi_ssid = ', (cooker.wifi_ssid), ', water_leak = ', (cooker.water_leak), \ 53 | ', water_level_low = ', (cooker.water_level_low), ', water_level_critical = ', (cooker.water_level_critical), ', heater_temp = ', (cooker.heater_temp), ', triac_temp = ', (cooker.triac_temp), \ 54 | ', water_temp = ', (cooker.water_temp)) 55 | elif (o == "j") or (o == "json") : 56 | #Build JSON array 57 | data = {} 58 | data['job_status'] = cooker.job_status 59 | data['job_time_remaining'] = cooker.job_time_remaining 60 | data['heater_duty_cycle'] = cooker.heater_duty_cycle 61 | data['motor_duty_cycle'] = cooker.motor_duty_cycle 62 | data['wifi_connected'] = cooker.wifi_connected 63 | data['wifi_ssid'] = cooker.wifi_ssid 64 | data['water_leak'] = cooker.water_leak 65 | data['water_level_low'] = cooker.water_level_low 66 | data['water_level_critical'] = cooker.water_level_critical 67 | data['heater_temp'] = cooker.heater_temp 68 | data['triac_temp'] = cooker.triac_temp 69 | data['water_temp'] = cooker.water_temp 70 | json_data = json.dumps(data) 71 | print(json_data) 72 | 73 | elif (o == "c") or (o == "cook") : 74 | cooker.cook = True 75 | cooker.cook_time = int(time) * 60 #convert cook time arg to minutes 76 | cooker.target_temp = float(temp) 77 | cooker.save() 78 | #Output the JSON 79 | except: 80 | #Build JSON array 81 | data = {} 82 | data['job_status'] = 'unknown' 83 | data['job_time_remaining'] = '0' 84 | data['heater_duty_cycle'] = '0' 85 | data['motor_duty_cycle'] = '0' 86 | data['wifi_connected'] = 'unknown' 87 | data['wifi_ssid'] = 'unknown' 88 | data['water_leak'] = 'unknown' 89 | data['water_level_low'] = 'unknown' 90 | data['water_level_critical'] = 'unknown' 91 | data['heater_temp'] = '0' 92 | data['triac_temp'] = '0' 93 | data['water_temp'] = '0' 94 | json_data = json.dumps(data) 95 | 96 | #Output the JSON 97 | print(json_data) 98 | 99 | else: 100 | parser=argparse.ArgumentParser('Script to control Anova cooker. For use with pyanova-api (https://github.com/ammarzuberi/pyanova-api)') 101 | parser.print_help(sys.stderr) --------------------------------------------------------------------------------