[Python] Bose Soundtouch Multiroom depuis domoticz

Vous avez créé un script LUA dont vous êtes fier, un .sh génial, un programme Python hors du commun, un Tuto, c'est ici que vous pouvez les partager.
lreymond
Messages : 18
Inscription : 25 nov. 2016, 20:06

Re: [Python][TUTO] Bose Soundtouch Multiroom depuis domoticz

Message par lreymond »

pas mieux...

Code : Tout sélectionner

2017-11-24 13:41:29.067 Error: Error executing script command (/home/pi/domoticz/scripts/python/bose_on.py). returned: 256
2017-11-24 13:41:32.447 Error: Error executing script command (/home/pi/domoticz/scripts/python/bose_off.py). returned: 256
ayziaa
Messages : 25
Inscription : 29 janv. 2017, 17:16

Re: [Python][TUTO] Bose Soundtouch Multiroom depuis domoticz

Message par ayziaa »

Oui mais quand tu exécute le script en ligne de commande, ça te donne toujours la même chose après avoir désinstaller et réinstaller la librairie?
Chrominator
Messages : 1042
Inscription : 19 déc. 2015, 07:29
Localisation : France

Re: [Python][TUTO] Bose Soundtouch Multiroom depuis domoticz

Message par Chrominator »

Chrominator a écrit :[...] comme j'avais OÜI FM, ça plantait.
Bon je suis pas au bout, je continue... :-)
Maintenant ça passe, les fonctions de la bibliothèque ne buttent plus sur les caractères non ascii.
Pour cela j'ai modifié le script device.py pour convertir les chaines en utf-8.

Il y deux fonctions à modifier refresh_presets() et select_preset() :

refresh_presets()

Code : Tout sélectionner

    def refresh_presets(self):
        """Refresh presets."""
        response = requests.get(
            "http://" + self._host + ":" + str(self._port) + "/presets")
        dom = minidom.parseString(response.text)
        self._presets = []
        for preset in _get_dom_elements(dom, "preset"):
            self._presets.append(Preset(preset))
devient

Code : Tout sélectionner

    def refresh_presets(self):
        """Refresh presets."""
        response = requests.get(
            "http://" + self._host + ":" + str(self._port) + "/presets")
        response.encoding = 'UTF-8'
        dom = minidom.parseString(response.text.encode('utf-8'))
        self._presets = []
        for preset in _get_dom_elements(dom, "preset"):
            self._presets.append(Preset(preset))
select_preset()

Code : Tout sélectionner

    def select_preset(self, preset):
        """Play selected preset.

        :param preset Selected preset.
        """
         requests.post(
            'http://' + self._host + ":" + str(self._port) + '/select',
            preset.source_xml)
devient

Code : Tout sélectionner

    def select_preset(self, preset):
        """Play selected preset.

        :param preset Selected preset.
        """
        preset.encoding = 'UTF-8'
        requests.post(
            'http://' + self._host + ":" + str(self._port) + '/select',
            preset.source_xml.encode('utf-8'))
Je n'en ai pas modifié d'autres étant donné que pour l'instant je ne me sers que de celles-ci.

Je vais peut être me fouler d'un post sur le github de cette library. :geek:
Un bon frein vaut mieux que deux sparadraps.

Ubuntu 24.04.3 LTS - Domoticz 2025.1 (build 16782)
Pentium G3220T 2.6GHz - 4Go DDR3
RFXtrx433 USB Ver Pro1/1043
Z-Stick GEN5 Version: 1.6-1136-g07ea22bb
Rtl433 RTL-SDR receiver
RFLink Gateway
Zigbee SLZB-06
ayziaa
Messages : 25
Inscription : 29 janv. 2017, 17:16

Re: [Python][TUTO] Bose Soundtouch Multiroom depuis domoticz

Message par ayziaa »

Wow! Pour quelqu'un qui connaissait pas le Python t'es plutôt doué :geek:

edit: pourrais tu m'indiquer dans quel répertoire tu trouve le device.py?
Chrominator
Messages : 1042
Inscription : 19 déc. 2015, 07:29
Localisation : France

Re: [Python][TUTO] Bose Soundtouch Multiroom depuis domoticz

Message par Chrominator »

Sur mon pi (ubuntu), le script s'est logé dans /usr/local/lib/python2.7/dist-packages/libsoundtouch/device.py

Si locate est installé sur ton système, ça peut aider à le trouver.
Tu tapes locate device.py et ça te montre la liste de tous les device.py de ton système.
Un bon frein vaut mieux que deux sparadraps.

Ubuntu 24.04.3 LTS - Domoticz 2025.1 (build 16782)
Pentium G3220T 2.6GHz - 4Go DDR3
RFXtrx433 USB Ver Pro1/1043
Z-Stick GEN5 Version: 1.6-1136-g07ea22bb
Rtl433 RTL-SDR receiver
RFLink Gateway
Zigbee SLZB-06
lreymond
Messages : 18
Inscription : 25 nov. 2016, 20:06

Re: [Python][TUTO] Bose Soundtouch Multiroom depuis domoticz

Message par lreymond »

ayziaa a écrit :Oui mais quand tu exécute le script en ligne de commande, ça te donne toujours la même chose après avoir désinstaller et réinstaller la librairie?
voici le résultat après desinstallation de la librairie:

Code : Tout sélectionner

pi@raspberrypi:~/domoticz/scripts/python $ python bose_off.py
Traceback (most recent call last):
  File "bose_off.py", line 4, in <module>
    import libsoundtouch
ImportError: No module named libsoundtouch
voici le résultat après reinstallation de la librairie

Code : Tout sélectionner

pi@raspberrypi:~/domoticz/scripts/python $ python bose_off.py
Traceback (most recent call last):
  File "bose_off.py", line 4, in <module>
    import libsoundtouch
  File "/usr/local/lib/python2.7/dist-packages/libsoundtouch/__init__.py", line 11, in <module>
    from zeroconf import Zeroconf, ServiceBrowser
  File "/usr/local/lib/python2.7/dist-packages/zeroconf.py", line 38, in <module>
    import netifaces
ImportError: No module named netifaces
ayziaa
Messages : 25
Inscription : 29 janv. 2017, 17:16

Re: [Python][TUTO] Bose Soundtouch Multiroom depuis domoticz

Message par ayziaa »

Essaye ça alors

Code : Tout sélectionner

sudo pip install netifaces
Il te manque des trucs parceque il trouve pas un import dans le Python.
lreymond
Messages : 18
Inscription : 25 nov. 2016, 20:06

Re: [Python][TUTO] Bose Soundtouch Multiroom depuis domoticz

Message par lreymond »

ayziaa a écrit :Essaye ça alors

Code : Tout sélectionner

sudo pip install netifaces
Il te manque des trucs parceque il trouve pas un import dans le Python.

voici mon nouveau test

Code : Tout sélectionner

pi@raspberrypi:~ $ sudo pip install netifaces
Downloading/unpacking netifaces
  Downloading netifaces-0.10.6.tar.gz
  Running setup.py (path:/tmp/pip-build-e5Bvgl/netifaces/setup.py) egg_info for package netifaces
    
Installing collected packages: netifaces
  Running setup.py install for netifaces
    checking for getifaddrs...found.
    checking for getnameinfo...found.
    checking for IPv6 socket IOCTLs...not found.
    checking for optional header files...netash/ash.h netatalk/at.h netax25/ax25.h neteconet/ec.h netipx/ipx.h netpacket/packet.h linux/irda.h linux/atm.h linux/llc.h linux/tipc.h linux/dn.h.
    checking whether struct sockaddr has a length field...no.
    checking which sockaddr_xxx structs are defined...at ax25 in in6 ipx un ash ec ll atmpvc atmsvc dn irda llc.
    checking for routing socket support...no.
    checking for sysctl(CTL_NET...) support...no.
    checking for netlink support...yes.
    will use netlink to read routing table
    building 'netifaces' extension
    arm-linux-gnueabihf-gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fno-strict-aliasing -D_FORTIFY_SOURCE=2 -g -fstack-protector-strong -Wformat -Werror=format-security -fPIC -DNETIFACES_VERSION=0.10.6 -DHAVE_GETIFADDRS=1 -DHAVE_GETNAMEINFO=1 -DHAVE_NETASH_ASH_H=1 -DHAVE_NETATALK_AT_H=1 -DHAVE_NETAX25_AX25_H=1 -DHAVE_NETECONET_EC_H=1 -DHAVE_NETIPX_IPX_H=1 -DHAVE_NETPACKET_PACKET_H=1 -DHAVE_LINUX_IRDA_H=1 -DHAVE_LINUX_ATM_H=1 -DHAVE_LINUX_LLC_H=1 -DHAVE_LINUX_TIPC_H=1 -DHAVE_LINUX_DN_H=1 -DHAVE_SOCKADDR_AT=1 -DHAVE_SOCKADDR_AX25=1 -DHAVE_SOCKADDR_IN=1 -DHAVE_SOCKADDR_IN6=1 -DHAVE_SOCKADDR_IPX=1 -DHAVE_SOCKADDR_UN=1 -DHAVE_SOCKADDR_ASH=1 -DHAVE_SOCKADDR_EC=1 -DHAVE_SOCKADDR_LL=1 -DHAVE_SOCKADDR_ATMPVC=1 -DHAVE_SOCKADDR_ATMSVC=1 -DHAVE_SOCKADDR_DN=1 -DHAVE_SOCKADDR_IRDA=1 -DHAVE_SOCKADDR_LLC=1 -DHAVE_PF_NETLINK=1 -I/usr/include/python2.7 -c netifaces.c -o build/temp.linux-armv7l-2.7/netifaces.o
    netifaces.c:1:20: fatal error: Python.h: Aucun fichier ou dossier de ce type
     #include <Python.h>
                        ^
    compilation terminated.
    error: command 'arm-linux-gnueabihf-gcc' failed with exit status 1
    Complete output from command /usr/bin/python -c "import setuptools, tokenize;__file__='/tmp/pip-build-e5Bvgl/netifaces/setup.py';exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --record /tmp/pip-7PYEOX-record/install-record.txt --single-version-externally-managed --compile:
    running install

running build

running build_ext

checking for getifaddrs...found.

checking for getnameinfo...found.

checking for IPv6 socket IOCTLs...not found.

checking for optional header files...netash/ash.h netatalk/at.h netax25/ax25.h neteconet/ec.h netipx/ipx.h netpacket/packet.h linux/irda.h linux/atm.h linux/llc.h linux/tipc.h linux/dn.h.

checking whether struct sockaddr has a length field...no.

checking which sockaddr_xxx structs are defined...at ax25 in in6 ipx un ash ec ll atmpvc atmsvc dn irda llc.

checking for routing socket support...no.

checking for sysctl(CTL_NET...) support...no.

checking for netlink support...yes.

will use netlink to read routing table

building 'netifaces' extension

arm-linux-gnueabihf-gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fno-strict-aliasing -D_FORTIFY_SOURCE=2 -g -fstack-protector-strong -Wformat -Werror=format-security -fPIC -DNETIFACES_VERSION=0.10.6 -DHAVE_GETIFADDRS=1 -DHAVE_GETNAMEINFO=1 -DHAVE_NETASH_ASH_H=1 -DHAVE_NETATALK_AT_H=1 -DHAVE_NETAX25_AX25_H=1 -DHAVE_NETECONET_EC_H=1 -DHAVE_NETIPX_IPX_H=1 -DHAVE_NETPACKET_PACKET_H=1 -DHAVE_LINUX_IRDA_H=1 -DHAVE_LINUX_ATM_H=1 -DHAVE_LINUX_LLC_H=1 -DHAVE_LINUX_TIPC_H=1 -DHAVE_LINUX_DN_H=1 -DHAVE_SOCKADDR_AT=1 -DHAVE_SOCKADDR_AX25=1 -DHAVE_SOCKADDR_IN=1 -DHAVE_SOCKADDR_IN6=1 -DHAVE_SOCKADDR_IPX=1 -DHAVE_SOCKADDR_UN=1 -DHAVE_SOCKADDR_ASH=1 -DHAVE_SOCKADDR_EC=1 -DHAVE_SOCKADDR_LL=1 -DHAVE_SOCKADDR_ATMPVC=1 -DHAVE_SOCKADDR_ATMSVC=1 -DHAVE_SOCKADDR_DN=1 -DHAVE_SOCKADDR_IRDA=1 -DHAVE_SOCKADDR_LLC=1 -DHAVE_PF_NETLINK=1 -I/usr/include/python2.7 -c netifaces.c -o build/temp.linux-armv7l-2.7/netifaces.o

