optional msd and atx

This commit is contained in:
Devaev Maxim 2019-03-18 02:00:58 +03:00
parent d1a6d79af5
commit d049400a97
10 changed files with 151 additions and 62 deletions

View File

@ -103,7 +103,7 @@ def _as_pin(pin: int) -> int:
def _as_optional_pin(pin: int) -> int:
if not isinstance(pin, int) or pin == 0:
if not isinstance(pin, int) or pin < -1:
raise ValueError("Invalid optional pin number")
return pin
@ -168,11 +168,13 @@ def _get_config_scheme() -> Dict:
},
"atx": {
"power_led_pin": Option(0, type=_as_pin),
"hdd_led_pin": Option(0, type=_as_pin),
"enabled": Option(True),
"power_led_pin": Option(-1, type=_as_optional_pin),
"hdd_led_pin": Option(-1, type=_as_optional_pin),
"power_switch_pin": Option(-1, type=_as_optional_pin),
"reset_switch_pin": Option(-1, type=_as_optional_pin),
"power_switch_pin": Option(0, type=_as_pin),
"reset_switch_pin": Option(0, type=_as_pin),
"click_delay": Option(0.1),
"long_click_delay": Option(5.5),
@ -180,9 +182,12 @@ def _get_config_scheme() -> Dict:
},
"msd": {
"target_pin": Option(0, type=_as_pin),
"reset_pin": Option(0, type=_as_pin),
"device": Option("", type=_as_path, rename="device_path"),
"enabled": Option(True),
"target_pin": Option(-1, type=_as_optional_pin),
"reset_pin": Option(-1, type=_as_optional_pin),
"device": Option("", type=_as_optional_path, rename="device_path"),
"init_delay": Option(2.0),
"reset_delay": Option(1.0),
"write_meta": Option(True),
@ -190,8 +195,8 @@ def _get_config_scheme() -> Dict:
},
"streamer": {
"cap_pin": Option(-1, type=_as_optional_pin),
"conv_pin": Option(-1, type=_as_optional_pin),
"cap_pin": Option(0, type=_as_optional_pin),
"conv_pin": Option(0, type=_as_optional_pin),
"sync_delay": Option(1.0),
"init_delay": Option(1.0),

View File

@ -23,7 +23,9 @@
import asyncio
from typing import Dict
from typing import Callable
from typing import AsyncGenerator
from typing import Any
from ...logging import get_logger
@ -32,13 +34,36 @@ from ... import gpio
# =====
class AtxIsBusy(aioregion.RegionIsBusyError):
class AtxError(Exception):
pass
class AtxOperationError(AtxError):
pass
class AtxDisabledError(AtxOperationError):
def __init__(self) -> None:
super().__init__("ATX is disabled")
class AtxIsBusyError(AtxOperationError, aioregion.RegionIsBusyError):
pass
def _atx_working(method: Callable) -> Callable:
async def wrap(self: "Atx", *args: Any, **kwargs: Any) -> Any:
if not self._enabled: # pylint: disable=protected-access
raise AtxDisabledError()
return (await method(self, *args, **kwargs))
return wrap
class Atx: # pylint: disable=too-many-instance-attributes
def __init__(
self,
enabled: bool,
power_led_pin: int,
hdd_led_pin: int,
@ -50,31 +75,43 @@ class Atx: # pylint: disable=too-many-instance-attributes
state_poll: float,
) -> None:
self.__power_led_pin = gpio.set_input(power_led_pin)
self.__hdd_led_pin = gpio.set_input(hdd_led_pin)
self._enabled = enabled
if self._enabled:
self.__power_led_pin = gpio.set_input(power_led_pin)
self.__hdd_led_pin = gpio.set_input(hdd_led_pin)
self.__power_switch_pin = gpio.set_output(power_switch_pin)
self.__reset_switch_pin = gpio.set_output(reset_switch_pin)
else:
self.__power_led_pin = -1
self.__hdd_led_pin = -1
self.__power_switch_pin = -1
self.__reset_switch_pin = -1
self.__power_switch_pin = gpio.set_output(power_switch_pin)
self.__reset_switch_pin = gpio.set_output(reset_switch_pin)
self.__click_delay = click_delay
self.__long_click_delay = long_click_delay
self.__state_poll = state_poll
self.__region = aioregion.AioExclusiveRegion(AtxIsBusy)
self.__region = aioregion.AioExclusiveRegion(AtxIsBusyError)
def get_state(self) -> Dict:
return {
"enabled": self._enabled,
"busy": self.__region.is_busy(),
"leds": {
"power": (not gpio.read(self.__power_led_pin)),
"hdd": (not gpio.read(self.__hdd_led_pin)),
"power": ((not gpio.read(self.__power_led_pin)) if self._enabled else False),
"hdd": ((not gpio.read(self.__hdd_led_pin)) if self._enabled else False),
},
}
async def poll_state(self) -> AsyncGenerator[Dict, None]:
while True:
yield self.get_state()
await asyncio.sleep(self.__state_poll)
if self._enabled:
yield self.get_state()
await asyncio.sleep(self.__state_poll)
else:
await asyncio.sleep(60)
async def click_power(self) -> None:
get_logger().info("Clicking power ...")
@ -88,6 +125,7 @@ class Atx: # pylint: disable=too-many-instance-attributes
get_logger().info("Clicking reset")
await self.__click(self.__reset_switch_pin, self.__click_delay)
@_atx_working
async def __click(self, pin: int, delay: float) -> None:
self.__region.enter()
asyncio.ensure_future(self.__inner_click(pin, delay))

View File

@ -54,9 +54,14 @@ class MsdOperationError(MsdError):
pass
class MsdIsNotOperationalError(MsdOperationError):
class MsdDisabledError(MsdOperationError):
def __init__(self) -> None:
super().__init__("Missing path for mass-storage device")
super().__init__("Mass-storage device is disabled")
class MsdOfflineError(MsdOperationError):
def __init__(self) -> None:
super().__init__("Mass-storage device is not found")
class MsdAlreadyConnectedToPcError(MsdOperationError):
@ -69,7 +74,7 @@ class MsdAlreadyConnectedToKvmError(MsdOperationError):
super().__init__("Mass-storage is already connected to KVM")
class MsdIsNotConnectedToKvmError(MsdOperationError):
class MsdNotConnectedToKvmError(MsdOperationError):
def __init__(self) -> None:
super().__init__("Mass-storage is not connected to KVM")
@ -177,10 +182,12 @@ def _explore_device(device_path: str) -> Optional[_MassStorageDeviceInfo]:
)
def _msd_operated(method: Callable) -> Callable:
def _msd_working(method: Callable) -> Callable:
async def wrap(self: "MassStorageDevice", *args: Any, **kwargs: Any) -> Any:
if not self._enabled: # pylint: disable=protected-access
raise MsdDisabledError()
if not self._device_path: # pylint: disable=protected-access
MsdIsNotOperationalError()
raise MsdOfflineError()
return (await method(self, *args, **kwargs))
return wrap
@ -189,6 +196,8 @@ def _msd_operated(method: Callable) -> Callable:
class MassStorageDevice: # pylint: disable=too-many-instance-attributes
def __init__(
self,
enabled: bool,
target_pin: int,
reset_pin: int,
@ -201,8 +210,15 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
loop: asyncio.AbstractEventLoop,
) -> None:
self.__target_pin = gpio.set_output(target_pin)
self.__reset_pin = gpio.set_output(reset_pin)
self._enabled = enabled
if self._enabled:
self.__target_pin = gpio.set_output(target_pin)
self.__reset_pin = gpio.set_output(reset_pin)
assert bool(device_path)
else:
self.__target_pin = -1
self.__reset_pin = -1
self._device_path = device_path
self.__init_delay = init_delay
@ -221,7 +237,7 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
self.__state_queue: asyncio.queues.Queue = asyncio.Queue()
logger = get_logger(0)
if self._device_path:
if self._enabled:
logger.info("Using %r as mass-storage device", self._device_path)
try:
logger.info("Enabled image metadata writing")
@ -231,10 +247,10 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
log = logger.error
else:
log = logger.exception
log("Mass-storage device is not operational: %s", err)
log("Mass-storage device is offline: %s", err)
self._device_path = ""
else:
logger.warning("Mass-storage device is not operational")
logger.info("Mass-storage device is disabled")
def get_state(self) -> Dict:
info = (self.__saved_device_info._asdict() if self.__saved_device_info else None)
@ -243,11 +259,12 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
info["image"] = (info["image"]._asdict() if info["image"] else None)
connected_to: Optional[str] = None
if self._device_path:
if self._enabled and self._device_path:
connected_to = ("kvm" if self.__device_info else "server")
return {
"in_operate": bool(self._device_path),
"enabled": self._enabled,
"online": (self._enabled and bool(self._device_path)),
"connected_to": connected_to,
"busy": bool(self.__device_file),
"written": self.__written,
@ -256,14 +273,18 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
async def poll_state(self) -> AsyncGenerator[Dict, None]:
while True:
yield (await self.__state_queue.get())
if self._enabled:
yield (await self.__state_queue.get())
else:
await asyncio.sleep(60)
async def cleanup(self) -> None:
await self.__close_device_file()
gpio.write(self.__target_pin, False)
gpio.write(self.__reset_pin, False)
if self._enabled:
await self.__close_device_file()
gpio.write(self.__target_pin, False)
gpio.write(self.__reset_pin, False)
@_msd_operated
@_msd_working
async def connect_to_kvm(self, no_delay: bool=False) -> Dict:
with self.__region:
if self.__device_info:
@ -277,7 +298,7 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
get_logger().info("Mass-storage device switched to KVM: %s", self.__device_info)
return state
@_msd_operated
@_msd_working
async def connect_to_pc(self) -> Dict:
with self.__region:
if not self.__device_info:
@ -289,7 +310,7 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
get_logger().info("Mass-storage device switched to Server")
return state
@_msd_operated
@_msd_working
async def reset(self) -> None:
with self.__region:
get_logger().info("Mass-storage device reset")
@ -298,12 +319,12 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
gpio.write(self.__reset_pin, False)
await self.__state_queue.put(self.get_state())
@_msd_operated
@_msd_working
async def __aenter__(self) -> "MassStorageDevice":
self.__region.enter()
try:
if not self.__device_info:
raise MsdIsNotConnectedToKvmError()
raise MsdNotConnectedToKvmError()
self.__device_file = await aiofiles.open(self.__device_info.path, mode="w+b", buffering=0)
self.__written = 0
return self

View File

@ -51,6 +51,7 @@ from .auth import AuthManager
from .info import InfoManager
from .logreader import LogReader
from .hid import Hid
from .atx import AtxOperationError
from .atx import Atx
from .msd import MsdOperationError
from .msd import MassStorageDevice
@ -148,7 +149,7 @@ def _exposed(http_method: str, path: str, auth_required: bool=True) -> Callable:
except RegionIsBusyError as err:
return _json_exception(err, 409)
except (BadRequestError, MsdOperationError) as err:
except (BadRequestError, AtxOperationError, MsdOperationError) as err:
return _json_exception(err, 400)
except UnauthorizedError as err:
return _json_exception(err, 401)

View File

@ -63,8 +63,8 @@ class Streamer: # pylint: disable=too-many-instance-attributes
loop: asyncio.AbstractEventLoop,
) -> None:
self.__cap_pin = (gpio.set_output(cap_pin) if cap_pin > 0 else cap_pin)
self.__conv_pin = (gpio.set_output(conv_pin) if conv_pin > 0 else conv_pin)
self.__cap_pin = (gpio.set_output(cap_pin) if cap_pin > 0 else 0)
self.__conv_pin = (gpio.set_output(conv_pin) if conv_pin > 0 else 0)
self.__sync_delay = sync_delay
self.__init_delay = init_delay

