Script python dont le résultat MQTT n'est pas le même entre bash et systemctl start

Hello,

Je vous sollicite pour savoir si vous avez déjà été confrontés à ce genre de problème : je dispose d’un script python sur une machine PI3B+ distante de mon serveur HA supervised sur PI4. Le github GitHub - kbowman-newedenconsultinggroup/sounddetector sur lequel je m’appuie propose de lancer un service « sounddetector » qui exécute un script python « sounddetector.py »; j’ai bien-sûr fait toutes les déclarations nécessaires pour configurer sounddetector.service

switch:
  - platform: mqtt
    name: "Sound Detector"
    command_topic: "sounddetector/cmds"
    state_topic: "sounddetector/state"
    payload_on: "systemctl start sounddetector"
    payload_off: "systemctl stop sounddetector"
    state_off: "offline"
    state_on: "online"
    icon: mdi:music-note

Le service sounddetector.service est défini de la manière suivante :

[Unit]
After=networking.service

[Service]
User=pi
Group=pi
WorkingDirectory=/home/pi/home/sounddetector
ExecStart=/home/pi/home/sounddetector/sounddetector.py

[Install]
WantedBy=default.target

D’une, transitant via MQTT je me dis que je n’ai pas à passer par un ssh -i /config/.ssh/id_rsa -o StrictHostKeyChecking=no -p 22 pi@192.168.1.32 'sudo systemctl start sounddetector'

De deux, il s’avère que quand j’exécute sur machine distante un « python3 sounddetector.py » je vois bien passer les publications en surveillant le MQTT; mais quand j’exécute un sudo systemctl start sounddetector je vois bien qu’il démarre sounddetector.py, mais je ne vois aucune publication MQTT arrivée :frowning: Savez-vous d’où cela peut venir ?
Merci d’avance pour vos aide et conseil

pour info, le code du sounddetector.py ressemble à ça :

#!/usr/bin/env python
#Tone detection shamelessly stolen from:
#https://benchodroff.com/2017/02/18/using-a-raspberry-pi-with-a-microphone-to-hear-an-audio-alarm-using-fft-in-python/
#
# this entire thing stolen from  Sufficiently-Advanced (Allen Pan)
#
import pyaudio
from numpy import zeros,linspace,short,frombuffer,hstack,transpose,log
from scipy.fftpack import fft
from time import sleep
from collections import deque
import paho.mqtt.client as mqtt
import requests
import datetime
import time
import sys
import signal
from secrets import mqtthost, mqttuser, mqttpass

def terminateProcess(signalNumber, frame):
    client.publish("sounddetector/state", "offline")
    sys.exit()

if __name__ == '__main__':
    # register the signals to be caught
    signal.signal(signal.SIGTERM, terminateProcess)

#mqtt stuff
client = mqtt.Client(client_id="sounddetector")
client.username_pw_set(username=mqttuser,password=mqttpass)
client.connect(mqtthost,1883,300)

#Volume Sensitivity, 0.05: Extremely Sensitive, may give false alarms
#             0.1: Probably Ideal volume
#             1: Poorly sensitive, will only go off for relatively loud
SENSITIVITY= 1

#Bandwidth for detection (i.e., detect frequencies within this margin of error of the TONE)
BANDWIDTH = 10
#How many 46ms blips before we declare a beep? (Take the beep length in ms, divide by 46ms, subtract a bit)
beeplength=5
# How many beeps before we declare a tone?
tonelength=5
# How many false 46ms blips before we declare the alarm is not ringing
resetlength=8
# How many reset counts until we clear an active alarm?
clearlength=10
# Enable blip, beep, and reset debug output
debug=True
# Show the most intense frequency detected (useful for configuration)
frequencyoutput=True

blipcount=0
beepcount=0
resetcount=0
clearcount=0
tone=True

# what devices are we using
if debug:
    pd = pyaudio.PyAudio()
    info = pd.get_host_api_info_by_index(0)
    numdevices = info.get('deviceCount')
    for i in range(0, numdevices):
        if (pd.get_device_info_by_host_api_device_index(0, i).get('maxInputChannels')) > 0:
            print ("Input Device id ", i, " - ", pd.get_device_info_by_host_api_device_index(0, i).get('name'))