netifaces.c:1:20: fatal error: Python.h: Aucun fichier ou dossier de ce type

 #include <Python.h>

                    ^

compilation terminated.

error: command 'arm-linux-gnueabihf-gcc' failed with exit status 1

----------------------------------------
Cleaning up...
Command /usr/bin/python -c "import setuptools, tokenize;__file__='/tmp/pip-build-e5Bvgl/netifaces/setup.py';exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --record /tmp/pip-7PYEOX-record/install-record.txt --single-version-externally-managed --compile failed with error code 1 in /tmp/pip-build-e5Bvgl/netifaces
Storing debug log for failure in /root/.pip/pip.log
pi@raspberrypi:~ $ cd domoticz/scripts/python
pi@raspberrypi:~/domoticz/scripts/python $ python bose_off.py
Traceback (most recent call last):
  File "bose_off.py", line 4, in <module>
    import libsoundtouch
  File "/usr/local/lib/python2.7/dist-packages/libsoundtouch/__init__.py", line 11, in <module>
    from zeroconf import Zeroconf, ServiceBrowser
  File "/usr/local/lib/python2.7/dist-packages/zeroconf.py", line 38, in <module>
    import netifaces
ImportError: No module named netifaces
Chrominator
Messages : 1042
Inscription : 19 déc. 2015, 07:29
Localisation : France

Re: [Python][TUTO] Bose Soundtouch Multiroom depuis domoticz

Message par Chrominator »

Bon c'est bien rigolo Python finalement, je me suis régalé.

J'ai fait deux petits scripts pour Domoticz.

Le premier en Python ne fait pas grand chose d'autre que ce que fait celui de ayziaa, mais accepte des paramètres pour indiquer l'enceinte Bose maîtresse en multiroom, le niveau sonore au démarrage, la présélection à jouer.
Ce même script permet aussi d'éteindre les enceintes.

On le lance comme ça : bose_all.py --master bureau --volume 10 --preset 6 --switch on

Ah et puis il écrit ce qu'il fait dans le log de domoticz, c'est pratique.

Le second appelle le premier et sera typiquement sur un interrupteur dans Domoticz.
C'est un script shell tel que recommandé dans le WIKI domoticz (voir scripting in Domoticz); il lance en batch le script Python pour libérer Domoticz et ne pas attendre la fin... ;)

