Skip to content

Commit

Permalink
Merge pull request #28 from iloveicedgreentea/check_on
Browse files Browse the repository at this point in the history
Add retries on connection closed
  • Loading branch information
iloveicedgreentea authored Feb 21, 2024
2 parents 709f90d + a483e31 commit 9589d1b
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 21 deletions.
4 changes: 2 additions & 2 deletions jvc_projector/command_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ async def send_command(
return cons_command, ack
return await self._do_command(cons_command, ack.value, command_type)
return "No command provided", False
# raise connectionclosed error to be handled by callers
except (
ConnectionClosedError,
CommandTimeoutError,
BlankMessageError,
ConnectionRefusedError,
Expand Down Expand Up @@ -160,7 +160,7 @@ async def _do_command(
await self.writer.drain()
except ConnectionError as err:
# reaching this means the writer was closed somewhere
self.logger.error(err)
self.logger.debug("ConnectionError in _do_command: %s", err)
raise ConnectionClosedError(err) from err

# if we send a command that returns info, the projector will send
Expand Down
80 changes: 62 additions & 18 deletions jvc_projector/jvc_projector.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@
TheaterOptimizer,
model_map,
)
from jvc_projector.error_classes import (
BlankMessageError,
CommandTimeoutError,
ConnectionClosedError,
)


@dataclass
Expand Down Expand Up @@ -84,6 +89,7 @@ class JVCAttributes: # pylint: disable=too-many-instance-attributes
software_version: str = ""
laser_time: int = 0
lamp_time: int = 0
connection_active: bool = False


class JVCProjectorCoordinator: # pylint: disable=too-many-public-methods
Expand Down Expand Up @@ -154,18 +160,32 @@ async def _get_modelfamily(self) -> str:
+ Commands.get_model.value
+ Footer.close.value
)
res, _ = await self.commander.send_command(
cmd,
command_type=Header.reference.value,
ack=ACKs.model.value,
)

model_res = self.commander.replace_headers(res).decode()
self.logger.debug(model_res)
return model_map.get(model_res[-4:], "Unsupported")
retries = 0
while retries < 3:
try:
res, _ = await self.commander.send_command(
cmd,
command_type=Header.reference.value,
ack=ACKs.model.value,
)
except ConnectionClosedError:
self.logger.error("Connection closed")
# open connection and try again
await self.open_connection()
await asyncio.sleep(1)
retries += 1
continue

model_res = self.commander.replace_headers(res).decode()
self.logger.debug(model_res)
return model_map.get(model_res[-4:], "Unsupported")

async def open_connection(self) -> bool:
"""Open a connection to the projector asynchronously"""
# If the connection is already open, return True
if self.writer is not None and not self.writer.is_closing():
self.logger.info("Connection already open.")
return True
while True:
try:
self.logger.info(
Expand All @@ -188,6 +208,7 @@ async def open_connection(self) -> bool:
return False
self.logger.info("Handshake and connection completed")
self.connection_open = True
self.attributes.connection_active = True
return True
except asyncio.TimeoutError:
self.logger.warning("Connection timed out, retrying in 2 seconds")
Expand Down Expand Up @@ -215,21 +236,35 @@ async def exec_command(
self.logger.debug(
"exec_command Executing command: %s - %s", command, command_type
)
return await self.commander.send_command(command, command_type)
retries = 0
while retries < 3:
try:
return await self.commander.send_command(command, command_type)
except ConnectionClosedError:
self.logger.debug("Connection closed. Reconnecting")
# open connection and try again
await self.open_connection()
await asyncio.sleep(1)
retries += 1
continue

async def close_connection(self):
"""Close the projector connection asynchronously"""
try:
if self.writer:
self.writer.close()
await self.writer.wait_closed()
self.commander.reader = self.reader
self.commander.writer = self.writer

self.logger.info("Connection closed")
except BrokenPipeError:
self.logger.warning("Connection already closed - Broken pipe encountered")
except Exception as e:
self.logger.error("Error closing JVC Projector connection - %s", e)
finally:
self.commander.reader = self.reader
self.commander.writer = self.writer
self.connection_open = False
self.attributes.connection_active = False

async def info(self) -> tuple[str, bool]:
"""
Expand All @@ -241,12 +276,21 @@ async def info(self) -> tuple[str, bool]:
+ Commands.info.value
+ Footer.close.value
)

return await self.commander.send_command(
cmd,
ack=ACKs.menu_ack,
command_type=Header.operation.value,
)
retries = 0
while retries < 3:
try:
return await self.commander.send_command(
cmd,
ack=ACKs.menu_ack,
command_type=Header.operation.value,
)
except ConnectionClosedError:
self.logger.error("Connection closed")
# open connection and try again
await self.open_connection()
await asyncio.sleep(1)
retries += 1
continue

async def power_on(
self,
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name="pyjvc",
version="4.1.1",
version="4.2.1",
author="iloveicedgreentea2",
description="A package to control JVC projectors over IP",
long_description=long_description,
Expand Down

0 comments on commit 9589d1b

Please sign in to comment.