albertogeniola / MerossIot

Async Python library for controlling Meross devices
https://albertogeniola.github.io/MerossIot/
MIT License
489 stars 89 forks source link

cannot reuse already awaited coroutine #372

Closed MaierJuerg closed 6 months ago

MaierJuerg commented 7 months ago

using python3.10 and meross-iot 0.4.7.0
My python task checks a number of input values and turns on/off a fan.
The sequence of commands is as follows, each command is in a try/except section but none triggers.

http_api_client = await MerossHttpClient.async_from_user_password(
    email=EMAIL,
    password=MEROSS,
    api_base_url="https://iotx-eu.meross.com")
manager = MerossManager(http_client=http_api_client)
await manager.async_device_discovery()
await dev.async_update()
await dev.async_turn_off(channel=0)
manager.close()
await http_api_client.async_logout()

This usually works fine bot sometimes i get this exception:
Task failed, msg=Task exception was never retrieved, exception=cannot reuse already awaited coroutine
how can I avoid getting this message?

albertogeniola commented 7 months ago

Hi! it looks like the code you posted is invalid... For instance, there is no dev declaration: you are invoking dev.async_update() without declaring a dev variable.

I guess you did not post the entire code listing... can you post the whole code that gives you the exception? Please also include a complete stack trace with the specific error.

MaierJuerg commented 7 months ago

Hi Alberto Thanks for your reply. I loop over all devices and call dev.async_update() with reference to the device? I have attached my code but it is partially in german and includes more than just on/off. the imported config module contains some shared variable and functions the appendValues module persists the data into csv-files As for the complete stack trace I have no idea how to create that If it of any help I could try to make a minimal example that uses no modules besides you meross code. Regards, Jürg

Am Di., 30. Apr. 2024 um 09:04 Uhr schrieb Alberto Geniola < @.***>:

Hi! it looks like the code you posted is invalid... For instance, there is no dev declaration: you are invoking dev.async_update() without declaring a dev variable.

I guess you did not post the entire code listing... can you post the whole code that gives you the exception? Please also include a complete stack trace with the specific error.

— Reply to this email directly, view it on GitHub https://github.com/albertogeniola/MerossIot/issues/372#issuecomment-2084528919, or unsubscribe https://github.com/notifications/unsubscribe-auth/BGXJ63KSFU25WQY6X35442DY747BXAVCNFSM6AAAAABG7UPTT6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAOBUGUZDQOJRHE . You are receiving this because you authored the thread.Message ID: @.***>

-- Jürg Maier Trungerstrasse 33 9543 St. Margarethen Schweiz @.***

MaierJuerg commented 7 months ago

Alberto created this more straight forward example. It usually runs trough but I sometimes get this error errTurnOn.txt scratch_1.txt

albertogeniola commented 7 months ago

I confirm there is still a race condition in the last version that might cause the issue you are experiencing. I've released a new beta version that attempts to fix that. Can you please try this version and report if working as intended?

You can update your library version with the following command: pip install meross-iot==0.4.7.2b1

MaierJuerg commented 7 months ago

updated to your latest version 0.4.7.2b1 and gave it a try. first run was successful, second run:

ERROR:Task exception was never retrieved future: <Task finished name='Task-18' coro=<delayed_execution() done, defined at /home/keller/.local/lib/python3.10/site-packages/meross_iot/manager.py:1159> exception=RuntimeError('cannot reuse already awaited coroutine')> Traceback (most recent call last): File "/home/keller/.local/lib/python3.10/site-packages/meross_iot/manager.py", line 1161, in delayed_execution await coro RuntimeError: cannot reuse already awaited coroutine

Am Di., 30. Apr. 2024 um 21:55 Uhr schrieb Alberto Geniola < @.***>:

I confirm there is still a race condition in the last version that might cause the issue you are experiencing. I've released a new beta version that attempts to fix that. Can you please try this version and report if working as intended?

You can update your library version with the following command: pip install meross-iot==0.4.7.2b1

— Reply to this email directly, view it on GitHub https://github.com/albertogeniola/MerossIot/issues/372#issuecomment-2086953519, or unsubscribe https://github.com/notifications/unsubscribe-auth/BGXJ63NERXAQTK4FX4D773TY77ZL7AVCNFSM6AAAAABG7UPTT6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAOBWHE2TGNJRHE . You are receiving this because you authored the thread.Message ID: @.***>

-- Jürg Maier Trungerstrasse 33 9543 St. Margarethen Schweiz @.***

