The vacuum only runs when the chairs are up
A ball tilt switch in a cheap Zigbee door sensor tells Home Assistant the chairs are up, so the robot vacuum only runs when we're out and the floor is clear.
- Difficulty
- Easy
- Parts cost
- About A$3 of tilt switches, plus a Zigbee door sensor per chair (mine came in a pack for about A$39)
- Build time
- An evening
- Skills
- Soldering, Home Assistant
The problem
I wanted the robot vacuum to run by itself while we’re out. My partner’s rule was simple: it only runs by itself when the chairs are up, flipped onto the bench, so it isn’t working round chair legs. We only put the chairs up when we want a vacuum, so the chairs going up is the request. All I needed was a way for Home Assistant to know a chair was upside down.
How it works
A ball tilt switch is a tiny metal can with a ball inside. Tip it one way and the ball rolls onto two contacts and closes the circuit. Tip it the other way and it opens. Inside a cheap Zigbee door sensor there’s a tiny three-legged chip, a hall-effect sensor, that pulls a signal line to ground when the door magnet comes close. I soldered the tilt switch from that signal line to ground, so the tilt switch does the magnet’s job. Now the sensor thinks “door closed” whenever the chair is upside down.
I set it closed when the chair is upside down and open when it’s upright. That way the switch sits open all day while people are sitting on the chair, and there’s nothing to chatter or flicker as someone shifts about.
Home Assistant then waits for two things together: everyone’s phones saying they’re away, and the chairs up for a couple of minutes. Then it starts the vacuum, once a day at most, inside a time window. When the vacuum docks again it sends a reminder to put the chairs back down, and when the kids get home with the chairs still up, their phones get told too.
I looked at Zigbee tilt and vibration sensors first. Most of the cheap ones only report “vibration”, not which way up they are. The few that do give angles cost more and are more complicated than this needs. A door sensor plus a few cents of tilt switch is simpler. It’s the same trick as my mailbox sensor: a cheap door sensor makes a handy battery Zigbee radio for anything that can close a contact.
What you need
- Ball tilt switches, SW-520D or SW-200D type. They come in packs: https://www.aliexpress.com/item/4000540584730.html
- A cheap Zigbee door/window sensor for each chair you want to watch: https://www.aliexpress.com/item/1005012129523396.html
- Home Assistant with a working Zigbee setup. I use Zigbee2MQTT.
- A robot vacuum that Home Assistant can start.
- The Home Assistant phone app on everyone’s phone, so Home Assistant knows when you’re all out.
- VHB tape (thick double-sided foam tape) to stick the sensor under the chair.
- A soldering iron and a multimeter.
The plan is two sensors: one on a bar stool and one on a dining chair. With our rule (chairs only go up when we want a vacuum), two is plenty. You don’t need one on every chair. I’ve built one so far, and the config below shows two so you can see how to add more.
Wiring
There are only two connections:
| Tilt switch | Door sensor board |
|---|---|
| Leg 1 | The hall sensor’s output (signal) leg |
| Leg 2 | The hall sensor’s ground leg |
The hall sensor is a three-legged surface-mount part: supply, ground and output. When a magnet is near, it pulls the output to ground, and the board reads that as “closed”. The tilt switch, wired from the output to ground, does exactly the same thing, and a ball tilt switch is just a contact, so it doesn’t matter which way round its legs go. My boards (marked ZB-SENSOR V2.0) have it marked Q1. Meter the legs on yours first: the output is the one that drops to 0 V when you hold a magnet near. Some sensors use a little glass reed switch instead. If yours does, solder across its two pads; it works the same way.
Fix the tilt switch so it can’t move against the sensor. If it can wobble, its idea of “upside down” drifts. Inside the sensor case if it fits, otherwise glued to the outside.
Firmware and config
There’s no firmware to write. The door sensor pairs like any other door sensor. Name it something like
“Chair tilt” in Zigbee2MQTT and you’ll get binary_sensor.chair_tilt_contact.
A door sensor says off when it’s closed. Here closed means the chair is up, which reads backwards. So I
made a template sensor that says on when the chairs are up. It also deals with the sensor going offline:
a flat battery or a dropped Zigbee link shows as unavailable, which is not off, so the template reads
not up. If Home Assistant can’t hear the sensor, the vacuum doesn’t run.
# configuration.yaml (or a template: package)
template:
- binary_sensor:
- name: "Chair up"
unique_id: chair_up
icon: mdi:chair-rolling
# Each sensor reads "off" (closed) when its chair is upside down.
# unavailable / unknown are not "off", so an offline sensor = chair NOT up.
state: >
{{ is_state('binary_sensor.chair_tilt_contact', 'off')
and is_state('binary_sensor.stool_tilt_contact', 'off') }}
Only one chair? Delete the second line and the and.
Then the automation. zone.home holds a count of the people at home, so "0" means everyone’s phones
say they’re out.
# automations.yaml
- id: vacuum_when_chairs_up
alias: "Vacuum when everyone is out and the chairs are up"
mode: single
triggers:
# Everyone has been away for 5 minutes (stops a phone's GPS blip starting it)
- trigger: state
entity_id: zone.home
to: "0"
for: "00:05:00"
# Everyone left before the window opened
- trigger: time
at: "09:00:00"
conditions:
- condition: state
entity_id: zone.home
state: "0"
- condition: state
entity_id: binary_sensor.chair_up
state: "on"
for: "00:02:00"
- condition: time
after: "09:00:00"
before: "15:00:00"
# Docked means it's not already running and not stuck on an error
- condition: state
entity_id: vacuum.robot_vacuum
state: docked
# Not already run by this automation today
- condition: template
value_template: >
{{ this.attributes.last_triggered is none
or this.attributes.last_triggered < today_at() }}
actions:
- action: vacuum.start
target:
entity_id: vacuum.robot_vacuum
Pick a time window that suits your house. Keep both ends: a window with no end will happily start the vacuum at 11 at night.
If you’d rather name the people, swap the zone.home condition for this. It passes only when none of
them is home:
- condition: not
conditions:
- condition: state
entity_id: person.example
state: home
- condition: state
entity_id: person.example_2
state: home
Either way, Home Assistant only knows about people whose phones run the app. A visitor sitting on the couch counts as nobody.
And the reminder. It watches the vacuum itself, not the automation, so it also works when someone starts a clean from the vacuum’s own app:
- id: chairs_down_reminder
alias: "Remind to put the chairs down"
mode: single
triggers:
- trigger: state
entity_id: vacuum.robot_vacuum
to: docked
for: "00:02:00"
not_from:
- unavailable
- unknown
conditions:
- condition: state
entity_id: binary_sensor.chair_up
state: "on"
actions:
- action: notify.mobile_app_example_phone
data:
title: "Vacuum's done"
message: "The floor's clean. The chairs can come down."
data:
tag: chairs_down # a second reminder replaces the first instead of stacking up
That reminder is the obvious one, and it’s the one that didn’t work. To an adult, chairs upside down on the bench mean “put me back”. To our kids, they’re just something to walk past. I’d get home to eight chairs with their legs in the air, and my partner, busy with the baby, wasn’t about to do all eight. So the reminder that actually matters goes to the kids. When one of them gets home and the chairs are still up, their own phone tells them:
- id: chairs_down_when_kids_get_home
alias: "Chairs down: tell the kids when they get home"
mode: single
triggers:
- trigger: state
entity_id: person.example_kid
to: home
conditions:
- condition: state
entity_id: binary_sensor.chair_up
state: "on"
actions:
- action: notify.mobile_app_example_kid
data:
title: "Chairs!"
message: "The vacuum's been. Chairs down please, before Dad gets home."
Make a copy for each kid, with their own person and their own phone. That way it only goes to whoever just
walked in, and nobody gets nagged about chairs while they’re still at school.
Step by step
- Meter-check a tilt switch. Set the meter to continuity, hold the switch one way then the other, and work out which way up it closes.
- Open the door sensor, find the hall sensor’s output and ground legs (or the reed switch’s two pads, if it has one) and solder the tilt switch across them.
- Hold the sensor the way it will sit under the chair. With the meter or the Zigbee2MQTT page open, check it reads open with the chair upright and closed with the chair upside down. Get this right now. It’s a pain once it’s taped on.
- Pair the sensor and name it (for example “Chair tilt”).
- Stick it under the seat with VHB tape, somewhere you can still get the battery out.
- Add the template sensor and check
binary_sensor.chair_upfollows the chair as you flip it. - Add the two automations.
- Test it dry (below) before you let it start the vacuum for real.
Testing it
- Never start the vacuum to test this. Test the logic, not the robot.
- Flip the chair up and down and watch
binary_sensor.chair_upin Developer tools → States. - Check the fail-safe: with the chair up, use Developer tools → States to set
binary_sensor.chair_tilt_contacttounavailable.chair_upshould drop tooff. (This only changes what Home Assistant shows; the sensor’s next report puts it back.) - For the automation, swap
vacuum.startfor a phone notification that says “would have started the vacuum”. Leave it like that for a few days. Every day the chairs go up and everyone goes out, you should get exactly one message, inside the window. - Open the automation’s Traces to see which condition stopped it on the days it didn’t fire.
- When the dry run looks right, put
vacuum.startback and be home to watch the first real run.
Lessons learnt
“Every chair is up” is true when there are no chairs. My first plan checked that every chair sensor read “up”. With no sensors paired yet, that check has nothing to look at, and “all of nothing” counts as true. The vacuum would have run straight into the chair legs. So name each sensor in the template, treat offline as “not up”, and don’t switch the automation on until every sensor is paired and reading properly.
The reminder didn’t come when the vacuum was started from its app. My first reminder only fired after runs that Home Assistant had started. One day the clean was started from the vacuum’s own app with the chair up, and nobody was told to put it down. The reminder now watches the vacuum, not the automation, so any clean with the chairs up gets one.
A mid-job stop can send the reminder twice. A mopping vacuum goes back to wash its mop partway
through a job. If that break runs past two minutes, the reminder goes out early and again at the end. The
tag on the notification makes the second one replace the first instead of stacking up.