Last active
November 4, 2025 22:34
-
-
Save ddlsmurf/05ee450043a2a3df29f798acdb93a0c8 to your computer and use it in GitHub Desktop.
piCorePlayer rudimentary MQTT control. Install `bash` extension, edit the variables, copy over by scp and set as user command
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| MQTT_HOST="192.168.1.1" | |
| MQTT_USER="username" | |
| MQTT_PASS="hunter2" | |
| MQTT_CLIENT_ID_PREFIX="pCP-" | |
| MQTT_PREFIX="piCorePlayer/instance1" | |
| MQTT_TOPIC_STATUS_PREFIX="$MQTT_PREFIX/status" | |
| MQTT_TOPIC_COMMAND="$MQTT_PREFIX/command" | |
| MQTT_TOPIC_MODE="$MQTT_TOPIC_STATUS_PREFIX/mode" | |
| MQTT_TOPIC_WILL="$MQTT_TOPIC_STATUS_PREFIX/LWT" | |
| MQTT_WILL_ONLINE="Online" | |
| MQTT_WILL_OFFLINE="Offline" | |
| MQTT_WILL_INIT="Starting..." | |
| POLL_MODE_PERIOD_S=2 | |
| RETRY_SUBSCRIBE_AFTER_S=10 | |
| # I need both, without this, if the first command after power on is 'pause', | |
| # then 'pause' commands after won't stop it again until the 'stop' command is used | |
| PAUSE_AT_STARTUP=true # note the pause command starts playing (with the 'pause' command) | |
| STOP_AT_STARTUP=true # if this is true also, the playing is very brief | |
| # Because there is currently no way to read the volume, if it is hardcoded here | |
| # the brief pause-stop at startup will be silent then set to this | |
| VOLUME_AT_STARTUP=90 | |
| ALLOWED_PCP_COMMANDS=",play,pause,stop,up,down,next,prev,rand,power,volume,mode,sd,rb," | |
| # - pcp play : play current track in playlist | |
| # - pcp stop : stop current track | |
| # - pcp pause : pause current track | |
| # - pcp up : volume up | |
| # - pcp down : volume down | |
| # - pcp next : next track | |
| # - pcp prev : previous track | |
| # - pcp rand : generate random playlist | |
| # - pcp power [on|off] : software power on or off | |
| # - pcp volume [0-100] : set volume between 0 to 100 | |
| # - pcp rescan : look for new and changed media files in connected LMS library | |
| # - pcp wipecache : clear connected LMS library and rescan | |
| # - pcp mode : display Squeezelite's current mode | |
| # - pcp bu : (b)ack(u)p | |
| # - pcp sd : (s)hut(d)own | |
| # - pcp bs : (b)ackup then (s)hutdown | |
| # - pcp rb : (r)e(b)oot | |
| # - pcp br : (b)ackup then (r)eboot | |
| # - pcp leds [on|off] : turn leds on or off | |
| # (incomplete: https://docs.picoreplayer.org/information/pcp_cli/ ) | |
| info() { echo "$@" >&2 ; } | |
| die() { info "$@" ; exit 1; } | |
| run_at_exit() { | |
| if [[ -z "${CLEANUP_FUNC_LIST:-}" ]]; then | |
| CLEANUP_FUNC_LIST="$@" | |
| __cleanup() { for cmd in $CLEANUP_FUNC_LIST ; do $cmd || echo "cleanup '$cmd' failed with $?" ; done ; } | |
| trap "__cleanup" EXIT INT TERM | |
| else | |
| CLEANUP_FUNC_LIST="$@ $CLEANUP_FUNC_LIST" | |
| fi | |
| } | |
| # mock pcp for local testing | |
| if ! which pcp > /dev/null ; then | |
| info "⚠️ pcp not found, using a mock" | |
| pcp() { | |
| case "$1" in | |
| mode) | |
| if [[ $SECONDS -lt 10 ]]; then | |
| info Yikes too early | |
| return 1 | |
| fi | |
| local idx="$(((SECONDS / 5) % 3))" | |
| local mode="stop" | |
| if [[ "$idx" == 0 ]]; then | |
| mode="pause" | |
| elif [[ "$idx" == 1 ]]; then | |
| mode="play" | |
| fi | |
| # info "🖲️ Execute pcp $1 - will be '$mode'" | |
| echo "$mode" | |
| ;; | |
| play|pause|stop|volume) | |
| info "!! Execute pcp $@" | |
| ;; | |
| *) | |
| info "Error: unknown pcp command: '$@'" | |
| return 1 | |
| ;; | |
| esac | |
| } | |
| fi | |
| publish() { | |
| local topic="$1" | |
| shift | |
| info "=> Publish '$1' to '$topic'" | |
| mosquitto_pub \ | |
| -h "$MQTT_HOST" \ | |
| -u "$MQTT_USER" \ | |
| -P "$MQTT_PASS" \ | |
| -I "$MQTT_CLIENT_ID_PREFIX" \ | |
| -t "$topic" \ | |
| -q 1 \ | |
| -m "$@" \ | |
| || info "Error: Failed to publish, ignoring" | |
| } | |
| start_updating_state() { | |
| ( | |
| previous="" | |
| while true ; do | |
| sleep "$POLL_MODE_PERIOD_S" | |
| local state | |
| if ! state="$(pcp mode 2>&1)" ; then | |
| state="Failed: $?: $state" | |
| fi | |
| if [[ "$previous" != "$state" ]]; then | |
| previous="$state" | |
| publish "$MQTT_TOPIC_MODE" "$state" | |
| fi | |
| done | |
| )& | |
| PID="$!" | |
| stop_updating_state() { | |
| kill "$PID" | |
| } | |
| run_at_exit stop_updating_state | |
| } | |
| publish_LWT_offline() { | |
| publish "$MQTT_TOPIC_WILL" "$MQTT_WILL_OFFLINE" --retain | |
| publish "$MQTT_TOPIC_MODE" "" | |
| } | |
| subscribe_to_commands() { | |
| info "<= Subscribing to '$MQTT_TOPIC_COMMAND'..." | |
| mosquitto_sub \ | |
| -h "$MQTT_HOST" \ | |
| -u "$MQTT_USER" \ | |
| -P "$MQTT_PASS" \ | |
| -I "$MQTT_CLIENT_ID_PREFIX" \ | |
| -t "$MQTT_TOPIC_COMMAND" \ | |
| -q 2 \ | |
| --will-retain \ | |
| --will-payload "$MQTT_WILL_OFFLINE" \ | |
| --will-qos 1 \ | |
| --will-topic "$MQTT_TOPIC_WILL" \ | |
| | while read -r CMD ARG REST ; do | |
| [[ -n "$CMD" ]] || continue | |
| info "<= Got command: '$CMD' arg: '$ARG' rest: '$REST'" | |
| if [[ -n "$REST" ]]; then | |
| info "!! Error: too many arguments" | |
| elif [[ -n "$ARG" ]] && [[ "$ARG" != "on" ]] && [[ "$ARG" != "off" ]] && ! [[ "$ARG" =~ ^[0-9]+$ ]]; then | |
| info "!! Error: invalid argument '$ARG'" | |
| elif [[ "$ALLOWED_PCP_COMMANDS" != *",$CMD,"* ]]; then | |
| info "!! Error: command is not in ALLOWED_PCP_COMMANDS" | |
| else | |
| # ARG is now shell safe, either only a number, or "on" or "off", but don't pass unless needed | |
| pcp "$CMD" $ARG || info "!! Error: command failed with $?" | |
| fi | |
| done | |
| } | |
| start_updating_state | |
| run_at_exit publish_LWT_offline | |
| publish "$MQTT_TOPIC_WILL" "$MQTT_WILL_INIT" | |
| [[ -z "${VOLUME_AT_STARTUP:-}" ]] || pcp volume 0 | |
| [[ "${PAUSE_AT_STARTUP:-false}" == "false" ]] || pcp pause | |
| [[ "${STOP_AT_STARTUP:-false}" == "false" ]] || pcp stop | |
| [[ -z "${VOLUME_AT_STARTUP:-}" ]] || pcp volume "$VOLUME_AT_STARTUP" | |
| while true ; do | |
| publish "$MQTT_TOPIC_WILL" "$MQTT_WILL_ONLINE" --retain | |
| subscribe_to_commands || ( | |
| info "Error: Subscribe failed, restarting in ${RETRY_SUBSCRIBE_AFTER_S}s..." | |
| sleep "$RETRY_SUBSCRIBE_AFTER_S" | |
| ) | |
| done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment