Différences entre les versions de « Iot speaker »
| Ligne 475 : | Ligne 475 : | ||
} | } | ||
</source> | </source> | ||
| + | === 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 : | ||
| + | <source lang="c"> | ||
| + | // 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; | ||
| + | </source> | ||
| + | A la fin de la fonction ''setup()'' nous allons ajouter les lignes suivantes : | ||
| + | <source lang="c"> | ||
| + | // On démarre le serveur TCP | ||
| + | initMp3Server(); | ||
| + | </source> | ||
| + | A la suite, on insère les fonctions suivantes : | ||
| + | <source lang="c"> | ||
| + | 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 !")); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </source> | ||
| + | Enfin, on ajoute le ligne suivante dans la fonction ''loop()'' : | ||
| + | <source lang="c"> | ||
| + | readMp3(); | ||
| + | </source> | ||
| + | A ce stade, l'ESP devrait être capable de récupérer le flux et de le jouer ! | ||
Version du 22 février 2018 à 19:11
Introduction
Projets
| 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
Trouver des amplis sur ebay.com pas cher de classe AB
Classe D
| Modèle | Image | Phonique | Puissance | Impédance | Tension | PCC | POP | Dissipateur | Documentation | Avis |
|---|---|---|---|---|---|---|---|---|---|---|
|
XH-M531 |
||||||||||
|
PAM8403 |
Stéréo |
3W(*2 ?) |
4 Ω |
5V |
OUI |
Reduit mais présent |
||||
|
Stéréo |
||||||||||
|
XPT8871 |
||||||||||
|
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 |
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 librairies 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 Fichier:ESP VS1053 Library.zip 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 !
Communication
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é, ce 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, cela est uniquement possible 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 important, de la programmation orientée objet et sont fournis à titre indicatif dans l'unique but de démontrer la faisabilité d'un tel projet ! |
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 microseconde 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 !