My Own Memory Hole
Blog♯Raspberry Pi

Mosquitto, un broker MQTT

J'ai découvert le protocole MQTT (Message Queuing Telemetry Transport) lors de l'interfaçage de ma station météo Netatmo et du logiciel WeeWX sur mon Raspberry Pi ; il s'agit d'un protocole de messagerie de type publication-abonnement, extrêmement rapide et léger, utilisé notamment dans l'internet des objets.

Nous allons ici voir comment installer et configurer un broker, un agent MQTT, en l'occurrence Mosquitto, en nous appuyant très largement sur l'article « How to setup your own MQTT Broker » de Pat O'Brien.

Installation et premiers tests

On commence par l'installer :

sudo apt install mosquitto mosquitto-clients

Nous pouvons dès lors « jouer » un peu...
On peut commencer par vérifier la version de notre agent ou le temps écoulé depuis son lancement :

mosquitto_sub -t \$SYS/broker/version
mosquitto_sub -t \$SYS/broker/uptime

Puis commencez par ouvrir deux sessions dans votre terminal (on ne saurait trop vous conseiller d'utiliser tmux, présenté dans cet article)–: dans la première, on lance la commande mosquitto_sub -t test/# puis dans la seconde on lance mosquitto_pub -t test/test -m "Test message"...

premiers pas avec mosquitto

Configuration, accès, SSL

Il convient de préciser d'emblée que Mosquitto est très regardant sur le formatage de ses fichiers de configuration et qu'il n'accepte pas d'espace en fin de ligne.

On commence par éditer un fichier /etc/mosquitto/conf.f/conf.d/myconfig.conf :

persistence false

# mqtt
listener 1883
protocol mqtt

# websockets
listener 9001
protocol websockets

On redémarre alors Mosquitto :

sudo service mosquitto restart

Gestion des accès

Mosquitto permet de gérer topic par topic les droits de publication et d'abonnement.

On commence par générer un couple identifiant / mot de passe, ainsi :

sudo mosquitto_passwd -c /etc/mosquitto/passwd USER

puis on édite un fichier /etc/mosquitto/acl

# Autoriser l'abonnement anonyme à $SYS
topic read $SYS/#

# Autoriser l'abonnement et la publication anonymes à test 
topic test/#

# Autoriser le seul abonnement anonyme à testlecture
topic read testlecture/#

# Accès limité pour l'écriture sur testlecture
# Abonnement et publication sur secret pour USER seulement
user USER
topic write testlecture
topic secret/#

on édite ensuite le fichier /etc/mosquitto/conf.d/myconfig.conf pour qu'il ressemble à cela :

persistence false
allow_anonymous true
password_file /etc/mosquitto/passwd
acl_file /etc/mosquitto/acl

# mqtt
listener 1883
protocol mqtt

# websockets
listener 9001
protocol websockets

On redémarre le service avant de vérifier avec la commande netstat que les ports déclarés sont bien ouverts :

$ sudo service mosquitto restart
$ sudo netstat -tulpn | grep 1883
tcp        0      0 0.0.0.0:1883            0.0.0.0:*               LISTEN      142015/mosquitto
tcp6       0      0 :::1883                 :::*                    LISTEN      142015/mosquitto
$ sudo netstat -tulpn | grep 9001
tcp6       0      0 :::9001                 :::*                    LISTEN      142015/mosquitto

Pour le canal test, accessible sans authentification ni pour l'abonnement ni pour la publication, on vérifie avec cette commande dans notre première session :

mosquitto_sub -h localhost -p 1883 -t test/#

et dans la seconde nous lançons :

mosquitto_pub -h localhost -p 1883 -t test/1 -m "Hello topic test/1"

Le canal testlecture ne devrait être accessible anonymement qu'en lecture :

mosquitto_sub -h localhost -p 1883 -t testlecture/#

La première des deux commandes suivantes ne devrait rien renvoyer dans notre première session ; il convient en effet de s'authentifier pour publier dans ce canal :

mosquitto_pub -t testlecture/test -m "test"
mosquitto_pub -u USER -P PASSWORD -t testlecture/test -m "test"

Enfin, le canal secret nécessite de s'authentifier aussi bien pour l'abonnement que pour la publication :

mosquitto_sub -u USER -P PASSWORD -t secret/#
mosquitto_pub -u USER -P PASSWORD -t secret/test -m "test"

Certificat SSL

Il est bien évidemment possible de rendre cet agent MQTT accessible sur Internet et de le configurer pour qu'il utilise une connexion sécurisée grâce à un certificat comme ceux proposés par Let's Encrypt. Nous allons considérer ici que vous avez déjà configuré votre nom de domaine et généré le certificat, comme évoqué ici.

Ainsi, dans mon cas, vous devriez pouvoir vous abonner au canal weather produit par WeeWX et son plugin weewx-mqtt avec la commande suivante :

mosquitto_sub -h meteo.momh.fr -p 8883 --capath /etc/ssl/certs/ -t weather/#

Il convient, après avoir généré son certificat, d'éditer le fichier /etc/mosquitto/conf.d/myconfig.conf de la sorte :

# Insecure mqtt to localhost only, and secure mqtt
listener 1883 localhost
listener 8883
certfile /etc/letsencrypt/live/DOMAINE/cert.pem
cafile /etc/letsencrypt/live/DOMAINE/chain.pem
keyfile /etc/letsencrypt/live/DOMAINE/privkey.pem
protocol mqtt

# websockets
listener 9001
certfile /etc/letsencrypt/live/DOMAINE/cert.pem
cafile /etc/letsencrypt/live/DOMAINE/chain.pem
keyfile /etc/letsencrypt/live/DOMAINE/privkey.pem
protocol websockets

Après avoir redémarré le service

sudo service mosquitto restart

on peut tester ainsi, avec pour chacune de nos deux sessions, les commandes :

mosquitto_sub -h DOMAINE -p 8883 --capath /etc/ssl/certs/ -u USER -P PASSWORD -t secret/#
mosquitto_pub -h DOMAINE -p 8883 -u USER -P PASSWORD -t secret/test -m "Test message dans le canal secret"

Nous pouvons désormais utiliser notre agent MQTT avec WeeWX !

Auto-hébergement, DynHost OVH et certificat Let's Encrypt sous Raspbian

Ayant « associé » ma station météo Netatmo à mon Raspberry Pi grâce à WeeWX (lire ce post), je souhaite désormais rendre accessible sur Internet le site web ainsi généré. Je considère ainsi que vous avez déjà un serveur Apache fonctionnel et configuré (même si nous aborderons la création d'un VirtualHost par la suite).

DynHost et mise à jour de l'IP

Étant chez OVH, je profite de leur service DynHOST qui « permet de faire pointer un sous-domaine vers une adresse IP dynamique qui sera mise à jour dans votre zone DNS à chaque changement de celle-ci. ».

On crée notre DynHost puis on en gère les accès, en créant un nouvel identifiant. Puis, sur notre Raspberry Pi, on installe ddclient :

sudo apt install ddclient

Lors de l'installation, des écrans successifs vont nous permettre de le configurer :

  1. Fournisseur de service de DNS dynamique : Autre
  2. Protocole de mise à jour du DNS dynamique : dyndns2
  3. Serveur de DNS dynamique : www.ovh.com
  4. Mandataire HTTP : néant
  5. Identifiant : l'identifiant saisi précédemment (comprenant le nom de domaine en préfixe)
  6. Mot de passe : le mot de passe correspondant
  7. Méthode de découverte d'adresse IP : Service de découverte d'IP basée sur le web
  8. Hôtes à mettre à jour : le DynHost créé précédemment

La configuration peut se faire sinon à la main, en éditant le fichier /etc/ddclient.conf qui doit ressembler à cela :

protocol=dyndns2
server=www.ovh.com
login=DOMAINE-USER
password='PASSWORD'
DYNHOST

Redirection des ports

Ensuite, il faut se rendre dans l'interface de gestion de votre Box Internet et configurer, dans la section NAT, la redirection des port 80 et 443 vers ceux de votre Raspberry Pi (pour en connaître l'IP sur votre réseau local, utilisez la commande hostname -I).

Désormais, en saisissant l'URL de votre DynHost, vous devriez accéder à la même page que lorsque vous accédez à l'adresse localhost.

Mise en place du HTTPS

Pour ce faire, nous allons générer un certificat Let's Encrypt pour notre DynHost.

On commence par copier, dans /etc/apache2/sites-available/, le fichier 000-default.conf vers DYNHOST.conf ; par exemple :

sudo cp /etc/apache2/sites-available/000-default.conf /etc/apache2/sites-available/meteo.momh.fr.conf

puis par le configurer pour pointer vers notre répertoire /var/www/html/weewx ou autre répertoire en fonction du ou des skin(s) que vous utilisez. Par exemple, minimalement :

<VirtualHost *:80>
        ServerName meteo.momh.fr

        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/html/weewx

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

</VirtualHost>

Pour générer le certificat, nous allons utiliser certbot, qu'il convient d'installer :

sudo apt install certbot python3-certbot-apache

puis d'activer le module ssl d'Apache :

sudo a2enmode ssl
sudo systemctl restart apache2

On peut alors générer notre certificat avec la commande suivante :

sudo certbot --apache -d DynHOST

Il vous est proposé de rediriger l'éventuel traffic HTTP vers HTTPS :

Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for new sites, or if you're confident your site works on HTTPS. You can undo this change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2

Un certificat est alors généré et un nouvel hôte virtuel est configuré dans Apache, à l'emplacement /etc/apache2/sites-available/DYNHOST-le-ssl.conf. Si vous avez choisi l'option 2, le VirtualHost présenté ci-dessus se voit modifié avec les lignes suivantes :

RewriteEngine on
RewriteCond %{SERVER_NAME} =DYNHOST
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]

