Différences entre les versions de « Iot speaker »
| Ligne 258 : | Ligne 258 : | ||
= Communication = | = 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 [https://www.tala-informatique.fr/wiki/index.php/Esp8266_udp_server multicast], pour parler à un groupe d'enceintes, cela est uniquement possible en UDP ! | ||
== Envoi : serveur en PHP == | == 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... | ||
| + | {|style="width:650px" align="center" | ||
| + | | | ||
| + | [[Fichier:warning-icon.png|centré|60px]] | ||
| + | |valign="top" align="justify"| | ||
| + | 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'' : | ||
| + | <source lang="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); | ||
| + | } | ||
| + | } | ||
| + | </source> | ||
| + | === Launch.php === | ||
| + | On créé un fichier ''Launch.php'' qui va servir à appeler la classe précédente : | ||
| + | <source lang="php"> | ||
| + | <?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); | ||
| + | </source> | ||
== Réception : ESP8266 == | == Réception : ESP8266 == | ||
| + | {|style="width:650px" align="center" | ||
| + | | | ||
| + | [[Fichier:Warning manual.jpg|centré|300px]] | ||
| + | |valign="top" align="justify"| | ||
| + | Soyez sûr de comprendre la section sur [https://www.tala-informatique.fr/wiki/index.php/Arduino_sketch_writing| 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 ! | ||
| + | <source lang="c"> | ||
| + | #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()); | ||
| + | } | ||
| + | </source> | ||
| + | === Lecteur MP3 === | ||
| + | Vient ensuite la partie du lecteur MP3. Dans la partie des imports ajoutez la ligne suivante : | ||
| + | <source lang="c"> | ||
| + | #include <VS1053.h> | ||
| + | </source> | ||
| + | Dans la partie des variables statiques ajoutez les lignes suivantes : | ||
| + | <source lang="c"> | ||
| + | // 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; | ||
| + | </source> | ||
| + | A la fin de la fonction ''setup()'' nous allons ajouter les lignes suivantes : | ||
| + | <source lang="c"> | ||
| + | // On démarre le shield MP3 | ||
| + | initMP3(); | ||
| + | </source> | ||
| + | A la suite, on insère la fonction suivante : | ||
| + | <source lang="c"> | ||
| + | void initMP3() { | ||
| + | SPI.begin(); | ||
| + | // Démarrage du lecteur MP3 | ||
| + | player.begin(); | ||
| + | player.switchToMp3Mode(); | ||
| + | player.setVolume(volume_level); | ||
| + | } | ||
| + | </source> | ||
Version du 22 février 2018 à 19:07
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);
}