NickWaterton / Roomba980-Python

Python program and library to control iRobot Roomba 980 Vacuum Cleaner
MIT License
361 stars 103 forks source link

Cannot (really) control Roomba 980 #128

Open Rafsisa opened 1 month ago

Rafsisa commented 1 month ago

Hi!

I'm working with the current script:

import asyncio
from roomba import Roomba

async def test():
        await roomba.connect()
        roomba.send_command('start')
        await asyncio.sleep(5)
        roomba.send_command('stop')
        roomba.send_command('dock')
        await roomba.disconnect()

roomba = Roomba('172.17.23.104')
loop = asyncio.get_event_loop()
loop.run_until_complete(test())

This indeed starts the Roomba 980 at 172.17.23.104, but that's the last thing that works. The vacuum never stops or docks as it should. The output of this script is as follows:

Pending event queue size is: 1
Pending event queue size is: 1
/home/user/src/Roomba980-Python/roomba/roomba.py:607: RuntimeWarning: coroutine 'Roomba._disconnect' was never awaited
  self.loop.create_task(self._disconnect())
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
Task exception was never retrieved
future: <Task finished name='Task-26' coro=<Roomba._disconnect() done, defined at /home/user/src/Roomba980-Python/roomba/roomba.py:609> exception=AttributeError("type object '_asyncio.Task' has no attribute 'all_tasks'")>
Traceback (most recent call last):
  File "/home/user/src/Roomba980-Python/roomba/roomba.py", line 612, in _disconnect
    tasks = [t for t in asyncio.Task.all_tasks() if t is not asyncio.Task.current_task()]
                        ^^^^^^^^^^^^^^^^^^^^^^
AttributeError: type object '_asyncio.Task' has no attribute 'all_tasks'
Traceback (most recent call last):
  File "/home/user/src/Roomba980-Python/roomba/./test.py", line 16, in <module>
    loop.run_until_complete(test())
  File "/usr/lib/python3.12/asyncio/base_events.py", line 687, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/home/user/src/Roomba980-Python/roomba/./test.py", line 12, in test
    await roomba.disconnect()
TypeError: object NoneType can't be used in 'await' expression
NickWaterton commented 1 month ago

You don’t seem to know how asyncio works.

roomba.connect() and roomba.disconnect() are not async methods.

Rafsisa commented 1 month ago

Sorry, I was a bit exhausted then...

I'm now using this wrapper class:

import asyncio
import roomba as wrapped

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
lruc = loop.run_until_complete

class Roomba980:
    def __init__(self, *args, **kwargs):
        self._roomba = wrapped.Roomba(*args, **kwargs)
    def connect(self):
        return lruc(self._connect_async())
    def disconnect(self):
        return self._roomba.disconnect()
    def start(self):
        lruc(self._command_async('start', 15))
    def stop(self):
        lruc(self._command_async('stop'))
    def dock(self):
        lruc(self._command_async('dock'))
    @property
    def phase(self):
        return self._get_prop('phase')
    @property
    def current_state(self):
        return self._get_prop('current_state')
    @property
    def sku(self):
        return self._get_prop('sku')
    async def _connect_async(self):
        return await self._roomba.connect()
    async def _command_async(self, cmd, sleep=5):
        self._roomba.send_command(cmd)
        await asyncio.sleep(sleep)
    def _get_prop(self, name):
        return self._roomba.get_property(name)

roomba = Roomba980('172.17.23.104')
roomba.connect()
print('connected')

roomba.start()
print('started')
roomba.stop()
print('stopped')
roomba.dock()
print('docked')

print('phase        : %r'%roomba.phase)
print('current_state: %r'%roomba.current_state)
print('sku          : %r'%roomba.sku)

roomba.disconnect()
print('disconnected')

The properties (phase, current_state and sku) are None when run without the start-stop-dock code. Is that the case with your Roomba too? Also sometimes the connection is lost after successfully stopping of the Roomba (printing 'Unexpected Disconnect! - reconnecting') and docking is omitted. In some cases, this still succeeds after the 'Unexpected Disconnect'. How can I handle or even fix that?

Besides that, since I'm running Python 3.12.3 I had to modify roomba.py:

diff --git a/roomba/roomba.py b/roomba/roomba.py
index 09da3bf..4a6c021 100755
--- a/roomba/roomba.py
+++ b/roomba/roomba.py
@@ -609,7 +609,7 @@ class Roomba(object):
     async def _disconnect(self):
         #if self.ws:
         #    await self.ws.cancel()
-        tasks = [t for t in asyncio.Task.all_tasks() if t is not asyncio.Task.current_task()]
+        tasks = [t for t in asyncio.all_tasks(asyncio.get_event_loop()) if t is not asyncio.current_task()]
         [task.cancel() for task in tasks]
         self.log.info("Cancelling {} outstanding tasks".format(len(tasks)))
         await asyncio.gather(*tasks, return_exceptions=True)

Thank you for maintainig this module!