Bose Soundtouch besturen met Domoticz

Vanmorgen zette ik mijn Bose Soundtouch speaker aan voor wat muziek en dacht “waarom kan ik die speaker nog niet besturen met Domoticz ?”. Googleen op “Domoticz Soundtouch” leverde me al snel de hit van de Domoticz wiki op https://www.domoticz.com/wiki/Plugins/Soundtouch. Gelijk maar aan de slag gegaan:

Stap 1. Python library libsoundtouch installeren

pip3 install libsoundtouch

Automatisch worden de bibliotheken zeroconf en websocket mee geïnstalleerd

Stap 2. Domoticz plugin installeren

cd /home/pi/Domoticz/plugins
mkdir Soundtouch
cd Soundtouch
vi plugin.py

Plak het volgende script (shift-INS):

{“type”:”block”,”srcClientIds”:[“e0fbcfcb-cd99-49f7-8d42-f1c14247a456″],”srcRootClientId”:””}


# Soundtouch plugin
#
# Author: Gerrit Hulleman
#
"""
<plugin key="SoundTouch" name="Soundtouch plugin" author="Gerrit Hulleman" version="1.0.1" wikilink="http://www.domoticz.com/wiki/plugins/plugin.html" externallink="">
   <description>
       <h2>Soundtouch plugin</h2><br/>
       Uses the libsoundtouch (https://github.com/CharlesBlonde/libsoundtouch) to communicate with bose soundtouch devices.
       <h3>Features</h3>
       <ul style="list-style-type:square">
             <li>Turn off / switch preset stations</li>
           <li>Control audio</li>
       </ul>
       <h3>Configuration</h3>
       Soundtouch host: the IP/DNS name of the soundtouch device
       Interval: active reread of device status. Note: only needed if the device op operated directly
       Automatic presets: use preset in device to update control.
       Debug: additional debug information to domoticz log and logfile.
   </description>
   <params>
       <param field="Address" label="Soundtouch host" width="200px" required="true" default="192.168.178.24"/>
       <param field="Mode1" label="Interval" width="200px" required="true" default="10"/>
       <param field="Mode2" label="Automatic presets" width="75px">
           <options>
               <option label="True" value="1" default="true"/>
               <option label="False" value="0" />
           </options>
       </param>

       <param field="Mode6" label="Debug" width="75px">
           <options>
               <option label="True" value="Debug"/>
               <option label="False" value="Normal"  default="true" />
           </options>
       </param>

   </params>
</plugin>
"""
import Domoticz
import sys
sys.path.append('/home/pi/.local/lib/python3.7/site-packages/')

from libsoundtouch import soundtouch_device
from libsoundtouch.utils import Source, Type

