sábado, 30 de marzo de 2019

Transceptor de señales débiles, bit a bit (Parte 7)

La estrategia utilizada hasta ahora para ir implementando las distintas piezas es mas o menos repetitiva. Para la cadena receptora se comienza con las muestras que entrega un dongle RTL-SDR, el cual abarca un ancho de banda de mas o menos 600 KHz (podría hacerselo mas grande de ser necesario) y con distintos trucos para manipular la señal con bloques SDR se vá por un lado recortando el ancho de banda a unos pocos cientos de Hz o unos pocos KHz por un lado y se vá de modulando la señal que nos interesa. No tenemos por ahora una implementación tan general como es un receptor, pero estamos haciendo los distintos pedazos para que eventualmente podamos operar en cualquier modo, en cualquier frecuencia. Una vez que tenemos el equivalente a una señal de audio digitalizada la podemos mandar a un auricular (y escucharla) o mediante un cable virtual a otro programa (y decodificarla). Para los modos digitales en general se hará lo último. Para los modos digitales de baja señal en general se utilizará el programa WSJT-X y para los modos digitales mas convencionales el FLDIGI; pero nada impide con las adaptaciones del caso utilizar cualquier programa que pueda ser alimentado con la salida de una placa de sonido, como lo haríamos con un transceiver convencional. En ocasiones pienso que tener los distintos modos sueltos e irlos conectando para distintas implementaciones no es muy diferente a cuando construimos un transceiver físico, módulo por módulo y trabajándolo sobre la mesa de trabajo para asegurarnos que anda y poder hacer mediciones fáciles. Normalmente pasa bastante tiempo ante que ponemos todo en una caja bonita (o no tanto, depende de la habilidad de cada cual) y por supuesto nos olvidamos completamente del proyecto pues ya estamos en otro igual o mas divertido.
Para la cadena transmisora hacemos algo parecido, solo que recorremos la cadena en sentido opuesto; empezamos con algo que genera audio (o, siendo mas general, una señal a modular) digitalizado, lo capturamos con un cable virtual en otro programa y lo vamos tratando con distintos procesamientos de la señal hasta que tenemos una "banda base" modulada y la aplicamos entonces al programa rpitx.
Hasta ahora de la salida de rpitx se puede optar por salir directamente a la antena con un filtro pasabajos simple (10 mW aprox.), poner un filtrado mas robusto y algo de amplificador (100 mW aprox) o ya a partir de alli tratar de alimentar una cadena de transmisión mas potente. Como en todo proyecto interesante es casi imposible evitar que a cada paso que damos se abran ramas diferentes que son igualmente interesantes y que estamos seguros nos darían un enorme placer experimentar. Solo algunas para concretar el punto, estas técnicas básicas con muy pocas modificaciones pueden usarse para hacer un repetidor de FM, o un transponder lineal como el que utilizan los satélites, o balizas para globos, o emisores de TV analógica y digital, o trabajar en packet APRS, o ... bueno, creo que se entiende. Por otra parte la integración de hardware, tema que no tiene tanto interés para mi pues estoy mas inclinado a la experimentación basada en software; por ejemplo con una sola placa pequeña (una Raspberry Pi Zero, por ejemplo) se puede potenciar un emisor de FM o uno de SSB canalero para transformarlo en uno multimodo, multibanda, por ejemplo.
Como quiera que sea hasta ahora he mostrado como implementar una baliza WSPR, una baliza FT8 y una baliza en CW; le ha llegado el turno a otros modos. Empezando por PSK31.
Este modo es bastante popular, su comportamiento con señales bajas no supera al CW, pero si lo hace con el SSB por lo que a iguales potencias se puede comunicar mejor con igual propagación, su complejidad técnica para implementarlo es baja y puede hacerse con virtualmente cualquier transceiver de SSB con una PC relativamente modesta. Hay bastantes programas para generarlo pero los mas habituales son MixW, MMVARI y FLDIGI para el mundo Windows y en el mundo Linux FLDIGI tiene una implementación muy potente (que eventualmente sirve para Packet).
Este último puede construirse para la placa Raspberry Pi, como siempre la secuencia es larga y es necesario hacerla con cuidado y sin olvidarse nada por lo que es útil usar scripts para eso; en éste caso para los pre-requisitos, la bajada y la construcción propiamente dicha. Los pasos son:

Bajar los paquetes e instalar los pre-requisitos (link).
Construir fldigi (link)
Construir flrig (link).
Construir flxmlrpc (link).

Antes que nada hay que revisar si en el primer script se están bajando los paquetes del último nivel disponible pues cambian continuamente, si se detectan cambios hay que hacer los ajustes de nombres en los subsiguientes paquetes.
La ejecución puede hacerse con  la misma técnica utilizada hasta aquí para WSJT-X, es decir alimentarlo con un cable virtual, extraer su modulación con otro cable virtual y procesar ambas cadenas con bloques constructivos SDR. Detalles de implementación de lado las cadenas SDR a utilizar son idénticas a las ya vistas.
Esta configuración con FLDIGI es suficientemente robusta como para hacer QSO con ella; sin embargo no es práctico generar una señal de baliza utilizandola. Primero porque es pesado levantar y bajar la aplicación cada vez que se lo hace, en segundo lugar porque requiere una placa mas grande sin ningún propósito útil y finalmente por no es completamente sencillo automatizar todo el proceso. Una baliza lo que necesita es una forma simple de enviar un texto fijo, o al menos un texto que se pueda construir dinámicamente como se mostró en la entrada anterior para el caso de FT8 o CW.
Para lograrlo empezamos con un "transceiver de ssb genérico" virtualizado mediante el siguiente script ssb.sh (link).

arecord -c1 -r48000 -D default -fS16_LE - | csdr convert_i16_f \
  | csdr fir_interpolate_cc 2 | csdr dsb_fc \
  | csdr bandpass_fir_fft_cc 0.002 0.06 0.01 | csdr fastagc_ff \
  | sudo /home/pi/rpitx/sendiq -i /dev/stdin -s 96000 -f "$1" -t float

Esta cadena ya se usó y explicó en entradas anteriores, pero básicamente se comienza por una señal de audio digitalizada y se la vá procesando para generar una señal de SSB por el método de rotación de fase para finalmente cuando están disponibles las señales I/Q respectivas emitirlas mediante rpitx. Nótese que éste script requiere ser ejecutado indicando la frecuencia en Hz de emisión (ej. ./ssb.sh 14097000").
Para generar la "banda base" con la señal de PSK31 ya modulada volvemos a recurrir al utilitario para SDR csdr quien tiene todas las funciones necesarias para hacerlo; el script correspondiente es PSK.sh (link).

#!/bin/sh
#*-----------------------------------------------------------------*
#* tx_PSK31
#* Experimental PSK31/SSB generator
#*
#* tx_PSK31.sh FREQ [in Hz] "MESSAGE"
#*-----------------------------------------------------------------*
#f="14097000"
echo "***********************************************"
echo "* PSK Generator                               "*
echo "***********************************************"
echo "Freq=$1 Message=$2"
f="$1"
echo "Starting SSB Generator at $f"
mkfifo pskfork || exit
/home/pi/whisper/ssb.sh $f 2> pskfork &
pid1=$!
while read line; do
  case $line in
    *high_cut*) echo "$line"; echo "found KEY=high_cut"; break;;
    *) echo "$line";;
  esac
