MQTT にも一通り使い慣れた?ので、
送信側に m5stack、受信側にブラウザを使用した「ヤルキスイッチ」を作成しました。
役には立ちません。いわゆる「ネタアプリ?」でしょうか。
今までコマンドラインだけ、スイッチだけだったものが、連動して動くようになると本当に楽しいです。
とりあえず動くものを見てほしいよ
完成品を動かすと、以下の動画みたいになります。
それじゃ解説です。
目次
全体の構成
以下の構成でデータが流れます。
M5Stack(mqtt 送信側) ==> 公開サーバー(mqtt ブローカー) ==> ブラウザ(mqtt 受信側)
ブローカーは前回のMQTT を使いたい。3で作成したものをベースにしただけなので、割愛。
- M5Stack(mqtt 送信側)
- ブラウザ(mqtt 受信側)
以上二つを主に解説します。
M5Stack(mqtt 送信側)
先に準備として m5stack には 320*200 の大きさの jpg 画像をそれぞれ
- yaruki1logo.jpg
- yaruki2logo.jpg
- yaruki3logo.jpg
 という名前で保存しておきます。
 M5Stack の Lcd に、押したボタンに対応した画像を表示するためのものです。
以下を参考にして作成しました。
Arduino Client for MQTT
ESP32 を MQTT で Publish する
書き込んだソースファイルは以下の通り(簡単のため WIFI,MQTT の再接続は考慮しません)
yaruki-pub.ino| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 
 | #include <WiFi.h>
 #include <M5Stack.h>
 #include <PubSubClient.h>
 
 
 const char* SSID = "derutaASUS";
 
 const char* PW = "sankaku3";
 
 
 const char *mqttHost = "xxx.xxx.xxx.xxx";
 
 const int mqttPort = 00000;
 
 const char *clientid = "pubM5S";
 
 const char *user = "pub_user_M5S";
 
 const char *pw = "xdorytbseyrixdfgdsfgns";
 
 const char *topic = "topicSW";
 
 
 WiFiClient wifiClient;
 
 PubSubClient mqttClient(wifiClient);
 
 
 void setup()
 {
 
 M5.begin();
 M5.Lcd.setBrightness(70);
 
 
 WiFi.begin(SSID, PW);
 M5.Lcd.setTextSize(2);
 M5.Lcd.printf("Connecting");
 while (WiFi.status() != WL_CONNECTED)
 {
 
 M5.Lcd.printf(".");
 delay(500);
 }
 
 M5.Lcd.clear();
 M5.Lcd.setCursor(0, 0);
 M5.Lcd.printf("Connect!");
 
 
 mqttClient.setServer(mqttHost, mqttPort);
 while (!mqttClient.connected())
 {
 M5.Lcd.printf("Connecting to MQTT...");
 if (mqttClient.connect(clientid, user, pw))
 {
 M5.Lcd.printf("mqttconnected");
 break;
 }
 delay(1000);
 randomSeed(micros());
 }
 }
 
 
 void loop()
 {
 M5.update();
 
 if (M5.BtnA.wasPressed())
 {
 mqttClient.publish(topic, "{\"data\":{\"button\":\"push-A\"}}");
 M5.Lcd.clear();
 M5.Lcd.drawJpgFile(SD, "/yaruki1logo.jpg", 0, 40, 320, 200);
 }
 
 if (M5.BtnB.wasPressed())
 {
 mqttClient.publish(topic, "{\"data\":{\"button\":\"push-B\"}}");
 M5.Lcd.clear();
 M5.Lcd.drawJpgFile(SD, "/yaruki2logo.jpg", 0, 40, 320, 200);
 }
 
 if (M5.BtnC.wasPressed())
 {
 mqttClient.publish(topic, "{\"data\":{\"button\":\"push-C\"}}");
 M5.Lcd.clear();
 M5.Lcd.drawJpgFile(SD, "/yaruki3logo.jpg", 0, 40, 320, 200);
 }
 mqttClient.loop();
 }
 
 | 
 