# Alarm frequencies (Hz) to detect (Use audacity to record a wave and then do Analyze->Plot Spectrum)
D1 = 1260
D2 = 470
F = 940
G = 1130
D5 = 1420
#These numbers work for my ocarina in my house with a blue yeti, ymmv
minD1 = D1-BANDWIDTH
maxD1 = D1+BANDWIDTH
minD2 = D2-BANDWIDTH
maxD2 = D2+BANDWIDTH
minF = F-40
maxF = F+BANDWIDTH
minG = G-BANDWIDTH
maxG = G+BANDWIDTH
minD5 = D5-BANDWIDTH
maxD5 = D5+BANDWIDTH

# Song note sequences
doorbell = deque(['D1','D1','D1','D1','D1','D1'])
test = deque(['D2','F','D2','F','D2','F'])
#heard note sequence deque
notes = deque(['F','F','F','F','F','F'], maxlen=6)

# Show the most intense frequency detected (useful for configuration)
frequencyoutput=True
freqNow = 1.0
freqPast = 1.0

#Set up audio sampler - 
NUM_SAMPLES = 1024
SAMPLING_RATE = 44100 #make sure this matches the sampling rate of your mic!
pa = pyaudio.PyAudio()
_stream = pa.open(format=pyaudio.paInt16,
                  channels=1, rate=SAMPLING_RATE,
                  input=True,
                  frames_per_buffer=NUM_SAMPLES)

if debug: print("Alarm detector working. Press CTRL-C to quit.")
client.publish("sounddetector/state", "online")
client.publish("sounddetector/doorbell_detected", "false")

