[Résolu] : ETHERNET - reception osc et envoi tcp (PjLink)

Bonjour à tous,

J'essaie de faire un projet arduino pour piloter le shutter interne de video-projecteurs compatible PJ-Link.
Il s'agit donc d'envoyer une commande TCP :
"%1AVMT 30" pour ouvrir le shutter
"%1AVMT 31" pour fermer le shutter

Je souhaite commander cette fonction via OSC avec un message simple :
"/pjlink.1/shutter 0" pour ouvrir le shutter
"/pjlink.1/shutter 1" pour fermer le shutter

J'arrive à faire fonctionner la chose, mais seulement le premier ordre est effectué… Ensuite j'ai besoin de faire un "Reset" de l'arduino pour qu'un nouvel ordre soit effectué.
J'ai mis en place un "serial.println" en parralele du "client.println" afin de debugger la chose, et je m'apercois que l'ordre OSC passe bien, le probleme étant localisé dans l'envoi TCP.

Ce que j'aimerais est que la connexion TCP s'ouvre vers l'IP et le port selectionné à chaque fois qu'une commande OSC est reçu, car j'aimerais par la suite pouvoir avoir plusieurs messages OSC qui enverraient des commandes vers plusieurs IP

mais je rame déjà avec un, alors …

si quelqu'un a une piste sur laquelle me lancer…

par avance, merci !!

renaud

#include <Ethernet.h>
#include <SPI.h>
#include <ArdOSC.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 10, 0, 0, 10 };
byte vp3[] = { 10, 0, 0, 13 }; 
int serverPort = 10000;

EthernetClient client;
OSCServer server;

void setup()
{
  Ethernet.begin(mac, ip);
  Serial.begin(19200);
  client.connect(vp3, 4352);
  server.begin(serverPort);
  server.addCallback("/pjlink.1/shutter",&shutter1);
  delay(1000);
  Serial.println("connecting...");
  if (client.connected()) {
    Serial.println("connected");
  } else {
    Serial.println("connection failed");
  }
}

void loop()
{ 
    if(server.aviableCheck()>0){
}
  if (client.available()) {
    char c = client.read();
    Serial.print(c);
  }

  if (!client.connected()) {
    Serial.println();
    Serial.println("disconnecting.");
    client.stop();
    for(;;)
      ;
  }
}

void shutter1(OSCMessage *_mes) {
  int value = _mes->getArgInt32(0);
  if(value == 1){
      Serial.println("UN");
      client.println("%1AVMT 31");
  }
  else{
      Serial.println("ZERO");
      client.println("%1AVMT 30");
   }
}

Bonjour

Juste pour être clair :

je m'apercois que l'ordre OSC passe bien

Est-ce que ca veut dire que tu as bien les traces situées dans la fonction shutter1() ?

le probleme étant localisé dans l'envoi TCP.

Mais que le client.println() semble ne pas marcher ?

Est-ce que tu tombes des fois dans le code

    Serial.println("disconnecting."); 
    client.stop();
    for(;;)
      ;

Salut,

je m'apercois que l'ordre OSC passe bien /
Est-ce que ca veut dire que tu as bien les traces situées dans la fonction shutter1() ?

J'ai bien les traces dans la console ZERO et UN a chaque fois que j'envoie des valeurs (0/1)

Mais que le client.println() semble ne pas marcher ?

C'est exactement cela, c'est le "client.println()" qui ne fonctionne qu'une seule fois, la premiere fois apres reboot/reset de la duino…

Je tombe parfois sur la deconnection, mais pas de suite… ca sera surement le probleme suivant !!

Mais je m'y prends mal peut-être depuis le debut, mais il n'y a qu'avec ces morceaux de code récupérés sur les pages de référence de la librairie ethernet que j'arrive à un début de résultat.

Mon but est d'associer une reception de message osc à un envoi de message TCP…

La réception de "/pjlink.1/shutter 0" devra envoyer "‰1AVMT 30" à l'ip 10.0.0.11
La réception de "/pjlink.1/shutter 1" devra envoyer "‰1AVMT 31" à l'ip 10.0.0.11
La réception de "/pjlink.2/shutter 0" devra envoyer "‰1AVMT 30" à l'ip 10.0.0.12
La réception de "/pjlink.2/shutter 1" devra envoyer "‰1AVMT 31" à l'ip 10.0.0.12
La réception de "/pjlink.3/shutter 0" devra envoyer "‰1AVMT 30" à l'ip 10.0.0.13
La réception de "/pjlink.3/shutter 1" devra envoyer "‰1AVMT 31" à l'ip 10.0.0.13

merci !!

renaud

Il est possible que ta syntaxe du message vers le projecteur ne soit pas exacte.
Ce que je veut dire c'est que le "%1AVMT 30" marche mais que le println() qui ajoute les caractères CR et LF après pose problème.
La commande serait prise en compte mais les caractère supplémentaires mettrait le projecteur dans un mode où il n'accepte plus de commande.

As tu expérimenté le controle du projecteur depuis un soft PC par exemple ?
Es-tu sur de la syntaxe de fin de commande ?

De toute façon, il va falloir change la logique de ton programme :

  • pour supporter les déconnexions intenpestives
  • pour gérer plusieurs IP destinations

En effet, l'Arduino ne peut pas maintenir plusieurs connexions clientes simultanées.
Tu n'aura donc pas le choix a terme de :

  • pouvoir ré-connecter au projecteur en cas de deconnexion
  • deconnecter et se reconnecter à un autre projecteur

Je te suggère dans un premier temps de déplacer le client.connect() dans la fonction shutter1 :

void setup()
{
  Ethernet.begin(mac, ip);
  Serial.begin(19200);
  server.begin(serverPort);
  server.addCallback("/pjlink.1/shutter",&shutter1);
  delay(1000);
}

void loop()
{ 
    if(server.aviableCheck()>0) {
    }
}

void shutter1(OSCMessage *_mes) {
  int value = _mes->getArgInt32(0);

  Serial.println("connecting...");
  client.connect(vp3, 4352);
  if (!client.connected()) {
    Serial.println("connection failed");
    return;
  }

  Serial.println("connected");

  if(value == 1){
      client.println("%1AVMT 31");
      Serial.println("UN");
  }
  else{
      client.println("%1AVMT 30");
      Serial.println("ZERO");
   }
   client.stop();
}

Comme cela tu ouvres et ferme la connection TCP a chaque fois.
Après, si ca marche, on pourra essayer d'optimiser

Juste avant, essaye de modifier d'abord shutter comme cela :

      client.print("%1AVMT 31\r");

D'après cette doc : http://pjlink.jbmia.or.jp/english/data/PJLink%20Specifications100.pdf
Le terminateur de ligne doit être CR uniquement
or println() envoie CR + LF
Le LF peut poser problème

Ce que je veut dire c'est que le "%1AVMT 30" marche mais que le println() qui ajoute les caractères CR et LF après pose problème.

Oui, cela doit être effectivement cela… je fais l'essai tout à l'heure des que j'arrive… Mais effectivement, lorsque je m'en sers de la commande tcp via terminal ou Max(cycling74), je n'envoie qu'un retour chariot, et j'ai survolé trop vite la doc de "println" pour m'apercevoir qu'un LF était envoyé en plus du CR…
Donc cela parait logique, la machine reçoit l'ordre une première fois correctement, puis avec une syntaxe inapproprié ce qui met le système en carafe…

Je te suggère dans un premier temps de déplacer le client.connect() dans la fonction shutter1 :

Je testerais cela de suite après, car il est vrai que pour plusieurs VP, il faut que je me connecte à chaque fois avant d'envoyer l'ordre… C'est comme ca que je fais dans la version max…

Merci pour tes conseils avisés, et à tout à l'heure,

renaud

J'ai donc updaté mon code, conformément à tes conseils, et cela tourne impeccable… Génial, cela fonctionne !!!

En revanche je ne savais plus ou mettre ce code, afin de permettre d'avoir le retour d'information. En effet le projecteur me réponds suite à l'envoi d'un ordre grâce à ces quelques lignes

  if (client.available()) {
    char c = client.read();
    Serial.print(c);
  }

J'ai donc essayé de le mettre avant le mettre au début de la fonction shutter, ainsi la connection reste ouverte pour permettre le retour d'info dans le "void loop", et avant chaque connection, (client.connect) j'ai mis une déconnexion (client.stop) au cas ou un autre client était connecté… cela fonctionne assez bien… j'envoie une ouverture ou une fermeture au vp concerné et cela s'effectue bien…
Le "hic", c'est lorsque j'envoie les 3 envois (ouverture ou fermeture) "simultanément" pour ouvrir ou fermer les 3 shutters, cela me semble étrange :

  • #1 : je n'ai qu'un des 3 retours d'informations… C'est à dire qu'un seul des projecteurs à le temps de me répondre, je pense du au fait qu'il faille déconnecter un vp pour se connecter à un second
  • #2 : les 3 ouvertures ou fermetures sont quelques peu espacées dans le temps, je veux dire quelque chose comme 100 ms environ…

Est-ce que ces deux "problèmes" sont des limitations de l'arduino ou bien y-aurais-til possibilité de faire mieux?

En tout cas merci pour le coup de main… je ne m'en sortais pas !!

Voici le code pour mes 3 video-projecteurs :

#include <Ethernet.h>
#include <SPI.h>
#include <ArdOSC.h>
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 10, 0, 0, 10 };
byte vp1[] = { 10, 0, 0, 11 }; 
byte vp2[] = { 10, 0, 0, 12 }; 
byte vp3[] = { 10, 0, 0, 13 }; 
int serverPort = 10000;
EthernetClient client;
OSCServer server;

void setup() {
  Ethernet.begin(mac, ip);
  Serial.begin(19200);
  server.begin(serverPort);
  server.addCallback("/pjlink.1/shutter",&shutter1);
  server.addCallback("/pjlink.2/shutter",&shutter2);
  server.addCallback("/pjlink.3/shutter",&shutter3);
  delay(1000);
  }

void loop() { 
  // reception de messages OSC
    if(server.aviableCheck()>0){
          }
  if (client.available()) {
    char c = client.read();
    Serial.print(c);
    }
  }
void shutter1(OSCMessage *_mes) {
  //deconnection
     client.stop();
    int value = _mes->getArgInt32(0);
    // connexion au vp
     Serial.println("connecting...");
        client.connect(vp1, 4352);
    if (!client.connected()) {
          Serial.println("connection failed");
          return;
          }
  
    Serial.println("connected");
  if(value == 1){
      Serial.println();
      Serial.println("UN");
      client.print("%1AVMT 31\r");
  }
  else{
      Serial.println();
      Serial.println("ZERO");
      client.print("%1AVMT 30\r");
   }
}

void shutter2(OSCMessage *_mes) {
  //deconnection
     client.stop();
    int value = _mes->getArgInt32(0);
    // connexion au vp
     Serial.println("connecting...");
        client.connect(vp2, 4352);
    if (!client.connected()) {
          Serial.println("connection failed");
          return;
          }
  
    Serial.println("connected");
  if(value == 1){
      Serial.println();
      Serial.println("UN");
      client.print("%1AVMT 31\r");
  }
  else{
      Serial.println();
      Serial.println("ZERO");
      client.print("%1AVMT 30\r");
   }
}

void shutter3(OSCMessage *_mes) {
  //deconnection
     client.stop();
    int value = _mes->getArgInt32(0);
    // connexion au vp
     Serial.println("connecting...");
        client.connect(vp3, 4352);
    if (!client.connected()) {
          Serial.println("connection failed");
          return;
          }
  
    Serial.println("connected");
  if(value == 1){
      Serial.println();
      Serial.println("UN");
      client.print("%1AVMT 31\r");
  }
  else{
      Serial.println();
      Serial.println("ZERO");
      client.print("%1AVMT 30\r");
   }
}

Pour ceux que cela intéresse, le code va évoluer sur un repo Git
https://github.com/reno-/osc-pjlink/

Si tu envoie tes 3 commandes OSC à la suite, l'Arduino va les traiter en série.
A chaque fois il va se connecter, envoyer, et aussi faire les traces sur la console série.
Si tu laisses le code d'affichage du retour d'info dans loop(), surtout comme il est écrit, il est presque sur que tu vas te prendre la 2nde connexion dans la foulée et donc que tu va fermer le client et le réouvrir. Donc tu n'auras pas la suite.

Si tu écris le code comme cela :

while (client.available()) {
    char c = client.read();
    Serial.print(c);
  }

Tu auras tout mais au prix peut être d'encore plus de décalage.

Normalement, le W5100 peut gérer 4 connections en //
Mais OSC doit en prendre 1 aussi

Donc tu dois pouvoir gérer 3 projecteurs en //

Tu peux créer 3 variables de type EthernetClient, voir même un tableau et idem pour les adresses IP :

byte vpIp[3][4] = { { 10, 0, 0, 11 },  { 10, 0, 0, 12 },  { 10, 0, 0, 13 } } ;
EthernetClient vpClient[3];

Evite la dupplicationd e code et optimise shutter :

void shutter1(OSCMessage *_mes) {
  shutter( 1, _mes );
}

void shutter2(OSCMessage *_mes) {
  shutter( 2, _mes );
}

void shutter3(OSCMessage *_mes) {
  shutter( 3, _mes );
}

void shutter(byte vp, OSCMessage *_mes) {

  --vp; // 1, 2, 3 => 0, 1, 2 pour index de tableau

    int value = _mes->getArgInt32(0);

    if ( ! vpClient[vp].connected() )
    {
       // connexion au vp
       Serial.println("connecting...");
       int ret = vpClient[vp].connect(vp3, 4352);
       if ( (ret == 0) || !vpClient[vp].connected() )
       {
          Serial.println("connection failed");
         return;
       }
      Serial.println("connected");

      if ( value == 1 ) {
      Serial.println();
      Serial.println("UN");
      vpClient[vp].print("%1AVMT 31\r");
      }
      else {
      Serial.println();
      Serial.println("ZERO");
      cvClient[vp].print("%1AVMT 30\r");
   }
}

Et pour ta boucle loop() :

void loop() { 
  // reception de messages OSC
  if(server.aviableCheck()>0) {
  }

  for ( byte vp = 0 ; vp < 3 ; vp++ )
  while ( vpClient[vp].available() )
    Serial.print(vpClient[vp]);
}

Attention: code écrit vite fait et non testé. Il peut rester des fautes de frappes et autres petites erreurs mais l'idée est là.
Dis moi ce que tu en penses.

Waou…… Super explication et très belle optimisation… je ne pouvais réver mieux…

J'ai donc importé les morceaux de code proposés, et cela fonctionne bien… Merci !!!!!

Bon, mais cela ne fonctionne que durant 30 secondes, ensuite c'est déconnecté et cela ne veux plus se reconnecter… Mais cela est du au protocole PJLink qui demande à fermer la connection lors du retour d'information… c'est à implémenter maintenant…

Voici le code fonctionnel (durant 30 secondes)

La déclaration de variable et les imports de librairie

#include <Ethernet.h>
#include <SPI.h>
#include <ArdOSC.h>
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 10, 0, 0, 10 };
byte vpIp[3][4] = { { 10, 0, 0, 11 },  { 10, 0, 0, 12 },  { 10, 0, 0, 13 } } ;
EthernetClient vpClient[3];
int serverPort = 10000;
OSCServer server;

Le setup

void setup() {
  Ethernet.begin(mac, ip);
  Serial.begin(19200);
  server.begin(serverPort);
  server.addCallback("/pjlink.1/shutter",&shutter1);
  server.addCallback("/pjlink.2/shutter",&shutter2);
  server.addCallback("/pjlink.3/shutter",&shutter3);
  delay(1000);
  }

La loop

void loop() { 
  // reception de messages OSC
  if(server.aviableCheck()>0) {
  } 
  for ( byte vp = 0 ; vp < 3 ; vp++ )
  while ( vpClient[vp].available() )
      {
      char c = vpClient[vp].read();
      Serial.print(c);
      }
}

La fonction shutter

void shutter1(OSCMessage *_mes) {
  shutter( 1, _mes );
}

void shutter2(OSCMessage *_mes) {
  shutter( 2, _mes );
}

void shutter3(OSCMessage *_mes) {
  shutter( 3, _mes );
}

void shutter(byte vp, OSCMessage *_mes) {

  --vp; // 1, 2, 3 => 0, 1, 2 pour index de tableau

    int value = _mes->getArgInt32(0);

    if ( ! vpClient[vp].connected() )
    {
       // connexion au vp
       Serial.println("connecting...");
       int ret = vpClient[vp].connect(vpIp[vp], 4352);
       if ( (ret == 0) || !vpClient[vp].connected() )
       {
          Serial.println("connection failed");
          return;
       }
       else {
      Serial.println("connected");
       }
    }
      if ( value == 1 ) {
      Serial.println();
      Serial.println(vpClient[vp]);
      vpClient[vp].print("%1AVMT 31\r");
      }
      else {
      Serial.println();
      Serial.println(vpClient[vp]);
      vpClient[vp].print("%1AVMT 30\r");
   }
}

En (re)-lisant la doc PJLINK dans les détails, il apparait qu'il faut fermer la connection après avoir reçu la réponse comme quoi l'ordre a bien été executé.

J'ai donc ajouté une fermeture de la connection à l'aide de la commande "vpClient[vp].client.stop()" placée après un délai de 2 secondes aprèsl'envoi de la commande au VP.
Cela donne donc le code suivant, qui permet d'envoyer des commandes même après 30 secondes puisqu'on ferme et re-ouvre la connection à chaque fois… En revanche, j'ai perdu le retour d'info avec cet ajout, car il semble que l'arduino reste en pause et la "loop" ne s'effectue plus pendant ce temps…

Peux-être que le "delay(2000)" n'est pas la fonction la plus adapté, car d'après la doc de référence, cela met le prgramme général en pause, et la loop ne s'effectue plus, et le retour d'info et dans la loop…

D'en arriver la, du coup j'aurais envie d'implémenter tout le protocole PJLINK, avec les retours d'info et tout et tout, les autres fonctions etc… mais je sens que ca va prendre du temps !!!

merci des précédents conseils en tout cas, et je reste preneur d'une piste pour me lancer pour la suite (retrouver les retours d'info)

      if ( value == 1 ) {
      Serial.println();
      vpClient[vp].print("%1AVMT 31\r");
      delay(2000);
      vpClient[vp].stop();
      }
      else {
      Serial.println();
      vpClient[vp].print("%1AVMT 30\r");
      delay(2000);
      vpClient[vp].stop();
   }

S'il faut fermer la connexion après la réponse, alors autant le faire dans la fonction shutter
Mais au lieu de faire un delay(), reprend la boucle de lecture.
Et fait le dans cet ordre :

client.print( commande );
Serial.print( commande );
while ( client.available() )
  Serial.print(client.read());
client.close();

L'intérêt de mettre le Serial.print après le client.print c'est que le temps du Serial.print, ton projecteur exécute la commande et envoi sa réponse.
Donc quand tu arrives sur le while(), la réponse a normalement du arriver.

Après dans ta boucle loop() il ne reste plus que la gestion du serveur.

S'il faut fermer la connexion après la réponse, alors autant le faire dans la fonction shutter
Mais au lieu de faire un delay(), reprend la boucle de lecture.
Et fait le dans cet ordre :

Merci du conseil, je vais essayer dès que j'arrive près de la machine tout à l'heure.

J'ai essayé ce que tu m'as dit mais je n'ai pas réussi à bien faire la chose.

J'ai modifié la fonction shutter comme ceci mais je n'ai toujours pas de réponse du vp. J'ai la réponse du projecteur si j'enleve le client.stop, mais dans ce cas la cela n'est pas conforme au protocole pjlink qui demande à fermer la connection s'il y a 30 secondes d'inactivité, car dans ce cas la le projecteur fermera la communication.

        if ( value == 1 ) {
        vpClient[vp].print("%1AVMT 31\r");
        while ( vpClient[vp].available() )
           {         
                char c = (vpClient[vp].read());
                Serial.print(c);
            }
          vpClient[vp].stop();
      }
      else {
        vpClient[vp].print("%1AVMT 30\r");
        while ( vpClient[vp].available() )
           {         
                char c = (vpClient[vp].read());
                Serial.print(c);
            }
            vpClient[vp].stop();
      }

Cette solution est viable pour moi en l'état, c'est à dire en retirant les client.print car ils prennent du temps, mais cela me donne envie de coder tout le protocole PJlink dans une arduino avec un petit LCD, mais je pense que c'est une autre affaire qui necessitera du temps… je reviendrais surement sur le forum à ce moment la !!

J'ai encore accès aux projecteurs demain, si quelqu'un à une idée… ensuite je ne les retrouverais que dans quelques semaines…

merci encore pour cette précieuse aide…

renaud

reno-:
J'ai essayé ce que tu m'as dit mais je n'ai pas réussi à bien faire la chose.

Comme tu as enlevé les Serial.pribnt(), quand tu arrive au while(), le projecteur n'a pas encore répondu. Trop tôt.

cela n'est pas conforme au protocole pjlink qui demande à fermer la connection s'il y a 30 secondes d'inactivité, car dans ce cas la le projecteur fermera la communication.

Arg, ce n'est pas ce que tu as dis plus haut :

il apparait qu'il faut fermer la connection après avoir reçu la réponse comme quoi l'ordre a bien été exécuté.

Dans ce cas effectivement, il vaut mieux remettre les boucle de lecture des réponses dans loop() avec un calcul de temps passé pour fermer au bout de 30sec.

Comme tu as enlevé les Serial.pribnt(), quand tu arrive au while(), le projecteur n'a pas encore répondu. Trop tôt.

Oui… ben en fait je me suis un peu perdu…
En fait il faut bien fermer la connexion après avoir reçu la réponse comme quoi l'ordre a bien été exécuté, sinon (si on ne ferme pas la connection) celle-ci sera fermée par le projecteur au bout de 30 secondes d'inactivité.
Mais cette réponse arrive "dans les 2 secondes" après que la commande ait été reçue… Or dès que je ferme la connection, je n'ai plus de réponse…

Je vais donc essayer avec tes conseils le code suivant (la semaine prochaine) :

J'ai mis le "c" dans serial.print qui est le contenu de ce qui est recu provenant du vp ("vpClient[vp].available()" C'est bien ca?

        if ( value == 1 ) {
        vpClient[vp].print("%1AVMT 31\r");
        Serial.print(c);
        while ( vpClient[vp].available() )
           {         
                char c = (vpClient[vp].read());
                Serial.print(c);
            }
          vpClient[vp].stop();
      }
      else {
        vpClient[vp].print("%1AVMT 30\r");
        Serial.print(c);
        while ( vpClient[vp].available() )
           {         
                char c = (vpClient[vp].read());
                Serial.print(c);
            }
            vpClient[vp].stop();
      }

Voici le topo en image :
:

Spécifications PJLink : http://pjlink.jbmia.or.jp/english/data/PJLink%20Specifications100.pdf