power: allow indefinite remote device init
Remote devices, such as the tplink smartplug and http based devices, may not be immediately available when Moonraker starts. Previously this would result in an error. Remote switches that requiring polling for state will now reattempt initialization indefinitely. This behavior brings them in line with devices that are updated asynchronously. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
835e49c10e
commit
31d4335e3e
|
@ -11,7 +11,6 @@ import struct
|
|||
import socket
|
||||
import asyncio
|
||||
import time
|
||||
from tornado.escape import json_decode
|
||||
|
||||
# Annotation imports
|
||||
from typing import (
|
||||
|
@ -102,30 +101,11 @@ class PrinterPower:
|
|||
return pstate == "printing"
|
||||
|
||||
async def component_init(self) -> None:
|
||||
event_loop = self.server.get_event_loop()
|
||||
# Wait up to 5 seconds for the machine component to init
|
||||
machine_cmp: Machine = self.server.lookup_component("machine")
|
||||
await machine_cmp.wait_for_init(5.)
|
||||
cur_time = event_loop.get_loop_time()
|
||||
endtime = cur_time + 120.
|
||||
query_devs = list(self.devices.values())
|
||||
failed_devs: List[PowerDevice] = []
|
||||
while cur_time < endtime:
|
||||
for dev in query_devs:
|
||||
if not await dev.initialize():
|
||||
failed_devs.append(dev)
|
||||
if not failed_devs:
|
||||
logging.debug("All power devices initialized")
|
||||
return
|
||||
query_devs = failed_devs
|
||||
failed_devs = []
|
||||
await asyncio.sleep(2.)
|
||||
cur_time = event_loop.get_loop_time()
|
||||
if failed_devs:
|
||||
failed_names = [d.get_name() for d in failed_devs]
|
||||
self.server.add_warning(
|
||||
"The following power devices failed init:"
|
||||
f" {failed_names}")
|
||||
for dev in self.devices.values():
|
||||
if not dev.initialize():
|
||||
self.server.add_warning(
|
||||
f"Power device '{dev.get_name()}' failed to initialize"
|
||||
)
|
||||
|
||||
def _handle_klippy_shutdown(self) -> None:
|
||||
for dev in self.devices.values():
|
||||
|
@ -209,9 +189,13 @@ class PrinterPower:
|
|||
if name in self.devices:
|
||||
raise self.server.error(
|
||||
f"Device [{name}] already configured")
|
||||
if not await device.initialize():
|
||||
success = device.initialize()
|
||||
if asyncio.iscoroutine(success):
|
||||
success = await success
|
||||
if not success:
|
||||
self.server.add_warning(
|
||||
f"Failed to initialize power device: {device.get_name()}")
|
||||
f"Power device '{device.get_name()}' failed to initialize"
|
||||
)
|
||||
return
|
||||
self.devices[name] = device
|
||||
|
||||
|
@ -232,6 +216,7 @@ class PowerDevice:
|
|||
self.type: str = config.get('type')
|
||||
self.state: str = "init"
|
||||
self.request_lock = asyncio.Lock()
|
||||
self.init_task: Optional[asyncio.Task] = None
|
||||
self.locked_while_printing = config.getboolean(
|
||||
'locked_while_printing', False)
|
||||
self.off_when_shutdown = config.getboolean('off_when_shutdown', False)
|
||||
|
@ -351,11 +336,12 @@ class PowerDevice:
|
|||
def init_state(self) -> Optional[Coroutine]:
|
||||
return None
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
def initialize(self) -> bool:
|
||||
self._setup_bound_service()
|
||||
ret = self.init_state()
|
||||
if ret is not None:
|
||||
await ret
|
||||
eventloop = self.server.get_event_loop()
|
||||
self.init_task = eventloop.create_task(ret)
|
||||
return self.state != "error"
|
||||
|
||||
async def process_request(self, req: str) -> str:
|
||||
|
@ -401,7 +387,10 @@ class PowerDevice:
|
|||
raise NotImplementedError
|
||||
|
||||
def close(self) -> Optional[Coroutine]:
|
||||
pass
|
||||
if self.init_task is not None:
|
||||
self.init_task.cancel()
|
||||
self.init_task = None
|
||||
return None
|
||||
|
||||
class HTTPDevice(PowerDevice):
|
||||
def __init__(self,
|
||||
|
@ -422,7 +411,23 @@ class HTTPDevice(PowerDevice):
|
|||
|
||||
async def init_state(self) -> None:
|
||||
async with self.request_lock:
|
||||
await self.refresh_status()
|
||||
last_err: Exception = Exception()
|
||||
while True:
|
||||
try:
|
||||
state = await self._send_status_request()
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
if type(last_err) != type(e) or last_err.args != e.args:
|
||||
logging.info(f"Device Init Error: {self.name}\n{e}")
|
||||
last_err = e
|
||||
await asyncio.sleep(5.)
|
||||
continue
|
||||
else:
|
||||
self.init_task = None
|
||||
self.state = state
|
||||
self.notify_power_changed()
|
||||
return
|
||||
|
||||
async def _send_http_command(self,
|
||||
url: str,
|
||||
|
@ -815,21 +820,39 @@ class TPLinkSmartPlug(PowerDevice):
|
|||
res += chr(val)
|
||||
return res
|
||||
|
||||
async def _send_info_request(self) -> int:
|
||||
res = await self._send_tplink_command("info")
|
||||
if self.output_id is not None:
|
||||
# TPLink device controls multiple devices
|
||||
children: Dict[int, Any]
|
||||
children = res['system']['get_sysinfo']['children']
|
||||
return children[self.output_id]['state']
|
||||
else:
|
||||
return res['system']['get_sysinfo']['relay_state']
|
||||
|
||||
async def init_state(self) -> None:
|
||||
async with self.request_lock:
|
||||
await self.refresh_status()
|
||||
last_err: Exception = Exception()
|
||||
while True:
|
||||
try:
|
||||
state: int = await self._send_info_request()
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
if type(last_err) != type(e) or last_err.args != e.args:
|
||||
logging.info(f"Device Init Error: {self.name}\n{e}")
|
||||
last_err = e
|
||||
await asyncio.sleep(5.)
|
||||
continue
|
||||
else:
|
||||
self.init_task = None
|
||||
self.state = "on" if state else "off"
|
||||
self.notify_power_changed()
|
||||
return
|
||||
|
||||
async def refresh_status(self) -> None:
|
||||
try:
|
||||
state: str
|
||||
res = await self._send_tplink_command("info")
|
||||
if self.output_id is not None:
|
||||
# TPLink device controls multiple devices
|
||||
children: Dict[int, Any]
|
||||
children = res['system']['get_sysinfo']['children']
|
||||
state = children[self.output_id]['state']
|
||||
else:
|
||||
state = res['system']['get_sysinfo']['relay_state']
|
||||
state: int = await self._send_info_request()
|
||||
except Exception:
|
||||
self.state = "error"
|
||||
msg = f"Error Refeshing Device Status: {self.name}"
|
||||
|
|
Loading…
Reference in New Issue