done < pskfork

echo "Removing FIFO buffer"
rm -f pskfork  2>&1 > /dev/null

m="$2"
echo "Sending message frame($m)"

echo $(echo -n "$m") |
csdr psk31_varicode_encoder_u8_u8 | \
csdr differential_encoder_u8_u8 | \
csdr psk_modulator_u8_c 2 | \
csdr gain_ff 0.25 | \
csdr psk31_interpolate_sine_cc 256 | \
csdr shift_addition_cc 0.125 | \
csdr realpart_cf | \
csdr convert_f_s16 | \
mplayer -cache 1024 -quiet \
     -rawaudio samplesize=2:channels=1:rate=8000 -demuxer rawaudio - & 

echo "Waiting for termination"
sleep 20

echo "cleaning up processes"
sudo killall mplayer 2>&1 > /dev/null
sudo kill $pid1  2>&1 > /dev/null
sudo killall csdr  2>&1 > /dev/null
#sudo killall arecord  2>&1 > /dev/null
#sudo killall sendiq  2>&1 > /dev/null

exit 0

El script hace varias cosas y puede lucir confuso por lo que lo explico en partes; el segmento que genera PSK31 es el siguiente:

...
echo $(echo -n "$m") |
csdr psk31_varicode_encoder_u8_u8 | \
csdr differential_encoder_u8_u8 | \
csdr psk_modulator_u8_c 2 | \
csdr gain_ff 0.25 | \
csdr psk31_interpolate_sine_cc 256 | \
csdr shift_addition_cc 0.125 | \
csdr realpart_cf | \
csdr convert_f_s16 | \
mplayer -cache 1024 -quiet \
     -rawaudio samplesize=2:channels=1:rate=8000 -demuxer rawaudio - & 
...

El segmento empieza con la presentación de un texto a ser emitido el cual se entrega a una función de csdr llamada psk31_varicode_encoder_u8_u8 que como su nombre lo indica genera el patrón de bits a ser modulado, seguido de una función que hace la codificación diferencial (differential_encoder_u8_u8) y finalmente la modulación PSK propiamente dicha (psk_modulator_u8_c) que transforma la señal desde una codificación digital de 8 bits a una compleja de punto flotante ya modulada. El resto de los pasos se corresponden con el tratamiento de la señal en términos de su ganancia, las transiciones de potencia y la conversión al formato y tasa de muestreo final que requiere mplayer para generar el "sonido" final.
Notese que este segmento comienza con el texto y emite los sonidos "a la nada", estará de nuestra parte luego mediante pavucontrol conectar la salida del programa mplayer a uno de los extremos de un cable virtual (por ej. Virtual0) tal como, me repito, se hacía en WSJT-X en entregas anteriores.
Hay que hacer algunos trucos adicionales porque en los casos anteriores se conectaba la salida de un script con otra aplicación que estaba corriendo por separado y aquí, ambas aplicaciones parecen correr juntas... Eso es un poco la función del resto del script.
Al comenzar se llama al script ssb.sh y se lo ejecuta en el trasfondo (background), pero no es posible comenzar con la parte moduladora si el transmisor no está andando, basta una fracción de segundo de diferencia para que uno de los dos no funcione bien. Por eso el script genera un pipe (pskfork) donde se pone la salida que va emitiendo ssb.sh y mediante un loop redireccionado de ese pipe (a continuación) se controla que aparezca alguno de los mensajes que podemos esperar cuando ya está listo, tras lo cual borra el pipe y continúa invocando al segmento que genera PSK31.
Para que funcionen correctamente ambos pedazos (el transceptor y el generador PSK31) son lanzados como procesos en el background, por lo que a continuación el script se queda esperando un tiempo suficiente para que el mensaje sea emitido correctamente en PSK31, pasado el cual se eliminan todos los scripts que están corriendo antes de terminar. El tiempo de espera es suficiente para un texto cualquier de aproximadamente medio renglón, si fuera mas corto o mas largo puede reducirse o aumentarse en forma correspondiente el retardo.


viernes, 22 de marzo de 2019

Transceptor de señales débiles, bit a bit (Parte 6)

Es relativamente trivial configurar los scripts para que arranquen en cualquier modo, basta alterar los parámetros para que la cadena receptora funcione en 14095.6 MHz (WSPR) o 14074 MHz, o cualquier otra frecuencia y banda por caso. Mi estación monitora opera en 20 metros porque el filtro y amplificador de salida está en esa banda, pero el principio de funcionamiento es el mismo no importa cual sea la banda que se utilice. Pero por mas que apile modos sigo teniendo dos placas Raspberry Pi (una para receptor y otra para transmisor) y una antena; es decir que de alguna forma hay que coordinar todo.
Mi primer intento fue, casi obvio, hacer andar todo en una placa. Aparte del beneficio de usar solo una placa la coordinación de actividades es un poco mas simple, pero muy poco en realidad. Sin embargo la cadena receptora utiliza el clock de la máquina para establecer las ventanas (rigurosas) de los modos de baja señal, y está sincronizado mediante el protocolo NTP para ser preciso. Sin embargo, el procesamiento de la cadena SDR toma algún tiempo, dos segundo y medio aproximadamente. Eso basta para que la performance de recepción se degrade enormemente pues es muy crítico que los algoritmos funcionen sincronizado en tiempo, para solucionar eso se "corre" el reloj de forma que atrase 2.5 segundos a pesar de estar sincronizados por NTP (es decir, sincroniza la hora exacta y le resta 2.5 segundos) que es el tiempo que retarda la cadena de procesamiento SDR, con lo cual el receptor está dentro de medio segundo de la ventana de tiempo sincronizada y la recepción mejora enormemente. Ese mismo atraso es, sin embargo, perjudicial para la cadena transmisora, pues su retardo es casi despreciable y por lo tanto emite fuera de ventana y perjudica sus chances de ser decodificado correctamente por otras estaciones. Busqué soluciones a ese problema y encontré algunas, pero ninguna fue satisfactoria. La solución fue entonces correr el script de baliza (emisor) en una placa y el de monitor (receptor) en la otra. Santo remedio. Pero .. si era difícil coordinar ambos scripts cuando estaban en la misma placa ... ¿como hacerlo ahora que están en dos placas diferentes?. Mis primeros intentos fueron justamente buscar formas de sincronizarlos, y probé con esquemas un tanto rebuscados utilizando sea un socket TCP (ncat) sea  puertos series virtuales (pipe) u otros esquemas tanto o mas atravesados. Nuevamente, funcionaron, pero eran solo marginalmente satisfactorios. La simplicidad terminó dominando la solución (criterio de Occam se lo suele llamar). La forma mas simple de resolver una conmutación compleja es ..... no hacerla. En efecto, por sofisticada que fuera la solución mientras la baliza transmite, por mas que lo haga con solo 100 mW (o incluso con 10 mW) el receptor queda "sordo". Y de todas maneras solo asigno una antena al conjunto, así que por mas que hiciera una conmutación rara al final tengo que conmutar físicamente la antena a uno u otro. Entonces la solución mas simple es colocar un relay que activado por el script de emisión de la baliza se conmute la antena cuando va a transmitir y la libere cuando deja de hacerlo. En emisión el monitor no puede hacer nada relevante, o a lo sumo decodificar la señal que está emitiendo la baliza, pero tampoco corre riesgos al no tener antena conectada de resultar dañado por su potencia u otro efecto indeseable.
El circuito de conmutación es extremadamente simple, básicamente un relay de +5V conmutado por un transistor NPN (siempre uso 2N3904, pero es .. cualquiera) alimentado por la linea GPIO27 de la placa emisora; la señal de control tiene +3.3V y difiere de la del relay que es +5V pero el circuito es seguro, no obstante podría hacerse con un opto-aislador si se prefiriera mas aislación. El relay es uno común y silvestre, no uno coaxial que son mucho mas caros, Marcelo Gruber (LU1IBL, en grupo Facebook TECNICA-LU) había hecho la medición demostrando que por debajo de 30 MHz el deterioro de performance de un relay común es casi despreciable (y la diferencia de costo no lo es).
Desde el punto de vista software es aún mas simple, al comenzar hay que configurar el puerto GPIO27 como de salida con:

gpio mode 27 out  2>&1 | tee -a $DPATH$LOG

Y luego, cada vez que se quiere activar el relay (antes de emitir):

gpio -g write 27 1 2>&1 | tee -a $DPATH$LOG

Y al terminar

gpio -g write 27 0 2>&1 | tee -a $DPATH$LOG

El funcionamiento general se explicó previamente, pero los scripts de detalle son simples. En transmisión el script de la baliza es lanzado cada minuto por crontab, es un script re-entrante y re-usable que termina si encuentra que ya está corriendo (no sin registrar la telemetría primero); pero cuando detecta que no está corriendo previamente se queda ejecutando el ciclo del beacon, que lo voy variando pero es que emita WSPR cada 10 minutos, seguido de FT8 durante un minuto (dos llamadas) y seguido de CW. En breve estará funcionando probablemente también en PSK31 y/o RTTY (y SSTV y ...), obviamente que cuanto mas modos le ponga mas voy a tener que espaciar las emisiones para dar lugar a que ocurra la parte del monitoreo, pues por mas que volemos alto la antena seguiría siendo una (y si le asignara otra antena quedaría sordo el monitor mientras emite la baliza).
El receptor es otra historia, básicamente se trata de tener corriendo en forma permanente al programa WSJT-X con la cadena SDR que oficia de receptor explicada en entradas anteriores; el programa no prende y apaga, solo tiene o no tiene antena. En este momento lo tengo recibiendo (monitoreando) FT8 entre las 1500Z y las 2300Z mientras que el resto del tiempo monitorea WSPR, los horarios son bastante volátiles aún pues estoy tratando de ajustar que las ventanas maximicen de alguna forma las estaciones monitoreadas en función de la propagación y la actividad, es probable que en ocasiones tenga un modo funcionando en forma permanente por sobre el otro; mucha experimientación sobre el tema sigue teniendo lugar.
Para ejecutarlo llamo al script launchWSJTX que es bien simple y de acuerdo al horario lanza la cadena receptora configurada para WSPR o FT8, el script es lanzado mediante su inclusión en el archivo 

/home/pi/.config/lxsession/LXDE-pi/autostart

agregando una linea como

@/home/pi/whisper/launchWSJTX

El contenido de ese archivo es ejecutado cada vez que arranca el GUI, que básicamente es cada vez que se da reboot. Debido a ello para cambiar de modo solo hay que dar reboot en el horario apropiado, cosa que es facil de implementar mediante el crontab.
El reporte del beacon en WSPR se obtiene en el sitio wsprnet.orghttp://www.wsprnet.org mientras que en FT8 en www.pskreporter.info. Al momento de escribir esta entrada el sitio wsprnet reporta tanto al beacon como lo que el monitoreo recibe, sin embargo PSKReporter solo muestra estaciones que me escuchan pero no logro aún que mi propio monitoreo se vea reflejado. También estoy trabajando en forma muy experimental en reportar en el Reverse Beacon Network la actividad FT8, esta plataforma que realmente revolucionó los modos digitales está adoptando en forma muy cauta el reporte de FT8, sus responsables han visto con asombro que el número de spots de ese modo rápidamente transforman el número total de todos los otros modos en una fracción del total. Al momento FT8 no es reportada en el sitio Web sino solamente mediante el acceso telnet. Todos esos esfuerzos tienen todavía tela para cortar y resumirá conclusiones mas adelante (básicamente, cuando las tenga). Para implementar en la placa del beacon las distintas emisiones utilizo WsprryPi (WSPR),

sudo $WSPRPATH$WSPR $TXOPTS $CALLSIGN $GRID $POWER $BAND 2>&1 | tee -a $DPATH$LOG

ft8pi que es parte del paquete ritx que emite mensajes en FT8 (es un programa con muchisima tela para cortar experimentando), donde básicamente se le coloca un texto a transmitir y el programa define si lo puede canalizar mediante un mensaje standard de FT8 o debe enviar un mensaje especial, el formato para invocarlo es:

sudo $RPITXPATH$FT8 -f "$FFT8" -m "CQ $CALLSIGN $GRID" -o "$OFFSET" -s "$SLOT" 2>&1 | tee -a $DPATH$LOG

PiCW para CW (provisoriamente en 14.099 MHz) aunque con una señal tan miserable es improbable que sea escuchado excepto en forma muy local, no tengo reportes al menos.

sudo $CWPATH$CW --freq $FCW --wpm 20 "CQ DE $CALLSIGN/B $GRID $POWER $TLM K" 2>&1 | tee -a $DPATH$LOG

En entradas posteriores comentaré como mediante la manipulación de la señal utilizando técnicas de SDR mediante el utilitario csdr es posible generar básicamente cualquier modulación, pero en particular aplicado a PSK31, RTTY y SSTV.

Un experimento que estoy haciendo es agregar cuando es posible  un mensaje de telemetría codificado como se lo haría en WSPR si fuera un globo, es mas que nada para jugar con los algoritmos para generar esa codificación. La misma la hago con un programa en python llamado wsprtlm.py. Este frame de telemetría permite emitir la temperatura, el voltaje y la posición mas exacta (últimos dos caracteres del grid locator). También emite la altura, que en un globo es relevante pero en mi caso no, por lo que quizás se pueda utilizar eso para codificar otra cosa, como por ejemplo el status del procesador, el porcentaje de CPU o de espacio en disco (o todas esas cosas juntas con la codificación apropiada).

Si bien una cosa es emitir un mensaje corto con una baliza y otra operar un QSO arbitrario en realidad es bastante facil pasar de los scripts que hagan una cosa a los que hagan otra; o aplicando la estrategia que utiliza el programa qtcsdr ir invocando los programas que necesitamos para generar la señal que queremos en un momento dado.

viernes, 15 de marzo de 2019

Transceptor de señales débiles, bit a bit (Parte 5)

En todo sistema complejo es importante avanzar en la implementación conceptual, pero también solucionar los problemas prácticos que se van encontrando, la mayoría de ellos que no se han previsto y cuya solución no siempre es trivial.
El sistema implementado recibe bien y cumple su propósito, pero que lo haga en forma continua a modo de estación de monitoreo permanente es otro precio.
Para empezar luego de intentar infructuosamente que la parte de baliza y la de monitoreo funcionaran en la misma placa encontré que era mas conveniente que lo hicieran en dos placas separadas, hay varias buenas razones para hacerlo así, pero la principal es que la placa de monitoreo (recepción) tiene un clock compensado con 2,5 segundos de atraso mientras que la de baliza no lo necesita y esa diferencia le hace perder las ventanas (muy críticas) de WSPR y FT8 principalmente. En una entrada siguiente explicaré como se coordinan las dos placas para cumplir ambos propósitos (con una sola antena). En lo inmediato lo que concierne a ésta entrada es interesante ver la clase de problemas que ésto genera y una posible forma de manejarlos.
He comentado en entradas anteriores que es necesario en sistemas de funcionamiento permanente llevar un registro (log) lo mas detallado posible de todo lo que va pasando. ¿Que tan detallado es detallado?, bueno, nunca lo suficiente cuando se tiene que buscar un problema. Sin embargo debe operar un equilibrio puesto que en definitiva una placa Raspberry Pi no tiene un disco, solo una tarjeta de memoria que funge como tal, y por lo tanto no puede ser ilimitado lo que se almacene. Hay también una consideración de performance, si bien Linux es extremadamente eficiente manejando archivos a medida que los archivos se hacen mas grandes la performance de grabarles información se va paulatinamente deteriorando. No quiero inferir que decidir con detalles que guardar en el log es un "arte", pero ciertamente es bastante subjetivo. El criterio que uso es aplicar lo que se llama el criterio de complejidad ciclomática de McCabe que dice básicamente que la complejidad está determinada por los nodos de bifurcación o decisión que tiene una determinada lógica. Eso da la estructura básica de los puntos de información en el log. Pueden, además, agregarse datos de información adicional tales como tiempos de ejecución de determinados módulos, o los reportes que emite. Con esa información se puede manejar un log del orden de unos pocos MBytes por día, lo que es manejable aún aplicando acciones de mantenimiento que guarden los contenidos diarios por una semana o cosa similar.
En un script BASH la información en el log se agrega con una sentencia del tipo:

 echo "Whisper: "`date`" Starting  Ver "$VERSION" P("$N") PID("$$") CPU($CPU %)" 2>&1 | tee -a $DPATH$LOG

Lo que parece muy obscuro pero no es mas que grabar el nombre del programa (también se puede usar la variable predefinida $0 pero es un poco mas extensa porque incluye el path de ejecución), se incluye la fecha y hora de la grabación (¡siempre! ¡importantisimo!) donde se puede notar que la instrucción está encerrada en una comilla inversa "`". BASH es eficiente pero hay veces que hay que conceder que es obscuro.... la comilla inversa indica que debe ser reemplazada por la ejecución del comando que ellas encierran. Las uso por costumbre aunque concedo que es mucho mas claro el equivalente de encerrar el comando en el formato $(comando). Luego variables que están definidas durante la ejecución (las que se distinguen por estar antecedidas por "$" tales como VERSION (del script), número de procesos (N) medidos al ejecutar, PID identificador de proceso del script, CPU consumo de CPU medido durante la ejecución y que se va dejando como referencia para identificar si en algún momento hay recursos limitados de procesamiento. La sentencia finaliza con dos redirecciones. La primera "2>&1" es una forma algo compacta de decir "todo lo que salga por standard error redireccionarlo a standard output. La segunda "| tee -a $DPATH$LOG" signfica todo lo que salga por standard output entrará en el programa tee quien lo separará en dos corrientes, una la agregará al archivo de log indicado por $DPATH$LOG (definido por el script) y la otra corriente la mostrará en la salida standard; nada mas que un truco para mostrar un mensaje en consola y al mismo tiempo archivarlo. El script de gestión de la baliza whisper funciona continuamente, mientras que el de supervisión del monitoreo wsmon es invocado periódicamente mediante llamados de crontab.
Entre los datos almacenados en el log incluí información de monitoreo del estado del sistema, en particular %CPU, ocupación de disco, temperatura, voltaje del procesador y estado de restricciones del sistema.

#+-----------------------------------------------------------------------------
#* Get telemetry from all major sub-systems
#*-----------------------------------------------------------------------------
getCPU () {
sar 1 3 | grep "Media:" | while read a ; do
 echo $a | awk '{print $3 + $4 + $5 + $6 + $7}';
done
}
getTemp () {
TEMP=$(vcgencmd measure_temp)
echo $TEMP | cut -f2 -d"=" | cut -f1 -d"'"
}
getVolt () {
VOLT=$(vcgencmd measure_volts | cut -f2 -d"=" | cut -f1 -d"V" )
VOLT=$(python -c "print ('%.2f' % ($VOLT*1.0))" )
echo $VOLT 
}
getClock () {
CLOCK=$(vcgencmd measure_clock arm | cut -f2 -d"=")
FX=$(python -c "print float($CLOCK)/1000000")
echo $FX
}
getStatus () {
STATUS=$(vcgencmd get_throttled)
echo $STATUS | cut -f2 -d"="
}
getDASD () {
sudo df -k | grep "/dev/root" | awk '{ print $5 ; }' | cut -f1 -d"%"
}
.....
VOLT=$(getVolt)
TEMP=$(getTemp)
STATE="T($TEMP°C) V($VOLT) Clk($(getClock)MHz) St($(getStatus)) CPU($(getCPU)%) DASD($(getDASD)%)" 
echo $STATE | logger -i -t "TLM"
echo "Whisper: "`date`" Telemetry: "$STATE 2>&1 | tee -a $DPATH$LOG
......

Si bien parece que se pone la misma información dos veces en el log en realidad se pone una vez en el log propio del script (un archivo de texto) y otra vez en el system log (un database con toda la información del sistema), eso es solo por conveniencia de poder hacer otras cosas donde trabajar con el system log es mas facil y rápido que hacerlo con un archivo de texto.
La medición de la carga de máquina se hace mediante la invocación del utilitario sar y la manipulación de su salida. Estas lineas son invocadas periódicamente por el script que maneja el beacon o mediante crontab en el caso de la estación monitora (que ejecuta continuamente wsjt-x).
De esta forma es posible ir revisando si el espacio en disco se vuelve insuficiente, si estamos pidiendo al procesador que haga demasiadas cosas y otros factores.
En particular observé que trabajar continuamente al programa wsjt-x, junto con el procesamiento de señales que es necesario para alimentarlo provocaba un calentamiento excesivo de la placa, era bastante frecuente que dejara de funcionar por exceder los 85°C de temperatura, y en realidad dejaba de funcionar correctamente cuando superaba 80-82°C por mas que siguiera funcionando todo en apariencia.
La primer reacción fue agregar un método de gestión de la temperatura en el nodo emisor:
#*-----------------------------------------------------------------------------
#* manageTemp()
#* Get core temperature and stop the beacon to cool down if exceeded
#*-----------------------------------------------------------------------------
manageTemp() {
#echo "$DAEMON: "`date`" Managing temperature procedure" 2>&1 | tee -a $DPATH$LOG
A=$(vcgencmd measure_temp)
B=$(echo $A  | cut -f2 -d"=" | cut -d"'" -f1 | tr -d '\r')
C=$(echo $B | cut -f1 -d".")
D=$(($C * 1))
echo $D
}
.........................
T=$(manageTemp)
if [ $T -ge $MAXTEMP ]
then
   echo "Whisper: "`date`" Temp ("$T") exceeds max ($MAX), stopping TX to cool down during $COOLTIME secs"  \ 
   2>&1 | tee -a $DPATH$LOG && sleep $COOLTIME
   echo "Whisper: "`date`" Temperature is now "$(vcgencmd measure_temp)" TX resumed" 2>&1 |  tee -a $DPATH$LOG
fi

La placa de la baliza (emisora) tenía pocos problemas de temperatura, y nunca superó el punto en el cual dejaba de funcionar, pero observé que la diferencia térmica provocaba que el clock de la placa tuviera diferencias y éstas correlacionaban con menor frecuencia de reportes en wsprnet.org; por lo que consideré preferible que si había temperatura elevada directamente no emitiera la baliza; la gestión es bien simple, si la temperatura excede un valor máximo (MAXTEMP) simplemente espera sin emitir la baliza por un tiempo (COOLTIME) que lo tengo especificado en media hora. Al cabo del cual se reporta como estaba la temperatura, fui extendiendo el COOLTIME hasta que efectivamente luego de la "parada" saliera con una buena temperatura y margen para trabajar correctamente bastante tiempo. Pero si por alguna razón eso no pasaba un par de minutos después cuando completara el ciclo de la baliza volvería a quedar detenida.
Y en el nodo receptor (el mas perjudicado) la gestión resultó un poco mas compleja:


#*---------------------------------------------------------------------------

#* Kill all processes related to the monitoring function

#*---------------------------------------------------------------------------
killProcess() {

sudo ps -aux | for p in `pgrep "rtl_sdr"`; do echo "killing "$p; sudo kill -9 $p; done
sudo ps -aux | for p in `pgrep "csdr"`; do echo "killing "$p; sudo kill -9 $p; done
sudo ps -aux | for p in `pgrep "ncat"`; do echo "killing "$p; sudo kill -9 $p; done
sudo ps -aux | for p in `pgrep "mplayer"`; do echo "killing "$p; sudo kill -9 $p; done

sudo ps -aux | for p in `pgrep "jt9"`; do echo "killing "$p; sudo kill -9 $p; done
......
 if [ -f $DPATH$THERMALLOCK ]; then
    echo "$DAEMON: "`date`" Process is under thermal lock, evaluating" 2>&1 | tee -a $DPATH$LOG
    if [ $T -ge $TGTTEMP ];
   then
    echo "$DAEMON: "`date`" Temp ("$T" °C) exceeds cooling target ("$TGTTEMP" °C) wait" 2>&1 | tee -a $DPATH$LOG
             echo "Thermal runaway detected!" | logger -i -t "TLM"
             exit 2
   else
        echo "$DAEMON: "`date`" Temp ("$T" °C) is below target ("$TGTTEMP" °C) remove and reboot" \ 
        2>&1 |  tee -a $DPATH$LOG
        echo "Thermal management, cooling off detected " | logger -i -t "TLM"
        sudo rm -r $DPATH$THERMALLOCK 2>&1 | tee -a $DPATH$LOG
        sudo reboot
        sleep 10000
        exit 0
    fi
fi
if [ $T -ge $MAXTEMP ];
       then
          echo "$DAEMON: "`date`" Temp ("$T" °C) exceeds max ("$MAXTEMP" °C) kill procs and thermal lock"  \ 
          2>&1 | tee -a $DPATH$LOG
          sudo touch $DPATH$THERMALLOCK 2>&1 | tee -a $DPATH$LOG
          killProcess 2>&1 | tee -a $DPATH$LOG
          echo "Thermal runaway detected!" | logger -i -t "TLM"
         exit 0
       else
    echo "$DAEMON: "`date`" Temp ("$T" °C) is nominal and below max ("$MAXTEMP" °C)" \
    2>&1 |  tee -a $DPATH$LOG
fi

En éste caso el script que controla la temperatura no es el mismo que produce el calor (wsjt-x) por lo que lo primero que hay que hacer es detener ese programa (killProcess), pero el script de control está funcionando con una lógica de ser re-entrante y re-usable por lo que luego de bajar a wsjt-x marca la condición de corrida térmica  (generando un archivo cuyo nombre y path están dados por $DPATH$THERMALLOCK) y termina. Al cabo de un tiempo crontab lo va a volver a lanzar, donde observará que está en corrida térmica y entonces verificará si la temperatura bajó lo suficiente, caso que no vuelve a salir. Al cabo de varios ciclos, típicamente 10 o 15 minutos, la temperatura bajaba lo suficiente y entonces podía volverse a comenzar el monitoreo.
Inicialmente intenté arrancar desde allí a wsjt-x pero encontré que no siempre el sistema había quedado estable luego de "matar" los procesos o mismo de la corrida térmica, así que lo mas higiénico resultó ser dar "reboot" y dejar que el arranque del monitor asegurara un comienzo "limpio". Puede parecer disruptivo como método, pero usándolo pasó de estar varias horas en condición inestable (varios días una vez que ocurrió mientras viajaba) a unos pocos minutos. Dependiendo del día, la temperatura ambiente y otros factores incluyendo la cantidad de estaciones que se tienen que decodificar en un ciclo, podía hacer 3 o 4 de éstos ciclos por día. Cuando pude identificar claramente ésta situación corté por lo sano, le puse disipadores mas grandes a los chips de la placa y un pequeño ventilador de +5V alimentado por la placa misma. Santo remedio, la placa nunca más superó los 45°C a partir del agregado del ventilador.

viernes, 8 de marzo de 2019

Transceptor de señales débiles, bit a bit (Parte 4)

A poco de operar wsjtx con la configuración que se viene discutiendo se hacen obvios algunos
problemas. El primero es que la "ganancia" del receptor implementado con rtl_fm no parece ser mucha, el segundo es que no se ha implementado una forma de transmitir con wsjtx y al intentar hacerlo con la baliza basada en WsprryPi que se discutiera previamente aparecen problemas prácticos y de protocolo.
Un truco útil a ésta altura es poder probar si el dongle RTL-SDR es correctamente reconocido por la placa, normalmente podemos ver cuando todo está funcionando un patrón de recepción de señales bien distintivo en el "waterfall" de wsjt-x, pero como en todo sistema que tiene muchos componentes es bueno tener alguna forma de ir partiendo el problema en pedazos manejables, aqui se puede usar el comando "rtl_test" el cual revisa que el dongle funciona correctamente, es bueno hacerlo antes de seguir experimentando.

Usando rtl_sdr (método #2)

El método recomendado por el artículo ya referenciado "Tutorial: Setting up a Low Cost QRP (FT8, JT9, WSPR etc) Monitoring Station with an RTL-SDR V3 and Raspberry Pi 3" [link] usa un método de recepción diferente, usando diferentes programas, y uno empieza a sospechar que quizás hay algún motivo por el cual lo hace así.
Este método involucra varios programas nuevos que la configuración anterior no usaba, ncat ("network cat") y en particular una pequeña joya llamada csdr.
El enfoque es un poco diferente, empieza por otro programa disponible en el paquete rtl-sdr (no hay que bajar ni compilar el paquete de vuelta) que se llama rtl_sdr justamente (notar que es con guion bajo "_" en lugar de alto "-") y opera en forma mas primitiva si se quiere que rtl_fm, de hecho no realiza ningún intento de demodular la señal. Básicamente entrega las muestras "crudas" tal como salen del dongle RTL-SDR, las que tienen que ser procesadas para que, finalmente, puedan ser digeridas por wsjtx. Entonces todo lo que antes "rtl_fm" hacía por dentro hay que hacerlo por fuera.
Los scripts, son dos ahora y  lucen asi:

El "receptor" propiamente dicho

#!/bin/sh
rtl_sdr -s 1200000 -f 14100000 -D 2 - \
    | csdr convert_u8_f  \
    | ncat -4l 4952 -k --send-only --allow 127.0.0.1  &

El decodificador SSB será:

#!/bin/sh
FX=$(python -c "print float(14100000-14095600)/1200000")
ncat -v 127.0.0.1 4952 \
     | csdr shift_addition_cc $FX \
     | csdr fir_decimate_cc 25 0.05 HAMMING \
     | csdr bandpass_fir_fft_cc 0 0.5 0.05 \
     | csdr realpart_cf \
     | csdr agc_ff \
     | csdr limit_ff \
     |csdr convert_f_s16 \
    | mplayer -nocache -rawaudio samplesize=2:channels=1:rate=48000 -demuxer rawaudio - &

exit 0

Es interesante entender la estructura de éstos dos scripts y su rol en la operación como receptor.
Hay que empezar diciendo que el paquete csdr es un procesador DSP simplificado (pero extremadamente potente) escrito Andras Retzler (HA7ILM) como parte de su tesis de grado/maestría [link] donde cada operación puede ser implementada por conversiones simples donde el "flujo" de entrada viene por "standard input", la operación a realizar indicada por el argumento con que se llama a csdr y el resultado puesto en "standard output". De esa manera opera con una filosofía similar a la de los procesadores DSP nativos, donde el flujo de señal digitalizada fluye en forma continua y es sometida a distintos procesamientos en respectivas colas de entrada/salida.
El primer script "lee" desde el dongle con una tasa de muestreo de 1200000 muestras/segundo y los entrega al siguiente paso donde csdr convierte las muestras I/Q (en formato 8 bits unsigned) a punto flotante para poder aplicar sobre los mismos algoritmos de procesamiento digital, a continuación coloca el resultado en una cola implementada con un socket TCP/IP por medio del utilitario ncat. La especificación dada a ncat hace que éste sea en una dirección y puerto de modo que solo otro proceso en la misma máquina pueda leerlo. El símbolo "&" al final del comando comanda que el mismo quede ejecutando en el background en forma permanente.
En otro script separado ocurre el resto del procesamiento, como se dijo, debe correr en la misma placa (dirección IP 127.0.0.1). Primero se lee la corriente de datos de punto flotante generados en el paso anterior, lo que vuelve a hacer el programa ncat. Luego la señal es sujeta a varias manipulaciones, que para cualquier familiarizado con el proceso digital de señales rápidamente identifica como los necesarios para demodular SSB por el método de la rotación de fase. La señal es desplazada, diezmada, filtrada, limitada en su amplitud por un sistema AGC y finalmente convertida de vuelta desde formato de punto flotante a 16 bits con signo, que es como los "stream" de audio son representados.
Al final del proceso, y como se hacía en el script anterior, es el reproductor multimedia mplayer el encargado de reproducir el audio demodulado. Como en el caso anterior el comando se lanza a ejecutar en el "background" mediante la indicación "&".
Hay algunos elementos a considerar. El primero es que la recepción no se hace en la frecuencia que deseo escuchar sino un poco desplazado (en éste caso 14.100 MHz), eso es para que la señal que queremos escuchar no caiga justo en el "cero" del cálculo digital, en el demulador se corrige ese efecto por la diferencia entre el primero y la frecuencia que se desea escuchar (14.0956 MHz en el ejemplo, frecuencia de WSPR). Para tomar en otra frecuencia (dentro de +/- 24 KHz del oscilador local del RTL-SDR) solo hay que alterar el 2do script. En el script están marcados con distintos colores los números que están relacionados y deben cambiarse en forma conjunta.
Hay varias otras particularidades que vale la pena detenerse. La primera es porque canalizar todo el flujo de datos por un socket mediante ncat, en definitiva se graba y lee en la misma máquina, porque no hacer "ducto" (piping) desde un procesamiento de csdr al otro sin pasar por ncat, y hacerlo todo en un solo script. Hay buenas razones para hacerlo así; he encontrado al menos dos, y es posible que existan mas.
La primera es que, como expliqué en el caso de mplayer, hay una acción de "buffer" que suaviza el flujo de multimedia y lo hace mas parejo. El segundo, mas importante, es que para que los algoritmos de la segunda parte funcionen necesitan datos. Y el programa "rtl_sdr" tarda un par de segundos (una eternidad) en comenzar a funcionar, de esa forma el segundo script termina "anormalmente" por "broken pipe" (ducto roto) por no tener datos si se lo arranca demasiado rápido. La forma de trabajar es lanzar el primer script y luego que éste comenzó a operar hacerlo con el segundo.
Si bien ambos script tienen "&" al final y se lanzan el el background para las pruebas es quizás útil arrancarlos y detenerlos, o que es muy facil con Ctl-C si están en una sesión normal pero que requiere un par de comandos para hacerlo si están en el background como por ejemplo sudo pkill o "pgrep | kill".  Por eso mientras se prueba puede ser conveniente abrir sendas ventanas de un procesador bash (terminal) y lanzar cada script en la suya.
Esta sincronización en el arranque, trivial de hacer a mano pero que no es tan trivial hacerla en forma automática puede automatizarse mediante un script receiver  el que usa un truco mediante una pipe para lanzar el primer script y quedarse observando su salida hasta que aparece en ella un texto que indica que completó su arranque para solo entonces lanzar el segundo.
Una vez lanzados ambos script puede observarse en el panel de pavucontrol que está disponible mplayer como fuente de una corriente (stream) de datos multimedia, el cual, como en el caso anterior, se puede asociar a un extremo del cable virtual, pudiendo asociar el otro extremo a wsjt-x.
Integración feliz, sistema estable. consumo de CPU y memoria es muy modesto, en un RBPi de 4 núcleos típicamente habrá uno de ellos usado al 25-35% en la máquina que corre wsjt-x. Es necesario monitorear la temperatura y quizás sea conveniente disponer de un pequeño ventilador, el conjunto entre el procesador haciendo cálculos continuamente y el calor mismo del dongle RTL-SDR hace que la temperatura de la placa RBPi opere entre 65 y 75 °C, dentro de limites seguros, pero si la temperatura ambiente no coopera puede fácilmente superar los 80-85 °C que si ya representan un problema.La configuración es estable, efectiva, se mantiene operando si no existen inconvenientes con buena disponibilidad y el consumo de CPU es modesto tanto en recepción como en transmisión. Es posible automatizar el lanzamiento de todo el conjunto cuando se arranca la máquina de forma que cada vez que se lance el GUI también se lance el script "receiver" y al programa wsjtx.
Voila!! wsjtx estará recibiendo señales WSPR! O en FT8 cambiando donde dice 14095600 por 14074000.

sábado, 2 de marzo de 2019

Transceptor de señales débiles, bit a bit [Parte 3]

Siguiendo con la linea de tiempo del proyecto la baliza estuvo operando durante varios meses donde progresivamente fui trabajando en su estabilidad y confiabilidad haciendo muchas pruebas, la mayor parte sin resultados significativos como para compartirlos. Pero lo cierto es que  llegó el momento de querer experimentar con promover la baliza a una estación de monitoreo de WSPR, es decir que pudiera operar como baliza (y ser reportada) y como estación receptora (y reportar). Una elección inicial obvia fue buscar la respuesta en el programa wsjt-x trabajando con alguno de los transceptores de la estación, pero a poco de pensarlo me quedó claro que no era ese el propósito de la experimentación.
Es claro que para operar como receptor el RBPi necesita hardware adicional. Para eso es ideal utilizar un dongle RTL-SDR, el cual representa un buen compromiso entre funcionalidad y costo. Este dispositivo puede ser configurado como un procesador SDR capaz de sintonizar en el rango nominal de 28 MHz a 1.5 GHz proveyendo una gama de posibilidades en su tasa de muestreo (y por lo tanto la anchura de la banda base). En una cuenta rápida, si la tasa de muestreo es 1200000 muestras/seg el teorema de Nyquist nos ayuda a comprender que se pueden sintonizar segmentos de algo menos de 600 KHz por vez, y el RTL-SDR puede manejar el doble de eso (cómodamente) e incluso llegar a anchos banda de hasta 4 MHz (ver especificación). En realidad se necesita muchisimo menos que eso, el máximo ancho de banda que se requiere para modos digitales de señales débiles es el de un transceptor de SSB en fonía, es decir 2.5 KHz, la enorme cantidad de muestras puede hasta ser una molestia innecesaria, aunque termina agregando "resolución espectral" al proceso de demodulación que es muy útil. El dongle trabaja en un rango de frecuencias muy interesante pero que comienza en el HF lejano (28 MHz) y se extiende bien dentro del rango de VHF y UHF. Sin embargo mi principal interés es operar señales débiles en el rango de bandas bajas de HF. Para extender el rango del dongle lo recomendado es utilizar un "upconverter" que convierta las frecuencias "hacia arriba" desde HF a alguna banda dentro del rango de funcionamiento del RTL-SDR, hay dispositivos comerciales (caros...) o puede hacerse uno casero pues son relativamente simples [link]. Sin embargo los modelos mas recientes del RTL-SDR tienen una función que se denomina "direct sampling" (muestreo directo), que si bien degrada un poco la performance [link]; si bien no experimenté con upconverter (el cual objetivamente es una mejor solución técnica) los resultados con muestreo directo fueron suficientemente buenos como para adoptarlos. En algunos modelos antiguos de RTL-SDR hay que realizar una modificación de hardware para habilitar el muestreo directo, lo que es confuso y luce limitante, pero en los modelos mas recientes es una función que se habilita por software y soportado por la versión actualizada de los drivers.
Para poder un utilizar dongle RTL-SDR  en una RBPi hay que instalar drivers adecuados, uno de los cuales es la librerías denominada librtlsdr. Para evitarnos tener que desarrollar la aplicación que implemente las funciones de la librería es útil recurrir a programas ya hechos, y uno de los más útiles que encontré fue el paquete rtl-sdr
Hay varias versiones ("branches") en desarrollo, algunas mas estables que otras, y otras de naturaleza mas experimental. La que utilizo es una experimental porque tiene algunas funciones que me son útiles. Para instalar ambas librerías:

#!/bin/sh
#*----- Install librtlsdr

sudo apt-get update
sudo apt-get install git build-essential cmake libfftw3-dev pulseaudio pavucontrol mplayer
sudo apt-get install  libusb-1.0-0-dev curl libcurl4-gnutls-dev ntp
sudo git clone https://github.com/steve-m/librtlsdr 
cd /home/pi/librtlsdr
sudo mkdir build
cd build/
sudo cmake ../
sudo make
sudo make install
sudo ldconfig

Y para instalar el paquete rtl-sdr

#!/bin/sh
git clone https://github.com/keenerd/rtl-sdr
cd rtl-sdr/
mkdir build
cd build
cmake ../ -DINSTALL_UDEV_RULES=ON -DDETACH_KERNEL_DRIVER=ON
make
sudo make install
sudo cp ../rtl-sdr.rules /etc/udev/rules.d/
sudo ldconfig

sudo reboot

Este paquete permite explorar distintas alternativas para construir una cadena receptora completa.

Usando rtl_fm (método #1)

Como parte del paquete está el programa rtl_fm, originalmente concebido para poder demodular señales de FM de banda ancha (comercial) y posteriormente expandido para recibir prácticamente todos los modos. Es parte de los programas compilados al construir el paquete rtl-sdr y su funcionamiento consiste en leer muestras desde el dongle, filtrarlas de acuerdo al modo que se esté demodulando, diezmarlas (decimarlas) para retener solo un número de muestras consistentes con el ancho de banda del modo que se está demodulando (desde centenares de KHz a un par de ellos) y producir una señal "cruda"  de audio digital que puede ser reproducida por un reproductor multimedia como aplay o mplayer. El primero está normalmente instalado por defecto en la distribución Raspbian, mientras que el segundo fue instalado como parte de los paquetes desplegados en el paso previo.
El script típico de ejecución será usando mplayer:

rtl_fm -M usb -g 29 -s 1200000 -f 14095600 -r 48k -l 0 -E direct \
    | mplayer -nocache -rawaudio samplesize=2:channels=1:rate=48000 -demuxer rawaudio -

Usando aplay hay que cambiar la segunda parte de la cadena poniendo parámetros similares correspondientes. En sucesivos experimentos he tenido mejores resultados (mas continuos, menos interrupciones) con mplayer, probablemente debido a que mplayer prioriza la continuidad de la reproducción y por lo tanto establece buffers para asegurarla. El precio es que tiene un retardo de entre 2 y 3 segundos en su ejecución, lo que en modos digitales de baja señal terminan siendo críticos y deben ser compensados, veremos como mas tarde.
Desafortunadamente, el programa rtl_fm opera en modo direct sampling con el canal 1 (I) y para éste uso se debe habilitar el canal 2 (Q), la modificación es simple, hay que editar

nano /home/pi/rtl-sdr/rtl_fm.c

Y cambiar

if (strcmp("direct",  optarg) == 0) {
dongle.direct_sampling = 1;}

por

if (strcmp("direct",  optarg) == 0) {
dongle.direct_sampling = 2;}

Luego del cambio hay que salvar el fuente y construir nuevamente el paquete para lo cual hay que ejecutar

cd /home/pi/build
cmake ../ -DINSTALL_UDEV_RULES=ON -DDETACH_KERNEL_DRIVER=ON
make
sudo make install
sudo cp ../rtl-sdr.rules /etc/udev/rules.d/
sudo ldconfig
sudo reboot

Si ejecutamos el script y abrimos el diálogo de pavucontrol veremos que hay una instancia de mplayer operando como "fuente", si asociamos a ella la salida de auricular de la RBPi o una placa de sonido USB y conectamos auriculares a ella "escucharemos" el receptor tal como lo haríamos en un transceptor común.
Podremos por supuesto cambiar la frecuencia y utilizar esta misma configuración con frecuencias de FT8 (14074.0 KHz), o CW, o SSB, o RTTY o PSK o SSTV, es un receptor SSB (USB) como cualquier otro. El único detalle es que tendremos que cancelar el script (con Ctl-C) para editar la frecuencia en cada caso, no muy ágil para "ruletear" la banda, pero mas que adecuado para "escuchar" un segmento cualquiera haciendo pruebas.
Parece muy poco cómodo pero si uno empieza a pensar un poco con criterio Linux empezará a ver los problemas como una sucesión de pequeños módulos que se conectan unos con otros para lograr un propósito, en esa dirección no sería descabellado inventar un programa que actuando de "pantalla" permita manejar los controles de un transceptor (receptor por ahora) en cuanto a frecuencia, modo, ganancia y otros para, una vez especificados, actuar en la "trastienda" deteniendo el script y lanzándolo nuevamente con los nuevos argumentos. Pero no hace falta inventar eso, ya está inventado, es el programa qtcsdr, solo que usa otras formas de implementar el transceptor (nada impide cambiarselas a éstas, pero eso, eso es otro proyecto).
Hay un buen número de cuestiones prácticas que resolver para integrar un "transceptor" virtual implementado a partir del dongle RTL-SDR (u otro similar) con wsjt-x de forma de utilizarlo en modos de señales débiles, primariamente WSPR y FT8.
Mucho de lo que viene a continuación toma ideas originalmente expuestas en un artículo fantástico denominado "TUTORIAL: SETTING UP A LOW COST QRP (FT8, JT9, WSPR ETC) MONITORING STATION WITH AN RTL-SDR V3 AND RASPBERRY PI 3" [link] cuya lectura es altamente recomendable.
El primero es como lograr que el "audio" generado por el transceptor pueda ser utilizado por wsjt-x.
No es difícil pensar en una solución mas bien de fuerza bruta, con pavucontrol (previamente instalado) conectar la ejecución de mplayer a la salida de una placa USB de sonido, conectar con un cable la salida a la entrada de MIC, configurar wsjt-x para que tome como su entrada a esa y ajustar los niveles de audio para que no haya distorsión. Todo lo que no tiene de elegante el método lo tiene de efectivo, por cierto que anda.
Pero es cierto también que hay formas mas elegantes de hacer lo mismo, y sin necesidad de una placa física USB, el procesamiento de ese audio por esa placa dista de ser gratis en términos de CPU y en la RBPi la CPU no sobra....
Para salvar esta situación se recurre a un "virtual cable" o cualquiera de los nombre con que se lo encuentra; se trata de un programa que tiene la habilidad de comportarse como una placa de sonido (virtual) que opera como un "cable", todo lo que se le coloca en una entrada (virtual) sale por la otra (virtual también). La carga de CPU es enormemente menor, en realidad los datos casi no sea procesan, solo se mueven punteros en memoria muy rápida y eficientemente.
Hay al menos dos formas de implementarlo en forma mas o menos sencilla, una es usando snd-loop y la otra pulseaudio. Ambos son "cables virtuales", el primero viene ya en la distribución RSBPi pero hay que activarlo, al segundo hay que instalarlo.
Para activar y configurar snd-loop hay que seguir los siguientes pasos:

[activar snd-loop]
[configurar  snd-loop]

Para instalar y configurar pulseaudio hay que seguir los siguientes pasos (hechos parcialmente en pasos anteriores pero que no daña repetir):

#!/bin/sh
sudo apt-get update
sudo apt-get pulseaudio pavucontrol

Hay que editar el archivo de configuración

sudo nano /etc/pulse/default.pa

y agregar las siguientes lineas al final del archivo

load-module module-null-sink sink_name=Virtual0 sink_properties=device.description="Virtual0"
load-module module-null-sink sink_name=Virtual1 sink_properties=device.description="Virtual1"

Finalmente hay que optimizar el funcionamiento del pulseaudio limitando el logging que hace, hay que editar el archivo daemon.conf

sudo nano /etc/pulse/daemon.conf

Buscar "log-level" (con Ctl-W) y cambiar a "log-level = error" y remover el comentario para que quede como:

; log-target = auto
log-level = error
; log-meta = no

Salvando el archivo hay que relanzar pulseaudio, lo que se puede hacer con un reboot (sudo reboot) o con "pulseaudio -k".
Si hicimos las cosas bien veremos que en pavucontrol podemos asociar un dispositivo "Virtual0" o "Virtual1" a mplayer. Asignemos "Virtual0". Ejecutando wsjtx tenemos que ir a configuración y designar como dispositivo de entrada "Virtual0.monitor" (el otro extremo del cable virtual), con lo que veremos que el medidor de sonido se "enciende". Hay que trabajar un poco con los controles de nivel para asegurar que el mismo está entre 50 y 70 dB, en todo caso en color "verde".
Es probable que si bien wsjtx en apariencia funcione bien aún así no decodifique correctamente señales, lo haga en forma errática o tenga muchos errores; puede ser que algo de todo lo anterior no anda del todo bien. Pero la causa mas habitual en mi experiencia con ésta configuración es que simplemente hay un problema de sincronismo en el tiempo.
Previamente se dijo que los modos de señales débiles requieren una sincronización de tiempo muy estricta, en incluso se instaló un paquete denominado ntp para asegurar que el reloj esté correcto. Sin embargo el procesamiento de la señal no es instantáneo, tarda entre 2 y 3 segundos, eso significa que cuando la señal es decodificada por wsjtx está "corrida" respecto a la ventana en esa magnitud, muy pequeña pero suficientemente crítica. Para evitar ese problema hay que "correr" las sincronización para que la hora del RBPi "atrase" un par de segundos respecto a la sincronización perfecta; ntp no permite hacer eso, pero un paquete similar llamado "chrony" si.
Para instalarlo

sudo apt-get update
sudo apt-get install chrony

Chrony cuando se instala desinstala automáticamente a ntp, al que reemplaza completamente. Editar el archivo de configuración chrony.conf

sudo nano /etc/chrony/chrony.conf

Y agregar

pool 2.debian.pool.ntp.org iburst offset -2.5

Luego reciclar chrony con

sudo invoke-rc.d chrony restart

Se podrá percibir que el reloj de la máquina ahora atrasa aproximadamente 2 segundos; esto a su debido tiempo generará sus propios problemas, pero por ahora ayudará a que todo funcione.
El programa wsjtx podrá recibir (sintonizando en las frecuencias y modos correctos) recibir señales y decodificarlas normalmente. Recordar que no hay conexión alguna entre la frecuencia que wsjtx asume que está y la que realmente está "escuchando" el transceptor, asegurarse que son las mismas (error común olvidarse de verificar que lo sean).

Maravilla de maravillas, ¡la frambuesita tiene oidos ahora!