Skip to content

Commit

Permalink
Merge pull request #30 from galactic-forensics/software_lockout_function
Browse files Browse the repository at this point in the history
Software lockout functionality, firmware combination

This PR upgrades to `cli_v0.2.0`, `fw_v020` and `gui_v0.2.2`
  • Loading branch information
trappitsch committed Jan 11, 2024
2 parents 26660eb + c8eb88e commit 4d43b8c
Show file tree
Hide file tree
Showing 28 changed files with 889 additions and 29 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/check_mode.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ jobs:
run: |
grep 'self.dummy = False' controller/src/main/python/main.py
grep 'self.debug = False' controller/src/main/python/main.py
grep 'const bool debug = false;' firmware/DigOutBox_fw*/config.h
grep 'const bool EnableInterlock = false;' firmware/DigOutBox_fw*/config.h
32 changes: 32 additions & 0 deletions controller/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,38 @@ Here you can configure if you want the status of channels to be automatically re
and how often (in seconds) this should be done.
When you are done, hit "Ok" to save the settings.

## Lockouts

The GUI does not behave differently depending on which lockout is active.

### Interlock safety

If enabled in the firmware,
the interlock safety -- if triggered --
will disable sending commands to the hardware from any input method.
The GUI will change its behavior to reflect this and,
after reading the hardware,
gray out all interaction buttons.

If the interlock safety is triggered,
all channels will turned off and stay off.
Neither the software,
nor the remote can overwrite this.

### Software lockout

The remote can be configured to have a software lockout button,
see firmware notes.
If enabled,
the GUI will change its behavior to reflect this and,
after reading the hardware,
gray out all interaction buttons.

If the software lockout is triggered,
the software will not be allowed to change the state of any channel.
However, the remote can still change the state of the channels.


## Saving your configuration file / loading a configuration file

The "File" entry in the menu bar allows you to save the current configuration
Expand Down
7 changes: 5 additions & 2 deletions controller/release_text.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
This is the first version of the DigOutBox controller.
This is DigOutBox controller `gui_v0.2.0`.

Please read the readme file for more information on how to set up and use this program. You can find it [here](https://github.com/galactic-forensics/DigOutBox/tree/main/controller).

Fixes/enhancements in this minor version `gui_v0.1.1`:
**Important**: This version of the GUI needs the DigOutBox firmware version `v0.2.0` or higher and requires CLI version `v0.2.0` or higher.

Fixes/enhancements in this version:

- Software lockout functionality has been added to the GUI. If software lockout is activated via the remote, the GUI will not be able to change the state of the channels. The GUI will still be able to read the state of the channels.
- Extended help via tooltips
2 changes: 1 addition & 1 deletion controller/src/build/settings/base.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"app_name": "DigOutBox Controller",
"author": "Reto Trappitsch",
"main_module": "src/main/python/main.py",
"version": "0.1.1"
"version": "0.2.0"
}
34 changes: 30 additions & 4 deletions controller/src/main/python/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ def __init__(self, is_windows=False) -> None:
self.hw_config = None
self.init_hw_config()

# buttons to toggle
self.all_on_button = None

# init GUI
self.main_widget = None
self.init_menubar()
Expand Down Expand Up @@ -323,16 +326,16 @@ def build_ui(self):
all_read_button.setToolTip("Read the status of all channels")
all_read_button.clicked.connect(self.read_all)

all_on_button = QtWidgets.QPushButton("All On")
all_on_button.setToolTip("Turn all channels on")
all_on_button.clicked.connect(lambda: self.set_all_channels(state=True))
self.all_on_button = QtWidgets.QPushButton("All On")
self.all_on_button.setToolTip("Turn all channels on")
self.all_on_button.clicked.connect(lambda: self.set_all_channels(state=True))

all_off_button = QtWidgets.QPushButton("All Off")
all_off_button.setToolTip("Turn all channels off")
all_off_button.clicked.connect(lambda: self.set_all_channels(state=False))

all_on_off_layout.addWidget(all_read_button)
all_on_off_layout.addWidget(all_on_button)
all_on_off_layout.addWidget(self.all_on_button)
all_on_off_layout.addWidget(all_off_button)