Web(mqtt 受信側)
ブラウザをでの MQTT 送受信は以下を参考にして作成しました。
Node.js で MQTT ブローカーを立てて、ブラウザから確認する
今回は MQTT の受信の結果で動きを見せたいので、
pixi.jsを使用しました。
次などを参考にしています。
PixiJS — The HTML5 Creation Engine
learningPixi
css は skelton-css を使用してます。
以下を作成しました。
index.html| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 
 | <!DOCTYPE html><html>
 <head>
 <meta charset="utf-8">
 <title>YARUKI SWITCH</title>
 
 <link rel="stylesheet" href="./Skeleton-2.0.4/css/normalize.css">
 <link rel="stylesheet" href="./Skeleton-2.0.4/css/skeleton.css">
 <link rel="stylesheet" href="./a.css">
 
 </head>
 <body>
 <div class="container">
 <div class="row">
 <h1>YARUKISWICH</h1>
 <div>
 <div class="row">
 <div class="twelve columns cn">
 <canvas id="cvtarget"></canvas>
 </div>
 </div>
 </div>
 </body>
 <script src="bundle.js"></script>
 </html>
 
 | 
 
index.js| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 
 | import * as PIXI from "pixi.js";const mqtt = require("mqtt");
 
 
 let files = [
 { name: "0", pass: "images/yaruki1.png" },
 { name: "1", pass: "images/yaruki2.png" },
 { name: "2", pass: "images/yaruki3.png" },
 { name: "3", pass: "images/yaruki4.png" },
 { name: "back", pass: "images/back.png" },
 ];
 
 let logofiles = [
 { name: "0", pass: "images/yaruki1logo.png" },
 { name: "1", pass: "images/yaruki2logo.png" },
 { name: "2", pass: "images/yaruki3logo.png" },
 ];
 
 
 let Textures = undefined;
 let Textureslogo = undefined;
 
 
 let load = function (files) {
 let result = [];
 files.forEach((el) => {
 result.push({
 Texture: PIXI.Texture.fromImage(el.pass),
 filename: el,
 name: el.name,
 });
 });
 return result;
 };
 
 
 function name2tex(tex, name) {
 let result = undefined;
 tex.forEach((el) => {
 if (el.name == name) {
 result = el.Texture;
 }
 });
 return result;
 }
 
 
 let count = 0;
 
 let texselect = 0;
 
 function addsprite(select) {
 select = select == undefined ? texselect : select;
 
 Sprites.push(new PIXI.Sprite(name2tex(Textures, `${select}`)));
 let len = Sprites.length - 1;
 let r = Math.random();
 Sprites[len].sp = 6 + 3 * r;
 r += 0.5;
 let scale = app.screen.width / Sprites[len].texture.baseTexture.width / 3;
 Sprites[len].scale.x *= scale * r;
 Sprites[len].scale.y *= scale * r;
 Sprites[len].x = (app.screen.width / 4) * count * (r > 1 ? 1 : r);
 Sprites[len].y = (app.screen.height * 2) / 3;
 app.stage.addChild(Sprites[len]);
 
 count++;
 texselect++;
 if (count > 3) {
 count = 0;
 }
 if (texselect > 2) {
 texselect = 0;
 }
 }
 
 
 function addspritelogo(select) {
 console.log("addspritelogo");
 select = select == undefined ? 1 : select;
 
 Spriteslogo.push(new PIXI.Sprite(name2tex(Textureslogo, `${select}`)));
 let len = Spriteslogo.length - 1;
 let r = Math.random();
 Spriteslogo[len].sp = 6 + 3 * r;
 r += 0.5;
 let scale = app.screen.width / Spriteslogo[len].texture.baseTexture.width / 3;
 Spriteslogo[len].scale.x *= scale * r;
 Spriteslogo[len].scale.y *= scale * r;
 Spriteslogo[len].x = (app.screen.width / 4) * count;
 Spriteslogo[len].y = (app.screen.height * 1) / 2;
 app.stage.addChild(Spriteslogo[len]);
 }
 
 
 let Sprites = [];
 let Spriteslogo = [];
 
 
 let lashcount = 0;
 
 
 let app = undefined;
 
 function start() {
 
 let width = document.getElementById("cvtarget").width;
 let height = width * 1.5;
 height = height > window.innerHeight ? window.innerHeight : height;
 
 
 app = new PIXI.Application(width, height, {
 backgroundColor: 0x1099bb,
 view: document.body.querySelector("#cvtarget"),
 });
 
 
 Textures = load(files);
 Textureslogo = load(logofiles);
 
 
 var back = new PIXI.Sprite(name2tex(Textures, "back"));
 
 back.scale.x *= app.screen.width / back.texture.baseTexture.width;
 back.scale.y *= app.screen.height / back.texture.baseTexture.height;
 
 app.stage.addChild(back);
 
 
 let loop = () => {
 
 for (let i = 0; i < Sprites.length; i++) {
 Sprites[i].y -= Sprites[i].sp;
 Sprites[i].sp += -0.18;
 if (Sprites[i].y > app.screen.height) {
 app.stage.removeChild(Sprites[i]);
 }
 }
 
 for (let j = 0; j < Spriteslogo.length; j++) {
 Spriteslogo[j].y -= Spriteslogo[j].sp;
 Spriteslogo[j].sp += -0.18;
 if (Sprites[j].y > app.screen.height) {
 app.stage.removeChild(Spriteslogo[j]);
 }
 }
 
 if (lashcount > 0) {
 if (lashcount % 5 == 0 && lashcount > 60) {
 addspritelogo(0);
 addsprite();
 }
 if (lashcount == 5) {
 addspritelogo(1);
 addsprite(3);
 }
 lashcount--;
 }
 };
 
 app.ticker.add(loop);
 
 
 const client = mqtt.connect(
 "ws://[mqttブローカーサーバアドレス]:[mqttブローカサービスポート]",
 {
 clientId: "[クライアントID]",
 username: "[ユーザー名]",
 password: "[接続パスワード]",
 }
 );
 
 client.subscribe("topicSW");
 
 
 client.on("message", (topic, message) => {
 let data = JSON.parse(message.toString()).data;
 if (data.button == "push-A") {
 addspritelogo(0);
 addsprite();
 } else if (data.button == "push-B") {
 addspritelogo(1);
 addsprite(3);
 } else if (data.button == "push-C") {
 lashcount += 200;
 }
 console.log(JSON.parse(message.toString()));
 });
 }
 
 start();
 
 | 
 