Pour voir que tout fonctionne, on peut utiliser l'outil de test proposé par ssllabs.com.

Station météo Netatmo & WeeWX [bis]

Suite à mon premier post sur le sujet, je me suis attelé plus longuement à la compatibilité du plugin weewx-netatmo de Matthew Wall avec les versions 4 de WeeWX sous Python 3.

Installation

De ce fait, on n'a plus besoin d'installer une ancienne version de WeeWX et l'on peut se reporter sur la version disponible dans les dépôts :

sudo apt install weewx

Désormais, la configuration et autres skins ne se trouvent plus dans /home/weewx mais dans /etc/weewx. L'installation du plugin weewx-netatmo peut se faire depuis mon fork du plugin :

wget -O weewx-netatmo.zip https://github.com/bricebou/weewx-netatmo/archive/master.zip
sudo wee_extension --install weewx-netatmo.zip
sudo wee_config --reconfigure

On ouvre ensuite le fichier de configuration /etc/weewx/weewx.conf pour vérifier que nos identifiants Netatmo soient bien pris en compte. Ensuite, on redémarre WeeWX :

sudo service weewx restart

Modifications apportées

Pour ce faire, on utilise l'outil 2to3 :

sudo pip3 install 2to3

puis on lance la commande :

2to3 -w netatmo.py

