PTT Schaltung: Audiosignal

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Antworten
mmueller-87
User
Beiträge: 1
Registriert: Sonntag 11. Juni 2023, 07:25

Einen schönen Sonntag an alle,
ich brauche eure Fachmännische Beratung. Meine Kenntnisse in Python halten sich in Grenzen, da ich mal vor 20 Jahren angefangen habe und lange Pause hatte. Deshalb nimmt es mir nicht übel, wenn ich nicht immer folgen kann. Ich habe ein kleines Projekt, das ich gerade anstrebe. Ich bin leidenschaftlicher Funker und nutzte bis vor kurzem noch ein Windows System mit einer VOIP Software, um die Sprachübertragung von einem VOIP Client auf das Funkgerät zu übertragen. Funktioniert soweit ganz gut. Nun habe ich ein kleines Skript, das mein Audiosignal der Soundkarte abfragt und beim anliegen ein DTR / RTS Pin schaltet.

Auf mein neues Ubuntu Linux System funktioniert das soweit auch ganz gut und habe da auch ein kleines Skript, das ich nun noch anpassen möchte. Dazu habe ich allerdings noch ein paar Fragen. Hier der bisherige Code, der bisher seine Arbeit sehr gut verrichtet (allerdings nur bei Ubuntu, beim Raspberry Pi habe ich allerdings Probleme zwecks der fehlenden "alsaaudio" Bibliotheken.)

Code: Alles auswählen

#!/usr/bin/python
import alsaaudio
import audioop
import serial
import time

# -------------------------------------------------------------------
#
# Einstellungen
#
# -------------------------------------------------------------------
# COM Port
com = '/dev/ttyUSB1'
# Level wann auf Durchgang geschalten wird
dbm = 0
# Sendeverzoegerung
delay = 0
# PTT nach Level halten
level = 100
# Sende und Empfangslevel
dbr = 0
dbx = 0

# -------------------------------------------------------------------
#
# RS232: initalisieren
#
# -------------------------------------------------------------------
rs232 = serial.Serial(str(com), baudrate = 9600, timeout = 3.0)

# -------------------------------------------------------------------
#
# ALSA: initalisieren
#
# -------------------------------------------------------------------
audio = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK,
    channels = 2,
    rate = 8000,
    format = alsaaudio.PCM_FORMAT_S8,
    periodsize = 200)

# -------------------------------------------------------------------
#
# Programminfo
#
# -------------------------------------------------------------------
print('PTT RTS / DTR Pin Schaltung: Zum beenden der PTT Schaltung einfach dieses Konsolenfenster schließen.')
print('----------------------------------------------------------------------------------------------------')
print('COM Schnittstelle: ' + str(com) + '\nDurchgang bei: ' +
      str(dbm) + ' db\nPTT nach Level halten: ' +
      str(level) + ' Millisekunden\nSendeverzg: ' +
      str(delay) + ' Millisekunden\n')

# -------------------------------------------------------------------
#
# RTS/DTR: initalisieren
#
# -------------------------------------------------------------------
while True:
    try:
        len, data = audio.read()
        if audioop.avgpp(data, 1) > dbm:
            dbx = dbx + 2
            if dbx > delay:
                dbx = dbx - 1
                dbr = 0
                rs232.setDTR(1)
                rs232.setRTS(1)
            time.sleep(.025)
        else:
            dbr = dbr + 2
            if dbr >= level:
                dbr = dbr - 1
                dbx = 0
                rs232.setDTR(0)
                rs232.setRTS(0)
        time.sleep(.010)
    except Exception as e:
        print(e)
Nun meine Fragen dazu:
Ich würde gerne den Status der Schaltung im Konsolenfenster angezeigt bekommen, der mir sagt, ob der DTR / RTS Pin geschaltet wurde. Schön wäre es auch, wenn ich beim starten des Skriptes in der Konsole auswählen kann, welche Soundkarte und welche COM Schnittstelle ich nutzen möchte und diese ggf. prüft, ob diese auch angeschlossen sind. Soweit reichen meine Kenntnisse leider nicht aus, um das umzusetzen.

Kann mir einer behilflich sein?
Benutzeravatar
__blackjack__
User
Beiträge: 13533
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mmueller-87: Als erstes würde ich das mal überarbeiten und ins Jahr 2023 holen. Also eine She-Bang-Zeile die Python 3 enthält, denn zumindest auf dem Raspi besteht sonst die Gefahr, dass das mit Python 2.7 ausgeführt wird.

Solche Trennlinienkommentare macht man eigentlich nicht. Und dafür das die dadurch besonders hervorgehoben sind, stehen da sehr triviale Dinge drin die man nicht kommentieren würde. Und dann auch noch teilweise falsche Informationen.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Namen sollten keine kryptischen Abkürzungen enthalten oder gar nur daraus bestehen. Das `level`, `dbx` und `dbr` für *Zeitspannen* stehen ist verwirrend. Bei allen drei Namen hätte ich erst mal auf Signalstärken getippt. Der Kommentar zu `dbx` und `dbr` behauptet das ja sogar.

Konstanten werden KOMPLETT_GROSS geschrieben, damit man sie leicht von Variablen unterscheiden kann.

Wenn man Namen kurz kommentiert was sie bedeuten, macht es meistens Sinn sich noch mal über den Namen gedanken zu machen. Den Kommentar ``# COM Port`` kann man sich beispielsweise sparen wenn man das nicht `com` sondern tatsächlich `com_port` nennt. Wobei das ein recht PC und dort auch noch DOS/Windows-lastiger Name ist. `serial_port` trifft es allgemeiner.

