M5StackでSymbolリスナーを作ってみた

# はじめに
Twitterでｽﾀｯｸﾁｬﾝを見かけるようになり、M5Stack-core2を衝動買いしました。
有志の方の助けを借りてChatGPTとGoogleTTSで喋るｽﾀｯｸﾁｬﾝは開発できました！

しかし、M5Stackで何が出来るのか全くわかっていない状態なので基礎的な部分を
学んだついでにSymbolブロックチェーンを監視するための簡単な仕組みを作ってみました。

Symbolブロックチェーンってなんぞやという方はこちらを参照してください。
コミュニティの方がドキュメントをまとめてくださっています。

https://symbol-community.com/ja

ｽﾀｯｸﾁｬﾝとはなんぞやという方はこちらを参照してください。

https://protopedia.net/prototype/2345

記事は読まなくていいからコードはよ！というかたはこちら。

https://github.com/villhell/M5Stack-Symbol-Listener

# 目次
1. [動かした時の画像](#Chapter1)
1. [環境構築](#Chapter2)
1. [使っている機能](#Chapter3)
1. [参考文献](#reference)

<a id="#Chapter1"></a>
# 動かした時の画像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532264/781196d5-6c0d-6fb3-96c7-ccd9c401dd4a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532264/2e0014dd-4e53-ad5c-7852-5366142becba.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532264/98b73146-9225-76b2-db7d-3e049233ebf0.png)



<a id="#Chapter2"></a>
# 環境構築
1. vscodeのインストール
1. vscode拡張機能PlatformIO IDEをインストール
1. M5Stack-Symobl-Listenerをクローン
1. ビルド＆書き込み

問題なく実機に書き込みまでできればSDカードからwifi接続情報を読取り、
wifi接続が行われます。

> **そもそもM5Stack使ったことない人はUSBシリアルドライバが必要なので参考文献の「M5Stack の開発環境を準備する」
からドライバインストールしておいてください。**


<a id="#Chapter3"></a>
# 使っている機能
### 1. SDカード読込
SDカードの読み書きはよく使うので覚えておきましょう！
``` c++
  // SDカード読込
  File f = SD.open("/wifi.txt", FILE_READ);
  
  // SDカードが読み込めたかどうか
  if (f) {
    M5.Lcd.println("open SD");
    String line;

    // ファイルのサイズを取得
    size_t sz = f.size();
    
    // ファイルの内容を格納するバッファを確保
    char buf[sz + 1];

    // ファイルの内容を読み込む
    f.read((uint8_t*)buf, sz);
    
    // バッファの終端にヌル文字を追加
    buf[sz] = 0;
    
    // ファイルを閉じる
    f.close();

    int y = 0;
    for(int x = 0; x < sz; x++) {
      if(buf[x] == 0x0a || buf[x] == 0x0d)
        buf[x] = 0;
      else if (!y && x > 0 && !buf[x - 1] && buf[x])
        y = x;
    }
    M5.Lcd.print(line);
  } else {
    M5.Lcd.print("Failed to open file for reading");
  }
```

### 2. wifi接続
使う場合はタイムアウトを設定してください！
``` c++
    // WiFi接続
    WiFi.begin(buf, &buf[y]);
    
    // WiFi接続待ち
    while (WiFi.status() != WL_CONNECTED) {
      //M5.Lcd.println("wifi no connected");
      delay(500);
      M5.Lcd.print(".");
    }

    M5.Lcd.println("wifi connected");
    M5.Lcd.println(WiFi.localIP());
```

### 3. websocket client
symbolノードにwebsocket接続する場合はwss://NODE:3001/wsとなるので
beginSSLで接続します。ws://の場合はbeginでOK!

``` c++
    // server address, port and URL
    webSocket.beginSSL("mikun-testnet.tk", 3001, "/ws");

    // event handler
    webSocket.onEvent(webSocketEvent);

    // try ever 5000 again if connection has failed
    webSocket.setReconnectInterval(5000);

/**
 * @brief WebSocketイベント
*/
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {

  // 画面初期化
  initScreen();

  switch(type) {
		case WStype_DISCONNECTED:
      M5.Lcd.print("[WSc] Disconnected!\n");
			break;
		case WStype_CONNECTED:
      M5.Lcd.printf("[WSc] Connected to url: %s\n", payload);
			break;
		case WStype_TEXT:
        {
          DynamicJsonDocument json_payload(1024);
          DeserializationError error = deserializeJson(json_payload, (const char*)payload);
          if (error) {
            M5.Lcd.print(F("deserializeJson() failed: "));
            M5.Lcd.println(error.c_str());
            uid = "";
          }else{
            if(json_payload.containsKey("uid")) {
              uid = json_payload["uid"].as<String>();
          
              // uncomfirmedを購読
              subscribeUncomfirmed(uid);
              // comfirmedを購読
              subscribeComfirmed(uid);

            } else{
              M5.Lcd.println(json_payload["topic"].as<String>());
              M5.Lcd.println(json_payload["data"]["meta"]["hash"].as<String>());
            }
        
          }
    			break;
        }

		case WStype_BIN:
			M5.Lcd.printf("[WSc] get binary length: %u\n", length);
			hexdump(payload, length);
			break;
		case WStype_ERROR:
          M5.Lcd.printf("[WSc] get error: %s\n", payload);
          break;
		case WStype_FRAGMENT_TEXT_START:
          M5.Lcd.print("WStype_FRAGMENT_TEXT_START");
          break;
        case WStype_FRAGMENT_BIN_START:
          M5.Lcd.print("WStype_FRAGMENT_BIN_START");
          break;
		case WStype_FRAGMENT:
          M5.Lcd.print("WStype_FRAGMENT");
          break;
		case WStype_FRAGMENT_FIN:
          M5.Lcd.print("WStype_FRAGMENT_FIN");
			break;
	}
}
```

### 4. symbol
symbolノードにwebsocket接続をリクエストした場合、はじめにUIDが返却されます。
そのUIDをもとにunconfirmed,confirmedを購読するリクエストを投げて始めて監視が出来るようになります。
他にもblockを購読することもできます。（※blockについては記載していません。）

購読部分はこんな感じ
``` c++
/**
 * @brief Uncomfirmed
*/
void subscribeUncomfirmed(String uid){
  String message = "{\"uid\":\"" + uid + "\", \"subscribe\":\"unconfirmedAdded/" + ADDRESS + "\"}";
  webSocket.sendTXT(message);
  M5.Lcd.println("subscribe unconfirmedAdded");
}

/**
 * @brief Comfirmed
*/
void subscribeComfirmed(String uid){
  String message = "{\"uid\":\"" + uid + "\", \"subscribe\":\"confirmedAdded/" + ADDRESS + "\"}";
  webSocket.sendTXT(message);
  M5.Lcd.println("subscribe confirmedAdded");
}
```

# 最後に
ADDRESSはできればSDカードに格納し、複数のアドレスを関し出来る状態にしたほう
が良かったかなと思います。

今後はSDカードから画像や音声ファイルを読み出し、
トランザクションを検知したタイミングでスタックチャンに喋ってもらう！
とかやってみたいですね。c++ムズカシイィィ！ではまた！

<a id="#reference"></a>
# 参考文献
- [【M5Stack】VSCodeの開発環境構築（写真で丁寧に解説）](https://kumabelog.com/m5stack-development-environment-for-vscode/)
- [M5Stack の開発環境を準備する](https://users.soracom.io/ja-jp/guides/dev-boards/m5stack/development-environment/)