Skip to content

Commit

Permalink
add listen cli command
Browse files Browse the repository at this point in the history
  • Loading branch information
hahn-th committed Jun 9, 2024
1 parent 5d4eedc commit 66f36c6
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 27 deletions.
68 changes: 43 additions & 25 deletions src/homematicip/cli/hmip.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from homematicip.events.event_types import ModelUpdateEvent
from homematicip.model.anoymizer import handle_config
from homematicip.model.enums import ClimateControlMode, ClimateControlDisplay
from homematicip.model.hmip_base import HmipBaseModel
from homematicip.model.model_components import Device


Expand Down Expand Up @@ -265,30 +266,42 @@ def firmware():


async def _model_update_event_handler(event: ModelUpdateEvent, args) -> None:
click.echo(f"ModelUpdateEvent: {event}; {args}")
hmip_item = None
if isinstance(args, HmipBaseModel):
hmip_item = args

output = {"event": event.name,
"updated_type": type(args).__name__ if args else None,
"updated_id": getattr(args, "id") if args else None,
"updated_item": repr(hmip_item) if hmip_item else None}

formatted_output = json.dumps(output, indent=4)
click.echo(formatted_output)


@cli.command
def listen():
"""Listen to events. If filename is specified, events are written to the file. If not, they are written to
console."""
asyncio.run(listen_wrapped())

# @cli.command
# def listen():
# """Listen to events. If filename is specified, events are written to the file. If not, they are written to
# console."""
# runner = asyncio.run(get_initialized_runner())
# runner.event_manager.subscribe(ModelUpdateEvent.ITEM_CREATED, _model_update_event_handler)
# runner.event_manager.subscribe(ModelUpdateEvent.ITEM_UPDATED, _model_update_event_handler)
# runner.event_manager.subscribe(ModelUpdateEvent.ITEM_REMOVED, _model_update_event_handler)
#
# loop = asyncio.new_event_loop()
# task = loop.create_task(runner.async_listening_for_updates())
#
# try:
# click.echo("Waiting for events... press CTRL+C to stop listening.")
# loop.run_until_complete(task)
# except asyncio.CancelledError:
# pass
# except KeyboardInterrupt:
# task.cancel()
# loop.stop()
# click.echo("Stopping listener...")

async def listen_wrapped():
"""The wrapped listen function."""
runner = await get_initialized_runner()
runner.event_manager.subscribe(ModelUpdateEvent.ITEM_CREATED, _model_update_event_handler)
runner.event_manager.subscribe(ModelUpdateEvent.ITEM_UPDATED, _model_update_event_handler)
runner.event_manager.subscribe(ModelUpdateEvent.ITEM_REMOVED, _model_update_event_handler)

task = asyncio.create_task(runner.async_listening_for_updates())

try:
click.echo("Waiting for events... press CTRL+C to stop listening.")
await task
except KeyboardInterrupt:
click.echo("Stop listener...")
task.cancel()
await task


@cli.command
Expand Down Expand Up @@ -324,7 +337,8 @@ def turn_on(id: str, channel: int = None):
f"Run turn_on for device {get_device_name(device_or_group)} with result: {result.status_text} ({result.status})")
else:
result = asyncio.run(action_set_switch_state_group(runner.rest_connection, device_or_group, True))
click.echo(f"Run turn_on for device {get_group_name(device_or_group)} with result: {result.status_text} ({result.status})")
click.echo(
f"Run turn_on for device {get_group_name(device_or_group)} with result: {result.status_text} ({result.status})")


@run.command()
Expand Down Expand Up @@ -443,7 +457,8 @@ def set_point_temperature(id: str, temperature: float):
click.echo(f"Group with id {id} not found.", err=True, color=True)
return

result = asyncio.run(action_set_point_temperature_group(runner.rest_connection, runner.model.groups[id], temperature))
result = asyncio.run(
action_set_point_temperature_group(runner.rest_connection, runner.model.groups[id], temperature))
if result.exception is not None:
click.echo(f"Error while running set_point_temperature: {result.exception}", err=True, color=True)
return
Expand Down Expand Up @@ -483,7 +498,8 @@ def set_active_profile(id: str, profile_index: str):
click.echo(f"Group with id {id} not found.", err=True, color=True)
return