Une erreur subsistait mais le problème avait déjà été résolu par kwalker05 ; il suffit, à la ligne 521, de remplacer :

params = urlencode(params)

par

params = urlencode(params).encode("utf-8")

ncspot, un client ncurse pour Spotify

Si mes recherches pour utiliser Spotify depuis mon Raspverry Pi sous Raspbian Buster m'ont tout d'abord conduit à la prise en charge des DRM pour utiliser le client web open.spotify.com puis au client Spotify Connect raspotify, j'ai enfin découvert un client Spotify ne nécessitant pas de serveur X : ncspot est ainsi un client ncurse pour Spotify écrit en Rust et hautement inspiré des clients ncmpc ou ncmpcpp pour MPD.

Installation

On commence par installer les dépendances :

sudo apt install libncursesw5-dev libdbus-1-dev libpulse-dev libssl-dev libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev

On installe ensuite une « instance » de Rust, grâce au script rustup :

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

avant de lancer l'installation à proprement parler de ncspot :

cargo install ncspot

Usage et configuration

Il suffit alors de lancer la commande ncspot et l'on peut alors naviguer entre trois écrans grâce aux touches F1, F2 et F3 : queue, recherche, bibliothèque.

Pour en savoir plus, il suffit d'accéder à l'écran d'aide avec la touche ?.

La configuration se fait dans le fichier ~/.config/ncspot/config.toml avec a possibilité de définir soi-même des raccourcis clavier au sein d'une section [keybindings] et un thème au sein d'une section [theme] (un générateur vous en facilite la création) :

backend = "pulseaudio"

[saved_state]
volume = 70
repeat = "no"
shuffle = true

[keybindings]

"Shift+p" = "pause"

[theme]
background = "black"
primary = "green"
secondary = "cyan"
title = "magenta"
playing = "black"
playing_selected = "blue"
playing_bg = "magenta"
highlight = "black"
highlight_bg = "green"
error = "white"
error_bg = "red"
statusbar = "magenta"
statusbar_progress = "magenta"
statusbar_bg = "black"
cmdline = "cyan"
cmdline_bg = "light black"

Scrobbling

Si l'on souhaite scrobbler ce que l'on écoute avec ncspot, il convient d'installer rescrobbled, écrit lui aussi en Rust :

wget https://github.com/InputUsername/rescrobbled/archive/v0.2.0.tar.gz
tar xvzf v0.2.0.tar.gz
cd rescrobbled-0.2.0/
cargo install --path .

La configuration se fait à travers le fichier ~/.config/rescrobbled/config.toml mais nécessite d'avoir généré au préalable un couple clé-secret via cette page de Last.fm :

mkdir ~/.config/rescrobbled/
nano ~/.config/rescrobbled/config.toml
lastfm-key = "Last.fm API key"
lastfm-secret = "Last.fm API secret"
#listenbrainz-token = "ListenBrainz API token"
enable-notifications = false
min-play-time = 0 # in seconds
player-whitelist = [ "ncspot" ] # if empty or ommitted, will allow all players

Il faut ensuite lancer la commande rescrobbled alors que le lecteur ncspot fonctionne ; il vous sera alors demander votre identifiant et votre mot de passe.
Pour lancer le service en tant que démon, il faut placer le fichier ~/rescrobbled-0.2.0/rescrobbled.service dans votre répertoire ~/.config/systemd/user/ :

mkdir -p ~/.config/systemd/user
cp ~/rescrobbled-0.2.0/rescrobbled.service ~/.config/systemd/user/

Lancez ensuite la commande suivante pour lancer rescrobbled au démarrage :

systemctl --user enable rescrobbled.service

et pour le lancer sans avoir à redémarrer :

systemctl --user start rescrobbled.service

Raspbian, Spotify et PulseAudio

Il est possible d'utiliser son Raspberry Pi sous Raspbian comme client Spotify Connect, c'est-à-dire d'en faire une sorte de module de sortie audio, et ce grâce à Raspotify.

Installation

Pour l'installer, rien de plus simple :

# Install curl and https apt transport
sudo apt-get -y install curl apt-transport-https

# Add repo and its GPG key
curl -sSL https://dtcooper.github.io/raspotify/key.asc | sudo apt-key add -v -
echo 'deb https://dtcooper.github.io/raspotify raspotify main' | sudo tee /etc/apt/sources.list.d/raspotify.list

# Install package
sudo apt-get update
sudo apt-get -y install raspotify

Ensuite, depuis votre application Spotify, il suffit de sélectionner la sortie « Raspotify » dans les options proposées dans le menu accessible depuis l'icone dans le bas inférieur gauche de l'écran de lecture :

spotify select outspotify select out raspotify

Configuration

Raspotify est fonctionnel out of the box mais, s'il en est besoin, il est possible de jouer sur certains paramètres dans le fichier /etc/default/raspotify.

En cas de modification, il convient alors de relancer le service raspotify :

sudo service raspotify restart

Raspotify & PulseAudio

Pour que Raspotify utilise PulseAudio, il suffit de suivre la démarche proposée par Marc Fauvain  :

cd /var/cache/raspotify
sudo mkdir .pulse
sudo sh -c 'echo "default-server = 127.0.0.1" > .pulse/client.conf'
sudo chown -R raspotify:raspotify .pulse

Il faut également éditer le fichier /etc/asound.rc :

sudo nano /etc/asound.rc
pcm.!default {
    type pulse
}

ctl.!default {
    type pulse
}

MPD, PulseAudio et Bluetooth sous Raspbian Buster

Alors que je venais juste de mettre en place la cohabitation entre MPD et mon ampli connecté en Bluetooth sur mon Raspberry Pi 2 (voir ici), je basculais sur un modèle 4, modifiant quelque peu mes usages et utiisant cette fois-ci l'interface graphique (afin de profiter notamment de Spotify, une fois la gestion des DRM ajoutée). Et du coup, afin d'avoir une maîtrise plus fine de mes sorties audio, j'ai opté pour l'utilisation de PulseAudio.

On commence par purger bluealsa s'il est installé :

sudo apt purge bluealsa

puis on installe tout ce qui concerne le Bluetooth et PulseAudio :

 sudo apt install pi-bluetooth pulseaudio pulseaudio-module-bluetooth paprefs pavumeter pavucontrol pasystray

On vérifie la configuration de PulseAudio (/etc/pulse/default.pa) notamment pour ce qui est du Bluetooth :

### Automatically load driver modules for Bluetooth hardware
.ifexists module-bluetooth-policy.so
load-module module-bluetooth-policy
.endif

.ifexists module-bluetooth-discover.so
load-module module-bluetooth-discover
.endif

et on ajoute ces éléments :

# automatically switch to newly-connected devices
load-module module-switch-on-connect

load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1 # IP of localhost

On tue PulseAudio :

pulseaudio -k

S'il ne redémarre pas tout seul, il suffit de lancer la commande :

pulseaudio -D

On veille à bien connecter notre enceinte Bluetooth via l'utilitaire en ligne de commande bluetoothctl : on commence par rechercher les périphériques disponibles (scan on), puis on lui accorde notre confiance (pas d'authentification nécessaire, trust) avant de faire l'appairage avec le bon device (pair) et de nous y connecter (connect) ; on peut alors cesser la recherche (scan off) :

bluetoothctl 
Agent registered
[bluetooth]# scan on
Discovery started
[CHG] Controller 00:1A:7D:DA:71:15 Discovering: yes
[CHG] Device FC:58:FA:14:27:BC RSSI: -71
[CHG] Device FC:58:FA:14:27:BC TxPower: 4
[...]
[bluetooth]# pair FC:58:FA:14:27:BC 
Attempting to pair with FC:58:FA:14:27:BC
[CHG] Device FC:58:FA:14:27:BC Connected: yes
[...]
[CHG] Device FC:58:FA:14:27:BC ServicesResolved: yes
[CHG] Device FC:58:FA:14:27:BC Paired: yes
Pairing successful
[CHG] Device FC:58:FA:14:27:BC ServicesResolved: no
[CHG] Device FC:58:FA:14:27:BC Connected: no
[bluetooth]# trust FC:58:FA:14:27:BC 
[CHG] Device FC:58:FA:14:27:BC Trusted: yes
Changing FC:58:FA:14:27:BC trust succeeded
[bluetooth]# connect FC:58:FA:14:27:BC 
Attempting to connect to FC:58:FA:14:27:BC
[CHG] Device FC:58:FA:14:27:BC Connected: yes
Connection successful
[CHG] Device FC:58:FA:14:27:BC ServicesResolved: yes
[Tangent Ampster BT]# scan off
Discovery stopped
[...]
[Tangent Ampster BT]# exit

On configure une sortie PulseAudio dans la configuration de mpd, /etc/mpd.conf :

sudo nano /etc/mpd.conf
audio_output {
        type            "pulse"
        name            "My Pulse Output"
        server          "localhost"
        mixer_type      "software"
}

On relance MPD et l'affaire est jouée :

sudo service mpd restart