Thank you for your donation!


Cloudsmith graciously provides open-source package management and distribution for our project.


Problem: loosing mpd connection in python
#1
I have had an issue with some python code, I'm running on my moode installation, where the code breaks after I updated moode, and enabled bluetooth. I addressed the issue here: http://moodeaudio.org/forum/showthread.php?tid=3709 although my initial premise was completely wrong. After asking here: https://github.com/Mic92/python-mpd2/issues/171 , I got a bit closer to the issue.

It turns out that Bluetooth apparently breaks the connection to mpd in python-mpd2

If I run the following code:

Code:
import logging, mpd, time
logging.basicConfig(level=logging.DEBUG)
client = mpd.MPDClient()
client.timeout = None
client.idletimeout = None
client.connect("localhost", 6600)
print(client.status())

while True:

    if client.status()['state'] == "play": # Playing or Airplay active
        print(client.status())

    else: # Either stopped or paused
        print(client.status())

    time.sleep(0.1)
    
client.close() # send the close command
client.disconnect() # disconnect from the server


Everything runs fine until I connect a bluetooth device, then this happens:
Code:
DEBUG:mpd.base:Calling MPD status()
DEBUG:mpd.base:Calling MPD status()
{'volume': '0', 'repeat': '0', 'random': '0', 'single': '0', 'consume': '0', 'partition': 'default', 'playlist': '2', 'playlistlength': '7', 'mixrampdb': '0.000000', 'state': 'play', 'song': '6', 'songid': '7', 'time': '335:335', 'elapsed': '334.685', 'bitrate': '32', 'duration': '334.889', 'audio': '44100:24:2'}
DEBUG:mpd.base:Calling MPD status()
DEBUG:mpd.base:Calling MPD status()
{'volume': '0', 'repeat': '0', 'random': '0', 'single': '0', 'consume': '0', 'partition': 'default', 'playlist': '2', 'playlistlength': '7', 'mixrampdb': '0.000000', 'state': 'play', 'song': '6', 'songid': '7', 'time': '335:335', 'elapsed': '334.788', 'bitrate': '32', 'duration': '334.889', 'audio': '44100:24:2'}
DEBUG:mpd.base:Calling MPD status()
DEBUG:mpd.base:Calling MPD status()
INFO:mpd.base:Calling MPD disconnect()
Traceback (most recent call last):
File "mpd_test2.py", line 15, in
print(client.status())
File "/usr/local/lib/python3.7/dist-packages/mpd/base.py", line 469, in mpd_command
return wrapper(self, name, args, callback)
File "/usr/local/lib/python3.7/dist-packages/mpd/base.py", line 532, in _execute
return retval()
File "/usr/local/lib/python3.7/dist-packages/mpd/base.py", line 454, in command_callback
res = function(self, self._read_lines())
File "/usr/local/lib/python3.7/dist-packages/mpd/base.py", line 394, in _parse_object
objs = list(self._parse_objects(lines))
File "/usr/local/lib/python3.7/dist-packages/mpd/base.py", line 240, in _parse_objects
for key, value in self._parse_pairs(lines):
File "/usr/local/lib/python3.7/dist-packages/mpd/base.py", line 235, in _parse_pairs
for line in lines:
File "/usr/local/lib/python3.7/dist-packages/mpd/base.py", line 586, in _read_lines
line = self._read_line()
File "/usr/local/lib/python3.7/dist-packages/mpd/base.py", line 571, in _read_line
raise ConnectionError("Connection lost while reading line")
mpd.base.ConnectionError: Connection lost while reading line
root@moode:/home/pi#

If i run the code at startup, through etc/rc.local, the code breaks immediately.

1. Is it expected behavior, that mpd closes/breaks the connection, when a Bluetooth connection is made?

2. Is there any explanation as to why mpd would close/break the connection immediately, when I start the script from etc/rc.local?
Reply
#2
1. Prolly because of the code in udev rule below that restarts MPD when a bluetooth connection comes in. It may be possible to replace the restart with something like RUN+="/usr.bin/mpc stop" or if that doesn't work then run a script or systemd unit that does "mpc stop". IIRC there were issues that led to having to do a complete MPD restart to get the rule to work correctly.

Code:
pi@rp2:~ $ cat /etc/udev/rules.d/10-a2dp-autoconnect.rules
ACTION=="add", KERNEL=="input[0-9]*", SUBSYSTEM=="input", ATTR{name}=="*:*:*:*:*:*", GOTO="bt_add"
ACTION=="remove", KERNEL=="input[0-9]*", SUBSYSTEM=="input", DEVPATH=="/devices/virtual/input/*" GOTO="bt_remove"
GOTO="bt_end"

LABEL="bt_add"
RUN+="/bin/systemctl restart mpd"
RUN+="/usr/local/bin/a2dp-autoconnect"
GOTO="bt_end"

LABEL="bt_remove"
RUN+="/usr/local/bin/a2dp-autoconnect"

LABEL="bt_end"
pi@rp2:~ $

