Skip to content
This repository was archived by the owner on Aug 16, 2026. It is now read-only.
This repository was archived by the owner on Aug 16, 2026. It is now read-only.

Mini Flip: Settings volume is capped at 10 while side keys use 0–20 #237

Description

@dime-online

Device / version

  • Miyoo Mini Flip
  • SprigUI v1.3.1 (68504009)
  • Also present on current development (63c7b338)

Reproduction

  1. Open Home → Settings → Volume.
  2. Press Right repeatedly. The displayed value stops at 10.
  3. Use the physical side volume buttons; that path can reach 20.
  4. With the side-button value at 20, press Left once in Settings.

Actual behavior

  • Home Settings is capped at 10.
  • A Settings adjustment can apply two hardware levels for one displayed step.
  • Pressing Left from a side-button value of 20 can collapse it to 10 instead of 19.
  • PyUI startup can rewrite stored values 11–20 back to 10.

Expected behavior

  • Settings and the side buttons should use the same 0–20 range.
  • Each input should change one stored/hardware level.
  • 20 → Left should produce 19.
  • Stored values 11–20 should survive PyUI startup.

Root cause

The rest of Sprig uses a stored/hardware range of 0–20:

  • SystemConfig.get_volume() maps stored volume to PyUI with * 5, and set_volume() maps it back with // 5:

    def get_volume(self):
    return self.config.get("vol", 0) * 5

    def set_volume(self, value):
    if(value == 0):
    self.config["mute"] = 1
    else:
    self.config["mute"] = 0
    self.config["vol"] = value //5

  • The generic Settings row changes PyUI volume by 5 and displays get_volume() // 5:

    def volume_adjust(self, input: ControllerInput):
    if(ControllerInput.DPAD_LEFT == input):
    Device.change_volume(-5)
    elif(ControllerInput.L1 == input):
    Device.change_volume(-5)
    elif(ControllerInput.DPAD_RIGHT == input):
    Device.change_volume(+5)
    elif(ControllerInput.R1 == input):
    Device.change_volume(+5)

    if(Device.supports_volume()):
    option_list.append(
    GridOrListEntry(
    primary_text=Language.volume(),
    value_text="< " + str(Device.get_volume()//5) + " >",
    image_path=None,
    image_path_selected=None,
    description=None,
    icon=None,
    value=self.volume_adjust
    )

  • The shell/side-button path directly uses stored levels 0–20:

    ########## VOLUME CONTROL ##########
    MIN_RAW_VOLUME=-60
    MAX_RAW_VOLUME=30
    MAX_VOLUME=20
    # Set volume level (0-20)
    # Usage: set_volume 15
    set_volume() {
    local volume="$1"
    # Clamp volume between 0 and MAX_VOLUME
    [ "$volume" -lt 0 ] && volume=0
    [ "$volume" -gt "$MAX_VOLUME" ] && volume="$MAX_VOLUME"
    # Save volume to config
    set_pyui_config_value ".vol" "$volume"
    # Calculate raw volume using logarithmic curve
    local volume_raw=0
    if [ "$volume" -ne 0 ]; then
    # Using integer arithmetic: volume_raw = round(48 * log10(1 + volume))
    # Approximation using lookup table for integer math
    case "$volume" in
    1) volume_raw=-44 ;;
    2) volume_raw=-37 ;;
    3) volume_raw=-32 ;;
    4) volume_raw=-29 ;;
    5) volume_raw=-26 ;;
    6) volume_raw=-24 ;;
    7) volume_raw=-22 ;;
    8) volume_raw=-20 ;;
    9) volume_raw=-18 ;;
    10) volume_raw=-17 ;;
    11) volume_raw=-15 ;;
    12) volume_raw=-14 ;;
    13) volume_raw=-13 ;;
    14) volume_raw=-12 ;;
    15) volume_raw=-11 ;;
    16) volume_raw=-10 ;;
    17) volume_raw=-9 ;;
    18) volume_raw=-8 ;;
    19) volume_raw=-7 ;;
    20) volume_raw=-6 ;;
    *) volume_raw="$MIN_RAW_VOLUME" ;;
    esac
    else
    volume_raw="$MIN_RAW_VOLUME"
    fi
    # Apply volume using hardware interface
    set_volume_raw "$volume_raw"
    log_message "Volume set to $volume (raw: ${volume_raw}dB)" -v
    }
    # Set raw hardware volume
    # Usage: set_volume_raw -20
    set_volume_raw() {
    local volume_raw="$1"
    # Clamp to hardware limits
    [ "$volume_raw" -lt "$MIN_RAW_VOLUME" ] && volume_raw="$MIN_RAW_VOLUME"
    [ "$volume_raw" -gt "$MAX_RAW_VOLUME" ] && volume_raw="$MAX_RAW_VOLUME"
    # Set volume via hardware interface
    if [ -e /proc/mi_modules/mi_ao/mi_ao0 ]; then
    echo "set_ao_volume 0 ${volume_raw}dB" > /proc/mi_modules/mi_ao/mi_ao0 2>/dev/null
    echo "set_ao_volume 1 ${volume_raw}dB" > /proc/mi_modules/mi_ao/mi_ao0 2>/dev/null
    # Handle mute state
    if [ "$volume_raw" -le "$MIN_RAW_VOLUME" ]; then
    echo "set_ao_mute 1" > /proc/mi_modules/mi_ao/mi_ao0 2>/dev/null
    else
    echo "set_ao_mute 0" > /proc/mi_modules/mi_ao/mi_ao0 2>/dev/null
    fi
    fi
    }
    # Increase volume
    # Usage: volume_up
    volume_up() {
    local current_volume
    current_volume=$(get_pyui_config_value ".vol" 10)
    local new_volume=$((current_volume + 1))
    set_volume "$new_volume"
    }
    # Decrease volume
    # Usage: volume_down
    volume_down() {
    local current_volume
    current_volume=$(get_pyui_config_value ".vol" 10)
    local new_volume=$((current_volume - 1))
    set_volume "$new_volume"

Only SprigMiyooMiniCommon clamps PyUI to 0–50 and rescales that to hardware 0–20:

def change_volume(self, amount):
"""Override to handle Sprig's 0-50 volume range (displays as 0-10)"""
from display.display import Display
# Get current volume (0-50 range for display as 0-10)
volume = self.get_volume() + amount
# Clamp to 0-50 for Sprig
if volume < 0:
volume = 0
elif volume > 50:
volume = 50
self._set_volume(volume)
Display.volume_changed(self.get_volume())
PyUiLogger.get_logger().info(f"Volume changed by {amount} to {volume}")
def _set_volume(self, volume: int) -> int:
"""Set volume using direct hardware control for instant feedback"""
try:
# Clamp volume between 0 and 50 (displays as 0-10)
volume = max(0, min(50, volume))
# Scale from 0-50 to 0-20 for hardware
volume_hw = int(volume * 20 / 50) # Maps 0-50 to 0-20
# Hardware volume mapping (same as helperFunctions.sh)
volume_map = {
0: -60, 1: -44, 2: -37, 3: -32, 4: -29, 5: -26,
6: -24, 7: -22, 8: -20, 9: -18, 10: -17, 11: -15,
12: -14, 13: -13, 14: -12, 15: -11, 16: -10,
17: -9, 18: -8, 19: -7, 20: -6
}
volume_raw = volume_map.get(volume_hw, -60)
try:
with open("/proc/mi_modules/mi_ao/mi_ao0", "w") as f:
f.write(f"set_ao_volume 0 {volume_raw}dB\n")
f.write(f"set_ao_volume 1 {volume_raw}dB\n")
# Handle mute state
if volume_raw <= -60:
f.write("set_ao_mute 1\n")
else:
f.write("set_ao_mute 0\n")
except Exception as e:
PyUiLogger.get_logger().warning(f"Direct hardware control failed, falling back to shell: {e}")
# Fallback if hw access fails
subprocess.run([
"/bin/sh", "-c",
f". /mnt/SDCARD/sprig/helperFunctions.sh && set_volume {volume_hw}"
], check=False)
# Update system config
self.system_config.set_volume(volume)
self.system_config.save_config()
PyUiLogger.get_logger().info(f"Sprig volume set to {volume} (hw:{volume_hw}, raw:{volume_raw}dB)")

That 0–50 adapter conflicts with the surrounding 0–100 PyUI ↔ 0–20 stored-volume contract.

Minimal fix

The fix can stay isolated to the Mini Flip adapter:

  • Clamp PyUI volume to 0–100 in change_volume() and _set_volume().
  • Convert PyUI to stored/hardware volume with volume_hw = volume // 5.
  • Keep the existing 0–20 hardware table, mute-at-zero behavior, and SystemConfig persistence.

I tested that one-file correction on hardware across all 21 levels, both clamps, 20 → 19, side-button/Settings interoperability, and persistence through a reboot. The next boot initialized at 20 as expected.

There are also two stale fallback paths in the same class: they source /mnt/SDCARD/sprig/helperFunctions.sh, while the installed helper is /mnt/SDCARD/sprig/scripts/helperFunctions.sh.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions