💡

Arduino Uno R4 WiFiを使ってLAN内から文章を表示してみた

2023/12/15に公開

<< 前:誰でも「それっぽく」使えるようになるクラス入門 by kokastar
>> 後:某電子公告的なTelnetサーバーをC言語で作る by toma09to

木更津高専 Advent Calendar 202315日目担当のこかすた〜です。2日連続の登場です。

はじめに

今日は、先月技適を取得し国内販売が始まったArduino UNO R4 WiFiで遊びます。Arduinoは過去に学校の授業で使用しただけで、個人で購入・使用するのは初めてです。

今回はまずArduino UNO R4 WiFIを触ってみて色々して、その後本題の開発について触れたいと思います。

Arduino UNO R4 WiFiについて

今回使用するのは、Arduino UNOシリーズの最新機である、Arduino UNO R4 WiFiです。僕は11月の上旬にSWITCH SCIENCEで購入しました。¥4950です。
過去のUNOシリーズと比べて、R4 WiFiには以下の特徴があります。

  • ESP32-S3ワイヤレスモジュールを搭載し、802.11 b/g/nに対応するようになった
  • ボード上にマトリックスパネルが搭載された(色は赤色のみ)
  • 接続がUSB-Cになった
  • 入力電圧が24Vまで対応に
  • マイコンがルネサス製に
  • メモリやクロックが強化され処理性能が向上

注文したら、1週間ほどで届きました。こんな感じの箱の中に本体が入ってました。(届いたときは、この箱が更に配送用の箱に入ってました)

全体はこんな感じになってます。
ボードの右下にある白い長方形のエリアがマトリックスパネルとなっています。  
また、左下の方にある銀色のチップがESP32-S3です。ここでワイヤレス通信を行います。

側面を見ると、USB-Cになってるのがわかると思います。

起動してみる

USBケーブルをPCとArduinoにそれぞれつなげると、勝手に起動します。
最初はGoogle Pixel4a購入時に付属してきたUSBケーブルで接続しようとしたのですが、うまく行かなかったのでAtoCケーブルにつないだらうまく行きました。

ちょっと画質が荒いですが、工場出荷状態で起動すると以下のようにマトリックスパネルに表示されます。
https://youtu.be/FTTSpupEba0

はじめに環境構築

前提として、僕は以下の環境を使用しています。だいぶ特殊な環境なのであまり参考にならないかもしれないです。

ASUS TUF GAMING A15
OS : Arch Linux
WM : i3wm

まずは、Arduino IDEをAURからインストールします。今回は2.x系にしてみます。

yay -S arduino-ide-bin

インストールが終わったらarduino-ideを起動し、ArduinoをPCと接続してIDE上部から「Arduino UNO R4 WiFi」を選択します。すると、ドライバ的なやつを入れるかどうかを右下で聞かれるので、Yesを押して待ちます。
ArduinoIDEはこんな感じです。

学校で使用しているのは1.x系ですが、左側にサイドバーができていたりなど、割と違う箇所があります。
インストールはしましたが、これだけではまだ使えません。IDE自体は使えるのですが、Arduinoに書き込みを行うときにエラーが発生してしまいます。そこで、

sudo chmod a+rw /dev/ttyACM0

を実行します。環境によっては/dev/以降が異なるかもしれないので、その場合は適宜書き換えてください。これで書き込みができるようになったので、環境構築も完了です。
(ただし、僕の環境では一度接続を外すと消えてしまうので、毎回実行しています。解決策は現在模索中です。)

LEDマトリックスパネルの制御

Arduinoで最初にやることと言ったらLチカ... はもう過去の話です。Arduinoは新時代に突入しました。せっかくマトリックスパネルがあるので、それを制御してみましょう。

表示するものを作成する

公式からLED MATRIX EDITORというツールが提供されていますので、今回はそこで絵を書いて表示させてみます。
僕はこのような絵を書きました。どこか既視感のある鳥です。

その後、右上のコードボタンを押して名前をつけてOKを押すと、データがダウンロードされます。先程の鳥の場合は以下のようになりました。

const uint32_t animation[][4] = {
	{
		0x41c63e7f,
		0xc3fc7fc3,
		0xf81f03c0,
		66
	}
};

これがマトリックスパネルのデータとなります。これをプログラムに組み込むことで、鳥を表示させます。

表示するプログラムを作成する

Arduino_LED_Matrix.hをincludeすることで、マトリックスパネルの制御を簡単に制御できます。

