La segmentation pour le marketing – Retour d’expérience

Voici un article que j’ai rédigé pour ma société, dont le thème est « comment mieux connaître ses clients ».

Lien direct : http://tekcollab.imdeo.com/la-segmentation-pour-le-marketing-retour-dexperience/

Introduction

Vous désirez mieux connaitre vos clients, rechercher parmi eux des cibles pour vendre vos produits, proposer vos services ? Qui n’a pas été contacté au moins une fois pour laisser son adresse email, son code postal ou un numéro de téléphone en échange d’un petit cadeau ?

Cette approche de gagnant / gagnant est un bon moyen d’augmenter votre connaissance d’un échantillon de la population. Chaque partie est gagnante lors d’un échange ; chacun y trouve son compte.
Certaines personnes sont tellement friandes de ces opérations marketing qu’elles se sont spécialisées en recherche d’opérations promotionnelles dans tous les domaines (gel douche, pot de yaourt, objets déco). Elles traquent les petits cadeaux ou promotions en échange de quelques infos personnelles.

Voici un exemple de méthodologie, visant à mieux connaître une partie de vos clients, pour leur proposer un produit qu’ils envisagent d’acheter. Autant qu’ils fassent cet acte après de vous…

 

Pour s’appuyer sur un exemple concret, imaginons un centre de balnéothérapie, qui a des clients récurrents et des nouvelles têtes. Comment vendre plus, ou mieux, aux bonnes personnes ?

 

Segmentation stratégique

Au début de chaque histoire, on pose les personnages : qui sont les cibles de cette campagne ?
Dans la masse de la population, tous vos clients ne sont pas intéressés par la campagne du moment. La segmentation stratégique vise à séparer en groupes distincts la population globale, avec une stratégie marketing. Votre base de données de clients doit être découpée en plusieurs groupes, en définissant des clés de sélection.

Pour le centre de balnéo, ce sera tous les clients (anciens et récents) qu’il faudra “segmenter”.

 

 

Ce découpage peut être fait selon plusieurs critères : la valeur de l’individu (ce qu’il peut acheter), son comportement (la fréquence d’achat), l’objectif commercial (à fidéliser, à reconquérir, à approfondir).

 

Les clients de la balnéo seront découpés en :

  • clients “fun”, qui viennent au centre pour se détendre, passer un bon moment entre amis, qu’ils soient des clients actuels ou non.
  • clients “contraints”, qui font de la rééducation, et ne viennent pas trop par plaisir.

 

Gain espéré

Avec cette campagne, votre objectif est de proposer un produit qui correspond aux attentes d’un segment particulier. Inutile de courir plusieurs lièvres à la fois ; il faudra se concentrer sur un seul segment.
Gardons en tête que l’objectif premier de la campagne est la meilleure connaissance d’un groupe de la population. La vente d’un produit n’est que la conséquence.

Si le client ne vient pas par lui-même pour acheter un produit, c’est qu’il n’en a pas besoin, ou qu’il n’a pas vu le produit, ou qu’il ne sait pas qu’il en a besoin.

Pour le premier cas, il n’y a rien à faire. Si on identifie clairement cette population, on ne perd pas son temps à proposer une offre alléchante. Voici un bon exemple avec Amazon : quand je recherche des lunettes de piscine, cette information est conservée pour me proposer d’acheter d’autres modèles de lunettes, pendant que j’hésite sur mon choix, pendant plusieurs jours. Une fois mon achat terminé, et mes lunettes reçues, des publicités de lunettes sont encore proposées, pour rien… Je ne vais pas acheter une autre paire de lunettes.

Si le segment est trop vaste et trop diversifié, une autre phase de segmentation plus fine est lancée, pour un autre cycle.

 

 

Dans notre cas de la balnéo, le gain est de connaître ceux qui sont “fun”, prêts à venir au centre pour le plaisir, et à y revenir. Ce sont des “clients VIP”, qu’il faudra chouchouter. Non seulement ils ont un potentiel, mais leur réseau d’amis aussi.

Parmi cette population, il sera peut-être plus facile de considérer les femmes des hommes, ou les jeunes des anciens.

 

Offre

Que désirez-vous donner en échange d’informations de vos clients ? Vous perdez un peu avec ce don, mais vous gagnerez en contrepartie. Pour rappel, l’objectif n’est pas de vendre maintenant, mais de connaître ceux à qui on peut proposer d’acheter plus tard.

Réfléchissez à un petit cadeau, en contrepartie d’une petite information. Et un gros cadeau, au regard d’une information très importante ou plus personnelle.

 

Dans notre centre balnéo, Les clients ont tous une adresse email. En échange d’informations plus personnelles, comme la catégorie socio-professionnelle, on pourra déterminer si ce sont des clients qui entrent dans le segment ou non. De plus, en recoupant avec les plannings des activités, les cartes d’abonnement, etc, on défini le segment “clients VIP”.
L’offre est simple : réservez en ligne avec votre email, commandez une carte d’abonnement sur notre site Internet, et profitez de 5% de réduction. Sinon, il faut passer au centre et payer plein pot.
Sur le site, les processus de réservation et de commande sont accompagnés de messages invitants à compléter ses informations personnelles. Pour réserver, peu d’informations sont demandées. Pour acheter une carte d’abonnement, il en faut plus.

 

Conclusion

Ce travail de segmentation n’est possible qu’après avoir fait le recueil des informations pertinentes de votre cible. Cela peut prendre du temps, à coup de campagnes de récupération de ces précieuses données.

Plus tard, l’exploitation de votre travail sera facilité et vous gagnerez en pertinence sur vos clients.

 

Sources :

Envoi de SMS par un robot

Le principe, les outils

