OBSをM5Stackでコントロールする
自作シリーズ!noteには、M5Stackで遊んでる様子を何個かあげてました。
今回は、OBS Stduio のシーンスイッチャーにしてみましょう。
WebSocketでのやりとり
OBS Studio は、映像録画/配信ソフトです。そして、遠隔操作するにはWebSocketプラグインを追加しましょう。
これをいれたら、あとは決められたシーケンスに従ってデータを送っておけば、遠隔操作が完了します。
ちょっとだけ解説
{
"request-type":"SetCurrentScene",
"message-id":"1aba1234Aasvee1a",
"scene-name":"シーン1"
}
送信されるデータはJSON形式。そして、リクエストタイプが「行うべきアクション」の指定になります。
そのアクションごとに、必要なパラメータがあるので、その分を追加して送付してください。(たとえば、シーン切り替えならシーン名が必要。)
また、メッセージIDは16桁のランダムでユニークな文字列を追加する仕様ですが、そうじゃなくても一応動くようです。
動いた様子
動画は、twitterに公開してます。
謝辞
各種プログラムや、関連ブログの情報を応用させていただいてます。
文字表示関連ライブラリ
ボタン関連ライブラリ
JSONライブラリ
WebSocket通信
フォント関連
(まだまだあるとおもう)
プログラム
エラー処理があまいし、時々リセットするので、もうちょっと作りこんだりする必要はあるんだけど、ひとまず公開。
SDに東雲フォントをおかないと、何も画面にでないのと、OBSのシーン名は「シーン1」~「シーン3」を切り替えるので、名前を合わせてみてください。
//==================================================
// OBS Studio 連携操作例 on M5Stack
// 2019.3.15
//==================================================
//==================================================
//プラットフォームライブラリ
//==================================================
#include <M5Stack.h>
//==================================================
//文字表示関連ライブラリ
//参考:https://www.mgo-tec.com/blog-entry-m5stack-font-scrolle-esp32.html
//==================================================
#include <ESP32_SD_UTF8toSJIS.h>
#include <ESP32_LCD_ILI9341_SPI.h>
#include <ESP32_SD_ShinonomeFNT.h>
//==================================================
//ボタン関連ライブラリ
//参考:https://www.mgo-tec.com/blog-entry-m5stack-button-arduino-esp32.html
//==================================================
#include <ESP32_Button_Switch.h>
//==================================================
//JSON分解ライブラリ
//参考: http://shuzo-kino.hateblo.jp/entry/2016/05/06/203603
//==================================================
#include <ArduinoJson.h>
//==================================================
//WS通信ライブラリ
//参考:https://log.niccol.li/2012/05/arduinowebsocketsocketio.html
//==================================================
#include <WebSockets.h>
#include <WebSocketsClient.h>
#include <WebSocketsServer.h>
//==================================================
//WiFi通信ライブラリ
//==================================================
#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiMulti.h>
#include <WiFiClientSecure.h>
//==================================================
//個別設定の定数
//==================================================
const char SSID[] = "WIFI000"; //WiFi SSID
const char PASSWORD[] = "PASSWORD"; //WiFi パスワード
const char WebServerIP[] = "192.168.0.2"; //OBS IP
const int WebServerPort = 4444; //OBS port
//==================================================
//ハード依存の定数
//==================================================
const int8_t sck = 18; // SPI clock pin
const int8_t miso = -1; // MISO(master input slave output) don't using
const int8_t mosi = 23; // MOSI(master output slave input) pin
const int8_t cs = 14; // Chip Select pin
const int8_t dc = 27; // Data/Command pin
const int8_t rst = 33; // Reset pin
const int8_t LCD_LEDpin = 32;
const uint8_t CS_SD = 4; //SD card CS ( Chip Select )
const uint8_t buttonA_GPIO = 39;
const uint8_t buttonB_GPIO = 38;
const uint8_t buttonC_GPIO = 37;
String prev_message="";
//==================================================
//fontの配置(SD上)
//参考;https://www.mgo-tec.com/kanji-font-shinonome
//==================================================
const char* UTF8SJIS_file = "/font/Utf8Sjis.tbl"; //UTF8 Shift_JIS 変換テーブルファイル名を記載しておく
const char* Shino_Zen_Font_file = "/font/shnmk16.bdf"; //全角フォントファイル名を定義
const char* Shino_Half_Font_file = "/font/shnm8x16r.bdf"; //半角フォントファイル名を定義
//==================================================
//変数定義(操作用オブジェクト)
//==================================================
WebSocketsClient wsclient;
ESP32_LCD_ILI9341_SPI LCD(sck, miso, mosi, cs, dc, rst, LCD_LEDpin);
ESP32_SD_ShinonomeFNT SFR(CS_SD, 40000000);
ESP32_Button_Switch BTN;
uint8_t btn_stateA = _Release;
uint8_t btn_stateB = _Release;
uint8_t btn_stateC = _Release;
TaskHandle_t th; //マルチタスクハンドル定義
//==================================================
//WS : →M5stackに通信がきたとき
//==================================================
void onMessage(WStype_t type, uint8_t * payload, size_t length){
//分解
StaticJsonDocument<1200> jsonBuffer;
DeserializationError error =deserializeJson(jsonBuffer,(char *)payload);
Serial.println((const char*)payload);
if (!error)
{
JsonObject root = jsonBuffer.as<JsonObject>();
//画面を切り替えた
if(root["update-type"]=="SwitchScenes")
{
M5.Lcd.fillScreen(BLACK);
printjp(0, 0,"[現在]",0xff,0xff,0xff,3);
printjp(0,48,root["scene-name"],0xff,0xff,0xff,3);
Serial.println((const char*)root["scene-name"]);
}
}
}
//==================================================
//LCDに日本語を出すためのラッパー関数
//==================================================
void printjp(int x,int y,String moji,int red,int green,int blue,int fsize)
{
uint8_t test_buf[100][16] = {};
uint8_t test_sj_length = SFR.StrDirect_ShinoFNT_readALL(moji, test_buf);
uint8_t LineLen=18;
uint8_t tLineLenq=LineLen;
uint8_t i2=0;
for(uint8_t i=0;i<test_sj_length;i+=tLineLenq)
{
tLineLenq=LineLen;
if(test_sj_length-i2*LineLen<LineLen) { tLineLenq=test_sj_length-i2*LineLen;}
LCD.HVsizeUp_8x16_Font_DisplayOut(fsize, fsize, tLineLenq, x, y+i2*fsize*16, red, green, blue, test_buf+i);
i2++;
}
}
//==================================================
//イニシャライズ
//==================================================
void setup() {
//ハード初期化
m5.begin();
M5.Speaker.mute();
dacWrite(25, 0); // Speaker OFF
pinMode(buttonA_GPIO, INPUT); //GPIO #39 は内部プルアップ無し
pinMode(buttonB_GPIO, INPUT); //GPIO #38 は内部プルアップ無し
pinMode(buttonC_GPIO, INPUT); //GPIO #37 は内部プルアップ無し
//シリアルモニタ用初期化
Serial.begin(115200);
//フォント読み込み
SFR.SD_Shinonome_Init3F(UTF8SJIS_file, Shino_Half_Font_file, Shino_Zen_Font_file); //ライブラリ初期化。3ファイル同時に開く
//画面出力系初期化
LCD.ILI9341_Init(false, 40000000);
LCD.Display_Clear(0, 0, 319, 239);
LCD.Brightness(200); //LCD LED Full brightness
//WiFiを子機として起動
WiFi.begin(SSID, PASSWORD);
M5.Lcd.print("WiFi connecting");
//接続をまつ
while (WiFi.status() != WL_CONNECTED) {
M5.Lcd.print(".");
delay(100);
}
//取得したIPの表示(DHCP対応)
IPAddress ipadr =WiFi.localIP();
char ipad_st[20];
sprintf(ipad_st,"IP:%d.%d.%d.%d",ipadr[0],ipadr[1],ipadr[2],ipadr[3]);
M5.Lcd.println(" connected");
M5.Lcd.println(ipad_st);
//OBS-APIに接続トライ
M5.Lcd.println("Connect to OBS....");
wsclient.setReconnectInterval(5);
wsclient.onEvent(onMessage);
wsclient.begin(WebServerIP,WebServerPort, "/", "websocket");
M5.Lcd.println("press ABC button");
randomSeed(0);
xTaskCreatePinnedToCore(
Task1, //関数ポインタ
"Task1", //タスク名
65534, //スタック
NULL, //引数
5, //優先度
&th, //ハンドル
1 //コア番号
);
}
//==================================================
//WSメッセージループ
//==================================================
void Task1(void *pvParameters) {
//これがないとWSイベントが起きない
while(1)
{
wsclient.loop();
M5.update();
delay(1); //most important
}
}
//==================================================
//OBS用のメッセージID生成
//==================================================
void NewMessageID(char *result,int length = 16)
{
const char pool []= "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (int i = 0; i < length; i++)
{
int index = random(0,(int)sizeof(pool)-1);
result[i] = pool[index];
}
}
//==================================================
//メッセージループ
//==================================================
void loop() {
char wsSend[200];
char result[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
//Aボタン応答
btn_stateA = BTN.Button(0, buttonA_GPIO, true, 10, 300);
switch( btn_stateA ){
case _MomentPress:
Serial.println("-------------Button A Moment Press");
NewMessageID(result);
Serial.println(result);
sprintf(wsSend,"{\"request-type\":\"SetCurrentScene\",\"message-id\":\"%s\",\"scene-name\":\"シーン1\"}",result);
wsclient.sendTXT(wsSend);
break;
case _ContPress:
Serial.println("-------------Button C Constant Press");
M5.Lcd.println("connecting");
wsclient.begin(WebServerIP,WebServerPort, "/", "websocket");
break;
}
//Bボタン応答
btn_stateB = BTN.Button(1, buttonB_GPIO, true, 10, 300);
switch( btn_stateB ){
case _MomentPress:
Serial.println("-------------Button B Moment Press");
NewMessageID(result);
sprintf(wsSend,"{\"request-type\":\"SetCurrentScene\",\"message-id\":\"%s\",\"scene-name\":\"シーン2\"}",result);
wsclient.sendTXT(wsSend);
break;
}
//Cボタン応答
btn_stateC= BTN.Button(2, buttonC_GPIO, true, 10, 300);
switch( btn_stateC ){
case _MomentPress:
Serial.println("-------------Button C Moment Press");
NewMessageID(result);
sprintf(wsSend,"{\"request-type\":\"SetCurrentScene\",\"message-id\":\"%s\",\"scene-name\":\"シーン3\"}",result);
wsclient.sendTXT(wsSend);
break;
case _ContPress:
Serial.println("-------------Button C Constant Press");
M5.Lcd.println("disconnecting");
wsclient.disconnect();
break;
}
}
開発したり研究したりするのに時間と費用がとてもかかるので、頂いたお気持ちはその費用に補填させていただきます。