Skip to content

Commit

Permalink
Merge branch 'future3/develop' into future3/proper-second-swipe
Browse files Browse the repository at this point in the history
  • Loading branch information
pabera committed Nov 10, 2024
2 parents 2d7f17e + 0df1a0c commit 369776a
Show file tree
Hide file tree
Showing 18 changed files with 1,063 additions and 270 deletions.
19 changes: 17 additions & 2 deletions documentation/developers/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,21 @@ would be of course useful to get rid of them, but currently we make a
trade-off between a development environment and solving the specific
details.

### Error when local libzmq Dockerfile has not been built:

``` bash
------
> [jukebox internal] load metadata for docker.io/library/libzmq:local:
------
failed to solve: libzmq:local: pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed
```

Build libzmq for your host machine

``` bash
docker build -f docker/Dockerfile.libzmq -t libzmq:local .
```

### `mpd` container

#### Pulseaudio issue on Mac
Expand Down Expand Up @@ -286,7 +301,7 @@ Error starting userland proxy: listen tcp4 0.0.0.0:6600: bind: address already i

Read these threads for details: [thread 1](https://unix.stackexchange.com/questions/456909/socket-already-in-use-but-is-not-listed-mpd) and [thread 2](https://stackoverflow.com/questions/5106674/error-address-already-in-use-while-binding-socket-with-address-but-the-port-num/5106755#5106755)

#### Other error messages
#### MPD issues

When starting the `mpd` container, you will see the following errors.
You can ignore them, MPD will run.
Expand All @@ -309,7 +324,7 @@ mpd | alsa_mixer: snd_mixer_handle_events() failed: Input/output error
mpd | exception: Failed to read mixer for 'My ALSA Device': snd_mixer_handle_events() failed: Input/output error
```

### `jukebox` container
#### `jukebox` container

Many features of the Phoniebox are based on the Raspberry Pi hardware.
This hardware can\'t be mocked in a virtual Docker environment. As a
Expand Down
3 changes: 2 additions & 1 deletion documentation/developers/status.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ Topics marked _in progress_ are already in the process of implementation by comm
- [x] Publish mechanism of timer status
- [x] Change multitimer function call interface such that endless timer etc. won't pass the `iteration` kwarg
- [ ] Make timer settings persistent
- [ ] Idle timer
- [x] Idle timer (basic implementation covering player, SSH, config and audio content changes)
- [ ] Idle timer: Do we need further extensions?
- This needs clearer specification: Idle is when no music is playing and no user interaction is taking place
- i.e., needs information from RPC AND from player status. Let's do this when we see a little clearer about Spotify

Expand Down
43 changes: 25 additions & 18 deletions installation/includes/02_helpers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -75,35 +75,42 @@ get_architecture() {
echo $arch
}

is_raspbian() {
if [[ $( . /etc/os-release; printf '%s\n' "$ID"; ) == *"raspbian"* ]]; then
is_debian_based() {
local os_release_id=$( . /etc/os-release; printf '%s\n' "$ID"; )
if [[ "$os_release_id" == *"raspbian"* ]] || [[ "$os_release_id" == *"debian"* ]]; then
echo true
else
echo false
fi
}

get_debian_version_number() {
source /etc/os-release
echo "$VERSION_ID"
_get_debian_version_number() {
if [ "$(is_debian_based)" = true ]; then
local debian_version_number=$( . /etc/os-release; printf '%s\n' "$VERSION_ID"; )
echo "$debian_version_number"
else
echo "-1"
fi
}

is_debian_version_at_least() {
local expected_version=$1
local debian_version_number=$(get_debian_version_number)

if [ "$debian_version_number" -ge "$expected_version" ]; then
echo true
else
echo false
fi
}

_get_boot_file_path() {
local filename="$1"
if [ "$(is_raspbian)" = true ]; then
local debian_version_number=$(get_debian_version_number)

# Bullseye and lower
if [ "$debian_version_number" -le 11 ]; then
echo "/boot/${filename}"
# Bookworm and higher
elif [ "$debian_version_number" -ge 12 ]; then
echo "/boot/firmware/${filename}"
else
echo "unknown"
fi
local is_debian_version_number_at_least_12=$(is_debian_version_at_least 12)
if [ "$(is_debian_version_number_at_least_12)" = true ]; then
echo "/boot/firmware/${filename}"
else
echo "unknown"
echo "/boot/${filename}"
fi
}

Expand Down
18 changes: 0 additions & 18 deletions installation/install-jukebox.sh
Original file line number Diff line number Diff line change
Expand Up @@ -86,23 +86,6 @@ Check install log for details:"
exit 1
}

# Check if current distro is a 32-bit version
# Support for 64-bit Distros has not been checked (or precisely: is known not to work)
# All Raspberry Pi OS versions report as machine "armv6l" or "armv7l", if 32-bit (even the ARMv8 cores!)
_check_os_type() {
local os_type=$(uname -m)

print_lc "\nChecking OS type '$os_type'"

if [[ $os_type == "armv7l" || $os_type == "armv6l" ]]; then
print_lc " ... OK!\n"
else
print_lc "ERROR: Only 32-bit operating systems are supported. Please use a 32-bit version of Raspberry Pi OS!"
print_lc "For Pi 4 models or newer running a 64-bit kernels, also see this: https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2041"
exit 1
fi
}

_check_existing_installation() {
if [[ -e "${INSTALLATION_PATH}" ]]; then
print_lc "
Expand Down Expand Up @@ -154,7 +137,6 @@ _load_sources() {
_setup_logging

### CHECK PREREQUISITE
_check_os_type
_check_existing_installation

### RUN INSTALLATION
Expand Down
4 changes: 4 additions & 0 deletions resources/default-settings/jukebox.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ gpioz:
enable: false
config_file: ../../shared/settings/gpio.yaml
timers:
idle_shutdown:
# If you want the box to shutdown on inactivity automatically, configure timeout_sec with a number of seconds (at least 60).
# Inactivity is defined as: no music playing, no active SSH sessions, no changes in configs or audio content.
timeout_sec: 0
# These timers are always disabled after start
# The following values only give the default values.
# These can be changed when enabling the respective timer on a case-by-case basis w/o saving
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
#!/usr/bin/env bash

source ../../../../../../installation/includes/02_helpers.sh

echo "Entering setup.inc.sh"

echo "Disabling login shell to be accessible over serial"
sudo raspi-config nonint do_serial 1

if [ "$(is_debian_version_at_least 12)" = true ]; then
sudo raspi-config nonint do_serial_hw 1
sudo raspi-config nonint do_serial_cons 1
else
sudo raspi-config nonint do_serial 1
end

echo "Enabling serial port hardware"
sudo raspi-config nonint set_config_var enable_uart 1 /boot/config.txt
Expand Down
57 changes: 29 additions & 28 deletions src/jukebox/components/timers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# RPi-Jukebox-RFID Version 3
# Copyright (c) See file LICENSE in project root folder

from jukebox.multitimer import (GenericTimerClass, GenericMultiTimerClass)
import logging
import jukebox.cfghandler
import jukebox.plugs as plugin
from jukebox.multitimer import GenericTimerClass
from .idle_shutdown_timer import IdleShutdownTimer
from .volume_fadeout_shutdown_timer import VolumeFadoutAndShutdown


logger = logging.getLogger('jb.timers')
Expand All @@ -24,35 +26,18 @@ def stop_player():
plugin.call_ignore_errors('player', 'ctrl', 'stop')


class VolumeFadeOutActionClass:
def __init__(self, iterations):
self.iterations = iterations
# Get the current volume, calculate step size
self.volume = plugin.call('volume', 'ctrl', 'get_volume')
self.step = float(self.volume) / iterations

def __call__(self, iteration):
self.volume = self.volume - self.step
logger.debug(f"Decrease volume to {self.volume} (Iteration index {iteration}/{self.iterations}-1)")
plugin.call_ignore_errors('volume', 'ctrl', 'set_volume', args=[int(self.volume)])
if iteration == 0:
logger.debug("Shut down from volume fade out")
plugin.call_ignore_errors('host', 'shutdown')


# ---------------------------------------------------------------------------
# Create the timers
# ---------------------------------------------------------------------------
timer_shutdown: GenericTimerClass
timer_stop_player: GenericTimerClass
timer_fade_volume: GenericMultiTimerClass
timer_fade_volume: VolumeFadoutAndShutdown
timer_idle_shutdown: IdleShutdownTimer


@plugin.finalize
def finalize():
# TODO: Example with how to call the timers from RPC?

# Create the various timers with fitting doc for plugin reference
# Shutdown Timer
global timer_shutdown
timeout = cfg.setndefault('timers', 'shutdown', 'default_timeout_sec', value=60 * 60)
timer_shutdown = GenericTimerClass(f"{plugin.loaded_as(__name__)}.timer_shutdown",
Expand All @@ -62,21 +47,26 @@ def finalize():
# auto-registration would register it with that module. Manually set package to this plugin module
plugin.register(timer_shutdown, name='timer_shutdown', package=plugin.loaded_as(__name__))

# Stop Playback Timer
global timer_stop_player
timeout = cfg.setndefault('timers', 'stop_player', 'default_timeout_sec', value=60 * 60)
timer_stop_player = GenericTimerClass(f"{plugin.loaded_as(__name__)}.timer_stop_player",
timeout, stop_player)
timer_stop_player.__doc__ = "Timer for automatic player stop"
plugin.register(timer_stop_player, name='timer_stop_player', package=plugin.loaded_as(__name__))

global timer_fade_volume
timeout = cfg.setndefault('timers', 'volume_fade_out', 'default_time_per_iteration_sec', value=15 * 60)
steps = cfg.setndefault('timers', 'volume_fade_out', 'number_of_steps', value=10)
timer_fade_volume = GenericMultiTimerClass(f"{plugin.loaded_as(__name__)}.timer_fade_volume",
steps, timeout, VolumeFadeOutActionClass)
timer_fade_volume.__doc__ = "Timer step-wise volume fade out and shutdown"
# Volume Fadeout and Shutdown Timer
timer_fade_volume = VolumeFadoutAndShutdown(
name=f"{plugin.loaded_as(__name__)}.timer_fade_volume"
)
plugin.register(timer_fade_volume, name='timer_fade_volume', package=plugin.loaded_as(__name__))

# Idle Timer
global timer_idle_shutdown
idle_timeout = cfg.setndefault('timers', 'idle_shutdown', 'timeout_sec', value=0)
timer_idle_shutdown = IdleShutdownTimer(package=plugin.loaded_as(__name__), idle_timeout=idle_timeout)
plugin.register(timer_idle_shutdown, name='timer_idle_shutdown', package=plugin.loaded_as(__name__))

# The idle Timer does work in a little sneaky way
# Idle is when there are no calls through the plugin module
# Ahh, but also when music is playing this is not idle...
Expand All @@ -101,4 +91,15 @@ def atexit(**ignored_kwargs):
timer_stop_player.cancel()
global timer_fade_volume
timer_fade_volume.cancel()
return [timer_shutdown.timer_thread, timer_stop_player.timer_thread, timer_fade_volume.timer_thread]
global timer_idle_shutdown
timer_idle_shutdown.cancel()
global timer_idle_check
timer_idle_check.cancel()
ret = [
timer_shutdown.timer_thread,
timer_stop_player.timer_thread,
timer_fade_volume.timer_thread,
timer_idle_shutdown.timer_thread,
timer_idle_check.timer_thread
]
return ret
Loading

0 comments on commit 369776a

Please sign in to comment.