outer_layout.addLayout(all_on_off_layout)
Expand Down Expand Up @@ -507,6 +510,9 @@ def read_all(self):
):
ch.set_status_from_read(read)

# software lockout
self.lockouts()

def save(self, ask_fname: bool = False):
"""Save the current configuration to default json file.
Expand Down Expand Up @@ -566,6 +572,26 @@ def set_all_channels(self, state: bool):
):
ch.set_status_custom(False)

def lockouts(self):
"""Activate/deactivate buttons depending on software lockout state."""
if self.dummy:
status = False
else:
status = self.comm.interlock_state or self.comm.software_lockout

buttons_to_toggle = [self.all_on_button] # non-widget buttons to toggle

for button in buttons_to_toggle:
button.setEnabled(not status)

for widget in itertools.chain(
self.channel_widgets_individual,
self.channel_widgets_grouped,
self.group_widgets,
):
widget.on_button.setEnabled(not status)
widget.off_button.setEnabled(not status)


if __name__ == "__main__":
# fbs installed
Expand Down
19 changes: 11 additions & 8 deletions controller/src/main/python/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ def __init__(
# status indicator
self.status_indicator = StatusIndicator()

# UI
self.on_button = None
self.off_button = None
self.init_ui()

self.is_on = is_on
Expand Down Expand Up @@ -84,19 +87,19 @@ def init_ui(self):
name_label.setFont(name_label_font)

# buttons
on_button = QtWidgets.QPushButton("On")
on_button.setToolTip(f"Turn {self.channel} on")
on_button.clicked.connect(self.on_button_clicked)
off_button = QtWidgets.QPushButton("Off")
off_button.setToolTip(f"Turn {self.channel} off")
off_button.clicked.connect(self.off_button_clicked)
self.on_button = QtWidgets.QPushButton("On")
self.on_button.setToolTip(f"Turn {self.channel} on")
self.on_button.clicked.connect(self.on_button_clicked)
self.off_button = QtWidgets.QPushButton("Off")
self.off_button.setToolTip(f"Turn {self.channel} off")
self.off_button.clicked.connect(self.off_button_clicked)

layout = QtWidgets.QHBoxLayout()
layout.addWidget(self.status_indicator)
layout.addWidget(name_label)
layout.addStretch()
layout.addWidget(on_button)
layout.addWidget(off_button)
layout.addWidget(self.on_button)
layout.addWidget(self.off_button)
self.setLayout(layout)

def on_button_clicked(self):
Expand Down
2 changes: 1 addition & 1 deletion controller_cli/controller_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
__all__ = ["DigIOBoxComm"]

# Package information
__version__ = "0.1.0"
__version__ = "0.2.0"

__title__ = "controller_cli"
__description__ = """Communication package to talk to the DigOutBox via Serial."""
Expand Down
10 changes: 10 additions & 0 deletions controller_cli/controller_cli/device_comm.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ def identify(self):
return "DigIOBox Dummy"
return self.query("*IDN?")

@property
def interlock_state(self) -> bool:
"""Read if software lockout is on."""
return bool(int(self.query("INTERLOCKState?")))

@property
def num_channels(self) -> int:
"""Get / Set number of available channels.
Expand All @@ -101,6 +106,11 @@ def num_channels(self) -> int:
def num_channels(self, value: int):
self._num_channels = int(value)

@property
def software_lockout(self) -> bool:
"""Read if software lockout is on."""
return bool(int(self.query("SWLockout?")))

@property
def states(self):
"""Read the states of all channels and return as a boolean array."""
Expand Down
16 changes: 16 additions & 0 deletions controller_cli/tests/test_device_comm.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,22 @@ def test_all_off():
dev.all_off()


@pytest.mark.parametrize("state", [0, 1])
def test_interlock_state(state):
"""Read state of the interlock."""
with expected_communication(
command=["INTERLOCKState?"], response=[f"{state}"]
) as dev:
assert dev.interlock_state == bool(state)


@pytest.mark.parametrize("state", [0, 1])
def test_software_lockout(state):
"""Read state of software lockout."""
with expected_communication(command=["SWLockout?"], response=[f"{state}"]) as dev:
assert dev.software_lockout == bool(state)


# CHANNEL PROPERTIES #


Expand Down
Loading

0 comments on commit 4d43b8c

Please sign in to comment.