Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions src/BTDevicesManager/BTAudioConnect
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ log "Reset /etc/asound.conf to default configuration"
}

bluetooth_asoundconf() {
cat << EOF >> /etc/asound.conf
cat << EOF >> "$ASOUND_TARGET"
pcm.bluetooth {
type plug
slave.pcm {
Expand All @@ -143,15 +143,29 @@ ctl.bluetooth {
type bluealsa
}
EOF
log "Added Bluetooth device '$2' ($1) to /etc/asound.conf"
log "Added Bluetooth device '$2' ($1) to $ASOUND_TARGET"
}

# Dreambox One/Two decode audio in software: enigma2 plays to the
# Bluetooth sink itself, so keep the box asound.conf and skip the
# capture/playback pipe.
SOFT_BTAUDIO=0
case "$(grep -m 1 '^model=' /usr/lib/enigma.info 2>/dev/null | cut -d'=' -f2 | tr -d "'\"")" in
dreamone|dreamtwo) SOFT_BTAUDIO=1 ;;
esac

rm -f "$STREAM_TOKENFILE"
stop_pcm "$PLAYBACK_PIDFILE"
stop_pcm "$CAPTURE_PIDFILE"

log "Applying default sound configuration"
default_asoundconf
if [ "$SOFT_BTAUDIO" = "1" ]; then
ASOUND_TARGET=/etc/alsa/conf.d/90-btdevicesmanager.conf
rm -f "$ASOUND_TARGET"
else
ASOUND_TARGET=/etc/asound.conf
log "Applying default sound configuration"
default_asoundconf
fi

if [ -n "$1" ]; then
start_dbus || exit 1
Expand Down Expand Up @@ -183,8 +197,14 @@ if [ -n "$1" ]; then
bluetoothname="$(bluetoothctl info "$bluetoothaddr" | grep "Name" | cut -d ':' -f 2 | xargs)"
log "Device name detected: $bluetoothname"

[ "$SOFT_BTAUDIO" = "1" ] && mkdir -p "$(dirname "$ASOUND_TARGET")"
bluetooth_asoundconf "$bluetoothaddr" "$bluetoothname"

if [ "$SOFT_BTAUDIO" = "1" ]; then
log "Script finished"
exit 0
fi

log "Starting audio streaming to Bluetooth device"
printf '%s\n' "$$" > "$STREAM_TOKENFILE"
capture_audio "$$" 9>&- | aplay -q -f dat --buffer-size=16384 --period-size=2048 -t raw -D bluetooth --process-id-file "$PLAYBACK_PIDFILE" 9>&- &
Expand Down
29 changes: 24 additions & 5 deletions src/BTDevicesManager/bluetoothctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# You should have received a copy of the GNU General Public License
# along with ReachView. If not, see <http://www.gnu.org/licenses/>.

from os import listdir
from pexpect import EOF, TIMEOUT, spawnu
from re import compile
from subprocess import check_output
Expand All @@ -46,13 +47,31 @@ def __init__(self):
self.next_retry_at = 0
self.start_thread = None
self.start_lock = threading.Lock()
try:
check_output("rfkill unblock bluetooth", shell=True)
except Exception as e:
self.start_error = f"rfkill unblock bluetooth failed: {e}"
print(f"[BluetoothManager] {self.start_error}")
# Some drivers (Amlogic bt-dev) power-cycle the controller on every
# unblock, which drops the firmware loaded by hciattach.
if self._bluetooth_blocked():
try:
check_output("rfkill unblock bluetooth", shell=True)
except Exception as e:
self.start_error = f"rfkill unblock bluetooth failed: {e}"
print(f"[BluetoothManager] {self.start_error}")
self._start_thread()

@staticmethod
def _bluetooth_blocked():
try:
for name in listdir("/sys/class/rfkill"):
path = f"/sys/class/rfkill/{name}"
with open(f"{path}/type") as fd:
if fd.read().strip() != "bluetooth":
continue
with open(f"{path}/soft") as fd:
if fd.read().strip() == "1":
return True
except OSError:
return True
return False

def _start_thread(self, force=False):
now = monotonic()
if not force and self.next_retry_at and now < self.next_retry_at:
Expand Down
92 changes: 84 additions & 8 deletions src/BTDevicesManager/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@
from signal import SIGUSR2
from twisted.internet.reactor import callInThread

from enigma import eTimer, iPlayableService
from enigma import eServiceReference, eTimer, iPlayableService

import NavigationInstance

from Components.Label import Label
from Components.Sources.StaticText import StaticText
from Components.ActionMap import ActionMap
from Components.config import config, ConfigText, ConfigSubsection, ConfigYesNo
from Components.Console import Console
from Components.MenuList import MenuList
from Components.ServiceEventTracker import ServiceEventTracker
from Plugins.Plugin import PluginDescriptor
Expand All @@ -44,9 +47,73 @@
config.btdevicesmanager.audioconnect = ConfigYesNo(default=False)
config.btdevicesmanager.audioaddress = ConfigText(default="", fixed_size=False)

BTAUDIO_PROC = "/proc/stb/audio/btaudio"


def isSoftBTAudio():

Check warning on line 53 in src/BTDevicesManager/plugin.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename function "isSoftBTAudio" to match the regular expression ^[a-z_][a-z0-9_]*$.

See more on https://sonarcloud.io/project/issues?id=oe-alliance-plugins_BTDevicesManager&issues=AaEiNRauNidLZKzUhiAf&open=AaEiNRauNidLZKzUhiAf&pullRequest=2
# Dreambox One/Two decode audio in software; enigma2 plays to the
# Bluetooth ALSA sink when the audio source is set to Bluetooth.
try:
with open("/usr/lib/enigma.info") as fd:
for line in fd:
if line.startswith("model="):
return line.split("=", 1)[1].strip().strip("'\"") in ("dreamone", "dreamtwo")
except OSError:
pass
return False


SOFT_BTAUDIO = isSoftBTAudio()
softBTAudioConsole = Console()


def setSoftBTAudioOutput(enabled):

Check warning on line 70 in src/BTDevicesManager/plugin.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename function "setSoftBTAudioOutput" to match the regular expression ^[a-z_][a-z0-9_]*$.

See more on https://sonarcloud.io/project/issues?id=oe-alliance-plugins_BTDevicesManager&issues=AaEiNRauNidLZKzUhiAh&open=AaEiNRauNidLZKzUhiAh&pullRequest=2
audioSource = getattr(config.av, "audio_source", None)

Check warning on line 71 in src/BTDevicesManager/plugin.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this local variable "audioSource" to match the regular expression ^[_a-z][a-z0-9_]*$.

See more on https://sonarcloud.io/project/issues?id=oe-alliance-plugins_BTDevicesManager&issues=AaEiNRauNidLZKzUhiAg&open=AaEiNRauNidLZKzUhiAg&pullRequest=2
if audioSource is None:
return
previous = audioSource.value
if enabled:
audioSource.value = "2"
elif audioSource.value == "2":
audioSource.value = "0"
audioSource.save()
if audioSource.value != previous:
restartMediaService()


def restartMediaService():

Check warning on line 84 in src/BTDevicesManager/plugin.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename function "restartMediaService" to match the regular expression ^[a-z_][a-z0-9_]*$.

See more on https://sonarcloud.io/project/issues?id=oe-alliance-plugins_BTDevicesManager&issues=AaEiNRauNidLZKzUhiAi&open=AaEiNRauNidLZKzUhiAi&pullRequest=2
# DVB follows the audio source on the fly, the GStreamer sink only picks
# its ALSA device when the service starts.
nav = NavigationInstance.instance
ref = nav and nav.getCurrentlyPlayingServiceReference()
if ref and ref.type != eServiceReference.idDVB:
nav.restartService()


def softBTAudioConnected(result, retval, extraArgs=None):

Check warning on line 93 in src/BTDevicesManager/plugin.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this parameter "extraArgs" to match the regular expression ^[_a-z][a-z0-9_]*$.

See more on https://sonarcloud.io/project/issues?id=oe-alliance-plugins_BTDevicesManager&issues=AaEiNRauNidLZKzUhiAj&open=AaEiNRauNidLZKzUhiAj&pullRequest=2

Check warning on line 93 in src/BTDevicesManager/plugin.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename function "softBTAudioConnected" to match the regular expression ^[a-z_][a-z0-9_]*$.

See more on https://sonarcloud.io/project/issues?id=oe-alliance-plugins_BTDevicesManager&issues=AaEiNRauNidLZKzUhiAk&open=AaEiNRauNidLZKzUhiAk&pullRequest=2
if retval == 0:
setSoftBTAudioOutput(True)
else:
print(f"[BluetoothManager] Bluetooth audio connect failed: {retval}")


def applySoftBTAudioState():

Check warning on line 100 in src/BTDevicesManager/plugin.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename function "applySoftBTAudioState" to match the regular expression ^[a-z_][a-z0-9_]*$.

See more on https://sonarcloud.io/project/issues?id=oe-alliance-plugins_BTDevicesManager&issues=AaEiNRauNidLZKzUhiAl&open=AaEiNRauNidLZKzUhiAl&pullRequest=2
config.btdevicesmanager.audioaddress.save()
config.btdevicesmanager.audioconnect.save()
commandconnect = resolveFilename(SCOPE_CURRENT_PLUGIN, "Extensions/BTDevicesManager/BTAudioConnect")
audioaddress = config.btdevicesmanager.audioaddress.value if config.btdevicesmanager.audioconnect.value else ""
if audioaddress:
softBTAudioConsole.ePopen(f"{commandconnect} {audioaddress}", softBTAudioConnected)
else:
setSoftBTAudioOutput(False)
softBTAudioConsole.ePopen(commandconnect)


def applyBTAudioState():
if not isfile("/proc/stb/audio/btaudio"):
if SOFT_BTAUDIO:
applySoftBTAudioState()
return
if not isfile(BTAUDIO_PROC):
return

newState = "off"
Expand All @@ -60,7 +127,7 @@
config.av.btaudio.save()

try:
with open("/proc/stb/audio/btaudio", "w") as fn:
with open(BTAUDIO_PROC, "w") as fn:
fn.write(newState)
except Exception as e:
print(f"[BluetoothManager] Error writing btaudio: {e}")
Expand Down Expand Up @@ -125,7 +192,7 @@

self.cb_mac_address = None
self.cb_name = None
self.hasBTAudio = isfile("/proc/stb/audio/btaudio")
self.hasBTAudio = isfile(BTAUDIO_PROC) or SOFT_BTAUDIO
self["audioActions"].setEnabled(self.hasBTAudio)
self.rootDir = "/var/lib/bluetooth"
self.controlerPath = None
Expand Down Expand Up @@ -310,11 +377,13 @@
if config.btdevicesmanager.audioaddress.value == current[1]:
config.btdevicesmanager.audioaddress.value = ""
config.btdevicesmanager.audioconnect.value = False
config.av.btaudio.value = False
if not SOFT_BTAUDIO:
config.av.btaudio.value = False
else:
config.btdevicesmanager.audioaddress.value = current[1]
config.btdevicesmanager.audioconnect.value = True
config.av.btaudio.value = True
if not SOFT_BTAUDIO:
config.av.btaudio.value = True
applyBTAudioState()
self.selectionChanged()

Expand Down Expand Up @@ -371,14 +440,21 @@
def sessionstart(session, reason, **kwargs):
global iBluetoothDevicesTask
if reason == 0:
if isfile("/proc/stb/audio/btaudio"):
if SOFT_BTAUDIO:
# Stay on HDMI until the stored audio device is connected again.
setSoftBTAudioOutput(False)
if config.btdevicesmanager.audioconnect.value:
applyBTAudioState()
elif isfile(BTAUDIO_PROC):
applyBTAudioState()
if iBluetoothDevicesTask is None:
iBluetoothDevicesTask = BluetoothDevicesTask(session)


def Plugins(**kwargs):
if fileCheck("/sys/class/bluetooth/hci0"):
# The internal UART controller of the soft BT audio boxes is attached
# in the background and may show up after the plugins are loaded.
if fileCheck("/sys/class/bluetooth/hci0") or SOFT_BTAUDIO:
return [
PluginDescriptor(where=[PluginDescriptor.WHERE_SESSIONSTART], fnc=sessionstart),
PluginDescriptor(name=_("Bluetooth Devices Manager"), description=_("This is bt devices manager"), icon="plugin.png", where=PluginDescriptor.WHERE_PLUGINMENU, fnc=main)
Expand Down
Loading