Je désire envoyer un SMS à tous les adhérents de mon association, pour leur communiquer une date d’un évènement sportif. Le mail ne fait plus d’effet ; il n’est pas lu, pas ouvert, pas arrivé…

J’opte pour un petit programme qui enverra en masse les SMS par mon smartphone. L’outil SMSGatewayMe me propose cette solution, gratuite, avec une API en PHP et une application à installer sur mon Smartphone.

 

La solution en détails

Depuis mon PC, mon programme (= mon robot) contacte par WebService la plateforme SMSGatewayMe, avec des messages du type « Envoyer le texto ‘Salut, il y a un barbecue ce week-end. tchao ! (association AAA)’ à ’06.12.34.56.78′, depuis mon smartphone, id=13245 ».

La plateforme SMSGatewayMe stocke cette info dans sa base de données, avec l’id du smartphone, le message, le destinataire.

L’application sur mon smartphone contacte régulièrement la plateforme SMSGatewayMe : « Est-ce qu’il y a des SMS à envoyer pour le smartphone id=12345 ?« . Elle lui retourne en réponse le SMS à envoyer.

L’application sur mon smartphone crée un nouveau SMS, visible depuis mes messages de mon smartphone. Les personnes me répondent directement.

 

La technique

Après avoir créé un compte sur https://smsgateway.me/, il faut récupérer le fichier « smsGateway.php ». Regardons le programme « run.php » :

<?php
include "smsGateway.php";

function main(){
  $smsGateway = new SmsGateway('moi-meme@gmail.com', 'monMot2Passe');
  $deviceID = 12345;

  $message = "Salut,
il y a un barbecue ce week-end.
tchao !
(association AAA)";

  $row = 1;
  if (($handle = fopen("envoi-sms.csv", "r")) !== FALSE) {
    while (($data = fgetcsv($handle, 1000, ";")) !== FALSE) {
      //echo "----". $data . "\n";
      $nbChamps = count($data);
      //echo "$nbChamps champs dans la ligne #$row\n";
      if($nbChamps < 2) {
        echo "fini\n";
        return;
      }
      $user = $data[0];
      $adresse = $data[1];
      $tel = $data[2];
      if( $user == "Nom" ){
        //echo "ligne d'entete\n";
        continue;
      }
      echo "Envoi d'un SMS vers ".$user." --> ".$tel."\n";
      $result = $smsGateway->sendMessageToNumber($tel, $message, $deviceID);

      $row++;
    }
    fclose($handle);
  }
  echo "--\n";

}

main();
?>

Le fichier « envoi-sms.csv » est un tableur Excel exporté en CSV, avec les colonnes suivantes : « Nom », « Adresse », « Téléphone ». Il y a une ligne d’entête pour mieux se repérer.

Enfin, une fois que tout est prêt, il suffit de lancer la commande suivante :

php run.php

 

Remarques

Ok, l’évènement n’est pas très sportif, passons…

Le nombre de caractères est limité. Faites des tests pour vous assurer que les sauts de ligne sont correctement placés, les caractères spécieux sont bien interprétés, le compte est bien configuré, etc.

Génération de PDF avec CodeIgniter

Introduction

Dans votre application, vous désirez générer un fichier PDF avec vos données, votre charte graphique, etc. Voici un exemple d’utilisation d’une bibliothèque, qui permet de faire :

  • une entête
  • un pied de page
  • ajouter une pagination (numéro de la page en cours ; nombre de page du document)
  • insérer des images
  • insérer du texte HTML

 http://hulan.info/item/html2pdf-convert-your-xhtml-to-pdf-easily

 

Démarrage

Voici comment procéder pour installer cette bibliothèque dans votre projet :

  • Copier dans application/libraries les fichiers :
    • Fpdf_rotate.php
    • Fpdf.php
    • Pdf_html.php
    • Pdf.php
  • Copier dans application/third_party les fichiers :
    • tout le répertoire « fpdf/fonts »
  • Modifier le fichier application/config/config.php avec ces lignes :
//FPDF FONT DIRECTORY
$config['fonts_path'] = APPPATH.'third_party/fpdf/fonts/';

 

 

Générer le PDF depuis votre controller

Définir le point d’entrée du controller (classique CodeIgniter), par exemple « getAttestation() » :

public function getAttestation(){

 

Définir le répertoire des polices :

define('FPDF_FONTPATH',$this->config->item('fonts_path'));

 

Charger les bibliothèques :

$this->load->library(array('fpdf','fpdf_rotate','pdf', 'pdf_html'));

 

Mettre du contenu dans le PDF :

$pdf = new PDF_HTML();

$pdf->Open();
$pdf->SetTopMargin(10);
$pdf->AliasNbPages();
$pdf->AddPage();
$pdf->SetFont('Arial', '', 12);
$pdf->SetDrawColor(0);

$pdf->SetTitle('Attestation');
$pdf->Ln(5);
$pdf->SetFontSize(16);
$pdf->x = 70;
$pdf->Write(0, utf8_decode( "OK") );
$pdf->Output('', 'I'); // I : inline file

 

et terminer la fonction « getAttestation() »:

}

Lorsqu’on lance l’URL de ce controller dans le navigateur, un fichier PDF est généré :

 

Le fichier a pour titre « Attestation », comme prévu par le code.

CodeIgniter + React JS

Introduction

ReactJS est une bibliothèque prometteuse pour changer la façon dont les applications web sont conçues. J’ai retenu une chose importante, ce qui fait la différence par rapport à un appel AJAX simple et classique, c’est que React compare le DOM de la page avant l’appel et après l’appel pour appliquer les changements uniquement sur les modifications.

Il est donc inutile de faire recharger une page complète en cas d’ajout d’item dans une liste, ou même de l’ajouter « à la main » (avec un « append » en jQuery). React s’occupe de faire ces changements.

Soudain, on a envie de passer par cette bibliothèque pour tous les petites modifications à faire sur un écran (notification, compteur) et en exploitant cette technologie à son paroxysme, toute l’application y passe.

 

Quels impacts pour une application MVC ?

Si on prend React avec le framework « Flux », le MVC est mort. Dommage ! Faut-il recoder mes applications avec un autre paradigme ? Pas sûr.

Par contre, une application CodeIgniter qui désire utiliser React doit fondamentalement revoir la signification du V, pour la « Vue ».

En théorie, la vue, c’est ce que que les utilisateurs « voient », donc le HTML qui résulte du serveur hébergeant l’application.

Avec React, la Vue au sens HTML n’est plus à gérer par l’application. React s’en chargera.

Version classique du MVC de CodeIgniter (ou d’autres frameworks)

 

Casser la vue !

Oui, il faut casser la vue. L’application CodeIgniter ne fera que des interactions en manipulant des données JSON, et n’ayant que pour interface technique des WebServices REST.

Une autre application pourra se charger de la vue, des interactions utilisateur et des actions vers le serveur (liste des items, ajout, modification, suppression d’un item). Elle appellera l’application CodeIgniter pour le stockage en base de données, les règles métier, etc.

La partie « vue » de l’application est supportée par une autre application dédiée à l’interaction utilisateur. L’avantage est que si vous avez une application mobile à réaliser, elle pourra interagir avec cette application CodeIgniter en utilisant les mêmes WebServices.

Avec React, la vue peut supportée par une autre application.

Conclusion

La question qu’on peut se poser est la suivante : est-ce utile de s’appuyer sur un Framework MVC pour mon application s’il n’y a plus de Vue ?

S’il y a du boulot côté Modèle ou Controller (ou service) = des objets complexes, des règles métiers, la réponse est oui.

Si c’est une représentation HTML des objets simples en base de données, avec peu de règles métier, la réponse est non.

Données d’un capteur vers un serveur Web

Introduction

Lors de mon précédent article, j’évoque l’envoi des données via Wifi depuis un ensemble de composants autour d’une carte Arduino. Or, la programmation n’est pas très facile pour donner plus d’intelligence à ces capteurs. Je suis donc reparti du capteur de température et de pression DHT11, mais relié à un Raspberry PI.


DHT11 - Cảm biến nhiệt độ và độ ẩm https://images.duckduckgo.com/iu/?u=http%3A%2F%2Fwww.openmediacentre.com.au%2Ffileadmin%2Fuser_upload%2Ftx_onqcatalogue%2Fproduct%2Fraspberry-pi-model-b_80_thumbimg1.jpg&f=1
(les photos ne sont pas à l’échelle)
Prix des composants :

  • Raspberry Pi 2 starter kit (avec les câbles, l’alimentation, une carte SD, des câbles, des radiateurs, un boitier, etc) : 75 € sur Amazon
  • DHT11 (capteur nu) : 2.20 € sur Amazon
  • Dongle Wifi (sur port USB) : quelques Euros (sur eBay)

La programmation est faite en script SH et Python dans le Raspberry et je dispose d’un serveur PHP qui héberge les données (mon PC).

 

Vue du Raspberry avec les câbles pour clavier, souris, écran (inutiles en mode normal, mais nécessaire pour la programmation) + le composant DHT11, relié par 3 fils.

 

Vue des composants minimum pour que les données soient envoyées après avoir tout programmé. Il faudra juste prévoir une boite pour protéger le Raspberry et laisser le capteur « à l’air libre ».

 

Protocole de communication – 1. Souscription

Le Raspberry dispose d’un Dongle Wifi pour se connecter au serveur. J’utilise donc l’adresse MAC du Wifi pour souscrire au serveur selon ce principe :

Dans ces échanges, l’adresse MAC sert de clé. Chaque unité de communication devrait avoir une adresse MAC unique sur le réseau.

Une fois le capteur identifié sur le serveur, il n’est pas encore actif. Le serveur a un capteur en attente de paramétrage manuel (donner un nom compréhensible par un humain, un picto, un groupe, une catégorie, etc…). Tant que cette étape n’est pas faite, les données ne sont pas transmises. Le capteur passe à l’état « Activé » lorsque le paramétrage est terminé.

 

Protocole de communication – 2. Envoi des données

Le serveur est maintenant prêt à réceptionner les données du capteur.

Dans le cas du DHT11, il y a 2 données à chaque envoi : température (en °C) et humidité (en %). Le Raspberry ne fait qu’un appel au serveur pour ces 2 informations.

 

Montage électronique

Le montage sur les PINs du Raspberry sont faits directement car le DHT11 accepte un voltage de 3.3v, fourni par le PIN numéro 1. Si cette alimentation directe n’est pas assez stable, on pourra utiliser un régulateur de tension (AMS1117, 4.70 € les 10 pièces sur Amazon), prenant en entrée du 5v et donnant en sortie du 3.3v stabilisé (monté avec un condensateur de 470µF). Ce sera surtout le cas si on utilise une autre source d’énergie qu’une prise de courant (pile, accu, batterie de voiture, capteurs solaires, etc).

 

Le DHT11 comme capteur

Le DHT11 est un composant assez instable, qui ne garanti pas toujours la bonne lecture des données. Il semble que d’autres capteurs soient plus efficaces, peu onéreux, fonctionnant avec une plus grande plage de voltage : TMP36GT9, DS18B20, etc.

Le DHT11 est par contre bien documenté, pour les bidouilleurs comme moi, devant programmer en Python.

 

Copies d’écran du serveur

Liste des capteurs

 

 

Détail d’un capteur d’humidité

 

Pour aller plus loin

A partir de ce système, il est possible d’équiper toute la maison, d’avoir des capteurs partout et de suivre les courbes. Le budget à prévoir reste collé au prix du Raspberry (carte nue à 40 €), car les capteurs ne coûtent que quelques Euros. D’autres solutions voient le jour, avec des cartes électroniques programmables, comme TESSEL (avec Wifi intégré) : https://www.tessel.io/

D’autres capteurs pourront être ajoutés, sous réserve de pouvoir lire les données (sur le même principe), depuis les ports GPIO du Raspberry. Grâce au grand nombre de PINs disponibles, on pourrait ajouter d’autres capteurs à un même Raspberry, recueillant ainsi un lot de données. Pour une pièce de la maison : température, humidité, ensoleillement, concentration CO2, odeur (oui, pour faire remonter le niveau de puanteur du placard à chaussures), méthane, détecteur de mouvement, etc.

 

Codeigniter 3 chez free.fr

Codeigniter 3

J’utilise le framework Codeigniter pour mes projets personnels, avec mon générateur de code. Sa simplicité d’utilisation et sa rapidité de mise en œuvre restent un atout majeur pour de petites applications web.

En voulant faire une requête de groupe, comme ceci, je me suis confronté à un problème :

select a, b from table1, table2
where table1.c1 = table2.c1
and c1 > 10
or (c2 > 20 and c3 > 30);

 

Les opérations avec les parenthèses sont réalisables de 2 façons :

  1. Faire la requête en SQL pure et la lancer dans CodeIgniter. C’est assez moche et doit être réservé à des requêtes complexes, visant à faire travailler le moteur du SGBD. La requête sera travaillée dans un MySQL Workbench pour être sûr du résultat. On restera alors sur une base de donnée d’un certain type (ex : MySQL), sans pouvoir passer un jour à un autre type (ex: SQLite).
  2. Passer à CodeIgniter 3 pour utiliser la fonctionnalité de regroupements dans les requêtes : voir la documentation dans CodeIgniter

Cette seconde méthode exploite pleinement le SQLBD tout en utilisant le QueryBuilder du Framework. Sauf que pour ça, il faut passer à CodeIgniter 3.

 

Et free dans tout ça ?

L’hébergeur free.fr est gratuit, oui. Mais il est aussi :

  • lent au chargement
  • limité dans l’accès FTP sur le nombre de sessions ouvertes, ce qui est assez pénalisant à l’usage
  • limité dans les bases de données par site (1 seule)
  • ancien, dans les techno qu’il nous mets à disposition
  • permissif, non sécurisé (piratage de compte vécu)
  • sans support (newsgroup étant le seul media d’échange, il reste sans réponse)

Pour que CodeIgniter 3 fonctionne, il doit utiliser PHP 5.2 minimum. Or, chez free, il est à la version 5.1 – dommage !

Pour ces multiples raisons, j’ai ouvert un autre site chez WebHost et j’ai fait un test chez eux.

 

Résultats

A part l’interface de gestion assez « old-scool », on retrouve ses repères. Ouverture de site, ouverture de compte mail, création de base de données MySQL, accès FTP : tout ça se fait très facilement et rapidement.

Mon choix est fait, adieu free, enfin pas pour tout. Je garde mon blog, même si je me sens dans une toute petite boite, sans pouvoir étendre mes jambes ou mes bras (version de WordPress assez ancienne, ne pouvant pas se mettre à jour, pas d’accès aux serveurs externes pour mettre un captcha, etc…).

 

Arduino + ESP8266 + DHT11 : relevé de température vers un serveur HTTP

Introduction

Suite au précédent article qui a permis de faire un HTTP GET sur un serveur en Wifi, grâce au module ESP8266, j’ai ajouté un détecteur de température : le module DHT11, au prix de 2.35 €.

data:image/jpeg;base64,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

Ce module intègre des résistances pour assurer son bon fonctionnement. On peut trouver le composant sans la plaque où celui-ci est soudé, mais il faudra ajouter les composants électroniques nécessaires lors du branchement.

 

 

Connexion

De multiples articles décrivent comment faire un relevé de température avec le module DHT11. Il faut penser à le brancher sur le 5v et laisser le 3.3v pourESP8266.

L’article suivant décrit ce qu’il faut faire pour stabiliser les tensions : https://github.com/esp8266/Arduino/blob/master/doc/boards.md#improved-stability

J’ai juste ajouté un condensateur de 100µF pour éviter les reboot intempestifs du composant ESP8266 (pour cause d’instabilité du signal).

 

Code complet

Voici le code pour Arduino :

#include <SoftwareSerial.h>
#include "DHT.h"

/** DHT **/
#define DHTPIN 5
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);

/** ESP8266 **/
String ssid = "NETGEAR";
String key = "xxxxxxxx";
String serverHost = "192.168.1.100";
String serverPort = "80";

SoftwareSerial esp8266(1, 0); // RX, TX
bool done = false;

void setup() {

  /** DHT **/
  dht.begin();

  /** ESP8266 **/
  esp8266.begin(115200);
  delay(500);
  esp8266.println("AT+RST");

  /**
   * Initialisation
   */
  delay(1000);
  esp8266.println("AT");
  done = esp8266.find("OK");
  if(!done){
    delay(1000);
    done = esp8266.find("OK");
  }

  /**
   * Se mettreen mode CLIENT
   */
  esp8266.println("AT+CWMODE=1");
  done = esp8266.find("OK");
  if(!done){
    delay(1000);
    done = esp8266.find("OK");
  }

  /**
   * Connexion auWifi
   */
  esp8266.println("AT+CWJAP=\""+ssid+"\",\""+key+"\"");
  done = esp8266.find("OK");
  while(!done){
    delay(1000);
    done = esp8266.find("OK");
  }

  /**
   * Se mettre en mode multiple connexions
   */
  esp8266.println("AT+CIPMUX=1");
  done = esp8266.find("OK");
  if(!done){
    delay(1000);
    done = esp8266.find("OK");
  }

  /**
   * Récupération de l'adresse IP
   */
  esp8266.println("AT+CIFSR");
  done = esp8266.find("STAIP");
  if(!done){
    delay(1000);
    done = esp8266.find("OK");
  }

}

void loop() {
  int maxLoops = 5;

  /**
   * DHT11 : Temperature
   */

  float temperature = dht.readTemperature();
  if( isnan(temperature) ){
    return;
  }
  // convert float --> String
  String temperatureStr = "";
  char temperatureChar[15];
  dtostrf(temperature,5,2,temperatureChar);
  temperatureStr = temperatureChar;

  /**
   * HTTP GET
   */
  String cmd = "AT+CIPSTART=4,\"TCP\",\""+serverHost+"\","+serverPort;
  esp8266.println(cmd);
  delay(500);
  done = esp8266.find("OK");
  int currentLoop = 0;
  while(!done){
    delay(500);
    done = esp8266.find("OK");
    if(currentLoop >= maxLoops){
      break;
    }
    currentLoop++;
  }

  String url = "/lda/index.php/data/createdatajson/append?datflval="+
    temperatureStr +
    "&thing=FIRST";
  String cmdGET = "GET " + url + " HTTP/1.1\r\n"+
    "Host: "+serverHost+"\r\nUser-Agent: ESP8266_HTTP_Client\r\nConnection: close\r\n\r\n";
  esp8266.print("AT+CIPSEND=4,");
  esp8266.println(cmdGET.length());
  delay(1000);
  done = esp8266.find(">");
  currentLoop = 0;
  while(!done){
    delay(500);
    done = esp8266.find(">");
    if(currentLoop >= maxLoops){
      break;
    }
    currentLoop++;
  }
  esp8266.println(cmdGET + "\r\n\r\n");
  delay(1000);

  esp8266.println("AT+CIPSTATUS");
  delay(1000);

  // Fermeture de toutes les connexions
  esp8266.println("AT+CIPCLOSE=5");
  delay(1000);

  // repart à zero
  esp8266.println("AT");

  // 4 secondes sont déjà passées
  delay(16000);

}

 

 

Annexe

Photos du projet avec la platine d’essai :

 

Arduino + ESP8266 : connexion Wifi

Introduction

La carte Arduino peut être connectée à un module de connexion Wifi (nommé ESP8266) pour lequel une configuration est nécessaire.

Voici la façon dont j’ai pu envoyer un simple HTTP GET depuis la carte Arduino avec ce module.

Dans certains tutoriels qu’on peut trouver sur le Net, il est mentionné la mise à jour du firmware, par l’intermédiaire d’une autre carte électronique. Cet article part du module sorti de son emballage, sans faire cette mise à jour.

 

Le module ESP8266

Ce module est un regroupement de multiples composants, sous forme de mini-carte électronique, avec 8 broches de connexion.
Il mesure 2.5 cm x 1.5 cm et coûte 7.33 € sur Amazon.

Il permet de se connecter à un point d’accès Wifi pour communiquer en mode client/serveur (être client ou être serveur) mais il peut aussi être défini en tant que point d’accès Wifi.

Branchements

Il faut brancher le composant avec la carte Arduino, de manière précise, sur le 3.3v.

 

Détail des branchements :

L’article http://www.labradoc.com/i/follower/p/notes-esp8266 détaille les branchements. En voici un rappel (en Français) :

  • RX vers le port TX de la carte Arduino
  • VCC vers le port 3.3v de la carte Arduino
  • GPIO 0 non branché
  • RESET non branché
  • CH_PD vers le port 3.3v de la carte Arduino
  • GPIO 2 non branché
  • TX vers le port RX de la carte Arduino
  • GND vers le port GND de la carte Arduino

Si vous avez branché RX et TX correctement, une LED bleue sur le module ESP8266 émet un bref flash lors de la mise sous tension. Si elle ne s’allume pas, c’est que le branchement n’est peut-être pas bon…

Quelques conseils :

  • Branchez tout et à la fin, le fil qui va vers 3.3v. Vous saurez si tout est opérationnel ou non avec cette LED bleue.
  • Il ne faut rien brancher avec le port 5v de la carte Arduino. En cas d’erreur, le module grillera. Je vous conseille donc de brancher un fil sur ce port 5v et de laisser non branché à l’autre bout du fil (par exemple, faire un nœud pas trop serré).

 

Comment donner des ordres au module ESP8266 ?

L’article http://www.labradoc.com/i/follower/p/notes-esp8266 détaille les commandes à passer par l’interface de programmation d’Arduino. Pour résumé, une série de commandes doivent être passées pour :

  • Initialiser le module ESP8266
  • Se mettre en mode client Wifi (et pas en mode point d’accès)
  • (en option) Rechercher les points d’accès disponibles (et afficher leur SSID)
  • (en option) Se définir une adresse IP
  • Se connecter sur un point d’accès (avec SSID et mot de passe)
  • Se mettre en mode « Connexions multiples » (pour faire plusieurs appels HTTP)
  • (en option) Afficher son adresse IP
  • (en option) Faire un PING sur un serveur
  • Faire un HTTP GET
  • Fermer la connexion (et préparer le prochain appel HTTP)

Ces commandes sont à transmettre par le port Série de la carte Arduino.

 

Code pour faire les appels HTTP GET

