リモート発表の寂しさ問題を解決するNIGIYAKASHI

# はじめに

今年も始まりました。[IoTLTのアドベントカレンダー](https://qiita.com/advent-calendar/2020/iotlt)！。この投稿のついでに前から作ってみようと思っていたものをつくってみます

# つくるもの

だいたいのイメージはこんな感じです。

![NIGIYAKASHI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/c229534d-a83d-e35e-f2f7-f2c505771681.jpeg)

やりたいことは以下です。

1. みんなのメッセージをPCやスマホから受け取ります
2. メッセージをクラウドで集めます
3. LEDデバイスで表示します

LEDデバイスは@kitazakiさんよりいただいた[MINI LED BADGE](https://protopedia.net/prototype/1904)を使います。
とりあえずこのアプリケーションを**NIGIYAKASHI**と名付けました。最近はオンラインでの勉強会での発表がメインになってきましたが、やはりオフラインと比べて賑やかし要素が少なく、寂しく感じる場面が多々あったのでこれを作ってみようと思いました。

# 完成品

とりあえず動くものから見せます。

[![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/i4qHHkDk6Gc/0.jpg)](https://www.youtube.com/watch?v=i4qHHkDk6Gc)

机の上若干ごちゃついているのは無視でw

# つかったもの

- メッセージの収集
  - [PubNub](https://www.pubnub.com/)
- メッセージ送信用のUIアプリ
  - [React](https://ja.reactjs.org/)
  - [Material-UI](https://material-ui.com/)
- メッセージ受信デバイス
  - [ESP32](https://akizukidenshi.com/catalog/g/gM-11819/)
  - [MINI LED BADGE](https://protopedia.net/prototype/1904)

# PubNub

詳しくは公式サイトで見てもらった方が良いかと思いますが、端的に説明するとリアルタイムメッセージチャットやMQTT用のブローカーのサーバーをすべて肩代わりしてくれるサービスです。このサービスを使うとメッセージ送信・受信用のコードを書くだけでリアルタイムメッセージのやり取りができるアプリケーションが作成できます。
 
# UIアプリの作成

コードの全体は[こちらで公開](https://github.com/ufoo68/nigiyakashi-ui)しています。ReactでPubNubをつかうためのパッケージ[pubnub-react](https://www.npmjs.com/package/pubnub-react)を使うと簡単にPubNubを使うことができます。実装したコードは↓のような感じで、公式ドキュメントのサンプルコードをフォークするだけで簡単に実装することができました。

```react:src/App.js
import React, { useCallback, useState } from 'react'
import PubNub from 'pubnub'
import { PubNubProvider, usePubNub } from 'pubnub-react'
import Button from '@material-ui/core/Button'
import Input from '@material-ui/core/Input'
import { makeStyles } from '@material-ui/core/styles'
import logo from './logo.png'

const pubnub = new PubNub({
  publishKey: process.env.REACT_APP_PUB_KEY,
  subscribeKey: process.env.REACT_APP_SUB_KEY,
})

const useStyles = makeStyles({
  container: {
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  preparedMessage: {
    flexDirection: 'row',
  },
  customMessage: {
    flexDirection: 'row',
  },
  logo: {
    width: 400,
  },
})

const simpleMessages = ['わーい！', 'すごーい！', 'たのしー！']

const Messenger = () => {
  const pubnub = usePubNub()
  const [input, setInput] = useState('')
  const classes = useStyles()

  const sendMessage = useCallback(
    async message => {
      await pubnub.publish({
        channel: 'nigiyakashi',
        message: { message },
      })

      setInput('')
    },
    [pubnub, setInput]
  )

  return (
    <div className={classes.container}>
      <img src={logo} alt='logo' className={classes.logo}/>
      <div className={classes.preparedMessage}>
        {simpleMessages.map(simpleMessage =>
          <Button
            onClick={e => {
              e.preventDefault()
              sendMessage(simpleMessage)
            }}
            variant="outlined"
          >{simpleMessage}</Button>
        )}
      </div>
      <div className={classes.customMessage}>
        <Input
          type="text"
          placeholder="自由記入欄"
          value={input}
          onChange={e => setInput(e.target.value)}
        />
        <Button
          onClick={e => {
            e.preventDefault()
            sendMessage(input)
          }}
          variant="contained"
          color="primary"
        >
          送信
      </Button>
      </div>
    </div>
  )
}

const App = () => {
  return (
    <PubNubProvider client={pubnub}>
      <Messenger />
    </PubNubProvider>
  )
}

export default App
```

# 受信デバイスの実装

[MINI LED BADGEのM5Stack用のサンプルコード](https://github.com/kitazaki/nora_badge_3rd/blob/master/example/M5Stack_grove_MINI_LED_BADGE/M5Stack_grove_MINI_LED_BADGE.ino)をベースに実装しました。PubNubとのやりとりは[n0bisukeさんの記事](https://qiita.com/n0bisuke/items/bc9032fc5c92247c61da)を参考に実装してみました。
またPubNubから送られてくるメッセージはJSONの形式となっているので、それをパースするために[Arduino_JSON](https://github.com/arduino-libraries/Arduino_JSON)を使いました。

まず[ベースのサンプルコードのTestLEDMatrix()](https://github.com/kitazaki/nora_badge_3rd/blob/master/example/M5Stack_grove_MINI_LED_BADGE/M5Stack_grove_MINI_LED_BADGE.ino#L41)をいじって、任意の長さの文字をスクロール表示するための関数を実装しました。

```c
void displayLEDMatrix(char *str){
  uint8_t buf[8];
  for (int x=17; x>= -1 * (int(String(str).length()) / 3 * 8) - 16; x--) {
    char *ptr = str;
    uint16_t n = 0;
    matrix.clear();
    matrix1.clear();
    matrix.setCursor(x,0);
    matrix1.setCursor(x+16,0);
    while(*ptr){
      ptr = getFontData(buf,ptr,true);
      if(!ptr)
        break;
      matrix.drawBitmap(x+n,0,buf,8,8,1);
      matrix1.drawBitmap(x+16+n,0,buf,8,8,1);
      n+=8;
    }
    matrix.writeDisplay();
    matrix1.writeDisplay();
  }
}
```

ただこれは全角の文字列が前提なので半角には対応してないですwあとはメッセージをSubscribeしたときのコールバック関数でJSON形式のメッセージをパースして`displayLEDMatrix()`に送るための文字列を取り出します。

```c
void callback(char* topic, byte* payload, unsigned int length){
  String msg = (char*) payload;
  msg = msg.substring(0, length);
  JSONVar msgObj = JSON.parse((char*) msg.c_str());
  displayLEDMatrix((char*) ((const char*) msgObj["message"]));
}
```

コード全体はこんな感じになりました。

```c
#include <M5Stack.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <Arduino_JSON.h>
#include <Adafruit_GFX.h>
#include <Adafruit_LEDBackpack.h>
#include "misakiUTF16.h"

Adafruit_8x16matrix matrix = Adafruit_8x16matrix();
Adafruit_8x16matrix matrix1 = Adafruit_8x16matrix();

const char* ssid = "ssid";
const char* password = "password";
const char* mqttServer = "mqtt.pndsn.com";
const char* pubnubid = "pub-c-xxx/sub-c-xxx/ufoo68";
const int mqttPort = 1883;

WiFiClient espClient;
PubSubClient client(espClient);

void callback(char* topic, byte* payload, unsigned int length){
  Serial.print("Message arrived in topic: ");
  Serial.println(topic);
  Serial.print("Message:");
  String msg = (char*) payload;
  msg = msg.substring(0, length);
  Serial.println(msg);
  JSONVar msgObj = JSON.parse((char*) msg.c_str());
  displayLEDMatrix((char*) ((const char*) msgObj["message"]));
  Serial.println("-----------------------");
}

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

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED){
    delay(500);
    Serial.println("Connecting to WiFi..");
  }
  Serial.println("Connected to the WiFi network");

  client.setServer(mqttServer, mqttPort);
  client.setCallback(callback);
  while (!client.connected()) {
    Serial.println("Connecting to MQTT...");
    if (client.connect(pubnubid)){
      Serial.println("connected");
    }else{
      Serial.print("failed with state ");
      Serial.print(client.state());
      delay(2000);
    }
  }
  client.subscribe("nigiyakashi");

  Wire.begin(21, 22, 1000);  // M5Stack Grove G21: SDA, G22: SCL
  matrix.begin(0x71);
  matrix1.begin(0x70);
  matrix.setBrightness(0);
  matrix1.setBrightness(0);
  matrix.setTextWrap(false);
  matrix1.setTextWrap(false);
  matrix.setTextColor(LED_ON);
  matrix1.setTextColor(LED_ON);
  matrix.setRotation(1);
  matrix1.setRotation(1);
}

void loop() {
  client.loop();
}

void displayLEDMatrix(char *str){
  uint8_t buf[8];
  for (int x=17; x>= -1 * (int(String(str).length()) / 3 * 8) - 16; x--) {
    char *ptr = str;
    uint16_t n = 0;
    matrix.clear();
    matrix1.clear();
    matrix.setCursor(x,0);
    matrix1.setCursor(x+16,0);
    while(*ptr){
      ptr = getFontData(buf,ptr,true);
      if(!ptr)
        break;
      matrix.drawBitmap(x+n,0,buf,8,8,1);
      matrix1.drawBitmap(x+16+n,0,buf,8,8,1);
      n+=8;
    }
    matrix.writeDisplay();
    matrix1.writeDisplay();
  }
}

```

# さいごに

今回は色んなサンプルコードを組み合わせたような実装になりましたが、Arduinoを使ったリアルタイムメッセージのやりとりをPubNubを使って簡単に実装することができました。かなりシンプルなものができましたが、実際に自分のリモート発表に使ってみようかなと思います。

次回は@wicketさんの投稿です。お楽しみに！