Tuto :
Comme j'utilise Python3 avec ce script, il faut :
  • Installer la bibliothèque libsoundtouch pour Python3 :

    Code : Tout sélectionner

    sudo -H pip3 install libsoudtouch
  • Décomprimer le tarball exec.tgz dans domoticz/scripts (si vous n'en avez pas déjà un)

    Code : Tout sélectionner

    cd /home/pi/domoticz/scripts
    tar xvf exec.tgz
  • Décomprimer le tarball bose_all.tgz dans domoticz/scripts/python

    Code : Tout sélectionner

    cd /home/pi/domoticz/scripts/python
    tar xvf bose_all.tgz
  • Créer un interrupteur dans Domoticz sur un device virtuel
  • Dans les rubriques Action On et Action Off, indiquer le script exec.sh avec comme paramètres le lancement de bose_all.py :

    Exemple pour Action On :

    Code : Tout sélectionner

    script://exec.sh python/bose_all.py --master bureau --volume 10 --preset 6 -s on
    Exemple pour Action Off

    Code : Tout sélectionner

    script://exec.sh python/bose_all.py -s off
  • Sauvegarder
Normalement ça fonctionne.
Pour vérifier, inspecter le log de Domoticz :
Capture.PNG
Capture.PNG (14.06 Kio) Consulté 7914 fois
Je joins le code en clair ici, pour le coup d'oeil.
Ah oui c'est en Python3, à cause d'un import que j'utilise.

Code : Tout sélectionner

#!/usr/bin/python3
# -*- coding: utf-8 -*-
'''-------------------------------------------------------------------------------------------------

 Script permettant de créer une zone multiroom Bose avec toutes les enceintes
 Auteur : Chrominator
 Version 0.1
 Changes :
			 2017-11-26 : Version initiale

 Principe : Récupère les paramètres enceinte maîtresse, volume du son, présélection
            et crée une zone multiroom avec toutes les enceintes trouvées.
            les paramètres sont --volume <volume_level> --master <master_device> --preset <# preset> --switch <switch on or off>               
            Le switch on crée la zone multiroom, off la supprime et éteint les enceintes.
            
 exemples :     bose_all.py --master bureau --volume 10 --preset 6 -s on
                bose_all.py -s off

-------------------------------------------------------------------------------------------------'''


import sys, getopt
import os
import urllib
import requests
import libsoundtouch    # https://github.com/CharlesBlonde/libsoundtouch

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

from libsoundtouch.device import NoSlavesException, NoExistingZoneException, \
    Preset, Config, SoundTouchDevice, SoundtouchInvalidUrlException

from urllib.request import urlopen

URL_DOMOTICZ = 'http://127.0.0.1:8080/json.htm?type='
    
def DomoticzData(cmd, get=False):
        url = URL_DOMOTICZ + cmd
        result = requests.get(url)

def SendMessage(msg, colour="SaddleBrown", bg="LemonChiffon"):
    data = urllib.parse.quote('<COLOR="' + colour + '" style="BACKGROUND-COLOR: ' + bg + '">' + os.path.basename(__file__) + ' : ' + msg + '</font>')
    cmd  = "command&param=addlogmessage&message=" + data
    DomoticzData(cmd)
    print(os.path.basename(__file__) + ' : ' + msg)

def main(argv):
    volume = ''         # Niveau de volume initial pour la création du groupe multiroom, de 1 à 100.
    masterdev = ''      # Nom de l'enceinte qui commandera le groupe multiroom.
    master = ''         
    chaine = ''         # Preset à sélectionner pour le multiroom : de 1 à 6, correspondent aux boutons de la télécommande.
    switch = ''         # Spécifie si on crée le multiroom ou si on le supprime : ON ou OFF.
    
    try:
        opts, args = getopt.getopt(argv,"hv:m:p:s:",["volume=","master=","preset=","switch="])
    except getopt.GetoptError:
        SendMessage (os.path.basename(__file__) + ' -v <volume_level> -m <master_device> -p <preset> -s <switch on or off>')
        sys.exit(2)
    for opt, arg in opts:
        if opt == '-h':
            SendMessage (os.path.basename(__file__) + ' -v <volume_level> -m <master_device> -p <preset> -s <switch on or off>')
            sys.exit()
        elif opt in ("-v", "--volume"):
            volume = arg
        elif opt in ("-p", "--preset"):
            chaine = arg
        elif opt in ("-s", "--switch"):
            switch = arg
        elif opt in ("-m", "--master"):
            masterdev = arg
    if switch.upper() not in ("ON", "OFF"):
        SendMessage('--switch : Le switch doit être ON ou OFF')
        sys.exit(2)

    devices = discover_devices(timeout=5)

    for device in devices:
        SendMessage ("Enceinte " + device.config.name + " mise hors fonction.")
        device.power_off()
        
    if switch.upper() != "ON":
        SendMessage ("Arrêt du multiroom.")
        sys.exit(2)

    if masterdev == '':
        SendMessage ('Aucune enceinte maître définie : Arrêt du programme')
        sys.exit(2)
    if chaine == '':
        chaine = '0'
        SendMessage ('Présélection sélectionnée par défaut')
        
    elif not chaine.isnumeric():
        SendMessage ('Présélection ' + chaine + ' invalide. Valeurs autorisées de 1 à 6')
        sys.exit(2)
        
    elif int(chaine) not in range(1, 7):
        SendMessage ('Préselection ' + chaine + ' invalide. Valeurs autorisées de 1 à 6')
        sys.exit(2)
   
    if volume == '':
        volume = '20'
        SendMessage ('Volume sélectionné par défaut.')
    elif not volume.isnumeric():
        SendMessage ('Le volume doit être compris entre 1 et 100.')
        sys.exit(2)
    elif int(volume) not in range(1, 101):
        SendMessage ('Le volume doit être compris entre 1 et 100.')
        sys.exit(2)

    SendMessage ('Preset is set to ' + chaine)
    SendMessage ('Volume is set to ' + volume)
    SendMessage ('Master device is set to ' + masterdev)

    #Noms des enceintes et types
    for device in devices:
        if device.config.name.upper() == masterdev.upper():
            device.create_zone(devices)
            master = device
            
        device.set_volume(int(volume))
        SendMessage ('Device trouvé: ' + device.config.name + " - " + device.config.type + " - volume forcé à " + str(device.volume().actual))
    
    if master == '':
        SendMessage ("L'enceinte " + masterdev + " n'existe pas. Arrêt du programme.")
    else:
        # Nombre d'enceintes en multiroom
        SendMessage ('Multiroom activé sur ' + str(len(devices)) + ' enceintes.')
    
        #ici on récupere les presets
        presets = master.presets()

        index = int(chaine)-1
        SendMessage ('Préselection n°' + chaine + ' choisie : ' + presets[index].name + ' ' + presets[index].source)

        master.select_preset(presets[index]) # 6 presets de 0 à 5


if __name__ == "__main__":
    main(sys.argv[1:])
TODO : Disposer d'un objet comme ceux qui viennent par KODI en upnp dans Domoticz et disposer de la navigation, musique suivante, précédente, changer le volume, etc..., mais là j'ai pas encore le niveau :lol:
Capture3.PNG
Capture3.PNG (57.17 Kio) Consulté 7914 fois
Pour éviter les indentations intempestives, voici les scripts en tarball.
bose_all.tgz
(2.01 Kio) Téléchargé 387 fois
exec.tgz
(190 octets) Téléchargé 381 fois
Un bon frein vaut mieux que deux sparadraps.

Ubuntu 24.04.3 LTS - Domoticz 2025.1 (build 16782)
Pentium G3220T 2.6GHz - 4Go DDR3
RFXtrx433 USB Ver Pro1/1043
Z-Stick GEN5 Version: 1.6-1136-g07ea22bb
Rtl433 RTL-SDR receiver
RFLink Gateway
Zigbee SLZB-06
Turismo
Messages : 16
Inscription : 27 avr. 2017, 22:42

Re: [Python][TUTO] Bose Soundtouch Multiroom depuis domoticz

Message par Turismo »

Bonjour à tous !
Je suivais le topic depuis quelques jours et j'ai décidé de me lancer, après plusieurs patinages résolu, il me reste un dernier problème (j'ai suivi le tuto de Chrominator).
J'arrive sans problèmes à lancer le script "bose_all.py" directement en ligne de commande avec

Code : Tout sélectionner

python3 bose_all.py --master salon --volume 20 --preset 6 -s on
la même commande avec python au lieu de python3 me renvoie vers un message d'erreur

Code : Tout sélectionner

pi@raspberrypi:~/domoticz/scripts/python$ python bose_all.py --master salon --volume 20 --preset 6 -s on
Traceback (most recent call last):
  File "bose_all.py", line 25, in <module>
    import requests
ImportError: No module named requests
Par contre, impossible de lancer le bose depuis Domoticz, mais je n'ai pas de messages d'erreur non plus.

Code : Tout sélectionner

2017-12-03 02:03:53.800 User: Admin initiated a switch command (558/Bose test/On)
2017-12-03 02:03:54.032 Executing script: /home/pi/domoticz/scripts/exec.sh
Vu que mes connaissances en Python sont très limitées, je tourne un peu en rond.
S jamais quelqu'un à une solution à mon problème ...
Répondre