Auto-format Python sources with black, fix all issues reported by flake8 (#1010)

* Update config for black and flake8
* Auto-format Python sources with black
* Fix issues reported by flake8
* Exclude protobuf files from black
* Address formatting feedback
This commit is contained in:
nucleogenic
2022-11-30 05:19:17 +00:00
committed by GitHub
parent 5afc6b911f
commit 315ef9f248
44 changed files with 1073 additions and 725 deletions
+1
View File
@@ -7,6 +7,7 @@ from ctrlboard_hw.ctrlboard_hw_constants import CtrlBoardHardwareConstants
# pylint: disable=too-few-public-methods
class CtrlboardConfig:
"""Class for central RaSCSI control board configuration parameters"""
ROTATION = 0
WIDTH = 128
HEIGHT = 64
@@ -19,8 +19,12 @@ from rascsi_menu_controller import RascsiMenuController
class CtrlBoardMenuUpdateEventHandler(Observer):
"""Class interfacing the menu controller the RaSCSI Control Board hardware."""
def __init__(self, menu_controller: RascsiMenuController, sock_cmd: SocketCmds,
ractl_cmd: RaCtlCmds):
def __init__(
self,
menu_controller: RascsiMenuController,
sock_cmd: SocketCmds,
ractl_cmd: RaCtlCmds,
):
self.message = None
self._menu_controller = menu_controller
self._menu_renderer_config = self._menu_controller.get_menu_renderer().get_config()
@@ -73,16 +77,18 @@ class CtrlBoardMenuUpdateEventHandler(Observer):
def handle_button1(self):
"""Method for handling the first cycle button (cycle profiles)"""
if self.rascsi_profile_cycler is None:
self.rascsi_profile_cycler = RascsiProfileCycler(self._menu_controller, self.sock_cmd,
self.ractl_cmd, return_entry=True)
self.rascsi_profile_cycler = RascsiProfileCycler(
self._menu_controller, self.sock_cmd, self.ractl_cmd, return_entry=True
)
else:
self.rascsi_profile_cycler.cycle()
def handle_button2(self):
"""Method for handling the second cycle button (cycle shutdown)"""
if self.rascsi_shutdown_cycler is None:
self.rascsi_shutdown_cycler = RascsiShutdownCycler(self._menu_controller, self.sock_cmd,
self.ractl_cmd)
self.rascsi_shutdown_cycler = RascsiShutdownCycler(
self._menu_controller, self.sock_cmd, self.ractl_cmd
)
else:
self.rascsi_shutdown_cycler.cycle()
@@ -100,8 +106,10 @@ class CtrlBoardMenuUpdateEventHandler(Observer):
handler_function(info_object)
except AttributeError:
log = logging.getLogger(__name__)
log.error("Handler function [%s] not found or returned an error. Skipping.",
str(handler_function_name))
log.error(
"Handler function [%s] not found or returned an error. Skipping.",
str(handler_function_name),
)
# noinspection PyUnusedLocal
# pylint: disable=unused-argument
@@ -109,9 +117,11 @@ class CtrlBoardMenuUpdateEventHandler(Observer):
"""Method handles the rotary button press with the scsi list to open the action menu."""
context_object = self._menu_controller.get_active_menu().get_current_info_object()
self.context_stack.append(context_object)
self._menu_controller.segue(CtrlBoardMenuBuilder.ACTION_MENU, context_object=context_object,
transition_attributes=self._menu_renderer_config.
transition_attributes_left)
self._menu_controller.segue(
CtrlBoardMenuBuilder.ACTION_MENU,
context_object=context_object,
transition_attributes=self._menu_renderer_config.transition_attributes_left,
)
# noinspection PyUnusedLocal
# pylint: disable=unused-argument
@@ -119,9 +129,10 @@ class CtrlBoardMenuUpdateEventHandler(Observer):
"""Method handles the rotary button press to return from the
action menu to the scsi list."""
self.context_stack.pop()
self._menu_controller.segue(CtrlBoardMenuBuilder.SCSI_ID_MENU,
transition_attributes=self._menu_renderer_config.
transition_attributes_right)
self._menu_controller.segue(
CtrlBoardMenuBuilder.SCSI_ID_MENU,
transition_attributes=self._menu_renderer_config.transition_attributes_right,
)
# noinspection PyUnusedLocal
# pylint: disable=unused-argument
@@ -129,9 +140,11 @@ class CtrlBoardMenuUpdateEventHandler(Observer):
"""Method handles the rotary button press on attach in the action menu."""
context_object = self._menu_controller.get_active_menu().context_object
self.context_stack.append(context_object)
self._menu_controller.segue(CtrlBoardMenuBuilder.IMAGES_MENU, context_object=context_object,
transition_attributes=self._menu_renderer_config.
transition_attributes_left)
self._menu_controller.segue(
CtrlBoardMenuBuilder.IMAGES_MENU,
context_object=context_object,
transition_attributes=self._menu_renderer_config.transition_attributes_left,
)
# noinspection PyUnusedLocal
def handle_action_menu_slot_detacheject(self, info_object):
@@ -139,39 +152,43 @@ class CtrlBoardMenuUpdateEventHandler(Observer):
context_object = self._menu_controller.get_active_menu().context_object
self.detach_eject_scsi_id()
self.context_stack = []
self._menu_controller.segue(CtrlBoardMenuBuilder.SCSI_ID_MENU,
context_object=context_object,
transition_attributes=self._menu_renderer_config.
transition_attributes_right)
self._menu_controller.segue(
CtrlBoardMenuBuilder.SCSI_ID_MENU,
context_object=context_object,
transition_attributes=self._menu_renderer_config.transition_attributes_right,
)
# noinspection PyUnusedLocal
def handle_action_menu_slot_info(self, info_object):
"""Method handles the rotary button press on 'Info' in the action menu."""
context_object = self._menu_controller.get_active_menu().context_object
self.context_stack.append(context_object)
self._menu_controller.segue(CtrlBoardMenuBuilder.DEVICEINFO_MENU,
transition_attributes=self._menu_renderer_config.
transition_attributes_left,
context_object=context_object)
self._menu_controller.segue(
CtrlBoardMenuBuilder.DEVICEINFO_MENU,
transition_attributes=self._menu_renderer_config.transition_attributes_left,
context_object=context_object,
)
# noinspection PyUnusedLocal
def handle_device_info_menu_return(self, info_object):
"""Method handles the rotary button press on 'Return' in the info menu."""
self.context_stack.pop()
context_object = self._menu_controller.get_active_menu().context_object
self._menu_controller.segue(CtrlBoardMenuBuilder.ACTION_MENU,
transition_attributes=self._menu_renderer_config.
transition_attributes_right,
context_object=context_object)
self._menu_controller.segue(
CtrlBoardMenuBuilder.ACTION_MENU,
transition_attributes=self._menu_renderer_config.transition_attributes_right,
context_object=context_object,
)
# noinspection PyUnusedLocal
def handle_action_menu_loadprofile(self, info_object):
"""Method handles the rotary button press on 'Load Profile' in the action menu."""
context_object = self._menu_controller.get_active_menu().context_object
self.context_stack.append(context_object)
self._menu_controller.segue(CtrlBoardMenuBuilder.PROFILES_MENU,
transition_attributes=self._menu_renderer_config.
transition_attributes_left)
self._menu_controller.segue(
CtrlBoardMenuBuilder.PROFILES_MENU,
transition_attributes=self._menu_renderer_config.transition_attributes_left,
)
# noinspection PyUnusedLocal
def handle_profiles_menu_loadprofile(self, info_object):
@@ -186,28 +203,31 @@ class CtrlBoardMenuUpdateEventHandler(Observer):
self._menu_controller.show_message("Loading failed!")
self.context_stack = []
self._menu_controller.segue(CtrlBoardMenuBuilder.SCSI_ID_MENU,
transition_attributes=self._menu_renderer_config.
transition_attributes_right)
self._menu_controller.segue(
CtrlBoardMenuBuilder.SCSI_ID_MENU,
transition_attributes=self._menu_renderer_config.transition_attributes_right,
)
# noinspection PyUnusedLocal
def handle_action_menu_shutdown(self, info_object):
"""Method handles the rotary button press on 'Shutdown' in the action menu."""
self.ractl_cmd.shutdown("system")
self._menu_controller.show_message("Shutting down!", 150)
self._menu_controller.segue(CtrlBoardMenuBuilder.SCSI_ID_MENU,
transition_attributes=self._menu_renderer_config.
transition_attributes_right)
self._menu_controller.segue(
CtrlBoardMenuBuilder.SCSI_ID_MENU,
transition_attributes=self._menu_renderer_config.transition_attributes_right,
)
# noinspection PyUnusedLocal
def handle_images_menu_return(self, info_object):
"""Method handles the rotary button press on 'Return' in the image selection menu
(through attach/insert)."""
context_object = self.context_stack.pop()
self._menu_controller.segue(CtrlBoardMenuBuilder.ACTION_MENU,
context_object=context_object,
transition_attributes=self._menu_renderer_config.
transition_attributes_right)
self._menu_controller.segue(
CtrlBoardMenuBuilder.ACTION_MENU,
context_object=context_object,
transition_attributes=self._menu_renderer_config.transition_attributes_right,
)
def handle_images_menu_image_attachinsert(self, info_object):
"""Method handles the rotary button press on an image in the image selection menu
@@ -215,10 +235,11 @@ class CtrlBoardMenuUpdateEventHandler(Observer):
context_object = self._menu_controller.get_active_menu().context_object
self.attach_insert_scsi_id(info_object)
self.context_stack = []
self._menu_controller.segue(CtrlBoardMenuBuilder.SCSI_ID_MENU,
context_object=context_object,
transition_attributes=self._menu_renderer_config.
transition_attributes_right)
self._menu_controller.segue(
CtrlBoardMenuBuilder.SCSI_ID_MENU,
context_object=context_object,
transition_attributes=self._menu_renderer_config.transition_attributes_right,
)
def attach_insert_scsi_id(self, info_object):
"""Helper method to attach/insert an image on a scsi id given through the menu context"""
@@ -227,9 +248,9 @@ class CtrlBoardMenuUpdateEventHandler(Observer):
context_object = self._menu_controller.get_active_menu().context_object
scsi_id = context_object["scsi_id"]
params = {"file": image_name}
result = self.ractl_cmd.attach_device(scsi_id=scsi_id,
device_type=device_type,
params=params)
result = self.ractl_cmd.attach_device(
scsi_id=scsi_id, device_type=device_type, params=params
)
if result["status"] is False:
self._menu_controller.show_message("Attach failed!")
@@ -268,7 +289,10 @@ class CtrlBoardMenuUpdateEventHandler(Observer):
self._menu_controller.show_message("Detach failed!")
else:
log = logging.getLogger(__name__)
log.info("Device type '%s' currently unsupported for detach/eject!", str(device_type))
log.info(
"Device type '%s' currently unsupported for detach/eject!",
str(device_type),
)
def show_id_action_message(self, scsi_id, action: str):
"""Helper method for displaying an action message in the case of an exception."""
@@ -8,6 +8,7 @@ from ctrlboard_hw.encoder import Encoder
class CtrlBoardPrintEventHandler(observer.Observer):
"""Class implements a basic event handler that prints button presses from the RaSCSI
Control Board hardware."""
def update(self, updated_object):
if isinstance(updated_object, HardwareButton):
print(updated_object.name + " has been pressed!")
@@ -6,8 +6,13 @@ class RascsiShutdownCycler(Cycler):
"""Class implementing the shutdown cycler for the RaSCSI Control Board UI"""
def __init__(self, menu_controller, sock_cmd, ractl_cmd):
super().__init__(menu_controller, sock_cmd, ractl_cmd, return_entry=True,
empty_messages=False)
super().__init__(
menu_controller,
sock_cmd,
ractl_cmd,
return_entry=True,
empty_messages=False,
)
self.executed_once = False
def populate_cycle_entries(self):
@@ -16,6 +16,7 @@ from observable import Observable
# pylint: disable=too-many-instance-attributes
class CtrlBoardHardware(Observable):
"""Class implements the RaSCSI Control Board hardware and provides an interface to it."""
def __init__(self, display_i2c_address, pca9554_i2c_address, debounce_ms=200):
self.display_i2c_address = display_i2c_address
self.pca9554_i2c_address = pca9554_i2c_address
@@ -33,63 +34,78 @@ class CtrlBoardHardware(Observable):
self.pca_driver = pca9554multiplexer.PCA9554Multiplexer(self.pca9554_i2c_address)
# setup pca9554
self.pca_driver.write_configuration_register_port(CtrlBoardHardwareConstants.
PCA9554_PIN_ENC_A,
PCA9554Multiplexer.PIN_ENABLED_AS_INPUT)
self.pca_driver.write_configuration_register_port(CtrlBoardHardwareConstants.
PCA9554_PIN_ENC_B,
PCA9554Multiplexer.PIN_ENABLED_AS_INPUT)
self.pca_driver.write_configuration_register_port(CtrlBoardHardwareConstants.
PCA9554_PIN_BUTTON_1,
PCA9554Multiplexer.PIN_ENABLED_AS_INPUT)
self.pca_driver.write_configuration_register_port(CtrlBoardHardwareConstants.
PCA9554_PIN_BUTTON_2,
PCA9554Multiplexer.PIN_ENABLED_AS_INPUT)
self.pca_driver.write_configuration_register_port(CtrlBoardHardwareConstants.
PCA9554_PIN_BUTTON_ROTARY,
PCA9554Multiplexer.PIN_ENABLED_AS_INPUT)
self.pca_driver.write_configuration_register_port(CtrlBoardHardwareConstants.
PCA9554_PIN_LED_1,
PCA9554Multiplexer.PIN_ENABLED_AS_OUTPUT)
self.pca_driver.write_configuration_register_port(CtrlBoardHardwareConstants.
PCA9554_PIN_LED_2,
PCA9554Multiplexer.PIN_ENABLED_AS_OUTPUT)
self.pca_driver.write_configuration_register_port(
CtrlBoardHardwareConstants.PCA9554_PIN_ENC_A,
PCA9554Multiplexer.PIN_ENABLED_AS_INPUT,
)
self.pca_driver.write_configuration_register_port(
CtrlBoardHardwareConstants.PCA9554_PIN_ENC_B,
PCA9554Multiplexer.PIN_ENABLED_AS_INPUT,
)
self.pca_driver.write_configuration_register_port(
CtrlBoardHardwareConstants.PCA9554_PIN_BUTTON_1,
PCA9554Multiplexer.PIN_ENABLED_AS_INPUT,
)
self.pca_driver.write_configuration_register_port(
CtrlBoardHardwareConstants.PCA9554_PIN_BUTTON_2,
PCA9554Multiplexer.PIN_ENABLED_AS_INPUT,
)
self.pca_driver.write_configuration_register_port(
CtrlBoardHardwareConstants.PCA9554_PIN_BUTTON_ROTARY,
PCA9554Multiplexer.PIN_ENABLED_AS_INPUT,
)
self.pca_driver.write_configuration_register_port(
CtrlBoardHardwareConstants.PCA9554_PIN_LED_1,
PCA9554Multiplexer.PIN_ENABLED_AS_OUTPUT,
)
self.pca_driver.write_configuration_register_port(
CtrlBoardHardwareConstants.PCA9554_PIN_LED_2,
PCA9554Multiplexer.PIN_ENABLED_AS_OUTPUT,
)
self.input_register_buffer = 0
# pylint: disable=no-member
GPIO.setmode(GPIO.BCM)
GPIO.setup(CtrlBoardHardwareConstants.PI_PIN_INTERRUPT, GPIO.IN)
GPIO.add_event_detect(CtrlBoardHardwareConstants.PI_PIN_INTERRUPT, GPIO.FALLING,
callback=self.button_pressed_callback)
GPIO.add_event_detect(
CtrlBoardHardwareConstants.PI_PIN_INTERRUPT,
GPIO.FALLING,
callback=self.button_pressed_callback,
)
# configure button of the rotary encoder
self.rotary_button = HardwareButton(self.pca_driver,
CtrlBoardHardwareConstants.PCA9554_PIN_BUTTON_ROTARY)
self.rotary_button = HardwareButton(
self.pca_driver, CtrlBoardHardwareConstants.PCA9554_PIN_BUTTON_ROTARY
)
self.rotary_button.state = True
self.rotary_button.name = CtrlBoardHardwareConstants.ROTARY_BUTTON
# configure button 1
self.button1 = HardwareButton(self.pca_driver,
CtrlBoardHardwareConstants.PCA9554_PIN_BUTTON_1)
self.button1 = HardwareButton(
self.pca_driver, CtrlBoardHardwareConstants.PCA9554_PIN_BUTTON_1
)
self.button1.state = True
self.button1.name = CtrlBoardHardwareConstants.BUTTON_1
# configure button 2
self.button2 = HardwareButton(self.pca_driver,
CtrlBoardHardwareConstants.PCA9554_PIN_BUTTON_2)
self.button2 = HardwareButton(
self.pca_driver, CtrlBoardHardwareConstants.PCA9554_PIN_BUTTON_2
)
self.button2.state = True
self.button2.name = CtrlBoardHardwareConstants.BUTTON_2
# configure rotary encoder pin a
self.rotary_a = HardwareButton(self.pca_driver,
CtrlBoardHardwareConstants.PCA9554_PIN_ENC_A)
self.rotary_a = HardwareButton(
self.pca_driver, CtrlBoardHardwareConstants.PCA9554_PIN_ENC_A
)
self.rotary_a.state = True
self.rotary_a.directionalTransition = False
self.rotary_a.name = CtrlBoardHardwareConstants.ROTARY_A
# configure rotary encoder pin b
self.rotary_b = HardwareButton(self.pca_driver,
CtrlBoardHardwareConstants.PCA9554_PIN_ENC_B)
self.rotary_b = HardwareButton(
self.pca_driver, CtrlBoardHardwareConstants.PCA9554_PIN_ENC_B
)
self.rotary_b.state = True
self.rotary_b.directionalTransition = False
self.rotary_b.name = CtrlBoardHardwareConstants.ROTARY_B
@@ -117,7 +133,7 @@ class CtrlBoardHardware(Observable):
# ignore button press if debounce time is not reached
if button.last_press is not None:
elapsed = (time.time_ns() - button.last_press)/1000000
elapsed = (time.time_ns() - button.last_press) / 1000000
if elapsed < self.debounce_ms:
return
@@ -149,8 +165,8 @@ class CtrlBoardHardware(Observable):
@staticmethod
def button_value_shifted_list(input_register_buffer, bit):
"""Helper method for dealing with multiple buffered input registers"""
input_register_buffer_length = int(len(format(input_register_buffer, 'b'))/8)
shiftval = (input_register_buffer_length-1)*8
input_register_buffer_length = int(len(format(input_register_buffer, "b")) / 8)
shiftval = (input_register_buffer_length - 1) * 8
tmp = input_register_buffer >> shiftval
bitmask = 1 << bit
tmp &= bitmask
@@ -162,12 +178,12 @@ class CtrlBoardHardware(Observable):
input_register_buffer = self.input_register_buffer
self.input_register_buffer = 0
input_register_buffer_length = int(len(format(input_register_buffer, 'b'))/8)
input_register_buffer_length = int(len(format(input_register_buffer, "b")) / 8)
if input_register_buffer_length < 1:
return
for i in range(0, input_register_buffer_length):
shiftval = (input_register_buffer_length-1-i)*8
shiftval = (input_register_buffer_length - 1 - i) * 8
input_register = (input_register_buffer >> shiftval) & 0b11111111
rot_a = self.button_value(input_register, 0)
@@ -211,7 +227,7 @@ class CtrlBoardHardware(Observable):
if 2 < _address < 120:
try:
_bus.read_byte(_address)
address = '%02x' % _address
address = "%02x" % _address
detected_i2c_addresses.append(int(address, base=16))
except IOError: # simply skip unsuccessful i2c probes
pass
@@ -223,8 +239,12 @@ class CtrlBoardHardware(Observable):
the expected i2c addresses are detected."""
# pylint: disable=c-extension-no-member
i2c_addresses = self.detect_i2c_devices(smbus.SMBus(1))
return bool((int(self.display_i2c_address) in i2c_addresses and
(int(self.pca9554_i2c_address) in i2c_addresses)))
return bool(
(
int(self.display_i2c_address) in i2c_addresses
and (int(self.pca9554_i2c_address) in i2c_addresses)
)
)
def detect_display(self):
"""Detects whether an i2c display is connected to the RaSCSI hat."""
@@ -4,8 +4,9 @@
# pylint: disable=too-few-public-methods
class CtrlBoardHardwareConstants:
"""Class containing the RaSCSI Control Board hardware constants"""
DISPLAY_I2C_ADDRESS = 0x3c
PCA9554_I2C_ADDRESS = 0x3f
DISPLAY_I2C_ADDRESS = 0x3C
PCA9554_I2C_ADDRESS = 0x3F
PCA9554_PIN_ENC_A = 0
PCA9554_PIN_ENC_B = 1
PCA9554_PIN_BUTTON_1 = 2
@@ -14,7 +15,7 @@ class CtrlBoardHardwareConstants:
PCA9554_PIN_LED_1 = 6
PCA9554_PIN_LED_2 = 7
PI_PIN_INTERRUPT = 9 # BCM
PI_PIN_INTERRUPT = 9 # BCM
BUTTON_1 = "Bt1"
BUTTON_2 = "Bt2"
+17 -5
View File
@@ -6,6 +6,7 @@ from ctrlboard_hw.hardware_button import HardwareButton
class Encoder:
"""Class implementing a detection mechanism to detect the rotary encoder directions
through the i2c multiplexer + interrupt"""
def __init__(self, enc_a: HardwareButton, enc_b: HardwareButton):
self.enc_a = enc_a
self.enc_b = enc_b
@@ -27,16 +28,27 @@ class Encoder:
state |= 0b10000000
# clockwise pattern detection
if (state == 0b11010010 or state == 0b11001000 or state == 0b11011000 or
state == 0b11010001 or state == 0b11011011 or state == 0b11100000 or
state == 0b11001011):
if (
state == 0b11010010
or state == 0b11001000
or state == 0b11011000
or state == 0b11010001
or state == 0b11011011
or state == 0b11100000
or state == 0b11001011
):
self.pos += 1
self.direction = 1
self.state = 0b00000000
return
# counter-clockwise pattern detection
elif (state == 0b11000100 or state == 0b11100100 or state == 0b11100001 or
state == 0b11000111 or state == 0b11100111):
elif (
state == 0b11000100
or state == 0b11100100
or state == 0b11100001
or state == 0b11000111
or state == 0b11100111
):
self.pos -= 1
self.direction = -1
self.state = 0b00000000
@@ -19,8 +19,10 @@ class PCA9554Multiplexer:
try:
self.i2c_bus = smbus.SMBus(1)
if self.read_input_register() is None:
logging.error("PCA9554 initialization test on specified i2c address %s failed",
self.i2c_address)
logging.error(
"PCA9554 initialization test on specified i2c address %s failed",
self.i2c_address,
)
self.i2c_bus = None
except IOError:
logging.error("Could not open the i2c bus.")
@@ -35,8 +37,9 @@ class PCA9554Multiplexer:
if bit_value:
updated_configuration_register = configuration_register | (1 << port_bit)
else:
updated_configuration_register = configuration_register & (0xFF -
(1 << port_bit))
updated_configuration_register = configuration_register & (
0xFF - (1 << port_bit)
)
self.i2c_bus.write_byte_data(self.i2c_address, 3, updated_configuration_register)
return True
+54 -24
View File
@@ -9,6 +9,7 @@ from rascsi.ractl_cmds import RaCtlCmds
class CtrlBoardMenuBuilder(MenuBuilder):
"""Class fgor building the control board UI specific menus"""
SCSI_ID_MENU = "scsi_id_menu"
ACTION_MENU = "action_menu"
IMAGES_MENU = "images_menu"
@@ -27,8 +28,12 @@ class CtrlBoardMenuBuilder(MenuBuilder):
def __init__(self, ractl_cmd: RaCtlCmds):
super().__init__()
self._rascsi_client = ractl_cmd
self.file_cmd = FileCmds(sock_cmd=ractl_cmd.sock_cmd, ractl=ractl_cmd,
token=ractl_cmd.token, locale=ractl_cmd.locale)
self.file_cmd = FileCmds(
sock_cmd=ractl_cmd.sock_cmd,
ractl=ractl_cmd,
token=ractl_cmd.token,
locale=ractl_cmd.locale,
)
def build(self, name: str, context_object=None) -> Menu:
if name == CtrlBoardMenuBuilder.SCSI_ID_MENU:
@@ -93,9 +98,14 @@ class CtrlBoardMenuBuilder(MenuBuilder):
if device_type != "":
menu_str += " [" + device_type + "]"
menu.add_entry(menu_str, {"context": self.SCSI_ID_MENU,
"action": self.ACTION_OPENACTIONMENU,
"scsi_id": scsi_id})
menu.add_entry(
menu_str,
{
"context": self.SCSI_ID_MENU,
"action": self.ACTION_OPENACTIONMENU,
"scsi_id": scsi_id,
},
)
return menu
@@ -103,18 +113,30 @@ class CtrlBoardMenuBuilder(MenuBuilder):
def create_action_menu(self, context_object=None):
"""Method creates the action submenu with action that can be performed on a scsi slot"""
menu = Menu(CtrlBoardMenuBuilder.ACTION_MENU)
menu.add_entry("Return", {"context": self.ACTION_MENU,
"action": self.ACTION_RETURN})
menu.add_entry("Attach/Insert", {"context": self.ACTION_MENU,
"action": self.ACTION_SLOT_ATTACHINSERT})
menu.add_entry("Detach/Eject", {"context": self.ACTION_MENU,
"action": self.ACTION_SLOT_DETACHEJECT})
menu.add_entry("Info", {"context": self.ACTION_MENU,
"action": self.ACTION_SLOT_INFO})
menu.add_entry("Load Profile", {"context": self.ACTION_MENU,
"action": self.ACTION_LOADPROFILE})
menu.add_entry("Shutdown", {"context": self.ACTION_MENU,
"action": self.ACTION_SHUTDOWN})
menu.add_entry(
"Return",
{"context": self.ACTION_MENU, "action": self.ACTION_RETURN},
)
menu.add_entry(
"Attach/Insert",
{"context": self.ACTION_MENU, "action": self.ACTION_SLOT_ATTACHINSERT},
)
menu.add_entry(
"Detach/Eject",
{"context": self.ACTION_MENU, "action": self.ACTION_SLOT_DETACHEJECT},
)
menu.add_entry(
"Info",
{"context": self.ACTION_MENU, "action": self.ACTION_SLOT_INFO},
)
menu.add_entry(
"Load Profile",
{"context": self.ACTION_MENU, "action": self.ACTION_LOADPROFILE},
)
menu.add_entry(
"Shutdown",
{"context": self.ACTION_MENU, "action": self.ACTION_SHUTDOWN},
)
return menu
def create_images_menu(self, context_object=None):
@@ -123,12 +145,15 @@ class CtrlBoardMenuBuilder(MenuBuilder):
images_info = self.file_cmd.list_images()
menu.add_entry("Return", {"context": self.IMAGES_MENU, "action": self.ACTION_RETURN})
images = images_info["files"]
sorted_images = sorted(images, key=lambda d: d['name'])
sorted_images = sorted(images, key=lambda d: d["name"])
for image in sorted_images:
image_str = image["name"] + " [" + image["detected_type"] + "]"
image_context = {"context": self.IMAGES_MENU, "name": str(image["name"]),
"device_type": str(image["detected_type"]),
"action": self.ACTION_IMAGE_ATTACHINSERT}
image_context = {
"context": self.IMAGES_MENU,
"name": str(image["name"]),
"device_type": str(image["detected_type"]),
"action": self.ACTION_IMAGE_ATTACHINSERT,
}
menu.add_entry(image_str, image_context)
return menu
@@ -138,9 +163,14 @@ class CtrlBoardMenuBuilder(MenuBuilder):
menu.add_entry("Return", {"context": self.IMAGES_MENU, "action": self.ACTION_RETURN})
config_files = self.file_cmd.list_config_files()
for config_file in config_files:
menu.add_entry(str(config_file),
{"context": self.PROFILES_MENU, "name": str(config_file),
"action": self.ACTION_LOADPROFILE})
menu.add_entry(
str(config_file),
{
"context": self.PROFILES_MENU,
"name": str(config_file),
"action": self.ACTION_LOADPROFILE,
},
)
return menu
+30 -22
View File
@@ -6,14 +6,17 @@ import logging
from config import CtrlboardConfig
from ctrlboard_hw.ctrlboard_hw import CtrlBoardHardware
from ctrlboard_hw.ctrlboard_hw_constants import CtrlBoardHardwareConstants
from ctrlboard_event_handler.ctrlboard_menu_update_event_handler \
import CtrlBoardMenuUpdateEventHandler
from ctrlboard_event_handler.ctrlboard_menu_update_event_handler import (
CtrlBoardMenuUpdateEventHandler,
)
from ctrlboard_menu_builder import CtrlBoardMenuBuilder
from menu.menu_renderer_config import MenuRendererConfig
from menu.menu_renderer_luma_oled import MenuRendererLumaOled
from rascsi.exceptions import (EmptySocketChunkException,
InvalidProtobufResponse,
FailedSocketConnectionException)
from rascsi.exceptions import (
EmptySocketChunkException,
InvalidProtobufResponse,
FailedSocketConnectionException,
)
from rascsi.ractl_cmds import RaCtlCmds
from rascsi.socket_cmds import SocketCmds
@@ -23,7 +26,7 @@ from rascsi_menu_controller import RascsiMenuController
def parse_config():
"""Parses the command line parameters and configured the RaSCSI Control Board UI accordingly"""
config = CtrlboardConfig()
cmdline_args_parser = argparse.ArgumentParser(description='RaSCSI ctrlboard service')
cmdline_args_parser = argparse.ArgumentParser(description="RaSCSI ctrlboard service")
cmdline_args_parser.add_argument(
"--rotation",
type=int,
@@ -68,7 +71,7 @@ def parse_config():
default=logging.WARN,
action="store",
help="Loglevel. Valid values: 0 (notset), 10 (debug), 30 (warning), "
"40 (error), 50 (critical). Default: Warning",
"40 (error), 50 (critical). Default: Warning",
)
cmdline_args_parser.add_argument(
"--transitions",
@@ -115,14 +118,14 @@ def main():
config = parse_config()
log_format = "%(asctime)s:%(name)s:%(levelname)s - %(message)s"
logging.basicConfig(stream=sys.stdout,
format=log_format,
level=config.LOG_LEVEL)
logging.basicConfig(stream=sys.stdout, format=log_format, level=config.LOG_LEVEL)
log = logging.getLogger(__name__)
log.debug("RaSCSI ctrlboard service started.")
ctrlboard_hw = CtrlBoardHardware(display_i2c_address=config.DISPLAY_I2C_ADDRESS,
pca9554_i2c_address=config.PCA9554_I2C_ADDRESS)
ctrlboard_hw = CtrlBoardHardware(
display_i2c_address=config.DISPLAY_I2C_ADDRESS,
pca9554_i2c_address=config.PCA9554_I2C_ADDRESS,
)
# for now, we require the complete rascsi ctrlboard hardware.
# Oled only will be supported as well at some later point in time.
@@ -143,8 +146,10 @@ def main():
exit(1)
if check_rascsi_connection(ractl_cmd) is False:
log.error("Communication with RaSCSI failed. Please check if password token must be set "
"and whether is set correctly.")
log.error(
"Communication with RaSCSI failed. Please check if password token must be set "
"and whether is set correctly."
)
exit(1)
menu_renderer_config = MenuRendererConfig()
@@ -156,18 +161,21 @@ def main():
menu_renderer_config.rotation = config.ROTATION
menu_builder = CtrlBoardMenuBuilder(ractl_cmd)
menu_controller = RascsiMenuController(config.MENU_REFRESH_INTERVAL, menu_builder=menu_builder,
menu_renderer=MenuRendererLumaOled(menu_renderer_config),
menu_renderer_config=menu_renderer_config)
menu_controller = RascsiMenuController(
config.MENU_REFRESH_INTERVAL,
menu_builder=menu_builder,
menu_renderer=MenuRendererLumaOled(menu_renderer_config),
menu_renderer_config=menu_renderer_config,
)
menu_controller.add(CtrlBoardMenuBuilder.SCSI_ID_MENU)
menu_controller.add(CtrlBoardMenuBuilder.ACTION_MENU)
menu_controller.show_splash_screen(f"resources/splash_start_64.bmp")
menu_controller.show_splash_screen("resources/splash_start_64.bmp")
menu_update_event_handler = CtrlBoardMenuUpdateEventHandler(menu_controller,
sock_cmd=sock_cmd,
ractl_cmd=ractl_cmd)
menu_update_event_handler = CtrlBoardMenuUpdateEventHandler(
menu_controller, sock_cmd=sock_cmd, ractl_cmd=ractl_cmd
)
ctrlboard_hw.attach(menu_update_event_handler)
menu_controller.set_active_menu(CtrlBoardMenuBuilder.SCSI_ID_MENU)
@@ -184,5 +192,5 @@ def main():
print(ex)
if __name__ == '__main__':
if __name__ == "__main__":
main()
@@ -5,6 +5,7 @@ from menu.screensaver import ScreenSaver
class BlankScreenSaver(ScreenSaver):
"""Class implementing a blank screen safer that simply blanks the screen after a
configured activation delay"""
def __init__(self, activation_delay, menu_renderer):
super().__init__(activation_delay, menu_renderer)
self._initial_draw_call = None
+12 -4
View File
@@ -8,9 +8,17 @@ class Cycler:
"""Class implementing button cycling functionality. Message is shown at the center of
the screen where repeated button presses cycle through the available selection
possibilities. Inactivity (cycle_timeout) actives cycle entry last shown on the screen."""
def __init__(self, menu_controller, sock_cmd, ractl_cmd,
cycle_timeout=3, return_string="Return ->",
return_entry=True, empty_messages=True):
def __init__(
self,
menu_controller,
sock_cmd,
ractl_cmd,
cycle_timeout=3,
return_string="Return ->",
return_entry=True,
empty_messages=True,
):
self._cycle_profile_timer_flag = Timer(activation_delay=cycle_timeout)
self._menu_controller = menu_controller
self.sock_cmd = sock_cmd
@@ -39,7 +47,7 @@ class Cycler:
"""Perform the return action, i.e., when no selection is chosen"""
def update(self):
""" Returns True if object has completed its task and can be deleted """
"""Returns True if object has completed its task and can be deleted"""
if self._cycle_profile_timer_flag is None:
return None
+3 -2
View File
@@ -4,6 +4,7 @@ from typing import List
class Menu:
"""Class implement the Menu class"""
def __init__(self, name: str):
self.entries: List = []
self.item_selection = 0
@@ -17,11 +18,11 @@ class Menu:
def get_current_text(self):
"""Returns the text content of the currently selected text in the menu."""
return self.entries[self.item_selection]['text']
return self.entries[self.item_selection]["text"]
def get_current_info_object(self):
"""Returns the data object to the currently selected menu item"""
return self.entries[self.item_selection]['data_object']
return self.entries[self.item_selection]["data_object"]
def __repr__(self):
print("entries: " + str(self.entries))
@@ -6,6 +6,7 @@ from menu.menu import Menu
# pylint: disable=too-few-public-methods
class MenuBuilder(ABC):
"""Base class for menu builders"""
def __init__(self):
pass
+62 -21
View File
@@ -18,6 +18,7 @@ from menu.screensaver import ScreenSaver
class MenuRenderer(ABC):
"""The abstract menu renderer class provides the base for concrete menu
renderer classes that implement functionality based on conrete hardware or available APIs."""
def __init__(self, config: MenuRendererConfig):
self.message = ""
self.mini_message = ""
@@ -25,7 +26,7 @@ class MenuRenderer(ABC):
self._config = config
self.disp = self.display_init()
self.image = Image.new('1', (self.disp.width, self.disp.height))
self.image = Image.new("1", (self.disp.width, self.disp.height))
self.draw = ImageDraw.Draw(self.image)
self.font = ImageFont.truetype(config.font_path, size=config.font_size)
# just a sample text to work with the font height
@@ -83,14 +84,21 @@ class MenuRenderer(ABC):
def draw_row(self, row_number: int, text: str, selected: bool):
"""Draws a single row of the menu."""
x_pos = 0
y_pos = row_number*self.font_height
y_pos = row_number * self.font_height
if selected:
selection_extension = 0
if row_number < self.rows_per_screen():
selection_extension = self._config.row_selection_pixel_extension
self.draw.rectangle((x_pos, y_pos, self.disp.width,
y_pos+self._config.font_size+selection_extension),
outline=0, fill=255)
self.draw.rectangle(
(
x_pos,
y_pos,
self.disp.width,
y_pos + self._config.font_size + selection_extension,
),
outline=0,
fill=255,
)
# in stage 1, we initialize scrolling for the currently selected line
if self._perform_scrolling_stage == 1:
@@ -99,9 +107,9 @@ class MenuRenderer(ABC):
# in stage 2, we know the details and can thus perform the scrolling to the left
if self._perform_scrolling_stage == 2:
if self._current_line_horizontal_overlap+self._x_scrolling > 0:
if self._current_line_horizontal_overlap + self._x_scrolling > 0:
self._x_scrolling -= 1
if self._current_line_horizontal_overlap+self._x_scrolling == 0:
if self._current_line_horizontal_overlap + self._x_scrolling == 0:
self._stage_timestamp = int(time.time())
self._perform_scrolling_stage = 3
@@ -115,11 +123,12 @@ class MenuRenderer(ABC):
# in stage 4, we scroll back to the right
if self._perform_scrolling_stage == 4:
if self._current_line_horizontal_overlap+self._x_scrolling >= 0:
if self._current_line_horizontal_overlap + self._x_scrolling >= 0:
self._x_scrolling += 1
if (self._current_line_horizontal_overlap +
self._x_scrolling) == self._current_line_horizontal_overlap:
if (
self._current_line_horizontal_overlap + self._x_scrolling
) == self._current_line_horizontal_overlap:
self._stage_timestamp = int(time.time())
self._perform_scrolling_stage = 5
@@ -131,8 +140,14 @@ class MenuRenderer(ABC):
self._stage_timestamp = None
self._perform_scrolling_stage = 2
self.draw.text((x_pos+self._x_scrolling, y_pos), text, font=self.font,
spacing=0, stroke_fill=0, fill=0)
self.draw.text(
(x_pos + self._x_scrolling, y_pos),
text,
font=self.font,
spacing=0,
stroke_fill=0,
fill=0,
)
else:
self.draw.text((x_pos, y_pos), text, font=self.font, spacing=0, stroke_fill=0, fill=255)
@@ -143,8 +158,15 @@ class MenuRenderer(ABC):
centered_height = (self.disp.height - font_height) / 2
self.draw.rectangle((0, 0, self.disp.width, self.disp.height), outline=0, fill=255)
self.draw.text((centered_width, centered_height), text, align="center", font=self.font,
stroke_fill=0, fill=0, textsize=20)
self.draw.text(
(centered_width, centered_height),
text,
align="center",
font=self.font,
stroke_fill=0,
fill=0,
textsize=20,
)
def draw_mini_message(self, text: str):
"""Draws a fullscreen message, i.e., a message covering only the center portion of
@@ -153,16 +175,33 @@ class MenuRenderer(ABC):
centered_width = (self.disp.width - font_width) / 2
centered_height = (self.disp.height - self.font_height) / 2
self.draw.rectangle((0, centered_height-4, self.disp.width,
centered_height+self.font_height+4), outline=0, fill=255)
self.draw.text((centered_width, centered_height), text, align="center", font=self.font,
stroke_fill=0, fill=0, textsize=20)
self.draw.rectangle(
(
0,
centered_height - 4,
self.disp.width,
centered_height + self.font_height + 4,
),
outline=0,
fill=255,
)
self.draw.text(
(centered_width, centered_height),
text,
align="center",
font=self.font,
stroke_fill=0,
fill=0,
textsize=20,
)
def draw_menu(self):
"""Method draws the menu set to the class instance."""
if self._menu.item_selection >= self.frame_start_row + self.rows_per_screen():
if self._config.scroll_behavior == "page":
self.frame_start_row = self.frame_start_row + (round(self.rows_per_screen()/2)) + 1
self.frame_start_row = (
self.frame_start_row + (round(self.rows_per_screen() / 2)) + 1
)
if self.frame_start_row > len(self._menu.entries) - self.rows_per_screen():
self.frame_start_row = len(self._menu.entries) - self.rows_per_screen()
else: # extend as default behavior
@@ -170,13 +209,15 @@ class MenuRenderer(ABC):
if self._menu.item_selection < self.frame_start_row:
if self._config.scroll_behavior == "page":
self.frame_start_row = self.frame_start_row - (round(self.rows_per_screen()/2)) - 1
self.frame_start_row = (
self.frame_start_row - (round(self.rows_per_screen() / 2)) - 1
)
if self.frame_start_row < 0:
self.frame_start_row = 0
else: # extend as default behavior
self.frame_start_row = self._menu.item_selection
self.draw_menu_frame(self.frame_start_row, self.frame_start_row+self.rows_per_screen())
self.draw_menu_frame(self.frame_start_row, self.frame_start_row + self.rows_per_screen())
def draw_menu_frame(self, frame_start_row: int, frame_end_row: int):
"""Draws row frame_start_row to frame_end_row of the class instance menu, i.e., it
@@ -11,8 +11,9 @@ class MenuRendererAdafruitSSD1306(MenuRenderer):
def display_init(self):
i2c = busio.I2C(SCL, SDA)
self.disp = adafruit_ssd1306.SSD1306_I2C(self._config.width, self._config.height, i2c,
addr=self._config.i2c_address)
self.disp = adafruit_ssd1306.SSD1306_I2C(
self._config.width, self._config.height, i2c, addr=self._config.i2c_address
)
self.disp.rotation = self._config.get_mapped_rotation()
self.disp.fill(0)
self.disp.show()
@@ -5,20 +5,16 @@
class MenuRendererConfig:
"""Class for configuring menu renderer instances. Provides configuration options
such as width, height, i2c address, font, transitions, etc."""
_rotation_mapper = {
0: 0,
90: 1,
180: 2,
270: 3
}
_rotation_mapper = {0: 0, 90: 1, 180: 2, 270: 3}
def __init__(self):
self.width = 128
self.height = 64
self.i2c_address = 0x3c
self.i2c_address = 0x3C
self.i2c_port = 1
self.display_type = "ssd1306" # luma-oled supported devices, "sh1106", "ssd1306", ...
self.font_path = 'resources/DejaVuSansMono-Bold.ttf'
self.font_path = "resources/DejaVuSansMono-Bold.ttf"
self.font_size = 12
self.row_selection_pixel_extension = 2
self.scroll_behavior = "page" # "extend" or "page"
@@ -5,14 +5,19 @@ from menu.menu_renderer import MenuRenderer
class MenuRendererLumaOled(MenuRenderer):
"""Class implementing the luma oled menu renderer"""
def display_init(self):
serial = i2c(port=self._config.i2c_port, address=self._config.i2c_address)
import luma.oled.device
device = getattr(luma.oled.device, self._config.display_type)
self.disp = device(serial_interface=serial, width=self._config.width,
height=self._config.height,
rotate=self._config.get_mapped_rotation())
self.disp = device(
serial_interface=serial,
width=self._config.width,
height=self._config.height,
rotate=self._config.get_mapped_rotation(),
)
self.disp.clear()
self.disp.show()
+1
View File
@@ -5,6 +5,7 @@ import time
class Timer:
"""Class implementing a timer class. Takes an activation delay and
sets a flag if the activation delay exprires."""
def __init__(self, activation_delay):
self.start_timestamp = int(time.time())
self.activation_delay = activation_delay
+4 -3
View File
@@ -17,6 +17,7 @@ class Transition:
class PushTransition(Transition):
"""Class implementing a push left/right transition."""
PUSH_LEFT_TRANSITION = "push_left"
PUSH_RIGHT_TRANSITION = "push_right"
@@ -32,7 +33,7 @@ class PushTransition(Transition):
if transition_attributes is not None and transition_attributes != {}:
direction = transition_attributes["direction"]
transition_image = Image.new('1', (self.disp.width, self.disp.height))
transition_image = Image.new("1", (self.disp.width, self.disp.height))
if direction == PushTransition.PUSH_LEFT_TRANSITION:
self.perform_push_left(end_image, start_image, transition_image)
@@ -57,8 +58,8 @@ class PushTransition(Transition):
"""Implements a push right transition. Is called by perform depending on the transition
attribute 'direction'."""
for x_pos in range(0, 128, self.transition_attributes["transition_speed"]):
left_region = start_image.crop((0, 0, 128-x_pos, 64))
right_region = end_image.crop((128-x_pos, 0, 128, 64))
left_region = start_image.crop((0, 0, 128 - x_pos, 64))
right_region = end_image.crop((128 - x_pos, 0, 128, 64))
transition_image.paste(left_region, (x_pos, 0, 128, 64))
transition_image.paste(right_region, (0, 0, x_pos, 64))
self.disp.display(transition_image)
+1
View File
@@ -5,6 +5,7 @@ from observer import Observer
class Observable:
"""Class implementing the Observable pattern"""
_observers: List[Observer] = []
def attach(self, observer: Observer):
+1
View File
@@ -5,6 +5,7 @@ from abc import ABC, abstractmethod
# pylint: disable=too-few-public-methods
class Observer(ABC):
"""Class implementing an abserver"""
@abstractmethod
def update(self, updated_object) -> None:
"""Abstract method for updating an observer. Needs to be implemented by subclasses."""
@@ -9,8 +9,13 @@ from menu.timer import Timer
class RascsiMenuController(MenuController):
"""Class implementing a RaSCSI Control Board UI specific menu controller"""
def __init__(self, refresh_interval, menu_builder: MenuBuilder,
menu_renderer=None, menu_renderer_config=None):
def __init__(
self,
refresh_interval,
menu_builder: MenuBuilder,
menu_renderer=None,
menu_renderer_config=None,
):
super().__init__(menu_builder, menu_renderer, menu_renderer_config)
self._refresh_interval = refresh_interval
self._menu_renderer: MenuRenderer = menu_renderer