power: improve device initialization

Refactor the PowerDevice initialize() method so that it acquires the
request lock. Always register the "klippy_started" event if the
"restart_klipper" option is set, and always check if Klipper is is
 the ready state before performing the restart.  Remove stale
 PowerDevice methods no longer used.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Eric Callahan 2022-02-27 06:34:11 -05:00
parent 970c8a4181
commit c165c40146
No known key found for this signature in database
GPG Key ID: 7027245FBBDDF59A
1 changed files with 33 additions and 40 deletions

View File

@ -111,10 +111,7 @@ class PrinterPower:
failed_devs: List[PowerDevice] = [] failed_devs: List[PowerDevice] = []
while cur_time < endtime: while cur_time < endtime:
for dev in query_devs: for dev in query_devs:
ret = dev.initialize() if not await dev.initialize():
if ret is not None:
await ret
if dev.get_state() == "error":
failed_devs.append(dev) failed_devs.append(dev)
if not failed_devs: if not failed_devs:
logging.debug("All power devices initialized") logging.debug("All power devices initialized")
@ -211,9 +208,10 @@ 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")
ret = device.initialize() if not await device.initialize():
if ret is not None: self.server.add_warning(
await ret f"Failed to initialize power device: {device.get_name()}")
return
self.devices[name] = device self.devices[name] = device
async def close(self) -> None: async def close(self) -> None:
@ -245,9 +243,12 @@ class PowerDevice:
self.klipper_restart = config.getboolean( self.klipper_restart = config.getboolean(
'restart_klipper_when_powered', False) 'restart_klipper_when_powered', False)
if self.klipper_restart: if self.klipper_restart:
self.restart_delay = config.getfloat('restart_delay', 1.) self.restart_delay = config.getfloat(
if self.restart_delay < .000001: 'restart_delay', 1., above=.000001
raise config.error("Option 'restart_delay' must be above 0.0") )
self.server.register_event_handler(
"server:klippy_started", self._schedule_firmware_restart
)
self.bound_service: Optional[str] = config.get('bound_service', None) self.bound_service: Optional[str] = config.get('bound_service', None)
self.need_scheduled_restart = False self.need_scheduled_restart = False
self.on_when_queued = config.getboolean('on_when_job_queued', False) self.on_when_queued = config.getboolean('on_when_job_queued', False)
@ -262,13 +263,6 @@ class PowerDevice:
pstate = result.get('print_stats', {}).get('state', "").lower() pstate = result.get('print_stats', {}).get('state', "").lower()
return pstate == "printing" return pstate == "printing"
def _is_bound_to_klipper(self):
return (
self.bound_service is not None and
self.bound_service.startswith("klipper") and
not self.bound_service.startswith("klipper_mcu")
)
def _schedule_firmware_restart(self, state: str = "") -> None: def _schedule_firmware_restart(self, state: str = "") -> None:
if not self.need_scheduled_restart: if not self.need_scheduled_restart:
return return
@ -285,9 +279,6 @@ class PowerDevice:
def get_name(self) -> str: def get_name(self) -> str:
return self.name return self.name
def get_state(self) -> str:
return self.state
def get_device_info(self) -> Dict[str, Any]: def get_device_info(self) -> Dict[str, Any]:
return { return {
'device': self.name, 'device': self.name,
@ -296,9 +287,6 @@ class PowerDevice:
'type': self.type 'type': self.type
} }
def get_locked_while_printing(self) -> bool:
return self.locked_while_printing
def notify_power_changed(self) -> None: def notify_power_changed(self) -> None:
dev_info = self.get_device_info() dev_info = self.get_device_info()
self.server.send_event("power:power_changed", dev_info) self.server.send_event("power:power_changed", dev_info)
@ -317,7 +305,7 @@ class PowerDevice:
# the startup state, schedule the restart in the # the startup state, schedule the restart in the
# "klippy_started" event callback. # "klippy_started" event callback.
return return
self._schedule_firmware_restart() self._schedule_firmware_restart(klippy_state)
def process_klippy_shutdown(self) -> None: def process_klippy_shutdown(self) -> None:
if not self.off_when_shutdown: if not self.off_when_shutdown:
@ -343,9 +331,9 @@ class PowerDevice:
def should_turn_on_when_queued(self) -> bool: def should_turn_on_when_queued(self) -> bool:
return self.on_when_queued and self.state == "off" return self.on_when_queued and self.state == "off"
def initialize(self) -> Optional[Coroutine]: def _setup_bound_service(self) -> None:
if self.bound_service is None: if self.bound_service is None:
return None return
if self.bound_service.startswith("moonraker"): if self.bound_service.startswith("moonraker"):
raise self.server.error( raise self.server.error(
f"Cannot bind to '{self.bound_service}' " f"Cannot bind to '{self.bound_service}' "
@ -356,17 +344,25 @@ class PowerDevice:
if self.bound_service not in avail_svcs: if self.bound_service not in avail_svcs:
raise self.server.error( raise self.server.error(
f"Bound Service {self.bound_service} is not available") f"Bound Service {self.bound_service} is not available")
if self._is_bound_to_klipper() and self.klipper_restart: logging.info(f"Power Device '{self.name}' bound to "
# Schedule the Firmware Restart after Klipper reconnects f"service '{self.bound_service}'")
logging.info(f"Power Device '{self.name}' bound to "
f"klipper service '{self.bound_service}'") def init_state(self) -> Optional[Coroutine]:
self.server.register_event_handler(
"server:klippy_started",
self._schedule_firmware_restart
)
return None return None
async def initialize(self) -> bool:
self._setup_bound_service()
ret = self.init_state()
if ret is not None:
await ret
return self.state != "error"
async def process_request(self, req: str) -> str: async def process_request(self, req: str) -> str:
if self.state == "init" and self.request_lock.locked():
# return immediately if the device is initializing,
# otherwise its possible for this to block indefinitely
# while the device holds the lock
return self.state
async with self.request_lock: async with self.request_lock:
base_state: str = self.state base_state: str = self.state
ret = self.refresh_status() ret = self.refresh_status()
@ -423,9 +419,8 @@ class HTTPDevice(PowerDevice):
"password", default_password).render() "password", default_password).render()
self.protocol = config.get("protocol", default_protocol) self.protocol = config.get("protocol", default_protocol)
async def initialize(self) -> None: async def init_state(self) -> None:
async with self.request_lock: async with self.request_lock:
super().initialize()
await self.refresh_status() await self.refresh_status()
async def _send_http_command(self, async def _send_http_command(self,
@ -493,8 +488,7 @@ class GpioDevice(PowerDevice):
initial_val = int(self.initial_state) initial_val = int(self.initial_state)
self.gpio_out = config.getgpioout('pin', initial_value=initial_val) self.gpio_out = config.getgpioout('pin', initial_value=initial_val)
def initialize(self) -> None: def init_state(self) -> None:
super().initialize()
self.set_power("on" if self.initial_state else "off") self.set_power("on" if self.initial_state else "off")
def refresh_status(self) -> None: def refresh_status(self) -> None:
@ -825,9 +819,8 @@ class TPLinkSmartPlug(PowerDevice):
res += chr(val) res += chr(val)
return res return res
async def initialize(self) -> None: async def init_state(self) -> None:
async with self.request_lock: async with self.request_lock:
super().initialize()
await self.refresh_status() await self.refresh_status()
async def refresh_status(self) -> None: async def refresh_status(self) -> None: