Cet article recense les étapes à suivre, valables au moment de la rédaction, pour installer et mettre en service une cellule radio TETRA faible puissance.
Avant toute chose, assurez-vous d’avoir l’autorisation d’émettre sur les fréquences que vous allez utiliser :
- Réseau pro avec autorisation d’utilisation de fréquence valide obtenue auprès de l’ANFR, et respect des puissances + localisation des émetteurs tel que déclaré ;
- Réseau radio-amateur sur les fréquences allouées.
Prérequis
– Raspberry Pi 4 ;
– Carte MicroSD classe 10 ;
– Transceiver SDR ADALM-PLUTO (révision B dans mon cas) ;
– Une antenne accordée pour la partie TX à minima.
Procédure
Installer le système d’exploitation sur la carte microSD en utilisant Raspberry Pi Imager. Dans mon cas, il s’agit de Raspberry Pi OS Lite (sans environnement graphique) Trixie 64-bit datée du 04/12/2025. Patienter jusqu’à la fin de l’écriture des données sur la carte microSD.
Linux raspberrypi 6.12.75+rpt-rpi-v8 #1 SMP PREEMPT Debian 1:6.12.75-1+rpt1 (2026-03-11) aarch64 GNU/Linux
Si vous utilisez une ancienne version de Windows qui n’accepte pas les mises à jour récentes de l’outil Raspberry Pi Imager, vous devrez créer vous-même le fichier contenant les identifiants utilisateur pour ouvrir une session sur la console :
- Créer un fichier texte nommé userconf.txt sur la racine de la carte microSD ;

- Ouvrir ce fichier en édition et coller les données suivantes :
pi:$6$c70VpvPsVNCG0YR5$l5vWWLsLko9Kj65gcQ8qvMkuOoRkEagI90qi3F/Y7rm8eNYZHW8CY6BOIKwMH7a3YYzZYL90zf304cAHLFaZE0 - Enregistrez les modifications et fermez le fichier. Ceci va créer le compte utilisateur pi avec le mot de passe raspberry, car il n’est plus défini par défaut sur les versions récentes du système d’exploitation Raspberry Pi OS.
Tant que vous êtes sur la racine de la carte microSD, créez un fichier texte vide nommé ssh. Il ne faut pas ajouter d’extension, juste nommer le fichier ssh.
Cela permettra d’activer l’accès ssh à la console.

Maintenant que les deux fichiers sont créés, vous pouvez extraire proprement la carte mémoire de son lecteur sur le PC avec la fonction Éjecter et l’insérer dans celui de la Raspberry Pi, puis la mettre sous tension.
Pour les premières étapes d’initialisation, j’utilise une interface RS232 à câbler sur la connectique 40 contacts de la Raspberry Pi. Cela permet de s’assurer que tout se déroule comme prévu, en attendant que la console devienne accessible pour continuer avec l’installation des logiciels.

Si vous avez un écran HDMI que vous pouvez connecter sur la Raspberry Pi, ça fait aussi l’affaire.
Comme toujours, on commence par mettre à jour le système :
sudo apt update
sudo apt upgrade
Ensuite, on s’appuie fortement sur la documentation officielle : https://github.com/MidnightBlueLabs/tetra-bluestation-docs/blob/main/wiki/02-Dependencies-and-Building.md.
Je vous laisse consulter la doc officielle pour la description de chaque étape.
sudo apt install -y --no-install-recommends \
git make g++ cmake \
libsoapysdr-dev \
soapysdr-tools \
libasound2-dev \
clang llvm-dev libclang-dev
curl https://sh.rustup.rs -sSf | sh
L’installation automatisée de Rust pose une question : appuyez sur la touche sur entrée pour procéder à l’installation standard.
sudo apt-get install build-essential libxml2-dev bison flex libcdk5-dev cmake libaio-dev libusb-1.0-0-dev libserialport-dev libavahi-client-dev doxygen graphviz python3 python3-pip python3-setuptools -y
cd ~
git clone -b libiio-v0 https://github.com/analogdevicesinc/libiio.git
cd libiio
mkdir build
cd build
cmake ../ -DCPP_BINDINGS=ON -DPYTHON_BINDINGS=ON
make -j$(nproc)
sudo make install
On observe quelques messages d’avertissement SetuptoolsDeprecationWarning. Il n’y a pas de raison de s’en inquiéter.
Dans mon cas, le partie radio sera assuré par un module SDR ADALM-PLUTO. Je vais donc m’intéresser à ce cas dans les étapes qui le concernent ci-dessous. Si vous utilisez un autre équipement, référez-vous à la documentation officielle : https://github.com/MidnightBlueLabs/tetra-bluestation-docs/blob/main/wiki/02-Dependencies-and-Building.md.
Aller sur le dépôt Git suivant et télécharger le premier fichier zip dans la liste : https://github.com/pgreenland/plutosdr-fw/releases/tag/v0.38_with_timestamping.
Connecter l’ADALM-PLUTO au port USB du PC et extraire tous les fichiers contenus dans l’archive zip téléchargée dans le disque amovible créé par l’ADALM-PLUTO :

Une fois cette opération effectuée, éjectez proprement le périphérique de stockage Pluto SDR :

La LED nommée LED1 se met alors a clignoter furieusement. Il ne faut absolument pas déconnecter l’ADALM-PLUTO tant que cette LED ne clignote pas à nouveau lentement.
À l’issue de sa mise à jour, l’ADALM-PLUTO redémarre avec son nouveau firmware.
L’ADALM-PLUTO peut maintenant être connecté à un port USB de la Raspberry Pi.
L’installation préalable de SoapySDR s’est avérée être obligatoire afin d’obtenir un bon fonctionnement de SoapyPlutoSDR juste après.
cd ~
git clone https://github.com/pothosware/SoapySDR.git
cd SoapySDR
mkdir build
cd build
cmake ..
make -j`nproc`
sudo make install -j`nproc`
sudo ldconfig
Installation de SoapyPlutoSDR :
cd ~
git clone https://github.com/pgreenland/SoapyPlutoSDR.git -b sdr_gadget_timestamping
cd SoapyPlutoSDR
mkdir build && cd build
cmake ..
make -j$(nproc)
sudo make install
sudo ldconfig
On observe à nouveau des avertissements sans effet négatif sur le reste des opérations.
Pour valider l’installation correcte, on utilise les commandes suivantes :
SoapySDRUtil --info
SoapySDRUtil --find
Elles doivent retourner respectivement quelque chose de ce type :

Si vous observez des lignes contenant (missing), cela signifie qu’il manque des fichiers pour assurer le bon fonctionnement de Soapy SDR :

Le cas échéant, supprimez le répertoire de SoapyPlutoSDR, installez SoapySDR (voir les explications ci-dessus), puis réinstallez SoapyPlutoSDR.
Il est inutile d’aller plus loin car l’exécution de TETRA BlueStation échouera :

Une fois que les informations retournées par SoapySDRUtil ne contiennent plus d’erreur, on peut passer à la suite :
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="0456", ATTR{idProduct}=="b673", MODE="666"' | sudo tee /etc/udev/rules.d/90-libiio_pluto.rules
sudo udevadm control --reload-rules && sudo udevadm trigger
Ouvrir le fichier limits.conf :
sudo nano /etc/security/limits.conf
Copier/coller le texte suivant juste avant la fin du fichier :
@users - rtprio 99
Résultat attendu :

Une fois effectué, quitter l’éditeur avec Ctrl+X suivi de Y et la touche Entrée pour valider l’enregistrement.
cd ~
git clone https://github.com/MidnightBlueLabs/tetra-bluestation
cd tetra-bluestation
. "$HOME/.cargo/env"
cargo build --release
J’ai constaté des plantages aléatoires pendant la compilation, avec ce type de message : error: rustc interrupted by SIGILL et error: rustc interrupted by SIGSEGV.
Si ça vous arrive également, exécutez à nouveau la commande de compilation pour reprendre là où ça a planté :
cargo build --release
Dès cette étape terminée, on peut enfin s’attaquer au paramétrage de TETRA BlueStation!
cd ~/tetra-bluestation
cp example_config/config.toml config.toml
sudo nano config.toml
Là, ça devient sérieux car vous ne pouvez plus vous contenter de faire des copier/coller.
Je vais vous donner tous les éléments qui m’ont permis d’aboutir à un fonctionnement complet dans mes conditions de test : deux portatifs Airbus TH9.
Voici le contenu de mon fichier de configuration, dans lequel j’ai juste remplacé le MNC par 999 pour des raisons de sécurité (la mienne!) :
# TETRA BlueStation example configuration file
# This is an example configuration file for the TETRA base station stack
# DO NOT RUN without editing to stay within legal limits of your jurisdiction
config_version = "0.6"
# Stack operation mode: "Bs" (Base Station), "Ms" (Mobile Station), or "Mon" (Monitor)
stack_mode = "Bs"
# Uncomment to record debug log. Files get large quickly and generate additional system load
#debug_log = "./verbose_log.txt"
###############################################################################
# PHY layer i/o configuration
[phy_io]
# Input type: set to SoapySdr. May allow for future non-soapy RF backends.
backend = "SoapySdr"
# DEBUG/TESTING code. Capture files get large quickly.
# dl_tx_file = "./dl_output.bin" # Debugging; uncomment to save generated DL RF samples to file
# ul_rx_file = "./ul_output.bin" # Debugging; uncomment to save received UL RF samples to file
[phy_io.soapysdr]
# Transmit tx(dl) and rx(ul) frequencies in Hz
# !!! Make sure to also edit all related fields in the cell_info section to fit this frequency.
tx_freq = 424812500
rx_freq = 414812500 # 10MHz duplex spacing
#rx_freq = 423025000
# Adjust if your SDR has a non-negligible tuning error
# ppm_err = 0.0
################################################################################################
## OPTIONAL: specific device, antenna, gain selection ##
## Make sure to check your device for valid settings using SoapySDRUtil --probe ##
## or check: https://github.com/MidnightBlueLabs/tetra-bluestation-docs/wiki/03-Configuration ##
################################################################################################
# Optional device selection arguments.
# If not specified, the first supported device found is selected automatically.
# You may need to specify this if you have multiple supported device connected,
# or if your device cannot be automatically detected by enumeration.
# For example, to use a Pluto+ with a given IP address:
# device = "driver=plutosdr,uri=ip:192.168.42.42"
# To select a LimeSDR with a given serial number (check with SoapySDRUtil --find):
# device = "driver=lime,serial=123456789"
#device = "driver=plutosdr,uri=ip:192.168.3.51"
# Optional antenna selection to override device-specific defaults
# Check antenna names with SoapySDRUtil --probe
# rx_antenna = "LNAW"
# tx_antenna = "BAND2"
rx_antenna = "A_BALANCED"
tx_antenna = "A"
# Optional gain values to override device-specific defaults.
# Check valid gain names with SoapySDRUtil --probe
# For example, to increase transmit power on a LimeSDR:
# tx_gain_pad = 50.0
# To adjust LNA gain to optimize RX performance on a LimeSDR or SXceiver:
# rx_gain_lna = 30.0
rx_gain_pga = 10.0
tx_gain_pga = 89.0
###############################################################################
# Network Information
[net_info]
# Mobile Country Code (MCC) - identifies your country
#mcc = 204 # Netherlands
mcc = 208 # Frankrijk
# Mobile Network Code (MNC) - identifies the TETRA network
# MAKE SURE MNC MAY BE USED, CONSULT RELEVANT AUTHORITIES IF NEEDED
#mnc = 1337
mnc = 999 # dummy one
# Cell Information
[cell_info]
# These parameters specify the DL and UL frequencies
# Read the spec if in doubt. Make sure these match the tx/rx freqs as given above
# See ETSI TS 100 392-15 V1.5.1 Clause 6: Duplex spacing
freq_band = 4 # 400MHz band
#main_carrier = 1521 # + 1521 * 25kHz = 38250kHz
main_carrier = 992 #
#duplex_spacing = 4 # Use 5MHz spacing between ul and dl (for 400 MHz band)
duplex_spacing = 0 # set to 7 to use custom duplex spacing below
#custom_duplex_spacing = 10000000 # Don't uncomment unless you have programmed a custom duplex spacing entry in your radios
freq_offset = 12500 # Offset from carrier. Usually 0. Options: 0, 6250, -6250, 12500.
reverse_operation = false # False: UL below DL. True: UL above DL.
# Location Area identifier
location_area = 2
# Colour code (0-63), helps distinguish between adjacent cells on the same frequency
colour_code = 1
###############################################################################
# OPTIONAL: Additional cell parameters
# WIP; uncomment and configure as needed, many are not currently implemented,
# defaults should be fine
# Neighbor cell broadcast settings
# neighbor_cell_broadcast = 0
# Cell load (Channel Allocation) - current load
# cell_load_ca = 0
# Late entry support - allows joining ongoing group calls
# late_entry_supported = true
# Subscriber class - defines which MS classes can access this cell
subscriber_class = 0xFFFF
# Registration and mobility features
registration = true
deregistration = true
# priority_cell = false
# no_minimum_mode = false
# migration = false
# Service availability flags
system_wide_services = true # If false, radios will operate in fallback mode (ignored when Brew enabled)
voice_service = true
# circuit_mode_data_service = true
# sndcp_service = true
# aie_service = false
# advanced_link = false
# System code (0-15) - identifies the TETRA system version
system_code = 0
# Sharing mode for the main control channel
# sharing_mode = 0
# Reserved frames for time slot allocation
# ts_reserved_frames = 0
# Discontinuous Transmission (DTX) on user plane
# u_plane_dtx = false
# Frame 18 extension support
# frame_18_ext = false
# IANA timezone for D-NWRK-BROADCAST time broadcasting. When set, the BS will
# broadcast UTC time and local time offset once per hyperframe (~61s) so MSs
# can synchronize their clocks. Handles DST automatically.
# timezone = "Europe/Amsterdam"
timezone = "Europe/Paris"
# Local SSI ranges for this cell. Traffic for these ranges will not leave the cell
# This overrides any settings in brew section, if enabled.
# Also, incoming brew traffic on those rangges will be dropped
# Range end is exclusive, so the range end number is NOT inside the ranges
# local_ssi_ranges = [
# [0, 91],
# ]
local_ssi_ranges = [
[1000, 3000],
]
###############################################################################
# Brew protocol: Connect to TetraPack/BrandMeister server via TETRA Homebrew Protocol.
# All groups that radios attach to are forwarded to Brew as affiliations.
# Uncomment this section to automatically load and use Brew entity
# See: https://wiki.tetrapack.online/books/tetra/page/brew
# [brew]
# Server address
# host = "core.tetrapack.online"
# port = 443
# Use TLS (wss:// / https://)
# tls = true
# HTTP Digest auth credentials, as registered in BrandMeister
# Username should be your SSID (DMR ID + 2-digit suffix).
# Password is your BrandMeister hotspot password.
# username = 123456700
# password = "012345"
# Reconnection delay (seconds)
# reconnect_delay_secs = 15
# Optional: additional initial latency compensation in frames for inbound Brew jitter playout.
# Adaptive jitter buffering is always enabled; this adds fixed startup delay if needed.
# jitter_initial_latency_frames = 0
# Enable SDS forwarding between local and Brew clients. Enabled by default.
# feature_sds_enabled = true
# Uncomment to allow only calls for select SSIs to be transmitted over Brew
# SDS works for all SSIs, currently, but the SDS over Brew feature may be fully disabled.
# If left commented, all (outside of local_ssi_ranges) calls are allowed over Brew
# whitelisted_ssis = [91]
Ce qu’il y a à savoir :
- duplex_spacing est défini à l’index 0 car j’utilisais des portatifs paeramétrés sur un réseau pro avec un écart duplex de 10MHz. La valeur de cet index doit être aussi égale à 10MHz dans le paramétrage du poste TETRA ;
- freq_offset dépend du paramétrage d’offset défini dans le paramétrage du poste TETRA. Dans mon cas, l’offset est de 12.5kHz soit 12500Hz ;
- tx_freq doit être strictement identique au paramétrage défini dans main_carrier et freq_offset, en suivant la formule de calcul suivante : tx_freq = (freq_band * 100) + (main_carrier * 25) + (freq_offset / 100). Concrètement, dans l’exemple que je vous fournis, cela donne tx_freq = (400000) + (992 * 25) + 12.5 = 400000 + 24800 + 12.5 = 424812,5 MHz, soit 424812500 Hz ;
- rx_freq doit être strictement identique à : tx_freq – valeur réelle du duplex_spacing, soit 424812500 – 10000000 = 414812500.
- J’ai dû définir explicitement le paramétrage de rx_gain_pga, tx_gain_pga, rx_antenna et tx_antenna pour que la gestion de la porteuse TETRA ne plante pas avec des dizaines d’erreurs par secondes affichées à l’écran dès l’inscription des postes TETRA sur le réseau. J’utilisais deux antennes distinctes mais proches lors de mes essais, au lieu d’intercaler un duplexeur. Les problèmes rencontrés venaient peut-être de là. ;
- J’ai décommenté les fonctions registration, deregistration et subscriber_class ;
- Notez que la qualité de l’alimentation électrique +5V DC reliée à la carte Raspberry Pi est également très importante. Si vous observez une suite sans fin d’avertissements dès la mise en route de BlueStation, changez de bloc d’alimentation et retentez votre chance.
Une fois le fichier édité et les modifications enregistrées, on peut lancer TETRA BlueStation avec la commande suivante :
chrt -f 73 ./target/release/bluestation-bs ./config.toml

Ici on observe l’activation de la cellule et l’enregistrement d’un poste sur le réseau.
Les appels phonie et les envois de message texte fonctionnent. C’est un succès!
Connexion à Brew
Cette fonctionnalité de BlueStation permet de connecter le node au réseau radioamateur TetraPack (rien à voir avec la société spécialisée dans l’emballage!) via le protocole Brew.
cd ~/tetra-bluestation
sudo nano config.toml
###############################################################################
# Brew protocol: Connect to TetraPack/BrandMeister server via TETRA Homebrew Protocol.
# All groups that radios attach to are forwarded to Brew as affiliations.
# Uncomment this section to automatically load and use Brew entity
# See: https://wiki.tetrapack.online/books/tetra/page/brew
[brew]
# Server address
host = "core.tetrapack.online"
port = 443
# Use TLS (wss:// / https://)
tls = true
# HTTP Digest auth credentials, as registered in BrandMeister
# Username should be your SSID (DMR ID + 2-digit suffix).
# Password is your BrandMeister hotspot password.
username = XXXXXXXYY
password = "Z"
# Reconnection delay (seconds)
reconnect_delay_secs = 15
# Optional: additional initial latency compensation in frames for inbound Brew jitter playout.
# Adaptive jitter buffering is always enabled; this adds fixed startup delay if needed.
# jitter_initial_latency_frames = 0
# Enable SDS forwarding between local and Brew clients. Enabled by default.
feature_sds_enabled = true
# Uncomment to allow only calls for select SSIs to be transmitted over Brew
# SDS works for all SSIs, currently, but the SDS over Brew feature may be fully disabled.
# If left commented, all (outside of local_ssi_ranges) calls are allowed over Brew
# whitelisted_ssis = [91]
Le champ username doit contenir votre identifiant CCS7, obtenu depuis le site Internet https://radioid.net/ si aucun identifiant ne vous a déjà été attribué.
Cet identifiant est composé de 7 chiffres représentés par la suite de X ci-dessus. Les Y ci-dessus représentent un chiffre à ajouter vous-même, compris entre 00 et 99.
Le champ password doit contenir à la place du Z ci-dessus le mot de passe défini sur le site Internet https://brandmeister.network/, sur la page SelfCare, dans le menu encadré en rouge ci-dessous :
Veillez à bien conserver les guillemets avant et après le mot de passe, exemple valide :
password = "M31nP@ßw0r7:|"
Exemple non valide :
password = M31nP@ßw0r7:|
Comme pour les modifications précédentes, lorsque tous les paramètres sont édités, il faut les enregistrer dans le fichier de configuration avec la combinaison de touches Ctrl+X pour quitter en enregistrant les modifications, suivie de Y puis de la touche Entrée pour confirmer l’enregistrement.
À l’exécution suivante de BlueStation, vous devez observer les messages suivants parmi les premiers évenements visibles :
INFO [entities/net_brew] worker.rs:156: BrewWorker: starting
INFO [entities/net_brew] worker.rs:162: BrewWorker: transport connected
INFO [entities/net_brew] entity.rs:327: BrewEntity: backhaul CONNECTED
DEBUG [entities/umac] bs_sched.rs:203: BsChannelScheduler: system_wide_services ENABLED
DEBUG [entities/umac] umac_bs.rs:227: UmacBs: system_wide_services ENABLED
Il ne restra plus qu’à paramétrer votre terminal TETRA avec votre CCS7 en tant qu’ISSI (le CCS7 seul, sans chiffre ajouté à la suite!) , et ajouter les GSSI des groupes qui vous intéressent. Il suffira ensuite de sélectionner sur le terminal TETRA un groupe commun entre plusieurs utilisateurs connectés au serveur TetraPack pour effectuer une communication entre cellules TETRA interconnectées.
Démarrage de Tetra BlueStation en tant que service
L’utilisation d’un service permet d’exécuter automatiquement BlueStation à l’issue de l’initialisation système de la Raspberry Pi, ce qui est très appréciable lorsque la cellule TETRA est isolée et subit des coupures d’alimentation électrique inopinées.
Créer le service
Ouvrir un nouveau fichier en édition :
cd ~/tetra-bluestation
sudo nano bluestation-bs.service
Copier/coller le contenu ci-dessous :
# Sample systemd service file for bluestation-bs
#
# Installation: (as root or with sudo)
# - adapt your user/group and paths, sample assumes:
# - user/group are both "user",
# - installation in /home/user/tetra-bluestation
# - configuration in /home/user/bluestation-bs-config.toml
# - copy file to "/etc/systemd/system"
# - execute "systemctl daemon-reload"
#
# Usage:
# - start service with "systemctl start bluestation-bs"
# - show state with "systemctl status bluestation-bs"
# - stop service with "systemctl stop bluestation-bs"
# - to enable start at boot, execute: "systemctl enable bluestation-bs"
# - to disable start at boot, execute: "systemctl disable bluestation-bs"
# - see log with "journalctl -u bluestation-bs --output=cat -f"
[Unit]
Description=TETRA BlueStation base station service
Requires=network.target
[Service]
User=pi
#Group=user
Type=simple
CPUSchedulingPolicy=fifo
CPUSchedulingPriority=73
WorkingDirectory=/home/pi/tetra-bluestation/
ExecStart=/home/pi/tetra-bluestation/target/release/bluestation-bs /home/pi/tetra-bluestation/config.toml
KillSignal=SIGINT
[Install]
WantedBy=multi-user.target
Quitter l’éditeur avec Ctrl+X suivi de Y et la touche Entrée pour valider l’enregistrement.
Copier ce fichier au bon emplacement système, recharger la liste des services, activer le service et démarrer le service :
sudo cp ~/tetra-bluestation/bluestation-bs.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable bluestation-bs
sudo systemctl start bluestation-bs
On peut confirmer que l’exécution s’est déroulée sans défaut en vérifiant le statut du service :
systemctl status bluestation-bs
● bluestation-bs.service - TETRA BlueStation base station service
Loaded: loaded (/etc/systemd/system/bluestation-bs.service; enabled; preset: enabled)
Active: active (running) since Wed 2026-05-06 16:23:05 BST; 15min ago
Invocation: a15cf9eaf6734bd0955a2018a7fb17da
Main PID: 1863 (bluestation-bs)
Tasks: 7 (limit: 764)
CPU: 8min 34.802s
CGroup: /system.slice/bluestation-bs.service
└─1863 /home/pi/tetra-bluestation/target/release/bluestation-bs /home/pi/tetra-bluestation/config.toml
Enfin, on peut surveiller les événements de BlueStation via cette commande :
journalctl -u bluestation-bs --output=cat -f
La nimi cellule TETRA est maintenant pleinement fonctionnelle et autonome.

