ゆるく楽しむプログラミング

M5Stamp PICOで 1:n のTCP/IP通信

Wi-Fi アクセスポイントに設定したM5Stamp PICOに、複数(今回は2台)のM5Stamp PICOを接続してTCP/IP通信でメッセージを送信する実験を行います。
屋外に置いた気温・気圧センサーの情報を屋内で表示する事を想定していますが、初期実験なので、まずは情報の送信手順の確認のみです。
クライアント側のLEDの状態をサーバに送信して、サーバ側のLEDを点灯する「リモートLチカ」を試してみます。

ArduinoIDEの設定・スケッチの書き込み

M5Stamp PICOの使い方は各ページを参照
ArduinoIDEの設定
スケッチの書き込み

Internet通信

Internet通信はザックリ4階層のレイヤを通して行われます。(実際にはもう少し細分化される)
HTTPは1コネクト1リクエスト(1回の接続で、1回だけリクエストを送信、応答を受信したら切断)のルールなので
1対1で使用する場合には都合の良いプロトコルのように思えます。
サンプルもHTTP接続ばかりなのでHTTPを使わせたいのかな…
アプリケーション層  HTTPなど、TCP/IPプロトコルを使って構築されるアプリケーション
HTTPの通信手順はRFC(Request For Comments)で決められている。
RFCはIEEEやISOとは異なり、厳密な規格ではないので割といい加減だったりする。
ESP32ではHTTP(Hyper Text Transfer Protocol)を使うためのライブラリが用意されている。
HTTPを使わせたい雰囲気が・・・
TCP層  TCP(Transmission Control Protocol)はIP(Internet Protocol)を使って構築される。
誤り訂正や再送要求などを行う。
IP層  IPアドレスを使用したパケットの転送を行う
ネットワーク層 MACアドレスを使用して、有線や無線で接続された接続先とデータの送受信を行う 

アクセスポイント(通信サーバ)

M5StampをWiFiアクセスポイントに設定します。
M5Stackライブラリの WiFiAP.h で定義されている WiFi.softAP(ssid, password) を使用します。
SSIDとパスワードを指定してsoftAPをコールするだけでWiFi APポイントが開設出来ます。

※ <>を全角<>に置き換えています。コピペの際は注意してください。
#include <WiFi.h>
#include <WiFiAP.h>

const char *ssid = "STAMP-PICO";
const char *password = "STAMP-PICO";

void setup() {
  WiFi.softAP(ssid, password);
}

void loop() {
}

 このスケッチを動かすと アクセスポイント STAMP-PICO に接続できるようになります。
  アクセスポイントに接続できるだけ未だ通信は出来ません。
 ネットワーク層同士の通信が出来るようになった状態



WiFi.softAPIP() でIP層での通信に必要なIPアドレスを割り当てます。
通信ポートは80番(HTTP通信に割り当てられているポート)を使用します。
無指定だと何処に割り当てられるか不明で不便ですので、WiFi.softAPConfig()で明示的にIPアドレスを割り当てます。
アドレスとして指定できるのはプライベートに割り当てられているアドレスで、それ意外のアドレスを指定するとインターネット上のどこかのサーバに接続しに行ってしまうので注意してください。
192.168.1.xxなどが、ご家庭のLANで良く使用されるプライベートアドレスではないかと思います。

 プライベートアドレス
 10.0.0.0~10.255.255.255
 172.16.0.0~172.31.255.255
 192.168.0.0~192.168.255.255


※ <>を全角<>に置き換えています。コピペの際は注意してください。
#include <WiFi.h>
#include <WiFiAP.h>
#include <WiFiClient.h>

const char *ssid = "STAMP-PICO";
const char *password = "STAMP-PICO";

WiFiServer server(80);

void setup() {
  WiFi.softAP(ssid, password);
  IPAddress Ip(192, 168, 10, 10);     // Server IP を固定
  IPAddress NMask(255, 255, 255, 0);  // Server SubNetMask
  WiFi.softAPConfig(Ip, Ip, NMask);   // IP , Gateway , SubNetMask
  IPAddress myIP = WiFi.softAPIP();
  server.begin();
}

void loop() {
  WiFiClient client = server.available();   // listen for incoming clients
  if (client) {                             // if you get a client,
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        if (c == '\n') {                    // if the byte is a newline character
          if (currentLine.length() == 0) {
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();

            client.println("<html><head></head><body><font size=+4>HELLO!!<br> M5Stamp PICO</font></body></html>");
            break;
          } else {    // if you got a newline, then clear currentLine:
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    client.stop();
  }
}

 WiFiアクセスポイント STAMP-PICOに接続して
 IPアドレス:192.168.10.10 にWebブラウザでアクセスすると
 ←が表示されます。



複数接続

M5Stamp PICO同士でデータ通信を行うのにHTTPは大げさなのでCSVでデータを送信します。
HTTP通信ではないのでTerminalポート(23番ポート)を使用します。
※ポート番号はどこでも構わないですが、一応意味があるので。

毎回通信を切断するのも面倒なので、接続は維持したままにします。
WiFiClient client がクライアントソケットを包括しているクラスなので、コレはloop()内にあるとloop()を抜けた瞬間に接続が切れてします(Socketが解放される)ので、グローバル変数にして複数(今回は4個:内使用するのは2個)設定します。

LEDを2個付けて、クライアントからのコマンド LED1:ON、LED1:OFF、LED2:ON、LED2:OFFでON/OFFするようにします。
処理状態が分からないと困るので表示用にSSD1306(I2C接続)を付けておきます。


M5Stamp-WiFi-TCP-Server3.zip
Tiny4kOLEDの文字フォントを使用しています。

※ <>を全角<>に置き換えています。コピペの際は注意してください。
// M5Stamp PICO TCP/IP Termianl Server 1
// 1対n接続 Terminal

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiAP.h>

//#define OLDMONITER

#ifdef OLDMONITER
#include "OLED.h"
// OLED用
char str[80] ;
OLED oled ;
#endif

// Server側:SSIDとパスワード
#define SERVERPORT 23
const char* ssid     = "STAMP-PICO";
const char* password = "STAMP-PICO";

WiFiServer server(SERVERPORT,4);  // Terminal Port:最大接続数4

#define ListenMax 4
#define BuffMaxLen 80
WiFiClient client[ListenMax] ;
int readBuffLen[ListenMax] ;
char readBuff[ListenMax][BuffMaxLen] ;

#define LED1 26
#define LED2 18
int led1Stat ;
int led2Stat ;
bool flagChangeLedStat ;

// コマンド解析
void comanExec(char *commanLine) {
  char command[BuffMaxLen] ;
  int commandNo = 0 ;
  int commandNo2 = 0 ;
  int commandLen = 0 ;
  int readLen = 0 ;
  memset(command,0,BuffMaxLen) ;
  while(commanLine[readLen] != '\0') {
    if (commanLine[readLen] == ',') {
      readLen ++ ;
      commandLen = 0 ;
      memset(command,0,BuffMaxLen) ;
      continue ;
    }
    if (commanLine[readLen] <= ' ') {
      readLen ++ ;
      continue ;
    }
    if (commanLine[readLen] == ':') {
      readLen ++ ;
      commandLen = 0 ;
      break ;
    }
    command[commandLen] = commanLine[readLen] ;
    commandLen ++ ; readLen ++ ;
  }

#ifdef OLDMONITER
  sprintf(str,"CMD:%s",command) ; oled.Print(str) ;
#else
  Serial.print("Command1:") ;
  Serial.println(command) ;
#endif

  if (strcmp(command,"LED1") == 0) { commandNo = 1; }
  else if (strcmp(command,"LED2") == 0) { commandNo = 2; }

  commandLen = 0 ;
  memset(command,0,BuffMaxLen) ;
  while(commanLine[readLen] != '\0') {
    if (commanLine[readLen] == ',') {
      readLen ++ ;
      commandLen = 0 ;
      memset(command,0,BuffMaxLen) ;
      continue ;
    }
    if (commanLine[readLen] <= ' ') {
      readLen ++ ;
      continue ;
    }
    if (commanLine[readLen] == ':') {
      readLen ++ ;
      commandLen = 0 ;
      break ;
    }
    command[commandLen] = commanLine[readLen] ;
    commandLen ++ ; readLen ++ ;
  }

  if (strcmp(command,"ON") == 0) { commandNo2 = 1; }
  else if (strcmp(command,"OFF") == 0) { commandNo2 = 2; }

#ifdef OLDMONITER
  sprintf(str,":%s",command) ; oled.Println(str) ;
#else
  Serial.print("Command2:") ;
  Serial.println(command) ;
  Serial.print("commandNo2:") ;
  Serial.println(commandNo2) ;
#endif

  for (int i=0;i<BuffMaxLen;i++) {
    if (command[i] == '\0') break ;
  }
  switch (commandNo) {
  case 1: 
    flagChangeLedStat = true ;
    if (strcmp(command,"ON") == 0) {
      led1Stat = HIGH ;
    } else {
      led1Stat = LOW ;
    }
    digitalWrite(LED1,led1Stat) ;
    break ;
  case 2: 
    flagChangeLedStat = true ;
    if (strcmp(command,"ON") == 0) {
      led2Stat = HIGH ;
    } else {
      led2Stat = LOW ;
    }
    digitalWrite(LED2,led2Stat) ;
    break ;
  }
  
}

// セットアップ
void setup() {
  // ---- LED設定
  led1Stat = LOW ;
  led2Stat = LOW ;
  flagChangeLedStat = false ;
  pinMode(LED1,OUTPUT) ;
  pinMode(LED2,OUTPUT) ;
  digitalWrite(LED1,led1Stat) ;
  digitalWrite(LED2,led2Stat) ;

  // ----- 受信バッファ初期化
  for (int i=0;i<ListenMax;i++) {
    readBuffLen[i] = 0 ;
    readBuff[i][0] = '\0' ;
  }

  // ----- Wi-Fi AP設定
  WiFi.softAP(ssid, password);  // Server APを設定
  IPAddress Ip(192, 168, 10, 10);     // Server IP を固定
  IPAddress NMask(255, 255, 255, 0);  // Server SubNetMask
  WiFi.softAPConfig(Ip, Ip, NMask);   // IP , Gateway , SubNetMask
  IPAddress myIP = WiFi.softAPIP();   // APを開設
  server.begin(); // Server Soket Listen開始

#ifdef OLDMONITER
  // ---- OLED
  oled.init();
  sprintf(str,"WIFI ACCESS POINT") ; oled.Println(str) ;
  sprintf(str,"Please connect:%s",ssid) ; oled.Println(str) ;
  sprintf(str,"Then access to:%s",myIP.toString()) ; oled.Println(str) ;
#else
  Serial.begin(9600);
  Serial.println("WIFI ACCESS POINT");  //print string.
  Serial.printf("Please connect:%s \nThen access to:",ssid);
  Serial.println(myIP);
#endif
}

// 処理ループ
void loop() {
  bool flagSendLedStat = flagChangeLedStat ;
  flagChangeLedStat = false ;
  for (int i=0;i<ListenMax;i++) {
    if (client[i].connected()) {
      // 接続済チャンネルの処理
      if (client[i].available()) {  // if there's bytes to read from the client.
        char c = client[i].read();  // store the read a byte.
        // client[i].print(c) ;  // ECHO BACK
        if (c == '\n') {
          // コマンド受信
          comanExec(readBuff[i]) ;

          readBuffLen[i] = 0 ;
          readBuff[i][readBuffLen[i]] = '\0' ;
        } else {
          readBuff[i][readBuffLen[i]] = c ;
          if (readBuffLen[i] < BuffMaxLen - 1) {
            readBuffLen[i] ++ ;
          }
          readBuff[i][readBuffLen[i]] = '\0' ;
        }
      }
      if (flagSendLedStat) {
        client[i].print("LED1-STAT:") ;
        if (led1Stat == HIGH) {
          client[i].println("ON") ;
        } else {
          client[i].println("OFF") ;
        }
        client[i].print("LED2-STAT:") ;
        if (led2Stat == HIGH) {
          client[i].println("ON") ;
        } else {
          client[i].println("OFF") ;
        }
      }
    } else {
      // 未接続チャンネルの処理:接続待ち
      client[i] = server.available();       // listen for incoming clients.
      if (client[i]) {                      // if you get a client.
        while (!client[i].connected());
        client[i].println("Hello!") ;

#ifdef OLDMONITER
        sprintf(str,"New Client:%s",client[i].remoteIP().toString()) ; oled.Println(str) ;
#else
        Serial.print("New Client:");
        Serial.println(client[i].remoteIP());
#endif
      }
    }
  }
}

クライアントはタイマーでLチカするタイプと、ボタンを押す毎にON/OFFするタイプの2種類
  M5Stamp-WiFi-TCP-Client1.zip   M5Stamp-WiFi-TCP-Client2.zip
   

※クライアントはどちらのタイプでも大差ないので、リストはタイマーでLチカするタイプです。
※ <>を全角<>に置き換えています。コピペの際は注意してください。
// WiFi Remote Lチカ・クライアント

#include <WiFi.h>

//#define OLDMONITER

#ifdef OLDMONITER
#include "OLED.h"
// OLED用
char str[80] ;
OLED oled ;
#endif

#define SERVERPORT 23
const char* ssid     = "STAMP-PICO";
const char* password = "STAMP-PICO";
const char* host = "192.168.10.10";

#define LED_PIN 26
#define ID_PIN 36
int ledVal = HIGH ;
int clientID = 0 ;

#define BuffMaxLen 80
int readBuffLen = 0 ;
char readBuff[BuffMaxLen] ;

WiFiClient client;

void setup()
{
  delay(10);

  pinMode(LED_PIN,OUTPUT) ;
  digitalWrite(LED_PIN,LOW) ;
  pinMode(ID_PIN,INPUT) ;
  if (digitalRead(ID_PIN) == LOW) {
    clientID = 1 ;
  } else {
    clientID = 2 ;
  }
    
#ifdef OLDMONITER
  // ---- OLED
  oled.init();
  sprintf(str,"Connecting to:%s",ssid) ; oled.Println(str) ;
#else
  Serial.begin(9600);
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
#endif

  // We start by connecting to a WiFi network
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
#ifdef OLDMONITER
    oled.Print(".");
#else
    Serial.print(".");
#endif
  }

#ifdef OLDMONITER
  oled.Println("WiFi connected");
  oled.Println("IP address: ");
  sprintf(str,"%s",WiFi.localIP().toString()) ; 
  oled.Println(str);
#else
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
#endif
}

void loop()
{
  if (WiFi.status() != WL_CONNECTED) {
    // WiFiが切れてたら再接続
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
#ifdef OLDMONITER
      oled.Print(".");
#else
      Serial.print(".");
#endif
    }
#ifdef OLDMONITER
    oled.Println("connecting to ");
    sprintf(str,"%s",host) ; 
    oled.Println(str);
    oled.Println("IP address: ");
    sprintf(str,"%s",WiFi.localIP().toString()) ; 
    oled.Println(str);
#else
    Serial.print("connecting to ");
    Serial.println(host);
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
#endif
  }

  // Use WiFiClient class to create TCP connections
  if (!client.connected( ) ) {
    if (!client.connect(host, SERVERPORT)) {
#ifdef OLDMONITER
      oled.Println("connection failed");
#else
      Serial.println("connection failed");
#endif
      return;
    }
  }

  // This will send the request to the server
  ledVal = (ledVal == HIGH) ? LOW : HIGH ;
  digitalWrite(LED_PIN,ledVal) ;

  sprintf(str,"LED%d:%s\n",clientID,((ledVal==HIGH)?"ON":"OFF")) ;
  client.println(str);
#ifdef OLDMONITER
  oled.Print("SEND ");
  oled.Println(str);
#else
  Serial.print("SEND ");
  Serial.println(str);
#endif

  for (int w = 0 ; w < 6 ; w++) {
    delay(500);
    // Read all the lines of the reply from server and print them to Serial
    while(client.available()) {
      char c = client.read();  // store the read a byte.
      if (c == '\n' || readBuffLen+1 >= BuffMaxLen) {
        readBuff[readBuffLen] = '\0' ;
        readBuffLen = 0 ;
#ifdef OLDMONITER
      oled.Println(readBuff);
#else
      Serial.println(readBuff);
#endif
      } else {
        if (c >= ' ') {
          readBuff[readBuffLen] = c ;
          readBuffLen ++ ;
        }
      }
    }
  }
}

見て分かり易いLチカで試しましたが、LEDの状態の代わりにセンサーの値を送信すれば、無線接続センサーが作れます。


解説動画