2. MPD is started by /var/www/command/worker.php which is launched as a background task in rc.local. Your script, whether it is launched before or after worker should check to see that MPD has started and is accepting connections before it does its thing.
Enjoy the Music!
moodeaudio.org | Mastodon Feed | GitHub
Reply
#3
Thank you very much for elaborating. I'll se if I can adapt my code accordingly.

One thing I need to check for, if there is no connection to mpd, is if Bluetooth is connected and/or playing. Is there a way, to do this using python using Python?
Max Schmeling
Reply
#4
If I enclose the mpd status requests in try-except statements, and then try continuously reconnecting when the connection is lost, the code works.

Code:
# Import the libraries to use time delays, send os commands and access GPIO pins
import RPi.GPIO as GPIO
import time
import os
import mpd
import sys
import sqlite3 as SQLite3
#import logging

# Setup Variables
PowerButtonPin = 7
KeepPoweredPin = 8 # Pin to pull high to keep the Pi Supply giving power
RedLedPin = 13 # Set pin number for "OFF" LED
RedExtLedPin = 16 # Set pin number for "OFF" LED
GreenLedPin = 15 # Set pin number for "ON" LED
GreenExtLedPin = 18 # Set pin number for "ON" LED
MainRelayPin = 11 # Set pin number for relay output
Debug = False # Set to true if you want debug from the program. Remember to set it to false if you start the script from rc.local, otherwise your promt will be junked
AmpState = "on" # Stores on/off/standby state of the player and amp
ButtonOverride = False # False except when button initiates standby.
ShutdownActive = False # True when system is shutting down.
TimeSinceActive = time.time() # Stores the time when MoOde was last active
StandbyTimeLimit = 60
OffTimeLimit = 120

# Setup GPIO pins
GPIO.setmode(GPIO.BOARD) # Set pin numbering to board numbering
GPIO.setup(MainRelayPin, GPIO.OUT, initial=GPIO.LOW) # Setup MainRelayPin as an output
#GPIO.setup(PowerButtonPin, GPIO.IN, pull_up_down = GPIO.PUD_DOWN) # Setup PowerButtonPin as an input
GPIO.setup(PowerButtonPin, GPIO.IN) # Setup PowerButtonPin as an input  
GPIO.setup(RedLedPin, GPIO.OUT) # Setup RedLedPin as an output
GPIO.setup(GreenLedPin, GPIO.OUT) # Setup GreenLedPin as an output
GPIO.setup(RedExtLedPin, GPIO.OUT) # Setup RedLedPin as an output
GPIO.setup(GreenExtLedPin, GPIO.OUT) # Setup GreenLedPin as an output  
GPIO.setup(KeepPoweredPin, GPIO.OUT, initial=GPIO.HIGH) #Setup KeepPoweredPin to pull high to keep the Pi Supply giving power

#logging.basicConfig(level=logging.DEBUG)

# Amplifier state control procedure
def SetAmpState(state):

    global AmpState
    
    GPIO.output(RedLedPin,False)
    GPIO.output(RedExtLedPin,False)
    GPIO.output(GreenLedPin,False)
    GPIO.output(GreenExtLedPin,False)

    if state == "on":
        GPIO.output(GreenLedPin,True)
        GPIO.output(GreenExtLedPin,True)
        GPIO.output(MainRelayPin, GPIO.HIGH)
        AmpState = "on"
        
    elif state == "off":
        GPIO.output(RedLedPin,True)
        GPIO.output(RedExtLedPin,True)
        GPIO.output(MainRelayPin, GPIO.LOW)
        AmpState = "off"
        #os.system("shutdown -h 0&")
        os.system("sudo shutdown now")
        #sys.exit("Amp was turned off")
        #sys.exit(0)
        
    elif state == "standby":
        GPIO.output(GreenLedPin,True)
        GPIO.output(GreenExtLedPin,True)
        GPIO.output(RedLedPin,True)
        GPIO.output(RedExtLedPin,True)
        GPIO.output(MainRelayPin, GPIO.LOW)
        try:
            Client.pause(1)
        except:
            pass
            
        AmpState = "standby"
        # Mute Moode player
        #os.system("/var/www/vol.sh mute")
            
    if (Debug): print("Amplifier state: {0}".format(AmpState))
    
    
# Airplay state check
def IsAirplayOn():
    
    global ButtonOverride
    
    AirplayOn = False
    try:
        cur = con.cursor()    
        cur.execute('select value from cfg_system where param like \'aplactive\'')
        data = cur.fetchone()[0]    
        if (Debug): print("SQL = %s" % data)
        if data == "1":
            AirplayOn = True

    except SQLite3.Error as e:
        if (Debug): print("Error %s:" % e.args[0])

    if ButtonOverride:
        AirplayOn = False
    
    if Debug: print("Returning IsAirplayOn = {0}".format(AirplayOn))
    return AirplayOn    

#Check if Bluetooth is on
def IsBluetoothOn():    
    
    global ButtonOverride
    
    Is_BT_On = False
    try:
        cur = con.cursor()    
        cur.execute('select value from cfg_system where param like \'btactive\'')
        data = cur.fetchone()[0]    
        if (Debug): print("SQL = %s" % data)
        if data == "1":
            Is_BT_On = True

    except SQLite3.Error as e:
        if (Debug): print("Error %s:" % e.args[0])

    if ButtonOverride:
        Is_BT_On = False
    
    if Debug: print("Is BT On = {0}".format(Is_BT_On))
    return Is_BT_On
    