View File

@ -43,18 +43,22 @@ def bcm() -> Generator[None, None, None]:
def set_output(pin: int, initial: bool=False) -> int:
assert pin > 0, pin
GPIO.setup(pin, GPIO.OUT, initial=initial)
return pin
def set_input(pin: int) -> int:
assert pin > 0, pin
GPIO.setup(pin, GPIO.IN)
return pin
def read(pin: int) -> bool:
assert pin > 0, pin
return bool(GPIO.input(pin))
def write(pin: int, flag: bool) -> None:
assert pin > 0, pin
GPIO.output(pin, flag)

View File

@ -129,7 +129,7 @@
<hr>
<button disabled data-force-hide-menu id="stream-reset-button">&bull; Reset stream</button>
<button disabled data-force-hide-menu id="hid-reset-button">&bull; Reset keyboard &amp; mouse</button>
<button disabled data-force-hide-menu id="msd-reset-button">&bull; Reset mass storage</button>
<button disabled data-force-hide-menu id="msd-reset-button" class="feature-disabled">&bull; Reset mass storage</button>
</div>
<hr>
<div class="menu-item-content-buttons">
@ -138,7 +138,7 @@
</div>
</li>
<li class="menu-right-items">
<li id="atx-dropdown" class="menu-right-items feature-disabled">
<a class="menu-item" href="#">
<img data-dont-hide-menu id="atx-power-led" class="led-gray" src="../share/svg/atx-power-led.svg" />
<img data-dont-hide-menu id="atx-hdd-led" class="led-gray" src="../share/svg/atx-hdd-led.svg" />
@ -152,18 +152,18 @@
</div>
</li>
<li class="menu-right-items">
<li id="msd-dropdown" class="menu-right-items feature-disabled">
<a class="menu-item" href="#">
<img data-dont-hide-menu id="msd-led" class="led-gray" src="../share/svg/msd-led.svg" />
Mass Storage &#8628;
</a>
<div data-dont-hide-menu id="msd-menu" class="menu-item-content">
<div id="msd-not-in-operate" class="msd-message">
<div id="msd-offline" class="msd-message">
<div class="menu-item-content-text">
<table>
<tr>
<td><img src="../share/svg/warning.svg" /></td>
<td><b>Mass Storage Device is not operational</b></td>
<td><b>Mass Storage Device is offline</b></td>
</tr>
</table>
</div>

