netscratch : passerelle Scratch/rObOscratch

Configuration de Scratch

Un fichier json est créé pour définir de nouveaux blocs :


{  "extensionName": "rObOtScratch",
   "extensionPort": 9010,
   "blockSpecs": [
      ["r", "Valeur du capteur %m.capteur", "capteur"],
      [" ", "Stop", "ST"],
      [" ", "Avant", "AV"],
      [" ", "Arrière", "AR"],
      [" ", "Droite", "DR"],
      [" ", "Gauche", "GA"],
      [" ", "Fixer la vitese à %n", "V", 50],
      [" ", "Mettre la vitesse du moteur %m.moteur à %n", 50],
      ["r", "Capteur droit", "CD"],
      ["r", "Capteur gauche", "CG"],
   ],
   "menus": {
      "capteur": ["droit", "gauche"],
      "moteur": ["droit", "gauche"],
    },
}

Lorsqu’on clique avec « shift » + « clic droit » sur le menu « fichier », un item « Importer l’extension expérimentale » apparaît.
On sélectionne alors le fichier json créé. À cet instant, un nouveau lot de blocs devient accessible dans le choix
de blocs « Ajouter blocs ».


Traitement des trames reçues de Scratch

Lorsque le programme scratch s’exécute, chaque instruction en relation avec le pilotage du robot génère une requête http vers l’adresse 127.0.0.1:9010. Puisqu’il y a des variables internes au robot, scratch interroge également en permanence ce serveur avec des requêtes /poll pour obtenir en retour l’état des variables.

Voici deux trames HTTP reçues de Scratch. Ce sont toutes les mêmes, à l’URL près.
En rouge: la partie qui nous intéresse vraiment.


GET /ST HTTP/1.1
Host: 127.0.0.1:9010
Accept-Encoding: deflate, gzip
Accept: text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, text/plain;q=0.8, text/css, image/png, image/jpeg, image/gif;q=0.8, application/x-shockwave-flash, video/mp4;q=0.9, flv-application/octet-stream;q=0.8, video/x-flv;q=0.7, audio/mp4, application/futuresplash, */*;q=0.5
User-Agent: Mozilla/5.0 (X11; U; Linux i686; fr-FR) AppleWebKit/531.9 (KHTML, like Gecko) AdobeAIR/2.6
x-flash-version: 10,2,159,1
Connection: Keep-Alive
Referer: app:/Scratch.swf


GET /poll HTTP/1.1
Host: 127.0.0.1:9010
Accept-Encoding: deflate, gzip
Accept: text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, text/plain;q=0.8, text/css, image/png, image/jpeg, image/gif;q=0.8, application/x-shockwave-flash, video/mp4;q=0.9, flv-application/octet-stream;q=0.8, video/x-flv;q=0.7, audio/mp4, application/futuresplash, */*;q=0.5
User-Agent: Mozilla/5.0 (X11; U; Linux i686; fr-FR) AppleWebKit/531.9 (KHTML, like Gecko) AdobeAIR/2.6
x-flash-version: 10,2,159,1
Connection: Keep-Alive
Referer: app:/Scratch.swf

....

Architecture logicielle de la passerelle netscratch

La classe ServeurM est la classe principale.
Au lancement, le serveur recherche l’adresse du robot. Le robot se comporte comme un hotspot wifi. L’ordinateur est connecté sur ce wifi.
Lorsque la passerelle connaît sa propre adresse IP externe, elle en déduit l’adresse du robot (même adresse, mais qui se termine par « .1 »)
Elle attend une connexion de scratch sur le port 9010. Dès qu’un client est connecté, il est pris en charge par une classe Lien.

La classe Lien analyse la requête http et selon le cas, envoie une commande au robot ou bien lui demande l’état des capteurs. Dans le cas des capteurs, une réponse est renvoyée à scratch.

La classe CommandeT sert à faire une requête http au robot.

La fonction java chargée de l’acquisition et du traitement des trames:

// Attente d'une trame HTTP, et traitement
// trame "poll" -> il faut renvoyer les variables à scratch
// trame "/AV", "/AR" ... -> on exécute la commande au niveau du robot
protected byte[] lireTrame(){
   byte[] b = null;
   do {
      try {
         try {Thread.sleep(30);} catch(Exception e){}
         int N = dis.available(); // nombre d'octets disponibles 
         if (N!=0) {
            b = new byte[N];
            dis.read(b);
            // transforme le tableau d'octets en chaîne java : 
            String str = new String(b); 
            System.out.println(str);
            String[] T = str.split("GET");
            if (T.length>1) {
               String[] T2 = T[1].split("HTTP");
               if (T2[0].indexOf("poll") >= 0) retourVariables();
               else  envoieCommande(T2[0]);
            }
         }
      } catch (Exception e){System.out.println("Erreur de reception");}
  } while (b==null);
  return b;
}

Lorsque le programme scratch qui s’exécute est :

L’affichage de la passerelle en correspondance est :

Archive netscratch

Hardware ESP8266 pour rObOtscratch II

l’ESP8266-1

L’ ESP8266-01 est la plus petite ESP8266 : elle ne dispose que de 8 broches. Parmi celles ci, Vcc, GND, RST (reset) et CH_PD (chip select) ne sont pas des entrées/sorties mais servent au fonctionnement u module. Cela laisse GPIO0, GPIO2, Tx et Rx comme entrées/sorties possibles.



Mais même ces 4 broches ont des rôles spécifiques: GPIO0 et GPIO2 déterminent le mode de démarrage du module, alors que Tx,Rx sont utilisés pour la programmation du module et la communication série.

GPIO0 and GPIO2 doivent être câblés avec des résistances de pull-up pour garantir le démarrage de la carte sur le programme chargé.

Le double driver YL-86 (à base de L9110)

Le L9110 est un driver très compact qui peut travailler avec des tensions d’alimentation allant de 2.5V à 12V, avec un courant de 800mA. Il intègre des diodes de protection, qui préservent l’électronique.


La carte utilisée ici comporte 2 drivers indépendants, ce qui permet de commander 2 moteurs cc.

IA1 IB1 IA2 IB2 sortie
0 0 x x Moteur 1 à l’arrêt
0 1 x x Moteur 1 en avant
1 0 x x Moteur 1 en arrière
1 1 x x Moteur 1 à l’arrêt
x x 0 0 Moteur 2 à l’arrêt
x x 0 1 Moteur 2 en avant
x x 1 0 Moteur 2 en arrière
x x 1 1 Moteur 2 à l’arrêt

Remarque : les notions « avant » et « arrière » dépendent bien sûr du câblage du moteur.

Firmware ESP8266-1 pour rObOtscratch II

Câblage

/**
 *  C.G. 2017 - Firmware ESP 8266-1 pour rObOscratch II
**/
 
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>

const char* ssid = "rObOtScratch";
const char* password = "56claude";

ESP8266WebServer server(80);


int bAV_D = 1; // variable d'état : moteur droite en avant (1) ou arrière (0)
int vAV_D = 0; // nb de ticks en position haute pour le hacheur
int vAV_Dold = 4; // pour reprendre à la meme vitesse apres un arret

int bAV_G = 1;
int vAV_G = 0;
int vAV_Gold = 4;

int h = 0;

void handleRoot() {
  server.send(200, "text/plain", "CG 2017 PETIT ROBOT avec ESP8266");
}

void handleAV() {
  bAV_D = 1;
  vAV_D = vAV_Dold;
  bAV_G = 1;
  vAV_G = vAV_Gold;
   
}

void handleAR() {
  bAV_D = 0;
  vAV_D = vAV_Dold;
  bAV_G = 0;
  vAV_G = vAV_Gold;
}

void handleDR() {
  bAV_D = 1;
  vAV_D = vAV_Dold;
  bAV_G = 0;
  vAV_G = vAV_Gold;  
}

void handleGA() {
   bAV_D = 0;
   vAV_D = vAV_Dold;
   bAV_G = 1;
   vAV_G = vAV_Gold;
}

void handlePlus() {
    vAV_D++; if (vAV_D>10) vAV_D=10;
    vAV_G++; if (vAV_G>10) vAV_G=10;
    vAV_Dold = vAV_D;
    vAV_Gold = vAV_G;
}

void handleMoins() {
    vAV_D--; if (vAV_D<0) vAV_D=0;
    vAV_G--; if (vAV_G<0) vAV_G=0;
    vAV_Dold = vAV_D;
    vAV_Gold = vAV_G;
}

void handleStop() {
  vAV_Dold = vAV_D;
  vAV_D = 0;
  vAV_Gold = vAV_G;
  vAV_G = 0; 
}

void handleNotFound(){
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET)?"GET":"POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i=0; i<server.args(); i++){ 
     message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; 
  } 
  server.send(404, "text/plain", message); 
} 


// HACHEUR 
volatile int toggle; 
void inline hacheur (void){ 
        h++; if (h>10) h=0;
	if (bAV_D == 1) { // moteur droite en avant
		if (h < vAV_D) { digitalWrite(0, 1);  digitalWrite(2, 0); } 
                else { digitalWrite(0, 0); digitalWrite(2, 0);}
	} else {
		if (h < vAV_D) { digitalWrite(0, 0); digitalWrite(2, 1);} 
                else { digitalWrite(0, 0); digitalWrite(2, 0);}
	}
	
	if (bAV_G == 1) { // moteur gauche en avant
		if (h < vAV_G) { digitalWrite(1, 1);  digitalWrite(3, 0);} 
                else { digitalWrite(1, 0); digitalWrite(3, 0);}
	} else {
		if (h < vAV_G) { digitalWrite(1, 0); digitalWrite(3, 1); } 
                else { digitalWrite(1, 0); digitalWrite(3, 0); } } 

       timer0_write(ESP.getCycleCount() + 80000); 
       // 80Mhz -> 80*10^6 = 1 seconde
       //          80*10^3 = 1 ms    
}

void setup(void){
   int i = 0;
   
   pinMode(0, OUTPUT);
   pinMode(1, OUTPUT);
   pinMode(2, OUTPUT);
   pinMode(3, OUTPUT);

   WiFi.begin(ssid, password);
   delay(500);
   // Attente de connexion
   while (WiFi.status() != WL_CONNECTED) delay(500);

   server.on("/", handleRoot);
   server.on("/av",[](){handleAV(); server.send(200, "text/plain", "AV");});
   server.on("/1", [](){server.send(200, "text/plain", "1");});
   server.on("/0", [](){server.send(200, "text/plain", "0");});
   server.on("/ar",[](){handleAR(); server.send(200, "text/plain", "AR");});
   server.on("/dr",[](){handleDR(); server.send(200, "text/plain", "DR");});
   server.on("/ga",[](){handleGA(); server.send(200, "text/plain", "GA");});
   server.on("/+", [](){handlePlus(); server.send(200, "text/plain", "+");});
   server.on("/-", [](){handleMoins(); server.send(200, "text/plain", "-");});
   server.on("/st",[](){handleStop(); server.send(200, "text/plain", "STOP");});
   server.onNotFound(handleNotFound);
   server.begin();  
   
   noInterrupts();
   timer0_isr_init();
   timer0_attachInterrupt(hacheur);
   timer0_write(ESP.getCycleCount() + 80000000);
   interrupts();
 
}

void loop(void){
  server.handleClient();
}