power: add "locked_while_printing" option to avoid accidental shutdown

Signed-off-by: Jordan Ruthe <jordan.ruthe@gmail.com>
This commit is contained in:
Jordan Ruthe 2021-01-21 20:40:09 -05:00 committed by Eric Callahan
parent ff8e53b269
commit b4602115d7
2 changed files with 49 additions and 36 deletions

View File

@ -164,6 +164,10 @@ initial_state: off
# The initial state for GPIO type devices. May be on or # The initial state for GPIO type devices. May be on or
# off. When moonraker starts the device will be set to this # off. When moonraker starts the device will be set to this
# state. Default is off. # state. Default is off.
locked_while_printing: False
# If True, locks the device so that the power cannot be changed while the
# printer is printing. This is useful to avert an accidental shutdown to
# the printer's power.
address: address:
port: port:
# The above options are used for "tplink_smartplug" devices. The # The above options are used for "tplink_smartplug" devices. The

View File

@ -60,6 +60,13 @@ class PrinterPower:
IOLoop.current().spawn_callback( IOLoop.current().spawn_callback(
self._initalize_devices, list(self.devices.values())) self._initalize_devices, list(self.devices.values()))
async def _check_klippy_printing(self):
klippy_apis = self.server.lookup_plugin('klippy_apis')
result = await klippy_apis.query_objects(
{'print_stats': None}, default={})
pstate = result.get('print_stats', {}).get('state', "").lower()
return pstate == "printing"
async def _initalize_devices(self, inital_devs): async def _initalize_devices(self, inital_devs):
for dev in inital_devs: for dev in inital_devs:
ret = dev.initialize() ret = dev.initialize()
@ -97,6 +104,14 @@ class PrinterPower:
async def _process_request(self, device, req): async def _process_request(self, device, req):
if req in ["on", "off"]: if req in ["on", "off"]:
printing = await self._check_klippy_printing()
logging.info("Device locked: %s" % device.get_locked_while_printing())
logging.info("Klippy printing: %s" % printing)
if device.get_locked_while_printing() and printing:
self.server.error(f"Unable to change power for {device} " +
"while printing")
dev_info = device.get_device_info()
return dev_info['status']
ret = device.set_power(req) ret = device.set_power(req)
if asyncio.iscoroutine(ret): if asyncio.iscoroutine(ret):
await ret await ret
@ -147,6 +162,28 @@ class PrinterPower:
self.chip_factory.close() self.chip_factory.close()
class PowerDevice:
def __init__(self, config):
name_parts = config.get_name().split(maxsplit=1)
if len(name_parts) != 2:
raise config.error(f"Invalid Section Name: {config.get_name()}")
self.name = name_parts[1]
self.state = "init"
self.locked_while_printing = config.getboolean('locked_while_printing', False)
self.off_when_shutdown = config.getboolean('off_when_shutdown', False)
def get_name(self):
return self.name
def get_device_info(self):
return {
'device': self.name,
'status': self.state
}
def get_locked_while_printing(self):
return self.locked_while_printing
class GpioChipFactory: class GpioChipFactory:
def __init__(self): def __init__(self):
self.chips = {} self.chips = {}
@ -162,13 +199,9 @@ class GpioChipFactory:
for chip in self.chips.values(): for chip in self.chips.values():
chip.close() chip.close()
class GpioDevice: class GpioDevice(PowerDevice):
def __init__(self, config, chip_factory): def __init__(self, config, chip_factory):
name_parts = config.get_name().split(maxsplit=1) super().__init__(config)
if len(name_parts) != 2:
raise config.error(f"Invalid Section Name: {config.get_name()}")
self.name = name_parts[1]
self.state = "init"
pin, chip_id, invert = self._parse_pin(config) pin, chip_id, invert = self._parse_pin(config)
try: try:
chip = chip_factory.get_gpio_chip(chip_id) chip = chip_factory.get_gpio_chip(chip_id)
@ -186,7 +219,6 @@ class GpioDevice:
f"Unable to init {pin}. Make sure the gpio is not in " f"Unable to init {pin}. Make sure the gpio is not in "
"use by another program or exported by sysfs.") "use by another program or exported by sysfs.")
raise config.error("Power GPIO Config Error") raise config.error("Power GPIO Config Error")
self.off_when_shutdown = config.getboolean('off_when_shutdown', False)
self.initial_state = config.getboolean('initial_state', False) self.initial_state = config.getboolean('initial_state', False)
def _parse_pin(self, config): def _parse_pin(self, config):
@ -214,13 +246,9 @@ class GpioDevice:
def initialize(self): def initialize(self):
self.set_power("on" if self.initial_state else "off") self.set_power("on" if self.initial_state else "off")
def get_name(self):
return self.name
def get_device_info(self): def get_device_info(self):
return { return {
'device': self.name, **super().get_device_info(),
'status': self.state,
'type': "gpio" 'type': "gpio"
} }
@ -254,18 +282,13 @@ class GpioDevice:
# https://github.com/softScheck/tplink-smartplug # https://github.com/softScheck/tplink-smartplug
# #
# Copyright 2016 softScheck GmbH # Copyright 2016 softScheck GmbH
class TPLinkSmartPlug: class TPLinkSmartPlug(PowerDevice):
START_KEY = 0xAB START_KEY = 0xAB
def __init__(self, config): def __init__(self, config):
super().__init__(config)
self.server = config.get_server() self.server = config.get_server()
self.addr = config.get("address") self.addr = config.get("address")
self.port = config.getint("port", 9999) self.port = config.getint("port", 9999)
self.off_when_shutdown = config.getboolean('off_when_shutdown', False)
name_parts = config.get_name().split(maxsplit=1)
if len(name_parts) != 2:
raise config.error(f"Invalid Section Name: {config.get_name()}")
self.name = name_parts[1]
self.state = "init"
async def _send_tplink_command(self, command): async def _send_tplink_command(self, command):
out_cmd = {} out_cmd = {}
@ -322,13 +345,9 @@ class TPLinkSmartPlug:
async def initialize(self): async def initialize(self):
await self.refresh_status() await self.refresh_status()
def get_name(self):
return self.name
def get_device_info(self): def get_device_info(self):
return { return {
'device': self.name, **super().get_device_info(),
'status': self.state,
'type': "tplink_smartplug" 'type': "tplink_smartplug"
} }
@ -357,18 +376,12 @@ class TPLinkSmartPlug:
self.state = state self.state = state
class Tasmota: class Tasmota(PowerDevice):
def __init__(self, config): def __init__(self, config):
self.server = config.get_server() self.server = config.get_server()
self.addr = config.get("address") self.addr = config.get("address")
self.output_id = config.getint("output_id", 1) self.output_id = config.getint("output_id", 1)
self.password = config.get("password", "") self.password = config.get("password", "")
self.off_when_shutdown = config.getboolean('off_when_shutdown', False)
name_parts = config.get_name().split(maxsplit=1)
if len(name_parts) != 2:
raise config.error(f"Invalid Section Name: {config.get_name()}")
self.name = name_parts[1]
self.state = "init"
async def _send_tasmota_command(self, command, password=None): async def _send_tasmota_command(self, command, password=None):
if command in ["on", "off"]: if command in ["on", "off"]:
@ -394,13 +407,9 @@ class Tasmota:
async def initialize(self): async def initialize(self):
await self.refresh_status() await self.refresh_status()
def get_name(self):
return self.name
def get_device_info(self): def get_device_info(self):
return { return {
'device': self.name, **super().get_device_info(),
'status': self.state,
'type': "tasmota" 'type': "tasmota"
} }