mirror of https://github.com/akuker/RASCSI.git
Merge branch 'develop' into bug_335_cache_fix_selectable
This commit is contained in:
commit
97f857b3b7
223
easyinstall.sh
223
easyinstall.sh
|
@ -52,6 +52,7 @@ VIRTUAL_DRIVER_PATH="$HOME/images"
|
|||
CFG_PATH="$HOME/.config/rascsi"
|
||||
WEB_INSTALL_PATH="$BASE/python/web"
|
||||
OLED_INSTALL_PATH="$BASE/python/oled"
|
||||
CTRLBOARD_INSTALL_PATH="$BASE/python/ctrlboard"
|
||||
PYTHON_COMMON_PATH="$BASE/python/common"
|
||||
SYSTEMD_PATH="/etc/systemd/system"
|
||||
HFS_FORMAT=/usr/bin/hformat
|
||||
|
@ -314,6 +315,17 @@ function stopRaScsiScreen() {
|
|||
fi
|
||||
}
|
||||
|
||||
# Stops the rascsi-ctrlboard service if it is running
|
||||
function stopRaScsiCtrlBoard() {
|
||||
if [[ -f "$SYSTEMD_PATH/rascsi-ctrlboard.service" ]]; then
|
||||
SERVICE_RASCSI_CTRLBOARD_RUNNING=0
|
||||
sudo systemctl is-active --quiet rascsi-ctrlboard.service >/dev/null 2>&1 || SERVICE_RASCSI_CTRLBOARD_RUNNING=$?
|
||||
if [[ SERVICE_RASCSI_CTRLBOARD_RUNNING -eq 0 ]]; then
|
||||
sudo systemctl stop rascsi-ctrlboard.service
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# disables and removes the old monitor_rascsi service
|
||||
function disableOldRaScsiMonitorService() {
|
||||
if [ -f "$SYSTEMD_PATH/monitor_rascsi.service" ]; then
|
||||
|
@ -332,6 +344,40 @@ function disableOldRaScsiMonitorService() {
|
|||
fi
|
||||
}
|
||||
|
||||
# disables the rascsi-oled service
|
||||
function disableRaScsiOledService() {
|
||||
if [ -f "$SYSTEMD_PATH/rascsi-oled.service" ]; then
|
||||
SERVICE_RASCSI_OLED_RUNNING=0
|
||||
sudo systemctl is-active --quiet rascsi-oled.service >/dev/null 2>&1 || SERVICE_RASCSI_OLED_RUNNING=$?
|
||||
if [[ $SERVICE_RASCSI_OLED_RUNNING -eq 0 ]]; then
|
||||
sudo systemctl stop rascsi-oled.service
|
||||
fi
|
||||
|
||||
SERVICE_RASCSI_OLED_ENABLED=0
|
||||
sudo systemctl is-enabled --quiet rascsi-oled.service >/dev/null 2>&1 || SERVICE_RASCSI_OLED_ENABLED=$?
|
||||
if [[ $SERVICE_RASCSI_OLED_ENABLED -eq 0 ]]; then
|
||||
sudo systemctl disable rascsi-oled.service
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# disables the rascsi-ctrlboard service
|
||||
function disableRaScsiCtrlBoardService() {
|
||||
if [ -f "$SYSTEMD_PATH/rascsi-ctrlboard.service" ]; then
|
||||
SERVICE_RASCSI_CTRLBOARD_RUNNING=0
|
||||
sudo systemctl is-active --quiet rascsi-ctrlboard.service >/dev/null 2>&1 || SERVICE_RASCSI_CTRLBOARD_RUNNING=$?
|
||||
if [[ $SERVICE_RASCSI_CTRLBOARD_RUNNING -eq 0 ]]; then
|
||||
sudo systemctl stop rascsi-ctrlboard.service
|
||||
fi
|
||||
|
||||
SERVICE_RASCSI_CTRLBOARD_ENABLED=0
|
||||
sudo systemctl is-enabled --quiet rascsi-ctrlboard.service >/dev/null 2>&1 || SERVICE_RASCSI_CTRLBOARD_ENABLED=$?
|
||||
if [[ $SERVICE_RASCSI_CTRLBOARD_ENABLED -eq 0 ]]; then
|
||||
sudo systemctl disable rascsi-ctrlboard.service
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Stops the macproxy service if it is running
|
||||
function stopMacproxy() {
|
||||
if [ -f "$SYSTEMD_PATH/macproxy.service" ]; then
|
||||
|
@ -339,7 +385,7 @@ function stopMacproxy() {
|
|||
fi
|
||||
}
|
||||
|
||||
# Starts the rascsi-oled service if installed
|
||||
# Checks whether the rascsi-oled service is installed
|
||||
function isRaScsiScreenInstalled() {
|
||||
SERVICE_RASCSI_OLED_ENABLED=0
|
||||
if [[ -f "$SYSTEMD_PATH/rascsi-oled.service" ]]; then
|
||||
|
@ -353,7 +399,19 @@ function isRaScsiScreenInstalled() {
|
|||
echo $SERVICE_RASCSI_OLED_ENABLED
|
||||
}
|
||||
|
||||
# Starts the rascsi-oled service if installed
|
||||
# Checks whether the rascsi-ctrlboard service is installed
|
||||
function isRaScsiCtrlBoardInstalled() {
|
||||
SERVICE_RASCSI_CTRLBOARD_ENABLED=0
|
||||
if [[ -f "$SYSTEMD_PATH/rascsi-ctrlboard.service" ]]; then
|
||||
sudo systemctl is-enabled --quiet rascsi-ctrlboard.service >/dev/null 2>&1 || SERVICE_RASCSI_CTRLBOARD_ENABLED=$?
|
||||
else
|
||||
SERVICE_RASCSI_CTRLBOARD_ENABLED=1
|
||||
fi
|
||||
|
||||
echo $SERVICE_RASCSI_CTRLBOARD_ENABLED
|
||||
}
|
||||
|
||||
# Checks whether the rascsi-oled service is running
|
||||
function isRaScsiScreenRunning() {
|
||||
SERVICE_RASCSI_OLED_RUNNING=0
|
||||
if [[ -f "$SYSTEMD_PATH/rascsi-oled.service" ]]; then
|
||||
|
@ -367,6 +425,19 @@ function isRaScsiScreenRunning() {
|
|||
echo $SERVICE_RASCSI_OLED_RUNNING
|
||||
}
|
||||
|
||||
# Checks whether the rascsi-oled service is running
|
||||
function isRaScsiCtrlBoardRunning() {
|
||||
SERVICE_RASCSI_CTRLBOARD_RUNNING=0
|
||||
if [[ -f "$SYSTEMD_PATH/rascsi-ctrlboard.service" ]]; then
|
||||
sudo systemctl is-active --quiet rascsi-ctrlboard.service >/dev/null 2>&1 || SERVICE_RASCSI_CTRLBOARD_RUNNING=$?
|
||||
else
|
||||
SERVICE_RASCSI_CTRLBOARD_RUNNING=1
|
||||
fi
|
||||
|
||||
echo $SERVICE_RASCSI_CTRLBOARD_RUNNING
|
||||
}
|
||||
|
||||
|
||||
# Starts the rascsi-oled service if installed
|
||||
function startRaScsiScreen() {
|
||||
if [[ $(isRaScsiScreenInstalled) -eq 0 ]] && [[ $(isRaScsiScreenRunning) -ne 1 ]]; then
|
||||
|
@ -375,6 +446,14 @@ function startRaScsiScreen() {
|
|||
fi
|
||||
}
|
||||
|
||||
# Starts the rascsi-ctrlboard service if installed
|
||||
function startRaScsiCtrlBoard() {
|
||||
if [[ $(isRaScsiCtrlBoardInstalled) -eq 0 ]] && [[ $(isRaScsiCtrlBoardRunning) -ne 1 ]]; then
|
||||
sudo systemctl start rascsi-ctrlboard.service
|
||||
showRaScsiCtrlBoardStatus
|
||||
fi
|
||||
}
|
||||
|
||||
# Starts the macproxy service if installed
|
||||
function startMacproxy() {
|
||||
if [ -f "$SYSTEMD_PATH/macproxy.service" ]; then
|
||||
|
@ -398,6 +477,11 @@ function showRaScsiScreenStatus() {
|
|||
systemctl status rascsi-oled | tee
|
||||
}
|
||||
|
||||
# Shows status for the rascsi-ctrlboard service
|
||||
function showRaScsiCtrlBoardStatus() {
|
||||
systemctl status rascsi-ctrlboard | tee
|
||||
}
|
||||
|
||||
# Shows status for the macproxy service
|
||||
function showMacproxyStatus() {
|
||||
systemctl status macproxy | tee
|
||||
|
@ -828,6 +912,7 @@ function installRaScsiScreen() {
|
|||
fi
|
||||
|
||||
stopRaScsiScreen
|
||||
disableRaScsiCtrlBoardService
|
||||
updateRaScsiGit
|
||||
|
||||
sudo apt-get update && sudo apt-get install libjpeg-dev libpng-dev libopenjp2-7-dev i2c-tools raspi-config -y </dev/null
|
||||
|
@ -875,6 +960,113 @@ function installRaScsiScreen() {
|
|||
sudo systemctl start rascsi-oled
|
||||
}
|
||||
|
||||
# updates configuration files and installs packages needed for the CtrlBoard script
|
||||
function installRaScsiCtrlBoard() {
|
||||
echo "IMPORTANT: This configuration requires a RaSCSI Control Board connected to your RaSCSI board."
|
||||
echo "See wiki for more information: https://github.com/akuker/RASCSI/wiki/RaSCSI-Control-Board"
|
||||
echo ""
|
||||
echo "Choose screen rotation:"
|
||||
echo " 1) 0 degrees"
|
||||
echo " 2) 180 degrees (default)"
|
||||
read REPLY
|
||||
|
||||
if [ "$REPLY" == "1" ]; then
|
||||
echo "Proceeding with 0 degrees rotation."
|
||||
ROTATION="0"
|
||||
else
|
||||
echo "Proceeding with 180 degrees rotation."
|
||||
ROTATION="180"
|
||||
fi
|
||||
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo ""
|
||||
echo "Did you protect your RaSCSI installation with a token password? [y/N]"
|
||||
read -r REPLY
|
||||
if [ "$REPLY" == "y" ] || [ "$REPLY" == "Y" ]; then
|
||||
echo -n "Enter the password that you configured with RaSCSI at the time of installation: "
|
||||
read -r TOKEN
|
||||
fi
|
||||
fi
|
||||
|
||||
stopRaScsiCtrlBoard
|
||||
updateRaScsiGit
|
||||
|
||||
sudo apt-get update && sudo apt-get install libjpeg-dev libpng-dev libopenjp2-7-dev i2c-tools raspi-config -y </dev/null
|
||||
# install numpy via apt to avoid compilation
|
||||
sudo apt-get install python3-numpy python3-cbor2 -y </dev/null
|
||||
|
||||
# enable i2c
|
||||
if [[ $(grep -c "^dtparam=i2c_arm=on" /boot/config.txt) -ge 1 ]]; then
|
||||
echo "NOTE: I2C support seems to have been configured already."
|
||||
REBOOT=0
|
||||
else
|
||||
sudo raspi-config nonint do_i2c 0 </dev/null
|
||||
echo "Modified the Raspberry Pi boot configuration to enable I2C."
|
||||
echo "A reboot will be required for the change to take effect."
|
||||
REBOOT=1
|
||||
fi
|
||||
|
||||
# determine target baudrate
|
||||
PI_MODEL=$(/usr/bin/tr -d '\0' < /proc/device-tree/model)
|
||||
TARGET_I2C_BAUDRATE=100000
|
||||
if [[ ${PI_MODEL} =~ "Raspberry Pi 4" ]]; then
|
||||
echo "Detected: Raspberry Pi 4"
|
||||
TARGET_I2C_BAUDRATE=1000000
|
||||
elif [[ ${PI_MODEL} =~ "Raspberry Pi 3" ]] || [[ ${PI_MODEL} =~ "Raspberry Pi Zero 2" ]]; then
|
||||
echo "Detected: Raspberry Pi 3 or Zero 2"
|
||||
TARGET_I2C_BAUDRATE=400000
|
||||
else
|
||||
echo "No Raspberry Pi 4, Pi 3 or Pi Zero 2 detected. Falling back on low i2c baudrate."
|
||||
echo "Transition animations will be disabled."
|
||||
fi
|
||||
|
||||
# adjust i2c baudrate according to the raspberry pi model detection
|
||||
set +e
|
||||
GREP_PARAM="^dtparam=i2c_arm=on,i2c_arm_baudrate=${TARGET_I2C_BAUDRATE}$"
|
||||
ADJUST_BAUDRATE=$(grep -c "${GREP_PARAM}" /boot/config.txt)
|
||||
if [[ $ADJUST_BAUDRATE -eq 0 ]]; then
|
||||
echo "Adjusting I2C baudrate in /boot/config.txt"
|
||||
sudo sed -i "s/dtparam=i2c_arm=on.*/dtparam=i2c_arm=on,i2c_arm_baudrate=${TARGET_I2C_BAUDRATE}/g" /boot/config.txt
|
||||
REBOOT=1
|
||||
else
|
||||
echo "I2C baudrate already correct in /boot/config.txt"
|
||||
fi
|
||||
set -e
|
||||
|
||||
echo "Installing the rascsi-ctrlboard.service configuration..."
|
||||
sudo cp -f "$CTRLBOARD_INSTALL_PATH/service-infra/rascsi-ctrlboard.service" "$SYSTEMD_PATH/rascsi-ctrlboard.service"
|
||||
sudo sed -i /^ExecStart=/d "$SYSTEMD_PATH/rascsi-ctrlboard.service"
|
||||
if [ ! -z "$TOKEN" ]; then
|
||||
sudo sed -i "8 i ExecStart=$CTRLBOARD_INSTALL_PATH/start.sh --rotation=$ROTATION --password=$TOKEN" "$SYSTEMD_PATH/rascsi-ctrlboard.service"
|
||||
sudo chmod 600 "$SYSTEMD_PATH/rascsi-ctrlboard.service"
|
||||
echo "Granted access to the RaSCSI Control Board UI with the password that you configured for RaSCSI."
|
||||
else
|
||||
sudo sed -i "8 i ExecStart=$CTRLBOARD_INSTALL_PATH/start.sh --rotation=$ROTATION" "$SYSTEMD_PATH/rascsi-ctrlboard.service"
|
||||
fi
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
# ensure that the old monitor_rascsi or rascsi-oled service is disabled and removed before the new one is installed
|
||||
disableOldRaScsiMonitorService
|
||||
disableRaScsiOledService
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable rascsi-ctrlboard
|
||||
|
||||
if [ $REBOOT -eq 1 ]; then
|
||||
echo ""
|
||||
echo "The rascsi-ctrlboard service will start on the next Pi boot."
|
||||
echo "Press Enter to reboot or CTRL-C to exit"
|
||||
read
|
||||
|
||||
echo "Rebooting..."
|
||||
sleep 3
|
||||
sudo reboot
|
||||
fi
|
||||
|
||||
sudo systemctl start rascsi-ctrlboard
|
||||
}
|
||||
|
||||
# Prints a notification if the rascsi.service file was backed up
|
||||
function notifyBackup {
|
||||
if "$SYSTEMD_BACKUP"; then
|
||||
|
@ -932,10 +1124,14 @@ function runChoice() {
|
|||
if [[ $(isRaScsiScreenInstalled) -eq 0 ]]; then
|
||||
echo "Detected rascsi oled service; will run the installation steps for the OLED monitor."
|
||||
installRaScsiScreen
|
||||
elif [[ $(isRaScsiCtrlBoardInstalled) -eq 0 ]]; then
|
||||
echo "Detected rascsi control board service; will run the installation steps for the control board ui."
|
||||
installRaScsiCtrlBoard
|
||||
fi
|
||||
installRaScsiWebInterface
|
||||
installWebInterfaceService
|
||||
showRaScsiScreenStatus
|
||||
showRaScsiCtrlBoardStatus
|
||||
showRaScsiStatus
|
||||
showRaScsiWebStatus
|
||||
notifyBackup
|
||||
|
@ -966,8 +1162,12 @@ function runChoice() {
|
|||
if [[ $(isRaScsiScreenInstalled) -eq 0 ]]; then
|
||||
echo "Detected rascsi oled service; will run the installation steps for the OLED monitor."
|
||||
installRaScsiScreen
|
||||
elif [[ $(isRaScsiCtrlBoardInstalled) -eq 0 ]]; then
|
||||
echo "Detected rascsi control board service; will run the installation steps for the control board ui."
|
||||
installRaScsiCtrlBoard
|
||||
fi
|
||||
showRaScsiScreenStatus
|
||||
showRaScsiCtrlBoardStatus
|
||||
showRaScsiStatus
|
||||
notifyBackup
|
||||
echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE:-FULLSPEC}) - Complete!"
|
||||
|
@ -1083,6 +1283,19 @@ function runChoice() {
|
|||
echo "Enabling authentication for the RaSCSI Web Interface - Complete!"
|
||||
echo "Use the credentials for user '$USER' to log in to the Web Interface."
|
||||
;;
|
||||
13)
|
||||
echo "Installing / Updating RaSCSI Control Board UI"
|
||||
echo "This script will make the following changes to your system:"
|
||||
echo "- Install additional packages with apt-get"
|
||||
echo "- Add and modify systemd services"
|
||||
echo "- Stop and disable the RaSCSI OLED service if it is running"
|
||||
echo "- Modify the Raspberry Pi boot configuration (may require a reboot)"
|
||||
sudoCheck
|
||||
preparePythonCommon
|
||||
installRaScsiCtrlBoard
|
||||
showRaScsiCtrlBoardStatus
|
||||
echo "Installing / Updating RaSCSI Control Board UI - Complete!"
|
||||
;;
|
||||
-h|--help|h|help)
|
||||
showMenu
|
||||
;;
|
||||
|
@ -1096,7 +1309,7 @@ function runChoice() {
|
|||
function readChoice() {
|
||||
choice=-1
|
||||
|
||||
until [ $choice -ge "0" ] && [ $choice -le "12" ]; do
|
||||
until [ $choice -ge "0" ] && [ $choice -le "13" ]; do
|
||||
echo -n "Enter your choice (0-12) or CTRL-C to exit: "
|
||||
read -r choice
|
||||
done
|
||||
|
@ -1126,6 +1339,8 @@ function showMenu() {
|
|||
echo " 10) compile and install RaSCSI stand-alone"
|
||||
echo " 11) configure the RaSCSI Web Interface stand-alone"
|
||||
echo " 12) enable authentication for the RaSCSI Web Interface"
|
||||
echo "EXPERIMENTAL FEATURES"
|
||||
echo " 13) install or update RaSCSI Control Board UI (requires hardware)"
|
||||
}
|
||||
|
||||
# parse arguments passed to the script
|
||||
|
@ -1148,7 +1363,7 @@ while [ "$1" != "" ]; do
|
|||
;;
|
||||
esac
|
||||
case $VALUE in
|
||||
FULLSPEC | STANDARD | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12)
|
||||
FULLSPEC | STANDARD | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13)
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: unknown option \"$VALUE\""
|
||||
|
|
|
@ -15,7 +15,7 @@ ignore-patterns=
|
|||
|
||||
# Python code to execute, usually for sys.path manipulation such as
|
||||
# pygtk.require().
|
||||
init-hook=import sys; sys.path.append("common/src"); sys.path.append("web/src"); sys.path.append("oled/src");
|
||||
init-hook=import sys; sys.path.append("common/src"); sys.path.append("web/src"); sys.path.append("oled/src"); sys.path.append("ctrlboard/src");
|
||||
# venv hook for pylint
|
||||
# Requires pylint-venv package:
|
||||
# $ pip install pylint-venv
|
||||
|
@ -140,8 +140,7 @@ disable=print-statement,
|
|||
xreadlines-attribute,
|
||||
deprecated-sys-function,
|
||||
exception-escape,
|
||||
comprehension-escape,
|
||||
import-outside-toplevel
|
||||
comprehension-escape
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
|
|
|
@ -4,8 +4,16 @@ Module for methods reading from and writing to the file system
|
|||
|
||||
import os
|
||||
import logging
|
||||
from pathlib import PurePath
|
||||
import asyncio
|
||||
from pathlib import PurePath
|
||||
from zipfile import ZipFile, is_zipfile
|
||||
from re import escape, findall
|
||||
from time import time
|
||||
from subprocess import run, CalledProcessError
|
||||
from json import dump, load
|
||||
|
||||
import requests
|
||||
|
||||
import rascsi_interface_pb2 as proto
|
||||
from rascsi.common_settings import CFG_DIR, CONFIG_FILE_SUFFIX, PROPERTIES_SUFFIX, RESERVATIONS
|
||||
from rascsi.ractl_cmds import RaCtlCmds
|
||||
|
@ -79,7 +87,6 @@ class FileCmds:
|
|||
prop_data = self.list_files(PROPERTIES_SUFFIX, CFG_DIR)
|
||||
prop_files = [PurePath(x[0]).stem for x in prop_data]
|
||||
|
||||
from zipfile import ZipFile, is_zipfile
|
||||
server_info = self.ractl.get_server_info()
|
||||
files = []
|
||||
for file in result.image_files_info.image_files:
|
||||
|
@ -225,12 +232,11 @@ class FileCmds:
|
|||
members contains all of the full paths to each of the zip archive members
|
||||
Returns (dict) with (boolean) status and (list of str) msg
|
||||
"""
|
||||
from asyncio import run
|
||||
server_info = self.ractl.get_server_info()
|
||||
prop_flag = False
|
||||
|
||||
if not member:
|
||||
unzip_proc = run(self.run_async(
|
||||
unzip_proc = asyncio.run(self.run_async(
|
||||
f"unzip -d {server_info['image_dir']} -n -j "
|
||||
f"{server_info['image_dir']}/{file_name}"
|
||||
))
|
||||
|
@ -241,14 +247,13 @@ class FileCmds:
|
|||
self.rename_file(f"{server_info['image_dir']}/{name}", f"{CFG_DIR}/{name}")
|
||||
prop_flag = True
|
||||
else:
|
||||
from re import escape
|
||||
member = escape(member)
|
||||
unzip_proc = run(self.run_async(
|
||||
unzip_proc = asyncio.run(self.run_async(
|
||||
f"unzip -d {server_info['image_dir']} -n -j "
|
||||
f"{server_info['image_dir']}/{file_name} {member}"
|
||||
))
|
||||
# Attempt to unzip a properties file in the same archive dir
|
||||
unzip_prop = run(self.run_async(
|
||||
unzip_prop = asyncio.run(self.run_async(
|
||||
f"unzip -d {CFG_DIR} -n -j "
|
||||
f"{server_info['image_dir']}/{file_name} {member}.{PROPERTIES_SUFFIX}"
|
||||
))
|
||||
|
@ -258,7 +263,6 @@ class FileCmds:
|
|||
logging.warning("Unzipping failed: %s", unzip_proc["stderr"])
|
||||
return {"status": False, "msg": unzip_proc["stderr"]}
|
||||
|
||||
from re import findall
|
||||
unzipped = findall(
|
||||
"(?:inflating|extracting):(.+)\n",
|
||||
unzip_proc["stdout"]
|
||||
|
@ -270,8 +274,6 @@ class FileCmds:
|
|||
Takes (str) url and one or more (str) *iso_args
|
||||
Returns (dict) with (bool) status and (str) msg
|
||||
"""
|
||||
from time import time
|
||||
from subprocess import run, CalledProcessError
|
||||
|
||||
server_info = self.ractl.get_server_info()
|
||||
|
||||
|
@ -287,7 +289,6 @@ class FileCmds:
|
|||
if not req_proc["status"]:
|
||||
return {"status": False, "msg": req_proc["msg"]}
|
||||
|
||||
from zipfile import is_zipfile, ZipFile
|
||||
if is_zipfile(tmp_full_path):
|
||||
if "XtraStuf.mac" in str(ZipFile(tmp_full_path).namelist()):
|
||||
logging.info("MacZip file format detected. Will not unzip to retain resource fork.")
|
||||
|
@ -339,7 +340,6 @@ class FileCmds:
|
|||
Takes (str) url, (str) save_dir, (str) file_name
|
||||
Returns (dict) with (bool) status and (str) msg
|
||||
"""
|
||||
import requests
|
||||
logging.info("Making a request to download %s", url)
|
||||
|
||||
try:
|
||||
|
@ -371,7 +371,6 @@ class FileCmds:
|
|||
Takes (str) file_name
|
||||
Returns (dict) with (bool) status and (str) msg
|
||||
"""
|
||||
from json import dump
|
||||
file_name = f"{CFG_DIR}/{file_name}"
|
||||
try:
|
||||
with open(file_name, "w", encoding="ISO-8859-1") as json_file:
|
||||
|
@ -433,7 +432,6 @@ class FileCmds:
|
|||
Takes (str) file_name
|
||||
Returns (dict) with (bool) status and (str) msg
|
||||
"""
|
||||
from json import load
|
||||
file_name = f"{CFG_DIR}/{file_name}"
|
||||
try:
|
||||
with open(file_name, encoding="ISO-8859-1") as json_file:
|
||||
|
@ -512,7 +510,6 @@ class FileCmds:
|
|||
Takes file name base (str) and (list of dicts) conf as arguments
|
||||
Returns (dict) with (bool) status and (str) msg
|
||||
"""
|
||||
from json import dump
|
||||
file_path = f"{CFG_DIR}/{file_name}"
|
||||
try:
|
||||
with open(file_path, "w") as json_file:
|
||||
|
@ -548,7 +545,6 @@ class FileCmds:
|
|||
Takes (str) file_path as argument.
|
||||
Returns (dict) with (bool) status, (str) msg, (dict) conf
|
||||
"""
|
||||
from json import load
|
||||
try:
|
||||
with open(file_path) as json_file:
|
||||
conf = load(json_file)
|
||||
|
|
|
@ -162,7 +162,7 @@ class RaCtlCmds:
|
|||
|
||||
def get_disk_device_types(self):
|
||||
"""
|
||||
Returns a (list) of (str) of four letter device acronyms
|
||||
Returns a (list) of (str) of four letter device acronyms
|
||||
that take image files as arguments.
|
||||
"""
|
||||
device_types = self.get_device_types()
|
||||
|
|
|
@ -3,7 +3,10 @@ Module for sending and receiving data over a socket connection with the RaSCSI b
|
|||
"""
|
||||
|
||||
import logging
|
||||
import socket
|
||||
from time import sleep
|
||||
from struct import pack, unpack
|
||||
|
||||
from rascsi.exceptions import (EmptySocketChunkException,
|
||||
InvalidProtobufResponse,
|
||||
FailedSocketConnectionException)
|
||||
|
@ -27,7 +30,6 @@ class SocketCmds:
|
|||
tries = 20
|
||||
error_msg = ""
|
||||
|
||||
import socket
|
||||
while counter < tries:
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
|
@ -56,7 +58,6 @@ class SocketCmds:
|
|||
Tries to extract and interpret the protobuf header to get response size.
|
||||
Reads data from socket in 2048 bytes chunks until all data is received.
|
||||
"""
|
||||
from struct import pack, unpack
|
||||
|
||||
# Sending the magic word "RASCSI" to authenticate with the server
|
||||
sock.send(b"RASCSI")
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
"""
|
||||
Module with methods that interact with the Pi system
|
||||
"""
|
||||
import subprocess
|
||||
import logging
|
||||
from subprocess import run
|
||||
from shutil import disk_usage
|
||||
from re import findall, match
|
||||
from socket import socket, gethostname, AF_INET, SOCK_DGRAM
|
||||
|
||||
|
||||
class SysCmds:
|
||||
"""
|
||||
Class for commands sent to the Pi's Linux system.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def running_env():
|
||||
"""
|
||||
Returns (str) git and (str) env
|
||||
git contains the git hash of the checked out code
|
||||
env is the various system information where this app is running
|
||||
"""
|
||||
try:
|
||||
ra_git_version = (
|
||||
subprocess.run(
|
||||
["git", "rev-parse", "HEAD"],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
.stdout.decode("utf-8")
|
||||
.strip()
|
||||
)
|
||||
except subprocess.CalledProcessError as error:
|
||||
logging.warning("Executed shell command: %s", " ".join(error.cmd))
|
||||
logging.warning("Got error: %s", error.stderr.decode("utf-8"))
|
||||
ra_git_version = ""
|
||||
|
||||
try:
|
||||
pi_version = (
|
||||
subprocess.run(
|
||||
["uname", "-a"],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
.stdout.decode("utf-8")
|
||||
.strip()
|
||||
)
|
||||
except subprocess.CalledProcessError as error:
|
||||
logging.warning("Executed shell command: %s", " ".join(error.cmd))
|
||||
logging.warning("Got error: %s", error.stderr.decode("utf-8"))
|
||||
pi_version = "Unknown"
|
||||
|
||||
return {"git": ra_git_version, "env": pi_version}
|
||||
|
||||
@staticmethod
|
||||
def running_proc(daemon):
|
||||
"""
|
||||
Takes (str) daemon
|
||||
Returns (int) proc, which is the number of processes currently running
|
||||
"""
|
||||
try:
|
||||
processes = (
|
||||
subprocess.run(
|
||||
["ps", "aux"],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
.stdout.decode("utf-8")
|
||||
.strip()
|
||||
)
|
||||
except subprocess.CalledProcessError as error:
|
||||
logging.warning("Executed shell command: %s", " ".join(error.cmd))
|
||||
logging.warning("Got error: %s", error.stderr.decode("utf-8"))
|
||||
processes = ""
|
||||
|
||||
matching_processes = findall(daemon, processes)
|
||||
return len(matching_processes)
|
||||
|
||||
@staticmethod
|
||||
def is_bridge_setup():
|
||||
"""
|
||||
Returns (bool) True if the rascsi_bridge network interface exists
|
||||
"""
|
||||
try:
|
||||
bridges = (
|
||||
subprocess.run(
|
||||
["brctl", "show"],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
.stdout.decode("utf-8")
|
||||
.strip()
|
||||
)
|
||||
except subprocess.CalledProcessError as error:
|
||||
logging.warning("Executed shell command: %s", " ".join(error.cmd))
|
||||
logging.warning("Got error: %s", error.stderr.decode("utf-8"))
|
||||
bridges = ""
|
||||
|
||||
if "rascsi_bridge" in bridges:
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def disk_space():
|
||||
"""
|
||||
Returns a (dict) with (int) total (int) used (int) free
|
||||
This is the disk space information of the volume where this app is running
|
||||
"""
|
||||
total, used, free = disk_usage(__file__)
|
||||
return {"total": total, "used": used, "free": free}
|
||||
|
||||
@staticmethod
|
||||
def introspect_file(file_path, re_term):
|
||||
"""
|
||||
Takes a (str) file_path and (str) re_term in regex format
|
||||
Will introspect file_path for the existance of re_term
|
||||
and return True if found, False if not found
|
||||
"""
|
||||
try:
|
||||
ifile = open(file_path, "r", encoding="ISO-8859-1")
|
||||
except (IOError, ValueError, EOFError, TypeError) as error:
|
||||
logging.error(str(error))
|
||||
return False
|
||||
for line in ifile:
|
||||
if match(re_term, line):
|
||||
return True
|
||||
return False
|
||||
|
||||
# pylint: disable=broad-except
|
||||
@staticmethod
|
||||
def get_ip_and_host():
|
||||
"""
|
||||
Use a mock socket connection to identify the Pi's hostname and IP address
|
||||
"""
|
||||
host = gethostname()
|
||||
sock = socket(AF_INET, SOCK_DGRAM)
|
||||
try:
|
||||
# mock ip address; doesn't have to be reachable
|
||||
sock.connect(('10.255.255.255', 1))
|
||||
ip_addr = sock.getsockname()[0]
|
||||
except Exception:
|
||||
ip_addr = False
|
||||
finally:
|
||||
sock.close()
|
||||
return ip_addr, host
|
||||
|
||||
@staticmethod
|
||||
def get_logs(lines, scope):
|
||||
"""
|
||||
Takes (int) lines and (str) scope.
|
||||
If scope is a None equivalent, all syslogs are returned.
|
||||
Returns either the decoded log output, or the stderr output of journalctl.
|
||||
"""
|
||||
line_param = []
|
||||
scope_param = []
|
||||
if lines:
|
||||
line_param = ["-n", lines]
|
||||
if scope:
|
||||
scope_param = ["-u", scope]
|
||||
process = run(
|
||||
["journalctl"] + line_param + scope_param,
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
if process.returncode == 0:
|
||||
return process.returncode, process.stdout.decode("utf-8")
|
||||
|
||||
return process.returncode, process.stderr.decode("utf-8")
|
|
@ -0,0 +1,56 @@
|
|||
# RaSCSI Control Board UI
|
||||
|
||||
## Run as standalone script for development / troubleshooting
|
||||
|
||||
```bash
|
||||
# Make a virtual env named venv
|
||||
$ python3 -m venv venv
|
||||
# Use that virtual env in this shell
|
||||
$ source venv/bin/activate
|
||||
# Install requirements
|
||||
$ pip3 install -r requirements.txt
|
||||
$ python3 src/main.py
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
The script parameters can be shown with
|
||||
```
|
||||
python src/main.py --help
|
||||
```
|
||||
or
|
||||
```
|
||||
start.sh --help
|
||||
```
|
||||
|
||||
Example:
|
||||
```
|
||||
$ python3 src/main.py --rotation=0 --transitions=0
|
||||
```
|
||||
|
||||
## Run the start.sh script standalone
|
||||
|
||||
The start.sh script can also be run standalone, and will handle the venv creation/updating for you. It takes the same command line parameters in the following format:
|
||||
|
||||
```
|
||||
$ ./start.sh --rotation=0 --transitions=0
|
||||
```
|
||||
### I2C baudrate and transitions
|
||||
The available bandwidth for the display through I2C is limited. The I2C baudrate is automatically adjusted in
|
||||
easy_install.sh and start.sh to maximize the possible baudrate, however, depending on the Raspberry Pi model in use, we found that for some
|
||||
models enabling the transitions does not work very well. As a result, we have implemented a model detection for
|
||||
the raspberry pi model and enable transitions only for the following pi models:
|
||||
- Raspberry Pi 4 models
|
||||
- Raspberry Pi 3 models
|
||||
- Raspberry Pi Zero 2 models
|
||||
|
||||
The model detection can be overriden by adding a --transitions parameter to start.sh.
|
||||
|
||||
## Credits
|
||||
### DejaVuSansMono-Bold.ttf
|
||||
* Source: https://dejavu-fonts.github.io
|
||||
* Distributed under DejaVu Fonts Lience (see DejaVu Fonts License.txt for full text)
|
||||
|
||||
### splash_start_\*.bmp, splash_stop_\*.bmp
|
||||
* Drawn by Daniel Markstedt
|
||||
* Distributed under BSD 3-Clause by permission from author (see LICENSE for full text)
|
|
@ -0,0 +1,11 @@
|
|||
#adafruit-circuitpython-busdevice==5.1.1
|
||||
#adafruit-circuitpython-framebuf==1.4.8
|
||||
#adafruit-circuitpython-ssd1306==2.12.3
|
||||
luma-oled==3.8.1
|
||||
Pillow==9.0.0
|
||||
RPi.GPIO==0.7.0
|
||||
protobuf==3.19.3
|
||||
unidecode==1.3.2
|
||||
smbus==1.1.post2
|
||||
# installed via apt to avoid lengthy compilation
|
||||
#numpy==1.21.5
|
|
@ -0,0 +1,97 @@
|
|||
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
|
||||
Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
|
||||
|
||||
Bitstream Vera Fonts Copyright
|
||||
------------------------------
|
||||
|
||||
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
|
||||
a trademark of Bitstream, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of the fonts accompanying this license ("Fonts") and associated
|
||||
documentation files (the "Font Software"), to reproduce and distribute the
|
||||
Font Software, including without limitation the rights to use, copy, merge,
|
||||
publish, distribute, and/or sell copies of the Font Software, and to permit
|
||||
persons to whom the Font Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice shall
|
||||
be included in all copies of one or more of the Font Software typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in particular
|
||||
the designs of glyphs or characters in the Fonts may be modified and
|
||||
additional glyphs or characters may be added to the Fonts, only if the fonts
|
||||
are renamed to names not containing either the words "Bitstream" or the word
|
||||
"Vera".
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts or Font
|
||||
Software that has been modified and is distributed under the "Bitstream
|
||||
Vera" names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but no
|
||||
copy of one or more of the Font Software typefaces may be sold by itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
|
||||
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
|
||||
FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
|
||||
ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||
THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
|
||||
FONT SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the names of Gnome, the Gnome
|
||||
Foundation, and Bitstream Inc., shall not be used in advertising or
|
||||
otherwise to promote the sale, use or other dealings in this Font Software
|
||||
without prior written authorization from the Gnome Foundation or Bitstream
|
||||
Inc., respectively. For further information, contact: fonts at gnome dot
|
||||
org.
|
||||
|
||||
Arev Fonts Copyright
|
||||
------------------------------
|
||||
|
||||
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the fonts accompanying this license ("Fonts") and
|
||||
associated documentation files (the "Font Software"), to reproduce
|
||||
and distribute the modifications to the Bitstream Vera Font Software,
|
||||
including without limitation the rights to use, copy, merge, publish,
|
||||
distribute, and/or sell copies of the Font Software, and to permit
|
||||
persons to whom the Font Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice
|
||||
shall be included in all copies of one or more of the Font Software
|
||||
typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in
|
||||
particular the designs of glyphs or characters in the Fonts may be
|
||||
modified and additional glyphs or characters may be added to the
|
||||
Fonts, only if the fonts are renamed to names not containing either
|
||||
the words "Tavmjong Bah" or the word "Arev".
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts
|
||||
or Font Software that has been modified and is distributed under the
|
||||
"Tavmjong Bah Arev" names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but
|
||||
no copy of one or more of the Font Software typefaces may be sold by
|
||||
itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
|
||||
TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the name of Tavmjong Bah shall not
|
||||
be used in advertising or otherwise to promote the sale, use or other
|
||||
dealings in this Font Software without prior written authorization
|
||||
from Tavmjong Bah. For further information, contact: tavmjong @ free
|
||||
. fr.
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 574 B |
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,14 @@
|
|||
[Unit]
|
||||
Description=RaSCSI Control Board service
|
||||
After=network.target rascsi.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=2s
|
||||
ExecStart=/home/pi/RASCSI/python/ctrlboard/start.sh
|
||||
ExecStop=/bin/pkill --signal 2 -f "python3 src/main.py"
|
||||
SyslogIdentifier=RASCSICTRLB
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -0,0 +1,35 @@
|
|||
"""
|
||||
Module for central RaSCSI control board configuration parameters
|
||||
"""
|
||||
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
|
||||
LINES = 8
|
||||
TOKEN = ""
|
||||
BORDER = 5
|
||||
TRANSITIONS = 1
|
||||
DISPLAY_I2C_ADDRESS = CtrlBoardHardwareConstants.DISPLAY_I2C_ADDRESS
|
||||
PCA9554_I2C_ADDRESS = CtrlBoardHardwareConstants.PCA9554_I2C_ADDRESS
|
||||
MENU_REFRESH_INTERVAL = 6
|
||||
LOG_LEVEL = 30 # Warning
|
||||
|
||||
RASCSI_HOST = "localhost"
|
||||
RASCSI_PORT = "6868"
|
||||
|
||||
def __str__(self):
|
||||
result = "rotation: " + str(self.ROTATION) + "\n"
|
||||
result += "width: " + str(self.WIDTH) + "\n"
|
||||
result += "height: " + str(self.HEIGHT) + "\n"
|
||||
result += "lines: " + str(self.LINES) + "\n"
|
||||
result += "border: " + str(self.BORDER) + "\n"
|
||||
result += "rascsi host: " + str(self.RASCSI_HOST) + "\n"
|
||||
result += "rascsi port: " + str(self.RASCSI_PORT) + "\n"
|
||||
result += "transitions: " + str(self.TRANSITIONS) + "\n"
|
||||
|
||||
return result
|
|
@ -0,0 +1,275 @@
|
|||
"""Module for interfacing between the menu controller and the RaSCSI Control Board hardware"""
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from ctrlboard_event_handler.rascsi_profile_cycler import RascsiProfileCycler
|
||||
from ctrlboard_event_handler.rascsi_shutdown_cycler import RascsiShutdownCycler
|
||||
from ctrlboard_menu_builder import CtrlBoardMenuBuilder
|
||||
from ctrlboard_hw.ctrlboard_hw_constants import CtrlBoardHardwareConstants
|
||||
from ctrlboard_hw.hardware_button import HardwareButton
|
||||
from ctrlboard_hw.encoder import Encoder
|
||||
from observer import Observer
|
||||
from rascsi.file_cmds import FileCmds
|
||||
from rascsi.ractl_cmds import RaCtlCmds
|
||||
from rascsi.socket_cmds import SocketCmds
|
||||
from rascsi_menu_controller import RascsiMenuController
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
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):
|
||||
self.message = None
|
||||
self._menu_controller = menu_controller
|
||||
self._menu_renderer_config = self._menu_controller.get_menu_renderer().get_config()
|
||||
self.sock_cmd = sock_cmd
|
||||
self.ractl_cmd = ractl_cmd
|
||||
self.context_stack = []
|
||||
self.rascsi_profile_cycler: Optional[RascsiProfileCycler] = None
|
||||
self.rascsi_shutdown_cycler: Optional[RascsiShutdownCycler] = None
|
||||
|
||||
def update(self, updated_object):
|
||||
if isinstance(updated_object, HardwareButton):
|
||||
if updated_object.name == CtrlBoardHardwareConstants.ROTARY_BUTTON:
|
||||
menu = self._menu_controller.get_active_menu()
|
||||
info_object = menu.get_current_info_object()
|
||||
|
||||
self.route_rotary_button_handler(info_object)
|
||||
|
||||
self._menu_controller.get_menu_renderer().render()
|
||||
else: # button pressed
|
||||
if updated_object.name == "Bt1":
|
||||
self.handle_button1()
|
||||
elif updated_object.name == "Bt2":
|
||||
self.handle_button2()
|
||||
if isinstance(updated_object, Encoder):
|
||||
active_menu = self._menu_controller.get_active_menu()
|
||||
if updated_object.direction == 1:
|
||||
if active_menu.item_selection + 1 < len(active_menu.entries):
|
||||
self._menu_controller.get_active_menu().item_selection += 1
|
||||
if updated_object.direction == -1:
|
||||
if active_menu.item_selection - 1 >= 0:
|
||||
active_menu.item_selection -= 1
|
||||
else:
|
||||
active_menu.item_selection = 0
|
||||
self._menu_controller.get_menu_renderer().render()
|
||||
|
||||
def update_events(self):
|
||||
"""Method handling non-blocking event handling for the cycle buttons."""
|
||||
if self.rascsi_profile_cycler is not None:
|
||||
result = self.rascsi_profile_cycler.update()
|
||||
if result is not None:
|
||||
self.rascsi_profile_cycler = None
|
||||
self.context_stack = []
|
||||
self._menu_controller.segue(result)
|
||||
if self.rascsi_shutdown_cycler is not None:
|
||||
self.rascsi_shutdown_cycler.empty_messages = False
|
||||
result = self.rascsi_shutdown_cycler.update()
|
||||
if result == "return":
|
||||
self.rascsi_shutdown_cycler = None
|
||||
|
||||
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)
|
||||
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)
|
||||
else:
|
||||
self.rascsi_shutdown_cycler.cycle()
|
||||
|
||||
def route_rotary_button_handler(self, info_object):
|
||||
"""Method for handling the rotary button press for the menu navigation"""
|
||||
if info_object is None:
|
||||
return
|
||||
|
||||
context = info_object["context"]
|
||||
action = info_object["action"]
|
||||
handler_function_name = "handle_" + context + "_" + action
|
||||
try:
|
||||
handler_function = getattr(self, handler_function_name)
|
||||
if handler_function is not None:
|
||||
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))
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
# pylint: disable=unused-argument
|
||||
def handle_scsi_id_menu_openactionmenu(self, info_object):
|
||||
"""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)
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
# pylint: disable=unused-argument
|
||||
def handle_action_menu_return(self, info_object):
|
||||
"""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)
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
# pylint: disable=unused-argument
|
||||
def handle_action_menu_slot_attachinsert(self, info_object):
|
||||
"""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)
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def handle_action_menu_slot_detacheject(self, info_object):
|
||||
"""Method handles the rotary button press on detach in the action menu."""
|
||||
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)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def handle_profiles_menu_loadprofile(self, info_object):
|
||||
"""Method handles the rotary button press in the profile selection menu
|
||||
for selecting a profile to load."""
|
||||
if info_object is not None and "name" in info_object:
|
||||
file_cmd = FileCmds(sock_cmd=self.sock_cmd, ractl=self.ractl_cmd)
|
||||
result = file_cmd.read_config(file_name=info_object["name"])
|
||||
if result["status"] is True:
|
||||
self._menu_controller.show_message("Profile loaded!")
|
||||
else:
|
||||
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)
|
||||
|
||||
# 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_pi("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)
|
||||
|
||||
# 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)
|
||||
|
||||
def handle_images_menu_image_attachinsert(self, info_object):
|
||||
"""Method handles the rotary button press on an image in the image selection menu
|
||||
(through attach/insert)"""
|
||||
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)
|
||||
|
||||
def attach_insert_scsi_id(self, info_object):
|
||||
"""Helper method to attach/insert an image on a scsi id given through the menu context"""
|
||||
image_name = info_object["name"]
|
||||
device_type = info_object["device_type"]
|
||||
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)
|
||||
|
||||
if result["status"] is False:
|
||||
self._menu_controller.show_message("Attach failed!")
|
||||
else:
|
||||
self.show_id_action_message(scsi_id, "attached")
|
||||
|
||||
def detach_eject_scsi_id(self):
|
||||
"""Helper method to detach/eject an image on a scsi id given through the menu context"""
|
||||
context_object = self._menu_controller.get_active_menu().context_object
|
||||
scsi_id = context_object["scsi_id"]
|
||||
device_info = self.ractl_cmd.list_devices(scsi_id)
|
||||
|
||||
if not device_info["device_list"]:
|
||||
return
|
||||
|
||||
device_type = device_info["device_list"][0]["device_type"]
|
||||
image = device_info["device_list"][0]["image"]
|
||||
if device_type in ("SAHD", "SCHD", "SCBR", "SCDP", "SCLP", "SCHS"):
|
||||
result = self.ractl_cmd.detach_by_id(scsi_id)
|
||||
if result["status"] is True:
|
||||
self.show_id_action_message(scsi_id, "detached")
|
||||
else:
|
||||
self._menu_controller.show_message("Detach failed!")
|
||||
elif device_type in ("SCRM", "SCMO", "SCCD"):
|
||||
if image:
|
||||
result = self.ractl_cmd.eject_by_id(scsi_id)
|
||||
if result["status"] is True:
|
||||
self.show_id_action_message(scsi_id, "ejected")
|
||||
else:
|
||||
self._menu_controller.show_message("Eject failed!")
|
||||
else:
|
||||
result = self.ractl_cmd.detach_by_id(scsi_id)
|
||||
if result["status"] is True:
|
||||
self.show_id_action_message(scsi_id, "detached")
|
||||
else:
|
||||
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))
|
||||
|
||||
def show_id_action_message(self, scsi_id, action: str):
|
||||
"""Helper method for displaying an action message in the case of an exception."""
|
||||
self._menu_controller.show_message("ID " + str(scsi_id) + " " + action + "!")
|
|
@ -0,0 +1,15 @@
|
|||
"""Module for test printing events when buttons from the RaSCSI Control Board are pressed"""
|
||||
import observer
|
||||
from ctrlboard_hw.hardware_button import HardwareButton
|
||||
from ctrlboard_hw.encoder import Encoder
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
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!")
|
||||
if isinstance(updated_object, Encoder):
|
||||
print(updated_object.pos)
|
|
@ -0,0 +1,24 @@
|
|||
"""Module providing the profile cycler class for the RaSCSI Control Board UI"""
|
||||
from ctrlboard_menu_builder import CtrlBoardMenuBuilder
|
||||
from menu.cycler import Cycler
|
||||
|
||||
|
||||
class RascsiProfileCycler(Cycler):
|
||||
"""Class implementing the profile cycler for the RaSCSI Control Baord UI"""
|
||||
|
||||
def populate_cycle_entries(self):
|
||||
cycle_entries = self.file_cmd.list_config_files()
|
||||
|
||||
return cycle_entries
|
||||
|
||||
def perform_selected_entry_action(self, selected_entry):
|
||||
result = self.file_cmd.read_config(selected_entry)
|
||||
self._menu_controller.show_timed_mini_message("")
|
||||
if result["status"] is True:
|
||||
return CtrlBoardMenuBuilder.SCSI_ID_MENU
|
||||
|
||||
self._menu_controller.show_message("Failed!")
|
||||
return CtrlBoardMenuBuilder.SCSI_ID_MENU
|
||||
|
||||
def perform_return_action(self):
|
||||
return CtrlBoardMenuBuilder.SCSI_ID_MENU
|
|
@ -0,0 +1,31 @@
|
|||
"""Module providing the shutdown cycler for the RaSCSI Control Board UI """
|
||||
from menu.cycler import Cycler
|
||||
|
||||
|
||||
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)
|
||||
self.executed_once = False
|
||||
|
||||
def populate_cycle_entries(self):
|
||||
cycle_entries = ["Shutdown"]
|
||||
|
||||
return cycle_entries
|
||||
|
||||
def perform_selected_entry_action(self, selected_entry):
|
||||
if self.executed_once is False:
|
||||
self.executed_once = True
|
||||
self._menu_controller.show_timed_message("Shutting down...")
|
||||
self.ractl_cmd.shutdown_pi("system")
|
||||
return "shutdown"
|
||||
|
||||
return None
|
||||
|
||||
def perform_return_action(self):
|
||||
self._menu_controller.show_timed_mini_message("")
|
||||
self._menu_controller.show_timed_message("")
|
||||
|
||||
return "return"
|
|
@ -0,0 +1,227 @@
|
|||
"""Module providing the interface to the RaSCSI Control Board hardware"""
|
||||
# noinspection PyUnresolvedReferences
|
||||
import logging
|
||||
import RPi.GPIO as GPIO
|
||||
import numpy
|
||||
import smbus
|
||||
|
||||
from ctrlboard_hw import pca9554multiplexer
|
||||
from ctrlboard_hw.hardware_button import HardwareButton
|
||||
from ctrlboard_hw.ctrlboard_hw_constants import CtrlBoardHardwareConstants
|
||||
from ctrlboard_hw.encoder import Encoder
|
||||
from ctrlboard_hw.pca9554multiplexer import PCA9554Multiplexer
|
||||
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):
|
||||
self.display_i2c_address = display_i2c_address
|
||||
self.pca9554_i2c_address = pca9554_i2c_address
|
||||
self.rascsi_controlboard_detected = self.detect_rascsi_controlboard()
|
||||
log = logging.getLogger(__name__)
|
||||
log.info("RaSCSI Control Board detected: %s", str(self.rascsi_controlboard_detected))
|
||||
self.display_detected = self.detect_display()
|
||||
log.info("Display detected: %s", str(self.display_detected))
|
||||
|
||||
if self.rascsi_controlboard_detected is False:
|
||||
return
|
||||
|
||||
self.pos = 0
|
||||
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.input_register_buffer = numpy.uint32(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)
|
||||
|
||||
# configure button of the rotary encoder
|
||||
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.state = True
|
||||
self.button1.name = CtrlBoardHardwareConstants.BUTTON_1
|
||||
|
||||
# configure 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.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.state = True
|
||||
self.rotary_b.directionalTransition = False
|
||||
self.rotary_b.name = CtrlBoardHardwareConstants.ROTARY_B
|
||||
|
||||
# configure encoder object
|
||||
self.rotary = Encoder(self.rotary_a, self.rotary_b)
|
||||
self.rotary.pos_prev = 0
|
||||
self.rotary.name = CtrlBoardHardwareConstants.ROTARY
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
# pylint: disable=unused-argument
|
||||
def button_pressed_callback(self, channel):
|
||||
"""Method is called when a button is pressed and reads the corresponding input register."""
|
||||
input_register = self.pca_driver.read_input_register()
|
||||
self.input_register_buffer <<= 8
|
||||
self.input_register_buffer |= input_register
|
||||
|
||||
def check_button_press(self, button):
|
||||
"""Checks whether the button state has changed."""
|
||||
if button.state_interrupt is True:
|
||||
return
|
||||
|
||||
value = button.state_interrupt
|
||||
|
||||
if value != button.state and value is False:
|
||||
button.state = False
|
||||
self.notify(button)
|
||||
button.state = True
|
||||
button.state_interrupt = True
|
||||
|
||||
def check_rotary_encoder(self, rotary):
|
||||
"""Checks whether the rotary state has changed."""
|
||||
rotary.update()
|
||||
if self.rotary.pos_prev != self.rotary.pos:
|
||||
self.notify(rotary)
|
||||
self.rotary.pos_prev = self.rotary.pos
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
@staticmethod
|
||||
def button_value(input_register_buffer, bit):
|
||||
"""Method reads the button value from a specific bit in the input register."""
|
||||
tmp = input_register_buffer
|
||||
bitmask = 1 << bit
|
||||
tmp &= bitmask
|
||||
tmp >>= bit
|
||||
return tmp
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
@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
|
||||
tmp = input_register_buffer >> shiftval
|
||||
bitmask = 1 << bit
|
||||
tmp &= bitmask
|
||||
tmp >>= bit
|
||||
return tmp
|
||||
|
||||
def process_events(self):
|
||||
"""Non-blocking event processor for hardware events (button presses etc.)"""
|
||||
input_register_buffer_length = int(len(format(self.input_register_buffer, 'b'))/8)
|
||||
if input_register_buffer_length <= 1:
|
||||
return
|
||||
|
||||
input_register_buffer = self.input_register_buffer
|
||||
self.input_register_buffer = 0
|
||||
|
||||
for i in range(0, input_register_buffer_length):
|
||||
shiftval = (input_register_buffer_length-1-i)*8
|
||||
input_register = (input_register_buffer >> shiftval) & 0b11111111
|
||||
|
||||
rot_a = self.button_value(input_register, 0)
|
||||
rot_b = self.button_value(input_register, 1)
|
||||
button_rotary = self.button_value(input_register, 5)
|
||||
button_1 = self.button_value(input_register, 2)
|
||||
button_2 = self.button_value(input_register, 3)
|
||||
|
||||
if button_1 == 0:
|
||||
self.button1.state_interrupt = bool(button_1)
|
||||
|
||||
if button_2 == 0:
|
||||
self.button2.state_interrupt = bool(button_2)
|
||||
|
||||
if button_rotary == 0:
|
||||
self.rotary_button.state_interrupt = bool(button_rotary)
|
||||
|
||||
if rot_a == 0:
|
||||
self.rotary.enc_a.state_interrupt = bool(rot_a)
|
||||
|
||||
if rot_b == 0:
|
||||
self.rotary.enc_b.state_interrupt = bool(rot_b)
|
||||
|
||||
self.check_button_press(self.rotary_button)
|
||||
self.check_button_press(self.button1)
|
||||
self.check_button_press(self.button2)
|
||||
self.check_rotary_encoder(self.rotary)
|
||||
|
||||
self.rotary.state = 0b11
|
||||
self.input_register_buffer = 0
|
||||
|
||||
@staticmethod
|
||||
def detect_i2c_devices(_bus):
|
||||
"""Method finds addresses on the i2c bus"""
|
||||
detected_i2c_addresses = []
|
||||
|
||||
for _address in range(128):
|
||||
if 2 < _address < 120:
|
||||
try:
|
||||
_bus.read_byte(_address)
|
||||
address = '%02x' % _address
|
||||
detected_i2c_addresses.append(int(address, base=16))
|
||||
except IOError: # simply skip unsuccessful i2c probes
|
||||
pass
|
||||
|
||||
return detected_i2c_addresses
|
||||
|
||||
def detect_rascsi_controlboard(self):
|
||||
"""Detects whether the RaSCSI Control Board is attached by checking whether
|
||||
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)))
|
||||
|
||||
def detect_display(self):
|
||||
"""Detects whether an i2c display is connected to the RaSCSI hat."""
|
||||
# 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)
|
||||
|
||||
@staticmethod
|
||||
def cleanup():
|
||||
"""Resets pin and interrupt settings on the pins."""
|
||||
# pylint: disable=no-member
|
||||
GPIO.cleanup()
|
|
@ -0,0 +1,24 @@
|
|||
"""Module containing the RaSCSI Control Board hardware constants"""
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class CtrlBoardHardwareConstants:
|
||||
"""Class containing the RaSCSI Control Board hardware constants"""
|
||||
DISPLAY_I2C_ADDRESS = 0x3c
|
||||
PCA9554_I2C_ADDRESS = 0x3f
|
||||
PCA9554_PIN_ENC_A = 0
|
||||
PCA9554_PIN_ENC_B = 1
|
||||
PCA9554_PIN_BUTTON_1 = 2
|
||||
PCA9554_PIN_BUTTON_2 = 3
|
||||
PCA9554_PIN_BUTTON_ROTARY = 5
|
||||
PCA9554_PIN_LED_1 = 6
|
||||
PCA9554_PIN_LED_2 = 7
|
||||
|
||||
PI_PIN_INTERRUPT = 9 # BCM
|
||||
|
||||
BUTTON_1 = "Bt1"
|
||||
BUTTON_2 = "Bt2"
|
||||
ROTARY_A = "RotA"
|
||||
ROTARY_B = "RotB"
|
||||
ROTARY_BUTTON = "RotBtn"
|
||||
ROTARY = "Rot"
|
|
@ -0,0 +1,66 @@
|
|||
"""Module containing an implementation for reading the rotary encoder directions through
|
||||
the i2c multiplexer + interrupt"""
|
||||
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
|
||||
self.pos = 0
|
||||
self.state = 0b0011
|
||||
self.direction = 0
|
||||
|
||||
def update(self):
|
||||
"""Updates the internal attributes wrt. to the encoder position and direction."""
|
||||
self.update2()
|
||||
|
||||
def update2(self):
|
||||
"""Primary method for detecting the direction"""
|
||||
value_enc_a = self.enc_a.state_interrupt
|
||||
value_enc_b = self.enc_b.state_interrupt
|
||||
|
||||
self.direction = 0
|
||||
state = self.state & 0b0011
|
||||
|
||||
if value_enc_a:
|
||||
state |= 0b0100
|
||||
if value_enc_b:
|
||||
state |= 0b1000
|
||||
|
||||
if state == 0b1011:
|
||||
self.pos += 1
|
||||
self.direction = 1
|
||||
|
||||
if state == 0b0111:
|
||||
self.pos -= 1
|
||||
self.direction = -1
|
||||
|
||||
self.state = state >> 2
|
||||
|
||||
self.enc_a.state_interrupt = True
|
||||
self.enc_b.state_interrupt = True
|
||||
|
||||
def update1(self):
|
||||
"""Secondary, less well working method to detect the direction"""
|
||||
if self.enc_a.state_interrupt is True and self.enc_b.state_interrupt is True:
|
||||
return
|
||||
|
||||
if self.enc_a.state_interrupt is False and self.enc_b.state_interrupt is False:
|
||||
self.enc_a.state_interrupt = True
|
||||
self.enc_b.state_interrupt = True
|
||||
return
|
||||
|
||||
self.direction = 0
|
||||
|
||||
if self.enc_a.state_interrupt is False:
|
||||
self.pos += 1
|
||||
self.direction = 1
|
||||
elif self.enc_a.state_interrupt is True:
|
||||
self.pos -= 1
|
||||
self.direction = -1
|
||||
|
||||
self.enc_a.state_interrupt = True
|
||||
self.enc_b.state_interrupt = True
|
|
@ -0,0 +1,17 @@
|
|||
"""Module containing an abstraction for the hardware button through the i2c multiplexer"""
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class HardwareButton:
|
||||
"""Class implementing a hardware button interface that uses the i2c multiplexer"""
|
||||
|
||||
def __init__(self, pca_driver, pin):
|
||||
self.pca_driver = pca_driver
|
||||
self.pin = pin
|
||||
self.state = True
|
||||
self.state_interrupt = True
|
||||
self.name = "n/a"
|
||||
|
||||
def read(self):
|
||||
"""Reads the configured port of the i2c multiplexer"""
|
||||
return self.pca_driver.read_input_register_port(self.pin)
|
|
@ -0,0 +1,64 @@
|
|||
"""
|
||||
Module for interfacting with the pca9554 multiplexer
|
||||
"""
|
||||
# pylint: disable=c-extension-no-member
|
||||
import logging
|
||||
import smbus
|
||||
|
||||
|
||||
class PCA9554Multiplexer:
|
||||
"""Class interfacing with the pca9554 multiplexer"""
|
||||
|
||||
PIN_ENABLED_AS_OUTPUT = 0
|
||||
PIN_ENABLED_AS_INPUT = 1
|
||||
|
||||
def __init__(self, i2c_address):
|
||||
"""Constructor for the pc9554 multiplexer interface class"""
|
||||
self.i2c_address = i2c_address
|
||||
|
||||
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)
|
||||
self.i2c_bus = None
|
||||
except IOError:
|
||||
logging.error("Could not open the i2c bus.")
|
||||
self.i2c_bus = None
|
||||
|
||||
def write_configuration_register_port(self, port_bit, bit_value):
|
||||
"""Reconfigures the configuration register. Updates the specified
|
||||
port bit with bit_value. Returns true if successful, false otherwise."""
|
||||
try:
|
||||
if (0 <= port_bit <= 8) and (0 <= bit_value <= 1):
|
||||
configuration_register = self.i2c_bus.read_byte_data(self.i2c_address, 3)
|
||||
if bit_value:
|
||||
updated_configuration_register = configuration_register | (1 << port_bit)
|
||||
else:
|
||||
updated_configuration_register = configuration_register & (0xFF -
|
||||
(1 << port_bit))
|
||||
self.i2c_bus.write_byte_data(self.i2c_address, 3, updated_configuration_register)
|
||||
return True
|
||||
|
||||
return False
|
||||
except IOError:
|
||||
return False
|
||||
|
||||
def read_input_register(self):
|
||||
"""Reads the complete 8 bit input port register from pca9554"""
|
||||
try:
|
||||
return self.i2c_bus.read_byte_data(self.i2c_address, 0)
|
||||
except IOError:
|
||||
return None
|
||||
|
||||
def read_input_register_port(self, port_bit):
|
||||
"""Reads the input port register and returns the logic level of one specific port in the
|
||||
argument"""
|
||||
try:
|
||||
if 0 <= port_bit <= 8:
|
||||
input_register = self.i2c_bus.read_byte_data(self.i2c_address, 0)
|
||||
return (input_register >> port_bit) & 1
|
||||
|
||||
return None
|
||||
except IOError:
|
||||
return None
|
|
@ -0,0 +1,185 @@
|
|||
"""Module for building the control board UI specific menus"""
|
||||
import logging
|
||||
|
||||
from menu.menu import Menu
|
||||
from menu.menu_builder import MenuBuilder
|
||||
from rascsi.file_cmds import FileCmds
|
||||
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"
|
||||
PROFILES_MENU = "profiles_menu"
|
||||
DEVICEINFO_MENU = "device_info_menu"
|
||||
|
||||
ACTION_OPENACTIONMENU = "openactionmenu"
|
||||
ACTION_RETURN = "return"
|
||||
ACTION_SLOT_ATTACHINSERT = "slot_attachinsert"
|
||||
ACTION_SLOT_DETACHEJECT = "slot_detacheject"
|
||||
ACTION_SLOT_INFO = "slot_info"
|
||||
ACTION_SHUTDOWN = "shutdown"
|
||||
ACTION_LOADPROFILE = "loadprofile"
|
||||
ACTION_IMAGE_ATTACHINSERT = "image_attachinsert"
|
||||
|
||||
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)
|
||||
|
||||
def build(self, name: str, context_object=None) -> Menu:
|
||||
if name == CtrlBoardMenuBuilder.SCSI_ID_MENU:
|
||||
return self.create_scsi_id_list_menu(context_object)
|
||||
if name == CtrlBoardMenuBuilder.ACTION_MENU:
|
||||
return self.create_action_menu(context_object)
|
||||
if name == CtrlBoardMenuBuilder.IMAGES_MENU:
|
||||
return self.create_images_menu(context_object)
|
||||
if name == CtrlBoardMenuBuilder.PROFILES_MENU:
|
||||
return self.create_profiles_menu(context_object)
|
||||
if name == CtrlBoardMenuBuilder.DEVICEINFO_MENU:
|
||||
return self.create_device_info_menu(context_object)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.warning("Provided menu name [%s] cannot be built!", name)
|
||||
|
||||
return self.create_scsi_id_list_menu(context_object)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def create_scsi_id_list_menu(self, context_object=None):
|
||||
"""Method creates the menu displaying the 7 scsi slots"""
|
||||
devices = self._rascsi_client.list_devices()
|
||||
reserved_ids = self._rascsi_client.get_reserved_ids()
|
||||
|
||||
devices_by_id = {}
|
||||
for device in devices["device_list"]:
|
||||
devices_by_id[int(device["id"])] = device
|
||||
|
||||
menu = Menu(CtrlBoardMenuBuilder.SCSI_ID_MENU)
|
||||
|
||||
if reserved_ids["status"] is False:
|
||||
menu.add_entry("No scsi ids reserved")
|
||||
|
||||
for scsi_id in range(0, 8):
|
||||
device = None
|
||||
if devices_by_id.get(scsi_id) is not None:
|
||||
device = devices_by_id[scsi_id]
|
||||
file = "-"
|
||||
device_type = ""
|
||||
|
||||
if str(scsi_id) in reserved_ids["ids"]:
|
||||
file = "[Reserved]"
|
||||
elif device is not None:
|
||||
file = str(device["file"])
|
||||
device_type = str(device["device_type"])
|
||||
|
||||
menu_str = str(scsi_id) + ":"
|
||||
|
||||
if device_type == "SCDP":
|
||||
menu_str += "Daynaport"
|
||||
elif device_type == "SCBR":
|
||||
menu_str += "X68000 Host Bridge"
|
||||
elif device_type == "SCLP":
|
||||
menu_str += "SCSI Printer"
|
||||
elif device_type == "SCHS":
|
||||
menu_str += "Host Services"
|
||||
else:
|
||||
if file == "":
|
||||
menu_str += "(empty)"
|
||||
else:
|
||||
menu_str += file
|
||||
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})
|
||||
|
||||
return menu
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
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})
|
||||
return menu
|
||||
|
||||
def create_images_menu(self, context_object=None):
|
||||
"""Creates a sub menu showing all the available images"""
|
||||
menu = Menu(CtrlBoardMenuBuilder.IMAGES_MENU)
|
||||
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'])
|
||||
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}
|
||||
menu.add_entry(image_str, image_context)
|
||||
return menu
|
||||
|
||||
def create_profiles_menu(self, context_object=None):
|
||||
"""Creates a sub menu showing all the available profiles"""
|
||||
menu = Menu(CtrlBoardMenuBuilder.PROFILES_MENU)
|
||||
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})
|
||||
|
||||
return menu
|
||||
|
||||
def create_device_info_menu(self, context_object=None):
|
||||
"""Create a menu displaying information of an image in a scsi slot"""
|
||||
menu = Menu(CtrlBoardMenuBuilder.DEVICEINFO_MENU)
|
||||
menu.add_entry("Return", {"context": self.DEVICEINFO_MENU, "action": self.ACTION_RETURN})
|
||||
|
||||
device_info = self._rascsi_client.list_devices(context_object["scsi_id"])
|
||||
|
||||
if not device_info["device_list"]:
|
||||
return menu
|
||||
|
||||
scsi_id = context_object["scsi_id"]
|
||||
file = device_info["device_list"][0]["file"]
|
||||
status = device_info["device_list"][0]["status"]
|
||||
if not status:
|
||||
status = "Read/Write"
|
||||
lun = device_info["device_list"][0]["unit"]
|
||||
device_type = device_info["device_list"][0]["device_type"]
|
||||
if "parameters" in device_info["device_list"][0]:
|
||||
parameters = device_info["device_list"][0]["parameters"]
|
||||
else:
|
||||
parameters = "{}"
|
||||
|
||||
menu.add_entry("ID : " + str(scsi_id))
|
||||
menu.add_entry("LUN : " + str(lun))
|
||||
menu.add_entry("File : " + str(file))
|
||||
menu.add_entry("Type : " + str(device_type))
|
||||
menu.add_entry("R/RW : " + str(status))
|
||||
menu.add_entry("Prms : " + str(parameters))
|
||||
menu.add_entry("Vndr : " + str(device_info["device_list"][0]["vendor"]))
|
||||
menu.add_entry("Prdct: " + str(device_info["device_list"][0]["product"]))
|
||||
menu.add_entry("Rvisn: " + str(device_info["device_list"][0]["revision"]))
|
||||
menu.add_entry("Blksz: " + str(device_info["device_list"][0]["block_size"]))
|
||||
menu.add_entry("Imgsz: " + str(device_info["device_list"][0]["size"]))
|
||||
|
||||
return menu
|
||||
|
||||
def get_rascsi_client(self):
|
||||
"""Returns an instance of the rascsi client"""
|
||||
return self._rascsi_client
|
|
@ -0,0 +1,188 @@
|
|||
"""Module is the entry point for the RaSCSI Control Board UI"""
|
||||
import argparse
|
||||
import sys
|
||||
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_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.ractl_cmds import RaCtlCmds
|
||||
from rascsi.socket_cmds import SocketCmds
|
||||
|
||||
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.add_argument(
|
||||
"--rotation",
|
||||
type=int,
|
||||
choices=[0, 90, 180, 270],
|
||||
default=180,
|
||||
action="store",
|
||||
help="The rotation of the screen buffer in degrees. Default: 180",
|
||||
)
|
||||
cmdline_args_parser.add_argument(
|
||||
"--height",
|
||||
type=int,
|
||||
choices=[64],
|
||||
default=64,
|
||||
action="store",
|
||||
help="The pixel height of the screen buffer. Default: 64",
|
||||
)
|
||||
cmdline_args_parser.add_argument(
|
||||
"--rascsi-host",
|
||||
type=str,
|
||||
default="localhost",
|
||||
action="store",
|
||||
help="RaSCSI host. Default: localhost",
|
||||
)
|
||||
cmdline_args_parser.add_argument(
|
||||
"--rascsi-port",
|
||||
type=int,
|
||||
default=6868,
|
||||
action="store",
|
||||
help="RaSCSI port. Default: 6868",
|
||||
)
|
||||
cmdline_args_parser.add_argument(
|
||||
"--password",
|
||||
type=str,
|
||||
default="",
|
||||
action="store",
|
||||
help="Token password string for authenticating with RaSCSI",
|
||||
)
|
||||
cmdline_args_parser.add_argument(
|
||||
"--loglevel",
|
||||
type=int,
|
||||
choices=[0, 10, 30, 40, 50],
|
||||
default=logging.WARN,
|
||||
action="store",
|
||||
help="Loglevel. Valid values: 0 (notset), 10 (debug), 30 (warning), "
|
||||
"40 (error), 50 (critical). Default: Warning",
|
||||
)
|
||||
cmdline_args_parser.add_argument(
|
||||
"--transitions",
|
||||
type=int,
|
||||
choices=[0, 1],
|
||||
default=1,
|
||||
action="store",
|
||||
help="Transition animations. Valid values: 0 (disabled), 1 (enabled). Default: 1",
|
||||
)
|
||||
args = cmdline_args_parser.parse_args()
|
||||
config.ROTATION = args.rotation
|
||||
|
||||
if args.height == 64:
|
||||
config.HEIGHT = 64
|
||||
config.LINES = 8
|
||||
elif args.height == 32:
|
||||
config.HEIGHT = 32
|
||||
config.LINES = 4
|
||||
config.TOKEN = args.password
|
||||
config.WIDTH = 128
|
||||
config.BORDER = 5
|
||||
config.RASCSI_HOST = args.rascsi_host
|
||||
config.RASCSI_PORT = args.rascsi_port
|
||||
config.LOG_LEVEL = args.loglevel
|
||||
config.TRANSITIONS = bool(args.transitions)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def check_rascsi_connection(ractl_cmd):
|
||||
"""Checks whether a RaSCSI connection exists by polling the RaSCSI server info.
|
||||
Returns true if connection works, false if connection fails."""
|
||||
try:
|
||||
info = ractl_cmd.get_reserved_ids()
|
||||
return bool(info["status"] is True)
|
||||
except FailedSocketConnectionException:
|
||||
log = logging.getLogger(__name__)
|
||||
log.error("Could not establish connection. Stopping service")
|
||||
exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function for the RaSCSI Control Board UI"""
|
||||
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)
|
||||
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)
|
||||
|
||||
# for now, we require the complete rascsi ctrlboard hardware.
|
||||
# Oled only will be supported as well at some later point in time.
|
||||
if ctrlboard_hw.rascsi_controlboard_detected is False:
|
||||
log.error("Ctrlboard hardware not detected. Stopping service")
|
||||
exit(1)
|
||||
|
||||
sock_cmd = None
|
||||
ractl_cmd = None
|
||||
try:
|
||||
sock_cmd = SocketCmds(host=config.RASCSI_HOST, port=config.RASCSI_PORT)
|
||||
ractl_cmd = RaCtlCmds(sock_cmd=sock_cmd, token=config.TOKEN)
|
||||
except EmptySocketChunkException:
|
||||
log.error("Retrieved empty data from RaSCSI. Stopping service")
|
||||
exit(1)
|
||||
except InvalidProtobufResponse:
|
||||
log.error("Retrieved unexpected data from RaSCSI. Stopping service")
|
||||
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.")
|
||||
exit(1)
|
||||
|
||||
menu_renderer_config = MenuRendererConfig()
|
||||
|
||||
if config.TRANSITIONS is False:
|
||||
menu_renderer_config.transition = None
|
||||
|
||||
menu_renderer_config.i2c_address = CtrlBoardHardwareConstants.DISPLAY_I2C_ADDRESS
|
||||
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.add(CtrlBoardMenuBuilder.SCSI_ID_MENU)
|
||||
menu_controller.add(CtrlBoardMenuBuilder.ACTION_MENU)
|
||||
|
||||
menu_controller.show_splash_screen(f"resources/splash_start_64.bmp")
|
||||
|
||||
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)
|
||||
|
||||
while True:
|
||||
# pylint: disable=broad-except
|
||||
try:
|
||||
ctrlboard_hw.process_events()
|
||||
menu_update_event_handler.update_events()
|
||||
menu_controller.update()
|
||||
except KeyboardInterrupt:
|
||||
ctrlboard_hw.cleanup()
|
||||
break
|
||||
except Exception as ex:
|
||||
print(ex)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,26 @@
|
|||
"""Module implementing a blank screensaver"""
|
||||
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
|
||||
|
||||
def draw_screensaver(self):
|
||||
if self._initial_draw_call is True:
|
||||
self.menu_renderer.blank_screen()
|
||||
else:
|
||||
self._initial_draw_call = False
|
||||
|
||||
def check_timer(self):
|
||||
already_enabled = False
|
||||
if self.enabled is True:
|
||||
already_enabled = True
|
||||
|
||||
super().check_timer()
|
||||
|
||||
if self.enabled is True and already_enabled is False: # new switch to screensaver
|
||||
self._initial_draw_call = True
|
|
@ -0,0 +1,82 @@
|
|||
"""Module that implements a button cycling functionality"""
|
||||
from abc import abstractmethod
|
||||
from menu.timer import Timer
|
||||
from rascsi.file_cmds import FileCmds
|
||||
|
||||
|
||||
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):
|
||||
self._cycle_profile_timer_flag = Timer(activation_delay=cycle_timeout)
|
||||
self._menu_controller = menu_controller
|
||||
self.sock_cmd = sock_cmd
|
||||
self.ractl_cmd = ractl_cmd
|
||||
self.file_cmd = FileCmds(sock_cmd=self.sock_cmd, ractl=self.ractl_cmd)
|
||||
self.cycle_entries = self.populate_cycle_entries()
|
||||
self.return_string = return_string
|
||||
self.return_entry = return_entry
|
||||
self.empty_messages = empty_messages
|
||||
if self.return_entry is True:
|
||||
self.cycle_entries.insert(0, self.return_string)
|
||||
self.selected_config_file_index = 0
|
||||
self.message = str(self.cycle_entries[self.selected_config_file_index])
|
||||
self.update()
|
||||
|
||||
@abstractmethod
|
||||
def populate_cycle_entries(self):
|
||||
"""Returns a list of entries to cycle"""
|
||||
|
||||
@abstractmethod
|
||||
def perform_selected_entry_action(self, selected_entry):
|
||||
"""Performs an action on the selected cycle menu entry"""
|
||||
|
||||
@abstractmethod
|
||||
def perform_return_action(self):
|
||||
"""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 """
|
||||
|
||||
if self._cycle_profile_timer_flag is None:
|
||||
return None
|
||||
|
||||
self._cycle_profile_timer_flag.check_timer()
|
||||
if self.message is not None:
|
||||
self._menu_controller.show_timed_mini_message(self.message)
|
||||
self._menu_controller.get_menu_renderer().render()
|
||||
|
||||
if self._cycle_profile_timer_flag.enabled is False: # timer is running
|
||||
return None
|
||||
|
||||
selected_cycle_entry = str(self.cycle_entries[self.selected_config_file_index])
|
||||
|
||||
if self.return_entry is True:
|
||||
if selected_cycle_entry != self.return_string:
|
||||
if self.empty_messages is True:
|
||||
self._menu_controller.show_timed_mini_message("")
|
||||
self._menu_controller.show_timed_message("")
|
||||
return self.perform_selected_entry_action(selected_cycle_entry)
|
||||
|
||||
self._menu_controller.show_timed_mini_message("")
|
||||
self._menu_controller.show_timed_message("")
|
||||
return self.perform_return_action()
|
||||
|
||||
return self.perform_selected_entry_action(selected_cycle_entry)
|
||||
|
||||
def cycle(self):
|
||||
"""Cycles between entries in the cycle menu"""
|
||||
if self._cycle_profile_timer_flag is None:
|
||||
return
|
||||
|
||||
self.selected_config_file_index += 1
|
||||
|
||||
if self.selected_config_file_index > len(self.cycle_entries) - 1:
|
||||
self.selected_config_file_index = 0
|
||||
|
||||
self.message = str(self.cycle_entries[self.selected_config_file_index])
|
||||
|
||||
self._cycle_profile_timer_flag.reset_timer()
|
|
@ -0,0 +1,30 @@
|
|||
"""Module for creating a menu"""
|
||||
from typing import List
|
||||
|
||||
|
||||
class Menu:
|
||||
"""Class implement the Menu class"""
|
||||
def __init__(self, name: str):
|
||||
self.entries: List = []
|
||||
self.item_selection = 0
|
||||
self.name = name
|
||||
self.context_object = None
|
||||
|
||||
def add_entry(self, text, data_object=None):
|
||||
"""Adds an entry to a menu"""
|
||||
entry = {"text": text, "data_object": data_object}
|
||||
self.entries.append(entry)
|
||||
|
||||
def get_current_text(self):
|
||||
"""Returns the text content of the currently selected text in the menu."""
|
||||
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']
|
||||
|
||||
def __repr__(self):
|
||||
print("entries: " + str(self.entries))
|
||||
print("item_selection: " + str(self.item_selection))
|
||||
print("name: " + self.name)
|
||||
print("context object: " + str(self.context_object))
|
|
@ -0,0 +1,14 @@
|
|||
"""Module for creating menus"""
|
||||
from abc import ABC, abstractmethod
|
||||
from menu.menu import Menu
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class MenuBuilder(ABC):
|
||||
"""Base class for menu builders"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def build(self, name: str, context_object=None) -> Menu:
|
||||
"""builds a menu and gives it a name and a context object"""
|
|
@ -0,0 +1,145 @@
|
|||
"""Module providing the menu controller."""
|
||||
import time
|
||||
import importlib
|
||||
from typing import Dict, Optional
|
||||
from PIL import Image
|
||||
|
||||
from menu.menu import Menu
|
||||
from menu.menu_builder import MenuBuilder
|
||||
from menu.menu_renderer_config import MenuRendererConfig
|
||||
from menu.menu_renderer_luma_oled import MenuRendererLumaOled
|
||||
from menu.transition import Transition
|
||||
|
||||
|
||||
class MenuController:
|
||||
"""Class providing the menu controller. The menu controller is a central class
|
||||
that controls the menu and its associated rendering to a screen."""
|
||||
|
||||
def __init__(self, menu_builder: MenuBuilder, menu_renderer=None, menu_renderer_config=None):
|
||||
self._menus: Dict[str, Menu] = {}
|
||||
self._active_menu: Optional[Menu] = None
|
||||
self._menu_renderer = menu_renderer
|
||||
self._menu_builder: MenuBuilder = menu_builder
|
||||
self._menu_renderer_config: Optional[MenuRendererConfig]
|
||||
if menu_renderer_config is None:
|
||||
self._menu_renderer_config = MenuRendererConfig()
|
||||
else:
|
||||
self._menu_renderer_config = menu_renderer_config
|
||||
|
||||
if menu_renderer is None: # default to LumaOled renderer if nothing else is stated
|
||||
self._menu_renderer = MenuRendererLumaOled(self._menu_renderer_config)
|
||||
else:
|
||||
self._menu_renderer = menu_renderer
|
||||
self._transition: Optional[Transition] = None
|
||||
if self._menu_renderer_config.transition is None:
|
||||
self._transition = None
|
||||
return
|
||||
try:
|
||||
module = importlib.import_module("menu.transition")
|
||||
try:
|
||||
transition_class = getattr(module, self._menu_renderer_config.transition)
|
||||
if transition_class is not None:
|
||||
self._transition = transition_class(self._menu_renderer.disp)
|
||||
except AttributeError:
|
||||
pass
|
||||
except ImportError:
|
||||
print("transition module does not exist. Falling back to default.")
|
||||
self._transition = None
|
||||
|
||||
def add(self, name: str, context_object=None):
|
||||
"""Adds a menu to the menu collection internal to the controller by name.
|
||||
The associated class menu builder builds the menu by name."""
|
||||
self._menus[name] = self._menu_builder.build(name)
|
||||
if context_object is not None:
|
||||
self._menus[name].context_object = context_object
|
||||
|
||||
def set_active_menu(self, name: str, display_on_device=True):
|
||||
"""Activates a menu from the controller internal menu collection by name."""
|
||||
self._active_menu = self._menus[name]
|
||||
self._menu_renderer.set_menu(self._active_menu)
|
||||
self._menu_renderer.render(display_on_device)
|
||||
|
||||
def refresh(self, name: str, context_object=None):
|
||||
"""Refreshes a menu by name (by calling the menu builder again to rebuild the menu)."""
|
||||
item_selection = None
|
||||
if self._menus.get(name) is not None:
|
||||
item_selection = self._menus[name].item_selection
|
||||
self._menus[name] = self._menu_builder.build(name, context_object)
|
||||
|
||||
if context_object is not None:
|
||||
self._menus[name].context_object = context_object
|
||||
|
||||
if item_selection is not None:
|
||||
self._menus[name].item_selection = item_selection
|
||||
|
||||
def get_menu(self, name: str):
|
||||
"""Returns the controller internal menu collection"""
|
||||
return self._menus[name]
|
||||
|
||||
def get_active_menu(self):
|
||||
"""Returns the currently activated menu"""
|
||||
return self._active_menu
|
||||
|
||||
def get_menu_renderer_config(self):
|
||||
"""Returns the menu renderer configuration"""
|
||||
return self._menu_renderer_config
|
||||
|
||||
def get_menu_renderer(self):
|
||||
"""Returns the menu renderer for this menu controller"""
|
||||
return self._menu_renderer
|
||||
|
||||
def segue(self, name, context_object=None, transition_attributes=None):
|
||||
"""Transitions one menu into the other with all associated actions such
|
||||
as transition animations."""
|
||||
self.get_active_menu().context_object = None
|
||||
self.refresh(name, context_object)
|
||||
|
||||
if self._transition is not None and transition_attributes is not None:
|
||||
source_image = self._menu_renderer.image.copy()
|
||||
transition_menu = self.get_menu(name)
|
||||
self._menu_renderer.set_menu(transition_menu)
|
||||
target_image = self._menu_renderer.render(display_on_device=False)
|
||||
transition_attributes["transition_speed"] = self._menu_renderer_config.transition_speed
|
||||
self._transition.perform(source_image, target_image, transition_attributes)
|
||||
|
||||
self.set_active_menu(name)
|
||||
|
||||
def show_message(self, message: str, sleep=1):
|
||||
"""Displays a blocking message on the screen that stays for sleep seconds"""
|
||||
self.get_menu_renderer().message = message
|
||||
self.get_menu_renderer().render()
|
||||
time.sleep(sleep)
|
||||
self.get_menu_renderer().message = ""
|
||||
|
||||
def show_timed_message(self, message: str):
|
||||
"""Shows a message on the screen. The timed message is non-blocking for the main loop and
|
||||
simply redraws the message on screen if necessary."""
|
||||
self.get_menu_renderer().message = message
|
||||
self.get_menu_renderer().render()
|
||||
|
||||
def show_mini_message(self, message: str, sleep=1):
|
||||
"""The mini message is a message on the screen that only coveres the center portion
|
||||
of the screen, i.e., the remaining content is still visible on the screen while the mini
|
||||
message is shown in the middle. This version is blocking and stays for sleep seconds."""
|
||||
self.get_menu_renderer().mini_message = message
|
||||
self.get_menu_renderer().render()
|
||||
time.sleep(sleep)
|
||||
self.get_menu_renderer().mini_message = ""
|
||||
|
||||
def show_timed_mini_message(self, message: str):
|
||||
"""The mini message is a message on the screen that only coveres the center portion of
|
||||
the screen, i.e., the remaining content is still visible on the screen while the mini
|
||||
message is shown in the middle. This version is non-blocking for the main loop and
|
||||
simply redraws the mini message on screen if necessary."""
|
||||
self.get_menu_renderer().mini_message = message
|
||||
self.get_menu_renderer().render()
|
||||
|
||||
def show_splash_screen(self, filename, sleep=2):
|
||||
"""Shows a splash screen for a given number of seconds."""
|
||||
image = Image.open(filename).convert("1")
|
||||
self.get_menu_renderer().update_display_image(image)
|
||||
time.sleep(sleep)
|
||||
|
||||
def update(self):
|
||||
"""Updates the menu / draws the screen if necessary."""
|
||||
self._menu_renderer.update()
|
|
@ -0,0 +1,254 @@
|
|||
"""Module provides the abstract menu renderer class"""
|
||||
import time
|
||||
import math
|
||||
import itertools
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from pydoc import locate
|
||||
|
||||
from typing import Optional
|
||||
from PIL import Image
|
||||
from PIL import ImageDraw
|
||||
from PIL import ImageFont
|
||||
from menu.menu import Menu
|
||||
from menu.menu_renderer_config import MenuRendererConfig
|
||||
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 = ""
|
||||
self._menu = None
|
||||
self._config = config
|
||||
self.disp = self.display_init()
|
||||
|
||||
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
|
||||
_, self.font_height = self.font.getsize("ABCabc")
|
||||
self.cursor_position = 0
|
||||
self.frame_start_row = 0
|
||||
self.render_timestamp = None
|
||||
# effectively a small state machine that deals with the scrolling
|
||||
self._perform_scrolling_stage = 0
|
||||
self._x_scrolling = 0
|
||||
self._current_line_horizontal_overlap = None
|
||||
self._stage_timestamp: Optional[int] = None
|
||||
|
||||
screensaver = locate(self._config.screensaver)
|
||||
# noinspection PyCallingNonCallable
|
||||
self.screensaver: ScreenSaver = screensaver(self._config.screensaver_delay, self)
|
||||
|
||||
@abstractmethod
|
||||
def display_init(self):
|
||||
"""Method initializes the displays for usage."""
|
||||
|
||||
@abstractmethod
|
||||
def display_clear(self):
|
||||
"""Methods clears the screen. Possible hardware clear call if necessary."""
|
||||
|
||||
@abstractmethod
|
||||
def blank_screen(self):
|
||||
"""Method blanks the screen. Based on drawing a blank rectangle."""
|
||||
|
||||
@abstractmethod
|
||||
def update_display_image(self, image):
|
||||
"""Method displays an image using PIL."""
|
||||
|
||||
@abstractmethod
|
||||
def update_display(self):
|
||||
"""Method updates the display."""
|
||||
|
||||
def set_config(self, config: MenuRendererConfig):
|
||||
"""Configures the menu renderer with a generic menu renderer configuration."""
|
||||
self._config = config
|
||||
|
||||
def get_config(self):
|
||||
"""Returns the menu renderer configuration."""
|
||||
return self._config
|
||||
|
||||
def set_menu(self, menu: Menu):
|
||||
"""Method sets the menu that the menu renderer should draw."""
|
||||
self._menu = menu
|
||||
|
||||
def rows_per_screen(self):
|
||||
"""Calculates the number of rows per screen based on the configured font size."""
|
||||
rows = self.disp.height / self.font_height
|
||||
return math.floor(rows)
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
# in stage 1, we initialize scrolling for the currently selected line
|
||||
if self._perform_scrolling_stage == 1:
|
||||
self.setup_horizontal_scrolling(text)
|
||||
self._perform_scrolling_stage = 2 # don't repeat once the scrolling has started
|
||||
|
||||
# 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:
|
||||
self._x_scrolling -= 1
|
||||
if self._current_line_horizontal_overlap+self._x_scrolling == 0:
|
||||
self._stage_timestamp = int(time.time())
|
||||
self._perform_scrolling_stage = 3
|
||||
|
||||
# in stage 3, we wait for a little when we have scrolled to the end of the text
|
||||
if self._perform_scrolling_stage == 3:
|
||||
current_time = int(time.time())
|
||||
time_diff = current_time - self._stage_timestamp
|
||||
if time_diff >= int(self._config.scroll_line_end_delay):
|
||||
self._stage_timestamp = None
|
||||
self._perform_scrolling_stage = 4
|
||||
|
||||
# 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:
|
||||
self._x_scrolling += 1
|
||||
|
||||
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
|
||||
|
||||
# in stage 5, we wait again for a little while before we start again
|
||||
if self._perform_scrolling_stage == 5:
|
||||
current_time = int(time.time())
|
||||
time_diff = current_time - self._stage_timestamp
|
||||
if time_diff >= int(self._config.scroll_line_end_delay):
|
||||
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)
|
||||
else:
|
||||
self.draw.text((x_pos, y_pos), text, font=self.font, spacing=0, stroke_fill=0, fill=255)
|
||||
|
||||
def draw_fullsceen_message(self, text: str):
|
||||
"""Draws a fullscreen message, i.e., a full-screen message."""
|
||||
font_width, font_height = self.font.getsize(text)
|
||||
centered_width = (self.disp.width - font_width) / 2
|
||||
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)
|
||||
|
||||
def draw_mini_message(self, text: str):
|
||||
"""Draws a fullscreen message, i.e., a message covering only the center portion of
|
||||
the screen. The remaining areas stay visible."""
|
||||
font_width, _ = self.font.getsize(text)
|
||||
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)
|
||||
|
||||
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
|
||||
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
|
||||
self.frame_start_row = self._menu.item_selection + 1 - self.rows_per_screen()
|
||||
|
||||
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
|
||||
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())
|
||||
|
||||
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
|
||||
draws a given frame of the complete menu that fits the screen."""
|
||||
self.draw.rectangle((0, 0, self.disp.width, self.disp.height), outline=0, fill=0)
|
||||
row_on_screen = 0
|
||||
row_in_menuitems = frame_start_row
|
||||
for menu_entry in itertools.islice(self._menu.entries, frame_start_row, frame_end_row):
|
||||
if row_in_menuitems == self._menu.item_selection:
|
||||
self.draw_row(row_on_screen, menu_entry["text"], True)
|
||||
else:
|
||||
self.draw_row(row_on_screen, menu_entry["text"], False)
|
||||
row_in_menuitems += 1
|
||||
row_on_screen += 1
|
||||
if row_on_screen >= self.rows_per_screen():
|
||||
break
|
||||
|
||||
def render(self, display_on_device=True):
|
||||
"""Method renders the menu."""
|
||||
if display_on_device is True:
|
||||
self.screensaver.reset_timer()
|
||||
self._perform_scrolling_stage = 0
|
||||
self._current_line_horizontal_overlap = None
|
||||
self._x_scrolling = 0
|
||||
|
||||
if self._menu is None:
|
||||
self.draw_fullsceen_message("No menu set!")
|
||||
self.disp.image(self.image)
|
||||
|
||||
if display_on_device is True:
|
||||
self.disp.show()
|
||||
return None
|
||||
|
||||
self.display_clear()
|
||||
|
||||
if self.message != "":
|
||||
self.draw_fullsceen_message(self.message)
|
||||
elif self.mini_message != "":
|
||||
self.draw_mini_message(self.mini_message)
|
||||
else:
|
||||
self.draw_menu()
|
||||
|
||||
if display_on_device is True:
|
||||
self.update_display_image(self.image)
|
||||
self.update_display()
|
||||
|
||||
self.render_timestamp = int(time.time())
|
||||
|
||||
return self.image
|
||||
|
||||
def setup_horizontal_scrolling(self, text):
|
||||
"""Configure horizontal scrolling based on the configured screen dimensions."""
|
||||
font_width, _ = self.font.getsize(text)
|
||||
self._current_line_horizontal_overlap = font_width - self.disp.width
|
||||
|
||||
def update(self):
|
||||
"""Method updates the menu drawing within the main loop in a non-blocking manner.
|
||||
Also updates the current entry scrolling if activated."""
|
||||
if self._config.scroll_line is False:
|
||||
return
|
||||
|
||||
self.screensaver.check_timer()
|
||||
|
||||
if self.screensaver.enabled is True:
|
||||
self.screensaver.draw()
|
||||
return
|
||||
|
||||
current_time = int(time.time())
|
||||
time_diff = current_time - self.render_timestamp
|
||||
|
||||
if time_diff >= self._config.scroll_delay:
|
||||
if self._perform_scrolling_stage == 0:
|
||||
self._perform_scrolling_stage = 1
|
||||
self.draw_menu()
|
||||
self.update_display_image(self.image)
|
|
@ -0,0 +1,34 @@
|
|||
"""Module providing the Adafruit SSD1306 menu renderer class"""
|
||||
# pylint: disable=import-error
|
||||
from board import SCL, SDA
|
||||
import busio
|
||||
import adafruit_ssd1306
|
||||
from menu.menu_renderer import MenuRenderer
|
||||
|
||||
|
||||
class MenuRendererAdafruitSSD1306(MenuRenderer):
|
||||
"""Class implementing a menu renderer using the Adafruit SSD1306 library"""
|
||||
|
||||
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.rotation = self._config.get_mapped_rotation()
|
||||
self.disp.fill(0)
|
||||
self.disp.show()
|
||||
|
||||
return self.disp
|
||||
|
||||
def update_display_image(self, image):
|
||||
self.disp.image(self.image)
|
||||
self.disp.show()
|
||||
|
||||
def update_display(self):
|
||||
self.disp.show()
|
||||
|
||||
def display_clear(self):
|
||||
self.disp.fill(0)
|
||||
|
||||
def blank_screen(self):
|
||||
self.disp.fill(0)
|
||||
self.disp.show()
|
|
@ -0,0 +1,39 @@
|
|||
"""Module for configuring menu renderer instances"""
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes, too-few-public-methods
|
||||
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
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.width = 128
|
||||
self.height = 64
|
||||
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_size = 12
|
||||
self.row_selection_pixel_extension = 2
|
||||
self.scroll_behavior = "page" # "extend" or "page"
|
||||
self.transition = "PushTransition" # "PushTransition" or "None
|
||||
self.transition_attributes_left = {"direction": "push_left"}
|
||||
self.transition_attributes_right = {"direction": "push_right"}
|
||||
self.transition_speed = 10
|
||||
self.scroll_line = True
|
||||
self.scroll_delay = 3
|
||||
self.scroll_line_end_delay = 2
|
||||
self.screensaver = "menu.blank_screensaver.BlankScreenSaver"
|
||||
self.screensaver_delay = 25
|
||||
self.rotation = 0 # 0, 180
|
||||
|
||||
def get_mapped_rotation(self):
|
||||
"""Converts human-readable rotation value to the one expected
|
||||
by the luma and adafruit libraries"""
|
||||
return self._rotation_mapper[self.rotation]
|
|
@ -0,0 +1,34 @@
|
|||
"""Module providing the luma oled menu renderer class"""
|
||||
from luma.core.interface.serial import i2c
|
||||
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.clear()
|
||||
self.disp.show()
|
||||
|
||||
return self.disp
|
||||
|
||||
def update_display_image(self, image):
|
||||
self.disp.display(image)
|
||||
|
||||
def update_display(self):
|
||||
self.disp.display(self.image)
|
||||
|
||||
def display_clear(self):
|
||||
pass
|
||||
|
||||
def blank_screen(self):
|
||||
self.disp.clear()
|
||||
self.draw.rectangle((0, 0, self.disp.width, self.disp.height), outline=0, fill=0)
|
||||
self.disp.show()
|
|
@ -0,0 +1,33 @@
|
|||
"""Module providing the menu screensaver class"""
|
||||
from abc import abstractmethod
|
||||
from menu.timer import Timer
|
||||
|
||||
|
||||
class ScreenSaver:
|
||||
"""Class implementing the menu screensaver"""
|
||||
|
||||
def __init__(self, activation_delay, menu_renderer):
|
||||
self.enabled = False
|
||||
self.menu_renderer = menu_renderer
|
||||
self.screensaver_activation_delay = activation_delay
|
||||
self.timer_flag = Timer(self.screensaver_activation_delay)
|
||||
|
||||
def draw(self):
|
||||
"""Draws the screen saver in a non-blocking way if enabled."""
|
||||
if self.enabled is True:
|
||||
self.draw_screensaver()
|
||||
|
||||
@abstractmethod
|
||||
def draw_screensaver(self):
|
||||
"""Draws the screen saver. Must be implemented in subclasses."""
|
||||
|
||||
def check_timer(self):
|
||||
"""Checks if the screen saver should be enabled given the configured
|
||||
activation delay."""
|
||||
self.timer_flag.check_timer()
|
||||
self.enabled = self.timer_flag.enabled
|
||||
|
||||
def reset_timer(self):
|
||||
"""Resets the screen saver timer if an activitiy happend."""
|
||||
self.timer_flag.reset_timer()
|
||||
self.enabled = self.timer_flag.enabled
|
|
@ -0,0 +1,24 @@
|
|||
"""Module providing a timer class"""
|
||||
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
|
||||
self.enabled = False
|
||||
|
||||
def check_timer(self):
|
||||
"""Checks the timer whether it has reached the activation delay."""
|
||||
current_timestamp = int(time.time())
|
||||
timestamp_diff = current_timestamp - self.start_timestamp
|
||||
|
||||
if timestamp_diff >= self.activation_delay:
|
||||
self.enabled = True
|
||||
|
||||
def reset_timer(self):
|
||||
"""Resets the timer and starts from the beginning."""
|
||||
self.start_timestamp = int(time.time())
|
||||
self.enabled = False
|
|
@ -0,0 +1,65 @@
|
|||
"""Module providing implementations for menu transitions."""
|
||||
from abc import abstractmethod
|
||||
from PIL import Image
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class Transition:
|
||||
"""Class provides the interface for menu transitions. Must be subclassed."""
|
||||
|
||||
def __init__(self, disp):
|
||||
self.disp = disp
|
||||
|
||||
@abstractmethod
|
||||
def perform(self, start_image: Image, end_image: Image, transition_attributes=None) -> None:
|
||||
"""Signature for an abstract transition. Must be implemented in subclasses."""
|
||||
|
||||
|
||||
class PushTransition(Transition):
|
||||
"""Class implementing a push left/right transition."""
|
||||
PUSH_LEFT_TRANSITION = "push_left"
|
||||
PUSH_RIGHT_TRANSITION = "push_right"
|
||||
|
||||
def __init__(self, disp):
|
||||
super().__init__(disp)
|
||||
self.transition_attributes = None
|
||||
|
||||
def perform(self, start_image: Image, end_image: Image, transition_attributes=None):
|
||||
"""Performs a push transition to the left or right depending on the
|
||||
transition attribute 'direction'."""
|
||||
direction = {}
|
||||
self.transition_attributes = transition_attributes
|
||||
if transition_attributes is not None and transition_attributes != {}:
|
||||
direction = transition_attributes["direction"]
|
||||
|
||||
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)
|
||||
elif direction == PushTransition.PUSH_RIGHT_TRANSITION:
|
||||
self.perform_push_right(end_image, start_image, transition_image)
|
||||
else:
|
||||
self.disp.image(end_image)
|
||||
self.disp.show()
|
||||
|
||||
def perform_push_left(self, end_image, start_image, transition_image):
|
||||
"""Implements a push left 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((x_pos, 0, 128, 64))
|
||||
right_region = end_image.crop((0, 0, x_pos, 64))
|
||||
transition_image.paste(left_region, (0, 0, 128 - x_pos, 64))
|
||||
transition_image.paste(right_region, (128 - x_pos, 0, 128, 64))
|
||||
self.disp.display(transition_image)
|
||||
self.disp.display(end_image)
|
||||
|
||||
def perform_push_right(self, end_image, start_image, transition_image):
|
||||
"""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))
|
||||
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)
|
||||
self.disp.display(end_image)
|
|
@ -0,0 +1,21 @@
|
|||
"""Module for Observable part of the Observer pattern functionality"""
|
||||
from typing import List
|
||||
from observer import Observer
|
||||
|
||||
|
||||
class Observable:
|
||||
"""Class implementing the Observable pattern"""
|
||||
_observers: List[Observer] = []
|
||||
|
||||
def attach(self, observer: Observer):
|
||||
"""Attaches an observer to an obserable object"""
|
||||
self._observers.append(observer)
|
||||
|
||||
def detach(self, observer: Observer):
|
||||
"""detaches an observer from an observable object"""
|
||||
self._observers.remove(observer)
|
||||
|
||||
def notify(self, updated_object):
|
||||
"""Notifies all observers with a given object parameter"""
|
||||
for observer in self._observers:
|
||||
observer.update(updated_object)
|
|
@ -0,0 +1,10 @@
|
|||
"""Module implementing the Observer part of the Observer pattern"""
|
||||
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."""
|
|
@ -0,0 +1,32 @@
|
|||
"""Module implementing the RaSCSI Control Board UI specific menu controller"""
|
||||
from ctrlboard_menu_builder import CtrlBoardMenuBuilder
|
||||
from menu.menu_builder import MenuBuilder
|
||||
from menu.menu_controller import MenuController
|
||||
from menu.menu_renderer import MenuRenderer
|
||||
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):
|
||||
super().__init__(menu_builder, menu_renderer, menu_renderer_config)
|
||||
self._refresh_interval = refresh_interval
|
||||
self._menu_renderer: MenuRenderer = menu_renderer
|
||||
self._scsi_list_refresh_timer_flag = Timer(self._refresh_interval)
|
||||
|
||||
def segue(self, name, context_object=None, transition_attributes=None):
|
||||
super().segue(name, context_object, transition_attributes)
|
||||
self._scsi_list_refresh_timer_flag.reset_timer()
|
||||
|
||||
def update(self):
|
||||
super().update()
|
||||
|
||||
if self.get_active_menu().name == CtrlBoardMenuBuilder.SCSI_ID_MENU:
|
||||
self._scsi_list_refresh_timer_flag.check_timer()
|
||||
if self._scsi_list_refresh_timer_flag.enabled is True:
|
||||
self._scsi_list_refresh_timer_flag.reset_timer()
|
||||
self.refresh(name=CtrlBoardMenuBuilder.SCSI_ID_MENU)
|
||||
if self._menu_renderer.screensaver.enabled is False:
|
||||
self.set_active_menu(CtrlBoardMenuBuilder.SCSI_ID_MENU, display_on_device=False)
|
|
@ -0,0 +1,128 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
# set -x # Uncomment to Debug
|
||||
|
||||
PI_MODEL=$(/usr/bin/tr -d '\0' < /proc/device-tree/model)
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
# verify packages installed
|
||||
ERROR=0
|
||||
if ! command -v dpkg -l i2c-tools &> /dev/null ; then
|
||||
echo "i2c-tools could not be found"
|
||||
echo "Run 'sudo apt install i2c-tools' to fix."
|
||||
ERROR=1
|
||||
fi
|
||||
if ! command -v python3 &> /dev/null ; then
|
||||
echo "python3 could not be found"
|
||||
echo "Run 'sudo apt install python3' to fix."
|
||||
ERROR=1
|
||||
fi
|
||||
if ! python3 -m venv --help &> /dev/null ; then
|
||||
echo "venv could not be found"
|
||||
echo "Run 'sudo apt install python3-venv' to fix."
|
||||
ERROR=1
|
||||
fi
|
||||
# Dep to build Pillow
|
||||
if ! dpkg -l python3-dev &> /dev/null; then
|
||||
echo "python3-dev could not be found"
|
||||
echo "Run 'sudo apt install python3-dev' to fix."
|
||||
ERROR=1
|
||||
fi
|
||||
if ! dpkg -l libjpeg-dev &> /dev/null; then
|
||||
echo "libjpeg-dev could not be found"
|
||||
echo "Run 'sudo apt install libjpeg-dev' to fix."
|
||||
ERROR=1
|
||||
fi
|
||||
if ! dpkg -l libpng-dev &> /dev/null; then
|
||||
echo "libpng-dev could not be found"
|
||||
echo "Run 'sudo apt install libpng-dev' to fix."
|
||||
ERROR=1
|
||||
fi
|
||||
if ! dpkg -l libopenjp2-7-dev &> /dev/null; then
|
||||
echo "libopenjp2-7-dev could not be found"
|
||||
echo "Run 'sudo apt install libopenjp2-7-dev' to fix."
|
||||
ERROR=1
|
||||
fi
|
||||
if [ $ERROR = 1 ] ; then
|
||||
echo
|
||||
echo "Fix errors and re-run ./start.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if pgrep -f "python3 src/main.py" &> /dev/null; then
|
||||
echo "Detected active rascsi control board service"
|
||||
echo "Terminating before launching a new one."
|
||||
sudo pkill -f "python3 src/main.py"
|
||||
fi
|
||||
|
||||
if ! i2cdetect -y 1 &> /dev/null ; then
|
||||
echo "i2cdetect -y 1 did not find a screen."
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Compiler flags needed for gcc v10 and up
|
||||
if [[ `gcc --version | awk '/gcc/' | awk -F ' ' '{print $3}' | awk -F '.' '{print $1}'` -ge 10 ]]; then
|
||||
COMPILER_FLAGS="-fcommon"
|
||||
fi
|
||||
|
||||
# Test for two known broken venv states
|
||||
if test -e venv; then
|
||||
GOOD_VENV=true
|
||||
! test -e venv/bin/activate && GOOD_VENV=false
|
||||
pip3 list 1> /dev/null
|
||||
test $? -eq 1 && GOOD_VENV=false
|
||||
if ! "$GOOD_VENV"; then
|
||||
echo "Deleting bad python venv"
|
||||
sudo rm -rf venv
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create the venv if it doesn't exist
|
||||
if ! test -e venv; then
|
||||
echo "Creating python venv for RaSCSI control board service"
|
||||
python3 -m venv venv --system-site-packages
|
||||
echo "Activating venv"
|
||||
source venv/bin/activate
|
||||
echo "Installing requirements.txt"
|
||||
pip3 install wheel
|
||||
CFLAGS="$COMPILER_FLAGS" pip3 install -r requirements.txt
|
||||
|
||||
set +e
|
||||
git rev-parse --is-inside-work-tree &> /dev/null
|
||||
if [[ $? -eq 0 ]]; then
|
||||
git rev-parse HEAD > current
|
||||
fi
|
||||
fi
|
||||
|
||||
source venv/bin/activate
|
||||
|
||||
# Detect if someone updates the git repo - we need to re-run pip3 install.
|
||||
set +e
|
||||
git rev-parse --is-inside-work-tree &> /dev/null
|
||||
if [[ $? -eq 0 ]]; then
|
||||
set -e
|
||||
if ! test -e current; then
|
||||
git rev-parse > current
|
||||
elif [ "$(cat current)" != "$(git rev-parse HEAD)" ]; then
|
||||
echo "New version detected, updating libraries from requirements.txt"
|
||||
CFLAGS="$COMPILER_FLAGS" pip3 install -r requirements.txt
|
||||
git rev-parse HEAD > current
|
||||
fi
|
||||
else
|
||||
echo "Warning: Not running from a valid git repository. Will not be able to update the code."
|
||||
fi
|
||||
set -e
|
||||
|
||||
export PYTHONPATH=${PWD}/src:${PWD}/../common/src
|
||||
|
||||
echo "Starting RaSCSI control board service..."
|
||||
|
||||
if [[ ${PI_MODEL} =~ "Raspberry Pi 4" ]] || [[ ${PI_MODEL} =~ "Raspberry Pi 3" ]] ||
|
||||
[[ ${PI_MODEL} =~ "Raspberry Pi Zero 2" ]]; then
|
||||
echo "Detected: Raspberry Pi 4, Pi 3 or Pi Zero 2"
|
||||
python3 src/main.py "$@" --transitions 1
|
||||
else
|
||||
echo "Detected: Raspberry Pi Zero, Zero W, Zero WH, ..."
|
||||
echo "Transition animations will be disabled."
|
||||
python3 src/main.py "$@" --transitions 0
|
||||
fi
|
|
@ -1,21 +0,0 @@
|
|||
"""
|
||||
Module with methods that interact with the Pi's Linux system
|
||||
"""
|
||||
|
||||
|
||||
def get_ip_and_host():
|
||||
"""
|
||||
Use a mock socket connection to identify the Pi's hostname and IP address
|
||||
"""
|
||||
from socket import socket, gethostname, AF_INET, SOCK_DGRAM
|
||||
host = gethostname()
|
||||
sock = socket(AF_INET, SOCK_DGRAM)
|
||||
try:
|
||||
# mock ip address; doesn't have to be reachable
|
||||
sock.connect(('10.255.255.255', 1))
|
||||
ip_addr = sock.getsockname()[0]
|
||||
except Exception:
|
||||
ip_addr = False
|
||||
finally:
|
||||
sock.close()
|
||||
return ip_addr, host
|
|
@ -39,9 +39,9 @@ from board import I2C
|
|||
from adafruit_ssd1306 import SSD1306_I2C
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from interrupt_handler import GracefulInterruptHandler
|
||||
from pi_cmds import get_ip_and_host
|
||||
from rascsi.ractl_cmds import RaCtlCmds
|
||||
from rascsi.socket_cmds import SocketCmds
|
||||
from rascsi.sys_cmds import SysCmds
|
||||
|
||||
parser = argparse.ArgumentParser(description="RaSCSI OLED Monitor script")
|
||||
parser.add_argument(
|
||||
|
@ -99,6 +99,7 @@ TOKEN = args.password
|
|||
|
||||
sock_cmd = SocketCmds(host=args.rascsi_host, port=args.rascsi_port)
|
||||
ractl_cmd = RaCtlCmds(sock_cmd=sock_cmd, token=TOKEN)
|
||||
sys_cmd = SysCmds()
|
||||
|
||||
WIDTH = 128
|
||||
BORDER = 5
|
||||
|
@ -159,9 +160,9 @@ LINE_SPACING = 8
|
|||
# Some other nice fonts to try: http://www.dafont.com/bitmap.php
|
||||
FONT = ImageFont.truetype('resources/type_writer.ttf', FONT_SIZE)
|
||||
|
||||
IP_ADDR, HOSTNAME = get_ip_and_host()
|
||||
IP_ADDR, HOSTNAME = sys_cmd.get_ip_and_host()
|
||||
REMOVABLE_DEVICE_TYPES = ractl_cmd.get_removable_device_types()
|
||||
SUPPORT_DEVICE_TYPES = ractl_cmd.get_support_device_types()
|
||||
PERIPHERAL_DEVICE_TYPES = ractl_cmd.get_peripheral_device_types()
|
||||
|
||||
def formatted_output():
|
||||
"""
|
||||
|
@ -183,7 +184,7 @@ def formatted_output():
|
|||
else:
|
||||
output.append(f"{line['id']} {line['device_type'][2:4]} {line['status']}")
|
||||
# Special handling of devices that don't use image files
|
||||
elif line["device_type"] in (SUPPORT_DEVICE_TYPES):
|
||||
elif line["device_type"] in PERIPHERAL_DEVICE_TYPES:
|
||||
if line["vendor"] == "RaSCSI":
|
||||
output.append(f"{line['id']} {line['device_type'][2:4]} {line['product']}")
|
||||
else:
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
"""
|
||||
Module for RaSCSI device management utility methods
|
||||
"""
|
||||
|
||||
from flask_babel import _
|
||||
|
||||
|
||||
def get_valid_scsi_ids(devices, reserved_ids):
|
||||
"""
|
||||
Takes a list of (dict)s devices, and list of (int)s reserved_ids.
|
||||
Returns:
|
||||
- (list) of (int)s valid_ids, which are the SCSI ids that are not reserved
|
||||
- (int) recommended_id, which is the id that the Web UI should default to recommend
|
||||
"""
|
||||
occupied_ids = []
|
||||
for device in devices:
|
||||
occupied_ids.append(device["id"])
|
||||
|
||||
unoccupied_ids = [i for i in list(range(8)) if i not in reserved_ids + occupied_ids]
|
||||
unoccupied_ids.sort()
|
||||
valid_ids = [i for i in list(range(8)) if i not in reserved_ids]
|
||||
valid_ids.sort(reverse=True)
|
||||
|
||||
if unoccupied_ids:
|
||||
recommended_id = unoccupied_ids[-1]
|
||||
else:
|
||||
recommended_id = occupied_ids.pop(0)
|
||||
|
||||
return valid_ids, recommended_id
|
||||
|
||||
|
||||
def sort_and_format_devices(devices):
|
||||
"""
|
||||
Takes a (list) of (dict)s devices and returns a (list) of (dict)s.
|
||||
Sorts by SCSI ID acending (0 to 7).
|
||||
For SCSI IDs where no device is attached, inject a (dict) with placeholder text.
|
||||
"""
|
||||
occupied_ids = []
|
||||
for device in devices:
|
||||
occupied_ids.append(device["id"])
|
||||
|
||||
formatted_devices = devices
|
||||
|
||||
# Add padding devices and sort the list
|
||||
for i in range(8):
|
||||
if i not in occupied_ids:
|
||||
formatted_devices.append({"id": i, "device_type": "-", \
|
||||
"status": "-", "file": "-", "product": "-"})
|
||||
# Sort list of devices by id
|
||||
formatted_devices.sort(key=lambda dic: str(dic["id"]))
|
||||
|
||||
return formatted_devices
|
||||
|
||||
|
||||
def map_device_types_and_names(device_types):
|
||||
"""
|
||||
Takes a (dict) corresponding to the data structure returned by RaCtlCmds.get_device_types()
|
||||
Returns a (dict) of device_type:device_name mappings of localized device names
|
||||
"""
|
||||
for key, value in device_types.items():
|
||||
device_types[key]["name"] = get_device_name(key)
|
||||
|
||||
return device_types
|
||||
|
||||
|
||||
def get_device_name(device_type):
|
||||
"""
|
||||
Takes a four letter device acronym (str) device_type.
|
||||
Returns the human-readable name for the device type.
|
||||
"""
|
||||
if device_type == "SAHD":
|
||||
return _("SASI Hard Disk")
|
||||
elif device_type == "SCHD":
|
||||
return _("SCSI Hard Disk")
|
||||
elif device_type == "SCRM":
|
||||
return _("Removable Disk")
|
||||
elif device_type == "SCMO":
|
||||
return _("Magneto-Optical")
|
||||
elif device_type == "SCCD":
|
||||
return _("CD / DVD")
|
||||
elif device_type == "SCBR":
|
||||
return _("X68000 Host Bridge")
|
||||
elif device_type == "SCDP":
|
||||
return _("DaynaPORT SCSI/Link")
|
||||
elif device_type == "SCLP":
|
||||
return _("Printer")
|
||||
elif device_type == "SCHS":
|
||||
return _("Host Services")
|
||||
else:
|
||||
return device_type
|
|
@ -1,156 +0,0 @@
|
|||
"""
|
||||
Module for methods controlling and getting information about the Pi's Linux system
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import logging
|
||||
from flask_babel import _
|
||||
from settings import AUTH_GROUP
|
||||
|
||||
|
||||
def running_env():
|
||||
"""
|
||||
Returns (str) git and (str) env
|
||||
git contains the git hash of the checked out code
|
||||
env is the various system information where this app is running
|
||||
"""
|
||||
try:
|
||||
ra_git_version = (
|
||||
subprocess.run(
|
||||
["git", "rev-parse", "HEAD"],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
.stdout.decode("utf-8")
|
||||
.strip()
|
||||
)
|
||||
except subprocess.CalledProcessError as error:
|
||||
logging.warning("Executed shell command: %s", " ".join(error.cmd))
|
||||
logging.warning("Got error: %s", error.stderr.decode("utf-8"))
|
||||
ra_git_version = ""
|
||||
|
||||
try:
|
||||
pi_version = (
|
||||
subprocess.run(
|
||||
["uname", "-a"],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
.stdout.decode("utf-8")
|
||||
.strip()
|
||||
)
|
||||
except subprocess.CalledProcessError as error:
|
||||
logging.warning("Executed shell command: %s", " ".join(error.cmd))
|
||||
logging.warning("Got error: %s", error.stderr.decode("utf-8"))
|
||||
pi_version = "Unknown"
|
||||
|
||||
return {"git": ra_git_version, "env": pi_version}
|
||||
|
||||
|
||||
def running_proc(daemon):
|
||||
"""
|
||||
Takes (str) daemon
|
||||
Returns (int) proc, which is the number of processes currently running
|
||||
"""
|
||||
try:
|
||||
processes = (
|
||||
subprocess.run(
|
||||
["ps", "aux"],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
.stdout.decode("utf-8")
|
||||
.strip()
|
||||
)
|
||||
except subprocess.CalledProcessError as error:
|
||||
logging.warning("Executed shell command: %s", " ".join(error.cmd))
|
||||
logging.warning("Got error: %s", error.stderr.decode("utf-8"))
|
||||
processes = ""
|
||||
|
||||
from re import findall
|
||||
matching_processes = findall(daemon, processes)
|
||||
return len(matching_processes)
|
||||
|
||||
|
||||
def is_bridge_setup():
|
||||
"""
|
||||
Returns (bool) True if the rascsi_bridge network interface exists
|
||||
"""
|
||||
try:
|
||||
bridges = (
|
||||
subprocess.run(
|
||||
["brctl", "show"],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
.stdout.decode("utf-8")
|
||||
.strip()
|
||||
)
|
||||
except subprocess.CalledProcessError as error:
|
||||
logging.warning("Executed shell command: %s", " ".join(error.cmd))
|
||||
logging.warning("Got error: %s", error.stderr.decode("utf-8"))
|
||||
bridges = ""
|
||||
|
||||
if "rascsi_bridge" in bridges:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def disk_space():
|
||||
"""
|
||||
Returns a (dict) with (int) total (int) used (int) free
|
||||
This is the disk space information of the volume where this app is running
|
||||
"""
|
||||
from shutil import disk_usage
|
||||
total, used, free = disk_usage(__file__)
|
||||
return {"total": total, "used": used, "free": free}
|
||||
|
||||
|
||||
def get_ip_address():
|
||||
"""
|
||||
Use a mock socket connection to identify the Pi's IP address
|
||||
"""
|
||||
from socket import socket, AF_INET, SOCK_DGRAM
|
||||
sock = socket(AF_INET, SOCK_DGRAM)
|
||||
try:
|
||||
# mock ip address; doesn't have to be reachable
|
||||
sock.connect(('10.255.255.255', 1))
|
||||
ip_addr = sock.getsockname()[0]
|
||||
except Exception:
|
||||
ip_addr = '127.0.0.1'
|
||||
finally:
|
||||
sock.close()
|
||||
return ip_addr
|
||||
|
||||
|
||||
def introspect_file(file_path, re_term):
|
||||
"""
|
||||
Takes a (str) file_path and (str) re_term in regex format
|
||||
Will introspect file_path for the existance of re_term
|
||||
and return True if found, False if not found
|
||||
"""
|
||||
from re import match
|
||||
try:
|
||||
ifile = open(file_path, "r", encoding="ISO-8859-1")
|
||||
except:
|
||||
return False
|
||||
for line in ifile:
|
||||
if match(re_term, line):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def auth_active():
|
||||
"""
|
||||
Inspects if the group defined in AUTH_GROUP exists on the system.
|
||||
If it exists, tell the webapp to enable authentication.
|
||||
Returns a (dict) with (bool) status and (str) msg
|
||||
"""
|
||||
from grp import getgrall
|
||||
groups = [g.gr_name for g in getgrall()]
|
||||
if AUTH_GROUP in groups:
|
||||
return {
|
||||
"status": True,
|
||||
"msg": _("You must log in to use this function"),
|
||||
}
|
||||
return {"status": False, "msg": ""}
|
|
@ -41,5 +41,8 @@ class ReturnCodeMapper:
|
|||
|
||||
parameters = payload["parameters"]
|
||||
|
||||
payload["msg"] = lazy_gettext(ReturnCodeMapper.MESSAGES[payload["return_code"]], **parameters)
|
||||
payload["msg"] = lazy_gettext(
|
||||
ReturnCodeMapper.MESSAGES[payload["return_code"]],
|
||||
**parameters,
|
||||
)
|
||||
return payload
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ _("RaSCSI Control Page") }}</title>
|
||||
<title>{{ _("RaSCSI Control Page") }} [{{ host }}]</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/pwa/apple-icon-57x57.png">
|
||||
|
@ -52,22 +52,27 @@
|
|||
<div>{{ _("Log In to Use Web Interface") }}</div>
|
||||
<input type="text" name="username" placeholder="{{ _("Username") }}">
|
||||
<input type="password" name="password" placeholder="{{ _("Password") }}">
|
||||
<input type="submit" value="Login">
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span style="display: inline-block; width: 100%; color: white; background-color: green; text-align: center; vertical-align: center; font-family: Arial, Helvetica, sans-serif;">{{ _("Web Interface Authentication Disabled") }} – {{ _("See <a href=\"%(url)s\" target=\"_blank\">Wiki</a> for more information", url="https://github.com/akuker/RASCSI/wiki/Web-Interface#enable-authentication") }}</span>
|
||||
{% endif %}
|
||||
<table width="100%">
|
||||
<table width="100%" style="background-color: black;">
|
||||
<tbody>
|
||||
<tr style="background-color: black;">
|
||||
<td style="background-color: black;">
|
||||
<tr align="center">
|
||||
<td>
|
||||
<a href="http://github.com/akuker/RASCSI" target="_blank">
|
||||
<h1>RaSCSI - 68kmla Edition</h1>
|
||||
<h1>{{ _("RaSCSI Control Page") }}</h1>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="color: white;">
|
||||
hostname: {{ host }} ip: {{ ip_addr }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
<input name="type" type="hidden" value="{{ device.device_type }}">
|
||||
<input name="file_size" type="hidden" value="{{ device.size }}">
|
||||
<select type="select" name="file_name">
|
||||
{% for f in files %}
|
||||
{% for f in files|sort(attribute='name') %}
|
||||
{% if device.device_type == "SCCD" %}
|
||||
{% if f["name"].lower().endswith(cdrom_file_suffix) %}
|
||||
<option value="{{ f["name"] }}">{{ f["name"].replace(base_dir, '') }}</option>
|
||||
|
@ -601,17 +601,23 @@
|
|||
<td style="border: none; vertical-align:top;">
|
||||
<form action="/logs/show" method="post">
|
||||
<label for="lines">{{ _("Log Lines:") }}</label>
|
||||
<input name="lines" type="number" placeholder="200" min="1" size="4">
|
||||
<input name="lines" type="number" value="200" min="1" size="4">
|
||||
<label for="scope">{{ _("Scope:") }}</label>
|
||||
<select name="scope">
|
||||
<option value="default">
|
||||
default
|
||||
<option value="">
|
||||
{{ _("All logs") }}
|
||||
</option>
|
||||
<option value="rascsi">
|
||||
rascsi.service
|
||||
rascsi
|
||||
</option>
|
||||
<option value="rascsi-web">
|
||||
rascsi-web.service
|
||||
rascsi-web
|
||||
</option>
|
||||
<option value="rascsi-oled">
|
||||
rascsi-oled
|
||||
</option>
|
||||
<option value="rascsi-ctrlboard">
|
||||
rascsi-ctrlboard
|
||||
</option>
|
||||
</select>
|
||||
<input type="submit" value="{{ _("Show Logs") }}">
|
||||
|
|
|
@ -6,6 +6,13 @@ import logging
|
|||
import argparse
|
||||
from pathlib import Path
|
||||
from functools import wraps
|
||||
from grp import getgrall
|
||||
from ast import literal_eval
|
||||
|
||||
import bjoern
|
||||
from werkzeug.utils import secure_filename
|
||||
from simplepam import authenticate
|
||||
from flask_babel import Babel, Locale, refresh, _
|
||||
|
||||
from flask import (
|
||||
Flask,
|
||||
|
@ -20,26 +27,30 @@ from flask import (
|
|||
session,
|
||||
abort,
|
||||
)
|
||||
from flask_babel import Babel, Locale, refresh, _
|
||||
|
||||
from pi_cmds import (
|
||||
running_env,
|
||||
running_proc,
|
||||
is_bridge_setup,
|
||||
disk_space,
|
||||
get_ip_address,
|
||||
introspect_file,
|
||||
auth_active,
|
||||
from rascsi.ractl_cmds import RaCtlCmds
|
||||
from rascsi.file_cmds import FileCmds
|
||||
from rascsi.sys_cmds import SysCmds
|
||||
|
||||
from rascsi.common_settings import (
|
||||
CFG_DIR,
|
||||
CONFIG_FILE_SUFFIX,
|
||||
PROPERTIES_SUFFIX,
|
||||
RESERVATIONS,
|
||||
)
|
||||
|
||||
from device_utils import (
|
||||
from return_code_mapper import ReturnCodeMapper
|
||||
from socket_cmds_flask import SocketCmdsFlask
|
||||
|
||||
from web_utils import (
|
||||
sort_and_format_devices,
|
||||
get_valid_scsi_ids,
|
||||
map_device_types_and_names,
|
||||
get_device_name,
|
||||
auth_active,
|
||||
is_bridge_configured,
|
||||
upload_with_dropzonejs,
|
||||
)
|
||||
from return_code_mapper import ReturnCodeMapper
|
||||
|
||||
from settings import (
|
||||
AFP_DIR,
|
||||
MAX_FILE_SIZE,
|
||||
|
@ -50,17 +61,6 @@ from settings import (
|
|||
LANGUAGES,
|
||||
)
|
||||
|
||||
from rascsi.common_settings import (
|
||||
CFG_DIR,
|
||||
CONFIG_FILE_SUFFIX,
|
||||
PROPERTIES_SUFFIX,
|
||||
RESERVATIONS,
|
||||
)
|
||||
from rascsi.ractl_cmds import RaCtlCmds
|
||||
from rascsi.file_cmds import FileCmds
|
||||
|
||||
from socket_cmds_flask import SocketCmdsFlask
|
||||
|
||||
|
||||
APP = Flask(__name__)
|
||||
BABEL = Babel(APP)
|
||||
|
@ -93,12 +93,13 @@ def get_supported_locales():
|
|||
return sorted_locales
|
||||
|
||||
|
||||
# pylint: disable=too-many-locals
|
||||
@APP.route("/")
|
||||
def index():
|
||||
"""
|
||||
Sets up data structures for and renders the index page
|
||||
"""
|
||||
if not ractl.is_token_auth()["status"] and not APP.config["TOKEN"]:
|
||||
if not ractl_cmd.is_token_auth()["status"] and not APP.config["TOKEN"]:
|
||||
abort(
|
||||
403,
|
||||
_(
|
||||
|
@ -107,11 +108,12 @@ def index():
|
|||
),
|
||||
)
|
||||
|
||||
server_info = ractl.get_server_info()
|
||||
devices = ractl.list_devices()
|
||||
device_types = map_device_types_and_names(ractl.get_device_types()["device_types"])
|
||||
image_files = file_cmds.list_images()
|
||||
config_files = file_cmds.list_config_files()
|
||||
server_info = ractl_cmd.get_server_info()
|
||||
devices = ractl_cmd.list_devices()
|
||||
device_types = map_device_types_and_names(ractl_cmd.get_device_types()["device_types"])
|
||||
image_files = file_cmd.list_images()
|
||||
config_files = file_cmd.list_config_files()
|
||||
ip_addr, host = sys_cmd.get_ip_and_host()
|
||||
|
||||
extended_image_files = []
|
||||
for image in image_files["files"]:
|
||||
|
@ -147,10 +149,11 @@ def index():
|
|||
return render_template(
|
||||
"index.html",
|
||||
locales=get_supported_locales(),
|
||||
bridge_configured=is_bridge_setup(),
|
||||
netatalk_configured=running_proc("afpd"),
|
||||
macproxy_configured=running_proc("macproxy"),
|
||||
ip_addr=get_ip_address(),
|
||||
bridge_configured=sys_cmd.is_bridge_setup(),
|
||||
netatalk_configured=sys_cmd.running_proc("afpd"),
|
||||
macproxy_configured=sys_cmd.running_proc("macproxy"),
|
||||
ip_addr=ip_addr,
|
||||
host=host,
|
||||
devices=formatted_devices,
|
||||
files=extended_image_files,
|
||||
config_files=config_files,
|
||||
|
@ -165,24 +168,24 @@ def index():
|
|||
reserved_scsi_ids=reserved_scsi_ids,
|
||||
RESERVATIONS=RESERVATIONS,
|
||||
max_file_size=int(int(MAX_FILE_SIZE) / 1024 / 1024),
|
||||
running_env=running_env(),
|
||||
running_env=sys_cmd.running_env(),
|
||||
version=server_info["version"],
|
||||
log_levels=server_info["log_levels"],
|
||||
current_log_level=server_info["current_log_level"],
|
||||
netinfo=ractl.get_network_info(),
|
||||
netinfo=ractl_cmd.get_network_info(),
|
||||
device_types=device_types,
|
||||
free_disk=int(disk_space()["free"] / 1024 / 1024),
|
||||
free_disk=int(sys_cmd.disk_space()["free"] / 1024 / 1024),
|
||||
valid_file_suffix=valid_file_suffix,
|
||||
cdrom_file_suffix=tuple(server_info["sccd"]),
|
||||
removable_file_suffix=tuple(server_info["scrm"]),
|
||||
mo_file_suffix=tuple(server_info["scmo"]),
|
||||
username=username,
|
||||
auth_active=auth_active()["status"],
|
||||
auth_active=auth_active(AUTH_GROUP)["status"],
|
||||
ARCHIVE_FILE_SUFFIX=ARCHIVE_FILE_SUFFIX,
|
||||
PROPERTIES_SUFFIX=PROPERTIES_SUFFIX,
|
||||
REMOVABLE_DEVICE_TYPES=ractl.get_removable_device_types(),
|
||||
DISK_DEVICE_TYPES=ractl.get_disk_device_types(),
|
||||
PERIPHERAL_DEVICE_TYPES=ractl.get_peripheral_device_types(),
|
||||
REMOVABLE_DEVICE_TYPES=ractl_cmd.get_removable_device_types(),
|
||||
DISK_DEVICE_TYPES=ractl_cmd.get_disk_device_types(),
|
||||
PERIPHERAL_DEVICE_TYPES=ractl_cmd.get_peripheral_device_types(),
|
||||
)
|
||||
|
||||
|
||||
|
@ -195,7 +198,7 @@ def drive_list():
|
|||
# The file resides in the current dir of the web ui process
|
||||
drive_properties = Path(DRIVE_PROPERTIES_FILE)
|
||||
if drive_properties.is_file():
|
||||
process = file_cmds.read_drive_properties(str(drive_properties))
|
||||
process = file_cmd.read_drive_properties(str(drive_properties))
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if not process["status"]:
|
||||
flash(process["msg"], "error")
|
||||
|
@ -215,7 +218,6 @@ def drive_list():
|
|||
cd_conf = []
|
||||
rm_conf = []
|
||||
|
||||
from werkzeug.utils import secure_filename
|
||||
for device in conf:
|
||||
if device["device_type"] == "SCHD":
|
||||
device["secure_name"] = secure_filename(device["name"])
|
||||
|
@ -234,21 +236,21 @@ def drive_list():
|
|||
else:
|
||||
username = None
|
||||
|
||||
server_info = ractl.get_server_info()
|
||||
server_info = ractl_cmd.get_server_info()
|
||||
|
||||
return render_template(
|
||||
"drives.html",
|
||||
files=file_cmds.list_images()["files"],
|
||||
files=file_cmd.list_images()["files"],
|
||||
base_dir=server_info["image_dir"],
|
||||
hd_conf=hd_conf,
|
||||
cd_conf=cd_conf,
|
||||
rm_conf=rm_conf,
|
||||
running_env=running_env(),
|
||||
running_env=sys_cmd.running_env(),
|
||||
version=server_info["version"],
|
||||
free_disk=int(disk_space()["free"] / 1024 / 1024),
|
||||
free_disk=int(sys_cmd.disk_space()["free"] / 1024 / 1024),
|
||||
cdrom_file_suffix=tuple(server_info["sccd"]),
|
||||
username=username,
|
||||
auth_active=auth_active()["status"],
|
||||
auth_active=auth_active(AUTH_GROUP)["status"],
|
||||
)
|
||||
|
||||
|
||||
|
@ -260,9 +262,6 @@ def login():
|
|||
username = request.form["username"]
|
||||
password = request.form["password"]
|
||||
|
||||
from simplepam import authenticate
|
||||
from grp import getgrall
|
||||
|
||||
groups = [g.gr_name for g in getgrall() if username in g.gr_mem]
|
||||
if AUTH_GROUP in groups:
|
||||
if authenticate(str(username), str(password)):
|
||||
|
@ -287,12 +286,12 @@ def logout():
|
|||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@APP.route("/pwa/<path:path>")
|
||||
def send_pwa_files(path):
|
||||
@APP.route("/pwa/<path:pwa_path>")
|
||||
def send_pwa_files(pwa_path):
|
||||
"""
|
||||
Sets up mobile web resources
|
||||
"""
|
||||
return send_from_directory("pwa", path)
|
||||
return send_from_directory("pwa", pwa_path)
|
||||
|
||||
|
||||
def login_required(func):
|
||||
|
@ -301,7 +300,7 @@ def login_required(func):
|
|||
"""
|
||||
@wraps(func)
|
||||
def decorated_function(*args, **kwargs):
|
||||
auth = auth_active()
|
||||
auth = auth_active(AUTH_GROUP)
|
||||
if auth["status"] and "username" not in session:
|
||||
flash(auth["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
@ -325,7 +324,7 @@ def drive_create():
|
|||
full_file_name = file_name + "." + file_type
|
||||
|
||||
# Creating the image file
|
||||
process = file_cmds.create_new_image(file_name, file_type, size)
|
||||
process = file_cmd.create_new_image(file_name, file_type, size)
|
||||
if process["status"]:
|
||||
flash(_("Image file created: %(file_name)s", file_name=full_file_name))
|
||||
else:
|
||||
|
@ -340,7 +339,7 @@ def drive_create():
|
|||
"revision": revision,
|
||||
"block_size": block_size,
|
||||
}
|
||||
process = file_cmds.write_drive_properties(prop_file_name, properties)
|
||||
process = file_cmd.write_drive_properties(prop_file_name, properties)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if process["status"]:
|
||||
flash(process["msg"])
|
||||
|
@ -370,7 +369,7 @@ def drive_cdrom():
|
|||
"revision": revision,
|
||||
"block_size": block_size,
|
||||
}
|
||||
process = file_cmds.write_drive_properties(file_name, properties)
|
||||
process = file_cmd.write_drive_properties(file_name, properties)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if process["status"]:
|
||||
flash(process["msg"])
|
||||
|
@ -389,7 +388,7 @@ def config_save():
|
|||
file_name = request.form.get("name") or "default"
|
||||
file_name = f"{file_name}.{CONFIG_FILE_SUFFIX}"
|
||||
|
||||
process = file_cmds.write_config(file_name)
|
||||
process = file_cmd.write_config(file_name)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if process["status"]:
|
||||
flash(process["msg"])
|
||||
|
@ -408,7 +407,7 @@ def config_load():
|
|||
file_name = request.form.get("name")
|
||||
|
||||
if "load" in request.form:
|
||||
process = file_cmds.read_config(file_name)
|
||||
process = file_cmd.read_config(file_name)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if process["status"]:
|
||||
flash(process["msg"])
|
||||
|
@ -417,7 +416,7 @@ def config_load():
|
|||
flash(process['msg'], "error")
|
||||
return redirect(url_for("index"))
|
||||
if "delete" in request.form:
|
||||
process = file_cmds.delete_file(f"{CFG_DIR}/{file_name}")
|
||||
process = file_cmd.delete_file(f"{CFG_DIR}/{file_name}")
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if process["status"]:
|
||||
flash(process["msg"])
|
||||
|
@ -436,29 +435,16 @@ def show_logs():
|
|||
"""
|
||||
Displays system logs
|
||||
"""
|
||||
lines = request.form.get("lines") or "200"
|
||||
scope = request.form.get("scope") or "default"
|
||||
lines = request.form.get("lines")
|
||||
scope = request.form.get("scope")
|
||||
|
||||
from subprocess import run
|
||||
if scope != "default":
|
||||
process = run(
|
||||
["journalctl", "-n", lines, "-u", scope],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
else:
|
||||
process = run(
|
||||
["journalctl", "-n", lines],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
if process.returncode == 0:
|
||||
returncode, logs = sys_cmd.get_logs(lines, scope)
|
||||
if returncode == 0:
|
||||
headers = {"content-type": "text/plain"}
|
||||
return process.stdout.decode("utf-8"), int(lines), headers
|
||||
return logs, int(lines), headers
|
||||
|
||||
flash(_("An error occurred when fetching logs."))
|
||||
flash(process.stderr.decode("utf-8"), "stderr")
|
||||
flash(logs, "stderr")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
|
@ -470,7 +456,7 @@ def log_level():
|
|||
"""
|
||||
level = request.form.get("level") or "info"
|
||||
|
||||
process = ractl.set_log_level(level)
|
||||
process = ractl_cmd.set_log_level(level)
|
||||
if process["status"]:
|
||||
flash(_("Log level set to %(value)s", value=level))
|
||||
return redirect(url_for("index"))
|
||||
|
@ -502,31 +488,18 @@ def attach_device():
|
|||
error_msg = _("Please follow the instructions at %(url)s", url=error_url)
|
||||
|
||||
if "interface" in params.keys():
|
||||
if params["interface"].startswith("wlan"):
|
||||
if not introspect_file("/etc/sysctl.conf", r"^net\.ipv4\.ip_forward=1$"):
|
||||
flash(_("Configure IPv4 forwarding before using a wireless network device."), "error")
|
||||
flash(error_msg, "error")
|
||||
return redirect(url_for("index"))
|
||||
if not Path("/etc/iptables/rules.v4").is_file():
|
||||
flash(_("Configure NAT before using a wireless network device."), "error")
|
||||
flash(error_msg, "error")
|
||||
return redirect(url_for("index"))
|
||||
else:
|
||||
if not introspect_file("/etc/dhcpcd.conf", r"^denyinterfaces " + params["interface"] + r"$"):
|
||||
flash(_("Configure the network bridge before using a wired network device."), "error")
|
||||
flash(error_msg, "error")
|
||||
return redirect(url_for("index"))
|
||||
if not Path("/etc/network/interfaces.d/rascsi_bridge").is_file():
|
||||
flash(_("Configure the network bridge before using a wired network device."), "error")
|
||||
flash(error_msg, "error")
|
||||
return redirect(url_for("index"))
|
||||
bridge_status = is_bridge_configured(params["interface"])
|
||||
if bridge_status:
|
||||
flash(bridge_status, "error")
|
||||
flash(error_msg, "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
kwargs = {
|
||||
"unit": int(unit),
|
||||
"device_type": device_type,
|
||||
"params": params,
|
||||
}
|
||||
process = ractl.attach_device(scsi_id, **kwargs)
|
||||
process = ractl_cmd.attach_device(scsi_id, **kwargs)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if process["status"]:
|
||||
flash(_(
|
||||
|
@ -557,14 +530,14 @@ def attach_image():
|
|||
|
||||
if device_type:
|
||||
kwargs["device_type"] = device_type
|
||||
device_types = ractl.get_device_types()
|
||||
device_types = ractl_cmd.get_device_types()
|
||||
expected_block_size = min(device_types["device_types"][device_type]["block_sizes"])
|
||||
|
||||
# Attempt to load the device properties file:
|
||||
# same file name with PROPERTIES_SUFFIX appended
|
||||
drive_properties = f"{CFG_DIR}/{file_name}.{PROPERTIES_SUFFIX}"
|
||||
if Path(drive_properties).is_file():
|
||||
process = file_cmds.read_drive_properties(drive_properties)
|
||||
process = file_cmd.read_drive_properties(drive_properties)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if not process["status"]:
|
||||
flash(process["msg"], "error")
|
||||
|
@ -576,7 +549,7 @@ def attach_image():
|
|||
kwargs["block_size"] = conf["block_size"]
|
||||
expected_block_size = conf["block_size"]
|
||||
|
||||
process = ractl.attach_device(scsi_id, **kwargs)
|
||||
process = ractl_cmd.attach_device(scsi_id, **kwargs)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if process["status"]:
|
||||
flash(_((
|
||||
|
@ -607,7 +580,7 @@ def detach_all_devices():
|
|||
"""
|
||||
Detaches all currently attached devices
|
||||
"""
|
||||
process = ractl.detach_all()
|
||||
process = ractl_cmd.detach_all()
|
||||
if process["status"]:
|
||||
flash(_("Detached all SCSI devices"))
|
||||
return redirect(url_for("index"))
|
||||
|
@ -624,7 +597,7 @@ def detach():
|
|||
"""
|
||||
scsi_id = request.form.get("scsi_id")
|
||||
unit = request.form.get("unit")
|
||||
process = ractl.detach_by_id(scsi_id, unit)
|
||||
process = ractl_cmd.detach_by_id(scsi_id, unit)
|
||||
if process["status"]:
|
||||
flash(_("Detached SCSI ID %(id_number)s LUN %(unit_number)s",
|
||||
id_number=scsi_id, unit_number=unit))
|
||||
|
@ -645,7 +618,7 @@ def eject():
|
|||
scsi_id = request.form.get("scsi_id")
|
||||
unit = request.form.get("unit")
|
||||
|
||||
process = ractl.eject_by_id(scsi_id, unit)
|
||||
process = ractl_cmd.eject_by_id(scsi_id, unit)
|
||||
if process["status"]:
|
||||
flash(_("Ejected SCSI ID %(id_number)s LUN %(unit_number)s",
|
||||
id_number=scsi_id, unit_number=unit))
|
||||
|
@ -664,7 +637,7 @@ def device_info():
|
|||
scsi_id = request.form.get("scsi_id")
|
||||
unit = request.form.get("unit")
|
||||
|
||||
devices = ractl.list_devices(scsi_id, unit)
|
||||
devices = ractl_cmd.list_devices(scsi_id, unit)
|
||||
|
||||
# First check if any device at all was returned
|
||||
if not devices["status"]:
|
||||
|
@ -700,9 +673,9 @@ def reserve_id():
|
|||
"""
|
||||
scsi_id = request.form.get("scsi_id")
|
||||
memo = request.form.get("memo")
|
||||
reserved_ids = ractl.get_reserved_ids()["ids"]
|
||||
reserved_ids = ractl_cmd.get_reserved_ids()["ids"]
|
||||
reserved_ids.extend(scsi_id)
|
||||
process = ractl.reserve_scsi_ids(reserved_ids)
|
||||
process = ractl_cmd.reserve_scsi_ids(reserved_ids)
|
||||
if process["status"]:
|
||||
RESERVATIONS[int(scsi_id)] = memo
|
||||
flash(_("Reserved SCSI ID %(id_number)s", id_number=scsi_id))
|
||||
|
@ -719,9 +692,9 @@ def release_id():
|
|||
Releases the reservation of a SCSI ID as well as the memo for the reservation
|
||||
"""
|
||||
scsi_id = request.form.get("scsi_id")
|
||||
reserved_ids = ractl.get_reserved_ids()["ids"]
|
||||
reserved_ids = ractl_cmd.get_reserved_ids()["ids"]
|
||||
reserved_ids.remove(scsi_id)
|
||||
process = ractl.reserve_scsi_ids(reserved_ids)
|
||||
process = ractl_cmd.reserve_scsi_ids(reserved_ids)
|
||||
if process["status"]:
|
||||
RESERVATIONS[int(scsi_id)] = ""
|
||||
flash(_("Released the reservation for SCSI ID %(id_number)s", id_number=scsi_id))
|
||||
|
@ -738,7 +711,7 @@ def restart():
|
|||
"""
|
||||
Restarts the Pi
|
||||
"""
|
||||
ractl.shutdown_pi("reboot")
|
||||
ractl_cmd.shutdown_pi("reboot")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
|
@ -748,7 +721,7 @@ def shutdown():
|
|||
"""
|
||||
Shuts down the Pi
|
||||
"""
|
||||
ractl.shutdown_pi("system")
|
||||
ractl_cmd.shutdown_pi("system")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
|
@ -762,7 +735,7 @@ def download_to_iso():
|
|||
url = request.form.get("url")
|
||||
iso_args = request.form.get("type").split()
|
||||
|
||||
process = file_cmds.download_file_to_iso(url, *iso_args)
|
||||
process = file_cmd.download_file_to_iso(url, *iso_args)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if process["status"]:
|
||||
flash(process["msg"])
|
||||
|
@ -772,7 +745,7 @@ def download_to_iso():
|
|||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
process_attach = ractl.attach_device(
|
||||
process_attach = ractl_cmd.attach_device(
|
||||
scsi_id,
|
||||
device_type="SCCD",
|
||||
params={"file": process["file_name"]},
|
||||
|
@ -795,8 +768,8 @@ def download_img():
|
|||
Downloads a remote file onto the images dir on the Pi
|
||||
"""
|
||||
url = request.form.get("url")
|
||||
server_info = ractl.get_server_info()
|
||||
process = file_cmds.download_to_dir(url, server_info["image_dir"], Path(url).name)
|
||||
server_info = ractl_cmd.get_server_info()
|
||||
process = file_cmd.download_to_dir(url, server_info["image_dir"], Path(url).name)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if process["status"]:
|
||||
flash(process["msg"])
|
||||
|
@ -815,7 +788,7 @@ def download_afp():
|
|||
"""
|
||||
url = request.form.get("url")
|
||||
file_name = Path(url).name
|
||||
process = file_cmds.download_to_dir(url, AFP_DIR, file_name)
|
||||
process = file_cmd.download_to_dir(url, AFP_DIR, file_name)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if process["status"]:
|
||||
flash(process["msg"])
|
||||
|
@ -833,55 +806,12 @@ def upload_file():
|
|||
Depending on the Dropzone.js JavaScript library
|
||||
"""
|
||||
# Due to the embedded javascript library, we cannot use the @login_required decorator
|
||||
auth = auth_active()
|
||||
auth = auth_active(AUTH_GROUP)
|
||||
if auth["status"] and "username" not in session:
|
||||
return make_response(auth["msg"], 403)
|
||||
|
||||
from werkzeug.utils import secure_filename
|
||||
from os import path
|
||||
|
||||
log = logging.getLogger("pydrop")
|
||||
file_object = request.files["file"]
|
||||
file_name = secure_filename(file_object.filename)
|
||||
|
||||
server_info = ractl.get_server_info()
|
||||
|
||||
save_path = path.join(server_info["image_dir"], file_name)
|
||||
current_chunk = int(request.form['dzchunkindex'])
|
||||
|
||||
# Makes sure not to overwrite an existing file,
|
||||
# but continues writing to a file transfer in progress
|
||||
if path.exists(save_path) and current_chunk == 0:
|
||||
return make_response(_("The file already exists!"), 400)
|
||||
|
||||
try:
|
||||
with open(save_path, "ab") as save:
|
||||
save.seek(int(request.form["dzchunkbyteoffset"]))
|
||||
save.write(file_object.stream.read())
|
||||
except OSError:
|
||||
log.exception("Could not write to file")
|
||||
return make_response(_("Unable to write the file to disk!"), 500)
|
||||
|
||||
total_chunks = int(request.form["dztotalchunkcount"])
|
||||
|
||||
if current_chunk + 1 == total_chunks:
|
||||
# Validate the resulting file size after writing the last chunk
|
||||
if path.getsize(save_path) != int(request.form["dztotalfilesize"]):
|
||||
log.error(
|
||||
"Finished transferring %s, "
|
||||
"but it has a size mismatch with the original file. "
|
||||
"Got %s but we expected %s.",
|
||||
file_object.filename,
|
||||
path.getsize(save_path),
|
||||
request.form['dztotalfilesize'],
|
||||
)
|
||||
return make_response(_("Transferred file corrupted!"), 500)
|
||||
|
||||
log.info("File %s has been uploaded successfully", file_object.filename)
|
||||
log.debug("Chunk %s of %s for file %s completed.",
|
||||
current_chunk + 1, total_chunks, file_object.filename)
|
||||
|
||||
return make_response(_("File upload successful!"), 200)
|
||||
server_info = ractl_cmd.get_server_info()
|
||||
return upload_with_dropzonejs(server_info["image_dir"])
|
||||
|
||||
|
||||
@APP.route("/files/create", methods=["POST"])
|
||||
|
@ -895,7 +825,7 @@ def create_file():
|
|||
file_type = request.form.get("type")
|
||||
full_file_name = file_name + "." + file_type
|
||||
|
||||
process = file_cmds.create_new_image(file_name, file_type, size)
|
||||
process = file_cmd.create_new_image(file_name, file_type, size)
|
||||
if process["status"]:
|
||||
flash(_("Image file created: %(file_name)s", file_name=full_file_name))
|
||||
return redirect(url_for("index"))
|
||||
|
@ -922,7 +852,7 @@ def delete():
|
|||
"""
|
||||
file_name = request.form.get("file_name")
|
||||
|
||||
process = file_cmds.delete_image(file_name)
|
||||
process = file_cmd.delete_image(file_name)
|
||||
if process["status"]:
|
||||
flash(_("Image file deleted: %(file_name)s", file_name=file_name))
|
||||
else:
|
||||
|
@ -932,7 +862,7 @@ def delete():
|
|||
# Delete the drive properties file, if it exists
|
||||
prop_file_path = f"{CFG_DIR}/{file_name}.{PROPERTIES_SUFFIX}"
|
||||
if Path(prop_file_path).is_file():
|
||||
process = file_cmds.delete_file(prop_file_path)
|
||||
process = file_cmd.delete_file(prop_file_path)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
|
||||
if process["status"]:
|
||||
|
@ -954,7 +884,7 @@ def rename():
|
|||
file_name = request.form.get("file_name")
|
||||
new_file_name = request.form.get("new_file_name")
|
||||
|
||||
process = file_cmds.rename_image(file_name, new_file_name)
|
||||
process = file_cmd.rename_image(file_name, new_file_name)
|
||||
if process["status"]:
|
||||
flash(_("Image file renamed to: %(file_name)s", file_name=new_file_name))
|
||||
else:
|
||||
|
@ -965,7 +895,7 @@ def rename():
|
|||
prop_file_path = f"{CFG_DIR}/{file_name}.{PROPERTIES_SUFFIX}"
|
||||
new_prop_file_path = f"{CFG_DIR}/{new_file_name}.{PROPERTIES_SUFFIX}"
|
||||
if Path(prop_file_path).is_file():
|
||||
process = file_cmds.rename_file(prop_file_path, new_prop_file_path)
|
||||
process = file_cmd.rename_file(prop_file_path, new_prop_file_path)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if process["status"]:
|
||||
flash(process["msg"])
|
||||
|
@ -987,11 +917,10 @@ def unzip():
|
|||
zip_member = request.form.get("zip_member") or False
|
||||
zip_members = request.form.get("zip_members") or False
|
||||
|
||||
from ast import literal_eval
|
||||
if zip_members:
|
||||
zip_members = literal_eval(zip_members)
|
||||
|
||||
process = file_cmds.unzip_file(zip_file, zip_member, zip_members)
|
||||
process = file_cmd.unzip_file(zip_file, zip_member, zip_members)
|
||||
if process["status"]:
|
||||
if not process["msg"]:
|
||||
flash(_("Aborted unzip: File(s) with the same name already exists."), "error")
|
||||
|
@ -1015,8 +944,8 @@ def change_language():
|
|||
"""
|
||||
locale = request.form.get("locale")
|
||||
session["language"] = locale
|
||||
ractl.locale = session["language"]
|
||||
file_cmds.locale = session["language"]
|
||||
ractl_cmd.locale = session["language"]
|
||||
file_cmd.locale = session["language"]
|
||||
refresh()
|
||||
|
||||
language = Locale.parse(locale)
|
||||
|
@ -1032,8 +961,8 @@ def detect_locale():
|
|||
This requires the Flask app to have started first.
|
||||
"""
|
||||
session["language"] = get_locale()
|
||||
ractl.locale = session["language"]
|
||||
file_cmds.locale = session["language"]
|
||||
ractl_cmd.locale = session["language"]
|
||||
file_cmd.locale = session["language"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -1074,12 +1003,12 @@ if __name__ == "__main__":
|
|||
APP.config["TOKEN"] = arguments.password
|
||||
|
||||
sock_cmd = SocketCmdsFlask(host=arguments.rascsi_host, port=arguments.rascsi_port)
|
||||
ractl = RaCtlCmds(sock_cmd=sock_cmd, token=APP.config["TOKEN"])
|
||||
file_cmds = FileCmds(sock_cmd=sock_cmd, ractl=ractl, token=APP.config["TOKEN"])
|
||||
ractl_cmd = RaCtlCmds(sock_cmd=sock_cmd, token=APP.config["TOKEN"])
|
||||
file_cmd = FileCmds(sock_cmd=sock_cmd, ractl=ractl_cmd, token=APP.config["TOKEN"])
|
||||
sys_cmd = SysCmds()
|
||||
|
||||
if Path(f"{CFG_DIR}/{DEFAULT_CONFIG}").is_file():
|
||||
file_cmds.read_config(DEFAULT_CONFIG)
|
||||
file_cmd.read_config(DEFAULT_CONFIG)
|
||||
|
||||
import bjoern
|
||||
print("Serving rascsi-web...")
|
||||
bjoern.run(APP, "0.0.0.0", arguments.port)
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
"""
|
||||
Module for RaSCSI Web Interface utility methods
|
||||
"""
|
||||
|
||||
import logging
|
||||
from grp import getgrall
|
||||
from os import path
|
||||
from pathlib import Path
|
||||
|
||||
from flask import request, make_response
|
||||
from flask_babel import _
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from rascsi.sys_cmds import SysCmds
|
||||
|
||||
def get_valid_scsi_ids(devices, reserved_ids):
|
||||
"""
|
||||
Takes a list of (dict)s devices, and list of (int)s reserved_ids.
|
||||
Returns:
|
||||
- (list) of (int)s valid_ids, which are the SCSI ids that are not reserved
|
||||
- (int) recommended_id, which is the id that the Web UI should default to recommend
|
||||
"""
|
||||
occupied_ids = []
|
||||
for device in devices:
|
||||
occupied_ids.append(device["id"])
|
||||
|
||||
unoccupied_ids = [i for i in list(range(8)) if i not in reserved_ids + occupied_ids]
|
||||
unoccupied_ids.sort()
|
||||
valid_ids = [i for i in list(range(8)) if i not in reserved_ids]
|
||||
valid_ids.sort(reverse=True)
|
||||
|
||||
if unoccupied_ids:
|
||||
recommended_id = unoccupied_ids[-1]
|
||||
else:
|
||||
recommended_id = occupied_ids.pop(0)
|
||||
|
||||
return valid_ids, recommended_id
|
||||
|
||||
|
||||
def sort_and_format_devices(devices):
|
||||
"""
|
||||
Takes a (list) of (dict)s devices and returns a (list) of (dict)s.
|
||||
Sorts by SCSI ID acending (0 to 7).
|
||||
For SCSI IDs where no device is attached, inject a (dict) with placeholder text.
|
||||
"""
|
||||
occupied_ids = []
|
||||
for device in devices:
|
||||
occupied_ids.append(device["id"])
|
||||
|
||||
formatted_devices = devices
|
||||
|
||||
# Add padding devices and sort the list
|
||||
for i in range(8):
|
||||
if i not in occupied_ids:
|
||||
formatted_devices.append({"id": i, "device_type": "-", \
|
||||
"status": "-", "file": "-", "product": "-"})
|
||||
# Sort list of devices by id
|
||||
formatted_devices.sort(key=lambda dic: str(dic["id"]))
|
||||
|
||||
return formatted_devices
|
||||
|
||||
|
||||
def map_device_types_and_names(device_types):
|
||||
"""
|
||||
Takes a (dict) corresponding to the data structure returned by RaCtlCmds.get_device_types()
|
||||
Returns a (dict) of device_type:device_name mappings of localized device names
|
||||
"""
|
||||
for device in device_types.keys():
|
||||
device_types[device]["name"] = get_device_name(device)
|
||||
|
||||
return device_types
|
||||
|
||||
|
||||
# pylint: disable=too-many-return-statements
|
||||
def get_device_name(device_type):
|
||||
"""
|
||||
Takes a four letter device acronym (str) device_type.
|
||||
Returns the human-readable name for the device type.
|
||||
"""
|
||||
if device_type == "SAHD":
|
||||
return _("SASI Hard Disk")
|
||||
if device_type == "SCHD":
|
||||
return _("SCSI Hard Disk")
|
||||
if device_type == "SCRM":
|
||||
return _("Removable Disk")
|
||||
if device_type == "SCMO":
|
||||
return _("Magneto-Optical")
|
||||
if device_type == "SCCD":
|
||||
return _("CD / DVD")
|
||||
if device_type == "SCBR":
|
||||
return _("X68000 Host Bridge")
|
||||
if device_type == "SCDP":
|
||||
return _("DaynaPORT SCSI/Link")
|
||||
if device_type == "SCLP":
|
||||
return _("Printer")
|
||||
if device_type == "SCHS":
|
||||
return _("Host Services")
|
||||
return device_type
|
||||
|
||||
|
||||
def auth_active(group):
|
||||
"""
|
||||
Inspects if the group defined in (str) group exists on the system.
|
||||
If it exists, tell the webapp to enable authentication.
|
||||
Returns a (dict) with (bool) status and (str) msg
|
||||
"""
|
||||
groups = [g.gr_name for g in getgrall()]
|
||||
if group in groups:
|
||||
return {
|
||||
"status": True,
|
||||
"msg": _("You must log in to use this function"),
|
||||
}
|
||||
return {"status": False, "msg": ""}
|
||||
|
||||
|
||||
def is_bridge_configured(interface):
|
||||
"""
|
||||
Takes (str) interface of a network device being attached.
|
||||
Returns (bool) False if the network bridge is configured.
|
||||
Returns (str) with an error message if the network bridge is not configured.
|
||||
"""
|
||||
sys_cmd = SysCmds()
|
||||
if interface.startswith("wlan"):
|
||||
if not sys_cmd.introspect_file("/etc/sysctl.conf", r"^net\.ipv4\.ip_forward=1$"):
|
||||
return _("Configure IPv4 forwarding before using a wireless network device.")
|
||||
if not Path("/etc/iptables/rules.v4").is_file():
|
||||
return _("Configure NAT before using a wireless network device.")
|
||||
else:
|
||||
if not sys_cmd.introspect_file(
|
||||
"/etc/dhcpcd.conf",
|
||||
r"^denyinterfaces " + interface + r"$",
|
||||
):
|
||||
return _("Configure the network bridge before using a wired network device.")
|
||||
if not Path("/etc/network/interfaces.d/rascsi_bridge").is_file():
|
||||
return _("Configure the network bridge before using a wired network device.")
|
||||
return False
|
||||
|
||||
|
||||
def upload_with_dropzonejs(image_dir):
|
||||
"""
|
||||
Takes (str) image_dir which is the path to the image dir to store files.
|
||||
Opens a stream to transfer a file via the embedded dropzonejs library.
|
||||
"""
|
||||
log = logging.getLogger("pydrop")
|
||||
file_object = request.files["file"]
|
||||
file_name = secure_filename(file_object.filename)
|
||||
|
||||
save_path = path.join(image_dir, file_name)
|
||||
current_chunk = int(request.form['dzchunkindex'])
|
||||
|
||||
# Makes sure not to overwrite an existing file,
|
||||
# but continues writing to a file transfer in progress
|
||||
if path.exists(save_path) and current_chunk == 0:
|
||||
return make_response(_("The file already exists!"), 400)
|
||||
|
||||
try:
|
||||
with open(save_path, "ab") as save:
|
||||
save.seek(int(request.form["dzchunkbyteoffset"]))
|
||||
save.write(file_object.stream.read())
|
||||
except OSError:
|
||||
log.exception("Could not write to file")
|
||||
return make_response(_("Unable to write the file to disk!"), 500)
|
||||
|
||||
total_chunks = int(request.form["dztotalchunkcount"])
|
||||
|
||||
if current_chunk + 1 == total_chunks:
|
||||
# Validate the resulting file size after writing the last chunk
|
||||
if path.getsize(save_path) != int(request.form["dztotalfilesize"]):
|
||||
log.error(
|
||||
"Finished transferring %s, "
|
||||
"but it has a size mismatch with the original file. "
|
||||
"Got %s but we expected %s.",
|
||||
file_object.filename,
|
||||
path.getsize(save_path),
|
||||
request.form['dztotalfilesize'],
|
||||
)
|
||||
return make_response(_("Transferred file corrupted!"), 500)
|
||||
|
||||
log.info("File %s has been uploaded successfully", file_object.filename)
|
||||
log.debug("Chunk %s of %s for file %s completed.",
|
||||
current_chunk + 1, total_chunks, file_object.filename)
|
||||
|
||||
return make_response(_("File upload successful!"), 200)
|
|
@ -184,7 +184,7 @@ ALL: all
|
|||
docs: $(DOC_DIR)/rascsi_man_page.txt $(DOC_DIR)/rasctl_man_page.txt $(DOC_DIR)/scsimon_man_page.txt
|
||||
|
||||
$(BINDIR)/$(RASCSI): $(SRC_PROTOBUF) $(OBJ_RASCSI) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI) -lpthread -lz -lpcap -lprotobuf -lstdc++fs
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI) -lpthread -lpcap -lprotobuf -lstdc++fs
|
||||
|
||||
$(BINDIR)/$(RASCTL): $(SRC_PROTOBUF) $(OBJ_RASCTL) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCTL) -lpthread -lprotobuf -lstdc++fs
|
||||
|
|
|
@ -115,7 +115,7 @@ void SASIDEV::Connect(int id, BUS *bus)
|
|||
// Set the logical unit
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void SASIDEV::SetUnit(int no, Device *dev)
|
||||
void SASIDEV::SetUnit(int no, PrimaryDevice *dev)
|
||||
{
|
||||
ASSERT(no < UnitMax);
|
||||
|
||||
|
@ -626,7 +626,7 @@ void SASIDEV::DataOut()
|
|||
void SASIDEV::Error(ERROR_CODES::sense_key sense_key, ERROR_CODES::asc asc, ERROR_CODES::status status)
|
||||
{
|
||||
// Get bus information
|
||||
((GPIOBUS*)ctrl.bus)->Aquire();
|
||||
ctrl.bus->Aquire();
|
||||
|
||||
// Reset check
|
||||
if (ctrl.bus->GetRST()) {
|
||||
|
@ -664,7 +664,7 @@ void SASIDEV::CmdTestUnitReady()
|
|||
LOGTRACE("%s TEST UNIT READY Command ", __PRETTY_FUNCTION__);
|
||||
|
||||
// Command processing on drive
|
||||
((Disk *)ctrl.device)->TestUnitReady(this);
|
||||
ctrl.device->TestUnitReady(this);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
@ -690,7 +690,7 @@ void SASIDEV::CmdRequestSense()
|
|||
LOGTRACE( "%s REQUEST SENSE Command ", __PRETTY_FUNCTION__);
|
||||
|
||||
// Command processing on drive
|
||||
((Disk *)ctrl.device)->RequestSense(this);
|
||||
ctrl.device->RequestSense(this);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
@ -847,8 +847,8 @@ void SASIDEV::CmdAssign()
|
|||
{
|
||||
LOGTRACE("%s ASSIGN Command ", __PRETTY_FUNCTION__);
|
||||
|
||||
// Command processing on drive
|
||||
bool status = ((Disk *)ctrl.device)->CheckReady();
|
||||
// Command processing on device
|
||||
bool status = ctrl.device->CheckReady();
|
||||
if (!status) {
|
||||
// Failure (Error)
|
||||
Error();
|
||||
|
@ -871,8 +871,8 @@ void SASIDEV::CmdSpecify()
|
|||
{
|
||||
LOGTRACE("%s SPECIFY Command ", __PRETTY_FUNCTION__);
|
||||
|
||||
// Command processing on drive
|
||||
bool status = ((Disk *)ctrl.device)->CheckReady();
|
||||
// Command processing on device
|
||||
bool status =ctrl.device->CheckReady();
|
||||
if (!status) {
|
||||
// Failure (Error)
|
||||
Error();
|
||||
|
@ -1143,10 +1143,12 @@ bool SASIDEV::XferOut(bool cont)
|
|||
case SASIDEV::eCmdWrite16:
|
||||
case SASIDEV::eCmdVerify10:
|
||||
case SASIDEV::eCmdVerify16:
|
||||
{
|
||||
// If we're a host bridge, use the host bridge's SendMessage10 function
|
||||
// TODO This class must not know about SCSIBR
|
||||
if (device->IsBridge()) {
|
||||
if (!((SCSIBR*)device)->SendMessage10(ctrl.cmd, ctrl.buffer)) {
|
||||
SCSIBR *bridge = dynamic_cast<SCSIBR *>(device);
|
||||
if (bridge) {
|
||||
if (!bridge->SendMessage10(ctrl.cmd, ctrl.buffer)) {
|
||||
// write failed
|
||||
return false;
|
||||
}
|
||||
|
@ -1158,8 +1160,9 @@ bool SASIDEV::XferOut(bool cont)
|
|||
|
||||
// Special case Write function for DaynaPort
|
||||
// TODO This class must not know about DaynaPort
|
||||
if (device->IsDaynaPort()) {
|
||||
if (!((SCSIDaynaPort*)device)->Write(ctrl.cmd, ctrl.buffer, ctrl.length)) {
|
||||
SCSIDaynaPort *daynaport = dynamic_cast<SCSIDaynaPort *>(device);
|
||||
if (daynaport) {
|
||||
if (!daynaport->Write(ctrl.cmd, ctrl.buffer, ctrl.length)) {
|
||||
// write failed
|
||||
return false;
|
||||
}
|
||||
|
@ -1191,6 +1194,7 @@ bool SASIDEV::XferOut(bool cont)
|
|||
// If normal, work setting
|
||||
ctrl.offset = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// SPECIFY(SASI only)
|
||||
case SASIDEV::eCmdInvalid:
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "fileio.h"
|
||||
|
||||
class Device;
|
||||
class PrimaryDevice;
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
|
@ -123,10 +124,10 @@ public:
|
|||
DWORD length; // Transfer remaining length
|
||||
|
||||
// Logical unit
|
||||
Device *unit[UnitMax];
|
||||
PrimaryDevice *unit[UnitMax];
|
||||
|
||||
// The current device
|
||||
Device *device;
|
||||
PrimaryDevice *device;
|
||||
|
||||
// The LUN from the IDENTIFY message
|
||||
int lun;
|
||||
|
@ -144,7 +145,7 @@ public:
|
|||
// Connect
|
||||
void Connect(int id, BUS *sbus); // Controller connection
|
||||
Device* GetUnit(int no); // Get logical unit
|
||||
void SetUnit(int no, Device *dev); // Logical unit setting
|
||||
void SetUnit(int no, PrimaryDevice *dev); // Logical unit setting
|
||||
bool HasUnit(); // Has a valid logical unit
|
||||
|
||||
// Other
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
SCSIDEV::SCSIDEV() : SASIDEV()
|
||||
{
|
||||
scsi.is_byte_transfer = false;
|
||||
scsi.bytes_to_transfer = 0;
|
||||
shutdown_mode = NONE;
|
||||
|
||||
|
@ -45,6 +46,7 @@ SCSIDEV::~SCSIDEV()
|
|||
|
||||
void SCSIDEV::Reset()
|
||||
{
|
||||
scsi.is_byte_transfer = false;
|
||||
scsi.bytes_to_transfer = 0;
|
||||
|
||||
// Work initialization
|
||||
|
@ -63,7 +65,7 @@ BUS::phase_t SCSIDEV::Process(int initiator_id)
|
|||
}
|
||||
|
||||
// Get bus information
|
||||
((GPIOBUS*)ctrl.bus)->Aquire();
|
||||
ctrl.bus->Aquire();
|
||||
|
||||
// Check to see if the reset signal was asserted
|
||||
if (ctrl.bus->GetRST()) {
|
||||
|
@ -164,18 +166,25 @@ void SCSIDEV::BusFree()
|
|||
|
||||
// When the bus is free RaSCSI or the Pi may be shut down
|
||||
switch(shutdown_mode) {
|
||||
case RASCSI:
|
||||
case STOP_RASCSI:
|
||||
LOGINFO("RaSCSI shutdown requested");
|
||||
exit(0);
|
||||
break;
|
||||
|
||||
case PI:
|
||||
case STOP_PI:
|
||||
LOGINFO("Raspberry Pi shutdown requested");
|
||||
if (system("init 0") == -1) {
|
||||
LOGERROR("Raspberry Pi shutdown failed: %s", strerror(errno));
|
||||
}
|
||||
break;
|
||||
|
||||
case RESTART_PI:
|
||||
LOGINFO("Raspberry Pi restart requested");
|
||||
if (system("init 6") == -1) {
|
||||
LOGERROR("Raspberry Pi restart failed: %s", strerror(errno));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -349,7 +358,7 @@ void SCSIDEV::MsgOut()
|
|||
void SCSIDEV::Error(ERROR_CODES::sense_key sense_key, ERROR_CODES::asc asc, ERROR_CODES::status status)
|
||||
{
|
||||
// Get bus information
|
||||
((GPIOBUS*)ctrl.bus)->Aquire();
|
||||
ctrl.bus->Aquire();
|
||||
|
||||
// Reset check
|
||||
if (ctrl.bus->GetRST()) {
|
||||
|
@ -395,24 +404,11 @@ void SCSIDEV::Send()
|
|||
ASSERT(!ctrl.bus->GetREQ());
|
||||
ASSERT(ctrl.bus->GetIO());
|
||||
|
||||
//if Length! = 0, send
|
||||
if (ctrl.length != 0) {
|
||||
LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Sending handhake with offset " + to_string(ctrl.offset) + ", length "
|
||||
+ to_string(ctrl.length)).c_str());
|
||||
|
||||
// TODO Get rid of Daynaport specific code in this class
|
||||
// The Daynaport needs to have a delay after the size/flags field
|
||||
// of the read response. In the MacOS driver, it looks like the
|
||||
// driver is doing two "READ" system calls.
|
||||
int len;
|
||||
if (ctrl.unit[0] && ctrl.unit[0]->IsDaynaPort()) {
|
||||
len = ((GPIOBUS*)ctrl.bus)->SendHandShake(
|
||||
&ctrl.buffer[ctrl.offset], ctrl.length, SCSIDaynaPort::DAYNAPORT_READ_HEADER_SZ);
|
||||
}
|
||||
else
|
||||
{
|
||||
len = ctrl.bus->SendHandShake(&ctrl.buffer[ctrl.offset], ctrl.length, BUS::SEND_NO_DELAY);
|
||||
}
|
||||
int len = ctrl.bus->SendHandShake(&ctrl.buffer[ctrl.offset], ctrl.length, ctrl.unit[0]->GetSendDelay());
|
||||
|
||||
// If you cannot send all, move to status phase
|
||||
if (len != (int)ctrl.length) {
|
||||
|
@ -515,13 +511,13 @@ void SCSIDEV::Receive()
|
|||
|
||||
// Length != 0 if received
|
||||
if (ctrl.length != 0) {
|
||||
LOGTRACE("%s length is %d", __PRETTY_FUNCTION__, (int)ctrl.length);
|
||||
LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, (int)ctrl.length);
|
||||
// Receive
|
||||
len = ctrl.bus->ReceiveHandShake(&ctrl.buffer[ctrl.offset], ctrl.length);
|
||||
|
||||
// If not able to receive all, move to status phase
|
||||
if (len != (int)ctrl.length) {
|
||||
LOGERROR("%s Not able to receive %d data, only received %d. Going to error",__PRETTY_FUNCTION__, (int)ctrl.length, len);
|
||||
LOGERROR("%s Not able to receive %d bytes of data, only received %d. Going to error",__PRETTY_FUNCTION__, (int)ctrl.length, len);
|
||||
Error();
|
||||
return;
|
||||
}
|
||||
|
@ -651,7 +647,7 @@ void SCSIDEV::Receive()
|
|||
// Transfer period factor (limited to 50 x 4 = 200ns)
|
||||
scsi.syncperiod = scsi.msb[i + 3];
|
||||
if (scsi.syncperiod > 50) {
|
||||
scsi.syncoffset = 50;
|
||||
scsi.syncperiod = 50;
|
||||
}
|
||||
|
||||
// REQ/ACK offset(limited to 16)
|
||||
|
@ -729,13 +725,13 @@ void SCSIDEV::ReceiveBytes()
|
|||
ASSERT(!ctrl.bus->GetIO());
|
||||
|
||||
if (ctrl.length) {
|
||||
LOGTRACE("%s length is %d", __PRETTY_FUNCTION__, ctrl.length);
|
||||
LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, ctrl.length);
|
||||
|
||||
len = ctrl.bus->ReceiveHandShake(&ctrl.buffer[ctrl.offset], ctrl.length);
|
||||
|
||||
// If not able to receive all, move to status phase
|
||||
if (len != ctrl.length) {
|
||||
LOGERROR("%s Not able to receive %d data, only received %d. Going to error",
|
||||
LOGERROR("%s Not able to receive %d bytes of data, only received %d. Going to error",
|
||||
__PRETTY_FUNCTION__, ctrl.length, len);
|
||||
Error();
|
||||
return;
|
||||
|
@ -897,6 +893,8 @@ bool SCSIDEV::XferOut(bool cont)
|
|||
|
||||
ASSERT(ctrl.phase == BUS::dataout);
|
||||
|
||||
scsi.is_byte_transfer = false;
|
||||
|
||||
PrimaryDevice *device = dynamic_cast<PrimaryDevice *>(ctrl.unit[GetEffectiveLun()]);
|
||||
if (device && ctrl.cmd[0] == scsi_defs::eCmdWrite6) {
|
||||
return device->WriteBytes(ctrl.buffer, scsi.bytes_to_transfer);
|
||||
|
|
|
@ -29,8 +29,9 @@ public:
|
|||
|
||||
enum rascsi_shutdown_mode {
|
||||
NONE,
|
||||
RASCSI,
|
||||
PI
|
||||
STOP_RASCSI,
|
||||
STOP_PI,
|
||||
RESTART_PI
|
||||
};
|
||||
|
||||
// Internal data definition
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
// Powered by XM6 TypeG Technology.
|
||||
// Copyright (C) 2016-2020 GIMONS
|
||||
//
|
||||
// Imported NetBSD support and some optimisation patch by Rin Okuyama.
|
||||
// Imported sava's bugfix patch(in RASDRV DOS edition).
|
||||
//
|
||||
// [ Host File System for the X68000 ]
|
||||
|
@ -30,21 +29,11 @@
|
|||
//---------------------------------------------------------------------------
|
||||
#define IC_BUF_SIZE 1024
|
||||
static char convert_buf[IC_BUF_SIZE];
|
||||
#ifndef __NetBSD__
|
||||
// Using POSIX.1 compliant iconv(3)
|
||||
#define CONVERT(src, dest, inbuf, outbuf, outsize) \
|
||||
convert(src, dest, (char *)inbuf, outbuf, outsize)
|
||||
static void convert(char const *src, char const *dest,
|
||||
char *inbuf, char *outbuf, size_t outsize)
|
||||
#else
|
||||
// Using NetBSD version of iconv(3): The second argument is 'const char **'
|
||||
#define CONVERT(src, dest, inbuf, outbuf, outsize) \
|
||||
convert(src, dest, inbuf, outbuf, outsize)
|
||||
static void convert(char const *src, char const *dest,
|
||||
const char *inbuf, char *outbuf, size_t outsize)
|
||||
#endif
|
||||
{
|
||||
#ifndef __APPLE__
|
||||
*outbuf = '\0';
|
||||
size_t in = strlen(inbuf);
|
||||
size_t out = outsize - 1;
|
||||
|
@ -61,7 +50,6 @@ static void convert(char const *src, char const *dest,
|
|||
|
||||
iconv_close(cd);
|
||||
*outbuf = '\0';
|
||||
#endif //ifndef __APPLE__
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
// Copyright (C) 2016-2020 GIMONS
|
||||
// Copyright (C) akuker
|
||||
//
|
||||
// Imported NetBSD support and some optimisation patches by Rin Okuyama.
|
||||
//
|
||||
// [ TAP Driver ]
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
@ -20,8 +18,6 @@
|
|||
#include <sys/ioctl.h>
|
||||
#include <linux/sockios.h>
|
||||
#endif
|
||||
// TODO Try to get rid of zlib, there is only one operation using it
|
||||
#include <zlib.h> // For crc32()
|
||||
#include "os.h"
|
||||
#include "ctapdriver.h"
|
||||
#include "log.h"
|
||||
|
@ -47,8 +43,6 @@ CTapDriver::CTapDriver()
|
|||
// Initialization
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
#ifdef __linux__
|
||||
|
||||
static bool br_setif(int br_socket_fd, const char* bridgename, const char* ifname, bool add) {
|
||||
struct ifreq ifr;
|
||||
ifr.ifr_ifindex = if_nametoindex(ifname);
|
||||
|
@ -349,53 +343,6 @@ bool CTapDriver::Init(const map<string, string>& const_params)
|
|||
|
||||
return true;
|
||||
}
|
||||
#endif // __linux__
|
||||
|
||||
#ifdef __NetBSD__
|
||||
bool CTapDriver::Init(const map<string, string>&)
|
||||
{
|
||||
struct ifreq ifr;
|
||||
struct ifaddrs *ifa, *a;
|
||||
|
||||
// TAP Device Initialization
|
||||
if ((m_hTAP = open("/dev/tap", O_RDWR)) < 0) {
|
||||
LOGERROR("Can't open tap: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get device name
|
||||
if (ioctl(m_hTAP, TAPGIFNAME, (void *)&ifr) < 0) {
|
||||
LOGERROR("Can't ioctl TAPGIFNAME: %s", strerror(errno));
|
||||
close(m_hTAP);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get MAC address
|
||||
if (getifaddrs(&ifa) == -1) {
|
||||
LOGERROR("Can't getifaddrs: %s", strerror(errno));
|
||||
close(m_hTAP);
|
||||
return false;
|
||||
}
|
||||
for (a = ifa; a != NULL; a = a->ifa_next)
|
||||
if (strcmp(ifr.ifr_name, a->ifa_name) == 0 &&
|
||||
a->ifa_addr->sa_family == AF_LINK)
|
||||
break;
|
||||
if (a == NULL) {
|
||||
LOGERROR("Can't get MAC address: %s", strerror(errno));
|
||||
close(m_hTAP);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save MAC address
|
||||
memcpy(m_MacAddr, LLADDR((struct sockaddr_dl *)a->ifa_addr),
|
||||
sizeof(m_MacAddr));
|
||||
freeifaddrs(ifa);
|
||||
|
||||
LOGINFO("Tap device: %s\n", ifr.ifr_name);
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif // __NetBSD__
|
||||
|
||||
void CTapDriver::OpenDump(const Filepath& path) {
|
||||
if (m_pcap == NULL) {
|
||||
|
@ -498,6 +445,19 @@ bool CTapDriver::PendingPackets()
|
|||
}
|
||||
}
|
||||
|
||||
// See https://stackoverflow.com/questions/21001659/crc32-algorithm-implementation-in-c-without-a-look-up-table-and-with-a-public-li
|
||||
uint32_t crc32(BYTE *buf, int length) {
|
||||
uint32_t crc = 0xffffffff;
|
||||
for (int i = 0; i < length; i++) {
|
||||
crc ^= buf[i];
|
||||
for (int j = 0; j < 8; j++) {
|
||||
uint32_t mask = -(crc & 1);
|
||||
crc = (crc >> 1) ^ (0xEDB88320 & mask);
|
||||
}
|
||||
}
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Receive
|
||||
|
@ -508,7 +468,7 @@ int CTapDriver::Rx(BYTE *buf)
|
|||
ASSERT(m_hTAP != -1);
|
||||
|
||||
// Check if there is data that can be received
|
||||
if(!PendingPackets()){
|
||||
if (!PendingPackets()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -524,11 +484,7 @@ int CTapDriver::Rx(BYTE *buf)
|
|||
// We need to add the Frame Check Status (FCS) CRC back onto the end of the packet.
|
||||
// The Linux network subsystem removes it, since most software apps shouldn't ever
|
||||
// need it.
|
||||
|
||||
// Initialize the CRC
|
||||
DWORD crc = crc32(0L, Z_NULL, 0);
|
||||
// Calculate the CRC
|
||||
crc = crc32(crc, buf, dwReceived);
|
||||
int crc = crc32(buf, dwReceived);
|
||||
|
||||
buf[dwReceived + 0] = (BYTE)((crc >> 0) & 0xFF);
|
||||
buf[dwReceived + 1] = (BYTE)((crc >> 8) & 0xFF);
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
// Copyright (C) 2016-2020 GIMONS
|
||||
// Copyright (C) akuker
|
||||
//
|
||||
// Imported NetBSD support and some optimisation patch by Rin Okuyama.
|
||||
//
|
||||
// [ TAP Driver ]
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
|
|
@ -184,8 +184,4 @@ public:
|
|||
|
||||
bool IsSASIHD() const { return type == "SAHD"; }
|
||||
bool IsSCSIHD() const { return type == "SCHD" || type == "SCRM"; }
|
||||
bool IsCdRom() const { return type == "SCCD"; }
|
||||
bool IsMo() const { return type == "SCMO"; }
|
||||
bool IsBridge() const { return type == "SCBR"; }
|
||||
bool IsDaynaPort() const { return type == "SCDP"; }
|
||||
};
|
||||
|
|
|
@ -31,7 +31,6 @@ DeviceFactory::DeviceFactory()
|
|||
sector_sizes[SCHD] = { 512, 1024, 2048, 4096 };
|
||||
sector_sizes[SCRM] = { 512, 1024, 2048, 4096 };
|
||||
sector_sizes[SCMO] = { 512, 1024, 2048, 4096 };
|
||||
// Some old Sun CD-ROM drives support 512 bytes per sector
|
||||
sector_sizes[SCCD] = { 512, 2048};
|
||||
|
||||
// 128 MB, 512 bytes per sector, 248826 sectors
|
||||
|
@ -124,20 +123,17 @@ Device *DeviceFactory::CreateDevice(PbDeviceType type, const string& filename)
|
|||
try {
|
||||
switch (type) {
|
||||
case SAHD:
|
||||
device = new SASIHD();
|
||||
device = new SASIHD(sector_sizes[SAHD]);
|
||||
device->SetSupportedLuns(2);
|
||||
device->SetProduct("SASI HD");
|
||||
((Disk *)device)->SetSectorSizes(sector_sizes[SAHD]);
|
||||
break;
|
||||
|
||||
case SCHD: {
|
||||
string ext = GetExtension(filename);
|
||||
if (ext == "hdn" || ext == "hdi" || ext == "nhd") {
|
||||
device = new SCSIHD_NEC();
|
||||
((Disk *)device)->SetSectorSizes({ 512 });
|
||||
device = new SCSIHD_NEC({ 512 });
|
||||
} else {
|
||||
device = new SCSIHD(false);
|
||||
((Disk *)device)->SetSectorSizes(sector_sizes[SCHD]);
|
||||
device = new SCSIHD(sector_sizes[SCHD], false);
|
||||
|
||||
// Some Apple tools require a particular drive identification
|
||||
if (ext == "hda") {
|
||||
|
@ -151,34 +147,30 @@ Device *DeviceFactory::CreateDevice(PbDeviceType type, const string& filename)
|
|||
}
|
||||
|
||||
case SCRM:
|
||||
device = new SCSIHD(true);
|
||||
device = new SCSIHD(sector_sizes[SCRM], true);
|
||||
device->SetProtectable(true);
|
||||
device->SetStoppable(true);
|
||||
device->SetRemovable(true);
|
||||
device->SetLockable(true);
|
||||
device->SetProduct("SCSI HD (REM.)");
|
||||
((Disk *)device)->SetSectorSizes(sector_sizes[SCRM]);
|
||||
break;
|
||||
|
||||
case SCMO:
|
||||
device = new SCSIMO();
|
||||
device = new SCSIMO(sector_sizes[SCMO], geometries[SCMO]);
|
||||
device->SetProtectable(true);
|
||||
device->SetStoppable(true);
|
||||
device->SetRemovable(true);
|
||||
device->SetLockable(true);
|
||||
device->SetProduct("SCSI MO");
|
||||
((Disk *)device)->SetSectorSizes(sector_sizes[SCRM]);
|
||||
((Disk *)device)->SetGeometries(geometries[SCMO]);
|
||||
break;
|
||||
|
||||
case SCCD:
|
||||
device = new SCSICD();
|
||||
device = new SCSICD(sector_sizes[SCCD]);
|
||||
device->SetReadOnly(true);
|
||||
device->SetStoppable(true);
|
||||
device->SetRemovable(true);
|
||||
device->SetLockable(true);
|
||||
device->SetProduct("SCSI CD-ROM");
|
||||
((Disk *)device)->SetSectorSizes(sector_sizes[SCCD]);
|
||||
break;
|
||||
|
||||
case SCBR:
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
// Copyright (C) 2010 Y.Sugahara
|
||||
//
|
||||
// Imported sava's Anex86/T98Next image and MO format support patch.
|
||||
// Imported NetBSD support and some optimisation patch by Rin Okuyama.
|
||||
// Comments translated to english by akuker.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
@ -40,8 +39,8 @@ Disk::Disk(const string& id) : ModePageDevice(id), ScsiBlockCommands()
|
|||
dispatcher.AddCommand(eCmdRead6, "Read6", &Disk::Read6);
|
||||
dispatcher.AddCommand(eCmdWrite6, "Write6", &Disk::Write6);
|
||||
dispatcher.AddCommand(eCmdSeek6, "Seek6", &Disk::Seek6);
|
||||
dispatcher.AddCommand(eCmdReserve6, "Reserve6", &Disk::Reserve6);
|
||||
dispatcher.AddCommand(eCmdRelease6, "Release6", &Disk::Release6);
|
||||
dispatcher.AddCommand(eCmdReserve6, "Reserve6", &Disk::Reserve);
|
||||
dispatcher.AddCommand(eCmdRelease6, "Release6", &Disk::Release);
|
||||
dispatcher.AddCommand(eCmdStartStop, "StartStopUnit", &Disk::StartStopUnit);
|
||||
dispatcher.AddCommand(eCmdSendDiag, "SendDiagnostic", &Disk::SendDiagnostic);
|
||||
dispatcher.AddCommand(eCmdRemoval, "PreventAllowMediumRemoval", &Disk::PreventAllowMediumRemoval);
|
||||
|
@ -56,8 +55,8 @@ Disk::Disk(const string& id) : ModePageDevice(id), ScsiBlockCommands()
|
|||
dispatcher.AddCommand(eCmdSynchronizeCache10, "SynchronizeCache10", &Disk::SynchronizeCache10);
|
||||
dispatcher.AddCommand(eCmdSynchronizeCache16, "SynchronizeCache16", &Disk::SynchronizeCache16);
|
||||
dispatcher.AddCommand(eCmdReadDefectData10, "ReadDefectData10", &Disk::ReadDefectData10);
|
||||
dispatcher.AddCommand(eCmdReserve10, "Reserve10", &Disk::Reserve10);
|
||||
dispatcher.AddCommand(eCmdRelease10, "Release10", &Disk::Release10);
|
||||
dispatcher.AddCommand(eCmdReserve10, "Reserve10", &Disk::Reserve);
|
||||
dispatcher.AddCommand(eCmdRelease10, "Release10", &Disk::Release);
|
||||
dispatcher.AddCommand(eCmdRead16, "Read16", &Disk::Read16);
|
||||
dispatcher.AddCommand(eCmdWrite16, "Write16", &Disk::Write16);
|
||||
dispatcher.AddCommand(eCmdVerify16, "Verify16", &Disk::Verify16);
|
||||
|
@ -103,7 +102,7 @@ bool Disk::Dispatch(SCSIDEV *controller)
|
|||
//---------------------------------------------------------------------------
|
||||
void Disk::Open(const Filepath& path)
|
||||
{
|
||||
ASSERT(disk.blocks > 0);
|
||||
assert(disk.blocks > 0);
|
||||
|
||||
SetReady(true);
|
||||
|
||||
|
@ -185,8 +184,6 @@ void Disk::Read6(SASIDEV *controller)
|
|||
{
|
||||
uint64_t start;
|
||||
if (GetStartAndCount(controller, start, ctrl->blocks, RW6)) {
|
||||
LOGDEBUG("%s READ(6) command record=$%08X blocks=%d", __PRETTY_FUNCTION__, (uint32_t)start, ctrl->blocks);
|
||||
|
||||
Read(controller, start);
|
||||
}
|
||||
}
|
||||
|
@ -195,8 +192,6 @@ void Disk::Read10(SASIDEV *controller)
|
|||
{
|
||||
uint64_t start;
|
||||
if (GetStartAndCount(controller, start, ctrl->blocks, RW10)) {
|
||||
LOGDEBUG("%s READ(10) command record=$%08X blocks=%d", __PRETTY_FUNCTION__, (uint32_t)start, ctrl->blocks);
|
||||
|
||||
Read(controller, start);
|
||||
}
|
||||
}
|
||||
|
@ -205,8 +200,6 @@ void Disk::Read16(SASIDEV *controller)
|
|||
{
|
||||
uint64_t start;
|
||||
if (GetStartAndCount(controller, start, ctrl->blocks, RW16)) {
|
||||
LOGDEBUG("%s READ(16) command record=$%08X blocks=%d", __PRETTY_FUNCTION__, (uint32_t)start, ctrl->blocks);
|
||||
|
||||
Read(controller, start);
|
||||
}
|
||||
}
|
||||
|
@ -269,8 +262,6 @@ void Disk::Write6(SASIDEV *controller)
|
|||
{
|
||||
uint64_t start;
|
||||
if (GetStartAndCount(controller, start, ctrl->blocks, RW6)) {
|
||||
LOGDEBUG("%s WRITE(6) command record=$%08X blocks=%d", __PRETTY_FUNCTION__, (uint32_t)start, ctrl->blocks);
|
||||
|
||||
Write(controller, start);
|
||||
}
|
||||
}
|
||||
|
@ -279,8 +270,6 @@ void Disk::Write10(SASIDEV *controller)
|
|||
{
|
||||
uint64_t start;
|
||||
if (GetStartAndCount(controller, start, ctrl->blocks, RW10)) {
|
||||
LOGDEBUG("%s WRITE(10) command record=$%08X blocks=%d",__PRETTY_FUNCTION__, (uint32_t)start, ctrl->blocks);
|
||||
|
||||
Write(controller, start);
|
||||
}
|
||||
}
|
||||
|
@ -289,8 +278,6 @@ void Disk::Write16(SASIDEV *controller)
|
|||
{
|
||||
uint64_t start;
|
||||
if (GetStartAndCount(controller, start, ctrl->blocks, RW16)) {
|
||||
LOGDEBUG("%s WRITE(16) command record=$%08X blocks=%d",__PRETTY_FUNCTION__, (uint32_t)start, ctrl->blocks);
|
||||
|
||||
Write(controller, start);
|
||||
}
|
||||
}
|
||||
|
@ -331,8 +318,6 @@ void Disk::Verify10(SASIDEV *controller)
|
|||
// Get record number and block number
|
||||
uint64_t record;
|
||||
if (GetStartAndCount(controller, record, ctrl->blocks, RW10)) {
|
||||
LOGDEBUG("%s VERIFY(10) command record=$%08X blocks=%d",__PRETTY_FUNCTION__, (uint32_t)record, ctrl->blocks);
|
||||
|
||||
Verify(controller, record);
|
||||
}
|
||||
}
|
||||
|
@ -342,8 +327,6 @@ void Disk::Verify16(SASIDEV *controller)
|
|||
// Get record number and block number
|
||||
uint64_t record;
|
||||
if (GetStartAndCount(controller, record, ctrl->blocks, RW16)) {
|
||||
LOGDEBUG("%s VERIFY(16) command record=$%08X blocks=%d",__PRETTY_FUNCTION__, (uint32_t)record, ctrl->blocks);
|
||||
|
||||
Verify(controller, record);
|
||||
}
|
||||
}
|
||||
|
@ -398,7 +381,7 @@ void Disk::SynchronizeCache16(SASIDEV *controller)
|
|||
|
||||
void Disk::ReadDefectData10(SASIDEV *controller)
|
||||
{
|
||||
ctrl->length = ReadDefectData10(ctrl->cmd, ctrl->buffer);
|
||||
ctrl->length = ReadDefectData10(ctrl->cmd, ctrl->buffer, ctrl->bufsize);
|
||||
if (ctrl->length <= 4) {
|
||||
controller->Error();
|
||||
return;
|
||||
|
@ -440,29 +423,9 @@ int Disk::ModeSense6(const DWORD *cdb, BYTE *buf)
|
|||
int length = (int)cdb[4];
|
||||
memset(buf, 0, length);
|
||||
|
||||
// Get changeable flag
|
||||
bool change = (cdb[2] & 0xc0) == 0x40;
|
||||
|
||||
// Get page code (0x00 is valid from the beginning)
|
||||
int page = cdb[2] & 0x3f;
|
||||
bool valid = page == 0x00;
|
||||
|
||||
LOGTRACE("%s Requesting mode page $%02X", __PRETTY_FUNCTION__, page);
|
||||
|
||||
// Basic information
|
||||
int size = 4;
|
||||
|
||||
// MEDIUM TYPE
|
||||
if (IsMo()) {
|
||||
// Optical reversible or erasable
|
||||
buf[1] = 0x03;
|
||||
}
|
||||
|
||||
// DEVICE SPECIFIC PARAMETER
|
||||
if (IsProtected()) {
|
||||
buf[2] = 0x80;
|
||||
}
|
||||
|
||||
// Add block descriptor if DBD is 0
|
||||
if ((cdb[1] & 0x08) == 0) {
|
||||
// Mode parameter header, block descriptor length
|
||||
|
@ -488,70 +451,14 @@ int Disk::ModeSense6(const DWORD *cdb, BYTE *buf)
|
|||
size = 12;
|
||||
}
|
||||
|
||||
// Page code 1 (read-write error recovery)
|
||||
if (page == 0x01 || page == 0x3f) {
|
||||
size += AddErrorPage(change, &buf[size]);
|
||||
valid = true;
|
||||
}
|
||||
|
||||
// Page code 3 (format device)
|
||||
if (page == 0x03 || page == 0x3f) {
|
||||
size += AddFormatPage(change, &buf[size]);
|
||||
valid = true;
|
||||
}
|
||||
|
||||
// Page code 4 (drive parameter)
|
||||
if (page == 0x04 || page == 0x3f) {
|
||||
size += AddDrivePage(change, &buf[size]);
|
||||
valid = true;
|
||||
}
|
||||
|
||||
// Page code 6 (optical)
|
||||
if (IsMo()) {
|
||||
if (page == 0x06 || page == 0x3f) {
|
||||
size += AddOptionPage(change, &buf[size]);
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Page code 8 (caching)
|
||||
if (page == 0x08 || page == 0x3f) {
|
||||
size += AddCachePage(change, &buf[size]);
|
||||
valid = true;
|
||||
}
|
||||
|
||||
// Page code 13 (CD-ROM)
|
||||
if (IsCdRom()) {
|
||||
if (page == 0x0d || page == 0x3f) {
|
||||
size += AddCDROMPage(change, &buf[size]);
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Page code 14 (CD-DA)
|
||||
if (IsCdRom()) {
|
||||
if (page == 0x0e || page == 0x3f) {
|
||||
size += AddCDDAPage(change, &buf[size]);
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Page (vendor special)
|
||||
int ret = AddVendorPage(page, change, &buf[size]);
|
||||
if (ret > 0) {
|
||||
size += ret;
|
||||
valid = true;
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
LOGTRACE("%s Unsupported mode page $%02X", __PRETTY_FUNCTION__, page);
|
||||
SetStatusCode(STATUS_INVALIDCDB);
|
||||
int pages_size = super::AddModePages(cdb, &buf[size], length - size);
|
||||
if (!pages_size) {
|
||||
return 0;
|
||||
}
|
||||
size += pages_size;
|
||||
|
||||
// Do not return more than ALLOCATION LENGTH bytes
|
||||
if (size > length) {
|
||||
LOGTRACE("%s %d bytes available, %d bytes requested", __PRETTY_FUNCTION__, size, length);
|
||||
size = length;
|
||||
}
|
||||
|
||||
|
@ -561,40 +468,18 @@ int Disk::ModeSense6(const DWORD *cdb, BYTE *buf)
|
|||
return size;
|
||||
}
|
||||
|
||||
int Disk::ModeSense10(const DWORD *cdb, BYTE *buf)
|
||||
int Disk::ModeSense10(const DWORD *cdb, BYTE *buf, int max_length)
|
||||
{
|
||||
// Get length, clear buffer
|
||||
int length = cdb[7];
|
||||
length <<= 8;
|
||||
length |= cdb[8];
|
||||
if (length > 0x800) {
|
||||
length = 0x800;
|
||||
int length = (cdb[7] << 8) | cdb[8];
|
||||
if (length > max_length) {
|
||||
length = max_length;
|
||||
}
|
||||
memset(buf, 0, length);
|
||||
|
||||
// Get changeable flag
|
||||
bool change = (cdb[2] & 0xc0) == 0x40;
|
||||
|
||||
// Get page code (0x00 is valid from the beginning)
|
||||
int page = cdb[2] & 0x3f;
|
||||
bool valid = page == 0x00;
|
||||
|
||||
LOGTRACE("%s Requesting mode page $%02X", __PRETTY_FUNCTION__, page);
|
||||
|
||||
// Basic Information
|
||||
int size = 8;
|
||||
|
||||
// MEDIUM TYPE
|
||||
if (IsMo()) {
|
||||
// Optical reversible or erasable
|
||||
buf[2] = 0x03;
|
||||
}
|
||||
|
||||
// DEVICE SPECIFIC PARAMETER
|
||||
if (IsProtected()) {
|
||||
buf[3] = 0x80;
|
||||
}
|
||||
|
||||
// Add block descriptor if DBD is 0
|
||||
if ((cdb[1] & 0x08) == 0) {
|
||||
// Only if ready
|
||||
|
@ -648,70 +533,14 @@ int Disk::ModeSense10(const DWORD *cdb, BYTE *buf)
|
|||
}
|
||||
}
|
||||
|
||||
// Page code 1 (read-write error recovery)
|
||||
if (page == 0x01 || page == 0x3f) {
|
||||
size += AddErrorPage(change, &buf[size]);
|
||||
valid = true;
|
||||
}
|
||||
|
||||
// Page code 3 (format device)
|
||||
if (page == 0x03 || page == 0x3f) {
|
||||
size += AddFormatPage(change, &buf[size]);
|
||||
valid = true;
|
||||
}
|
||||
|
||||
// Page code 4 (drive parameter)
|
||||
if (page == 0x04 || page == 0x3f) {
|
||||
size += AddDrivePage(change, &buf[size]);
|
||||
valid = true;
|
||||
}
|
||||
|
||||
// Page code 6 (optical)
|
||||
if (IsMo()) {
|
||||
if (page == 0x06 || page == 0x3f) {
|
||||
size += AddOptionPage(change, &buf[size]);
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Page code 8 (caching)
|
||||
if (page == 0x08 || page == 0x3f) {
|
||||
size += AddCachePage(change, &buf[size]);
|
||||
valid = true;
|
||||
}
|
||||
|
||||
// Page code 13 (CD-ROM)
|
||||
if (IsCdRom()) {
|
||||
if (page == 0x0d || page == 0x3f) {
|
||||
size += AddCDROMPage(change, &buf[size]);
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Page code 14 (CD-DA)
|
||||
if (IsCdRom()) {
|
||||
if (page == 0x0e || page == 0x3f) {
|
||||
size += AddCDDAPage(change, &buf[size]);
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Page (vendor special)
|
||||
int ret = AddVendorPage(page, change, &buf[size]);
|
||||
if (ret > 0) {
|
||||
size += ret;
|
||||
valid = true;
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
LOGTRACE("%s Unsupported mode page $%02X", __PRETTY_FUNCTION__, page);
|
||||
SetStatusCode(STATUS_INVALIDCDB);
|
||||
int pages_size = super::AddModePages(cdb, &buf[size], length - size);
|
||||
if (!pages_size) {
|
||||
return 0;
|
||||
}
|
||||
size += pages_size;
|
||||
|
||||
// Do not return more than ALLOCATION LENGTH bytes
|
||||
if (size > length) {
|
||||
LOGTRACE("%s %d bytes available, %d bytes requested", __PRETTY_FUNCTION__, size, length);
|
||||
size = length;
|
||||
}
|
||||
|
||||
|
@ -722,28 +551,65 @@ int Disk::ModeSense10(const DWORD *cdb, BYTE *buf)
|
|||
return size;
|
||||
}
|
||||
|
||||
int Disk::AddErrorPage(bool change, BYTE *buf)
|
||||
void Disk::SetDeviceParameters(BYTE *buf)
|
||||
{
|
||||
// Set the message length
|
||||
buf[0] = 0x01;
|
||||
buf[1] = 0x0a;
|
||||
|
||||
// Retry count is 0, limit time uses internal default value
|
||||
return 12;
|
||||
// DEVICE SPECIFIC PARAMETER
|
||||
if (IsProtected()) {
|
||||
buf[3] = 0x80;
|
||||
}
|
||||
}
|
||||
|
||||
int Disk::AddFormatPage(bool change, BYTE *buf)
|
||||
void Disk::AddModePages(map<int, vector<BYTE>>& pages, int page, bool changeable) const
|
||||
{
|
||||
// Set the message length
|
||||
buf[0] = 0x80 | 0x03;
|
||||
buf[1] = 0x16;
|
||||
// Page code 1 (read-write error recovery)
|
||||
if (page == 0x01 || page == 0x3f) {
|
||||
AddErrorPage(pages, changeable);
|
||||
}
|
||||
|
||||
// Page code 3 (format device)
|
||||
if (page == 0x03 || page == 0x3f) {
|
||||
AddFormatPage(pages, changeable);
|
||||
}
|
||||
|
||||
// Page code 4 (drive parameter)
|
||||
if (page == 0x04 || page == 0x3f) {
|
||||
AddDrivePage(pages, changeable);
|
||||
}
|
||||
|
||||
// Page code 8 (caching)
|
||||
if (page == 0x08 || page == 0x3f) {
|
||||
AddCachePage(pages, changeable);
|
||||
}
|
||||
|
||||
// Page (vendor special)
|
||||
AddVendorPage(pages, page, changeable);
|
||||
}
|
||||
|
||||
void Disk::AddErrorPage(map<int, vector<BYTE>>& pages, bool) const
|
||||
{
|
||||
vector<BYTE> buf(12);
|
||||
|
||||
// Retry count is 0, limit time uses internal default value
|
||||
|
||||
pages[1] = buf;
|
||||
}
|
||||
|
||||
void Disk::AddFormatPage(map<int, vector<BYTE>>& pages, bool changeable) const
|
||||
{
|
||||
vector<BYTE> buf(24);
|
||||
|
||||
// Page can be saved
|
||||
buf[0] = 0x80;
|
||||
|
||||
// Show the number of bytes in the physical sector as changeable
|
||||
// (though it cannot be changed in practice)
|
||||
if (change) {
|
||||
if (changeable) {
|
||||
buf[0xc] = 0xff;
|
||||
buf[0xd] = 0xff;
|
||||
return 24;
|
||||
|
||||
pages[3] = buf;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsReady()) {
|
||||
|
@ -765,18 +631,18 @@ int Disk::AddFormatPage(bool change, BYTE *buf)
|
|||
buf[20] = 0x20;
|
||||
}
|
||||
|
||||
return 24;
|
||||
pages[3] = buf;
|
||||
}
|
||||
|
||||
int Disk::AddDrivePage(bool change, BYTE *buf)
|
||||
void Disk::AddDrivePage(map<int, vector<BYTE>>& pages, bool changeable) const
|
||||
{
|
||||
// Set the message length
|
||||
buf[0] = 0x04;
|
||||
buf[1] = 0x16;
|
||||
vector<BYTE> buf(24);
|
||||
|
||||
// No changeable area
|
||||
if (change) {
|
||||
return 24;
|
||||
if (changeable) {
|
||||
pages[4] = buf;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsReady()) {
|
||||
|
@ -793,81 +659,30 @@ int Disk::AddDrivePage(bool change, BYTE *buf)
|
|||
buf[0x5] = 0x8;
|
||||
}
|
||||
|
||||
return 24;
|
||||
pages[4] = buf;
|
||||
}
|
||||
|
||||
int Disk::AddOptionPage(bool change, BYTE *buf)
|
||||
void Disk::AddCachePage(map<int, vector<BYTE>>& pages, bool) const
|
||||
{
|
||||
// Set the message length
|
||||
buf[0] = 0x06;
|
||||
buf[1] = 0x02;
|
||||
|
||||
// Do not report update blocks
|
||||
return 4;
|
||||
}
|
||||
|
||||
int Disk::AddCachePage(bool change, BYTE *buf)
|
||||
{
|
||||
// Set the message length
|
||||
buf[0] = 0x08;
|
||||
buf[1] = 0x0a;
|
||||
vector<BYTE> buf(12);
|
||||
|
||||
// Only read cache is valid, no prefetch
|
||||
return 12;
|
||||
|
||||
pages[8] = buf;
|
||||
}
|
||||
|
||||
int Disk::AddCDROMPage(bool change, BYTE *buf)
|
||||
void Disk::AddVendorPage(map<int, vector<BYTE>>&, int, bool) const
|
||||
{
|
||||
// Set the message length
|
||||
buf[0] = 0x0d;
|
||||
buf[1] = 0x06;
|
||||
|
||||
// No changeable area
|
||||
if (change) {
|
||||
return 8;
|
||||
}
|
||||
|
||||
// 2 seconds for inactive timer
|
||||
buf[3] = 0x05;
|
||||
|
||||
// MSF multiples are 60 and 75 respectively
|
||||
buf[5] = 60;
|
||||
buf[7] = 75;
|
||||
|
||||
return 8;
|
||||
// Nothing to add by default
|
||||
}
|
||||
|
||||
int Disk::AddCDDAPage(bool change, BYTE *buf)
|
||||
int Disk::ReadDefectData10(const DWORD *cdb, BYTE *buf, int max_length)
|
||||
{
|
||||
// Set the message length
|
||||
buf[0] = 0x0e;
|
||||
buf[1] = 0x0e;
|
||||
|
||||
// Audio waits for operation completion and allows
|
||||
// PLAY across multiple tracks
|
||||
return 16;
|
||||
}
|
||||
|
||||
int Disk::AddVendorPage(int /*page*/, bool /*change*/, BYTE *buf)
|
||||
{
|
||||
ASSERT(buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Disk::ReadDefectData10(const DWORD *cdb, BYTE *buf)
|
||||
{
|
||||
ASSERT(cdb);
|
||||
ASSERT(buf);
|
||||
|
||||
// Get length, clear buffer
|
||||
DWORD length = cdb[7];
|
||||
length <<= 8;
|
||||
length |= cdb[8];
|
||||
if (length > 0x800) {
|
||||
length = 0x800;
|
||||
int length = (cdb[7] << 8) | cdb[8];
|
||||
if (length > max_length) {
|
||||
length = max_length;
|
||||
}
|
||||
ASSERT((length >= 0) && (length < 0x800));
|
||||
memset(buf, 0, length);
|
||||
|
||||
// P/G/FORMAT
|
||||
|
@ -911,17 +726,10 @@ bool Disk::Format(const DWORD *cdb)
|
|||
return true;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// READ
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
// TODO Read more than one block in a single call. Currently blocked by the SASI code (missing early range check)
|
||||
// and the track-oriented cache.
|
||||
int Disk::Read(const DWORD *cdb, BYTE *buf, uint64_t block)
|
||||
{
|
||||
ASSERT(buf);
|
||||
|
||||
LOGTRACE("%s", __PRETTY_FUNCTION__);
|
||||
|
||||
if (!CheckReady()) {
|
||||
|
@ -941,14 +749,9 @@ int Disk::Read(const DWORD *cdb, BYTE *buf, uint64_t block)
|
|||
}
|
||||
|
||||
// Success
|
||||
return (1 << disk.size);
|
||||
return 1 << disk.size;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// WRITE check
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
int Disk::WriteCheck(DWORD block)
|
||||
{
|
||||
// Status check
|
||||
|
@ -970,20 +773,13 @@ int Disk::WriteCheck(DWORD block)
|
|||
}
|
||||
|
||||
// Success
|
||||
return (1 << disk.size);
|
||||
return 1 << disk.size;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// WRITE
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
// TODO Write more than one block in a single call. Currently blocked by the SASI code (missing early range check)
|
||||
// and the track-oriented cache.
|
||||
bool Disk::Write(const DWORD *cdb, const BYTE *buf, DWORD block)
|
||||
{
|
||||
ASSERT(buf);
|
||||
|
||||
LOGTRACE("%s", __PRETTY_FUNCTION__);
|
||||
|
||||
// Error if not ready
|
||||
|
@ -1013,6 +809,7 @@ bool Disk::Write(const DWORD *cdb, const BYTE *buf, DWORD block)
|
|||
return true;
|
||||
}
|
||||
|
||||
// TODO For SCSI the specification mandates that the block address is verified
|
||||
void Disk::Seek(SASIDEV *controller)
|
||||
{
|
||||
if (!CheckReady()) {
|
||||
|
@ -1091,23 +888,13 @@ bool Disk::SendDiag(const DWORD *cdb)
|
|||
|
||||
void Disk::ReadCapacity10(SASIDEV *controller)
|
||||
{
|
||||
if (!CheckReady() || disk.blocks <= 0) {
|
||||
controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::MEDIUM_NOT_PRESENT);
|
||||
return;
|
||||
}
|
||||
|
||||
BYTE *buf = ctrl->buffer;
|
||||
|
||||
memset(buf, 0, 8);
|
||||
|
||||
if (!CheckReady()) {
|
||||
controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::MEDIUM_NOT_PRESENT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (disk.blocks <= 0) {
|
||||
controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::MEDIUM_NOT_PRESENT);
|
||||
|
||||
LOGWARN("%s Capacity not available, medium may not be present", __PRETTY_FUNCTION__);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Create end of logical block address (disk.blocks-1)
|
||||
uint32_t blocks = disk.blocks - 1;
|
||||
buf[0] = (BYTE)(blocks >> 24);
|
||||
|
@ -1130,15 +917,13 @@ void Disk::ReadCapacity10(SASIDEV *controller)
|
|||
|
||||
void Disk::ReadCapacity16(SASIDEV *controller)
|
||||
{
|
||||
BYTE *buf = ctrl->buffer;
|
||||
|
||||
memset(buf, 0, 14);
|
||||
|
||||
if (!CheckReady() || disk.blocks <= 0) {
|
||||
controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::MEDIUM_NOT_PRESENT);
|
||||
return;
|
||||
}
|
||||
|
||||
BYTE *buf = ctrl->buffer;
|
||||
|
||||
// Create end of logical block address (disk.blocks-1)
|
||||
uint64_t blocks = disk.blocks - 1;
|
||||
buf[0] = (BYTE)(blocks >> 56);
|
||||
|
@ -1157,6 +942,8 @@ void Disk::ReadCapacity16(SASIDEV *controller)
|
|||
buf[10] = (BYTE)(length >> 8);
|
||||
buf[11] = (BYTE)length;
|
||||
|
||||
buf[12] = 0;
|
||||
|
||||
// Logical blocks per physical block: not reported (1 or more)
|
||||
buf[13] = 0;
|
||||
|
||||
|
@ -1186,7 +973,7 @@ void Disk::ReadCapacity16_ReadLong16(SASIDEV *controller)
|
|||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// RESERVE(6)
|
||||
// RESERVE/RELEASE(6/10)
|
||||
//
|
||||
// The reserve/release commands are only used in multi-initiator
|
||||
// environments. RaSCSI doesn't support this use case. However, some old
|
||||
|
@ -1194,52 +981,12 @@ void Disk::ReadCapacity16_ReadLong16(SASIDEV *controller)
|
|||
// just respond with an OK status.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void Disk::Reserve6(SASIDEV *controller)
|
||||
void Disk::Reserve(SASIDEV *controller)
|
||||
{
|
||||
controller->Status();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// RESERVE(10)
|
||||
//
|
||||
// The reserve/release commands are only used in multi-initiator
|
||||
// environments. RaSCSI doesn't support this use case. However, some old
|
||||
// versions of Solaris will issue the reserve/release commands. We will
|
||||
// just respond with an OK status.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void Disk::Reserve10(SASIDEV *controller)
|
||||
{
|
||||
controller->Status();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// RELEASE(6)
|
||||
//
|
||||
// The reserve/release commands are only used in multi-initiator
|
||||
// environments. RaSCSI doesn't support this use case. However, some old
|
||||
// versions of Solaris will issue the reserve/release commands. We will
|
||||
// just respond with an OK status.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void Disk::Release6(SASIDEV *controller)
|
||||
{
|
||||
controller->Status();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// RELEASE(10)
|
||||
//
|
||||
// The reserve/release commands are only used in multi-initiator
|
||||
// environments. RaSCSI doesn't support this use case. However, some old
|
||||
// versions of Solaris will issue the reserve/release commands. We will
|
||||
// just respond with an OK status.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void Disk::Release10(SASIDEV *controller)
|
||||
void Disk::Release(SASIDEV *controller)
|
||||
{
|
||||
controller->Status();
|
||||
}
|
||||
|
@ -1332,6 +1079,8 @@ bool Disk::GetStartAndCount(SASIDEV *controller, uint64_t& start, uint32_t& coun
|
|||
}
|
||||
}
|
||||
|
||||
LOGDEBUG("%s READ/WRITE/VERIFY command record=$%08X blocks=%d", __PRETTY_FUNCTION__, (uint32_t)start, count);
|
||||
|
||||
// Check capacity
|
||||
uint64_t capacity = GetBlockCount();
|
||||
if (start > capacity || start + count > capacity) {
|
||||
|
|
|
@ -96,10 +96,8 @@ private:
|
|||
void Seek10(SASIDEV *);
|
||||
void ReadCapacity10(SASIDEV *) override;
|
||||
void ReadCapacity16(SASIDEV *) override;
|
||||
void Reserve6(SASIDEV *);
|
||||
void Reserve10(SASIDEV *);
|
||||
void Release6(SASIDEV *);
|
||||
void Release10(SASIDEV *);
|
||||
void Reserve(SASIDEV *);
|
||||
void Release(SASIDEV *);
|
||||
|
||||
public:
|
||||
|
||||
|
@ -117,7 +115,7 @@ public:
|
|||
bool SendDiag(const DWORD *cdb); // SEND DIAGNOSTIC command
|
||||
|
||||
virtual int Read(const DWORD *cdb, BYTE *buf, uint64_t block);
|
||||
int ReadDefectData10(const DWORD *cdb, BYTE *buf);
|
||||
int ReadDefectData10(const DWORD *, BYTE *, int);
|
||||
|
||||
uint32_t GetSectorSizeInBytes() const;
|
||||
void SetSectorSizeInBytes(uint32_t, bool);
|
||||
|
@ -138,15 +136,14 @@ public:
|
|||
|
||||
protected:
|
||||
int ModeSense6(const DWORD *cdb, BYTE *buf);
|
||||
int ModeSense10(const DWORD *cdb, BYTE *buf);
|
||||
virtual int AddErrorPage(bool change, BYTE *buf);
|
||||
virtual int AddFormatPage(bool change, BYTE *buf);
|
||||
virtual int AddDrivePage(bool change, BYTE *buf);
|
||||
virtual int AddVendorPage(int page, bool change, BYTE *buf);
|
||||
int AddOptionPage(bool change, BYTE *buf);
|
||||
int AddCachePage(bool change, BYTE *buf);
|
||||
int AddCDROMPage(bool change, BYTE *buf);
|
||||
int AddCDDAPage(bool, BYTE *buf);
|
||||
int ModeSense10(const DWORD *cdb, BYTE *buf, int);
|
||||
virtual void SetDeviceParameters(BYTE *);
|
||||
void AddModePages(map<int, vector<BYTE>>&, int, bool) const override;
|
||||
virtual void AddErrorPage(map<int, vector<BYTE>>&, bool) const;
|
||||
virtual void AddFormatPage(map<int, vector<BYTE>>&, bool) const;
|
||||
virtual void AddDrivePage(map<int, vector<BYTE>>&, bool) const;
|
||||
void AddCachePage(map<int, vector<BYTE>>&, bool) const;
|
||||
virtual void AddVendorPage(map<int, vector<BYTE>>&, int, bool) const;
|
||||
|
||||
// Internal disk data
|
||||
disk_t disk;
|
||||
|
|
|
@ -27,9 +27,10 @@
|
|||
// uint8_t second; // 0-59
|
||||
// } mode_page_datetime;
|
||||
//
|
||||
// 2. STOP UNIT shuts down RaSCSI or the Raspberry Pi
|
||||
// 2. START/STOP UNIT shuts down RaSCSI or shuts down/reboots the Raspberry Pi
|
||||
// a) !start && !load (STOP): Shut down RaSCSI
|
||||
// b) !start && load (EJECT): Shut down the Raspberry Pi
|
||||
// c) start && load (LOAD): Reboot the Raspberry Pi
|
||||
//
|
||||
|
||||
#include "controllers/scsidev_ctrl.h"
|
||||
|
@ -77,122 +78,115 @@ void HostServices::StartStopUnit(SCSIDEV *controller)
|
|||
}
|
||||
|
||||
if (load) {
|
||||
controller->ScheduleShutDown(SCSIDEV::rascsi_shutdown_mode::PI);
|
||||
controller->ScheduleShutDown(SCSIDEV::rascsi_shutdown_mode::STOP_PI);
|
||||
}
|
||||
else {
|
||||
controller->ScheduleShutDown(SCSIDEV::rascsi_shutdown_mode::RASCSI);
|
||||
controller->ScheduleShutDown(SCSIDEV::rascsi_shutdown_mode::STOP_RASCSI);
|
||||
}
|
||||
|
||||
controller->Status();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (load) {
|
||||
controller->ScheduleShutDown(SCSIDEV::rascsi_shutdown_mode::RESTART_PI);
|
||||
|
||||
controller->Status();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB);
|
||||
}
|
||||
|
||||
int HostServices::ModeSense6(const DWORD *cdb, BYTE *buf)
|
||||
{
|
||||
// Get length, clear buffer
|
||||
// Block descriptors cannot be returned
|
||||
if (!(cdb[1] & 0x08)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int length = (int)cdb[4];
|
||||
memset(buf, 0, length);
|
||||
|
||||
// Get page code (0x00 is valid from the beginning)
|
||||
int page = cdb[2] & 0x3f;
|
||||
bool valid = page == 0x00;
|
||||
|
||||
LOGTRACE("%s Requesting mode page $%02X", __PRETTY_FUNCTION__, page);
|
||||
|
||||
// Basic information
|
||||
int size = 4;
|
||||
|
||||
int ret = AddRealtimeClockPage(page, &buf[size]);
|
||||
if (ret > 0) {
|
||||
size += ret;
|
||||
valid = true;
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
LOGTRACE("%s Unsupported mode page $%02X", __PRETTY_FUNCTION__, page);
|
||||
SetStatusCode(STATUS_INVALIDCDB);
|
||||
int pages_size = super::AddModePages(cdb, &buf[size], length - size);
|
||||
if (!pages_size) {
|
||||
return 0;
|
||||
}
|
||||
size += pages_size;
|
||||
|
||||
// Do not return more than ALLOCATION LENGTH bytes
|
||||
if (size > length) {
|
||||
LOGTRACE("%s %d bytes available, %d bytes requested", __PRETTY_FUNCTION__, size, length);
|
||||
size = length;
|
||||
}
|
||||
|
||||
// Final setting of mode data length
|
||||
buf[0] = size;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
int HostServices::ModeSense10(const DWORD *cdb, BYTE *buf)
|
||||
int HostServices::ModeSense10(const DWORD *cdb, BYTE *buf, int max_length)
|
||||
{
|
||||
// Get length, clear buffer
|
||||
int length = cdb[7];
|
||||
length <<= 8;
|
||||
length |= cdb[8];
|
||||
if (length > 0x800) {
|
||||
length = 0x800;
|
||||
}
|
||||
memset(buf, 0, length);
|
||||
|
||||
// Get page code (0x00 is valid from the beginning)
|
||||
int page = cdb[2] & 0x3f;
|
||||
bool valid = page == 0x00;
|
||||
|
||||
LOGTRACE("%s Requesting mode page $%02X", __PRETTY_FUNCTION__, page);
|
||||
|
||||
// Basic Information
|
||||
int size = 8;
|
||||
|
||||
int ret = AddRealtimeClockPage(page, &buf[size]);
|
||||
if (ret > 0) {
|
||||
size += ret;
|
||||
valid = true;
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
LOGTRACE("%s Unsupported mode page $%02X", __PRETTY_FUNCTION__, page);
|
||||
SetStatusCode(STATUS_INVALIDCDB);
|
||||
// Block descriptors cannot be returned
|
||||
if (!(cdb[1] & 0x08)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int length = (cdb[7] << 8) | cdb[8];
|
||||
if (length > max_length) {
|
||||
length = max_length;
|
||||
}
|
||||
memset(buf, 0, length);
|
||||
|
||||
// Basic information
|
||||
int size = 8;
|
||||
|
||||
int pages_size = super::AddModePages(cdb, &buf[size], length - size);
|
||||
if (!pages_size) {
|
||||
return 0;
|
||||
}
|
||||
size += pages_size;
|
||||
|
||||
// Do not return more than ALLOCATION LENGTH bytes
|
||||
if (size > length) {
|
||||
LOGTRACE("%s %d bytes available, %d bytes requested", __PRETTY_FUNCTION__, size, length);
|
||||
size = length;
|
||||
}
|
||||
|
||||
// Final setting of mode data length
|
||||
buf[0] = size >> 8;
|
||||
buf[1] = size;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
int HostServices::AddRealtimeClockPage(int page, BYTE *buf)
|
||||
void HostServices::AddModePages(map<int, vector<BYTE>>& pages, int page, bool changeable) const
|
||||
{
|
||||
if (page == 0x20) {
|
||||
if (page == 0x20 || page == 0x3f) {
|
||||
AddRealtimeClockPage(pages, changeable);
|
||||
}
|
||||
}
|
||||
|
||||
void HostServices::AddRealtimeClockPage(map<int, vector<BYTE>>& pages, bool changeable) const
|
||||
{
|
||||
vector<BYTE> buf(10);
|
||||
|
||||
if (!changeable) {
|
||||
// Data structure version 1.0
|
||||
buf[0] = 0x01;
|
||||
buf[1] = 0x00;
|
||||
buf[2] = 0x01;
|
||||
buf[3] = 0x00;
|
||||
|
||||
std::time_t t = std::time(NULL);
|
||||
std::tm tm = *std::localtime(&t);
|
||||
buf[2] = tm.tm_year;
|
||||
buf[3] = tm.tm_mon;
|
||||
buf[4] = tm.tm_mday;
|
||||
buf[5] = tm.tm_hour;
|
||||
buf[6] = tm.tm_min;
|
||||
buf[4] = tm.tm_year;
|
||||
buf[5] = tm.tm_mon;
|
||||
buf[6] = tm.tm_mday;
|
||||
buf[7] = tm.tm_hour;
|
||||
buf[8] = tm.tm_min;
|
||||
// Ignore leap second for simplicity
|
||||
buf[7] = tm.tm_sec < 60 ? tm.tm_sec : 59;
|
||||
|
||||
return 8;
|
||||
buf[9] = tm.tm_sec < 60 ? tm.tm_sec : 59;
|
||||
}
|
||||
|
||||
return 0;
|
||||
pages[32] = buf;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "mode_page_device.h"
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -29,7 +30,7 @@ public:
|
|||
void StartStopUnit(SCSIDEV *);
|
||||
|
||||
int ModeSense6(const DWORD *, BYTE *);
|
||||
int ModeSense10(const DWORD *, BYTE *);
|
||||
int ModeSense10(const DWORD *, BYTE *, int);
|
||||
|
||||
private:
|
||||
|
||||
|
@ -37,5 +38,6 @@ private:
|
|||
|
||||
Dispatcher<HostServices, SCSIDEV> dispatcher;
|
||||
|
||||
int AddRealtimeClockPage(int, BYTE *);
|
||||
void AddModePages(map<int, vector<BYTE>>&, int, bool) const override;
|
||||
void AddRealtimeClockPage(map<int, vector<BYTE>>&, bool) const;
|
||||
};
|
||||
|
|
|
@ -30,6 +30,66 @@ bool ModePageDevice::Dispatch(SCSIDEV *controller)
|
|||
return dispatcher.Dispatch(this, controller) ? true : super::Dispatch(controller);
|
||||
}
|
||||
|
||||
int ModePageDevice::AddModePages(const DWORD *cdb, BYTE *buf, int max_length)
|
||||
{
|
||||
bool changeable = (cdb[2] & 0xc0) == 0x40;
|
||||
|
||||
// Get page code (0x3f means all pages)
|
||||
int page = cdb[2] & 0x3f;
|
||||
|
||||
LOGTRACE("%s Requesting mode page $%02X", __PRETTY_FUNCTION__, page);
|
||||
|
||||
// Mode page data mapped to the respective page numbers, C++ maps are ordered by key
|
||||
map<int, vector<BYTE>> pages;
|
||||
AddModePages(pages, page, changeable);
|
||||
|
||||
// If no mode data were added at all something must be wrong
|
||||
if (pages.empty()) {
|
||||
LOGTRACE("%s Unsupported mode page $%02X", __PRETTY_FUNCTION__, page);
|
||||
SetStatusCode(STATUS_INVALIDCDB);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int size = 0;
|
||||
|
||||
vector<BYTE> page0;
|
||||
for (auto const& page : pages) {
|
||||
if (size + (int)page.second.size() > max_length) {
|
||||
LOGWARN("Mode page data size exceeds reserved buffer size");
|
||||
|
||||
page0.clear();
|
||||
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// The specification mandates that page 0 must be returned after all others
|
||||
if (page.first) {
|
||||
// Page data
|
||||
memcpy(&buf[size], page.second.data(), page.second.size());
|
||||
// Page code, PS bit may already have been set
|
||||
buf[size] |= page.first;
|
||||
// Page payload size
|
||||
buf[size + 1] = page.second.size() - 2;
|
||||
|
||||
size += page.second.size();
|
||||
}
|
||||
else {
|
||||
page0 = page.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Page 0 must be last
|
||||
if (!page0.empty()) {
|
||||
memcpy(&buf[size], page0.data(), page0.size());
|
||||
// Page payload size
|
||||
buf[size + 1] = page0.size() - 2;
|
||||
size += page0.size();
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void ModePageDevice::ModeSense6(SASIDEV *controller)
|
||||
{
|
||||
ctrl->length = ModeSense6(ctrl->cmd, ctrl->buffer);
|
||||
|
@ -43,7 +103,7 @@ void ModePageDevice::ModeSense6(SASIDEV *controller)
|
|||
|
||||
void ModePageDevice::ModeSense10(SASIDEV *controller)
|
||||
{
|
||||
ctrl->length = ModeSense10(ctrl->cmd, ctrl->buffer);
|
||||
ctrl->length = ModeSense10(ctrl->cmd, ctrl->buffer, ctrl->bufsize);
|
||||
if (ctrl->length <= 0) {
|
||||
controller->Error();
|
||||
return;
|
||||
|
@ -52,12 +112,9 @@ void ModePageDevice::ModeSense10(SASIDEV *controller)
|
|||
controller->DataIn();
|
||||
}
|
||||
|
||||
bool ModePageDevice::ModeSelect(const DWORD* /*cdb*/, const BYTE *buf, int length)
|
||||
bool ModePageDevice::ModeSelect(const DWORD*, const BYTE *, int)
|
||||
{
|
||||
ASSERT(buf);
|
||||
ASSERT(length >= 0);
|
||||
|
||||
// cannot be set
|
||||
// Cannot be set
|
||||
SetStatusCode(STATUS_INVALIDPRM);
|
||||
|
||||
return false;
|
||||
|
@ -67,7 +124,7 @@ void ModePageDevice::ModeSelect6(SASIDEV *controller)
|
|||
{
|
||||
LOGTRACE("%s Unsupported mode page $%02X", __PRETTY_FUNCTION__, ctrl->buffer[0]);
|
||||
|
||||
ctrl->length = ModeSelectCheck6(ctrl->cmd);
|
||||
ctrl->length = ModeSelectCheck6();
|
||||
if (ctrl->length <= 0) {
|
||||
controller->Error();
|
||||
return;
|
||||
|
@ -80,7 +137,7 @@ void ModePageDevice::ModeSelect10(SASIDEV *controller)
|
|||
{
|
||||
LOGTRACE("%s Unsupported mode page $%02X", __PRETTY_FUNCTION__, ctrl->buffer[0]);
|
||||
|
||||
ctrl->length = ModeSelectCheck10(ctrl->cmd);
|
||||
ctrl->length = ModeSelectCheck10();
|
||||
if (ctrl->length <= 0) {
|
||||
controller->Error();
|
||||
return;
|
||||
|
@ -89,11 +146,11 @@ void ModePageDevice::ModeSelect10(SASIDEV *controller)
|
|||
controller->DataOut();
|
||||
}
|
||||
|
||||
int ModePageDevice::ModeSelectCheck(const DWORD *cdb, int length)
|
||||
int ModePageDevice::ModeSelectCheck(int length)
|
||||
{
|
||||
// Error if save parameters are set for other types than of SCHD or SCRM
|
||||
// TODO This assumption is not correct, and this code should be located elsewhere
|
||||
if (!IsSCSIHD() && (cdb[1] & 0x01)) {
|
||||
// TODO The assumption above is not correct, and this code should be located elsewhere
|
||||
if (!IsSCSIHD() && (ctrl->cmd[1] & 0x01)) {
|
||||
SetStatusCode(STATUS_INVALIDCDB);
|
||||
return 0;
|
||||
}
|
||||
|
@ -101,21 +158,21 @@ int ModePageDevice::ModeSelectCheck(const DWORD *cdb, int length)
|
|||
return length;
|
||||
}
|
||||
|
||||
int ModePageDevice::ModeSelectCheck6(const DWORD *cdb)
|
||||
int ModePageDevice::ModeSelectCheck6()
|
||||
{
|
||||
// Receive the data specified by the parameter length
|
||||
return ModeSelectCheck(cdb, cdb[4]);
|
||||
return ModeSelectCheck(ctrl->cmd[4]);
|
||||
}
|
||||
|
||||
int ModePageDevice::ModeSelectCheck10(const DWORD *cdb)
|
||||
int ModePageDevice::ModeSelectCheck10()
|
||||
{
|
||||
// Receive the data specified by the parameter length
|
||||
int length = cdb[7];
|
||||
int length = ctrl->cmd[7];
|
||||
length <<= 8;
|
||||
length |= cdb[8];
|
||||
if (length > 0x800) {
|
||||
length = 0x800;
|
||||
length |= ctrl->cmd[8];
|
||||
if (length > ctrl->bufsize) {
|
||||
length = ctrl->bufsize;
|
||||
}
|
||||
|
||||
return ModeSelectCheck(cdb, length);
|
||||
return ModeSelectCheck(length);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
#include "primary_device.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -24,11 +26,16 @@ public:
|
|||
virtual bool Dispatch(SCSIDEV *) override;
|
||||
|
||||
virtual int ModeSense6(const DWORD *, BYTE *) = 0;
|
||||
virtual int ModeSense10(const DWORD *, BYTE *) = 0;
|
||||
virtual int ModeSense10(const DWORD *, BYTE *, int) = 0;
|
||||
|
||||
// TODO This method should not be called by SASIDEV
|
||||
virtual bool ModeSelect(const DWORD *, const BYTE *, int);
|
||||
|
||||
protected:
|
||||
|
||||
int AddModePages(const DWORD *, BYTE *, int);
|
||||
virtual void AddModePages(map<int, vector<BYTE>>&, int, bool) const = 0;
|
||||
|
||||
private:
|
||||
|
||||
typedef PrimaryDevice super;
|
||||
|
@ -40,7 +47,7 @@ private:
|
|||
void ModeSelect6(SASIDEV *);
|
||||
void ModeSelect10(SASIDEV *);
|
||||
|
||||
int ModeSelectCheck(const DWORD *, int);
|
||||
int ModeSelectCheck6(const DWORD *);
|
||||
int ModeSelectCheck10(const DWORD *);
|
||||
int ModeSelectCheck(int);
|
||||
int ModeSelectCheck6();
|
||||
int ModeSelectCheck10();
|
||||
};
|
||||
|
|
|
@ -68,11 +68,6 @@ void PrimaryDevice::ReportLuns(SASIDEV *controller)
|
|||
{
|
||||
BYTE *buf = ctrl->buffer;
|
||||
|
||||
if (!CheckReady()) {
|
||||
controller->Error();
|
||||
return;
|
||||
}
|
||||
|
||||
int allocation_length = (ctrl->cmd[6] << 24) + (ctrl->cmd[7] << 16) + (ctrl->cmd[8] << 8) + ctrl->cmd[9];
|
||||
memset(buf, 0, allocation_length);
|
||||
|
||||
|
@ -111,7 +106,7 @@ void PrimaryDevice::RequestSense(SASIDEV *controller)
|
|||
}
|
||||
|
||||
ctrl->length = ((PrimaryDevice *)ctrl->unit[lun])->RequestSense(ctrl->cmd, ctrl->buffer);
|
||||
ASSERT(ctrl->length > 0);
|
||||
assert(ctrl->length > 0);
|
||||
|
||||
LOGTRACE("%s Status $%02X, Sense Key $%02X, ASC $%02X",__PRETTY_FUNCTION__, ctrl->status, ctrl->buffer[2], ctrl->buffer[12]);
|
||||
|
||||
|
@ -124,7 +119,7 @@ bool PrimaryDevice::CheckReady()
|
|||
if (IsReset()) {
|
||||
SetStatusCode(STATUS_DEVRESET);
|
||||
SetReset(false);
|
||||
LOGTRACE("%s Disk in reset", __PRETTY_FUNCTION__);
|
||||
LOGTRACE("%s Device in reset", __PRETTY_FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -132,26 +127,32 @@ bool PrimaryDevice::CheckReady()
|
|||
if (IsAttn()) {
|
||||
SetStatusCode(STATUS_ATTENTION);
|
||||
SetAttn(false);
|
||||
LOGTRACE("%s Disk in needs attention", __PRETTY_FUNCTION__);
|
||||
LOGTRACE("%s Device in needs attention", __PRETTY_FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return status if not ready
|
||||
if (!IsReady()) {
|
||||
SetStatusCode(STATUS_NOTREADY);
|
||||
LOGTRACE("%s Disk not ready", __PRETTY_FUNCTION__);
|
||||
LOGTRACE("%s Device not ready", __PRETTY_FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialization with no error
|
||||
LOGTRACE("%s Disk is ready", __PRETTY_FUNCTION__);
|
||||
LOGTRACE("%s Device is ready", __PRETTY_FUNCTION__);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int PrimaryDevice::Inquiry(int type, int scsi_level, bool is_removable, const DWORD *cdb, BYTE *buf)
|
||||
{
|
||||
int allocation_length = cdb[4] + (((DWORD)cdb[3]) << 8);
|
||||
// EVPD and page code check
|
||||
if ((cdb[1] & 0x01) || cdb[2]) {
|
||||
SetStatusCode(STATUS_INVALIDCDB);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int allocation_length = cdb[4] + (cdb[3] << 8);
|
||||
if (allocation_length > 4) {
|
||||
if (allocation_length > 44) {
|
||||
allocation_length = 44;
|
||||
|
@ -167,6 +168,8 @@ int PrimaryDevice::Inquiry(int type, int scsi_level, bool is_removable, const DW
|
|||
buf[0] = type;
|
||||
buf[1] = is_removable ? 0x80 : 0x00;
|
||||
buf[2] = scsi_level;
|
||||
// Response data format is SCSI-2 for devices supporting SCSI-2 or newer, otherwise it is SCSI-1-CCS
|
||||
buf[3] = scsi_level >= 2 ? 2 : 0;
|
||||
buf[4] = 0x1F;
|
||||
|
||||
// Padded vendor, product, revision
|
||||
|
@ -178,22 +181,16 @@ int PrimaryDevice::Inquiry(int type, int scsi_level, bool is_removable, const DW
|
|||
|
||||
int PrimaryDevice::RequestSense(const DWORD *cdb, BYTE *buf)
|
||||
{
|
||||
ASSERT(cdb);
|
||||
ASSERT(buf);
|
||||
|
||||
// Return not ready only if there are no errors
|
||||
if (GetStatusCode() == STATUS_NOERROR) {
|
||||
if (!IsReady()) {
|
||||
SetStatusCode(STATUS_NOTREADY);
|
||||
}
|
||||
if (GetStatusCode() == STATUS_NOERROR && !IsReady()) {
|
||||
SetStatusCode(STATUS_NOTREADY);
|
||||
}
|
||||
|
||||
// Size determination (according to allocation length)
|
||||
int size = (int)cdb[4];
|
||||
ASSERT((size >= 0) && (size < 0x100));
|
||||
assert(size >= 0 && size < 0x100);
|
||||
|
||||
// For SCSI-1, transfer 4 bytes when the size is 0
|
||||
// (Deleted this specification for SCSI-2)
|
||||
if (size == 0) {
|
||||
size = 4;
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ public:
|
|||
virtual int Inquiry(const DWORD *, BYTE *) = 0;
|
||||
virtual int RequestSense(const DWORD *, BYTE *);
|
||||
virtual bool WriteBytes(BYTE *, uint32_t);
|
||||
virtual int GetSendDelay() { return BUS::SEND_NO_DELAY; }
|
||||
|
||||
protected:
|
||||
|
||||
|
|
|
@ -19,26 +19,11 @@
|
|||
#include "exceptions.h"
|
||||
#include "../config.h"
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// SASI Hard Disk
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Constructor
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
SASIHD::SASIHD() : Disk("SAHD")
|
||||
SASIHD::SASIHD(const set<uint32_t>& sector_sizes) : Disk("SAHD")
|
||||
{
|
||||
SetSectorSizes(sector_sizes);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Reset
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void SASIHD::Reset()
|
||||
{
|
||||
// Unlock, clear attention
|
||||
|
@ -50,14 +35,9 @@ void SASIHD::Reset()
|
|||
SetStatusCode(STATUS_NOERROR);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Open
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void SASIHD::Open(const Filepath& path)
|
||||
{
|
||||
ASSERT(!IsReady());
|
||||
assert(!IsReady());
|
||||
|
||||
// Open as read-only
|
||||
Fileio fio;
|
||||
|
@ -101,30 +81,17 @@ void SASIHD::Open(const Filepath& path)
|
|||
FileSupport::SetPath(path);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// INQUIRY
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
int SASIHD::Inquiry(const DWORD* /*cdb*/, BYTE* /*buf*/)
|
||||
{
|
||||
SetStatusCode(STATUS_INVALIDCMD);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// REQUEST SENSE
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
int SASIHD::RequestSense(const DWORD *cdb, BYTE *buf)
|
||||
{
|
||||
ASSERT(cdb);
|
||||
ASSERT(buf);
|
||||
|
||||
// Size decision
|
||||
int size = (int)cdb[4];
|
||||
ASSERT(size >= 0 && size < 0x100);
|
||||
assert(size >= 0 && size < 0x100);
|
||||
|
||||
// Transfer 4 bytes when size 0 (Shugart Associates System Interface specification)
|
||||
if (size == 0) {
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
class SASIHD : public Disk, public FileSupport
|
||||
{
|
||||
public:
|
||||
SASIHD();
|
||||
~SASIHD() {};
|
||||
SASIHD(const set<uint32_t>&);
|
||||
~SASIHD() {}
|
||||
|
||||
void Reset();
|
||||
void Open(const Filepath& path) override;
|
||||
|
|
|
@ -583,3 +583,11 @@ void SCSIDaynaPort::EnableInterface(SASIDEV *controller)
|
|||
|
||||
controller->Status();
|
||||
}
|
||||
|
||||
int SCSIDaynaPort::GetSendDelay()
|
||||
{
|
||||
// The Daynaport needs to have a delay after the size/flags field
|
||||
// of the read response. In the MacOS driver, it looks like the
|
||||
// driver is doing two "READ" system calls.
|
||||
return DAYNAPORT_READ_HEADER_SZ;
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ public:
|
|||
void SetInterfaceMode(SASIDEV *);
|
||||
void SetMcastAddr(SASIDEV *);
|
||||
void EnableInterface(SASIDEV *);
|
||||
int GetSendDelay() override;
|
||||
|
||||
bool Dispatch(SCSIDEV *) override;
|
||||
|
||||
|
|
|
@ -121,7 +121,7 @@ int SCSIBR::Inquiry(const DWORD *cdb, BYTE *buf)
|
|||
buf[0] = 0x09;
|
||||
buf[2] = 0x02;
|
||||
buf[3] = 0x02;
|
||||
buf[4] = 36 - 5 + 8; // required + 8 byte extension
|
||||
buf[4] = 0x1F + 8; // required + 8 byte extension
|
||||
|
||||
// Padded vendor, product, revision
|
||||
memcpy(&buf[8], GetPaddedName().c_str(), 28);
|
||||
|
@ -152,13 +152,9 @@ int SCSIBR::Inquiry(const DWORD *cdb, BYTE *buf)
|
|||
void SCSIBR::TestUnitReady(SASIDEV *controller)
|
||||
{
|
||||
// Always successful
|
||||
controller->Status();}
|
||||
controller->Status();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// GET MESSAGE(10)
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
int SCSIBR::GetMessage10(const DWORD *cdb, BYTE *buf)
|
||||
{
|
||||
// Type
|
||||
|
@ -239,16 +235,8 @@ int SCSIBR::GetMessage10(const DWORD *cdb, BYTE *buf)
|
|||
return 0;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SEND MESSAGE(10)
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
bool SCSIBR::SendMessage10(const DWORD *cdb, BYTE *buf)
|
||||
{
|
||||
ASSERT(cdb);
|
||||
ASSERT(buf);
|
||||
|
||||
// Type
|
||||
int type = cdb[2];
|
||||
|
||||
|
@ -301,11 +289,6 @@ bool SCSIBR::SendMessage10(const DWORD *cdb, BYTE *buf)
|
|||
return false;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// GET MESSAGE(10)
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::GetMessage10(SASIDEV *controller)
|
||||
{
|
||||
// Reallocate buffer (because it is not transfer for each block)
|
||||
|
@ -365,41 +348,22 @@ void SCSIBR::SendMessage10(SASIDEV *controller)
|
|||
controller->DataOut();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Get MAC Address
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
int SCSIBR::GetMacAddr(BYTE *mac)
|
||||
{
|
||||
ASSERT(mac);
|
||||
|
||||
memcpy(mac, mac_addr, 6);
|
||||
return 6;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Set MAC Address
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::SetMacAddr(BYTE *mac)
|
||||
{
|
||||
ASSERT(mac);
|
||||
|
||||
memcpy(mac_addr, mac, 6);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Receive Packet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::ReceivePacket()
|
||||
{
|
||||
static const BYTE bcast_addr[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||
|
||||
ASSERT(tap);
|
||||
assert(tap);
|
||||
|
||||
// previous packet has not been received
|
||||
if (packet_enable) {
|
||||
|
@ -429,15 +393,9 @@ void SCSIBR::ReceivePacket()
|
|||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Get Packet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::GetPacketBuf(BYTE *buf)
|
||||
{
|
||||
ASSERT(tap);
|
||||
ASSERT(buf);
|
||||
assert(tap);
|
||||
|
||||
// Size limit
|
||||
int len = packet_len;
|
||||
|
@ -452,15 +410,9 @@ void SCSIBR::GetPacketBuf(BYTE *buf)
|
|||
packet_enable = false;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Send Packet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::SendPacket(BYTE *buf, int len)
|
||||
{
|
||||
ASSERT(tap);
|
||||
ASSERT(buf);
|
||||
assert(tap);
|
||||
|
||||
tap->Tx(buf, len);
|
||||
}
|
||||
|
@ -472,9 +424,6 @@ void SCSIBR::SendPacket(BYTE *buf, int len)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_InitDevice(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
fs->Reset();
|
||||
fsresult = fs->InitDevice((Human68k::argument_t*)buf);
|
||||
}
|
||||
|
@ -486,13 +435,9 @@ void SCSIBR::FS_InitDevice(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_CheckDir(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
int i = 0;
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
i += sizeof(DWORD);
|
||||
int i = sizeof(DWORD);
|
||||
|
||||
Human68k::namests_t *pNamests = (Human68k::namests_t*)&buf[i];
|
||||
i += sizeof(Human68k::namests_t);
|
||||
|
@ -507,13 +452,9 @@ void SCSIBR::FS_CheckDir(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_MakeDir(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
int i = 0;
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
i += sizeof(DWORD);
|
||||
int i = sizeof(DWORD);
|
||||
|
||||
Human68k::namests_t *pNamests = (Human68k::namests_t*)&buf[i];
|
||||
i += sizeof(Human68k::namests_t);
|
||||
|
@ -528,13 +469,9 @@ void SCSIBR::FS_MakeDir(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_RemoveDir(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
int i = 0;
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
i += sizeof(DWORD);
|
||||
int i = sizeof(DWORD);
|
||||
|
||||
Human68k::namests_t *pNamests = (Human68k::namests_t*)&buf[i];
|
||||
i += sizeof(Human68k::namests_t);
|
||||
|
@ -549,13 +486,9 @@ void SCSIBR::FS_RemoveDir(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_Rename(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
int i = 0;
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
i += sizeof(DWORD);
|
||||
int i = sizeof(DWORD);
|
||||
|
||||
Human68k::namests_t *pNamests = (Human68k::namests_t*)&buf[i];
|
||||
i += sizeof(Human68k::namests_t);
|
||||
|
@ -573,13 +506,9 @@ void SCSIBR::FS_Rename(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_Delete(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
int i = 0;
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
i += sizeof(DWORD);
|
||||
int i = sizeof(DWORD);
|
||||
|
||||
Human68k::namests_t *pNamests = (Human68k::namests_t*)&buf[i];
|
||||
i += sizeof(Human68k::namests_t);
|
||||
|
@ -594,13 +523,9 @@ void SCSIBR::FS_Delete(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_Attribute(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
int i = 0;
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
i += sizeof(DWORD);
|
||||
int i = sizeof(DWORD);
|
||||
|
||||
Human68k::namests_t *pNamests = (Human68k::namests_t*)&buf[i];
|
||||
i += sizeof(Human68k::namests_t);
|
||||
|
@ -619,13 +544,9 @@ void SCSIBR::FS_Attribute(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_Files(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
int i = 0;
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
i += sizeof(DWORD);
|
||||
int i = sizeof(DWORD);
|
||||
|
||||
dp = (DWORD*)&buf[i];
|
||||
DWORD nKey = ntohl(*dp);
|
||||
|
@ -665,13 +586,9 @@ void SCSIBR::FS_Files(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_NFiles(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
int i = 0;
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
i += sizeof(DWORD);
|
||||
int i = sizeof(DWORD);
|
||||
|
||||
dp = (DWORD*)&buf[i];
|
||||
DWORD nKey = ntohl(*dp);
|
||||
|
@ -708,13 +625,9 @@ void SCSIBR::FS_NFiles(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_Create(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
int i = 0;
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
i += sizeof(DWORD);
|
||||
int i = sizeof(DWORD);
|
||||
|
||||
dp = (DWORD*)&buf[i];
|
||||
DWORD nKey = ntohl(*dp);
|
||||
|
@ -762,13 +675,9 @@ void SCSIBR::FS_Create(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_Open(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
int i = 0;
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
i += sizeof(DWORD);
|
||||
int i = sizeof(DWORD);
|
||||
|
||||
dp = (DWORD*)&buf[i];
|
||||
DWORD nKey = ntohl(*dp);
|
||||
|
@ -808,13 +717,9 @@ void SCSIBR::FS_Open(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_Close(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
int i = 0;
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
i += sizeof(DWORD);
|
||||
int i = sizeof(DWORD);
|
||||
|
||||
dp = (DWORD*)&buf[i];
|
||||
DWORD nKey = ntohl(*dp);
|
||||
|
@ -851,13 +756,9 @@ void SCSIBR::FS_Close(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_Read(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
int i = 0;
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nKey = ntohl(*dp);
|
||||
i += sizeof(DWORD);
|
||||
int i = sizeof(DWORD);
|
||||
|
||||
Human68k::fcb_t *pFcb = (Human68k::fcb_t*)&buf[i];
|
||||
i += sizeof(Human68k::fcb_t);
|
||||
|
@ -896,13 +797,9 @@ void SCSIBR::FS_Read(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_Write(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
int i = 0;
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nKey = ntohl(*dp);
|
||||
i += sizeof(DWORD);
|
||||
int i = sizeof(DWORD);
|
||||
|
||||
Human68k::fcb_t *pFcb = (Human68k::fcb_t*)&buf[i];
|
||||
i += sizeof(Human68k::fcb_t);
|
||||
|
@ -939,13 +836,9 @@ void SCSIBR::FS_Write(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_Seek(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
int i = 0;
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nKey = ntohl(*dp);
|
||||
i += sizeof(DWORD);
|
||||
int i = sizeof(DWORD);
|
||||
|
||||
Human68k::fcb_t *pFcb = (Human68k::fcb_t*)&buf[i];
|
||||
i += sizeof(Human68k::fcb_t);
|
||||
|
@ -986,13 +879,9 @@ void SCSIBR::FS_Seek(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_TimeStamp(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
int i = 0;
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
i += sizeof(DWORD);
|
||||
int i = sizeof(DWORD);
|
||||
|
||||
dp = (DWORD*)&buf[i];
|
||||
DWORD nKey = ntohl(*dp);
|
||||
|
@ -1033,9 +922,6 @@ void SCSIBR::FS_TimeStamp(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_GetCapacity(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
|
||||
|
@ -1058,13 +944,9 @@ void SCSIBR::FS_GetCapacity(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_CtrlDrive(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
int i = 0;
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
i += sizeof(DWORD);
|
||||
int i = sizeof(DWORD);
|
||||
|
||||
Human68k::ctrldrive_t *pCtrlDrive = (Human68k::ctrldrive_t*)&buf[i];
|
||||
|
||||
|
@ -1081,9 +963,6 @@ void SCSIBR::FS_CtrlDrive(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_GetDPB(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
|
||||
|
@ -1108,13 +987,9 @@ void SCSIBR::FS_GetDPB(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_DiskRead(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
int i = 0;
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
i += sizeof(DWORD);
|
||||
int i = sizeof(DWORD);
|
||||
|
||||
dp = (DWORD*)&buf[i];
|
||||
DWORD nSector = ntohl(*dp);
|
||||
|
@ -1135,9 +1010,6 @@ void SCSIBR::FS_DiskRead(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_DiskWrite(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
|
||||
|
@ -1151,13 +1023,9 @@ void SCSIBR::FS_DiskWrite(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_Ioctrl(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
int i = 0;
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
i += sizeof(DWORD);
|
||||
int i = sizeof(DWORD);
|
||||
|
||||
dp = (DWORD*)&buf[i];
|
||||
DWORD nFunction = ntohl(*dp);
|
||||
|
@ -1198,9 +1066,6 @@ void SCSIBR::FS_Ioctrl(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_Flush(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
|
||||
|
@ -1214,9 +1079,6 @@ void SCSIBR::FS_Flush(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_CheckMedia(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
|
||||
|
@ -1230,9 +1092,6 @@ void SCSIBR::FS_CheckMedia(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::FS_Lock(BYTE *buf)
|
||||
{
|
||||
ASSERT(fs);
|
||||
ASSERT(buf);
|
||||
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
DWORD nUnit = ntohl(*dp);
|
||||
|
||||
|
@ -1246,8 +1105,6 @@ void SCSIBR::FS_Lock(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
int SCSIBR::ReadFsResult(BYTE *buf)
|
||||
{
|
||||
ASSERT(buf);
|
||||
|
||||
DWORD *dp = (DWORD*)buf;
|
||||
*dp = htonl(fsresult);
|
||||
return sizeof(DWORD);
|
||||
|
@ -1260,8 +1117,6 @@ int SCSIBR::ReadFsResult(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
int SCSIBR::ReadFsOut(BYTE *buf)
|
||||
{
|
||||
ASSERT(buf);
|
||||
|
||||
memcpy(buf, fsout, fsoutlen);
|
||||
return fsoutlen;
|
||||
}
|
||||
|
@ -1273,8 +1128,6 @@ int SCSIBR::ReadFsOut(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
int SCSIBR::ReadFsOpt(BYTE *buf)
|
||||
{
|
||||
ASSERT(buf);
|
||||
|
||||
memcpy(buf, fsopt, fsoptlen);
|
||||
return fsoptlen;
|
||||
}
|
||||
|
@ -1286,8 +1139,6 @@ int SCSIBR::ReadFsOpt(BYTE *buf)
|
|||
//---------------------------------------------------------------------------
|
||||
void SCSIBR::WriteFs(int func, BYTE *buf)
|
||||
{
|
||||
ASSERT(buf);
|
||||
|
||||
fsresult = FS_FATAL_INVALIDCOMMAND;
|
||||
fsoutlen = 0;
|
||||
fsoptlen = 0;
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
// 2. The client sends the data to be printed with one or several PRINT commands. Due to
|
||||
// https://github.com/akuker/RASCSI/issues/669 the maximum transfer size per PRINT command is
|
||||
// limited to 4096 bytes.
|
||||
// 3. The client triggers printing with SYNCHRONIZE BUFFER.
|
||||
// 3. The client triggers printing with SYNCHRONIZE BUFFER. Each SYNCHRONIZE BUFFER results in
|
||||
// the print command for this printer (see below) to be called for the data not yet printed.
|
||||
// 4. The client releases the printer with RELEASE UNIT (optional step, mandatory for a
|
||||
// multi-initiator environment).
|
||||
//
|
||||
|
@ -68,7 +69,7 @@ SCSIPrinter::SCSIPrinter() : PrimaryDevice("SCLP"), ScsiPrinterCommands()
|
|||
|
||||
SCSIPrinter::~SCSIPrinter()
|
||||
{
|
||||
DiscardReservation();
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
bool SCSIPrinter::Init(const map<string, string>& params)
|
||||
|
@ -168,7 +169,7 @@ void SCSIPrinter::Print(SCSIDEV *controller)
|
|||
|
||||
// TODO This device suffers from the statically allocated buffer size,
|
||||
// see https://github.com/akuker/RASCSI/issues/669
|
||||
if (length > (uint32_t)controller->DEFAULT_BUFFER_SIZE) {
|
||||
if (length > (uint32_t)ctrl->bufsize) {
|
||||
LOGERROR("Transfer buffer overflow");
|
||||
|
||||
controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB);
|
||||
|
|
|
@ -177,8 +177,10 @@ bool CDTrack::IsAudio() const
|
|||
//
|
||||
//===========================================================================
|
||||
|
||||
SCSICD::SCSICD() : Disk("SCCD"), ScsiMmcCommands(), FileSupport()
|
||||
SCSICD::SCSICD(const set<uint32_t>& sector_sizes) : Disk("SCCD"), ScsiMmcCommands(), FileSupport()
|
||||
{
|
||||
SetSectorSizes(sector_sizes);
|
||||
|
||||
// NOT in raw format
|
||||
rawfile = false;
|
||||
|
||||
|
@ -263,7 +265,7 @@ void SCSICD::Open(const Filepath& path)
|
|||
// Successful opening
|
||||
ASSERT(GetBlockCount() > 0);
|
||||
|
||||
Disk::Open(path);
|
||||
super::Open(path);
|
||||
FileSupport::SetPath(path);
|
||||
|
||||
// Set RAW flag
|
||||
|
@ -415,7 +417,7 @@ int SCSICD::Inquiry(const DWORD *cdb, BYTE *buf)
|
|||
buf[1] = 0x80;
|
||||
buf[2] = 0x02;
|
||||
buf[3] = 0x02;
|
||||
buf[4] = 36 - 5; // Required
|
||||
buf[4] = 0x1F;
|
||||
|
||||
// Fill with blanks
|
||||
memset(&buf[8], 0x20, buf[4] - 3);
|
||||
|
@ -457,6 +459,48 @@ int SCSICD::Inquiry(const DWORD *cdb, BYTE *buf)
|
|||
return size;
|
||||
}
|
||||
|
||||
void SCSICD::AddModePages(map<int, vector<BYTE>>& pages, int page, bool changeable) const
|
||||
{
|
||||
super::AddModePages(pages, page, changeable);
|
||||
|
||||
// Page code 13
|
||||
if (page == 0x0d || page == 0x3f) {
|
||||
AddCDROMPage(pages, changeable);
|
||||
}
|
||||
|
||||
// Page code 14
|
||||
if (page == 0x0e || page == 0x3f) {
|
||||
AddCDDAPage(pages, changeable);
|
||||
}
|
||||
}
|
||||
|
||||
void SCSICD::AddCDROMPage(map<int, vector<BYTE>>& pages, bool changeable) const
|
||||
{
|
||||
vector<BYTE> buf(8);
|
||||
|
||||
// No changeable area
|
||||
if (!changeable) {
|
||||
// 2 seconds for inactive timer
|
||||
buf[3] = 0x05;
|
||||
|
||||
// MSF multiples are 60 and 75 respectively
|
||||
buf[5] = 60;
|
||||
buf[7] = 75;
|
||||
}
|
||||
|
||||
pages[13] = buf;
|
||||
}
|
||||
|
||||
void SCSICD::AddCDDAPage(map<int, vector<BYTE>>& pages, bool) const
|
||||
{
|
||||
vector<BYTE> buf(16);
|
||||
|
||||
// Audio waits for operation completion and allows
|
||||
// PLAY across multiple tracks
|
||||
|
||||
pages[14] = buf;
|
||||
}
|
||||
|
||||
int SCSICD::Read(const DWORD *cdb, BYTE *buf, uint64_t block)
|
||||
{
|
||||
ASSERT(buf);
|
||||
|
@ -498,7 +542,7 @@ int SCSICD::Read(const DWORD *cdb, BYTE *buf, uint64_t block)
|
|||
|
||||
// Base class
|
||||
ASSERT(dataindex >= 0);
|
||||
return Disk::Read(cdb, buf, block);
|
||||
return super::Read(cdb, buf, block);
|
||||
}
|
||||
|
||||
int SCSICD::ReadToc(const DWORD *cdb, BYTE *buf)
|
||||
|
|
|
@ -76,7 +76,7 @@ public:
|
|||
TrackMax = 96 // Maximum number of tracks
|
||||
};
|
||||
|
||||
SCSICD();
|
||||
SCSICD(const set<uint32_t>&);
|
||||
~SCSICD();
|
||||
|
||||
bool Dispatch(SCSIDEV *) override;
|
||||
|
@ -88,11 +88,18 @@ public:
|
|||
int Read(const DWORD *cdb, BYTE *buf, uint64_t block) override; // READ command
|
||||
int ReadToc(const DWORD *cdb, BYTE *buf); // READ TOC command
|
||||
|
||||
protected:
|
||||
|
||||
void AddModePages(map<int, vector<BYTE>>&, int, bool) const override;
|
||||
|
||||
private:
|
||||
typedef Disk super;
|
||||
|
||||
Dispatcher<SCSICD, SASIDEV> dispatcher;
|
||||
|
||||
void AddCDROMPage(map<int, vector<BYTE>>&, bool) const;
|
||||
void AddCDDAPage(map<int, vector<BYTE>>&, bool) const;
|
||||
|
||||
// Open
|
||||
void OpenCue(const Filepath& path); // Open(CUE)
|
||||
void OpenIso(const Filepath& path); // Open(ISO)
|
||||
|
|
|
@ -26,8 +26,9 @@
|
|||
//
|
||||
//===========================================================================
|
||||
|
||||
SCSIHD::SCSIHD(bool removable) : Disk(removable ? "SCRM" : "SCHD")
|
||||
SCSIHD::SCSIHD(const set<uint32_t>& sector_sizes, bool removable) : Disk(removable ? "SCRM" : "SCHD")
|
||||
{
|
||||
SetSectorSizes(sector_sizes);
|
||||
}
|
||||
|
||||
void SCSIHD::FinalizeSetup(const Filepath &path, off_t size)
|
||||
|
@ -75,7 +76,7 @@ void SCSIHD::Reset()
|
|||
|
||||
void SCSIHD::Open(const Filepath& path)
|
||||
{
|
||||
ASSERT(!IsReady());
|
||||
assert(!IsReady());
|
||||
|
||||
// Open as read-only
|
||||
Fileio fio;
|
||||
|
@ -105,12 +106,6 @@ int SCSIHD::Inquiry(const DWORD *cdb, BYTE *buf)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// Ready check (Error if no image file)
|
||||
if (!IsReady()) {
|
||||
SetStatusCode(STATUS_NOTREADY);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Basic data
|
||||
// buf[0] ... Direct Access Device
|
||||
// buf[1] ... Bit 7 set means removable
|
||||
|
@ -121,7 +116,7 @@ int SCSIHD::Inquiry(const DWORD *cdb, BYTE *buf)
|
|||
buf[1] = IsRemovable() ? 0x80 : 0x00;
|
||||
buf[2] = 0x02;
|
||||
buf[3] = 0x02;
|
||||
buf[4] = 28 + 3; // Value close to real HDD
|
||||
buf[4] = 0x1F;
|
||||
|
||||
// Padded vendor, product, revision
|
||||
memcpy(&buf[8], GetPaddedName().c_str(), 28);
|
||||
|
@ -139,10 +134,9 @@ int SCSIHD::Inquiry(const DWORD *cdb, BYTE *buf)
|
|||
|
||||
bool SCSIHD::ModeSelect(const DWORD *cdb, const BYTE *buf, int length)
|
||||
{
|
||||
int size;
|
||||
assert(length >= 0);
|
||||
|
||||
ASSERT(buf);
|
||||
ASSERT(length >= 0);
|
||||
int size;
|
||||
|
||||
// PF
|
||||
if (cdb[1] & 0x10) {
|
||||
|
@ -216,23 +210,19 @@ bool SCSIHD::ModeSelect(const DWORD *cdb, const BYTE *buf, int length)
|
|||
// Add Vendor special page to make drive Apple compatible
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
int SCSIHD::AddVendorPage(int page, bool change, BYTE *buf)
|
||||
void SCSIHD::AddVendorPage(map<int, vector<BYTE>>& pages, int page, bool changeable) const
|
||||
{
|
||||
ASSERT(buf);
|
||||
|
||||
// Page code 48 or 63
|
||||
// Page code 48
|
||||
if (page != 0x30 && page != 0x3f) {
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the message length
|
||||
buf[0] = 0x30;
|
||||
buf[1] = 0x1c;
|
||||
vector<BYTE> buf(30);
|
||||
|
||||
// No changeable area
|
||||
if (!change) {
|
||||
memcpy(&buf[0xa], "APPLE COMPUTER, INC.", 20);
|
||||
if (!changeable) {
|
||||
memcpy(&buf.data()[0xa], "APPLE COMPUTER, INC.", 20);
|
||||
}
|
||||
|
||||
return 30;
|
||||
pages[48] = buf;
|
||||
}
|
||||
|
|
|
@ -22,8 +22,8 @@
|
|||
class SCSIHD : public Disk, public FileSupport
|
||||
{
|
||||
public:
|
||||
SCSIHD(bool);
|
||||
virtual ~SCSIHD() {};
|
||||
SCSIHD(const set<uint32_t>&, bool);
|
||||
virtual ~SCSIHD() {}
|
||||
|
||||
void FinalizeSetup(const Filepath&, off_t);
|
||||
|
||||
|
@ -34,6 +34,5 @@ public:
|
|||
virtual int Inquiry(const DWORD *cdb, BYTE *buf) override;
|
||||
bool ModeSelect(const DWORD *cdb, const BYTE *buf, int length) override;
|
||||
|
||||
// Add vendor special page
|
||||
int AddVendorPage(int page, bool change, BYTE *buf) override;
|
||||
void AddVendorPage(map<int, vector<BYTE>>&, int, bool) const override;
|
||||
};
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include "fileio.h"
|
||||
#include "exceptions.h"
|
||||
|
||||
SCSIHD_NEC::SCSIHD_NEC() : SCSIHD(false)
|
||||
SCSIHD_NEC::SCSIHD_NEC(const set<uint32_t>& sector_sizes) : SCSIHD(sector_sizes, false)
|
||||
{
|
||||
// Work initialization
|
||||
cylinders = 0;
|
||||
|
@ -146,31 +146,30 @@ int SCSIHD_NEC::Inquiry(const DWORD *cdb, BYTE *buf)
|
|||
return size;
|
||||
}
|
||||
|
||||
int SCSIHD_NEC::AddErrorPage(bool change, BYTE *buf)
|
||||
void SCSIHD_NEC::AddErrorPage(map<int, vector<BYTE>>& pages, bool) const
|
||||
{
|
||||
ASSERT(buf);
|
||||
|
||||
// Set the message length
|
||||
buf[0] = 0x01;
|
||||
buf[1] = 0x06;
|
||||
vector<BYTE> buf(8);
|
||||
|
||||
// The retry count is 0, and the limit time uses the default value inside the device.
|
||||
return 8;
|
||||
|
||||
pages[1] = buf;
|
||||
}
|
||||
|
||||
int SCSIHD_NEC::AddFormatPage(bool change, BYTE *buf)
|
||||
void SCSIHD_NEC::AddFormatPage(map<int, vector<BYTE>>& pages, bool changeable) const
|
||||
{
|
||||
ASSERT(buf);
|
||||
vector<BYTE> buf(24);
|
||||
|
||||
// Set the message length
|
||||
buf[0] = 0x80 | 0x03;
|
||||
buf[1] = 0x16;
|
||||
// Page can be saved
|
||||
buf[0] = 0x80;
|
||||
|
||||
// Make the number of bytes in the physical sector appear mutable (although it cannot actually be)
|
||||
if (change) {
|
||||
if (changeable) {
|
||||
buf[0xc] = 0xff;
|
||||
buf[0xd] = 0xff;
|
||||
return 24;
|
||||
|
||||
pages[3] = buf;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsReady()) {
|
||||
|
@ -193,19 +192,15 @@ int SCSIHD_NEC::AddFormatPage(bool change, BYTE *buf)
|
|||
buf[20] = 0x20;
|
||||
}
|
||||
|
||||
return 24;
|
||||
pages[3] = buf;
|
||||
}
|
||||
|
||||
int SCSIHD_NEC::AddDrivePage(bool change, BYTE *buf)
|
||||
void SCSIHD_NEC::AddDrivePage(map<int, vector<BYTE>>& pages, bool changeable) const
|
||||
{
|
||||
ASSERT(buf);
|
||||
|
||||
// Set the message length
|
||||
buf[0] = 0x04;
|
||||
buf[1] = 0x12;
|
||||
vector<BYTE> buf(20);
|
||||
|
||||
// No changeable area
|
||||
if (!change && IsReady()) {
|
||||
if (!changeable && IsReady()) {
|
||||
// Set the number of cylinders
|
||||
buf[0x2] = (BYTE)(cylinders >> 16);
|
||||
buf[0x3] = (BYTE)(cylinders >> 8);
|
||||
|
@ -215,5 +210,5 @@ int SCSIHD_NEC::AddDrivePage(bool change, BYTE *buf)
|
|||
buf[0x5] = (BYTE)heads;
|
||||
}
|
||||
|
||||
return 20;
|
||||
pages[4] = buf;
|
||||
}
|
||||
|
|
|
@ -25,17 +25,17 @@
|
|||
class SCSIHD_NEC : public SCSIHD
|
||||
{
|
||||
public:
|
||||
SCSIHD_NEC();
|
||||
~SCSIHD_NEC() {};
|
||||
SCSIHD_NEC(const set<uint32_t>&);
|
||||
~SCSIHD_NEC() {}
|
||||
|
||||
void Open(const Filepath& path) override;
|
||||
|
||||
// Commands
|
||||
int Inquiry(const DWORD *cdb, BYTE *buf) override;
|
||||
|
||||
int AddErrorPage(bool change, BYTE *buf) override;
|
||||
int AddFormatPage(bool change, BYTE *buf) override;
|
||||
int AddDrivePage(bool change, BYTE *buf) override;
|
||||
void AddErrorPage(map<int, vector<BYTE>>&, bool) const override;
|
||||
void AddFormatPage(map<int, vector<BYTE>>&, bool) const override;
|
||||
void AddDrivePage(map<int, vector<BYTE>>&, bool) const override;
|
||||
|
||||
private:
|
||||
// Geometry data
|
||||
|
|
|
@ -19,29 +19,15 @@
|
|||
#include "fileio.h"
|
||||
#include "exceptions.h"
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// SCSI magneto-optical disk
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Constructor
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
SCSIMO::SCSIMO() : Disk("SCMO")
|
||||
SCSIMO::SCSIMO(const set<uint32_t>& sector_sizes, const map<uint64_t, Geometry>& geometries) : Disk("SCMO")
|
||||
{
|
||||
SetSectorSizes(sector_sizes);
|
||||
SetGeometries(geometries);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Open
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void SCSIMO::Open(const Filepath& path)
|
||||
{
|
||||
ASSERT(!IsReady());
|
||||
assert(!IsReady());
|
||||
|
||||
// Open as read-only
|
||||
Fileio fio;
|
||||
|
@ -77,16 +63,8 @@ void SCSIMO::Open(const Filepath& path)
|
|||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// INQUIRY
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
int SCSIMO::Inquiry(const DWORD *cdb, BYTE *buf)
|
||||
{
|
||||
ASSERT(cdb);
|
||||
ASSERT(buf);
|
||||
|
||||
// EVPD check
|
||||
if (cdb[1] & 0x01) {
|
||||
SetStatusCode(STATUS_INVALIDCDB);
|
||||
|
@ -104,7 +82,7 @@ int SCSIMO::Inquiry(const DWORD *cdb, BYTE *buf)
|
|||
buf[1] = 0x80;
|
||||
buf[2] = 0x02;
|
||||
buf[3] = 0x02;
|
||||
buf[4] = 36 - 5; // required
|
||||
buf[4] = 0x1F;
|
||||
|
||||
// Padded vendor, product, revision
|
||||
memcpy(&buf[8], GetPaddedName().c_str(), 28);
|
||||
|
@ -120,16 +98,36 @@ int SCSIMO::Inquiry(const DWORD *cdb, BYTE *buf)
|
|||
return size;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// MODE SELECT
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void SCSIMO::SetDeviceParameters(BYTE *buf)
|
||||
{
|
||||
Disk::SetDeviceParameters(buf);
|
||||
|
||||
// MEDIUM TYPE: Optical reversible or erasable
|
||||
buf[2] = 0x03;
|
||||
}
|
||||
|
||||
void SCSIMO::AddModePages(map<int, vector<BYTE>>& pages, int page, bool changeable) const
|
||||
{
|
||||
Disk::AddModePages(pages, page, changeable);
|
||||
|
||||
// Page code 6
|
||||
if (page == 0x06 || page == 0x3f) {
|
||||
AddOptionPage(pages, changeable);
|
||||
}
|
||||
}
|
||||
|
||||
void SCSIMO::AddOptionPage(map<int, vector<BYTE>>& pages, bool) const
|
||||
{
|
||||
vector<BYTE> buf(4);
|
||||
pages[6] = buf;
|
||||
|
||||
// Do not report update blocks
|
||||
}
|
||||
|
||||
bool SCSIMO::ModeSelect(const DWORD *cdb, const BYTE *buf, int length)
|
||||
{
|
||||
int size;
|
||||
|
||||
ASSERT(buf);
|
||||
ASSERT(length >= 0);
|
||||
|
||||
// PF
|
||||
|
@ -191,22 +189,20 @@ bool SCSIMO::ModeSelect(const DWORD *cdb, const BYTE *buf, int length)
|
|||
// Vendor Unique Format Page 20h (MO)
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
int SCSIMO::AddVendor(int page, BOOL change, BYTE *buf)
|
||||
void SCSIMO::AddVendorPage(map<int, vector<BYTE>>& pages, int page, bool changeable) const
|
||||
{
|
||||
ASSERT(buf);
|
||||
|
||||
// Page code 20h
|
||||
if ((page != 0x20) && (page != 0x3f)) {
|
||||
return 0;
|
||||
if (page != 0x20 && page != 0x3f) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the message length
|
||||
buf[0] = 0x20;
|
||||
buf[1] = 0x0a;
|
||||
vector<BYTE> buf(12);
|
||||
|
||||
// No changeable area
|
||||
if (change) {
|
||||
return 12;
|
||||
if (changeable) {
|
||||
pages[32] = buf;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -287,5 +283,7 @@ int SCSIMO::AddVendor(int page, BOOL change, BYTE *buf)
|
|||
buf[11] = (BYTE)bands;
|
||||
}
|
||||
|
||||
return 12;
|
||||
pages[32] = buf;
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -22,8 +22,8 @@
|
|||
class SCSIMO : public Disk, public FileSupport
|
||||
{
|
||||
public:
|
||||
SCSIMO();
|
||||
~SCSIMO() {};
|
||||
SCSIMO(const set<uint32_t>&, const map<uint64_t, Geometry>&);
|
||||
~SCSIMO() {}
|
||||
|
||||
void Open(const Filepath& path) override;
|
||||
|
||||
|
@ -31,6 +31,14 @@ public:
|
|||
int Inquiry(const DWORD *cdb, BYTE *buf) override;
|
||||
bool ModeSelect(const DWORD *cdb, const BYTE *buf, int length) override;
|
||||
|
||||
protected:
|
||||
|
||||
// Internal processing
|
||||
int AddVendor(int page, BOOL change, BYTE *buf); // Add vendor special page
|
||||
void SetDeviceParameters(BYTE *) override;
|
||||
void AddModePages(map<int, vector<BYTE>>&, int, bool) const override;
|
||||
void AddVendorPage(map<int, vector<BYTE>>&, int, bool) const override;
|
||||
|
||||
private:
|
||||
|
||||
void AddOptionPage(map<int, vector<BYTE>>&, bool) const;
|
||||
};
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
// Copyright (C) 2010 Y.Sugahara
|
||||
//
|
||||
// Imported sava's Anex86/T98Next image and MO format support patch.
|
||||
// Imported NetBSD support and some optimisation patch by Rin Okuyama.
|
||||
// Comments translated to english by akuker.
|
||||
//
|
||||
// [ DiskTrack and DiskCache ]
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
// Powered by XM6 TypeG Technology.
|
||||
// Copyright (C) 2016-2020 GIMONS
|
||||
//
|
||||
// Imported NetBSD support and some optimisation patch by Rin Okuyama.
|
||||
//
|
||||
// [ GPIO-SCSI bus ]
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
|
|
@ -488,7 +488,7 @@ public:
|
|||
// Bus signal acquisition
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
inline DWORD Aquire()
|
||||
inline DWORD Aquire() override
|
||||
{
|
||||
#if defined(__x86_64__) || defined(__X86__)
|
||||
// Only used for development/debugging purposes. Isn't really applicable
|
||||
|
@ -509,24 +509,24 @@ public:
|
|||
void SetENB(BOOL ast);
|
||||
// Set ENB signal
|
||||
|
||||
bool GetBSY();
|
||||
bool GetBSY() override;
|
||||
// Get BSY signal
|
||||
void SetBSY(bool ast);
|
||||
void SetBSY(bool ast) override;
|
||||
// Set BSY signal
|
||||
|
||||
BOOL GetSEL();
|
||||
BOOL GetSEL() override;
|
||||
// Get SEL signal
|
||||
void SetSEL(BOOL ast);
|
||||
void SetSEL(BOOL ast) override;
|
||||
// Set SEL signal
|
||||
|
||||
BOOL GetATN();
|
||||
BOOL GetATN() override;
|
||||
// Get ATN signal
|
||||
void SetATN(BOOL ast);
|
||||
void SetATN(BOOL ast) override;
|
||||
// Set ATN signal
|
||||
|
||||
BOOL GetACK();
|
||||
BOOL GetACK() override;
|
||||
// Get ACK signal
|
||||
void SetACK(BOOL ast);
|
||||
void SetACK(BOOL ast) override;
|
||||
// Set ACK signal
|
||||
|
||||
BOOL GetACT();
|
||||
|
@ -534,42 +534,42 @@ public:
|
|||
void SetACT(BOOL ast);
|
||||
// Set ACT signal
|
||||
|
||||
BOOL GetRST();
|
||||
BOOL GetRST() override;
|
||||
// Get RST signal
|
||||
void SetRST(BOOL ast);
|
||||
void SetRST(BOOL ast) override;
|
||||
// Set RST signal
|
||||
|
||||
BOOL GetMSG();
|
||||
BOOL GetMSG() override;
|
||||
// Get MSG signal
|
||||
void SetMSG(BOOL ast);
|
||||
void SetMSG(BOOL ast) override;
|
||||
// Set MSG signal
|
||||
|
||||
BOOL GetCD();
|
||||
BOOL GetCD() override;
|
||||
// Get CD signal
|
||||
void SetCD(BOOL ast);
|
||||
void SetCD(BOOL ast) override;
|
||||
// Set CD signal
|
||||
|
||||
BOOL GetIO();
|
||||
BOOL GetIO() override;
|
||||
// Get IO signal
|
||||
void SetIO(BOOL ast);
|
||||
void SetIO(BOOL ast) override;
|
||||
// Set IO signal
|
||||
|
||||
BOOL GetREQ();
|
||||
BOOL GetREQ() override;
|
||||
// Get REQ signal
|
||||
void SetREQ(BOOL ast);
|
||||
void SetREQ(BOOL ast) override;
|
||||
// Set REQ signal
|
||||
|
||||
BYTE GetDAT();
|
||||
BYTE GetDAT() override;
|
||||
// Get DAT signal
|
||||
void SetDAT(BYTE dat);
|
||||
void SetDAT(BYTE dat) override;
|
||||
// Set DAT signal
|
||||
BOOL GetDP();
|
||||
BOOL GetDP() override;
|
||||
// Get Data parity signal
|
||||
int CommandHandShake(BYTE *buf);
|
||||
int CommandHandShake(BYTE *buf) override;
|
||||
// Command receive handshake
|
||||
int ReceiveHandShake(BYTE *buf, int count);
|
||||
int ReceiveHandShake(BYTE *buf, int count) override;
|
||||
// Data receive handshake
|
||||
int SendHandShake(BYTE *buf, int count, int delay_after_bytes);
|
||||
int SendHandShake(BYTE *buf, int count, int delay_after_bytes) override;
|
||||
// Data transmission handshake
|
||||
|
||||
static BUS::phase_t GetPhaseRaw(DWORD raw_data);
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
// Copyright (C) 2016-2020 GIMONS
|
||||
// Copyright (C) 2020 akuker
|
||||
//
|
||||
// Imported NetBSD support and some optimisation patch by Rin Okuyama.
|
||||
//
|
||||
// [ OS related definitions ]
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
@ -58,23 +56,11 @@
|
|||
#include <pwd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#if defined(__linux__)
|
||||
#include <sys/epoll.h>
|
||||
#endif
|
||||
#include <netinet/in.h>
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/if.h>
|
||||
#include <linux/if_tun.h>
|
||||
#elif defined(__NetBSD__)
|
||||
#include <sys/param.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <net/if.h>
|
||||
#include <net/if_dl.h>
|
||||
#include <net/if_tap.h>
|
||||
#include <ifaddrs.h>
|
||||
#endif
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
|
|
|
@ -773,8 +773,9 @@ bool Insert(const CommandContext& context, const PbDeviceDefinition& pb_device,
|
|||
LOGINFO("Insert %sfile '%s' requested into %s ID %d, unit %d", pb_device.protected_() ? "protected " : "",
|
||||
filename.c_str(), device->GetType().c_str(), pb_device.id(), pb_device.unit());
|
||||
|
||||
Disk *disk = dynamic_cast<Disk *>(device);
|
||||
|
||||
if (pb_device.block_size()) {
|
||||
Disk *disk = dynamic_cast<Disk *>(device);
|
||||
if (disk && disk->IsSectorSizeConfigurable()) {
|
||||
if (!disk->SetConfiguredSectorSize(pb_device.block_size())) {
|
||||
return ReturnLocalizedError(context, ERROR_BLOCK_SIZE, to_string(pb_device.block_size()));
|
||||
|
@ -823,7 +824,6 @@ bool Insert(const CommandContext& context, const PbDeviceDefinition& pb_device,
|
|||
device->SetProtected(pb_device.protected_());
|
||||
}
|
||||
|
||||
Disk *disk = dynamic_cast<Disk *>(device);
|
||||
if (disk) {
|
||||
disk->MediumChanged();
|
||||
}
|
||||
|
|
|
@ -148,6 +148,7 @@ public:
|
|||
virtual void SetDAT(BYTE dat) = 0;
|
||||
virtual BOOL GetDP() = 0; // Get parity signal
|
||||
|
||||
virtual DWORD Aquire() = 0;
|
||||
virtual int CommandHandShake(BYTE *buf) = 0;
|
||||
virtual int ReceiveHandShake(BYTE *buf, int count) = 0;
|
||||
virtual int SendHandShake(BYTE *buf, int count, int delay_after_bytes) = 0;
|
||||
|
|
Loading…
Reference in New Issue