もしもarduino_led_matrix.h: そのようなファイルやディレクトリはありませんとなる場合は、 「ツール」→「ボード:(何かしらのボード名)」→ 「Arduino Renesas UNO R4 Boards」→「Arduino UNO R4 WiFi」を選択することで解決できます。

その上で、ダウンロードしてきた配列の0番目をmatrix.loadFrameすることで、表示させることができます。
アニメーションを作成することもできますが、これはまた後で触れます。

#include "Arduino_LED_Matrix.h"

ArduinoLEDMatrix matrix;

void setup() {
  Serial.begin(115200);
  matrix.begin();
}

//ここにはダウンロードしてきた配列の宣言を貼り付ける
const uint32_t animation[][4] = {
  {
    0x41c63e7f,
    0xc3fc7fc3,
    0xf81f03c0,
    66
  }
};

void loop(){
    matrix.loadFrame(animation[0]);
}

そして、左上のチェックマークからコンパイルし、その隣の書き込みボタンを押すと書き込みができます。

色が我々のよく知る「アレ」と違いますが、残念ながら赤色のみとなっています。

マトリックスパネルでアニメーション

次は、アニメーションを流してみます。
原理は特に難しいものではなく、先程のように書いた画像を連続で再生するだけです。これも公式が先程のツールで対応しているので、それを使ってみます。

表示するものを作成する

とりあえず正弦波でも流してみましょう。まぁ、正弦波をきれいにかけるほどドットは多くないんですけども...
画面下のプラスボタンを押すことでコマを追加できます。

エディタ中央上部にある再生ボタンを押すことでアニメーションのプレビューもできます。
そして、先程同様コードをダウンロードします。

const uint32_t animation[][4] = {
	{
		0x20050,
		0x18820440,
		0x28010000,
		66
	},
	{
		0x10028,
		0x4418220,
		0x14008000,
		66
	},
	{
		0x8014,
		0x2204118,
		0xa004000,
		66
	},
	{
		0x400a,
		0x1102084,
		0x5802000,
		66
	},
	{
		0x2005,
		0x88104a,
		0x2401000,
		66
	},
	{
		0x1002,
		0x80448825,
		0x1200000,
		66
	},
	{
		0x801,
		0x48224412,
		0x80100000,
		66
	},
	{
		0x480,
		0xa4112201,
		0x40080000,
		66
	},
	{
		0x80240,
		0x52081100,
		0xa0040000,
		66
	},
	{
		0x401a0,
		0x21040880,
		0x50020000,
		66
	}
};

こいつは、配列1つあたりが1コマを表しています。
1コマのデータには、0~2に表示するデータが16進数で入り、3にはフレームの表示時間がmsで記録されています。
フレームの表示時間はエディタ下部にあるフレーム一覧のところに数字があるので、そこから編集が可能です。あとは直接いじるのも手です。

表示するプログラムを作成する

先程のコード似てますが少しだけ違います。

#include "Arduino_LED_Matrix.h"

//アニメーションのコマ数。
#define SIZ 10

ArduinoLEDMatrix matrix;

void setup() {
  Serial.begin(115200);
  matrix.begin();
}

//アニメーションのデータをここで宣言する。長いので省略。
const uint32_t animation[][4]; 


void loop(){
  for(int i=0;i<SIZ;i++){
    matrix.loadFrame(animation[i]); //アニメーションデータのうちi番目を表示
    delay(animation[i][3]); //アニメーションデータのi番目から待機時間を読み取り待機
  }
}

フレームの長さを記録する定数を用意しておき、配列の戦闘から順にfor文で表示→待機 をループさせます。これによりアニメーションが表示できます。
書き込んで見ると、以下のように正弦波(というよりジグザグ?)が表示されました。
https://youtu.be/rDUVonvl-as

同一LAN内からマトリックスパネルの鳥を制御する

さて、マトリックスパネルを使って遊んだので、次は製品名にもなっている目玉機能である、WiFiを使ってみます。
公式が様々なサンプルプログラムを公開してくれているので、それを改造して「簡易Webサーバーを立ち上げ、そこから鳥のロゴのON/OFFを行う」プログラムにしてみたいと思います。

Webサーバーを立ち上げてみる

まずは、サンプルプログラムをそのまま用いてみます。前提として、SSIDとパスワードを記載したarduino_secrets.hを同一ディレクトリに置く必要があります。

//arduino_secrets.h header file
#define SECRET_SSID "yournetwork"
#define SECRET_PASS "yourpassword"

別ファイルに記述したほうが、間違ってGitHubにプッシュしてしまうなどの事故も防ぎやすいです。

用意したら、次はメインファイルにサンプルコードをそのままコピーして、コンパイル→書き込みを行います。IDE上部の「ツール」→「シリアルモニタ」を使うことで、Arduinoの現在の状態を確認できます。
しばらくすると、指定したIPアドレス(僕の場合はhttp://192.168.179.30/)に接続するようシリアルモニタに表示されるので、Arduinoと同じネットワーク内にある機器からアクセスすると、画像のようなサイトが開くと思います。

ここでhereをクリックすると、それに合わせてArduino上のLEDが光ったり消えたりします。すごい。

鳥の表示をON/OFFできるように改造してみる

では、ここからはこれを改造して、鳥の表示をON/OFFできるようにします。
まずは、ライブラリのインクルードと、鳥の表示されている状態とされていない状態の2つを格納した配列の宣言、それにmatrix変数の宣言を行います。

#include "Arduino_LED_Matrix.h"

//中略(もとからあったプログラムが間にあるため)

ArduinoLEDMatrix matrix;
const uint32_t animation[][4] = {
	{
		0x41c63e7f,
		0xc3fc7fc3,
		0xf81f03c0,
		66
	},
  {
		0x00000000,
		0x00000000,
		0x00000000,
		66
	}
};

また、setup関数内ではマトリックスパネルを初期化するために以下を追加します。

matrix.begin();

これで、マトリックスパネルの処理を変更できました。続けてWebサイトとその処理に手を加えていきます。
まずは、表示を変えます。「Click here to view/hide the bird logo」に変えてみました。

// the content of the HTTP response follows the header:
client.print("<p style=\"font-size:7vw;\">Click <a href=\"/H\">here</a> to view the bird logo<br></p>");
client.print("<p style=\"font-size:7vw;\">Click <a href=\"/L\">here</a> to hide the bird logo<br></p>");

更にその下には、クリックしたときの動作が記述されています。Hのときに見せて、Lのときに隠すので、以下のようにmatrix.loadFrame()を追加してみました。

// Check to see if the client request was "GET /H" or "GET /L":
if (currentLine.endsWith("GET /H")) {
  digitalWrite(LED_BUILTIN, HIGH);               // GET /H turns the LED on
  matrix.loadFrame(animation[0]);
}
if (currentLine.endsWith("GET /L")) {
  digitalWrite(LED_BUILTIN, LOW);                // GET /L turns the LED off
  matrix.loadFrame(animation[1]);
}

これで完成です。完全版のソースコードはここにあります。
後は先程同様に書き込みを行い、同一LAN内からhttp://192.168.179.30/にアクセスすると、Webサイトが表示されました。

https://youtu.be/R_NosD4q59I

画面下の方においてあるArduinoのマトリックスパネルの表示が切り替わっていることが確認できると思います。

同一LAN内からマトリックスパネルに文章を表示する

さて、ここから本題です。コードを記載しながら進めますが、説明しやすい順番にしている都合上、実際のコードに書かれている順番とは異なる場合もあります。
完成したコードはGitHubから確認できます。
この章では、ベースとして先程使用したサンプルコードを使っています。

文章表示システムの構築

ライブラリの導入

まずは、文字の表示です。先程鳥を表示していましたが、その代わりに文字を表示できるようにしてみます。
非常に参考になる記事を見つけたので、ブログのとおりに導入を進めます。僕の環境ではArduinoのライブラリフォルダは~/.arduino15/libraries/にあったので、そこにmisakiUTF16を入れ、インクルードを行いました。

#include <misakiUTF16.h>

byte buf[40][8];         //フォントデータ格納用。一つめの要素数が文字数(多めでいい)
byte matrix_buff[2000];  //表示用90度回転ピクセルデータ。文字数×8ぶん必要?(多めでいい)

//スクロール時間
#define SCROLL_TIME 100  //ミリ秒
unsigned long tm = 0;

char *str; //この後説明するように文字列を途中で変更するので、初期化を行っていない
int idx = 0, max_idx;
bool wifiConect = false;

また、文字列の表示部分に関しては関数化しました。そのほうが全体としてスッキリするためです。

void printTxt() {
  //スクロール時間が経過したらスライドさせる。末端に到達したら始めに戻る
  if (tm + SCROLL_TIME <= millis()) {
    if (idx < max_idx)
      idx++;
    else {
      idx = 0;
    }

    //経過時間を再計測
    tm = millis();
  }

  //12x8LED View
  for (int ic1 = 0; ic1 < 8; ic1++) {
    for (int ic2 = 0; ic2 < 12; ic2++) {
      turnLed(ic1 * 12 + ic2, matrix_buff[idx + ic2] >> (7 - ic1) & 0x01);
      delayMicroseconds(50);  //これを入れないとLEDがちらつく
    }
  }
}

更に、setup()関数にも以下の処理を追加しています。

matrix.begin();

文字列セット関数

ここからがブロクから改良した点となります。今回は同一LAN内の端末から文章を受け取るため、途中で文章を変更できる必要があります。そのため、以下のように関数化してみました。

void setTxt(String txt) {
  txt = " " + txt;
  free(str);
  char *pt = (char *)malloc(1000);
  str = pt;
  txt.toCharArray(str, 100);

  for (int i = 0; i < 2000; i++) {
    matrix_buff[i] = (byte)0;
  }

  char *ptr = str;
  max_idx = 10;
  byte line = 0;

  int n = 0;
  while (*ptr) {
    ptr = getFontData(&buf[n++][0], ptr);  // フォントデータの取得
  }
  max_idx = 0;
  for (byte j = 0; j < n; j++) {      //文字
    for (byte k = 0; k < 8; k++) {    //横
      for (byte i = 0; i < 8; i++) {  //縦
        line = bitWrite(line, 7 - i, bitRead(buf[j][i], 7 - k));
      }
      matrix_buff[max_idx] = line;
      max_idx++;
    }
  }
}

String型で引数として表示する文字列を受け取った後先頭にスペースを入れています。これは、ループが完了して先頭に戻ったときに、文字が突然表示しないようにするためです。

その後、既存の文字列をメモリから開放し、新たにmallocで確保しています。ここの確保容量も計算できたら良かったのですが、うまく行かなかったのでとりあえず定数にしています。今後改善していきます。
そして、toCharArray関数を用いてString型からchar[]型へと変換し、matrix_buffを初期化すれば、後はブログと同じ処理になります。スクロール用の変数であるmax_idxも変化するので、それに関してもmatrix_buffに書き込みながら更新を行っています。

WiFi接続画面の実装

WiFi接続中でも何らかの動きはほしいものです。実際、Webサイトは読み込みが長い場合は、アニメーションなどの動きをつけることでユーザーの待機率が上がるそうです。そこで、WiFiへの接続を試行するたびに四角がぐるりと周り、完了したらその四角が小さくなっていき、「オンラインです」と表示させるようにしてみました。

// attempt to connect to WiFi network:
while (status != WL_CONNECTED) {
  for (int j = 0; j < waitingFrame; j++) {
    matrix.loadFrame(waiting[j]);
    delay(waiting[j][3]);
  }
  Serial.print("Attempting to connect to Network named: ");
  Serial.println(ssid);  // print the network name (SSID);

  // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
  status = WiFi.begin(ssid, pass);
}
server.begin();     // start the web server on port 80
printWifiStatus();  // you're connected now, so print out the status

for (int j = 0; j < doneFrame; j++) {
  matrix.loadFrame(done[j]);  //アニメーションデータのうちi番目を表示
  delay(done[j][3]);          //アニメーションデータのi番目から待機時間を読み取り待機
}
setTxt("オンラインです");

setup()関数内のWiFiと接続を確立する部分にいくつか手を加えました。
まずは、statusWL_CONNECTEDでない間、つまり接続できていない間は接続を試行するたびにwaitingというアニメーションを作成します。
更に、接続確立後はdoneというアニメーションを再生し、その後文字列を「オンラインです」にセットするような処理にしました。

アニメーションの配列については長いので実際のソースコードを確認してみてください。アニメーションは以下の動画のようになっています。
https://youtu.be/Cifq9AoDUQs

文字列の受け取りと表示

web→Arduino

次は、文字列の受取です。このサイトを参考にさせていただきました。
まずは、loop()関数内のclient.println()達に手を加えて、サイトを変えていきます。

// the content of the HTTP response follows the header:
client.print("<CENTER><form method=get>Enter text to be displayed<input type=text size=10 name='txt'> <input type=submit value=submit></form></CENTER></body></html><br>");

テキストボックスとかんたんなラベルをつけました。見た目はこんな感じになります。

次にこのテキストボックスから送信されたテキストをArduino側で受け取る方法ですが、入力内容がclient.readStringUntil('\r')txt=に続く形で記録されます。そのため、以下のようにして受け取ることができます。

String readStr = client.readStringUntil('\r');
if (readStr.indexOf("txt=") >= 4) {
  readStr = readStr.substring(readStr.indexOf("txt=") + 4);
  if (readStr.indexOf(" ") != -1) {
    readStr = readStr.substring(0, readStr.indexOf(" "));
  }
}

まずは変数に代入した上で、indexOfを使ってtxt=があるかどうかを確認します。存在するのであればtxt=とそれより前は情報に含まれないのでsubstringでカットします。更に、文字列の後ろに情報がつくこともあるので、その場合もindexOfsubstringを用いてカットします。
これで、文字列を受け取れます。

文字列のデコード

何ということでしょう。この文字列の全角文字はなんと、元の文章からUTF-16へとエンコードされた上で、更にURLエンコードされているではありませんか。ということで、以下のようにしてデコードします。

readStr = url_decode(readStr);
readStr = ncr_decode(readStr);

まずはURLデコードをした上で、NCR(数値実態参照)のデコードを行います。
その関数についてですが、URLデコードに関しては有用なブログを見つけたので、そのソースコードを参考に少し改良してString型に対応させました。

static const char table[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
char decode_hex_to_char(const char *c) {
  // XXX: [0-9a-zA-Z]以外の全ての文字は、0として扱われる (e.g. "%@X" => 0)
  return (table[static_cast<unsigned char>(c[1])] << 4) + table[static_cast<unsigned char>(c[2])];
}

String url_decode(String str) {
  String dist;
  for (int i = 0; str[i] != '\0'; i++) {
    switch (str[i]) {
      case '%':
        dist += decode_hex_to_char(&str[i]);
        i += 2;
        break;  // XXX: 末尾に'%'が来る不正な文字列が渡された場合の挙動は未定義(ex. "abc%")
      case '+': dist += ' '; break;
      default: dist += str[i]; break;
    }
  }
  return dist;
}

しかし、NCRのデコードに関しては見つかりませんでした。そこで、Unicode対応 JIS X 0208 文字コード表を片手に変換する関数を実装しました。今回変換に対応させるのは、ほとんどのブラウザ等で対応されている記号4種<>%"とひらがなカタカナ、及びいくつかの記号です。

String ncr_decode(String str) {
  String result;
  for (int i = 0; str[i] != '\0'; i++) {
    if (str[i] == '&' && str[i + 1] == '#') {
      int num = 0, j = i + 2;
      while (str[j] >= '0' && str[j] <= '9') {
        num = num * 10 + (int)(str[j] - '0');
        j++;
      }
      if (j - i - 2 == 3) {
        if (num == 60) {
          result += '<';
          i += 5;
          continue;
        } else if (num == 62) {
          result += '>';
          i += 5;
          continue;
        } else if (num == 38) {
          result += '&';
          i += 5;
          continue;
        } else if (num == 34) {
          result += '"';
          i += 5;
          continue;
        }
      } else if (j - i - 2 == 5) {
        String hiragana[] = { "ぁ", "あ", "ぃ", "い", "ぅ", "う", "ぇ", "え", "ぉ", "お", "か", "が", "き", "ぎ", "く", "ぐ", "け", "げ", "こ", "ご", "さ", "ざ", "し", "じ", "す", "ず", "せ", "ぜ", "そ", "ぞ", "た", "だ", "ち", "ぢ", "っ", "つ", "ず", "て", "で", "と", "ど", "な", "に", "ぬ", "ね", "の", "は", "ば", "ぱ", "ひ", "び", "ぴ", "ふ", "ぶ", "ぷ", "へ", "べ", "ぺ", "ほ", "ぼ", "ぽ", "ま", "み", "む", "め", "も", "ゃ", "や", "ゅ", "ゆ", "ょ", "よ", "ら", "り", "る", "れ", "ろ", "ゎ", "わ", "ゐ", "ゑ", "を", "ん" };
        String katakana[] = { "ァ", "ア", "ィ", "イ", "ゥ", "ウ", "ェ", "エ", "ォ", "オ", "カ", "ガ", "キ", "ギ", "ク", "グ", "ケ", "ゲ", "コ", "ゴ", "サ", "ザ", "シ", "ジ", "ス", "ズ", "セ", "ゼ", "ソ", "ゾ", "タ", "ダ", "チ", "ヂ", "ッ", "ツ", "ヅ", "テ", "テ", "ト", "ド", "ナ", "ニ", "ヌ", "ネ", "ノ", "ハ", "バ", "パ", "ヒ", "ビ", "ピ", "フ", "ブ", "プ", "ヘ", "ベ", "ペ", "ホ", "ボ", "ポ", "マ", "ミ", "ム", "メ", "モ", "ャ", "ヤ", "ュ", "ユ", "ョ", "ヨ", "ラ", "ル", "ッ", "レ", "ロ", "ヮ", "ワ", "ヰ", "ヱ", "ヲ", "ン", "ヴ", "ヵ", "ヶ" };
        if (num == 12540) {
          result += "ー";
          i += 7;
          continue;
        } else if (num == 12316) {
          result += "〜";
          i += 7;
          continue;
        } else if (num == 12540) {
          result += "、";
          i += 7;
          continue;
        } else if (num == 12541) {
          result += "。";
          i += 7;
          continue;
        } else if (num >= 12354 && num <= 12435) {
          result += hiragana[num - 12353];
          i += 7;
          continue;
        } else if (num >= 12448 && num <= 12534) {
          result += katakana[num - 12449];
          i += 7;
          continue;
        }
      }
    }
    result += str[i];
  }
  return result;
}

仕組みとしては、文字列を先頭で見ていき、有効な文字(&#から始まるもの)以外はすべてresult変数の末尾に加えます。

&#から始まる箇所であれば、whileで数字が続く限り10倍してその数を足す を繰り返して、&#の後ろにある数字を求めます(なお、String型の最後には終端文字がありそこでループが必ず終わるので、範囲外アクセスは起こりません)。
その後、その数字の桁数が3桁であれば、&#060&#062&#038&#034のどれかであった場合はその記号を末尾に加えて、先へ進みます(5文字先まで探索済みとなるので、i+=5をします)。
5桁であれば、&#12316&#12540&#12541のどれかの場合それらの記号を追加し、&#12354&#12435の場合はひらがなを入れた配列の中から該当の箇所を追加し、&#12448&#12534の場合はカタカナを入れた配列の中から該当の箇所を追加します(追加を行った場合は同様にiに7を足します)。
どれにも該当しなかった場合はアルファベットや記号だと判断し、その文字をそのまま追加します。

文字の表示

デコードできたら、残りはそれを表示するだけです。

String readStr = client.readStringUntil('\r');
if (readStr.indexOf("txt=") >= 4) {
  readStr = readStr.substring(readStr.indexOf("txt=") + 4);
  if (readStr.indexOf(" ") != -1) {
    readStr = readStr.substring(0, readStr.indexOf(" "));
  }
  readStr = url_decode(readStr);
  readStr = ncr_decode(readStr);
  idx = 0;
  setTxt(readStr);
  Serial.println("文字列が更新されました");
  Serial.println(readStr);
}

デコードを行ったら、idxを0にしてテキストのスクロールを一番最初に戻し、文字列をセットします。これで、表示する文字を設定できます。
ついでに、シリアルモニタから表示される文字を確認できるようにログを残すようにしてみました。

マトリックスパネルへの表示

最後に、先ほど作成したprintTxt()関数を配置させます。

for (int i = 0; i < 50; i++) printTxt();
WiFiClient client = server.available();  // listen for incoming clients
for (int i = 0; i < 50; i++) printTxt();

まずは、loop()関数の先頭にあるWebサイトからの情報を受け取るための行の前後に50回ずつ入れました。1回でも良いのですが、受け取るための行は実行に時間がかかるため、マトリックスパネルにちらつきが発生してしまいます。そのため、それを軽減するために合計100回の表示を行い非表示の時間を相対的に減らしています。
ただし、この方法を使うとWebサイトの応答性が少し悪くなります。そのため、状況に応じてforの回数を変更してく必要があります。

if (client) {
  while (client.connected()) {   // loop while the client's connected
    printTxt();
    //中略
  }
  //中略
  printTxt();
}

また、ここにも2箇所に入れました。特に接続中はwhileの中から抜けないため、そこに入れておかないと通信中は非表示になってしまいます。ただし、whileの中も実行時間が多少かかるため、通信中はどうしてもちらつきが多くなってしまいます。for文を使うと通信に失敗する可能性も割と大きくなるので、使用しませんでした。

動作

ここまでくれば、同一LAN内からマトリックスパネルに文字を表示できます。実際に動作させてみた様子が以下の映像です。
https://youtu.be/31khtMs95as

終わりに

楽しかったです。

参考文献

以下の記事を参考にさせていただきました。ありがとうございます。

GitHubで編集を提案

Discussion