albertogeniola commented 7 months ago

So I am having hard time to replicate your issue. According to the logs, it seems you are "re-connecting" to the MerossCloud just before closing the connection. When this happens, the reconnection handler schedules a discovery update of all devices; however at the same time the manager tells the system to "close everything" and to cancel the update. There is an edge case (race condition) that arises when the scheduled (delayed) update executes after the manager has invoked the cancel() method on the scheduled coroutine.

To further investigate, I need your "test code" that triggers the race condition. Can you post the minimal python examples that "sometimes" fails?

albertogeniola commented 7 months ago

In the meantime, I'm releasing a newer beta, v0.4.7.2.b2. That should better address the issue. I would still love to have the code example to test. in any case.

As soon as the pipeline completes, you should find a new beta version and thus you can update the library and test again.

Let me know!

MaierJuerg commented 7 months ago

I have already sent you the code but it is as a .txt file because I can not send a .py file.

Am Mi., 1. Mai 2024 um 11:06 Uhr schrieb Alberto Geniola < @.***>:

In the meantime, I'm releasing a newer beta, v0.4.7.2.b2. That should better address the issue. I would still love to have the code example to test. in any case.

As soon as the pipeline completes https://github.com/albertogeniola/MerossIot/actions/runs/8907514136, you should find a new beta version https://pypi.org/project/meross-iot/0.4.7.2b2/ and thus you can update the library and test again.

Let me know!

— Reply to this email directly, view it on GitHub https://github.com/albertogeniola/MerossIot/issues/372#issuecomment-2088184876, or unsubscribe https://github.com/notifications/unsubscribe-auth/BGXJ63KZHHKX47M4KJ3RTQTZACWCFAVCNFSM6AAAAABG7UPTT6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAOBYGE4DIOBXGY . You are receiving this because you authored the thread.Message ID: @.***>

-- Jürg Maier Trungerstrasse 33 9543 St. Margarethen Schweiz @.***

/usr/bin/python3.10 /home/keller/.config/JetBrains/PyCharm2023.3/scratches/scratch_1.py INFO:Login successful against https://iot.meross.com INFO:

------- Triggering Manager Discovery, filter_device: [None] ------- INFO:Allocating new mqtt client for mqtt-eu-4.meross.com:443... INFO:Subscribed to topics, scheduling state update for already known devices. INFO:

------- Triggering Manager Discovery, filter_device: [None] ------- INFO:Allocating new mqtt client for mqtt-eu-5.meross.com:443... INFO:Subscribed to topics, scheduling state update for already known devices. INFO:

