ESPHome / Home Assistant Cat Feeder
by random_nerd in Circuits > Microcontrollers
7900 Views, 10 Favorites, 0 Comments
ESPHome / Home Assistant Cat Feeder
I bought an automatic pet feeder from Amazon Link to Amazon.
Model Number: spf-1010-ty.
it's a generic feeder that dispenses about half an egg-cup of food per "portion".
It uses Tuya as the internet connection method, so I decided to convert it to ESPHome and run some custom firmware to remove Tuya, and connect it to Home Assistant.
The pet feeder now runs without cloud connection. By connecting it with Home Assistant, I can command Alexa or Google Home to dispense food, or set up a schedule to automaticly feed the pet.
You'll need ESPHome and Home Assistant installed for this project, and assumes that you have experience with both applications.
For nerds, here's a link to the ESP device on the PCB:
Supplies
- 6" pz2 screwdriver
- pz1 screwdriver
- Serial converter, e.g. a USB to Serial converter
- ESP Home: https://esphome.io/
- ESP Flasher: https://github.com/esphome/esphome-flasher/releas...
- Home Assistant: https://www.home-assistant.io/
Dissasembly
Remove the small screw and pull off the stirring wand.
Turn over the feeder, and remove the 3 screws.
Remove the 3 screws holding down the PCB, remove PCB.
Wiggle and pull out the cables from the PCB.
Connecting the USB to Serial Converter
Any USB to Serial converter can be used. It's helpful if the device has a 3 volt output as this can be used to power the board during programming.
!! MAKE SURE THE POWER SUPPLY IS 3 VOLTS !!
- Connect 4 wires as shown to the PCB and then to a Serial Converter board:
- Connect the TX on the device (see photo) to RX on the Serial converter.
- Connect the RX on the device (see photo) to TX on the Serial converter.
- Connect 3 Volts to the PCB, (from the Serial Converter or an external power source), but don't turn on power yet.
- Connect Gnd to the Serial Converter and the PCB
Compiling the Code
Within ESPHome, create new project. I called my project "spf-1010-ty-pet-feeder"
Paste in the YAML from the attached file, change the WIFI credentials and IP address to something suitable for your network.
Remember to setup the following details in your secrets file:
- wifi_ssid - your wifi network name
- wifi_password - your wifi password
- ota_pwd - a password to protect OTA updates
api_pwd - only required if Home Assistant uses a password for ESPHome API access.
Compile the code by clicking the 'upload' button.
This will try to connect to the device over WIFI and will fail. This is perfectly ok at this stage.
substitutions: devicename: spf-1010-ty-pet-feeder friendly_name: Small Cat Feeder esphome: name: '${devicename}' platform: ESP8266 board: esp_wroom_02 wifi: ssid: !secret wifi_ssid password: !secret wifi_password power_save_mode: LIGHT # use_address: spf-1010-ty-pet-feeder # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: $devicename password: !secret ap_pwd captive_portal: # Enable logging logger: level: WARN baud_rate: 0 # disable logging over uart # Enable Home Assistant API api: # password: !secret api_pwd ota: password: !secret ota_pwd #------------------------------------------------------------------------------- ################################################################################ # NOTES: # Motor driver chip is a TC118S. # INA INB MOTOR # L L Hi-Z # L H Left # H L right # H H Brake #------------------------------------------------------------------------------- ################################################################################ sensor: - platform: wifi_signal update_interval: 30s id: rssi_sensor #------------------------------------------------------------------------------- - platform: template name: "${friendly_name} Wifi" unit_of_measurement: "%" accuracy_decimals: 0 icon: "mdi:wifi" update_interval: 30s lambda: |- // Taken from https://github.com/tzapu/WiFiManager/blob/master/... int quality; const int rssi = id(rssi_sensor).state; if(rssi <= -100){quality = 0;} else if (rssi >= -50){quality = 100;} else{quality = 2 * (rssi + 100);} return quality; #------------------------------------------------------------------------------- ################################################################################ text_sensor: - platform: template name: "${friendly_name} State" update_interval: 1s lambda: |- if( id(run_motor).is_running() ) { return {"Running"}; } else if( id(flash_pos_switch_error).is_running() ) { return {"Jammed"}; } else return {"Idle"}; #------------------------------------------------------------------------------- ################################################################################ switch: - platform: gpio # Motor driver pin: GPIO4 id: motor_drive_a restore_mode: ALWAYS_OFF #------------------------------------------------------------------------------- - platform: gpio # Motor driver pin: GPIO5 id: motor_drive_b restore_mode: ALWAYS_OFF #------------------------------------------------------------------------------- - platform: gpio # RED led (D3) pin: GPIO16 id: led_red inverted: true restore_mode: ALWAYS_OFF #------------------------------------------------------------------------------- - platform: gpio # BLUE led (D1) pin: GPIO14 id: led_blue inverted: true restore_mode: ALWAYS_OFF #------------------------------------------------------------------------------- ################################################################################ button: - platform: template # Home assistant control id: ha_run name: "${friendly_name} Run" icon: "mdi:cat" on_press: - logger.log: format: "ha_run on_press. Calling script run motor." level: DEBUG # Do not run if the product is jammed. - if: condition: - script.is_running: flash_pos_switch_error then: - logger.log: format: "ha_run on_press. Cannot run: Jammed." level: ERROR else: - script.execute: run_motor # Run the motor. #------------------------------------------------------------------------------- - platform: template # Home assistant control id: ha_run_even_when_jammed name: "${friendly_name} Run even when jammed" icon: "mdi:alert" on_press: - logger.log: format: "ha_run_even_when_jammed on_press. Calling script run motor." level: DEBUG - script.execute: run_motor # Run the motor. #------------------------------------------------------------------------------- ################################################################################ binary_sensor: - platform: gpio # Physical button on unit pin: number: GPIO0 id: sw_user_btn filters: - invert: - delayed_on_off: 25ms on_press: then: - logger.log: format: "sw_user_btn pressed. Calling script run motor." level: DEBUG - script.execute: run_motor # Run the motor. #------------------------------------------------------------------------------- - platform: template id: is_motor_running on_press: then: - switch.turn_on: motor_drive_a # Turn on motor. - script.execute: flash_motor_running_led - logger.log: format: "is_motor_running - go." level: DEBUG on_release: then: - switch.turn_off: motor_drive_a # Turn off motor. - script.stop: flash_motor_running_led - switch.turn_off: led_blue - logger.log: format: "is_motor_running - stop." level: DEBUG #------------------------------------------------------------------------------- - platform: gpio pin: number: GPIO13 id: sw_motor_position filters: - delayed_on_off: 25ms on_release: then: - logger.log: format: "Position switch released - stopping motor." level: DEBUG # Stop the motor. - binary_sensor.template.publish: id: is_motor_running state: OFF # The position switch is ok, clear any error. - script.stop: flash_pos_switch_error - switch.turn_on: led_red #------------------------------------------------------------------------------- - platform: gpio pin: number: GPIO2 id: unknown_usage #------------------------------------------------------------------------------- ################################################################################ script: - id: motor_overrun_protection # A script to stop the motor if it runs too long mode: restart then: - delay: 5000ms # If the motor is still running, an error occurred (position switch faulty or jammed) - if: condition: - binary_sensor.is_on: is_motor_running then: # Stop the motor. - binary_sensor.template.publish: id: is_motor_running state: OFF # Flash an error and log an error message. - script.execute: flash_pos_switch_error - logger.log: format: "Motor overrun!! Check motor position switch or clear jam!" level: ERROR #------------------------------------------------------------------------------- - id: run_motor # A script that runs the motor. # This script will be stopped by the motor position switch. # If this script runs for > 4 secs, the motors will be stopped and a error logged. mode: queued then: - logger.log: format: "run_motor script begin." level: DEBUG - binary_sensor.template.publish: id: is_motor_running state: ON - script.execute: motor_overrun_protection # Script will restart if it's already running. # Do nothing until the position switch turns off the motor. - wait_until: condition: - binary_sensor.is_off: is_motor_running - logger.log: format: "run_motor script complete." level: DEBUG #------------------------------------------------------------------------------- - id: flash_motor_running_led mode: restart then: while: condition: lambda: |- return true; then: - switch.turn_on: led_blue - delay: 250ms - switch.turn_off: led_blue - delay: 250ms #------------------------------------------------------------------------------- - id: flash_pos_switch_error mode: restart then: while: condition: lambda: |- return true; then: - switch.turn_on: led_red - delay: 150ms - switch.turn_off: led_red - delay: 150ms #------------------------------------------------------------------------------- - id: flash_wifi_error mode: restart then: - script.wait: flash_pos_switch_error # position sensor error takes priority over this script. - while: condition: lambda: |- return true; then: - switch.turn_on: led_red - delay: 500ms - switch.turn_off: led_red - delay: 500ms #------------------------------------------------------------------------------- ################################################################################ interval: # Check wifi is connected. - interval: 2s then: - script.wait: flash_pos_switch_error # position sensor error takes priority over this script. - if: condition: wifi.connected: then: - script.stop: flash_wifi_error - if: condition: switch.is_off: led_red then: - switch.turn_on: led_red else: - script.execute: flash_wifi_error #-------------------------------------------------------------------------------
Downloads
Programming of the PCB for the 1st Time
Next we must program the PCB. This is done with the USB to Serial converter. Once this has been done successfully, further future updates can be done wirelessly, Over the Air!
- Download and run https://github.com/esphome/esphome-flasher/release....
- Navigate to the output directory, and copy the *.bin file.
For my install, the output bin file is:
\spf-1010-ty-pet-feeder\.pioenvs\spf-1010-ty-pet-feeder\firmware.bin
This can be found by looking at the compile output (see photo). - Press and hold the button on the PCB and at the same time, apply power to the board.
Remember, 3 Volts only. - Keep holding the button, and start the programming process from ESPHome Flasher.
Once the board has started programming, and you see progress in the ESPHome Flasher application output window, you can (optionally) release the push button.
If ESPHome Flasher displays an error when trying to connect to the device, try swapping over TX and RX and try again.
After successful programming, disconnect and re-apply power.
You should see the red LED light up as follows:
- Flashing: Attempting to connect to WIFI.
- Solid on: Connected to WIFI.
If the LED remains flashing for more than 10 seconds, check the WIFI settings in the YAML code is correct for your network, modify, build, and reprogram as described above.
When the red LED is solid on, the next stage is to program the device Over The Air (OTA).
Programming the PCB - Over the Air (OTA)
In the ESPHome IDE, click the upload button. This will now attempt to program the board over the air, via WIFI.
If the upload was successful (see photo), then you can reprogram the board at any time wirelessly, so there's no need for a physical connection to the board.
You can disconnect the USB to Serial converter and reassemble the feeder.
Using the Feeder
The operation of the feeder is as follows:
LEDs:
- RED:
- Flashing slowly - Connecting to WIFI
- Solid - Connected to WIFI
- Flashing fast - feeder is jammed or the motor position switch is broken
- BLUE:
- Flashing - Dispensing food.
Button Operation:
Press the button to dispense one portion of food. Multiple presses will dispense multiple portions.
If the feeder is jammed (red LED flashing fast), try pressing the button once to see if the feeder un-jams. If not, you'll have to clear it out manually.
Don't try to run the feeder if it is jammed; you'll damage the gears or burn out the electronics.
If the product is showing as jammed, but is still dispensing food, then the motor position switch is broken and should be replaced.
Home Assistant - ESPHome Integration
The feeder will add an ESPHome device into the Configuration -> Devices screen in Home Assistant:
It is named $device_name as per the YAML. Feel free to change the $device_name in the YAML to suit your use-case.
For the images below,$device_name was 'spf-1010-ty-pet-feeder'.
By clicking 'entities', we can see there are 3 entities created. They are prefixed with the $friendly_name from the YAML. Feel free to change the $friendly_name in the YAML to suit your use-case.
For the images below, the friendly name was 'Small Pet Feeder'.
Entities:
- Small Pet Feeder Run - This is a switch, it dispenses one portion and will automatically turn off once that portion has been dispensed.
- Small Pet Feeder Run even when jammed - This is run the feeder, even if it's in the Jammed state. Use with caution.
Small Pet Feeder Wifi- The WIFI signal strength.
Small Pet Feeder State - The status of the device, this can be one of the following:
- Idle - Not running
- Running - Food is being dispensed
- Jammed - The feeder is jammed.
If the status is jammed, the feeder will no longer respond to run switch presses from home assistant. You must unjam the feeder and press the physical button on the unit or power cycle the device to allow Home Assistant to run the feeder again. This stops automatic feeding from Home Assistant causing damage to the device, or even burning up the electronics, potentially causing a fire.
Home Assistant - Scripting
The best way to operate the device is by running a script in Home Assistant. This allows correct timing to be achieved and allows the script to be called by Automations, Alexa, Google Home or other Home Assistant Integrations.
To run the device and dispense a 'single portion', the following steps must occur:
- Trigger the 'Run' switch.
- Wait for the 'Run' switch to turn of.
- Wait another second before any retriggering (e.g. for extra portions etc)
Multiple portions can then be dispensed by calling the 'single portion' script multiple times!
Home Assistant - Scripting - Single Portion
Here's an example of the 'single portion' script. Note the script is "Queued", allowing it to be called multiple times for multiple portions (this script is set to max of 10, change as required).
Click on the images to see the configuration. Home Assistant YAML:
feed_the_cat_one_portion: sequence: - type: turn_on entity_id: switch.small_cat_feeder_run domain: switch - wait_for_trigger: - platform: device type: turned_off entity_id: switch.small_cat_feeder_run domain: switch for: hours: 0 minutes: 0 seconds: 1 milliseconds: 0 continue_on_timeout: false timeout: 00:00:10 mode: queued icon: mdi:cat alias: Feed the cat one portion max: 10
Home Assistant - Scripting - Multiple Portions
Here's an example of the 'multiple portions' script.
This script simply calls the 'single portion' script multiple times. This is useful so you can create a script for each number of portions required, e.g. breakfast (3 portions), lunch (1 portion), dinner (2 portions), evening snack, midnight feast etc.. and each script simply calls the 'one portion' script the required number of times.
The individual scripts can then be called from Alexa, etc. "Alexa, feed the cat lunch", or from other triggers in Home Assistant.
Click on the images to see the configuration. Home Assistant YAML:
(this script will dispense 2 portions)
feed_the_cat_two_portions: sequence: - repeat: count: '2' sequence: - service: script.feed_the_cat_one_portion mode: queued icon: mdi:cat alias: Feed the cat two portions max: 10
Home Assistant - Automations
Home Assistant can be configured to automaticly call the previously configured scripts. This is great for feeding your pet automaticly.
Click on the images to view the automation setup.
This automation feeds my cat twice a day, at 5:30am and 4:30pm, two portions per meal.
Home Assistant YAML:
- alias: Feed the cat (breakfast and dinner) description: 2 portions trigger: - platform: time at: 05:30 - platform: time at: '16:30' condition: [] action: - repeat: count: '2' sequence: - service: script.feed_the_cat_one_portion mode: single
And this automation gives my cat a midnight snack (one portion):
- alias: Feed the cat (midnight snack) description: 1 portion trigger: - platform: time at: '00:30' condition: [] action: - repeat: count: '1' sequence: - service: script.feed_the_cat_one_portion mode: single<br>
Home Assistant - Running, Even If Jammed (!)
If the feeder jams, Home Assistant will no longer be able to dispense food when the "Small Pet Feeder Run" button is pressed, so don't rely on this if you're away from home. The chances of the product jamming are small, but possible.
If you want to try and run the feeder, even when jammed, use the "Small Pet Feeder Run even when jammed" button.
A better way would be to set up home assistant to alert you if the status changed to 'Jammed'; then call a friend to go round your house and fix it, but it's up to you.
Final Thoughts
This is a relatively cheap pet feeder. It's available rebranded by many different companies so should be obtainable in your own country.
The software YAML is simple to understand and doesn't do anything that complex, so it should be easy for you to customise the code to what you'd like.
I have mine connected to Alexa via Home Assistant, but mainly use it to automaticly dispense food for the cat twice a day, and it's been working now for a few months (May 2021) reliably enough.
For those that are adventurous, the ESP device has an ADC input that's not used. By rigging up a 2.2k and 10k resistor to produce a maximum 1 Volt input to the ADC, you could monitor the voltage of the battery backup.
Let me know if you manage to do this, that would be cool.
All the best, happy hacking!