de-cloud and offline control for legacy roomba 960 - 2025-12 - REST integration to homeassistant
Hi,
I open this issue because I used your code for free my Roomba 960 from cloud and gain offline control. I found various difficulties that where partially discussed in the issues but not really addressed. So In hope i can help someone, I wanted to share my progress so far here, until I maybe make a own project blog about it.
Also, because of the News that iRobot got bancrupt and is transferring control to PICEA Robotics in Shenzhen, I think there may be other people is wanting to take offline control of their roomba now, bevore we Risk some bad news. (Cloud dependency of Hardware is BAD, there is a lot of examples where good hardware stopped working because the Cloud service got shut down inexpectly, I don't wanted run into this risk) https://www.forbes.com/sites/greatspeculations/2025/12/16/irobot-bankruptcy-how-a-broken-growth-story-crushed-irbt-stock/
My Problem:
- My Roomba 960 is from 2018 and got its last Firmware update to 2.4.17-138 on 2023.08.17.
- With this comes that it has no updated SSL certifificate.
- Homeasssistant integration doesn't work anymore because of this and there seems nobody working on a fix.
- Roomba980-Pyton seems to have problems with the expired SSL cert.
My Objective:
- Remove any cloud connection from my Smarthome devices in general
- Keep maximum control of my Roomba 980 (Remote control and status, scheduling, map functions (optional)
- Keep all of this as integration in Homeassistant, not standalone
- Make things secure (operational and cybersecurity)
My workaround:
- Installed a Linux Container with Roomba-980
- make the connection work, ignoring the expired SSL cert.
- got the roomba's password and stored it in password manager
- set up a trusted SSL connection trough nxinx-proxy-manager so i don't have to deal with untrusted selfsigned SSL on homeassistant
- made a custom homeassistant config for integration
How:
- made an
openssl.conffile on the linux server with Roomba-960
openssl_conf = openssl_init
[openssl_init]
ssl_conf = ssl_sect
[ssl_sect]
system_default = system_default_sect
[system_default_sect]
Options = UnsafeLegacyRenegotiation
CipherString = DEFAULT@SECLEVEL=1
- set a variable to load the config
OPENSSL_CONF="/home/user/Roomba980-Python/roomba/openssl.cnf"
got password with ./password.py
- installed a service to run Roomba-980 automatically with webport
nano /etc/systemd/system/roomba.service
[Unit]
Description=Local Roomba Pyton Service
After=network.target
[Service]
Environment="OPENSSL_CONF=/home/user/Roomba980-Python/roomba/openssl.cnf"
Type=simple
ExecStart=/usr/bin/python3 /home/user/Roomba980-Python/roomba/roomba.py -wp 8080
Restart=always
User=user
WorkingDirectory=/home/user/Roomba980-Python/roomba
[Install]
WantedBy=multi-user.target
systemctl enable roomba systemctl start roomba systemctl status roomba`
- firewall settings
-
gave a static dhcp IP to roomba
-
blocked Internet access on gw-router for the roomba ip
-
blocked local access from smartphone VLAN to IoT VLAN (optional) i wanted this so the app cannot mess up the robot with firmware updates (in chase the new owner would release some that would break local netẃork access) planned to uninstall the app anyways on end of the project
-
with linux firewall limited access to the Linux Container where the Roomba-080 pyton runs
-
only allowed from my notebook and from homeassistant IP (for hardening)
-
nginx-proxy-manager roomba.mydomain.local redirect to roomba-pyton-host:8080
-
homeassistant
configuration.yamlcreating entries for manual roomba integration
# REST Commands for Roomba commands
rest_command:
roomba_start:
url: "https://roomba.my.domain.local/api/local/action/start"
method: GET
timeout: 10
headers:
User-Agent: "Home Assistant"
Accept: "*/*"
roomba_stop:
url: "https://roomba.my.domain.local/api/local/action/stop"
method: GET
timeout: 10
headers:
User-Agent: "Home Assistant"
Accept: "*/*"
roomba_pause:
url: "https://roomba.my.domain.local/api/local/action/pause"
method: GET
timeout: 10
headers:
User-Agent: "Home Assistant"
Accept: "*/*"
roomba_resume:
url: "https://roomba.my.domain.local/api/local/action/resume"
method: GET
timeout: 10
headers:
User-Agent: "Home Assistant"
Accept: "*/*"
roomba_dock:
url: "https://roomba.my.domain.local/api/local/action/dock"
method: GET
timeout: 10
headers:
User-Agent: "Home Assistant"
Accept: "*/*"
# REST Sensor for Status
rest:
- resource: "https://roomba.my.domain.local/api/local/info/state"
scan_interval: 30
sensor:
- name: "roomba State"
value_template: "{{ value_json.state.reported.cleanMissionStatus.phase }}"
json_attributes_path: "$.state.reported"
json_attributes:
- batPct
- bin
- cleanMissionStatus
- name
- dock
# Template Vacuum
template:
- vacuum:
- name: "roomba"
unique_id: roomba_vacuum
# Status
state: >
{% set phase = state_attr('sensor.roomba_state', 'cleanMissionStatus') %}
{% if phase %}
{% set phase_name = phase.phase %}
{% if phase_name == 'run' %}
cleaning
{% elif phase_name == 'charge' %}
docked
{% elif phase_name == 'stop' %}
idle
{% elif phase_name == 'hmUsrDock' %}
returning
{% else %}
idle
{% endif %}
{% else %}
idle
{% endif %}
# Battery
battery_level: >
{% set bat = state_attr('sensor.roomba_state', 'batPct') %}
{{ bat if bat else 0 }}
# Actions
start:
service: rest_command.roomba_start
stop:
service: rest_command.roomba_stop
pause:
service: rest_command.roomba_pause
return_to_base:
service: rest_command.roomba_dock
All this information is provided AS IS , is still work in progress and I give no support, because is a relatively complex setup. But I hope it can help someone with similar issues (I will also link to homeassistant Issue). Who knows if i get a clean setup and some further documentation time, maybe I give a more clean instruction, unfortunally I am no developer so I can not do coding contribuition to this project.
I thank all the people that contribuited this solution and all other I used, it helped me free my Roomba.
YAY Roomba is a free robot now 🧦 🤖