mirror of
https://github.com/akuker/RASCSI.git
synced 2026-04-21 18:17:07 +00:00
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:
@@ -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
|
||||
|
||||
+74
-50
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -5,6 +5,7 @@ from observer import Observer
|
||||
|
||||
class Observable:
|
||||
"""Class implementing the Observable pattern"""
|
||||
|
||||
_observers: List[Observer] = []
|
||||
|
||||
def attach(self, observer: Observer):
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user