onboard audio: JMISC HPH wiring, UCM fix, update scripts for git deploy
This commit is contained in:
@@ -5,3 +5,4 @@ __pycache__/
|
||||
*.log
|
||||
/tmp/
|
||||
build/
|
||||
.env
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
# Run with: just <recipe>
|
||||
# Override board host: just BOARD_HOST=192.168.1.100 deploy
|
||||
|
||||
BOARD_HOST := env_var_or_default('BOARD_HOST', 'uno-q.local')
|
||||
set dotenv-load := true
|
||||
|
||||
BOARD_HOST := env_var_or_default('BOARD_HOST', '192.168.9.167')
|
||||
BOARD_USER := env_var_or_default('BOARD_USER', 'arduino')
|
||||
BOARD_PASS := env_var_or_default('BOARD_PASS', 'arduino')
|
||||
SSH_KEY := env_var_or_default('SSH_KEY', '/home/david/.ssh/unoq_deploy_key')
|
||||
@@ -154,17 +156,18 @@ enable-host-mode: deploy-midi-bridge deploy-led-matrix-viz
|
||||
echo '[OK] Host mode services enabled and started'"
|
||||
|
||||
# Install all systemd services
|
||||
install-services:
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
SCP_OPTS="{{SSH_OPTS}}"
|
||||
echo "[INSTALL] Services on {{BOARD_USER}}@{{BOARD_HOST}}"
|
||||
for svc in usb-role-detect usb-gadget-audio pd-synth-auto pd-synth detect-i2s-device pd-synth-i2s; do
|
||||
scp $SCP_OPTS "system/$svc.service" "{{BOARD_USER}}@{{BOARD_HOST}}:/tmp/"
|
||||
scp $SCP_OPTS "system/${svc}.service" "{{BOARD_USER}}@{{BOARD_HOST}}:/tmp/"
|
||||
done
|
||||
scp $SCP_OPTS system/99-usb-role.rules "{{BOARD_USER}}@{{BOARD_HOST}}:/tmp/"
|
||||
ssh $SCP_OPTS "{{BOARD_USER}}@{{BOARD_HOST}}" "\
|
||||
for svc in usb-role-detect usb-gadget-audio pd-synth-auto pd-synth detect-i2s-device pd-synth-i2s; do \
|
||||
sudo mv /tmp/\$svc.service /etc/systemd/system/; \
|
||||
sudo mv /tmp/\${svc}.service /etc/systemd/system/; \
|
||||
done && \
|
||||
sudo mv /tmp/99-usb-role.rules /etc/udev/rules.d/ && \
|
||||
sudo udevadm control --reload-rules && \
|
||||
@@ -219,6 +222,27 @@ run-remote:
|
||||
echo "[RUN] Starting Pd on {{BOARD_USER}}@{{BOARD_HOST}} (auto-detect)"
|
||||
ssh $SCP_OPTS -t "{{BOARD_USER}}@{{BOARD_HOST}}" "killall pd 2>/dev/null; sudo {{SYSTEM_DIR}}/bin/start-synth.sh --auto --verbose"
|
||||
|
||||
# Fix UCM bug: removes nonexistent "RX HPH Mode" control
|
||||
# that prevents PipeWire from enabling the Headphones UCM device.
|
||||
# Must run once after OS updates or first install.
|
||||
fix-ucm:
|
||||
@ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "\
|
||||
if grep -q 'RX HPH Mode' /usr/share/alsa/ucm2/codecs/qcom-lpass/rx-macro/HeadphoneEnableSeq.conf 2>/dev/null; then \
|
||||
echo '{{BOARD_PASS}}' | sudo -S sed -i '/RX HPH Mode/d' /usr/share/alsa/ucm2/codecs/qcom-lpass/rx-macro/HeadphoneEnableSeq.conf && \
|
||||
sudo systemctl restart wireplumber && \
|
||||
echo '[OK] UCM HeadphoneEnableSeq patched'; \
|
||||
else \
|
||||
echo '[OK] UCM already patched'; \
|
||||
fi"
|
||||
|
||||
# Test onboard headphone audio (hw:0,0 — ArduinoImolaHPH)
|
||||
test-audio-headphones:
|
||||
@ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "\
|
||||
systemctl --user stop pipewire.socket pipewire.service wireplumber 2>/dev/null; \
|
||||
sleep 0.5; \
|
||||
speaker-test -t sine -f 440 -l 2 -D hw:0,0 2>&1 | tail -3; \
|
||||
systemctl --user start pipewire.socket pipewire.service wireplumber 2>/dev/null"
|
||||
|
||||
# Test audio (gadget mode)
|
||||
test-audio:
|
||||
@ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "speaker-test -D hw:UAC1Gadget,0 -c 2 -t sine -f 440 -l 1"
|
||||
@@ -233,6 +257,11 @@ test-audio-host:
|
||||
echo '[ERROR] No USB audio interface found'; \
|
||||
fi"
|
||||
|
||||
# LED matrix end-to-end diagnostic (router → MCU → UDP → viz)
|
||||
diag-led:
|
||||
@scp {{SSH_OPTS}} scripts/diag-led.sh "{{BOARD_USER}}@{{BOARD_HOST}}:/tmp/diag-led.sh"
|
||||
@ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "chmod +x /tmp/diag-led.sh && bash /tmp/diag-led.sh"
|
||||
|
||||
# Deploy diagnostic script to board
|
||||
deploy-diag:
|
||||
@scp {{SSH_OPTS}} scripts/diag.sh "{{BOARD_USER}}@{{BOARD_HOST}}:{{SYSTEM_DIR}}/bin/diag.sh"
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
just = "latest"
|
||||
|
||||
[env]
|
||||
BOARD_HOST = "uno-q.local"
|
||||
BOARD_HOST = "192.168.9.167"
|
||||
BOARD_USER = "arduino"
|
||||
BOARD_PASS = "arduino"
|
||||
SSH_KEY = "/home/david/.ssh/unoq_deploy_key"
|
||||
PD_PATCH = "synth.pd"
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
#!/bin/bash
|
||||
# diag-led.sh — LED matrix end-to-end diagnostic
|
||||
# Run on the board: just diag-led
|
||||
# Tests: router → MCU serial → draw_frame → OK
|
||||
# UDP 8082 → viz process → rchar increase
|
||||
set -euo pipefail
|
||||
|
||||
PASS=0; FAIL=0
|
||||
|
||||
pass() { echo " ✓ $*"; PASS=$((PASS+1)); }
|
||||
fail() { echo " ✗ $*"; FAIL=$((FAIL+1)); }
|
||||
|
||||
echo "=== LED Matrix Diagnostic ==="
|
||||
echo ""
|
||||
|
||||
# --- Test 1: Router socket ---
|
||||
echo "1. Router socket"
|
||||
if [ -S /var/run/arduino-router.sock ]; then
|
||||
pass "socket /var/run/arduino-router.sock exists"
|
||||
else
|
||||
fail "router socket missing"
|
||||
fi
|
||||
|
||||
# --- Test 2: arduino-router service ---
|
||||
echo "2. arduino-router service"
|
||||
if systemctl is-active arduino-router &>/dev/null; then
|
||||
pass "arduino-router is running"
|
||||
else
|
||||
fail "arduino-router not running"
|
||||
fi
|
||||
|
||||
# --- Test 3: RPC connectivity ---
|
||||
echo "3. Router RPC connectivity"
|
||||
RPC_RESPONSE=$(python3 -c "
|
||||
import socket, msgpack
|
||||
try:
|
||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
s.settimeout(3)
|
||||
s.connect('/var/run/arduino-router.sock')
|
||||
p = msgpack.Packer()
|
||||
s.sendall(p.pack([0, 99, 'get_status', ['']]))
|
||||
s.settimeout(1)
|
||||
resp = s.recv(256)
|
||||
s.close()
|
||||
print('OK:' + repr(resp[:60]))
|
||||
except Exception as e:
|
||||
print('FAIL:' + str(e))
|
||||
" 2>&1)
|
||||
if echo "$RPC_RESPONSE" | grep -q "^OK:"; then
|
||||
pass "get_status responded"
|
||||
else
|
||||
fail "get_status: $RPC_RESPONSE"
|
||||
fi
|
||||
|
||||
# --- Test 4: draw_frame ---
|
||||
echo "4. draw_frame RPC"
|
||||
FRAME_RESPONSE=$(python3 -c "
|
||||
import socket, msgpack, struct
|
||||
try:
|
||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
s.settimeout(3)
|
||||
s.connect('/var/run/arduino-router.sock')
|
||||
p = msgpack.Packer()
|
||||
# Alternating columns test pattern
|
||||
bits = 0
|
||||
for row in range(8):
|
||||
for col in range(0, 13, 2):
|
||||
bits |= 1 << (row * 13 + col)
|
||||
f0 = bits & 0xFFFFFFFF
|
||||
f1 = (bits >> 32) & 0xFFFFFFFF
|
||||
f2 = (bits >> 64) & 0xFFFFFFFF
|
||||
f3 = (bits >> 96) & 0xFFFFFFFF
|
||||
s.sendall(p.pack([0, 100, 'draw_frame', [str(f0), str(f1), str(f2), str(f3)]]))
|
||||
s.settimeout(1)
|
||||
resp = s.recv(128)
|
||||
s.close()
|
||||
if b'OK' in resp:
|
||||
print('OK')
|
||||
else:
|
||||
print('FAIL:' + repr(resp[:50]))
|
||||
except Exception as e:
|
||||
print('FAIL:' + str(e))
|
||||
" 2>&1)
|
||||
if echo "$FRAME_RESPONSE" | grep -q "^OK"; then
|
||||
pass "draw_frame returned OK"
|
||||
else
|
||||
fail "draw_frame: $FRAME_RESPONSE"
|
||||
fi
|
||||
|
||||
# --- Test 5: led-matrix-viz service ---
|
||||
echo "5. led-matrix-viz service"
|
||||
if systemctl is-active led-matrix-viz &>/dev/null; then
|
||||
pass "led-matrix-viz is running"
|
||||
else
|
||||
fail "led-matrix-viz not running"
|
||||
fi
|
||||
|
||||
# --- Test 6: UDP receive (send note, check rchar) ---
|
||||
echo "6. UDP → viz → router (end-to-end)"
|
||||
VIZ_PID=$(pgrep -f 'python3.*led-matrix-viz' 2>/dev/null | head -1)
|
||||
if [ -z "$VIZ_PID" ]; then
|
||||
fail "viz process not found"
|
||||
else
|
||||
R1=$(sudo cat /proc/$VIZ_PID/io 2>/dev/null | grep rchar | awk '{print $2}')
|
||||
W1=$(sudo cat /proc/$VIZ_PID/io 2>/dev/null | grep wchar | awk '{print $2}')
|
||||
|
||||
python3 -c "
|
||||
import socket
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
for n in [60, 64, 67, 72, 76, 79, 84]:
|
||||
s.sendto(f'midi 144 {n} 100;'.encode(), ('127.0.0.1', 8082))
|
||||
s.close()
|
||||
" 2>/dev/null
|
||||
|
||||
sleep 2
|
||||
|
||||
R2=$(sudo cat /proc/$VIZ_PID/io 2>/dev/null | grep rchar | awk '{print $2}')
|
||||
W2=$(sudo cat /proc/$VIZ_PID/io 2>/dev/null | grep wchar | awk '{print $2}')
|
||||
|
||||
RDIFF=$((R2 - R1))
|
||||
WDIFF=$((W2 - W1))
|
||||
|
||||
if [ "$RDIFF" -gt 0 ]; then
|
||||
pass "viz received +${RDIFF}r bytes, sent +${WDIFF}w bytes to router"
|
||||
else
|
||||
fail "viz rchar unchanged — not receiving UDP (r=$RDIFF, w=$WDIFF)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Test 7: f_midi gadget ---
|
||||
echo "7. f_midi gadget status"
|
||||
if [ -f /proc/asound/card2/midi0 ]; then
|
||||
RX=$(grep "Rx bytes" /proc/asound/card2/midi0 | awk '{print $4}')
|
||||
pass "f_midi present (Rx=${RX} bytes)"
|
||||
else
|
||||
fail "f_midi gadget not found"
|
||||
fi
|
||||
|
||||
# --- Test 8: midi-bridge forwarding ---
|
||||
echo "8. midi-bridge service"
|
||||
if systemctl is-active midi-bridge &>/dev/null; then
|
||||
MB_PID=$(pgrep -f 'python3.*midi-bridge' | head -1)
|
||||
pass "midi-bridge running (PID=$MB_PID)"
|
||||
else
|
||||
fail "midi-bridge not running"
|
||||
fi
|
||||
|
||||
# --- Clear matrix, summary ---
|
||||
python3 -c "
|
||||
import socket, msgpack
|
||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
s.settimeout(3)
|
||||
s.connect('/var/run/arduino-router.sock')
|
||||
p = msgpack.Packer()
|
||||
s.sendall(p.pack([0, 200, 'clear', []]))
|
||||
s.recv(128)
|
||||
s.close()
|
||||
" 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "=============================="
|
||||
if [ "$FAIL" -eq 0 ]; then
|
||||
echo " ALL CHECKS PASSED ($PASS/$((PASS+FAIL)))"
|
||||
echo " LED matrix chain is healthy."
|
||||
else
|
||||
echo " $PASS passed, $FAIL failed"
|
||||
fi
|
||||
echo "=============================="
|
||||
exit $FAIL
|
||||
@@ -26,7 +26,6 @@ def main():
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.bind(("0.0.0.0", VIZ_PORT))
|
||||
sock.setblocking(False)
|
||||
|
||||
router = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
router.settimeout(2)
|
||||
|
||||
+11
-61
@@ -1,17 +1,23 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
PD_DIR="/home/arduino/pd"
|
||||
REPO_DIR="/home/arduino/uno-q-audio-synth"
|
||||
PD_DIR="$REPO_DIR/pd"
|
||||
PATCH="$PD_DIR/synth.pd"
|
||||
MODE="usb"
|
||||
VERBOSE=false
|
||||
|
||||
# Fallback to legacy path if REPO_DIR not cloned
|
||||
[ -d "$PD_DIR" ] || PD_DIR="/home/arduino/pd"
|
||||
PATCH="$PD_DIR/synth.pd"
|
||||
|
||||
die() { echo "[ERROR] $*" >&2; exit 1; }
|
||||
info() { echo "[INFO] $*"; }
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-a|--auto) MODE="auto"; shift ;;
|
||||
-o|--onboard) MODE="onboard"; shift ;;
|
||||
-i|--i2s) MODE="i2s"; shift ;;
|
||||
-l|--loopback) MODE="loopback"; shift ;;
|
||||
-v|--verbose) VERBOSE=true; shift ;;
|
||||
@@ -30,53 +36,7 @@ fi
|
||||
|
||||
# --- Auto-detect mode ---
|
||||
if [ "$MODE" = "auto" ]; then
|
||||
if [ -f /run/usb-role ]; then
|
||||
ROLE=$(cat /run/usb-role)
|
||||
else
|
||||
ROLE=$(/usr/local/bin/detect-usb-role.sh 2>/dev/null && cat /run/usb-role || echo "gadget")
|
||||
fi
|
||||
|
||||
case "$ROLE" in
|
||||
gadget)
|
||||
MODE="usb"
|
||||
if ! aplay -l 2>/dev/null | grep -q "UAC1Gadget"; then
|
||||
info "Configuring USB gadget..."
|
||||
/usr/local/bin/configure-usb-audio.sh start 2>/dev/null || true
|
||||
sleep 1
|
||||
fi
|
||||
;;
|
||||
host)
|
||||
if [ -f /run/usb-audio-dev ] && [ -s /run/usb-audio-dev ]; then
|
||||
ALSA_DEV=$(cat /run/usb-audio-dev)
|
||||
else
|
||||
info "Detecting USB audio interface..."
|
||||
/usr/local/bin/configure-usb-host.sh 2>/dev/null || {
|
||||
die "No USB audio interface found. Plug one in via hub."
|
||||
}
|
||||
ALSA_DEV=$(cat /run/usb-audio-dev)
|
||||
fi
|
||||
info "Mode: USB host (MIDI controller + USB audio interface)"
|
||||
# MIDI handled by midi-bridge.service → UDP → Pd netreceive
|
||||
AUDIO_OPTS=(-audiooutdev 4 -audioindev 4)
|
||||
PD_ARGS=(
|
||||
-nogui -alsa
|
||||
-send "pd dsp 1"
|
||||
"${AUDIO_OPTS[@]}"
|
||||
-audiobuf 20 -blocksize 64 -r 48000
|
||||
-channels 2 -inchannels 0 -outchannels 2
|
||||
-path "$PD_DIR" "$PATCH"
|
||||
)
|
||||
if [ "$VERBOSE" = true ]; then pd "${PD_ARGS[@]}"
|
||||
else pd "${PD_ARGS[@]}" > /dev/null 2>&1 &
|
||||
PID=$!; info "Pd started (PID: $PID)"; info " Stop: kill $PID"
|
||||
fi
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
info "Auto-detect: unknown role '$ROLE', falling back to gadget"
|
||||
MODE="usb"
|
||||
;;
|
||||
esac
|
||||
MODE="onboard"
|
||||
fi
|
||||
|
||||
# --- Configure per-mode options ---
|
||||
@@ -93,19 +53,9 @@ if [ "$MODE" = "usb" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$MODE" = "i2s" ]; then
|
||||
if [ -f /run/i2s-audio-dev ] && [ -s /run/i2s-audio-dev ]; then
|
||||
ALSA_DEV=$(cat /run/i2s-audio-dev)
|
||||
else
|
||||
for dev in "hw:I2S,0" "hw:MI2S,0" "hw:0,0"; do
|
||||
if aplay -l 2>/dev/null | grep -qi "${dev%,*}"; then
|
||||
ALSA_DEV="$dev"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
info "Mode: I2S DAC (standalone analog audio)"
|
||||
AUDIO_OPTS=(-audioadddev "$ALSA_DEV")
|
||||
if [ "$MODE" = "onboard" ] || [ "$MODE" = "i2s" ]; then
|
||||
info "Mode: Onboard audio (PMIC headphone / line out on JMISC)"
|
||||
AUDIO_OPTS=(-audiooutdev 0 -inchannels 0 -outchannels 1)
|
||||
fi
|
||||
|
||||
if [ "$MODE" = "loopback" ]; then
|
||||
|
||||
@@ -4,7 +4,7 @@ Before=pd-synth-i2s.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/detect-i2s-device.sh
|
||||
ExecStart=/home/arduino/uno-q-audio-synth/scripts/detect-i2s-device.sh
|
||||
RemainAfterExit=yes
|
||||
|
||||
[Install]
|
||||
|
||||
@@ -5,7 +5,7 @@ After=arduino-router.service
|
||||
[Service]
|
||||
User=arduino
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/led-matrix-viz.py
|
||||
ExecStart=/home/arduino/uno-q-audio-synth/scripts/led-matrix-viz.py
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
StandardOutput=journal
|
||||
|
||||
@@ -4,7 +4,7 @@ After=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/midi-bridge.py
|
||||
ExecStart=/home/arduino/uno-q-audio-synth/scripts/midi-bridge.py
|
||||
User=root
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
|
||||
@@ -6,7 +6,7 @@ Wants=usb-role-detect.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/start-synth.sh --auto --verbose
|
||||
ExecStart=/home/arduino/uno-q-audio-synth/scripts/start-synth.sh --auto --verbose
|
||||
ExecStop=/usr/bin/killall -s TERM pd
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
@@ -6,9 +6,9 @@ Wants=usb-gadget-audio.service
|
||||
[Service]
|
||||
Type=simple
|
||||
User=arduino
|
||||
ExecStart=/usr/local/bin/start-synth.sh --i2s
|
||||
ExecStart=/home/arduino/uno-q-audio-synth/scripts/start-synth.sh --i2s
|
||||
ExecStop=/usr/bin/killall pd
|
||||
WorkingDirectory=/home/arduino/pd
|
||||
WorkingDirectory=/home/arduino/uno-q-audio-synth
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ Wants=usb-gadget-audio.service midi-bridge.service
|
||||
[Service]
|
||||
Type=simple
|
||||
User=arduino
|
||||
ExecStart=/usr/local/bin/start-synth.sh
|
||||
ExecStart=/home/arduino/uno-q-audio-synth/scripts/start-synth.sh
|
||||
ExecStop=/usr/bin/killall pd
|
||||
WorkingDirectory=/home/arduino/pd
|
||||
WorkingDirectory=/home/arduino/uno-q-audio-synth
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ Before=pd-synth.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/configure-usb-audio.sh start
|
||||
ExecStop=/usr/local/bin/configure-usb-audio.sh stop
|
||||
ExecStart=/home/arduino/uno-q-audio-synth/scripts/configure-usb-audio.sh start
|
||||
ExecStop=/home/arduino/uno-q-audio-synth/scripts/configure-usb-audio.sh stop
|
||||
RemainAfterExit=yes
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
|
||||
@@ -6,7 +6,7 @@ Before=pd-synth-auto.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/detect-usb-role.sh
|
||||
ExecStart=/home/arduino/uno-q-audio-synth/scripts/detect-usb-role.sh
|
||||
RemainAfterExit=yes
|
||||
StandardOutput=journal
|
||||
|
||||
|
||||
Reference in New Issue
Block a user