Iot speaker

De mydil
Aller à la navigation Aller à la recherche

Projet

Objectif Les acteurs Compétences Matériel

Construire des enceintes sans-fils (sauf le courant) actives connectées capables de travailler en "groupe"

  • électronique
  • C++ (microcontrôleur)
  • microcontrôleur (esp8266)
  • amplificateur audio (XPT8871, PAM8403, ...)
  • décodeur MP3 (VS1053B)
  • potentiomètre digital (X9C103S)

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

TDA7297.png

Stéréo

2 x 15W

2A (peak)

6 - 18 V

OUI

Avec µControlleur
(p. 3/4)

Obligatoire

TDA7297

Super amplification sans distorsion !
Forte dissipation thermique à pleine puissance :

  • 33W d'après la doc;
  • vraiment très (trop) fort !

Classe D

Modèle Image Phonique Puissance Impédance Tension PCC POP Dissipateur Documentation Avis

XH-M531 (YDA138)

XH-M531.jpg

Stéréo

2 x 10 W
2 x 20 W

8 Ω
4 Ω

12 - 15V

OUI

OUI

NON

YDA138

PAM8403

PAM8403.jpg

Stéréo

3W(*2 ?)

4 Ω

5V

OUI

Reduit mais présent

PAM8403

PAM8610

PAM8610.jpg

Stéréo

2 x 10 W

8 Ω

7 - 15 V

OUI

OUI

NON

PAM8610

XPT8871

XPT8871.jpg

Mono

3.5w
5w

3 Ω
2 Ω

5V

OUI

OUI

NON

XPT8871

  • Bien pour la voix, pas top pour la musique

TDA8932

TDA8932.jpg

Stéréo

2*15W

4 Ω

Entre 10 V et 36 V (Rechercher quel est le plus optimal)

OUI

NON

TDA8932

TPA3110

TPA3110.jpg

Stéréo

15W/ch

8 Ω

16 V

OUI

NON

OUI

TPA3110

  • Amplification moyenne
  • Basses creuses
  • Distorsions peu audibles

TPA3116

TPA3116.png

Stéréo

2 x 50W
2 x 15W

4 Ω
8 Ω

21 V
15 V

OUI

OUI par µC

OUI
NON

TPA3116

  • Bonne amplification
  • Basses profondes
  • Distorsions peu audibles

TPA3118

TPA3118.png

Stéréo

2 x 30W

8 Ω

12 - 24 V

OUI

OUI par µC

OUI

TPA3118

  • Bonne amplification
  • Basses profondes
  • Distorsions peu audibles

Potentiomètre digital

A faire

Décodeur MP3

Présentation

Le décodeur MP3 choisi est le VS1053

Vs1053 board.jpg

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
Wemos D1 R2 mini diagram.png
Vs1053 board diagram.png
ESP8266 VS1053
D0 XDCS
D1 XoS (Cable Select)
D3 DREQ
D5 SCK (Serial ClocK)
D6 MISO (Master In Slave Out)
D7 MOSI (Master Out Slave In)
RST XRST
5V 5V
G DGND

Vous devriez avoir quelque chose comme ça :

Wemos D1 vs1053 diagram.jpg

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...

Warning-icon.png

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

Warning manual.jpg

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
Alsamixer armbian.png

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]#