Iot speaker
Projet
| Objectif | Les acteurs | Compétences | Matériel |
|---|---|---|---|
|
Construire des enceintes sans-fils (sauf le courant) actives connectées capables de travailler en "groupe" |
|
|
Choix de l'amplificateur
A faire:
- Expliquer la différence entre classe AB et D (qualité sonore vs. éco énergie)
- Faire un tableau récapitulatif des caractéristiques des amplis:
- mono / stéréo / les deux
- tension de fonctionnement / consommation (5v, 12v, 24v, etc...)
- puissance (5w, 20w, 30w, etc...)
- impédance de sortie (2 x 4Ω, 1 x 8Ω, etc...)
- réponse fréquentielle ;
- résistance aux courts-circuits ;
- bruit pop (quand pas de musique);
- besoin d'un dissipateur ou non (effet joule à prévoir...)
- lien vers la documentation si besoin d'info(eg. pour le TDA8932)
- le plus important : le ressenti !
Classe AB
| Modèle | Image | Phonique | Puissance | Impédance | Tension | PCC | POP | Dissipateur | Documentation | Avis |
|---|---|---|---|---|---|---|---|---|---|---|
|
TDA7297 |
Stéréo |
2 x 15W |
2A (peak) |
6 - 18 V |
OUI |
Avec µControlleur |
Obligatoire |
Super amplification sans distorsion !
|
Classe D
| Modèle | Image | Phonique | Puissance | Impédance | Tension | PCC | POP | Dissipateur | Documentation | Avis |
|---|---|---|---|---|---|---|---|---|---|---|
|
XH-M531 (YDA138) |
Stéréo |
2 x 10 W |
8 Ω |
12 - 15V |
OUI |
OUI |
NON |
|||
|
PAM8403 |
Stéréo |
3W(*2 ?) |
4 Ω |
5V |
OUI |
Reduit mais présent |
||||
|
PAM8610 |
Stéréo |
2 x 10 W |
8 Ω |
7 - 15 V |
OUI |
OUI |
NON |
|||
|
XPT8871 |
Mono |
3.5w
|
3 Ω
|
5V |
OUI |
OUI |
NON |
| ||
|
TDA8932 |
Stéréo |
2*15W |
4 Ω |
Entre 10 V et 36 V (Rechercher quel est le plus optimal) |
OUI |
NON |
||||
|
TPA3110 |
Stéréo |
15W/ch |
8 Ω |
16 V |
OUI |
NON |
OUI |
| ||
|
TPA3116 |
Stéréo |
2 x 50W |
4 Ω |
21 V |
OUI |
OUI par µC |
OUI |
| ||
|
TPA3118 |
Stéréo |
2 x 30W |
8 Ω |
12 - 24 V |
OUI |
OUI par µC |
OUI |
|
Potentiomètre digital
A faire
Décodeur MP3
Présentation
Le décodeur MP3 choisi est le VS1053
Ce décodeur offre une interface SPI et permet de lire ainsi que d'enregistrer des fichiers aux formats :
- lecture :
- MP3 ;
- Ogg Vorbis (libre de droit) ;
- PCM;
- WAV;
- enregistrement :
- Ogg Vorbis(libre de droit) ;
Documentation technique
Voici la documentation du VS1053, pour ceux qui voudraient écrire une librairie C++ !
Schéma
Les tests ont été réalisés avec un WemOS D1 mini (ESP8266) :
| ESP8266 | VS1053 | Connections | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
Vous devriez avoir quelque chose comme ça :
Programmation
Tout d'abord un grand merci à baldram pour sa librairie que nous allons utiliser !
Le plus simple est de regarder l'exemple SimpleMP3Player fourni avec la librairie :
// La fameuse librairie de baldram
#include <VS1053.h>
// Un MP3 au format binaire dans un tableau (le fichier est livré avec la librairie)
#include <helloMp3.h>
// Définition des port SPI utilisé (ici pour l'ESP8266)
#define VS1053_CS D1
#define VS1053_DCS D0
#define VS1053_DREQ D3
// Définition du volume
#define VOLUME 80 // volume level 0-100
// Création de l'objet VS1053
VS1053 player(VS1053_CS, VS1053_DCS, VS1053_DREQ);
void setup () {
// Démarrage du SPI
SPI.begin();
// démarrage du VS1053
player.begin();
// Ligne nécessaire pour certain lecteur
player.switchToMp3Mode();
player.setVolume(VOLUME);
}
void loop() {
// On joue le MP3 dans le tableau
player.playChunk(helloMp3, sizeof(helloMp3));
// On attend 3 secondes
delay(3000);
}
Pour que cet exemple fonctionne, il faut soit copier le contenu de HelloMp3.h dans le fichier exemple, soit copier le fichier dans le répertoire de la librairie dans votre dossier Arduino.
Une fois la compilation terminée, vous devriez entendre du bruit sortir du VS1053 !
Flux musical
La communication se fera en générant un flux entre le programme (PC) et l'enceinte (ESP8266). Ce flux, pour des raisons de simplicité, se fera en TCP car ce protocole gère les retransmissions, les tampons d'entrée / sortie, etc...
A terme, une application temps réel comme la musique mériterait de passer sur de l'UDP. Surtout que, si l'on souhaite utiliser du multicast, pour parler à un groupe d'enceintes, on ne peut le faire qu'en UDP !
Envoi : serveur en PHP
La partie serveur prend un fichier, ici un MP3, lit un morceau du fichier et l'envoi à l'enceinte à travers une socket TCP. Deux paramètres sont à prendre en compte :
- le temps d'attente entre chaque envoi;
- la taille d'un morceau;
Ces paramètres nécessiteront certainement un temps d'ajustement qui sera en fonction de la latence, la congestion du réseau, les capacités des puces impliquées (ici l'ESP8266), etc...
|
Les extraits de code qui vont suivre respectent vaguement les préceptes, au combien importants, de la programmation orientée objet et sont fournis à titre indicatif dans l'unique but de démontrer la faisabilité d'un tel projet ! Voici l'archive qui contient le code exposé ci-dessous. |
MusicSender
Voici le contenu du fichier MusicSender.class.php :
<?php
class MusicSender
{
// L'adresse IP de l'enceinte connectée
private $ip;
// La socket utilisée pour communiquer avec l'enceinte
private $socket;
// Le port de l'enceinte connectée
private static $PORT = 5045;
// Le temps d'attente en microsecondes entre chaque paquets
private static $SLEEP_INTERVAL = 2500;
// La taille de chacun des paquets
private static $CHUNK_BUFFER = 32768;
public function __construct($ip)
{
$this->ip = $ip;
}
/**
* Envoie le MP3 à l'enceinte
*
* @param string $mp3File
* @return boolean
*/
public function play($mp3File)
{
if (is_file($mp3File)) {
// Ouverture du fichier en lecture binaire
$file = fopen($mp3File, 'rb');
if ($file === FALSE) {
echo "Fail reading file " . $file . "\n";
return FALSE;
}
// Ouverture de la socket
$socket = $this->openSocket();
// Tant qu'on est pas à la fin du fichier
while (! feof($file)) {
// Envoie d'un "morceau" de taille CHUNK_BUFFER à l'enceinte
if (@socket_write($this->socket, fread($file, self::$CHUNK_BUFFER), self::$CHUNK_BUFFER) === FALSE) {
// Problème de socket (fermeture, déconnexion, etc...)
return FALSE;
}
usleep(self::$SLEEP_INTERVAL);
}
// Fermeture de la socket
$this->closeSocket();
// Fermeture du fichier
fclose($file);
return TRUE;
}
echo $file." does not exists !\n";
return FALSE;
}
/**
* Lit tous les mp3 présents dans le dossier
*
* @param string $dir
*/
public function readFolder($dir)
{
if (is_dir($dir)) {
// On récupére les fichiers sans les 2 premiers '.' et '..'
$files = array_slice(scandir($dir), 2);
foreach ($files as $file) {
// On créé le chemin absolu
$mp3File = $dir . DIRECTORY_SEPARATOR . $file;
// On test l'extension
if (pathinfo($file)["extension"] == "mp3") {
$this->play($mp3File);
}
}
return true;
}
return false;
}
/**
* Ouverture de la socket
*/
private function openSocket()
{
if (($this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) == FALSE) {
echo "socket_create_listen() a échoué : " . socket_strerror(socket_last_error($this->socket)) . "\n";
return false;
}
if (socket_connect($this->socket, $this->ip, self::$PORT) == FALSE) {
echo "socket_bind() a échoué : " . socket_strerror(socket_last_error($this->socket)) . "\n";
return false;
}
}
/**
* Fermeture de la socket
*/
private function closeSocket()
{
socket_close($this->socket);
}
}
Launch.php
On créé un fichier Launch.php qui va servir à appeler la classe précédente :
<?php
include 'MusicSender.class.php';
$esp_ip = "192.168.1.200";
$dir = "!!chemin_vers_des_fichiers_mp3!!";
$sender = new MusicSender($esp_ip);
$sender->readFolder($dir);
Réception : ESP8266
|
Soyez sûr de comprendre la section sur comment écrire un sketch avant de poursuivre. Le code ci-dessous fait référence à des parties bien spécifiques, détaillées et expliquées dans la section suscitée. |
Partie WiFi
La première étape consiste à raccorder notre ESP au réseau WiFi !
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
// WiFi Parameters //
const char ssid[] = "cfcasa";
const char password[] = "***********";
IPAddress ip(192, 168, 1, 200);
IPAddress gw(192, 168, 1, 254);
IPAddress mask(255, 255, 252, 0);
void setup() {
// on démarre le port série
Serial.begin(115200);
// On attend "un peu" que le buffer soit prêt
delay(10);
// On démarre le WiFi
initWiFi();
}
void loop() {
}
void initWiFi() {
// On efface la configuration précédente
WiFi.disconnect(true);
Serial.printf("\nConnexion a %s", ssid);
// Initialisation de la connection
WiFi.config(ip, gw, mask, gw);
WiFi.begin(ssid, password);
// Test pour déterminer quand la connection est prete
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// Affichage des informations
Serial.printf(" connecté\nAdresse IP: %s\n", WiFi.localIP().toString().c_str());
}
Lecteur MP3
Vient ensuite la partie du lecteur MP3. Dans la partie des imports, ajoutez la ligne suivante :
#include <VS1053.h>
Dans la partie des variables statiques ajoutez les lignes suivantes :
// MP3 Parameters //
#define VS1053_CS D1
#define VS1053_DCS D0
#define VS1053_DREQ D3
#define VOLUME 70 // volume level 0-100
VS1053 player(VS1053_CS, VS1053_DCS, VS1053_DREQ);
uint8_t volume_level = VOLUME;
A la fin de la fonction setup() nous allons ajouter les lignes suivantes :
// On démarre le shield MP3
initMP3();
A la suite, on insère la fonction suivante :
void initMP3() {
SPI.begin();
// Démarrage du lecteur MP3
player.begin();
player.switchToMp3Mode();
player.setVolume(volume_level);
}
Récupération du stream MP3
On va maintenant récupérer le flux envoyé depuis le serveur. Dans la partie des variables statiques ajoutez les lignes suivantes :
// MP3 Server parameters //
const uint16_t MP3_PORT = 5045;
WiFiServer server(MP3_PORT);
const uint16_t BUFFER_SIZE = 16384;
//const uint16_t BUFFER_SIZE = 32768;
byte buffer[BUFFER_SIZE + 1];
uint16_t byteRead = 0;
bool isBufferReady = false;
WiFiClient client;
A la fin de la fonction setup() nous allons ajouter les lignes suivantes :
// On démarre le serveur TCP
initMp3Server();
A la suite, on insère les fonctions suivantes :
void initMp3Server() {
// Démarrage du serveur TCP
server.begin();
Serial.printf("Ecoute TCP sur le port %d\n", MP3_PORT);
}
void readMp3() {
// Un client est connecté
if (client) {
// Si le client est toujours connecté...
if (client.connected()) {
// Tant que l'on reçoit de la musique
while (client.available() > 0) {
// On rempli le buffer avec le contenu du paquet TCP
buffer[byteRead++] = client.read();
// Si le buffer est plein
if (byteRead == BUFFER_SIZE) {
// On previent que le buffer est plein
isBufferReady = true;
// On sort de la boucle
break;
}
}
// Si le buffer est plein
if (isBufferReady) {
// On envoi tout au lecteur MP3
player.playChunk(buffer, byteRead);
// On RAZ le compteur de bytes
byteRead = 0;
}
} else {
// Si le client est déconneté
client = server.available();
// On prévient que le buffer est vide
isBufferReady = false;
// On RAZ le compteur de bytes
byteRead = 0;
Serial.println(F("Client disconnected"));
}
} else {
// On test la présence d'un client
client = server.available();
if (client) {
// Un client est connecté !
Serial.println(F("New client !"));
}
}
}
Enfin, on ajoute le ligne suivante dans la fonction loop() :
readMp3();
A ce stade, l'ESP devrait être capable de récupérer le flux et de le jouer !
Envoi de commandes
Envoyer de la musique c'est bien, pouvoir régler le volume c'est encore mieux !
La façon la plus simple d'adresser le problème c'est d'utiliser un autre port et, comme les messages sont petits, d'utiliser UDP.
Partie Serveur
Dans la même veine, voici la classe qui permet d'envoyer les commandes :
<?php
class MusicHandler
{
// L'adresse IP de l'enceinte connectée
private $ip;
// La socket utilisée pour communiquer avec l'enceinte
private $socket;
// Le port de l'enceinte connectée
private static $PORT = 5046;
// Le port de l'enceinte connectée
private static $RETRIES = 3;
// Liste de commandes
private static $CMD_VOLUME = 'V';
private static $CMD_LOCATE = 'L';
private static $CMD_RESET = 'R';
private static $RESPONSE_NOK = 'N';
public function __construct($ip)
{
$this->ip = $ip;
}
/**
* Augmente le volume d'un incrément
*
* @return boolean
*/
public function volumeUp()
{
return $this->sendCommand(self::$CMD_VOLUME . "+");
}
/**
* Baisse le volume d'un incrément
*
* @return boolean
*/
public function volumeDown()
{
return $this->sendCommand(self::$CMD_VOLUME . "-");
}
/**
*
* @return boolean
*/
public function setVolume($volume)
{
if ($volume < 0 || $volume > 100) {
return false;
}
return $this->sendCommand(self::$CMD_VOLUME . $volume);
}
public function getVolume(){
return $this->sendCommand(self::$CMD_VOLUME);
}
/**
*
* @return boolean
*/
public function doReset()
{
return $this->sendCommand(self::$CMD_RESET);
}
public function doLocate(){
return $this->sendCommand(self::$CMD_LOCATE);
}
/**
* Envoie une commande au module
*
* @param string $message
*/
private function sendCommand($message)
{
$result = false;
if ($this->openSocket() !== FALSE) {
if (socket_sendto($this->socket, $message, strlen($message), 0, $this->ip, self::$PORT) !== FALSE) {
$result = $this->readAcknoledge();
}
}
$this->closeSocket();
return $result;
}
/**
* Attend la confirmation de la commande
*
* @return boolean
*/
private function readAcknoledge()
{
$buf = "";
$retries = 0;
while ($retries < self::$RETRIES) {
$byteReceived = socket_recvfrom($this->socket, $buf, 2048, 0, $this->ip, self::$PORT);
if ($byteReceived > 0) {
return $buf != self::$RESPONSE_NOK ? $buf : FALSE;
}
usleep(500000);
$retries ++;
}
return false;
}
/**
* Ouverture de la socket
*/
private function openSocket()
{
if (($this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP)) === FALSE) {
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
echo ("Couldn't create socket: [$errorcode] $errormsg \n");
return false;
}
return true;
}
/**
* Fermeture de la socket
*/
private function closeSocket()
{
socket_close($this->socket);
}
}
On créé un fichier Command.php qui va servir à appeler la classe précédente :
<?php
include 'MusicHandler.class.php';
$esp_ip = "192.168.1.200";
$handler = new MusicHandler($esp_ip);
// Exemple de commandes //
//$result = $handler->volumeUp();
//$result = $handler->getVolume();
//$result = $handler->doReset();
//$result = $handler->setVolume(70);
$result = $handler->doLocate();
echo $result !== FALSE ? "ok : $result\n" : "nok\n";
Partie Cliente
Dans la partie des imports ajoutez la ligne suivante :
#include <WiFiUDP.h>
Dans la partie des variables statiques ajoutez les lignes suivantes :
// Command Server parameters //
const uint16_t CMD_PORT = 5046;
// L'instance du serveur UDP
WiFiUDP udp;
// Liste de commandes
const char CMD_VOLUME = 'V';
const char CMD_LOCATE = 'L';
const char CMD_RESET = 'R';
const char RESPONSE_NOK[] = "N";
// Localisation parameters
uint8_t ledPin = LED_BUILTIN;
const uint16_t BLINK_FREQ = 500;
const uint8_t BLINK_REP = 60;
uint8_t repetition = 0;
uint32_t timer;
bool isLocate = false;
A la fin de la fonction setup() nous allons ajouter les lignes suivantes :
// On démarre le serveur UDP
initCmdServer();
// On prépare la led de localisation
initLocateLed();
A la suite, on insère les fonctions suivantes :
bool readCmd() {
if (udp.parsePacket() > 0) {
uint8_t len = udp.available();
char c = udp.read();
if (len == 1) {
if (c == CMD_RESET) {
player.softReset();
sendPacket(CMD_RESET);
return true;
} else if (c == CMD_VOLUME) {
sendPacket(volume_level);
return true;
} else if (c == CMD_LOCATE) {
if (!isLocate) {
locate(true);
sendPacket(CMD_LOCATE);
return true;
}
}
} else if (len == 2) {
char value = udp.read();
if (c == CMD_VOLUME) {
if (value == '+' && volume_level != 100) {
setVolume(volume_level + 1);
} else if (value == '-' && volume_level != 0) {
setVolume(volume_level - 1);
} else {
sendNack();
return false;
}
return true;
}
} else {
if (c == CMD_VOLUME) {
char value[4] = "0";
udp.read(value, 3);
uint8_t vol = atoi(value);
if (vol >= 0 && vol <= 100) {
setVolume(vol);
return true;
}
}
}
sendNack();
return false;
}
// Check if locate needs to be done
locate(false);
}
void locate(bool blink) {
if (blink) {
// Initialisation des variables
digitalWrite(ledPin, LOW);
isLocate = true;
repetition = 0;
timer = millis();
} else if (isLocate) {
// Calcule du temps
uint32_t elapsed = millis() - timer;
if (elapsed > BLINK_FREQ) {
repetition++;
if (repetition == BLINK_REP) {
// On a atteint le nombre de clignotements
isLocate = false;
digitalWrite(ledPin, HIGH);
} else {
// On fait clignoter la led
digitalWrite(ledPin, !digitalRead(ledPin));
timer = millis();
}
}
}
}
void setVolume(uint8_t vol) {
volume_level = vol;
Serial.printf("Setting volume to %d\n", volume_level);
player.setVolume(volume_level);
sendPacket(volume_level);
}
void sendNack() {
sendPacket(RESPONSE_NOK);
}
void sendPacket(const char content) {
udp.beginPacket(udp.remoteIP(), udp.remotePort());
udp.write(content);
udp.endPacket();
}
void sendPacket(const char content[]) {
udp.beginPacket(udp.remoteIP(), udp.remotePort());
udp.write(content);
udp.endPacket();
}
void sendPacket(int content) {
char value[4];
udp.beginPacket(udp.remoteIP(), udp.remotePort());
udp.write(itoa(content, value, 10));
udp.endPacket();
}
void initCmdServer() {
// Démarrage du serveur TCP
udp.begin(CMD_PORT);
Serial.printf("Ecoute UDP sur le port %d\n", CMD_PORT);
}
void initLocateLed() {
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, HIGH);
}
L'ESP est maintenant capable de :
- monter / baisser le volume ;
- de faire un reset du lecteur MP3 (en cas de problème)
- de faire clignoter sa led pour être localisé facilement.
Enceinte armbian
Même objectif mais cette fois-ci avec Rygel, un lecteur DLNA.
Installation
Le système:
# apt-get update
Le lecteur rygel:
# apt-get -y install rygel-playbin
Les différents plugins. Tout d'abord, récupérer les plugins en fonctions de la distribution:
- Pour le Banana PI:
# plugins=$(apt-cache search gstreamer | grep plugin | awk -F ' - ' '{print $1}')
- Pour le Raspberry PI:
# plugins=$(apt-cache search gstreamer1.0 | grep plugin | awk -F ' - ' '{print $1}' | grep plugins)
Ensuite on procéde à l'installation:
# apt-get install ${plugins}
Création du fichier de service
Il suffit de créer le fichier /etc/systemd/system/rygel.service:
[Unit] Description=Rygel service After=network-online.target [Service] Type=simple UMask=007 ExecStart=/usr/bin/rygel -g 5 Restart=on-failure # Configures the time to wait before service is stopped forcefully. TimeoutStopSec=300 [Install] WantedBy=multi-user.target
On peut lancer le service et l'activer au démarrage
# systemctl start rygel # systemctl enable rygel
Désactivation des logs
Comme l'enceinte n'est pas à proprement parler un serveur exécutant un service critique, on peut désactiver le système de logging pour économiser la durée de vie de la carte SD:
# systemctl stop rsyslog # systemctl disable rsyslog
Ajustement du volume
Il faut maintenant mettre le volume au maximum coté Pi avec le mixer ALSA wikipedia :
# alsamixer
Changement de la carte son par défaut
Une fois la carte son USB installée, il suffit d'utiliser la commande suivante pour voir son identifiant:
# aplay -l **** List of PLAYBACK Hardware Devices **** card 0: sun4icodec [sun4i-codec], device 0: CDC PCM Codec-0 [] Subdevices: 1/1 Subdevice #0: subdevice #0 card 1: Device [USB Audio Device], device 0: USB Audio [USB Audio] Subdevices: 0/1 Subdevice #0: subdevice #0
La carte son avec l'ID 0 est celle intégrée au PI, celle avec l'ID 1 est la carte USB. Pour dire à ALSA que c'est la carte avec l'ID 1 que l'on veut par défaut, il suffit de créer le fichier /etc/asound.conf avec les lignes suivantes:
defaults.pcm.card 1 defaults.ctl.card 1
La configuration est appliquée après un redémarrage !
WiringPI
Installation
Pour le Banana Pi M1:
# cd /opt # git clone https://github.com/LeMaker/WiringLMK.git # cd WiringLMK # chmod +x ./build # ./build
Pour le Raspberry Pi 2:
# apt install -y wiringpi
Utilisation
Pour la lecture des états
# gpio readall +-----+-----+---------+------+---+--Banana Pro--+---+------+---------+-----+-----+ | BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM | +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+ | | | 3.3v | | | 1 || 2 | | | 5v | | | | 2 | 8 | SDA.1 | ALT5 | 0 | 3 || 4 | | | 5V | | | | 3 | 9 | SCL.1 | ALT5 | 0 | 5 || 6 | | | 0v | | | | 4 | 7 | GPIO. 7 | IN | 0 | 7 || 8 | 1 | ALT2 | TxD | 15 | 14 | | | | 0v | | | 9 || 10 | 0 | IN | RxD | 16 | 15 | | 17 | 0 | GPIO. 0 | IN | 0 | 11 || 12 | 0 | IN | GPIO. 1 | 1 | 18 | | 27 | 2 | GPIO. 2 | IN | 0 | 13 || 14 | | | 0v | | | | 22 | 3 | GPIO. 3 | IN | 0 | 15 || 16 | 0 | IN | GPIO. 4 | 4 | 23 | | | | 3.3v | | | 17 || 18 | 0 | IN | GPIO. 5 | 5 | 24 | | 10 | 12 | MOSI | ALT5 | 0 | 19 || 20 | | | 0v | | | | 9 | 13 | MISO | ALT5 | 0 | 21 || 22 | 0 | IN | GPIO. 6 | 6 | 25 | | 11 | 14 | SCLK | ALT5 | 0 | 23 || 24 | 0 | ALT5 | CE0 | 10 | 8 | | | | 0v | | | 25 || 26 | 0 | ALT5 | CE1 | 11 | 7 | | 0 | 30 | SDA.0 | IN | 1 | 27 || 28 | 1 | IN | SCL.0 | 31 | 1 | | 5 | 21 | GPIO.21 | IN | 0 | 29 || 30 | | | 0v | | | | 6 | 22 | GPIO.22 | ALT4 | 0 | 31 || 32 | 0 | ALT4 | GPIO.26 | 26 | 12 | | 13 | 23 | GPIO.23 | IN | 0 | 33 || 34 | | | 0v | | | | 19 | 24 | GPIO.24 | IN | 0 | 35 || 36 | 0 | IN | GPIO.27 | 27 | 16 | | 26 | 25 | GPIO.25 | IN | 0 | 37 || 38 | 0 | IN | GPIO.28 | 28 | 20 | | | | 0v | | | 39 || 40 | 0 | IN | GPIO.29 | 29 | 21 | +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+ | BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM | +-----+-----+---------+------+---+--Banana Pro--+---+------+---------+-----+-----+
Ou d'un état:
# gpio read 1 0
Changement d'état:
# gpio -1 write 8 0
Détection de musique
Il est possible de détecter si de la musique est en train d'être jouée sur le PI grâce à la commande suivante:
# cat /proc/asound/card*/pcm*/sub*/status | grep RUNNING | wc -l
Si cette commande renvoie 1 cela veut dire que state: RUNNING est présent dans un des fichiers d'une des cartes son.
Cette commande peu permettre de piloter un amplificateur au travers d'un relais piloté par une GPIO... Nous aurions le script /opt/amp-relay.php
#!/usr/bin/php
<?php
function main($argv){
// Amp relay GPIO
$GPIO = 1;
$cmd = 'cat /proc/asound/card*/pcm*/sub*/status | grep RUNNING | wc -l';
shell_exec('gpio mode '.$GPIO.' output');
shell_exec('gpio write '.$GPIO.' 0');
while(1){
exec($cmd, $output, $ret);
if($ret==0){
shell_exec('gpio write '.$GPIO.' '.$output[0]);
unset($output);
}
usleep(50);
}
}
main($argv);
avec un petit script systemctl (/etc/systemd/system/ampli-relay.service):
[Unit] Description=Aplificator relay service After=bluetooth.target [Service] ExecStart=/opt/amp-relay.php Restart=on-failure TimeoutStopSec=300 [Install] WantedBy=multi-user.target
On n'oubliera pas de rendre le fichier exécutable et d'activer le service:
# chmod +x /etc/amp-relay.php # systemctl start amp-relay.service # systemctl enable amp-relay.service
Ajout du bluetooth
Paramétrage de la carte
Il faut d'abord installer blueZ:
# apt install -y bluez bluez-tools bluealsa
Il faut créer le fichier /etc/bluetooth/audio.conf:
[General] Enable=Source,Sink,Media,Socket
éditez le fichier /etc/bluetooth/main.conf pour modifier les lignes suivantes:
... Class = 0x00041C ... DiscoverableTimeout = 0 ... PairableTimeout = 0 ...
Il faut ensuite redémarrer le service bluetooth pour appairer le téléphone:
# systemctl restart bluetooth
Déclaration de services
Avant d'aller plus loin, nous allons enregistrer deux commandes en tant que service système:
- bluealsa -p a2dp-sink & : qui permet de déclarer le PI comme point de terminaison A2DP (musique);
- bluealsa-aplay 00:00:00:00:00:00 : qui permet de laisser passer la musique sur la carte son par défaut.
Pour cela nous allons utiliser la même technique que pour Rygel (plus haut):
- Dans le fichier /etc/systemd/system/blue-a2dp.service:
[Unit] Description=Alsa A2DP service After=bluetooth.target [Service] ExecStart=/usr/bin/bluealsa -p a2dp-sink Restart=on-failure TimeoutStopSec=300 [Install] WantedBy=multi-user.target
- Dans le fichier /etc/systemd/system/blue-aplay.service:
[Unit] Description=Alsa Aplay authorization service After=bluetooth.target [Service] ExecStart=/usr/bin/bluealsa-aplay 00:00:00:00:00:00 Restart=on-failure TimeoutStopSec=300 [Install] WantedBy=multi-user.target
Il ne reste plus qu'à enregistrer les services:
# systemctl start blue-a2dp.service # systemctl start blue-aplay.service # systemctl enable blue-aplay.service # systemctl enable blue-a2dp.service
Appairage
Il faut lancer l'utilitaire bluetoothctl:
# bluetoothctl Agent registered [bluetooth]# [CHG] Device 0C:2C:54:1B:A3:A9 Connected: yes [tala_phone]#