View File

@ -208,3 +208,7 @@ ul.footer li.footer-right {
ul.footer li a {
color: var(--cs-page-obscure-fg);
}
.feature-disabled {
display: none;
}

View File

@ -37,6 +37,14 @@ function Atx() {
/************************************************************************/
self.setState = function(state) {
if (state) {
if (state.enabled) {
$("atx-dropdown").classList.remove("feature-disabled");
} else {
$("atx-dropdown").classList.add("feature-disabled");
}
}
$("atx-power-led").className = ((state && state.leds.power) ? "led-green" : "led-gray");
$("atx-hdd-led").className = ((state && state.leds.hdd) ? "led-red" : "led-gray");

View File

@ -112,6 +112,14 @@ function Msd() {
var __applyState = function() {
if (__state) {
if (__state.enabled) {
$("msd-dropdown").classList.remove("feature-disabled");
$("msd-reset-button").classList.remove("feature-disabled");
} else {
$("msd-dropdown").classList.add("feature-disabled");
$("msd-reset-button").classList.add("feature-disabled");
}
if (__state.connected_to === "server") {
$("msd-another-another-user-uploads").style.display = "none";
$("msd-led").className = "led-green";
@ -125,29 +133,29 @@ function Msd() {
} else {
$("msd-another-another-user-uploads").style.display = "none";
$("msd-led").className = "led-gray";
if (__state.in_operate) {
if (__state.online) {
$("msd-status").innerHTML = $("msd-led").title = "Connected to KVM";
} else {
$("msd-status").innerHTML = $("msd-led").title = "Unavailable";
}
}
$("msd-not-in-operate").style.display = (__state.in_operate ? "none" : "block");
$("msd-offline").style.display = (__state.online ? "none" : "block");
$("msd-current-image-broken").style.display = (
__state.in_operate && __state.info.image &&
__state.online && __state.info.image &&
!__state.info.image.complete && !__state.busy ? "block" : "none"
);
$("msd-current-image-name").innerHTML = (__state.in_operate && __state.info.image ? __state.info.image.name : "None");
$("msd-current-image-size").innerHTML = (__state.in_operate && __state.info.image ? __formatSize(__state.info.image.size) : "None");
$("msd-storage-size").innerHTML = (__state.in_operate ? __formatSize(__state.info.size) : "Unavailable");
$("msd-current-image-name").innerHTML = (__state.online && __state.info.image ? __state.info.image.name : "None");
$("msd-current-image-size").innerHTML = (__state.online && __state.info.image ? __formatSize(__state.info.image.size) : "None");
$("msd-storage-size").innerHTML = (__state.online ? __formatSize(__state.info.size) : "Unavailable");
wm.switchDisabled($("msd-switch-to-kvm-button"), (!__state.in_operate || __state.connected_to === "kvm" || __state.busy));
wm.switchDisabled($("msd-switch-to-server-button"), (!__state.in_operate || __state.connected_to === "server" || __state.busy));
wm.switchDisabled($("msd-select-new-image-button"), (!__state.in_operate || __state.connected_to !== "kvm" || __state.busy || __upload_http));
wm.switchDisabled($("msd-upload-new-image-button"), (!__state.in_operate || __state.connected_to !== "kvm" || __state.busy || !__image_file));
wm.switchDisabled($("msd-abort-uploading-button"), (!__state.in_operate || !__upload_http));
wm.switchDisabled($("msd-reset-button"), (!__state.in_operate || __upload_http));
wm.switchDisabled($("msd-switch-to-kvm-button"), (!__state.online || __state.connected_to === "kvm" || __state.busy));
wm.switchDisabled($("msd-switch-to-server-button"), (!__state.online || __state.connected_to === "server" || __state.busy));
wm.switchDisabled($("msd-select-new-image-button"), (!__state.online || __state.connected_to !== "kvm" || __state.busy || __upload_http));
wm.switchDisabled($("msd-upload-new-image-button"), (!__state.online || __state.connected_to !== "kvm" || __state.busy || !__image_file));
wm.switchDisabled($("msd-abort-uploading-button"), (!__state.online || !__upload_http));
wm.switchDisabled($("msd-reset-button"), (!__state.online || __upload_http));
$("msd-new-image").style.display = (__image_file ? "block" : "none");
$("msd-progress").setAttribute("data-label", "Waiting for upload ...");
@ -160,7 +168,7 @@ function Msd() {
$("msd-led").className = "led-gray";
$("msd-status").innerHTML = "";
$("msd-led").title = "";
$("msd-not-in-operate").style.display = "none";
$("msd-offline").style.display = "none";
$("msd-current-image-broken").style.display = "none";
$("msd-current-image-name").innerHTML = "";
$("msd-current-image-size").innerHTML = "";