class BasePlugin:
   enabled = False
   debugMode = False
   soundTouchHost = ''
   deviceName = ''
   heartbeat = 10
   automaticPreset = False
   presetMap = {} #Contains location / preset id on startup

   def __init__(self):
       return

   def onStart(self):
       self.logMessage("Loading parameters")
       self.soundTouchHost = Parameters["Address"]
       self.heartbeat = Parameters["Mode1"]
       if Parameters["Mode2"] != "0":
           self.automaticPreset = True
       if Parameters["Mode6"] != "Debug":
           Domoticz.Debugging(0)
           self.debugMode = False
       else:
           self.debugMode = True
       if (self.heartbeat != 0):
           Domoticz.Heartbeat(int(self.heartbeat))

       if (len(Devices) == 0):
           # devices not created, possible first run or deleted.
           Options = {"LevelActions": "||||",
                      "LevelNames": "Off|Preset 1|Preset 2|Preset 3|Preset 4",
                      "LevelOffHidden": "false",
                      "SelectorStyle": "1"}
           Domoticz.Device(Name="control", Unit=1, TypeName="Selector Switch", Options=Options, Image=8).Create() # Image 8 = speaker icon
           Domoticz.Device(Name="volume", Unit=2, TypeName="Switch", Switchtype=7, Image=8).Create() # Switchtype 7 = dimmer, Image 8 = speaker icon
           self.logMessage("Soundtouch devices created");


       extDevice = soundtouch_device(self.soundTouchHost)
       presets = extDevice.presets()
       presetOptions = "Off"
       presetActions = ""
       for preset in presets:
           presetOptions += '|'
           presetActions += '|'
           presetOptions += preset.name
           # save location. If the channel is change on the device, easier lookup on heartbeat
           presetIdSwitch = int(preset.preset_id)*10; # 1 = 10, 2 = 20 etc.
           self.presetMap[preset.location] = presetIdSwitch
           self.logDebug("Channel: "+preset.name+ " id: "+str(preset.preset_id)+ " selector: "+str(presetIdSwitch))

       if (self.automaticPreset):
           self.logMessage("Preset update - auto : " + presetOptions)

           Options = {"LevelActions": presetActions,
                      "LevelNames": presetOptions,
                      "LevelOffHidden": "false" }
           # nValue/sValue is mandatory.
           Devices[1].Update(nValue=Devices[1].nValue, sValue=Devices[1].sValue, Options=Options)

       self.logMessage("Plugin operational")
   def onStop(self):
       self.logDebug("onStop called")

   def onConnect(self, Connection, Status, Description):
       self.logDebug("onConnect called")

   def onMessage(self, Connection, Data):
       self.logDebug("onMessage called")

   def onCommand(self, Unit, Command, Level, Hue):
       self.logDebug("onCommand called for Unit " + str(Unit) + ": Command '" + str(Command) + "', Level: " + str(Level))
       #(Bose 20) onCommand called for Unit 1: Parameter 'Set Level', Level: 10
       if (Unit == 1):
           # control device
           if (Command == "Set Level"):
               extDevice = soundtouch_device(self.soundTouchHost)
               if Level == 0:
                   extDevice.power_off()
               presetId = int((Level / 10) - 1)
               if (presetId >= 0
               and presetId <= 6):
                   presets = extDevice.presets()
                   extDevice.select_preset(presets[presetId])
               Devices[1].Update(2, str(Level)) # switch is not updated from domoticz itself.
           if (Command == "Off"):
               extDevice = soundtouch_device(self.soundTouchHost)
               extDevice.power_off()
               Devices[1].Update(2, str(Level)) # switch is not updated from domoticz itself.
       #(Bose 20) onCommand called for Unit 2: Command 'Set Level', Level: 17
       if (Unit == 2):
           # volume control
           extDevice = soundtouch_device(self.soundTouchHost)
           if (Command == "Off"):
               extDevice.set_volume(0)
               Devices[2].Update(0, str(Level)) # switch is not updated from domoticz itself.
           else:
               extDevice.set_volume(Level)
               Devices[2].Update(2, str(Level)) # switch is not updated from domoticz itself.

   def onNotification(self, Name, Subject, Text, Status, Priority, Sound, ImageFile):
       self.logDebug("Notification: " + Name + "," + Subject + "," + Text + "," + Status + "," + str(Priority) + "," + Sound + "," + ImageFile)

   def onDisconnect(self, Connection):
       self.logDebug("Disconnect")
       return

   def onHeartbeat(self):
       self.logDebug("onHeartbeat called")
       extDevice = soundtouch_device(self.soundTouchHost)
       try:
           # check/update station
           status = extDevice.status()
           # print(status.source) # TUNEIN, STANDBY, BLUETOOTH, AUX
           if (status.source == "STANDBY"):
               self.logDebug("on standby")
               Devices[1].Update(nValue = 0, sValue = "00")
               self.logDebug("Device updated")
           else:
               location = status.content_item.location
               if (location in self.presetMap):
                   # found -> update device to reflect channel
                   #self.logDebug("Found channel currently on " + str(self.presetMap[location]))
                   Devices[1].Update(2, sValue = str(self.presetMap[location]))

               # check/update volume
               actualVolume = extDevice.volume().actual
               self.logDebug("Set volume: " + str(actualVolume))
               Devices[2].Update(2, str(actualVolume))
               self.logDebug("Set volume done")
       except:
           self.logMessage("Unexpected error:" + sys.exc_info()[0])
       self.logDebug("Heartbeat done, return")

   def logMessage(self, Message):
       if self.debugMode:
           f= open("plugins/Soundtouch/log.txt","a+")
           f.write(Message+'\r\n')
       Domoticz.Log(Message)

   def logDebug(self, Message):
       if self.debugMode:
           self.logMessage(Message)

global _plugin
_plugin = BasePlugin()

def onStart():
   global _plugin
   _plugin.onStart()

def onStop():
   global _plugin
   _plugin.onStop()

def onConnect(Connection, Status, Description):
   global _plugin
   _plugin.onConnect(Connection, Status, Description)

def onMessage(Connection, Data):
   global _plugin
   _plugin.onMessage(Connection, Data)

def onCommand(Unit, Command, Level, Hue):
   global _plugin
   _plugin.onCommand(Unit, Command, Level, Hue)

def onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile):
   global _plugin
   _plugin.onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile)

def onDisconnect(Connection):
   global _plugin
   _plugin.onDisconnect(Connection)