#Check if anything is playing
def IsPlaying():

    Is_Playing = False
    
    try:
        if Client.status()['state'] == "play": # Playing
            Is_Playing = True
            if (Debug): print("mpd playing")
    except:
        if (Debug): print("mpd not playing or connection lost")

    if IsAirplayOn(): #Airplay active
        Is_Playing = True
        if (Debug): print("Airplay is active")
        
    if IsBluetoothOn(): #Bluetooth active
        Is_Playing = True
        if (Debug): print("BT is active")
    
    return Is_Playing

    
def Eval_buttonpress(channel):
    global ShutdownActive
    global ButtonOverride
    global TimeSinceActive
    
    # only react when there is no other shutdown process running
    if not ShutdownActive:
        ShutdownActive = True
        pressed = 1
        counter = 0
        while (pressed == 1):
            if (GPIO.input(PowerButtonPin) == True):
                # button is still pressed
                counter = counter + 1
                if (Debug): print(("Counter is now: {0}".format(counter)))
                # break if we count beyond 20 (long-press is a shutdown)
                if (counter >= 20):
                    pressed = 0
                else:
                    time.sleep(0.2)
            else:
                # button has been released
                pressed = 0
        
        if (Debug): print(("Button was pressed for: {0} counts.".format(counter)))
        
        # count how long the button was pressed
        if (counter < 2):
            # short press
            pressed = 0
            ShutdownActive = False
        else:
            if (counter < 20):
                # medium length press
                if (Debug): print("Muting Moode..")
                if AmpState == "on":
                    SetAmpState("standby")
                    ButtonOverride = True
                else:
                    SetAmpState("on")
                    ButtonOverride = False
                    TimeSinceActive = time.time()
                pressed = 0
                ShutdownActive = False
            else:
                # long press, initiate system shutdown
                if (Debug): print("shutting down..")
                SetAmpState("off")
                pressed = 0
    #ShutdownActive = False
    pressed = 0


# Setup MDPClient and connect to MPD on localhost
Client = mpd.MPDClient() # create client object
Client.timeout = None
Client.idletimeout = None

while True:
    try:
        Client.connect("localhost", 6600) # connect to localhost:6600
    except:
        continue
    break

    
# Connect to player database, in order to read Airplay status     
try:
        con = SQLite3.connect('/var/local/www/db/moode-sqlite3.db')    
except SQLite3.Error as e:
        if Debug: print("Error %s:" % e.args[0])
        sys.exit(1)

# Set PowerButtonPin as an interrupt input
GPIO.add_event_detect(PowerButtonPin, GPIO.RISING, callback = Eval_buttonpress)

SetAmpState("on")
#TimeSinceActive = time.time()

#global ButtonOverride
#global TimeSinceActive
#global AmpState
#global ShutdownActive

time.sleep(20)

TimeSinceActive = time.time()


# main loop
while True:
    ElapsedTime = time.time() - TimeSinceActive
    if (Debug): print(("{0} seconds have passed since last MPD was last playing.".format(ElapsedTime)))    
        
    if IsPlaying(): # Playing or Airplay active
        ButtonOverride = False
        StandbyTimeLimit = 240
        OffTimeLimit = 600
        TimeSinceActive = time.time()
        if not AmpState == "on" and ShutdownActive == False:
            SetAmpState("on") # Set GPIO pins to ON state
            if (Debug): print("MPD state = play! Entering ON state")
            if (Debug): print(("Amp is on = {0}".format(GPIO.input(MainRelayPin))))

    else: # Either stopped or paused
    
        try:
            if Client.status()['state'] == "play": # Playing
                Test = False
        except:
            if (Debug): print("Trying to connect to MPD")
            try:
                Client.connect("localhost", 6600) # connect to localhost:6600
                if (Debug): print("Connected to MPD")
            except:
                if (Debug): print("Connection to MPD Failed")
                continue

        if ElapsedTime >= StandbyTimeLimit:
            if AmpState == "on":
                SetAmpState("standby") # Set GPIO pins to OFF state
                if (Debug): print(("MPD has not been playing for {0} seconds! Entering standby!".format(ElapsedTime)))
                if (Debug): print(("Amp is on = {0}".format(GPIO.input(MainRelayPin))))

        if ElapsedTime > OffTimeLimit:
            if AmpState == "standby":
                SetAmpState("off") # Set GPIO pins to OFF state
                if (Debug): print(("MPD has not been playing for {0} seconds! Shutting down!".format(ElapsedTime)))
                if (Debug): print(("Amp is on = {0}".format(GPIO.input(MainRelayPin))))

    time.sleep(0.1)


# Close connection to MPD (not used since the main loop never exits)
Client.close() # send the close command
Client.disconnect() # disconnect from the server

# Close connection to database
if con:
    con.close()

# Close connection to database
if con:
    con.close()
Reply


Forum Jump: