Una radio qualunque, esposta in rete — senza dirle chi è.
Un ricetrasmettitore con una porta CAT USB e un codec audio USB non ha un'interfaccia di rete. DecoPort gliela dà. Un gateway pubblica la radio; un client sente l'annuncio, si collega, e da lì in poi la sintonizza, ne cambia il modo, ne riceve l'audio e ci trasmette.
A differenza di una libreria di controllo radio, DecoPort non chiede mai che radio hai. Il gateway legge quello che il sistema operativo già sa sulle porte seriali e sulle schede audio, accoppia quelle che appartengono allo stesso apparato USB, risolve il modello contro il catalogo che Hamlib pubblica a runtime, e pubblica ciò che ha trovato.
Il protocollo è descritto in doc/PROTOCOL.md.
Hamlib, OmniRig e i CAT nativi controllano una radio attaccata a questo computer, e l'audio non lo vedono nemmeno: passa per conto suo dal cavo USB alla scheda audio. Fanno bene un mestiere che è un altro.
DecoPort comincia dove finiscono loro: la radio è su un altro computer, e da qui non hai né il suo CAT né il suo audio. Sotto, il gateway può benissimo usare Hamlib — questa implementazione lo fa — ma quella scelta non attraversa mai il filo.
| controllo | audio | quali radio | cosa configuri | |
|---|---|---|---|---|
| CAT locale (Hamlib, OmniRig, nativo) | sì | no | quasi tutte | modello, porta, baud, CI-V |
rigctld (Hamlib in rete) |
sì | no | quasi tutte | modello, indirizzo, porta |
| TCI | sì | sì | solo chi parla TCI | indirizzo e porta |
| FlexRadio nativo | sì | compresso | solo FlexRadio | niente |
| DecoPort | sì | non compresso | CAT USB + codec USB | una password |
L'audio non è compresso. Un codec percettivo è fatto per l'orecchio umano: butta via ciò che l'orecchio non nota. Un modem non è un orecchio, e quello che il codec giudica trascurabile può essere esattamente il segnale a −24 dB che stavi cercando di decodificare. Qui passano campioni interi a 12 kHz, che per FT8 è tutto il passabanda che serve a un quarto della banda di rete che costerebbero i 48.
L'audio di trasmissione porta l'ora in cui va suonato, non l'ora in cui è partito. Il gateway lo trattiene fino a quell'istante. Un modem che trasmette in FT8 deve partire entro poche decine di millisecondi dal confine di slot; suonare quello che arriva quando arriva sommerebbe il jitter della rete a quell'allineamento. Spedire in anticipo con un'ora di riproduzione lo sposta dentro un buffer, dove non fa danno.
I comandi parlano di funzioni, non di modelli. DIGU significa «metti il
codec USB dentro il modulatore, banda laterale superiore»: su uno Yaesu è
DATA-USB, su un Icom USB-D, altrove PKT. Il client chiede DIGU e come si
scriva dall'altra parte è affare del gateway.
Fallisce chiuso. Senza chiave il gateway si rifiuta di partire e il client di collegarsi: non esiste una modalità in chiaro da dimenticare accesa.
Il PTT ha una scadenza. Un trasmettitore alzato da una rete può essere lasciato alzato da una rete: se il client muore, se cade il collegamento, se l'audio finisce a metà, la radio resterebbe in aria a portante vuota finché qualcuno non se ne accorge. L'audio riprodotto rinnova la scadenza; tre secondi dopo che l'audio smette, il PTT scende da solo. La guardia sta nella libreria, non nell'applicazione ospite, perché vale per chiunque la usi.
Serve Qt 6 (Core, Network, SerialPort, Multimedia) e, per il driver CAT, Hamlib 4.
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build buildSenza Hamlib la libreria si compila lo stesso: il gateway funziona con i ganci verso un'applicazione ospite invece che aprendo la radio da solo.
Sul computer con la radio:
./decoport-gateway --password "una password"Trova la radio, la apre, e pubblica. Da un altro computer sulla stessa rete:
./decoport-listen --password "una password" # chi c'è
./decoport-listen --password "una password" --connect 192.168.1.50Oppure, senza compilare niente, con il client Python:
python tools/decoport_probe.py --listen 15 --password "una password"
python tools/decoport_probe.py --connect 192.168.1.50 --tune 14074000 --password "..."Lo stesso strumento sa anche fare la radio, per provare un client senza avere una seconda radio e senza mettere niente in aria:
python tools/decoport_probe.py --serve --password "una password"Si annuncia come un gateway vero, risponde ai comandi, manda un tono in tempo reale, e di una trasmissione che riceve misura frame, secondi, picco, ritardi e durata del PTT invece di suonarla.
Il gateway non sa niente di radio: riceve dei ganci.
DecoPortGateway gw;
DecoPortGateway::RigHooks hooks;
hooks.connected = [] { return rig.isOpen(); };
hooks.frequencyHz = [] { return rig.frequencyHz(); };
hooks.modeName = [] { return rig.modeName(); };
hooks.setFrequencyHz = [](double f){ rig.setFrequencyHz(f); };
hooks.setModeName = [](QString m){ rig.setModeName(m); };
hooks.setPtt = [](bool on) { rig.setPtt(on); };
hooks.canTransmit = [] { return rig.isOpen(); };
gw.setRigHooks(std::move(hooks));
gw.setAuthKey(decoport::deriveKeyFromPassword("una password"));
gw.start();
// l'audio ricevuto, quando ce l'hai:
gw.setAudioFormat(12000, 1);
gw.pushRxAudio(samples, decoport::nowUnixNs());
// l'audio da trasmettere, quando è ora di suonarlo:
connect(&gw, &DecoPortGateway::txAudioDue, [](const QVector<short>& s){ ... });Il client è l'altra metà:
DecoPortLink link;
link.setAuthKey(decoport::deriveKeyFromPassword("una password"));
link.connectTo("192.168.1.50");
connect(&link, &RadioLink::rxAudio, [](const QVector<short>& s, quint64 ts){ ... });
link.tune(14074000);
link.setModeName("DIGU");
link.sendTxAudio(frame, playAtNs);RadioLink è l'interfaccia neutra: se un giorno arriva un secondo trasporto —
un backend Flex, un TCI — sta dietro la stessa interfaccia e chi la usa non
cambia.
- Una frequenza sola. Lo split è rifiutato apposta: fingere due frequenze sposterebbe quella di ricezione.
- Audio da modem, non da ascolto. 12 kHz mono è giusto per i modi digitali, non è un collegamento per ascoltare la banda.
- Rete locale. La scoperta va in broadcast, quindi stessa sottorete; e circa 200–250 kbit/s per direzione senza compressione sono tanti per una linea magra.
- Una serratura da rete locale. Chiave condivisa, firma HMAC-SHA256 troncata a 128 bit, finestra anti-replay di 30 secondi, cinque firme sbagliate in un minuto e quel mittente resta fuori per cinque. È ragionevole per una LAN, non è una postura da Internet pubblico.
- È nuovo. Hamlib e OmniRig hanno vent'anni di guasti già trovati da qualcun altro.
Verificato su un Yaesu FT-991A, con decoport-gateway compilato da questo
repository e nient'altro in mezzo:
| riconoscimento | [Yaesu FT-991 / FT-991A / FT-DX10] → modello Hamlib 1035, fiducia 90/100 |
| audio in ricezione | 2501 pacchetti in 25 s, resa 1.000 (1.000 = tempo reale) |
| frequenza di cattura | 11 987,6 Hz contro i 12 000 dichiarati: 0,1% di deriva fra il clock del codec e quello del PC — è per questo che i pacchetti portano un timestamp |
| annuncio | su tutte le interfacce, con CAT AUDIO-IN AUDIO-OUT CAN-TX |
Una nota per chi costruisce un gateway proprio: Hamlib parte con la traccia
accesa. Su una radio interrogata due volte al secondo sono centinaia di
kilobyte al minuto di I/O sincrono su stderr, e in questa prova bastavano a far
scendere la resa a 0,94 — un campione ogni diciassette non partiva mai. Il
driver di questa libreria chiama rig_set_debug(RIG_DEBUG_ERR) una volta sola;
se ne scrivi uno tuo, ricordatene.
DecoPort è nato dentro Decodium 4 FT2, dove è in servizio, ed è stato estratto perché il protocollo e la libreria non hanno niente dell'applicazione dentro: nessun file di questa libreria include una riga di Decodium.
Una conseguenza da conoscere: il sale del PBKDF2 è la stringa letterale
Decodium-DecoPort-v1. È una costante di formato, quanto il magic
dell'header — cambiarla farebbe derivare una chiave diversa dalla stessa
password e romperebbe il collegamento con ogni installazione già in servizio.
Licenza: GPL-3.0-or-later.
IU8LMC