Skip to content

Commit

Permalink
Merge branch '0.4.X.X' into dependabot/pip/coverage-7.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
albertogeniola authored Dec 30, 2022
2 parents 882e37e + 3c58eaf commit 8bfe9ce
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 71 deletions.
16 changes: 10 additions & 6 deletions meross_iot/controller/mixins/diffuser_light.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ async def async_handle_update(self, namespace: Namespace, data: dict) -> bool:
_LOGGER.debug(f"Handling {self.__class__.__name__} mixin data update.")
locally_handled = False
if namespace == Namespace.SYSTEM_ALL:
diffuser_data = data.get('all', {}).get('digest', {}).get('diffuser', {}).get('light',[])
diffuser_data = data.get('all', {}).get('digest', {}).get('diffuser', {}).get('light', [])
for l in diffuser_data:
channel = l['channel']
self._channel_diffuser_light_status[channel] = l
Expand All @@ -88,12 +88,14 @@ def get_light_rgb_color(self, channel=0, *args, **kwargs) -> Optional[RgbTuple]:
:return: a Tuple containing three integer 8bits values (red, green, blue)
"""
self.check_full_update_done()
info = self._channel_diffuser_light_status.get(channel,{}).get('rgb')
info = self._channel_diffuser_light_status.get(channel, {}).get('rgb')
if info is None:
return None
return int_to_rgb(info)

async def async_set_light_mode(self, channel: int = 0, onoff: bool = None, mode: DiffuserLightMode = None, brightness: int = None, rgb: Optional[RgbTuple] = None, timeout: Optional[float] = None, *args, **kwargs) -> None:
async def async_set_light_mode(self, channel: int = 0, onoff: bool = None, mode: DiffuserLightMode = None,
brightness: int = None, rgb: Optional[RgbTuple] = None,
timeout: Optional[float] = None, *args, **kwargs) -> None:
"""
Sets the light mode for this device.
:param channel: channel to configure
Expand All @@ -119,10 +121,12 @@ async def async_set_light_mode(self, channel: int = 0, onoff: bool = None, mode:
payload=payload,
timeout=timeout)
# Immediately update local state
self._channel_diffuser_light_status[channel].update(light_payload)
if channel not in self._channel_diffuser_light_status:
self._channel_diffuser_light_status[channel] = {}

self._channel_diffuser_light_status[channel].update(light_payload)

