Skip to content

Commit

Permalink
feat: complete rework of client
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Client now pushes all data to handlers
BREAKING CHANGE: Client pushes data to handlers at interval set by update_interval
  • Loading branch information
bj00rn committed Dec 11, 2023
1 parent d69be43 commit 9ac4d69
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 48 deletions.
93 changes: 53 additions & 40 deletions src/pysaleryd/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,30 @@
class Client:
"""Client to manage communication with HRV"""

def __init__(self, url: str, port: int, session: aiohttp.ClientSession):
def __init__(
self, url: str, port: int, session: aiohttp.ClientSession, update_interval=30
):
self._update_interval = update_interval
self._url = url
self._port = port
self._session = session
self._data = {}
self._error_cache = ErrorCache()
self._handlers = []
self._socket = WSClient(self._session, self._url, self._port, self._handler)
self._parser = Parser()
self._message_handler_task = asyncio.create_task(self._message_handler())
self._data_handler_task = asyncio.create_task(self._call_handlers())
self._incoming_queue = asyncio.queues.Queue()

@property
def state(self):
"""Get internal socket state"""
return self._socket.state

@property
def data(self):
"""Get data from system"""
return self._data

async def __aenter__(self):
"""Start socket and wait for connection"""
Expand All @@ -33,39 +48,29 @@ async def __aenter__(self):
async def __aexit__(self, _type, value, traceback):
self.disconnect()

async def connect(self):
"""Connect to system and wait for connection"""

async def check_connection():
while self._socket.state != State.RUNNING:
await asyncio.sleep(0.2)

self._socket.start()
await asyncio.gather(check_connection())

def disconnect(self):
"""Disconnect from system"""
self._socket.stop()

def add_handler(self, handler: Callable[[str], None]):
"""Add event handler"""
self._handlers.append(handler)

def _call_handlers(self, data):
async def _call_handlers(self):
"""Call handlers with data"""
for handler in self._handlers:
try:
handler(data)
except Exception:
_LOGGER.warning("Failed to call handler", exc_info=True)
while True:
for handler in self._handlers:
try:
handler(self._data)
except Exception:
_LOGGER.warning("Failed to call handler", exc_info=True)
await asyncio.sleep(self._update_interval)

async def _handler(
self, signal: Signal, data: str, state: State = None
self, signal: Signal, data: str, state: "State" = None
): # pylint: disable W0613
"""Call handlers if data"""
if signal == Signal.DATA:
await self._incoming_queue.put(data)

async def _message_handler(self):
while True:
try:
(key, value) = self._parser.from_str(data)
msg = await self._incoming_queue.get()
# update state
# if ack force push state to handler
(key, value) = Parser.from_str(msg)

if key in ["*EA", "*EB", "*EZ"]:
if key == "*EA":
Expand All @@ -75,26 +80,34 @@ async def _handler(
if key == "*EZ":
self._error_cache.end_frame()
self._data["*EB"] = self._error_cache.data
self._call_handlers(("*EB", self._error_cache.data))
else:
self._data[key] = value
self._call_handlers(data)
except ParseError:
pass

@property
def state(self):
"""Get internal socket state"""
return self._socket.state
async def connect(self):
"""Connect to system and wait for connection"""

@property
def data(self):
"""Get data from system"""
return self._data
async def check_connection():
while self._socket.state != State.RUNNING:
await asyncio.sleep(0.2)

self._socket.start()
await asyncio.gather(check_connection())

def disconnect(self):
"""Disconnect from system"""
self._socket.stop()
self._data_handler_task.cancel()
self._message_handler_task.cancel()

def add_handler(self, handler: Callable[[str], None]):
"""Add event handler"""
self._handlers.append(handler)

async def send_command(self, key, value: str | int):
"""Send command to HRV"""
message = self._parser.to_str(key, value)
message = Parser.to_str(key, value)

async def ack_command():
"""Should probably ack command here, just sleep for now"""
Expand Down
6 changes: 4 additions & 2 deletions src/pysaleryd/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ class ParseError(BaseException):
class Parser:
"""Message parser. Parse HRV system messages"""

def to_str(self, key, value):
@staticmethod
def to_str(key, value):
"""Parse message to string
Args:
Expand All @@ -23,7 +24,8 @@ def to_str(self, key, value):
"""
return f"#{key}:{value}\r"

def from_str(self, msg: str):
@staticmethod
def from_str(msg: str):
"""Parse message string
Args:
Expand Down
4 changes: 3 additions & 1 deletion src/pysaleryd/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ async def _running(self) -> None:
if msg.type == aiohttp.WSMsgType.TEXT:
_LOGGER.debug("Received: %s", msg.data)
asyncio.create_task(
self._session_handler_callback(Signal.DATA, data=msg.data)
self._session_handler_callback(
Signal.DATA, data=msg.data, state=self._state
)
)
continue

Expand Down
15 changes: 11 additions & 4 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
async def _hrv_client(ws_server):
"""HRV Client"""
async with aiohttp.ClientSession() as session:
async with Client("localhost", 3001, session) as client:
async with Client("localhost", 3001, session, 3) as client:
yield client


Expand All @@ -33,15 +33,22 @@ async def test_client_connect(hrv_client: "Client"):
@pytest.mark.asyncio
async def test_handler(hrv_client: "Client", mocker):
"""Test handler callback"""
handler = mocker.Mock()

data = None # noqa: F841

def handler(_data):
nonlocal data
data = _data

def broken_handler(data):
raise Exception() # pylint: disable=W0719

hrv_client.add_handler(broken_handler)
hrv_client.add_handler(handler)
await asyncio.sleep(3)
handler.assert_called()
await asyncio.sleep(5)

assert isinstance(data, dict)
assert any(data.keys())


@pytest.mark.asyncio
Expand Down
2 changes: 1 addition & 1 deletion tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

@pytest.fixture(name="parser")
def _parser() -> Parser:
return Parser()
return Parser


def test_parse_int_from_list_str(parser: Parser):
Expand Down

0 comments on commit 9ac4d69

Please sign in to comment.