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 socket
|
||||||
import asyncio
|
import asyncio
|
||||||
import time
|
import time
|
||||||
from tornado.escape import json_decode
|
|
||||||
|
|
||||||
# Annotation imports
|
# Annotation imports
|
||||||
from typing import (
|
from typing import (
|
||||||
|
@ -102,30 +101,11 @@ class PrinterPower:
|
||||||
return pstate == "printing"
|
return pstate == "printing"
|
||||||
|
|
||||||
async def component_init(self) -> None:
|
async def component_init(self) -> None:
|
||||||
event_loop = self.server.get_event_loop()
|
for dev in self.devices.values():
|
||||||
# Wait up to 5 seconds for the machine component to init
|
if not dev.initialize():
|
||||||
machine_cmp: Machine = self.server.lookup_component("machine")
|
self.server.add_warning(
|
||||||
await machine_cmp.wait_for_init(5.)
|
f"Power device '{dev.get_name()}' failed to initialize"
|
||||||
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}")
|
|
||||||
|
|
||||||
def _handle_klippy_shutdown(self) -> None:
|
def _handle_klippy_shutdown(self) -> None:
|
||||||
for dev in self.devices.values():
|
for dev in self.devices.values():
|
||||||
|
@ -209,9 +189,13 @@ class PrinterPower:
|
||||||
if name in self.devices:
|
if name in self.devices:
|
||||||
raise self.server.error(
|
raise self.server.error(
|
||||||
f"Device [{name}] already configured")
|
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(
|
self.server.add_warning(
|
||||||
f"Failed to initialize power device: {device.get_name()}")
|
f"Power device '{device.get_name()}' failed to initialize"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
self.devices[name] = device
|
self.devices[name] = device
|
||||||
|
|
||||||
|
@ -232,6 +216,7 @@ class PowerDevice:
|
||||||
self.type: str = config.get('type')
|
self.type: str = config.get('type')
|
||||||
self.state: str = "init"
|
self.state: str = "init"
|
||||||
self.request_lock = asyncio.Lock()
|
self.request_lock = asyncio.Lock()
|
||||||
|
self.init_task: Optional[asyncio.Task] = None
|
||||||
self.locked_while_printing = config.getboolean(
|
self.locked_while_printing = config.getboolean(
|
||||||
'locked_while_printing', False)
|
'locked_while_printing', False)
|
||||||
self.off_when_shutdown = config.getboolean('off_when_shutdown', False)
|
self.off_when_shutdown = config.getboolean('off_when_shutdown', False)
|
||||||
|
@ -351,11 +336,12 @@ class PowerDevice:
|
||||||
def init_state(self) -> Optional[Coroutine]:
|
def init_state(self) -> Optional[Coroutine]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def initialize(self) -> bool:
|
def initialize(self) -> bool:
|
||||||
self._setup_bound_service()
|
self._setup_bound_service()
|
||||||
ret = self.init_state()
|
ret = self.init_state()
|
||||||
if ret is not None:
|
if ret is not None:
|
||||||
await ret
|
eventloop = self.server.get_event_loop()
|
||||||
|
self.init_task = eventloop.create_task(ret)
|
||||||
return self.state != "error"
|
return self.state != "error"
|
||||||
|
|
||||||
async def process_request(self, req: str) -> str:
|
async def process_request(self, req: str) -> str:
|
||||||
|
@ -401,7 +387,10 @@ class PowerDevice:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def close(self) -> Optional[Coroutine]:
|
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):
|
class HTTPDevice(PowerDevice):
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
|
@ -422,7 +411,23 @@ class HTTPDevice(PowerDevice):
|
||||||
|
|
||||||
async def init_state(self) -> None:
|
async def init_state(self) -> None:
|
||||||
async with self.request_lock:
|
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,
|
async def _send_http_command(self,
|
||||||
url: str,
|
url: str,
|
||||||
|
@ -815,21 +820,39 @@ class TPLinkSmartPlug(PowerDevice):
|
||||||
res += chr(val)
|
res += chr(val)
|
||||||
return res
|
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 def init_state(self) -> None:
|
||||||
async with self.request_lock:
|
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:
|
async def refresh_status(self) -> None:
|
||||||
try:
|
try:
|
||||||
state: str
|
state: int = await self._send_info_request()
|
||||||
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']
|
|
||||||
except Exception:
|
except Exception:
|
||||||
self.state = "error"
|
self.state = "error"
|
||||||
msg = f"Error Refeshing Device Status: {self.name}"
|
msg = f"Error Refeshing Device Status: {self.name}"
|
||||||
|
|
Loading…
Reference in New Issue