------- Triggering Manager Discovery, filter_device: [None] ------- INFO:Device Solar (#BASE:2205066058372251080148e1e991c7d0) has been already added to the registry. INFO:Allocating new mqtt client for mqtt-eu-2.meross.com:443... INFO:Device Warmwasser (#BASE:2205062451391251080148e1e991cfb1) has been already added to the registry. INFO:Subscribed to topics, scheduling state update for already known devices. INFO:

------- Triggering Manager Discovery, filter_device: [None] ------- INFO:Device pcjm (#BASE:2104170345833890849948e1e96d19e3) has been already added to the registry. INFO:Fetch and update done INFO: ------- Manager Discovery ended -------

WARNING:Found a new device pcjm (mss310, HW 6.0.0, FW 6.3.23, class: mss310:6.0.0:6.3.23) that has become online while we were offline. WARNING:Found a new device Lüfter Wärmetauscher (mss310, HW 6.0.0, FW 6.3.23, class: mss310:6.0.0:6.3.23) that has become online while we were offline. WARNING:Updating status for device Solar (mss310, HW 6.0.0, FW 6.3.23, class: mss310:6.0.0:6.3.23) INFO:Device Lüfter Wärmetauscher (#BASE:2205063954567451080148e1e991cf31) has been already added to the registry. INFO:Device Lüfter Wärmetauscher (#BASE:2205063954567451080148e1e991cf31) has been already added to the registry. INFO:Fetch and update done INFO: ------- Manager Discovery ended -------

WARNING:Found a new device Solar (mss310, HW 6.0.0, FW 6.3.23, class: mss310:6.0.0:6.3.23) that has become online while we were offline. WARNING:Found a new device Unterstand (mss310, HW 6.0.0, FW 6.3.23, class: mss310:6.0.0:6.3.23) that has become online while we were offline. WARNING:Found a new device Warmwasser (mss310, HW 6.0.0, FW 6.3.23, class: mss310:6.0.0:6.3.23) that has become online while we were offline. WARNING:Found a new device pcjm (mss310, HW 6.0.0, FW 6.3.23, class: mss310:6.0.0:6.3.23) that has become online while we were offline. WARNING:Found a new device Lüfter Wärmetauscher (mss310, HW 6.0.0, FW 6.3.23, class: mss310:6.0.0:6.3.23) that has become online while we were offline. INFO:Device Warmwasser (#BASE:2205062451391251080148e1e991cfb1) has been already added to the registry. INFO:Device Unterstand (#BASE:2104170168841390849948e1e96d1fc0) has been already added to the registry. INFO:Fetch and update done INFO: ------- Manager Discovery ended -------

INFO:Device pcjm (#BASE:2104170345833890849948e1e96d19e3) has been already added to the registry. INFO:Fetch and update done INFO: ------- Manager Discovery ended -------

WARNING:Found a new device Warmwasser (mss310, HW 6.0.0, FW 6.3.23, class: mss310:6.0.0:6.3.23) that has become online while we were offline. WARNING:Found a new device pcjm (mss310, HW 6.0.0, FW 6.3.23, class: mss310:6.0.0:6.3.23) that has become online while we were offline. WARNING:Found a new device Lüfter Wärmetauscher (mss310, HW 6.0.0, FW 6.3.23, class: mss310:6.0.0:6.3.23) that has become online while we were offline. WARNING:Updating status for device Solar (mss310, HW 6.0.0, FW 6.3.23, class: mss310:6.0.0:6.3.23) isOn=1 already on INFO:Manager stop requested. INFO:Disconnection detected. Reason: 0 INFO:Disconnection detected. Reason: 0 INFO:Disconnection detected. Reason: 0 ERROR:Task exception was never retrieved future: <Task finished name='Task-20' coro=<delayed_execution() done, defined at /home/keller/.local/lib/python3.10/site-packages/meross_iot/manager.py:1163> exception=RuntimeError('cannot reuse already awaited coroutine')> Traceback (most recent call last): File "/home/keller/.local/lib/python3.10/site-packages/meross_iot/manager.py", line 1165, in delayed_execution await coro RuntimeError: cannot reuse already awaited coroutine INFO:Logout succeeded.

Process finished with exit code 0

albertogeniola commented 7 months ago

Hi @MaierJuerg ,

maybe I'm missing something, but it looks like you have posted the logs, not the python code.

scratch_1.txt looks like the following: image

errTurnOn.txt looks like this: image

I believe you updated the same file twice :-)

Can you please doublecheck? Moreover, have you tried v0.4.7.2.b2?

MaierJuerg commented 7 months ago

Hi Alberto Sorry for the confusion I am creating. Must have been a copy paste error. Tried now your version 0.4.7.2b3 and the error still exists although it looks to happen fewer times. Here the code I am using:

import asyncio
import os

from meross_iot.controller.mixins.electricity import ElectricityMixin
from meross_iot.http_api import MerossHttpClient
from meross_iot.manager import MerossManager
from meross_iot.model.enums import Namespace

EMAIL = ***@***.***' #os.environ.get('MEROSS_EMAIL') or
"YOUR_MEROSS_CLOUD_EMAIL"
PASSWORD = "***"

async def main():
    # Setup the HTTP client API from user-password
    http_api_client = await
MerossHttpClient.async_from_user_password(email=EMAIL,
password=PASSWORD, api_base_url="https://iot.meross.com")

    # Setup and start the device manager
    manager = MerossManager(http_client=http_api_client)
    await manager.async_init()

    # Retrieve all the devices that implement the electricity mixin
    await manager.async_device_discovery()
    devs = manager.find_devices(device_class=ElectricityMixin)

    if len(devs) < 1:
        print("No electricity-capable device found...")
    else:
        for dev in devs:
            if dev.name == "Lüfter Wärmetauscher":

                # Update device status: this is needed only the very
first time we play with this device (or if the
                #  connection goes down)
                await dev.async_update()

                # get current status
                response_all = await manager.async_execute_cmd(
                    destination_device_uuid=dev.uuid,
                    method="GET",
                    namespace=Namespace.SYSTEM_ALL,
                    payload={},
                    mqtt_hostname=dev.mqtt_host,
                    mqtt_port=dev.mqtt_port)
                isOn = response_all['all']['digest']['togglex'][0]['onoff']
                print(f"{isOn=}")

                if isOn:
                    print("already on")
                else:
                    print(f"trying to turn on")
                    try:
                        await dev.async_turn_on(channel=0)
                    except Exception as e:
                        print(f"fehler await async_turn_on: {e}")

                    response_all = await manager.async_execute_cmd(
                        destination_device_uuid=dev.uuid,
                        method="GET",
                        namespace=Namespace.SYSTEM_ALL,
                        payload={},
                        mqtt_hostname=dev.mqtt_host,
                        mqtt_port=dev.mqtt_port)
                    isOn = response_all['all']['digest']['togglex'][0]['onoff']
                    print(f"{isOn=}")

                    if isOn:
                        print("successfully turned on")

    # Close the manager and logout from http_api
    manager.close()
    await http_api_client.async_logout()

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
    loop.stop()
albertogeniola commented 7 months ago

Hi @MaierJuerg ,

Looking at your code, I don't understand a number of things. I'm not sure there is a correlation between the way you are coding and the issue you are getting.

Let me ask you the following:

  1. Why are you manually invoking manager.async_execute_cmd() to get info about the onOff status? Why don't you use the is_on() method on the device?
  2. Why are you invoking again the same method after issuing the async_turn_on() method on the device? This should not be needed.

I would rewrite the code as follows:

import asyncio
import os

from meross_iot.controller.mixins.electricity import ElectricityMixin
from meross_iot.http_api import MerossHttpClient
from meross_iot.manager import MerossManager
from meross_iot.model.enums import Namespace, OnlineStatus

EMAIL = os.environ.get('MEROSS_EMAIL')
PASSWORD = os.environ.get('MEROSS_PASSWORD')
DEV_NAME = "Lüfter Wärmetauscher"

async def main():
    # Setup the HTTP client API from user-password
    http_api_client = await MerossHttpClient.async_from_user_password(email=EMAIL, password=PASSWORD, api_base_url="https://iot.meross.com")

    # Setup and start the device manager
    manager = MerossManager(http_client=http_api_client)
    await manager.async_init()

    # Retrieve all the devices that implement the electricity mixin
    await manager.async_device_discovery()
    devs = manager.find_devices(device_name=DEV_NAME, online_status=OnlineStatus.ONLINE)
    if len(devs)<1:
        raise Exception(f"Could not find device {DEV_NAME} or it's not online.")
    dev=devs[0]

    # Update the initial status of the device
    await dev.async_update()

    # Check if the device is on
    dev_is_on = dev.is_on()
    if dev_is_on:
        print(f"Device {DEV_NAME} is already on, nothing to do!")
    else:
        print(f"Turning {DEV_NAME} on...")
        # If this method does not raise an exception, we can assume the device has been turned on.
        await dev.async_turn_on(channel=0)

        # ------- THE FOLLOWING IS REDUNDANT AND YOU MIGHT NOT NEED IT ----------
        # If you really want to make a full check to be extra sure it is on (although is not really necessary in my
        # opinion, you just have to re-issue the update)
        print(f"Forcing a full update for device {DEV_NAME}...")
        await dev.async_update()
        dev_is_on = dev.is_on()
        print(f"Device {DEV_NAME} is: {'on' if dev_is_on else 'off'}")
        # ------- END OF REDUNDANT STATE CHECK ----------

        if dev_is_on:
            print("Turn on operation was successful")

    # Close the manager and logout from http_api
    print("Closing the connection and logging out...")
    manager.close()
    await http_api_client.async_logout()
    print("All done!")

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
    loop.stop()

In any case, I tried to execute the code as you posted, but I was unable to trigger the error (I tried 20 times in a row).

MaierJuerg commented 7 months ago

Hi Alberto Thanks for looking at my code and revise it. I wasn't aware of the dev.is_on() function but will use it in the future for sure to simplify my code. The wish to check the is_on after running turn_on came because I got these unexpected exceptions and wanted to know in what status the device finally is. I will gladly adapt your suggestions into my code and hope my problems are solved with this.

Now, not being aware of the is_on() function, is there in your opinion also a way to get either hourly consumption values for a day or the current total consumption within a day? If not, I might give Tasmota a try. I had tried to achieve that with a tuya device but got stuck with permission denied for that feature. Have a good time and thanks for all the work you have invested into this very useful library. Regards Juerg

Am Sa., 4. Mai 2024 um 12:41 Uhr schrieb Alberto Geniola < @.***>:

Hi @MaierJuerg https://github.com/MaierJuerg ,

Looking at your code, I don't understand a number of things. I'm not sure there is a correlation between the way you are coding and the issue you are getting.

Let me ask you the following:

  1. Why are you manually invoking manager.async_execute_cmd() to get info about the onOff status? Why don't you use the is_on() method on the device?
  2. Why are you invoking again the same method after issuing the async_turn_on() method on the device? This should not be needed.

I would rewrite the code as follows:

import asyncioimport os from meross_iot.controller.mixins.electricity import ElectricityMixinfrom meross_iot.http_api import MerossHttpClientfrom meross_iot.manager import MerossManagerfrom meross_iot.model.enums import Namespace, OnlineStatus EMAIL = os.environ.get('MEROSS_EMAIL')PASSWORD = os.environ.get('MEROSS_PASSWORD')DEV_NAME = "Lüfter Wärmetauscher" async def main():

Setup the HTTP client API from user-password

http_api_client = await MerossHttpClient.async_from_user_password(email=EMAIL, password=PASSWORD, api_base_url="https://iot.meross.com")

# Setup and start the device manager
manager = MerossManager(http_client=http_api_client)
await manager.async_init()

# Retrieve all the devices that implement the electricity mixin
await manager.async_device_discovery()
devs = manager.find_devices(device_name=DEV_NAME, online_status=OnlineStatus.ONLINE)
if len(devs)<1:
    raise Exception(f"Could not find device {DEV_NAME} or it's not online.")
dev=devs[0]

# Update the initial status of the device
await dev.async_update()

# Check if the device is on
dev_is_on = dev.is_on()
if dev_is_on:
    print(f"Device {DEV_NAME} is already on, nothing to do!")
else:
    print(f"Turning {DEV_NAME} on...")
    # If this method does not raise an exception, we can assume the device has been turned on.
    await dev.async_turn_on(channel=0)

    # ------- THE FOLLOWING IS REDUNDANT AND YOU MIGHT NOT NEED IT ----------
    # If you really want to make a full check to be extra sure it is on (although is not really necessary in my
    # opinion, you just have to re-issue the update)
    print(f"Forcing a full update for device {DEV_NAME}...")
    await dev.async_update()
    dev_is_on = dev.is_on()
    print(f"Device {DEV_NAME} is: {'on' if dev_is_on else 'off'}")
    # ------- END OF REDUNDANT STATE CHECK ----------

    if dev_is_on:
        print("Turn on operation was successful")

# Close the manager and logout from http_api
print("Closing the connection and logging out...")
manager.close()
await http_api_client.async_logout()
print("All done!")

if name == 'main': loop = asyncio.get_event_loop() loop.run_until_complete(main()) loop.stop()

In any case, I tried to execute the code as you posted, but I was unable to trigger the error (I tried 20 times in a row).

— Reply to this email directly, view it on GitHub https://github.com/albertogeniola/MerossIot/issues/372#issuecomment-2094115054, or unsubscribe https://github.com/notifications/unsubscribe-auth/BGXJ63MS7JUGBPSRSJF7WODZAS3OLAVCNFSM6AAAAABG7UPTT6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAOJUGEYTKMBVGQ . You are receiving this because you were mentioned.Message ID: @.***>

-- Jürg Maier Trungerstrasse 33 9543 St. Margarethen Schweiz @.***

albertogeniola commented 6 months ago

Hi @MaierJuerg ,

sure, you can get the total daily power consumption by calling this method.

Let me know if everything works out now :)

MaierJuerg commented 6 months ago

many thanks Alberto, that provides what I was looking for. Juerg

Am Mo., 6. Mai 2024 um 14:13 Uhr schrieb Alberto Geniola < @.***>:

Hi @MaierJuerg https://github.com/MaierJuerg ,

sure, you can get the total daily power consumption by calling this method https://albertogeniola.github.io/MerossIot/api-reference/controller/mixins/consumption.html#consumptionxmixin .

Let me know if everything works out now :)

— Reply to this email directly, view it on GitHub https://github.com/albertogeniola/MerossIot/issues/372#issuecomment-2095875880, or unsubscribe https://github.com/notifications/unsubscribe-auth/BGXJ63MF33RLGRAYU4HVD2TZA5XXBAVCNFSM6AAAAABG7UPTT6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAOJVHA3TKOBYGA . You are receiving this because you were mentioned.Message ID: @.***>

-- Jürg Maier Trungerstrasse 33 9543 St. Margarethen Schweiz @.***