はじめに
今年も始まりました。IoTLTのアドベントカレンダー!。この投稿のついでに前から作ってみようと思っていたものをつくってみます
つくるもの
だいたいのイメージはこんな感じです。
やりたいことは以下です。
- みんなのメッセージをPCやスマホから受け取ります
- メッセージをクラウドで集めます
- LEDデバイスで表示します
LEDデバイスは@kitazakiさんよりいただいたMINI LED BADGEを使います。
とりあえずこのアプリケーションをNIGIYAKASHIと名付けました。最近はオンラインでの勉強会での発表がメインになってきましたが、やはりオフラインと比べて賑やかし要素が少なく、寂しく感じる場面が多々あったのでこれを作ってみようと思いました。
完成品
とりあえず動くものから見せます。
机の上若干ごちゃついているのは無視でw
つかったもの
- メッセージの収集
- メッセージ送信用のUIアプリ
- メッセージ受信デバイス
PubNub
詳しくは公式サイトで見てもらった方が良いかと思いますが、端的に説明するとリアルタイムメッセージチャットやMQTT用のブローカーのサーバーをすべて肩代わりしてくれるサービスです。このサービスを使うとメッセージ送信・受信用のコードを書くだけでリアルタイムメッセージのやり取りができるアプリケーションが作成できます。
UIアプリの作成
コードの全体はこちらで公開しています。ReactでPubNubをつかうためのパッケージpubnub-reactを使うと簡単にPubNubを使うことができます。実装したコードは↓のような感じで、公式ドキュメントのサンプルコードをフォークするだけで簡単に実装することができました。
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用のサンプルコードをベースに実装しました。PubNubとのやりとりはn0bisukeさんの記事を参考に実装してみました。
またPubNubから送られてくるメッセージはJSONの形式となっているので、それをパースするためにArduino_JSONを使いました。
まずベースのサンプルコードのTestLEDMatrix()をいじって、任意の長さの文字をスクロール表示するための関数を実装しました。
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()
に送るための文字列を取り出します。
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"]));
}
コード全体はこんな感じになりました。
#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さんの投稿です。お楽しみに!