index.js は Webpack+babel でトランスパイルして使用します。
これらを動かすと冒頭の「ヤルキスイッチ」のフロントエンドが完成します。
この時の package.json は以下のようになります。
package.json| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 
 | {"name": "yarukiswitch",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
 "test": "echo \"Error: no test specified\" && exit 1",
 "build": "webpack --watch --progress --mode development",
 "dev": "webpack-dev-server  --watch --progress --mode development"
 },
 "keywords": [],
 "author": "",
 "license": "ISC",
 "devDependencies": {
 "@babel/core": "^7.2.2",
 "babel": "^6.23.0",
 "babel-core": "^6.26.3",
 "babel-loader": "^8.0.4",
 "babel-preset-es2015-riot": "^1.1.0",
 "webpack": "^4.28.3",
 "webpack-cli": "^3.1.2",
 "webpack-dev-server": "^3.1.14"
 },
 "dependencies": {
 "mqtt": "^2.18.8",
 "pixi.js": "^4.8.5"
 }
 }
 
 | 
 
だいたい 1 か月で、デバイスからインターネットを介して、ブラウザとやり取りができるようになりました。
まさに IoT な感じです。
ツイッターにアップしたテスト版は某プロレスラーをイメージしたいらすとやの画像を混ぜる悪ノリをしたけど、
記事にするにあたって、C ボタンは「ヤルキラッシュ」ということで雪崩のように画像が流れて、
最後に「モエツキ」がポンと一つだけポップするようにしました。
何気にその最後の挙動がお気に入りだったりします。
M5Stack とかを ArduinoIDE で開発をしていると、文字列の型の宣言にchar 変数名*に書いたあたり、
昔 C を書いてた頃を思い出して少し懐かしい感じでした。
次回は MQTT で、デバイスは obniz を使用してドアセンサーを仕上げたいと思います。
間に何か(pixi.js と riot.js)挟むかも・・・。
ではでは