Avec Adruino, 2 fonctions sont préparées pour que l’utilisateur écrive son code à l’intérieur, avec 2 objectifs :

  • setup() : cette fonction est lancée une seule fois au démarrage de la carte Arduino
  • loop() : après la fonction « setup() », cette fonction est lancée autant de fois que la carte Arduino est allumée

Une partie d’initialisation est faire avant ces 2 fonctions

#include <SoftwareSerial.h>

String ssid = "NETGEAR";
String key = "pA$$W0Rd";

SoftwareSerial esp8266(1, 0); // RX, TX
bool done = false;

Seule la bibliothèque SoftwareSerial est nécessaire.

La variable « done » sera utilisée de manière globale.

 

Code de la fonction setup()

 void setup() {
  // put your setup code here, to run once:

  esp8266.begin(115200);
  delay(500);
  esp8266.println("AT+RST");

  /**
   * Initialisation
   */
  delay(1000);
  esp8266.println("AT");
  done = esp8266.find("OK");
  if(!done){
    delay(1000);
    done = esp8266.find("OK");
  }

  /**
   * Se mettre en mode CLIENT
   */
  esp8266.println("AT+CWMODE=1");
  done = esp8266.find("OK");
  if(!done){
    delay(1000);
    done = esp8266.find("OK");
  }

  /**
   * Affecter son adresse IP manuellement
   */
   /*
  delay(1000);
  esp8266.println("AT+CIPSTA=\"192.168.1.200\",\"192.168.92.254\",\"255.255.255.0\"");
  done = esp8266.find("OK");
  while(!done){
    delay(1000);
    done = esp8266.find("OK");
  }*/

  /**
   * Rechercher les points d'accès WIFI
   */
   /*
  delay(1000);
  esp8266.println("AT+CWLAP");
  done = esp8266.find("OK");
  while(!done){
    delay(1000);
    done = esp8266.find("OK");
    delay(3000);
    break;
  }*/

  /**
   * Se connecter au point d'accès Wifi défini dans la variable "ssid"
   */
  esp8266.println("AT+CWJAP=\""+ssid+"\",\""+key+"\"");
  done = esp8266.find("OK");
  while(!done){
    delay(1000);
    done = esp8266.find("OK");
  }

  /**
   * Se mettre en mode connexions multiples
   */
  esp8266.println("AT+CIPMUX=1");
  done = esp8266.find("OK");
  if(!done){
    delay(1000);
    done = esp8266.find("OK");
  }

  /**
   * afficher son adresse IP
   */
  esp8266.println("AT+CIFSR");
  done = esp8266.find("STAIP");
  if(!done){
    delay(1000);
    done = esp8266.find("OK");
  }

  /**
   * faire un ping sur un server
   */
   /*
  delay(1000);
  esp8266.println("AT+PING=\"192.168.1.100\"");
  done = false;
  if(!done){
    delay(1000);
    done = esp8266.find("OK");
  }*/

}

Il est nécessaire de mettre des temps d’attente (moins une seconde maxi) pour que le module se mette dans un état convenable, qu’il communique avec le point d’accès Wifi. Sinon, vous aurez des messages du genre « busy p… » ou « busy s… », qui peuvent être bloquant ou non.

Ajustez ces temps selon vos résultats.

 

Code de la fonction loop()

void loop() {
  int maxLoops = 5;

  /**
   * Faire un HTTP GET
   */
  String cmd = "AT+CIPSTART=4,\"TCP\",\"192.168.1.100\",80";
  esp8266.println(cmd);
  delay(500);
  done = esp8266.find("OK");
  int currentLoop = 0;
  while(!done){
    delay(500);
    done = esp8266.find("OK");
    if(currentLoop >= maxLoops){
      break;
    }
    currentLoop++;
  }

  String url = "/lda/index.php/data/createdatajson/append?datflval=1.55&thing=FIRST";
  String cmdGET = "GET " + url + " HTTP/1.1\r\n"+
    "Host: 192.168.1.100\r\nUser-Agent: ESP8266_HTTP_Client\r\nConnection: close\r\n\r\n";
  esp8266.print("AT+CIPSEND=4,");
  esp8266.println(cmdGET.length());
  delay(1000);
  done = esp8266.find(">");
  currentLoop = 0;
  while(!done){
    delay(500);
    done = esp8266.find(">");
    if(currentLoop >= maxLoops){
      break;
    }
    currentLoop++;
  }
  esp8266.println(cmdGET+"\r\n\r\n");
  delay(1000);

  esp8266.println("AT+CIPSTATUS");
  delay(1000);

  // Close all connections
  esp8266.println("AT+CIPCLOSE=5");
  delay(1000);

  // restart from zero
  esp8266.println("AT");

  // 4 secondes déjà passées
  delay(20000);

}

 

Je désire faire l’appel à l’URL « http://192.168.1.100:80/lda/index.php/data/createdatajson/append?datflval=1.55&thing=FIRST » en GET. Tous les paramètres de cette requête sont après le caractère « ? ».

J’ai un serveur HTTP qui est capable de lire cette requête et de stocker la valeur « 1.55″ pour un objet « FIRST » dans une base de données.

Ce code se découpe en plusieurs parties :

  1. Se connecter au serveur 192.168.1.100, en mode TCP, sur le port 80 : « AT+CIPSTART=… ». Il faut attendre que le moduleESP8266 réponde « OK » pour continuer.
  2. Appeler l’URL en GET : « AT+CIPSEND=… ». Il faut absolument ajouter dans l’entête de cette requête le « Host: xxx », sinon le serveur répond « Error HTTP 400 : BAD REQUEST ». Pour être propre, je rajoute « User-Agent » et « Connection ». Les « \r\n » sont importants. A la fin, il faut les mettre en double : « \r\n\r\n ». Sinon, le serveur attend des données.
  3. (En option) Demander le statut de la connexion : « AT+CIPSTATUS ». elle doit être fermée (le serveur a pris la requête est il a fermé la connexion)
  4. S’assurer que toutes les connexions sont bien fermées : « AT+CIPCLOSE=5″ (le chiffre 5 signifie ‘toutes les connexions’)
  5. Réinitialiser le module ESP8266 : « AT ». Lors de la prochaine connexion au serveur (étape 1 de la boucle), la connexion Wifi sera conservée.
  6. Attendre 20 secondes pour le prochain envoi de données

Ici aussi, les délais d’attente entre les commandes sont importants. Ils peuvent être personnalisés selon votre besoin, mais ils ne pourront pas être mis à 0.

 

Envoi vers la carte Arduino

Les ports TX et RX sont utilisés lors du transfert du programme vers la carte Arduino. si vous laisser le 3.3v branché, Il faudra débrancher les TX et RX pour que le transfert se fasse correctement. Je réitère donc mon conseil de débrancher le 3.3v lors du transfert.

 

Résultat sur le serveur

Les données sont toutes récupérées, à intervalle régulier (toutes les 24 secondes) :

 

Trace laissée par le module

Dans la console « Moniteur série », voici les infos affichées par le module. On y trouve les commandes passées et les réponses du module.

Ai-Thinker Technology Co.,Ltd.

ready
AT

OK
AT+CWMODE=1

OK
AT+CWJAP="NETGEAR","pA$$W0Rd"

WIFI CONNECTED
WIFI GOT IP

OK
AT+CIPMUX=1

OK
AT+CIFSR

+CIFSR:STAIP,"192.168.1.200"
+CIFSR:STAMAC,"5c:cf:7f:10:d6:ab"

OK
AT+CIPSTART=4,"TCP","192.168.1.100",80

4,CONNECT

OK
AT+CIPSEND=4,157

OK
> 

busy s...

Recv 157 bytes

SEND OK

+IPD,4,728:HTTP/1.1 200 OK
Date: Sat, 16 Jan 2016 17:32:03 GMT
Server: Apache/2.4.12 (Ubuntu)
Set-Cookie: ci_session=a%3A5%3A%7Bs%3A10%3A%22session_id%22%3Bs%3A32%3A%227c75547487eb7803fa0afe75dead178d%22%3Bs%3A10%3A%22ip_address%22%3Bs%3A13%3A%22192.168.1.200%22%3Bs%3A10%3A%22user_agent%22%3Bs%3A19%3A%22ESP8266_HTTP_Client%22%3Bs%3A13%3A%22last_activity%22%3Bi%3A1452965523%3Bs%3A9%3A%22user_data%22%3Bs%3A0%3A%22%22%3B%7Deb938672b48c15066e92aef755ab6250; expires=Sat, 16-Jan-2016 19:32:03 GMT; Max-Age=7200; path=/
Access-Control-Allow-Origin: *
Vary: Accept-Encoding
Content-Length: 81
Connection: close
Content-Type: text/html; charset=UTF-8

{"datiddat":41,"datflval":"1.55","datdhacq":"2016-01-16 18:32:03","datidthn":"1"}4,CLOSED
AT+CIPSTATUS

STATUS:4

OK
AT+CIPCLOSE=5

OK
AT

OK
AT+CIPSTART=4,"TCP","192.168.1.100",80

4,CONNECT

OK
AT+CIPSEND=4,157

OK
> 

busy s...

Recv 157 bytes

SEND OK

+IPD,4,728:HTTP/1.1 200 OK
Date: Sat, 16 Jan 2016 17:32:27 GMT
Server: Apache/2.4.12 (Ubuntu)
Set-Cookie: ci_session=a%3A5%3A%7Bs%3A10%3A%22session_id%22%3Bs%3A32%3A%220a10f1ca33ba3316e6acfee18ba76b2f%22%3Bs%3A10%3A%22ip_address%22%3Bs%3A13%3A%22192.168.1.200%22%3Bs%3A10%3A%22user_agent%22%3Bs%3A19%3A%22ESP8266_HTTP_Client%22%3Bs%3A13%3A%22last_activity%22%3Bi%3A1452965547%3Bs%3A9%3A%22user_data%22%3Bs%3A0%3A%22%22%3B%7Db946991f8b9d1ef88d0667330b515767; expires=Sat, 16-Jan-2016 19:32:27 GMT; Max-Age=7200; path=/
Access-Control-Allow-Origin: *
Vary: Accept-Encoding
Content-Length: 81
Connection: close
Content-Type: text/html; charset=UTF-8

{"datiddat":42,"datflval":"1.55","datdhacq":"2016-01-16 18:32:27","datidthn":"1"}4,CLOSED
AT+CIPSTATUS

STATUS:4

OK
AT+CIPCLOSE=5

OK
AT

OK
...

 

Annexe

Toutes les commandes et la doc technique (en anglais) de ce module ESP8266 : https://nurdspace.nl/ESP8266

Code complet : téléchargez

 

Raspberry : un feu de cheminée ou un aquarium

Introduction

Noël approche, qui n’a pas rêvé d’avoir un feu de cheminée. Je vous propose de transformer votre Raspberry en « boite à diffuser une vidéo de distraction », comme un feu de cheminée, un aquarium ou une jolie vue de ruisseau.

 

Configuration du Raspberry

Rien de plus qu’un Raspberry qui fonctionne et qui est connecté à un écran.

 

Choix de la vidéo

Youtube fourni un grand nombre de vidéos, en HD de feu de cheminée ou d’aquarium. Voici mon choix :

https://www.youtube.com/watch?v=0fYL_qiDYf0

La vidéo dure 2 heures, mais on n’a besoin de tout le fichier. Il faut passer par une outil de téléchargement (cherchez « youtube downloader firefox plugin ») pour récupérer le fichier MP4.

 

Extraction d’une portion de quelques minutes

Passez par ffmpeg pour extraire une partie de la vidéo. Notez la période : début et durée, puis lancer la ligne de commande suivante pour l’extraction :

ffmpeg -ss 00:30:00 -i feuDeCheminee-full.mp4 -t 00:02:00 -vcodec copy -acodec copy feu-portion.mp4

Sur la vidéo « feuDeCheminee-full.mp4 », je saute 30 minutes du début et je prends 2 minutes de vidéo finale. Le fichier « feu-portion.mp4 » contiendra le résultat : une vidéo de quelques dizaines de Mo (au lieu de plus d’un Go pour la vidéo complète).

 

Lire la vidéo avec le Raspberry

Tentez de lire la vidéo avec VLC : c’est très lent. Impossible de choisir cette solution. En allant voir sur le Net, le player conseillé est « omxplayer ». Cherchez à l’installer avec les bibliothèques pour être sûr du résultat.

Mon Raspberry est configuré en point d’accès Wifi, avec une application Web pour le piloter (depuis mon smartphone ou depuis un PC). J’ajoute cette fonctionnalité, en appelant un script pour lancer la vidéo :

#!/bin/bash

export DISPLAY=:0.0

SERVICE='omxplayer'
RES=0
trap "exit" INT
while [ $RES -eq 0 ]; do
  if ps ax | grep -v grep | grep $SERVICE > /dev/null
  then
    sleep 1
  else
    xterm -fullscreen -fg black -bg black -e omxplayer --win "0 0 1600 1200" -r $1
    RES=$?
    echo "RES: $RES"
  fi
done

xrefresh -display :0

 

Vous constaterez des lignes particulières (que j’utilise peu) :

trap "exit" INT

Cela signifie que si un sous-script est lancé et qu’il se fait killer, le script s’arrête aussi.

 

xrefresh -display :0

A la fin du script, je force le rafraîchissement de l’écran pour nettoyer ce qui peut rester.

 

xterm -fullscreen -fg black -bg black -e omxplayer --win "0 0 1600 1200" -r $1
RES=$?

La ligne de commande principale, qui lance la vidéo, dans un terminal (xterm), en plein écran, avec omxplayer en mode fenêtre, d’une taille de 1600×1200. La variable RES récupère le code retour du player. Si elle vaut 0, on boucle, sinon, on s’arrête (le CTRL-C fait un RES = 2).

Pour simuler la lecture sans fin, j’ai une boucle while autour de tout ça.

 

Arrêt de la lecture par un script

J’ai aussi un autre script qui arrête la lecture en boucle :

PID=$(ps aux | grep omxplayer | awk {'print $2'})
kill -9 $PID 2>/dev/null

xrefresh -display :0

Rien de très novateur. J’ai essayé la commande « killall -9 omxplayer », mais elle n’est pas satisfaisante.

Mon interface (depuis un PC) pour lancer quelques vidéos et un bouton en bas pour arrêter :

Arduino : premiers essais

Introduction

Après avoir complètement paramétré mon Raspberry Pi en point d’accès wifi, avec une webcam et une application web pour le piloter depuis mon smartphone (article complet), je voulais essayer jouer avec les PIN du GPIO. Mais je ne voulais pas refaire un autre Raspberry pour jouer avec les « sensors » (détecteur de température, détecteur de mouvement, etc.). Pour faire ça, il y a Arduino, en plus simple et plus économique…

 

 

Installation de l’environnement

Depuis longtemps, je suis habitué à l’IDE Eclipse pour mes développements. J’ai pris mes réflexes et je pense être moins désorienté dans ces environnement. C’est pour cette raison que j’installe un Eclipse dédié.

J’ai donc suivi quelques tutoriels, mais celui-ci me semble très complet pour tout savoir sur l’interface entre ma machine et la carte Adruino : http://www.codeproject.com/Articles/1003347/Creating-Arduino-programs-in-Eclipse

Avec Ubuntu, le port est à changer. Pour savoir quel port il faut configurer (« //./COM15″ dans le tutoriel), voici les étapes, depuis un terminal :

  1. Branchez votre carte Arduino sur le port USB et tapez « lsusb ». Si une ligne apparaît avec « Arduino », c’est bon, la carte est reconnue.
  2. Tapez « dmesg ». Vous devriez voir :
    [ 1013.247248] usb 8-2: Product: Arduino Uno
    [ 1013.247250] usb 8-2: Manufacturer: Arduino Srl
    [ 1013.247253] usb 8-2: SerialNumber: 85431303636351D0A101
    [ 1013.272419] cdc_acm 8-2:1.0: ttyACM0: USB ACM device
  3. Tapez « ls /dev/ttyACM* ». Vous devriez avoir le port de l’Arduino : /dev/ttyACM0
  4. Il reste à permettre à l’utilisateur d’écrire dans ce device : sudo chmod 777 /dev/ttyACM0

 

Premier script : « blink »

En suivant le tutoriel, je crée le programme « blink.cpp » en copiant / collant le code fourni, mais une erreur survient sur l’include :

#include "arduino.h"

 

Il faut la changer en :

#include "Arduino.h"

En consultant le fichier recherché dans mon code (version 1.6.6 d’Arduino), il y a un « A » (majuscule).

Le build se passe correctement et l’installation sur la carte aussi. La LED clignote bien.

 

Conclusion

Même si l’IDE fourni par Arduino fonctionne très bien, il m’a fallu un peu plus d’une heure pour installer quelques plugins et adapter le dernier. J’espère que cet article fera gagner du temps à ceux qui essaieront.