mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-02-03 03:21:54 +08:00
ugpio plugins
This commit is contained in:
@@ -30,62 +30,89 @@ from typing import Optional
|
||||
|
||||
from ...logging import get_logger
|
||||
|
||||
from ...plugins.ugpio import GpioError
|
||||
from ...plugins.ugpio import GpioOperationError
|
||||
from ...plugins.ugpio import GpioDriverOfflineError
|
||||
from ...plugins.ugpio import BaseUserGpioDriver
|
||||
from ...plugins.ugpio import get_ugpio_driver_class
|
||||
|
||||
from ... import aiotools
|
||||
from ... import gpio
|
||||
|
||||
from ...yamlconf import Section
|
||||
|
||||
from ...errors import OperationError
|
||||
from ...errors import IsBusyError
|
||||
|
||||
|
||||
# =====
|
||||
class GpioChannelNotFoundError(OperationError):
|
||||
class GpioChannelNotFoundError(GpioOperationError):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("GPIO channel is not found")
|
||||
|
||||
|
||||
class GpioSwitchNotSupported(OperationError):
|
||||
class GpioSwitchNotSupported(GpioOperationError):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("This GPIO channel does not support switching")
|
||||
|
||||
|
||||
class GpioPulseNotSupported(OperationError):
|
||||
class GpioPulseNotSupported(GpioOperationError):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("This GPIO channel does not support pulsing")
|
||||
|
||||
|
||||
class GpioChannelIsBusyError(IsBusyError):
|
||||
class GpioChannelIsBusyError(IsBusyError, GpioError):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("Performing another GPIO operation on this channel, please try again later")
|
||||
|
||||
|
||||
# =====
|
||||
class _GpioInput:
|
||||
def __init__(self, channel: str, config: Section, reader: gpio.BatchReader) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
channel: str,
|
||||
config: Section,
|
||||
driver: BaseUserGpioDriver,
|
||||
) -> None:
|
||||
|
||||
self.__channel = channel
|
||||
self.__pin: int = config.pin
|
||||
self.__inverted: bool = config.inverted
|
||||
|
||||
self.__reader = reader
|
||||
self.__driver = driver
|
||||
self.__driver.register_input(self.__pin)
|
||||
|
||||
def get_scheme(self) -> Dict:
|
||||
return {}
|
||||
|
||||
def get_state(self) -> Dict:
|
||||
return {"state": (self.__reader.get(self.__pin) ^ self.__inverted)}
|
||||
(online, state) = (True, False)
|
||||
try:
|
||||
state = (self.__driver.read(self.__pin) ^ self.__inverted)
|
||||
except GpioDriverOfflineError:
|
||||
online = False
|
||||
return {
|
||||
"online": online,
|
||||
"state": state,
|
||||
}
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Input({self.__channel}, pin={self.__pin})"
|
||||
return f"Input({self.__channel}, driver={self.__driver.get_instance_name()}, pin={self.__pin})"
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class _GpioOutput: # pylint: disable=too-many-instance-attributes
|
||||
def __init__(self, channel: str, config: Section, notifier: aiotools.AioNotifier) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
channel: str,
|
||||
config: Section,
|
||||
driver: BaseUserGpioDriver,
|
||||
notifier: aiotools.AioNotifier,
|
||||
) -> None:
|
||||
|
||||
self.__channel = channel
|
||||
self.__pin: int = config.pin
|
||||
self.__inverted: bool = config.inverted
|
||||
self.__initial: bool = config.initial
|
||||
|
||||
self.__switch: bool = config.switch
|
||||
|
||||
@@ -95,6 +122,9 @@ class _GpioOutput: # pylint: disable=too-many-instance-attributes
|
||||
|
||||
self.__busy_delay: float = config.busy_delay
|
||||
|
||||
self.__driver = driver
|
||||
self.__driver.register_output(self.__pin, (config.initial ^ config.inverted))
|
||||
|
||||
self.__region = aiotools.AioExclusiveRegion(GpioChannelIsBusyError, notifier)
|
||||
|
||||
def get_scheme(self) -> Dict:
|
||||
@@ -105,20 +135,31 @@ class _GpioOutput: # pylint: disable=too-many-instance-attributes
|
||||
"min_delay": (self.__min_pulse_delay if self.__pulse_delay else 0),
|
||||
"max_delay": (self.__max_pulse_delay if self.__pulse_delay else 0),
|
||||
},
|
||||
"hw": {
|
||||
"driver": self.__driver.get_instance_name(),
|
||||
"pin": self.__pin,
|
||||
},
|
||||
}
|
||||
|
||||
def get_state(self) -> Dict:
|
||||
busy = self.__region.is_busy()
|
||||
(online, state) = (True, False)
|
||||
if not busy:
|
||||
try:
|
||||
state = self.__read()
|
||||
except GpioDriverOfflineError:
|
||||
online = False
|
||||
return {
|
||||
"state": (self.__read() if not busy else False),
|
||||
"online": online,
|
||||
"state": state,
|
||||
"busy": busy,
|
||||
}
|
||||
|
||||
def cleanup(self) -> None:
|
||||
try:
|
||||
gpio.write(self.__pin, False)
|
||||
self.__driver.write(self.__pin, (self.__initial ^ self.__inverted))
|
||||
except Exception:
|
||||
get_logger().exception("Can't cleanup GPIO %s", self)
|
||||
get_logger().exception("Can't cleanup %s", self)
|
||||
|
||||
async def switch(self, state: bool) -> bool:
|
||||
if not self.__switch:
|
||||
@@ -126,21 +167,25 @@ class _GpioOutput: # pylint: disable=too-many-instance-attributes
|
||||
async with self.__region:
|
||||
if state != self.__read():
|
||||
self.__write(state)
|
||||
get_logger(0).info("Switched %s to %d", self, state)
|
||||
get_logger(0).info("Switched %s to state=%d", self, state)
|
||||
await asyncio.sleep(self.__busy_delay)
|
||||
return True
|
||||
await asyncio.sleep(self.__busy_delay)
|
||||
return False
|
||||
|
||||
@aiotools.atomic
|
||||
async def pulse(self, delay: float) -> None:
|
||||
async def pulse(self, delay: float, wait: bool) -> None:
|
||||
if not self.__pulse_delay:
|
||||
raise GpioPulseNotSupported()
|
||||
delay = min(max((delay or self.__pulse_delay), self.__min_pulse_delay), self.__max_pulse_delay)
|
||||
await aiotools.run_region_task(
|
||||
f"Can't perform pulse of {self} or operation was not completed",
|
||||
self.__region, self.__inner_pulse, delay,
|
||||
)
|
||||
if wait:
|
||||
async with self.__region:
|
||||
await self.__inner_pulse(delay)
|
||||
else:
|
||||
await aiotools.run_region_task(
|
||||
f"Can't perform pulse of {self} or operation was not completed",
|
||||
self.__region, self.__inner_pulse, delay,
|
||||
)
|
||||
|
||||
@aiotools.atomic
|
||||
async def __inner_pulse(self, delay: float) -> None:
|
||||
@@ -153,13 +198,13 @@ class _GpioOutput: # pylint: disable=too-many-instance-attributes
|
||||
get_logger(0).info("Pulsed %s with delay=%.2f", self, delay)
|
||||
|
||||
def __read(self) -> bool:
|
||||
return (gpio.read(self.__pin) ^ self.__inverted)
|
||||
return (self.__driver.read(self.__pin) ^ self.__inverted)
|
||||
|
||||
def __write(self, state: bool) -> None:
|
||||
gpio.write(self.__pin, (state ^ self.__inverted))
|
||||
self.__driver.write(self.__pin, (state ^ self.__inverted))
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Output({self.__channel}, pin={self.__pin})"
|
||||
return f"Output({self.__channel}, driver={self.__driver.get_instance_name()}, pin={self.__pin})"
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
@@ -170,27 +215,23 @@ class UserGpio:
|
||||
self.__view = config.view
|
||||
|
||||
self.__state_notifier = aiotools.AioNotifier()
|
||||
self.__reader = gpio.BatchReader(
|
||||
pins=[
|
||||
(
|
||||
gpio.set_input(ch_config.pin)
|
||||
if ch_config.mode == "input" else
|
||||
gpio.set_output(ch_config.pin, (ch_config.initial ^ ch_config.inverted))
|
||||
)
|
||||
for ch_config in config.scheme.values()
|
||||
],
|
||||
interval=config.state_poll,
|
||||
notifier=self.__state_notifier,
|
||||
)
|
||||
|
||||
self.__drivers = {
|
||||
driver: get_ugpio_driver_class(drv_config.type)(**drv_config._unpack(ignore=["type"]))
|
||||
for (driver, drv_config) in config.drivers.items()
|
||||
}
|
||||
|
||||
self.__inputs: Dict[str, _GpioInput] = {}
|
||||
self.__outputs: Dict[str, _GpioOutput] = {}
|
||||
|
||||
for (channel, ch_config) in sorted(config.scheme.items(), key=operator.itemgetter(0)):
|
||||
driver = self.__drivers.get(ch_config.driver)
|
||||
if driver is None:
|
||||
raise RuntimeError(f"Missing User-GPIO driver configuration: {ch_config.driver}")
|
||||
if ch_config.mode == "input":
|
||||
self.__inputs[channel] = _GpioInput(channel, ch_config, self.__reader)
|
||||
self.__inputs[channel] = _GpioInput(channel, ch_config, driver)
|
||||
else: # output:
|
||||
self.__outputs[channel] = _GpioOutput(channel, ch_config, self.__state_notifier)
|
||||
self.__outputs[channel] = _GpioOutput(channel, ch_config, driver, self.__state_notifier)
|
||||
|
||||
async def get_model(self) -> Dict:
|
||||
return {
|
||||
@@ -216,13 +257,26 @@ class UserGpio:
|
||||
prev_state = state
|
||||
await self.__state_notifier.wait()
|
||||
|
||||
def sysprep(self) -> None:
|
||||
get_logger().info("Preparing User-GPIO drivers ...")
|
||||
for (_, driver) in sorted(self.__drivers.items(), key=operator.itemgetter(0)):
|
||||
driver.prepare(self.__state_notifier)
|
||||
|
||||
async def systask(self) -> None:
|
||||
get_logger(0).info("Polling User-GPIO inputs ...")
|
||||
await self.__reader.poll()
|
||||
get_logger(0).info("Running User-GPIO drivers ...")
|
||||
await asyncio.gather(*[
|
||||
driver.run()
|
||||
for (_, driver) in sorted(self.__drivers.items(), key=operator.itemgetter(0))
|
||||
])
|
||||
|
||||
async def cleanup(self) -> None:
|
||||
for gout in self.__outputs.values():
|
||||
gout.cleanup()
|
||||
for driver in self.__drivers.values():
|
||||
try:
|
||||
driver.cleanup()
|
||||
except Exception:
|
||||
get_logger().exception("Can't cleanup driver %r", driver.get_instance_name())
|
||||
|
||||
async def switch(self, channel: str, state: bool) -> bool:
|
||||
gout = self.__outputs.get(channel)
|
||||
|
||||
Reference in New Issue
Block a user