def onHeartbeat():
   global _plugin
   _plugin.onHeartbeat()

   # Generic helper functions
def DumpConfigToLog():
   for x in Parameters:
       if Parameters[x] != "":
           Domoticz.Log( "'" + x + "':'" + str(Parameters[x]) + "'")
   Domoticz.Log("Device count: " + str(len(Devices)))
   for x in Devices:
       Domoticz.Log("Device:           " + str(x) + " - " + str(Devices[x]))
       Domoticz.Log("Device ID:       '" + str(Devices[x].ID) + "'")
       Domoticz.Log("Device UnitID:   '" + str(Devices[x].Unit) + "'")
       Domoticz.Log("Device DeviceID: '" + str(Devices[x].DeviceID) + "'")
       Domoticz.Log("Device Name:     '" + Devices[x].Name + "'")
       Domoticz.Log("Device nValue:    " + str(Devices[x].nValue))
       Domoticz.Log("Device sValue:   '" + Devices[x].sValue + "'")
       Domoticz.Log("Device LastLevel: " + str(Devices[x].LastLevel))
   return
    • Start Domoticz opnieuw op om de plugin te activeren
sudo /etc/init.d/domoticz.sh restart

Stap 3. Soundtouch device als hardware toevoegen aan Domoticz

    • Ga naar Instellingen, Hardware
    • Kies bij Type voor Soundtouch plugin

 

    • Achterhaal de FCDN-hostnaam van je speaker door de de Soundtouch app op je telefoon te openen

    • Klik onderaan op het icoontje van je speaker, het volgende scherm verschijnt

    • Klik op het Instellingen (tandwieltje) icoontje, het volgende scherm verschijnt

    • Onder Naam Luidspreker vindt je de naam van je speaker terug, in mijn geval “Speaker fitnessruimte”. Om er een FCDN-hostname van te maken die aan de BIND regels voldoet plakt Bose er SoundTouch voor en vervangt de spaties door ‘-’ streepjes. In mijn geval is de FCDN-naam van de speaker “SoundTouch-Speaker-fitnessruimte.sydspost.nl”, nslookup hiervan levert het eventueel het IP-adres op:
nslookup SoundTouch-Speaker-fitnessruimte.sydspost.nl

Server:         192.168.2.29
Address:        192.168.2.29#53

Name:   SoundTouch-Speaker-fitnessruimte.sydspost.nl Address: 192.168.2.250
  • Omdat ik met DHCP werk, vul ik de FCDN-hostnaam van de speaker in op het tabblad Hardware en geef de hardware-plugin een zinvolle naam. Klik daarna op Toevoegen

  • Toelichting op niet default waarden:
Label Waarde Toelichting
Naam Speaker fitnessruimte Zinvolle naam
Soundtouch host SoundTouch-Speaker-fitnessruimte.sydspost.nl FCDN-hostnaam van je speaker
  • Check de logging of de plugin goed opstart: Instellingen, Log
2021-06-23 11:32:29.777 Speaker fitnessruimte hardware started.
2021-06-23 11:32:29.777 Status: Speaker fitnessruimte: (Speaker fitnessruimte) Entering work loop.
2021-06-23 11:32:29.777 Status: Speaker fitnessruimte: (Speaker fitnessruimte) Started.
2021-06-23 11:32:30.530 Speaker fitnessruimte: (Speaker fitnessruimte) Loading parameters
2021-06-23 11:32:30.531 Speaker fitnessruimte: (Speaker fitnessruimte) Debug logging mask set to: NONE
2021-06-23 11:32:30.532 Speaker fitnessruimte: (Speaker fitnessruimte) Soundtouch devices created
2021-06-23 11:32:30.609 Speaker fitnessruimte: (Speaker fitnessruimte) Preset update - auto : Off|Syds|100% NL Puur
2021-06-23 11:32:30.610 Speaker fitnessruimte: (Speaker fitnessruimte) Plugin operational
2021-06-23 11:32:30.529 Status: Speaker fitnessruimte: (Speaker fitnessruimte) Initialized version 1.0.1, author 'Gerrit Hulleman'
  • Open Instellingen, Apparaten. Er zijn twee devices toegevoegd, klik op de Groene pijltjes om ze toe te voegen aan je Schakelaars

  • Pas eventueel de selectorstijl van de control schakelaar aan (Bij meerdere snelkeuzes met lange namen loop je uit je ruimte)

  • Toelichting op niet default waarden
Label Waarde Toelichting
Selectorstijl Selecteer menu Geeft je een picklist om uit de ingestelde kanalen te selecteren

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *