Traitement asynchrone avec Arduino (traitement des demandes de traitement de Linux de manière asynchrone)

Dans un autre article, j'ai écrit [Corriger l'affichage de la matrice LED 16x8]. En utilisant cette matrice de LED, si vous envoyez une demande d'affichage de message depuis Linux, nous allons créer un Arduino qui fait défiler le message et renvoie une réponse. Le but de cet article est "Recevoir des requêtes et afficher des messages dans Arduino de manière asynchrone (à proprement parler, pseudo-asynchrone)".

Quoi utiliser.


Vérification de la connexion série USB Linux <-> arduino

Message du noyau lorsque Arduino NANO est connecté à Linux
usb 1-12: new full-speed USB device number 3 using xhci_hcd
usb 1-12: New USB device found, idVendor=0403, idProduct=6001
usb 1-12: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 1-12: Product: FT232R USB UART
usb 1-12: Manufacturer: FTDI
usb 1-12: SerialNumber: ALxxxxxx
usbcore: registered new interface driver usbserial_generic
usbserial: USB Serial support registered for generic
usbcore: registered new interface driver ftdi_sio
usbserial: USB Serial support registered for FTDI USB Serial Device
ftdi_sio 1-12:1.0: FTDI USB Serial Device converter detected
usb 1-12: Detected FT232RL
usb 1-12: FTDI USB Serial Device converter now attached to ttyUSB0
Message du noyau lorsque Arduino NANO Every est connecté à Linux
usb 1-6: new full-speed USB device number 4 using xhci_hcd
usb 1-6: New USB device found, idVendor=2341, idProduct=0058
usb 1-6: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 1-6: Product: Arduino Nano Every
usb 1-6: Manufacturer: Arduino LLC
usb 1-6: SerialNumber: FAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
cdc_acm 1-6:1.0: ttyACM0: USB ACM device
usbcore: registered new interface driver cdc_acm
cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters

serial_test.ino


#include <Wire.h>
 
void setup()
{
  Wire.begin();
  Serial.begin(115200);
  Serial.println("serial test");
}

void loop()
{
  String str;  
  if(Serial.available()>0) {
    str = Serial.readString();
    Serial.println("[" + str + "]");
  }
}

screen&nbsp;Contrôle de fonctionnement avec


$ screen /dev/ttyUSB0 115200
serial test
hogehoge
[hogehoge]

Essayez de faire défiler le texte

Lorsque vous transmettez une requête de Linux à arduino, il renverra une réponse lorsque le contenu est exécuté. Pour le moment, implémentez les fonctions suivantes comme point de départ. Les données sont transmises en JSON.

une fonction request response
Affichage du texte par défilement {"textscr":"hello world !"} {"status":200, "msg":"..."}
Vérification de l'état d'Arduino {"status":{}} {"status":200, "msg":"..."}

Voici un extrait du code principal d'Arduino

test.ino


int sendResponse(int status, String msg) {
  StaticJsonDocument<100> response;
  
  response["status"] = status;
  response["msg"] = msg;

  serializeJson(response, Serial);
  Serial.println();
}

int ExecCmd(String cmd, JsonVariant value) {
  if(cmd == "status") {
    sendResponse(100, "alive");
   
  } else if(cmd == "textscr") {
    JsonObject obj2 = value.as<JsonObject>();
    
    String msg = obj2["msg"];
    int size = obj2["size"];

    if(size < 1 || size > 2) {
      size = 1;
    }
    matrix.setTextSize(size);
    matrix.setTextWrap(false);
    matrix.setTextColor(LED_ON);

    int l = msg.length();
    for(int16_t x = 7; x >= -6*l; x--) {
      matrix.clear();
      matrix.setCursor(x,0);
      matrix.print(msg);
      matrix.writeDisplay();
      delay(100);
    }

    sendResponse(200, "msg:" + msg + ",size:" + (String)size);

  } else { // "status" : 404
    sendResponse(404, "command not found:" + cmd);
  }
}

StaticJsonDocument<200> request;

void loop() {
  if(Serial.available() > 0) {
    DeserializationError error = deserializeJson(request, Serial);
    if (error) {
      sendResponse(500, error.c_str());
      return;
    }

    JsonObject obj = request.as<JsonObject>();
    for (JsonPair p : obj) {
      ExecCmd((String)p.key().c_str(), p.value());
    }
  }
}
Le code Arduino complet est ici

test.ino


#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_LEDBackpack.h>
#include <ArduinoJson.h>

#ifndef _swap_int16_t
#define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; }
#endif

class aitendo_KLED1608K33D_8x16matrix : public Adafruit_LEDBackpack, public Adafruit_GFX {
 public:
  aitendo_KLED1608K33D_8x16matrix(void);

  void drawPixel(int16_t x, int16_t y, uint16_t color);

 private:
};

aitendo_KLED1608K33D_8x16matrix::aitendo_KLED1608K33D_8x16matrix(void) : Adafruit_GFX(16, 8) {
}

void aitendo_KLED1608K33D_8x16matrix::drawPixel(int16_t x, int16_t y, uint16_t color) {
  if ((y < 0) || (x < 0)) return;
  if ((getRotation() % 2 == 0) && ((x >= 16) || (y >= 8))) return;
  if ((getRotation() % 2 == 1) && ((y >= 16) || (x >= 8))) return;

 // check rotation, move pixel around if necessary
  switch (getRotation()) {
  case 0:
    if (x >= 8) {
      x -= 8;
      y += 8; 
    }
    break;
  case 1:
    y = 16 - y - 1;
    if(y >= 8) {
      y -= 8;
      x += 8;
    }
    _swap_int16_t(x, y);
    break;
  case 2:
    x = 16 - x - 1;
    y = 8 - y - 1;
    if (x >= 8) {
      x -= 8;
      y += 8; 
    }
    break;
  case 3:
    x = 8 - x - 1;
    if(y >= 8) {
      y -= 8;
      x += 8;
    }
    _swap_int16_t(x, y);
    break;
  }

  if (color) {
    displaybuffer[x] |= 1 << y;
  } else {
    displaybuffer[x] &= ~(1 << y);
  }
}

aitendo_KLED1608K33D_8x16matrix matrix = aitendo_KLED1608K33D_8x16matrix();

void setup() {
  Serial.begin(115200);
  Serial.println("16x8 LED Matrix");

  matrix.begin(0x70);
  matrix.setBrightness(5);
  matrix.setRotation(0);
  matrix.clear();
  matrix.writeDisplay();
}

int sendResponse(int status, String msg) {
  StaticJsonDocument<100> response;
  
  response["status"] = status;
  response["msg"] = msg;

  serializeJson(response, Serial);
  Serial.println();
}

int ExecCmd(String cmd, JsonVariant value) {
  if(cmd == "status") {
    sendResponse(100, "alive");
   
  } else if(cmd == "textscr") {
    JsonObject obj2 = value.as<JsonObject>();
    
    String msg = obj2["msg"];
    int size = obj2["size"];

    if(size < 1 || size > 2) {
      size = 1;
    }
    matrix.setTextSize(size);
    matrix.setTextWrap(false);
    matrix.setTextColor(LED_ON);

    int l = msg.length();
    for(int16_t x = 7; x >= -6*l; x--) {
      matrix.clear();
      matrix.setCursor(x,0);
      matrix.print(msg);
      matrix.writeDisplay();
      delay(100);
    }

    sendResponse(200, "msg:" + msg + ",size:" + (String)size);

  } else { // "status" : 404
    sendResponse(404, "command not found:" + cmd);
  }
}

StaticJsonDocument<200> request;

void loop() {
  if(Serial.available() > 0) {
    DeserializationError error = deserializeJson(request, Serial);
    if (error) {
      sendResponse(500, error.c_str());
      return;
    }

    JsonObject obj = request.as<JsonObject>();
    for (JsonPair p : obj) {
      ExecCmd((String)p.key().c_str(), p.value());
    }
  }
}

Voici le code côté Linux Tout d'abord, j'ai finalement vérifié la connexion USB entre la box Linux et Arduino, mais ici j'utilise Ubuntu, qui est l'environnement de test du sous-système Windows Linux.

test.py


import serial
import time
import json

s = serial.Serial()
s.port = "/dev/ttyS4" #Connectez-vous à COM4 arduino
s.baudrate = 115200
s.timeout = 1
s.dtr = False   #Empêche arduino d'être réinitialisé lors de la connexion en série
s.open()
time.sleep(1)   #Attendez les sentiments

s.reset_input_buffer()  #Nettoyage du tampon de réception du port série

def request(data):
    print("request:", data)
    s.write(json.dumps(data).encode())  # encode()Doit être une chaîne binaire dans
    while True: #Attendez que la réponse revienne
        msg = s.readline().decode()
        if(len(msg) > 0):
            print("response:", msg)
            break

request({"textscr" : {"msg":"Hello World !!!"}})
request({"status" : {}})

Résultat d'exécution led.gif Il est correct que la réponse à la requête "textcr" soit {"status": 200, "msg": Hello World !!! "}, mais il semble que le premier caractère a été renversé à ce moment. Pensez à la gestion des erreurs. C'est ça?


Gérer le blocage du traitement pendant l'affichage du défilement du texte

Comme vous pouvez le voir dans le résultat de l'exécution, la réponse n'est pas renvoyée tant que l'affichage des textes n'est pas terminé, et une autre commande n'est pas acceptée en attendant. C'est un peu difficile à gérer. Le défilement de texte du côté Arduino utilise une boucle for pour attendre 100 ms que le caractère soit affiché, puis le décale horizontalement de 1 point, de sorte que chaque fois que le texte est décalé d'un point, le processus est renvoyé en boucle () et l'étape suivante est en cours de défilement. Vous permet de recevoir des commandes. De plus, si un nouveau textcr est envoyé pendant le défilement, l'exécution d'affichage existante sera supprimée et écrasée.

<détails>

Version modifiée Arduino Le code entier est ici </ summary>

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_LEDBackpack.h>
#include <ArduinoJson.h>

#ifndef _swap_int16_t
#define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; }
#endif

class aitendo_KLED1608K33D_8x16matrix : public Adafruit_LEDBackpack, public Adafruit_GFX {
 public:
  aitendo_KLED1608K33D_8x16matrix(void);

  void drawPixel(int16_t x, int16_t y, uint16_t color);

 private:
};

aitendo_KLED1608K33D_8x16matrix::aitendo_KLED1608K33D_8x16matrix(void) : Adafruit_GFX(16, 8) {
}

void aitendo_KLED1608K33D_8x16matrix::drawPixel(int16_t x, int16_t y, uint16_t color) {
  if ((y < 0) || (x < 0)) return;
  if ((getRotation() % 2 == 0) && ((x >= 16) || (y >= 8))) return;
  if ((getRotation() % 2 == 1) && ((y >= 16) || (x >= 8))) return;

 // check rotation, move pixel around if necessary
  switch (getRotation()) {
  case 0:
    if (x >= 8) {
      x -= 8;
      y += 8; 
    }
    break;
  case 1:
    y = 16 - y - 1;
    if(y >= 8) {
      y -= 8;
      x += 8;
    }
    _swap_int16_t(x, y);
    break;
  case 2:
    x = 16 - x - 1;
    y = 8 - y - 1;
    if (x >= 8) {
      x -= 8;
      y += 8; 
    }
    break;
  case 3:
    x = 8 - x - 1;
    if(y >= 8) {
      y -= 8;
      x += 8;
    }
    _swap_int16_t(x, y);
    break;
  }

  if (color) {
    displaybuffer[x] |= 1 << y;
  } else {
    displaybuffer[x] &= ~(1 << y);
  }
}


aitendo_KLED1608K33D_8x16matrix matrix = aitendo_KLED1608K33D_8x16matrix();

#define DR_UNRELATED 0
#define DR_STOP 1
#define DR_OVERRIDE 2
#define DR_NEW 10
#define DR_CONTINUE 11

typedef struct {
  const char *cmd;
  int (*drawfunc)(int, JsonVariant);
} Cmds;
Cmds *drawing;

void setup() {
  drawing = NULL;

  Serial.begin(115200);
  Serial.println("16x8 LED Matrix");

  matrix.begin(0x70);
  matrix.setBrightness(5);
  matrix.setRotation(0);
  matrix.clear();
  matrix.writeDisplay();
}

int sendResponse(int status, String msg) {
  StaticJsonDocument<100> response;
  
  response["status"] = status;
  response["msg"] = msg;

  serializeJson(response, Serial);
  Serial.println();
}

int cmdStatus(int stat, JsonVariant value) {
  sendResponse((drawing ? 102 : 100), (drawing ? "drawing" : "free time"));
  return(DR_UNRELATED);
}

int cmdTextscr(int stat, JsonVariant value) {
  static String sMsg;
  static int16_t l, x;

  if(stat == DR_NEW) {
    JsonObject obj = value.as<JsonObject>();

    String msg = obj["msg"];

    sMsg = msg;
    l = msg.length();
    x = 7;

    matrix.setTextSize(1);
    matrix.setTextWrap(false);
    matrix.setTextColor(LED_ON);
  }

  if(x >= -6*l) {
    matrix.clear();
    matrix.setCursor(x,0);
    matrix.print(sMsg);
    matrix.writeDisplay();
    delay(100);
    x--;
  } else {
    //sendResponse(200, "finish textscr msg:" + msg);
    return(DR_STOP);
  }

  if(stat == DR_NEW) {
    sendResponse(200, "textscr msg:" + sMsg);
    return(DR_OVERRIDE);
  }
  return(DR_UNRELATED);
}

Cmds cmds[] = {
  {"status", cmdStatus},
  {"textscr", cmdTextscr},
  {"", NULL},
};

StaticJsonDocument<200> request;
JsonVariant JVNULL = JsonVariant();

void loop() {
  if(Serial.available() > 0) {
    DeserializationError error = deserializeJson(request, Serial);
    if (error) {
      sendResponse(500, error.c_str());
      return;
    }

    JsonObject obj = request.as<JsonObject>();
    for (JsonPair p : obj) {
      String cmd = (String)p.key().c_str();
      int i;
      for(i = 0; cmds[i].cmd != ""; i++) {
        if((String)cmds[i].cmd == cmd) {
          int r = (*cmds[i].drawfunc)(DR_NEW, p.value());
          switch(r) {
            case DR_OVERRIDE:
              drawing = &cmds[i];
              break;
            case DR_STOP:
              drawing = NULL;
              break;
          }
          break;
        }
      }
      if(cmds[i].cmd == "") {
        sendResponse(404, "command not found:" + cmd);
      }
    }

  } else {
    if(drawing) {
      int r = drawing->drawfunc(DR_CONTINUE, JVNULL);
      switch(r) {
        case DR_STOP:
          drawing = NULL;
          break;
      }
    }
  }
}

Extrait ci-dessous.

test2.ino


#define DR_UNRELATED 0  //Renvoyé si le traitement cmdxxxx n'écrase pas les autres requêtes en cours d'exécution
#define DR_STOP 1       //Renvoyé lorsque le traitement de cmdxxxx est terminé
#define DR_OVERRIDE 2   //Renvoyé lorsque le traitement cmdxxxx écrase une autre demande en cours d'exécution
#define DR_NEW 10       //Passer à cmdxxxx lorsqu'une nouvelle demande arrive
#define DR_CONTINUE 11  //Passé lors de l'appel de cmdxxxx lors d'un traitement ultérieur

int cmdStatus(int stat, JsonVariant value) {
  ...
}

int cmdTextscr(int stat, JsonVariant value) {
  ...
}

typedef struct {
  const char *cmd;
  int (*drawfunc)(int, JsonVariant);
} Cmds;

Cmds *drawing;

Cmds cmds[] = {
  {"status", cmdStatus},
  {"textscr", cmdTextscr},
  {"", NULL},
};

void loop() {
  if(Serial.available() > 0) {
    ...
    for (JsonPair p : obj) {
      String cmd = (String)p.key().c_str(); //Stocker la chaîne de demande reçue dans cmd
      int i;
      for(i = 0; cmds[i].cmd != ""; i++) { //cmds qui correspondent à cmd[]Boucle pour trouver et appeler des membres
        if((String)cmds[i].cmd == cmd) {
          int r = (*cmds[i].drawfunc)(DR_NEW, p.value());
          switch(r) {
            case DR_OVERRIDE:
              drawing = &cmds[i]; //Souvenez-vous de la fonction que vous venez d'appeler car elle continuera à être traitée.
              break;
            case DR_STOP:
              drawing = NULL; //La fonction que je viens d'appeler est terminée, il n'y a donc pas de fonction pour continuer le traitement
              break;
          }
          break;
        }
      }
      ...
    }

  } else {
    if(drawing) { //S'il y a une fonction de demande en cours d'exécution, appelez cette fonction
      int r = drawing->drawfunc(DR_CONTINUE, JVNULL);
      ...
    }
  }
}
  1. Préparez la fonction d'affichage à LED cmdxxxx pour chaque commande de requête (actuellement seulement deux, "textcr" et "status"), et placez-la dans cmds [], qui est une liste de structures Cmds, avec le nom de la commande.
  2. Par exemple, si vous envoyez une requête "textcr" depuis Linux, cmdTextscr sera appelé depuis loop () avec stat = DR_NEW pour démarrer le défilement.
  3. cmdTextscr affiche le texte, attend 100 ms et renvoie DR_OVERRIDE ou DR_UNRELATED s'il n'a pas encore atteint la fin du défilement. Affichage par défilement Renvoie DR_STOP lorsqu'il est terminé jusqu'à la fin.
  4. Le côté loop () qui a reçu la réponse se souvient de drawing = & cmds [1] car drawing = NULL si DR_STOP et en cours d'exécution si DR_OVERRIDE.
  5. loop () répète ce qui précède dans une boucle for, et si dessin! = NULL, il appelle le traitement en cours d'exécution, et si une nouvelle requête arrive, il répète l'exécution.

Désormais, du côté Linux, vous n'avez pas à attendre après l'envoi d'une requête, et vous pouvez envoyer une requête à tout moment.

test2.py


import serial
import time
import datetime
import json

s = serial.Serial()
s.port = "/dev/ttyS4"
s.baudrate = 115200
s.timeout = 1
s.dtr = False   #Empêche arduino d'être réinitialisé lors de la connexion en série
s.open()
time.sleep(1)   #Attendez les sentiments

s.reset_input_buffer()  #Nettoyage du tampon de réception du port série

def request(data):
    print(datetime.datetime.now().strftime('%H:%M:%S'), "request:", data)
    s.write(json.dumps(data).encode())  # encode()Doit être une chaîne binaire dans
    while True: #Attendez que la réponse revienne
        msg = s.readline().decode()
        if(len(msg) > 0):
            print(datetime.datetime.now().strftime('%H:%M:%S' ), "response:", msg)
            break

request({"textscr" : {"msg":"Hello World !!!"}})
time.sleep(3)
request({"status" : {}})
time.sleep(1)
request({"textscr" : {"msg":"\\(^_^)/"}})
time.sleep(6)
request({"status" : {}})

test2.résultat d'exécution py


ubuntu:~$ python3 test.py
00:25:39 request: {'textscr': {'msg': 'Hello World !!!'}}
00:25:39 response: {"status":200,"msg":"textscr msg:Hello World !!!"}

00:25:42 request: {'status': {}}
00:25:42 response: {"status":102,"msg":"drawing"}

00:25:43 request: {'textscr': {'msg': '\\(^_^)/'}}
00:25:43 response: {"status":200,"msg":"textscr msg:\\(^_^)/"}

00:25:49 request: {'status': {}}
00:25:49 response: {"status":100,"msg":"free time"}

Le temps est ajouté avant la demande et la réponse pour une compréhension facile. Il renverra une réponse dès que vous commencerez le défilement du texte, et si vous envoyez une autre requête textcr tout en défilant encore, il exécutera la nouvelle requête. Parfait. fin.

Recommended Posts

Traitement asynchrone avec Arduino (traitement des demandes de traitement de Linux de manière asynchrone)
Traitement parallèle avec Parallel de scikit-learn
Bases du traitement d'images binarisées par Python
Dessin avec Matrix-Reinventor of Python Image Processing-
Exemple de traitement efficace des données avec PANDAS
Traitement d'image à partir de zéro avec python (5) Transformation de Fourier
Traitement d'image à partir de zéro avec python (4) Extraction de contour
Sauvegarde de QNAP vers Linux avec rsync
Apprenez Nim avec Python (dès le début de l'année).
Ouvrez la version Chrome de LINE à partir de la ligne de commande [Linux]
[Chapitre 5] Introduction à Python avec 100 coups de traitement du langage
[Chapitre 6] Introduction à scicit-learn avec 100 coups de traitement du langage
[Chapitre 3] Introduction à Python avec 100 coups de traitement du langage
[Chapitre 2] Introduction à Python avec 100 coups de traitement du langage
Traitement asynchrone de Python ~ Comprenez parfaitement async et attendez ~
Essayez de falsifier les demandes de l'iPhone avec Burp Suite
Apprenez le traitement / collouts asynchrones Python en comparant avec Node.js
[Bases de la science des données] Collecte de données depuis RSS avec python
Apprenez facilement 100 traitements linguistiques Knock 2020 avec "Google Colaboratory"
[Chapitre 4] Introduction à Python avec 100 coups de traitement du langage
Accès ODBC à SQL Server depuis Linux avec Python