result = asyncio.run(action_set_active_profile_group(runner.rest_connection, runner.model.groups[id], profile_index))
result = asyncio.run(
action_set_active_profile_group(runner.rest_connection, runner.model.groups[id], profile_index))
click.echo(
f"Run set_active_profile for group {runner.model.groups[id].label or runner.model.groups[id].id} with "
f"result: {result.status_text} ({result.status})")
Expand Down Expand Up @@ -527,6 +543,7 @@ def set_display(id: str, channel: int, display: str):
f"Run set_display for group {get_device_name(device)} with "
f"result: {result.status_text} ({result.status})")


@run.command
@click.option("--id", type=str, required=True, help="ID of the device or group, which the run command is applied to.")
@click.option("-d", "--dim_level", type=click.FloatRange(0.0, 1.0), help="Target Dim Level", required=True)
Expand Down Expand Up @@ -659,6 +676,7 @@ def set_on_time(id: str, on_time: int):
click.echo(
f"Run set_on_time for group {get_group_name(group)} with result: {result.status_text} ({result.status})")


@run.command
@click.option("--id", type=str, required=True, help="ID of the device or group, which the run command is applied to.")
@click.option("-c", "--channel", type=int, required=False, default=None,
Expand Down
5 changes: 5 additions & 0 deletions src/homematicip/connection/websocket_handler.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import ssl
from typing import Callable

import certifi
import websockets

from homematicip.connection.rest_connection import ConnectionContext, ATTR_AUTH_TOKEN, ATTR_CLIENT_AUTH, LOGGER
Expand All @@ -9,12 +11,15 @@
class WebSocketHandler:
async def listen(self, context: ConnectionContext, connection_state_callback: Callable, reconnect_on_error: bool = True):
uri = context.websocket_url
ssl_context = ssl.create_default_context()
ssl_context.load_verify_locations(certifi.where())
async with websockets.connect(
uri,
extra_headers={
ATTR_AUTH_TOKEN: context.auth_token,
ATTR_CLIENT_AUTH: context.client_auth_token,
},
ssl=ssl_context,
) as websocket:
# Process messages received on the connection.
async for message in websocket:
Expand Down
6 changes: 4 additions & 2 deletions src/homematicip/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ def rest_connection(self):
pass



@dataclass(kw_only=True)
class Runner(AbstractRunner):
model: Model = None
Expand Down Expand Up @@ -110,7 +109,10 @@ async def async_initialize_runner_without_init_model(self):

async def async_listening_for_updates(self):
"""Start listening for updates from HomematicIP Cloud. This method will not return."""
await self._async_start_listening_for_updates(self._connection_context)
try:
await self._async_start_listening_for_updates(self._connection_context)
except asyncio.CancelledError:
pass

async def async_get_current_state(self):
"""
Expand Down
15 changes: 15 additions & 0 deletions tests/cli/test_hmip.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import pytest
from click.testing import CliRunner

from homematicip.cli import hmip
from homematicip.cli.hmip import _model_update_event_handler
from homematicip.events.event_types import ModelUpdateEvent
from homematicip.model.model import Model, build_model_from_json


@pytest.fixture
def filled_model(sample_data_complete) -> Model:
return build_model_from_json(sample_data_complete)

#
# def test_version():
Expand All @@ -11,3 +20,9 @@
# def test_list_devices(mocker):
# result = CliRunner().invoke(hmip.cli, ["list", "devices"])
# assert result.exit_code == 0

@pytest.mark.asyncio
async def test_model_update_event_handler(filled_model: Model):
device = filled_model.devices["3014F7110000RAIN_SENSOR"]

await _model_update_event_handler(ModelUpdateEvent.ITEM_UPDATED, device)

0 comments on commit 66f36c6

Please sign in to comment.