while True:
    timeout = 1 # report in to mqtt every 30 seconds
    timeout_start = time.time()
    client.publish("sounddetector/doorbell_detected", "false")
    while time.time() < timeout_start + timeout:
        test = 0
        if test == 5:
            break
        test -= 1

        while _stream.get_read_available()< NUM_SAMPLES: sleep(0.05)
        audio_data  = frombuffer(_stream.read(
            _stream.get_read_available()), dtype=short)[-NUM_SAMPLES:]
        # Each data point is a signed 16 bit number, so we can normalize by dividing 32*1024
        normalized_data = audio_data / 32768.0
        intensity = abs(fft(normalized_data))[:NUM_SAMPLES//2]
        frequencies = linspace(0.0, float(SAMPLING_RATE)//2, num=NUM_SAMPLES//2) 
        if frequencyoutput:
          which = intensity[1:].argmax()+1
          # use quadratic interpolation around the max
          if which != len(intensity)-1:
            y0,y1,y2 = log(intensity[which-1:which+2:])
            x1 = (y2 - y0) * .5 / (2 * y1 - y2 - y0)
            # find the frequency and output it
            #freqPast = freqNow
            freqNow = (which+x1)*SAMPLING_RATE/NUM_SAMPLES
          else:
            freqNow = which*SAMPLING_RATE/NUM_SAMPLES

        if debug: print ("\t\t\t\tfreq=",freqNow) #,"\t",freqPast)
#        if debug: print ("\t\t\t\tnotes=",notes)

        if max(intensity[(frequencies < D1+BANDWIDTH) & (frequencies > D1-BANDWIDTH )]) > max(intensity[(frequencies < D1-1000) & (frequencies > D1-2000)]) + SENSITIVITY:
          blipcount+=1
          resetcount=0
          if debug: print ("\t\tBlip",blipcount)
          if (blipcount>=beeplength):
            blipcount=0
            resetcount=0
            beepcount+=1
            if debug: print ("\tBeep",beepcount,freqNow)
            if (beepcount>=tonelength):
                clearcount=0
                tone=True
                if debug: print ("ToneDetected")
                now = datetime.datetime.now()
                ringtime="{\"datetime\":\"" + now.strftime("%Y-%m-%d %H:%M:%S") + "\"}"
                client.publish("sounddetector/doorbell", ringtime)
                client.publish("sounddetector/doorbell_detected", "true")
                sleep(1)
                beepcount=0
        else:
            blipcount=0
            resetcount+=1
            if debug: print ("\t\t\treset",resetcount)
            if (resetcount>=resetlength):
                resetcount=0
                beepcount=0
                if tone:
                    clearcount+=1
                    if debug: print ("\t\tclear",clearcount)
                    if clearcount>=clearlength:
                        clearcount=0
                        if debug: print ("Tone Too Short - Cleared")
                        tone=False

    client.publish("sounddetector/state", "online")

Salut,

que se passe t’il quand tu vas sur ta machine pour exécuter directement "./sounddetector.py " en enlevant le « python3 » ?
Et sinon tu as des erreurs dans « /var/log/syslog »?
(je ne suis pas certain du fichier de logs, ça dépend de la version de rapberry pi os)

Bonjour,

Pour moi la declaration de ton switch dans ha n’est pas bonne.
Les « payload_on » et « payload_off » sont senses etre le retour d’etat de ton ‹ state_topic › à l’action du switch.
Je comprends pas ce que viennent faire ici les commandes systemctl.

Salut,

@Christianb233 a mis 2 choses dans son message, d’un côté le sensor HA qui va recevoir le message MQTT. De l’autre le programme python et la config du service sur la machine qui le fera tourner.

Mais effectivement le HA ne colle pas complètement au python.
Il manque le topic « sounddetector/doorbell_detected » côté HA.
Je ne vois pas non plus le topic « sounddetector/cmds » dans le python.
Et le « sounddetector/state » semple plus être un topic d’avialability que d’état.

Salut @AlexHass
Concernant « ./sounddetector.py », ça marche bien avec des publications MQTT.
Il déclare malgré tout une erreur dans le /var/log/syslog de type

Dec  8 00:33:16 raspberrypi kernel: [176890.509887] bcm2835-i2s 3f203000.i2s: I2S SYNC error!

Même message dans le /var/log/syslog si j’exécute « sudo systemctl start sounddetector.service »

@vdomos @AlexHass oui je confirme :
1- le sounddetector/state est juste une publi. d’état
2- il existe des sensors MQTT sous HA pour publier si la sonnerie est reconnue « doorbell detected » et à quelle heure « doorbell detector »:

mqtt:
  sensor:
    - name: "Doorbell Detector"
      state_topic: "sounddetector/doorbell"
      value_template: "{{ value_json.datetime }}"
    - name: "Doorbell Detected"
      state_topic: "sounddetector/doorbell_detected"
      value_template: "{{ value }}"

Et tu es certain de n’avoir qu’un seul script python qui tourne à un moment donné?
En MQTT à tout moment il ne peut y avoir plus d’un client avec un « client_id » donné.

#mqtt stuff
client = mqtt.Client(client_id="sounddetector")
client.username_pw_set(username=mqttuser,password=mqttpass)
client.connect(mqtthost,1883,300)

il y a d’autres script python que « sounddetector »; mais quand je turn on le switch « sound detector » je vois passer la publication cmd « sudo systemctl start sounddetector.service » mais rien d’autre et un « sudo systemctl status sounddetector.service » n’est pas en Active (running)


De toute façon, quand je fais un « sudo systemctl start service » sur la machine distante ne fait publier que 2 « doorbell_detected » (comme si, je ne m’y connais pas trop en pyhton, mais la boucle permanente « while: True » n’était pas exécutée en appel service alors qu’elle fonctionne en exécution python3 (dans ce cas doorbell selected est publiée toutes les secondes à l’état false, et éventuellement true quand le son est reconnu)
De plus, par rapport au client « sounddetector » je ne vois pas d’autres déclarations de ce « client_ID » autre que dans ce fichier « sounddetector.py » (et si c’était un pb de « client_ID » ça ne fonctionnerait pas en exécutant sounddetector via python)

peut-être un indice, et encore, je n’en suis pas si sûr ; dans journalctl je trouve ça (alors que le service déclare pi comme User

déc. 09 00:57:49 raspberrypi sudo[2513]:       pi : TTY=pts/0 ; PWD=/home/pi/sounddetector ; USER=root ; COMMAND=/bin/systemctl start sounddetector.service
déc. 09 00:57:49 raspberrypi sudo[2513]: pam_unix(sudo:session): session opened for user root by pi(uid=0)
déc. 09 00:57:50 raspberrypi systemd[1]: Started sounddetector.service.
déc. 09 00:57:50 raspberrypi sudo[2513]: pam_unix(sudo:session): session closed for user root
d

Hello,
En poursuivant mes investigations, je pense que le service arrête l’exécution du script car celui délivre des erreurs. Pour autant, le python3 sounddetector.py affiche lui aussi des erreurs mais ça ne l’empêche pas de poursuivre la boucle infinie while: True
Voyez-vous comment forcer le service à ne pas interrompre le script quand il rencontre des erreurs ?