def get_light_is_on(self, channel=0, *args, **kwargs) -> Optional[bool]:
def get_light_is_on(self, channel: int = 0, *args, **kwargs) -> Optional[bool]:
"""
Returns True if the light is ON, False otherwise.
Expand All @@ -131,7 +135,7 @@ def get_light_is_on(self, channel=0, *args, **kwargs) -> Optional[bool]:
:return: current onoff state
"""
self.check_full_update_done()
onoff = self._channel_diffuser_light_status.get(channel,{}).get('onoff')
onoff = self._channel_diffuser_light_status.get(channel, {}).get('onoff')
if onoff is None:
return None
return onoff == 1
Expand Down
2 changes: 1 addition & 1 deletion meross_iot/controller/mixins/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ async def async_update(self,
**kwargs) -> None:
# call the superclass implementation first
await super().async_update(*args, **kwargs)
await self.async_get_runtime_info()
await self.async_update_runtime_info()
14 changes: 10 additions & 4 deletions meross_iot/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -620,11 +620,17 @@ def _on_message(self, client, userdata, msg):
_LOGGER.debug("Found a pending command waiting for response message")
if message_method == "ERROR":
err = CommandError(error_payload=message.get('payload'))
self._loop.call_soon_threadsafe(_handle_future, future, None, err)
if not self._loop.is_closed():
self._loop.call_soon_threadsafe(_handle_future, future, None, err)
else:
_LOGGER.warning("Could not return message %s to caller as the event loop has been closed already", message)
elif message_method in ("SETACK", "GETACK"):
self._loop.call_soon_threadsafe(
_handle_future, future, message, None
) # future.set_exception
if not self._loop.is_closed():
self._loop.call_soon_threadsafe(
_handle_future, future, message, None
) # future.set_exception
else:
_LOGGER.warning("Could not return message %s to caller as the event loop has been closed already", message)
else:
_LOGGER.error(
f"Unhandled message method {message_method}. Please report it to the developer."
Expand Down
4 changes: 2 additions & 2 deletions meross_iot/utilities/conversion.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Union
from typing import Union, Tuple


def rgb_to_int(rgb: Union[tuple, dict, int]) -> int:
Expand All @@ -20,7 +20,7 @@ def rgb_to_int(rgb: Union[tuple, dict, int]) -> int:
return r+g+b


def int_to_rgb(rgb: int) -> tuple[int,int,int]:
def int_to_rgb(rgb: int) -> Tuple[int, int, int]:
red = (rgb & 16711680) >> 16
green = (rgb & 65280) >> 8
blue = (rgb & 255)
Expand Down
50 changes: 5 additions & 45 deletions tests/test_diffuser.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import random
from random import randint
from typing import Union

Expand Down Expand Up @@ -45,20 +46,20 @@ async def test_light_rgb(self):
print(f"Testing device: {light.name}...")
await light.async_update()

await light.async_set_mode(mode=DiffuserLightMode.FIXED_RGB)
await light.async_set_light_mode(mode=DiffuserLightMode.FIXED_RGB)

# Set a random color
r = randint(0, 256)
g = randint(0, 256)
b = randint(0, 256)
await light.async_set_light_color(rgb=(r, g, b), onoff=True)
await light.async_set_light_mode(rgb=(r, g, b), onoff=True)

# Check the color property returns red
color = light.get_rgb_color()
color = light.get_light_rgb_color()
self.assertEqual(color, (r, g, b))

@unittest_run_loop
async def test_turn_on_off(self):
async def test_turn_light_on_off(self):
if len(self.light_devices) < 1:
self.skipTest("Could not find any DiffuserLightMixin within the given set of devices. "
"The test will be skipped.")
Expand Down Expand Up @@ -118,46 +119,6 @@ async def test_light_brightness(self):
await asyncio.sleep(0.5)
self.assertEqual(light.get_light_brightness(), i)

@unittest_run_loop
async def test_light_rgb_push_notification(self):
if len(self.light_devices) < 1:
self.skipTest("Could not find any DiffuserLightMixin within the given set of devices. "
"The test will be skipped.")

light: Union[BaseDevice, DiffuserLightMixin] = self.light_devices[0]
print(f"Selected test device: {light.name}.")

# Create a new manager
new_meross_client, requires_logout = await async_get_client()
m = None
try:
# Retrieve the same device with another manager
m = MerossManager(http_client=new_meross_client)
await m.async_init()
await m.async_device_discovery()
devs = m.find_devices(device_uuids=(light.uuid,))
if len(devs) < 1:
self.skipTest("Could not find dev for push notification")
return
dev = devs[0]

# Set RGB color to known state
r = await light.async_set_light_mode(rgb=(255, 0, 0), onoff=True)
await asyncio.sleep(2)

# Turn on the device
r = await light.async_set_light_mode(rgb=(0, 255, 0), onoff=True)

# Wait a bit and make sure the other manager received the push notification
await asyncio.sleep(10)
self.assertEqual(light.get_light_rgb_color(), (0, 255, 0))
self.assertEqual(dev.get_light_rgb_color(), (0, 255, 0))
finally:
if m is not None:
m.close()
if requires_logout:
await new_meross_client.async_logout()

@unittest_run_loop
async def test_spray_mode(self):
if len(self.spray_devices) < 1:
Expand All @@ -177,7 +138,6 @@ async def test_spray_mode(self):
await spray.async_set_spray_mode(DiffuserSprayMode.OFF)
self.assertEqual(spray.get_current_spray_mode(), DiffuserSprayMode.OFF)


async def tearDownAsync(self):
if self.requires_logout:
await self.meross_client.async_logout()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ async def test_subdevice_listing(self):
@unittest_run_loop
async def test_bad_login(self):
with self.assertRaises(BadLoginException):
return await MerossHttpClient.async_from_user_password(email="wronguser@anythin.com",
return await MerossHttpClient.async_from_user_password(email="albertogeniola@gmail.com",
password="thisIzWRONG!")

@unittest_run_loop
Expand Down
30 changes: 18 additions & 12 deletions tests/test_roller_shutter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@

if os.name == 'nt':
import asyncio

asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
else:
import asyncio


class TestRollerShutter(AioHTTPTestCase):
test_device: Union[RollerShutterTimerMixin, BaseDevice, None]

async def get_application(self):
return web.Application()

Expand All @@ -32,7 +34,8 @@ async def setUpAsync(self):
self.meross_manager = MerossManager(http_client=self.meross_client)
await self.meross_manager.async_init()
devices = await self.meross_manager.async_device_discovery()
roller_devices = self.meross_manager.find_devices(device_class=RollerShutterTimerMixin, online_status=OnlineStatus.ONLINE)
roller_devices = self.meross_manager.find_devices(device_class=RollerShutterTimerMixin,
online_status=OnlineStatus.ONLINE)

if len(roller_devices) < 1:
self.test_device = None
Expand All @@ -53,11 +56,12 @@ async def test_open(self):
state_idle = asyncio.Event()
position_opened = asyncio.Event()
position_closed = asyncio.Event()
async def coro(namespace:Namespace, data: Dict, device_internal_id: str):

async def coro(namespace: Namespace, data: Dict, device_internal_id: str):
if namespace == Namespace.ROLLER_SHUTTER_STATE:
states = data.get('state')
# Filter by channel
state = next(filter(lambda s: s.get('channel')==0, states)).get('state')
state = next(filter(lambda s: s.get('channel') == 0, states)).get('state')
if state == RollerShutterState.OPENING.value:
state_opening.set()
elif state == RollerShutterState.CLOSING.value:
Expand All @@ -67,7 +71,7 @@ async def coro(namespace:Namespace, data: Dict, device_internal_id: str):
if namespace == Namespace.ROLLER_SHUTTER_POSITION:
positions = data.get('position')
# Filter by channel
position = next(filter(lambda s: s.get('channel')==0, positions)).get('position')
position = next(filter(lambda s: s.get('channel') == 0, positions)).get('position')
if position == 100:
position_opened.set()
elif position == 0:
Expand Down Expand Up @@ -102,11 +106,12 @@ async def test_close(self):
state_idle = asyncio.Event()
position_opened = asyncio.Event()
position_closed = asyncio.Event()
async def coro(namespace:Namespace, data: Dict, device_internal_id: str):

async def coro(namespace: Namespace, data: Dict, device_internal_id: str):
if namespace == Namespace.ROLLER_SHUTTER_STATE:
states = data.get('state')
# Filter by channel
state = next(filter(lambda s: s.get('channel')==0, states)).get('state')
state = next(filter(lambda s: s.get('channel') == 0, states)).get('state')
if state == RollerShutterState.OPENING.value:
state_opening.set()
elif state == RollerShutterState.CLOSING.value:
Expand All @@ -116,7 +121,7 @@ async def coro(namespace:Namespace, data: Dict, device_internal_id: str):
if namespace == Namespace.ROLLER_SHUTTER_POSITION:
positions = data.get('position')
# Filter by channel
position = next(filter(lambda s: s.get('channel')==0, positions)).get('position')
position = next(filter(lambda s: s.get('channel') == 0, positions)).get('position')
if position == 100:
position_opened.set()
elif position == 0:
Expand Down Expand Up @@ -167,19 +172,20 @@ async def test_set_config(self):
self.skipTest("No RollerShutter device has been found to run this test on.")
print(f"Testing device {self.test_device.name}")

open_timer = random.randint(10,120)
open_timer = random.randint(10, 120)
close_timer = random.randint(10, 120)
await self.test_device.async_set_config(open_timer_seconds=open_timer, close_timer_seconds=close_timer, channel=0)
await self.test_device.async_set_config(open_timer_seconds=open_timer, close_timer_seconds=close_timer,
channel=0)
await self.test_device.async_fetch_config()
opening_timer = self.test_device.get_open_timer_duration_millis(channel=0)
self.assertEqual(opening_timer, open_timer*1000)
self.assertEqual(opening_timer, open_timer * 1000)
closing_timer = self.test_device.get_close_timer_duration_millis(channel=0)
self.assertEqual(closing_timer, close_timer*1000)
self.assertEqual(closing_timer, close_timer * 1000)

async def tearDownAsync(self):
if self.requires_logout:
await self.meross_client.async_logout()
self.meross_manager.close()

# Give a change to asyncio clean everything up
await asyncio.sleep(1)
await asyncio.sleep(1)

0 comments on commit 8bfe9ce

Please sign in to comment.