`dbx` und `dbr` werden viel zu weit von der Stelle definiert an der sie letztendlich verwendet werden. Das macht das Programm unübersichtlicher und es wird aufwändiger zum Beispiel die Hauptschleife in eine eigene Funktion heraus zu ziehen.

Das zusammenstückeln von Zeichenketten und Werten mittels ``+`` und `str()` ist eher BASIC als Python. Dafür gibt es die `format()`-Methode auf Zeichenketten und f-Zeichenkettenliterale. Der Code für die Info-Ausgabe ist auch sehr unübersichtlich formatiert.

Einfach das Fenster zu schliessen ist keine gute Art ein Programm abzubrechen. Üblicherweise macht man das bei Konsolenprogrammen mit Strg+C.

Sowohl die serielle Schnittstelle als auch das Audiogerät sollte man am Ende sauber wieder schliessen. `Serial`-Objekte sind Kontextmanager, die kann man direkt mit der ``with``-Anweisung verwenden. Beim `PCM`-Objekt hilft `contextlib.closing()`.

`com` ist bereits eine Zeichenkette, die braucht man nicht noch mal mit `str()` in eine Zeichenkette umwandeln.

Das `timeout`-Argument macht keinen Sinn wenn man nur DTR/DTS schalten will.

`len` ist der Name einer eingebauten Funktion, den sollte man nicht an etwas anderes binden. Der Wert wird ja auch gar nicht verwendet.

Das ``try``/``execpt`` was einfach alles ignoriert, hat da in der Schleife nichts zu suchen.

`setDTR()`/`setDTS()` sind veraltet, die verwendet man nicht mehr. `Serial`-Objekte haben für diese beiden Leitungen Properties.

`dbr`/`dbx` sind komisch. Mit beiden wird im Grunde das selbe gemacht: Schleifendurchläufe gezählt. Ich habe mal versucht es durch *einen* Zähler auszudrücken, der immer dann losrennt wenn die Signalstärke nicht zu DTR/DTS passt. Dabei ist ein ``>`` zu einem ``>=`` geworden und man muss da noch die Vergleichskonstante anpassen. Wobei auch das zusätzliche `sleep()` für den einen Fall raus geflogen ist. Die beiden Konstanten für die beiden Fälle hatten dadurch ja so gar nicht die gleiche Zeiteinheit und schon gar keine Millisekunden wie der Info-Text behauptet — bei einer Schleife die eine Verzögerung von mindestens 10 Millisekunden eingebaut hat.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/python
import audioop
import time
from contextlib import closing, suppress

import alsaaudio
import serial

SERIAL_PORT = "/dev/ttyUSB1"
TRIGGER_LEVEL = 0
SEND_DELAY = 0
PTT_HOLD_DELAY = 100


def main():
    with suppress(KeyboardInterrupt):
        text = (
            "PTT RTS / DTR Pin Schaltung:"
            " Zum beenden der PTT Schaltung Strg+C drücken."
        )
        print(text)
        print("-" * len(text))
        print(
            f"COM Schnittstelle: {SERIAL_PORT}\n"
            f"Durchgang bei: {TRIGGER_LEVEL} db\n"
            f"PTT nach Level halten: {PTT_HOLD_DELAY} Millisekunden\n"
            f"Sendeverzug: {SEND_DELAY} Millisekunden\n"
        )
        with serial.Serial(SERIAL_PORT) as rs232, closing(
            alsaaudio.PCM(
                alsaaudio.PCM_CAPTURE,
                alsaaudio.PCM_NONBLOCK,
                channels=2,
                rate=8000,
                format=alsaaudio.PCM_FORMAT_S8,
                periodsize=200,
            )
        ) as audio:
            loop_counter = None
            while True:
                _, data = audio.read()
                if (audioop.avgpp(data, 1) > TRIGGER_LEVEL) != rs232.dtr:
                    if loop_counter is None:
                        loop_counter = 0
                    else:
                        loop_counter += 1

                    if loop_counter >= (
                        SEND_DELAY if rs232.dtr else PTT_HOLD_DELAY
                    ):
                        rs232.dtr = rs232.dts = not rs232.dtr
                        loop_counter = None

                time.sleep(0.01)
                assert rs232.dtr == rs232.dts


if __name__ == "__main__":
    main()
Ich weiss auch gar nicht ob das so sinnvoll ist die Audiodaten nicht-blockierend zu lesen. Denn die Sample-Rate und Puffergrösse ergeben zusammen ja eine Verzögerung die man gut benutzen kann, und man hätte dann auch gleich immer gleich grosse Datenpakete über die man die Signalstärke ermittelt. Eventuell macht es auch Sinn das immer über die letzten beiden Datenpakete zu ermitteln.

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wenn du den Zustand deiner Erkennung wechselst, einfach eine Print-Ausgabe. Das löst das Anzeige-Problem. Und wenn du statt der generischen, durchnummerierten Namen ttyUSBx die unter /dev/serial/by-id benutzt, bekommst du stabil immer das passende, oder einen Fehler, wenn es nicht da ist.

Soundkarten und deren Aufzählung und wenn warum welche benutzt wird, ist mir zu anstrengend. Ich mag ALSA nicht.
Antworten