Stream Deckを自作する【M5Stack Core2】

こんばんは、Rivièreです。好きなUIはSHIGUREです。

すっかり寒くなりましたが、皆さんいかがお過ごしですか?

私は今年も自宅で作業することが多く、季節を感じぬままここまできてしまいました。

気付けば明日はもうクリスマスイブですね。

 

この記事はWMMC AdventCalendar2021 23日目の記事

adventar.org

です。

 

さて、昨日、一昨日は、ぱわぷろさんのWMMC活動の振り返り記事でした。

21日目:

個人的WMMC活動を振り返る(ステッパー編) - ぱわぷろ活動日誌

22日目:

個人的WMMC活動を振り返る(DCマウス編) - ぱわぷろ活動日誌


私が所属する以前の顛末も書かれていて、先輩方の苦労の一端を知ることができました。すでに老害に片足を突っ込んでしまう時期になりましたが、何とかこのサークルが存続できるように尽力していければと思います。

 

また、私が直前まで題材を決めていないという怠惰を晒したために、ぱわぷろさんには「再び未定」という謎の題でブログを書くと伝わってしまいました。

ごめんなさい、別に哲学っぽい何かを語るとかいうわけではなかったんです...

 

というわけで、M5Stack Core2 を使って遊んでいきます。

 

目次

 

はじめに

今回は、M5Stack Core2 を使ってStream Deckのようなものを作っていきます。

とはいえなんぞやという方も多いかと思いますので、まずは軽く紹介します。

 

M5Stack Core2

M5Stack Core2は2020年9月にM5Stack社から発売されたESP32モジュールです。

タッチスクリーンや6軸IMU、ブザー、マイク、内部モーター、microSDスロットなどなどハードウェア周りが充実しています。

また、ESP32を使用しているので、WiFiBluetoothが標準搭載されており、背面からGPIOを扱うことも可能です。

さらにstackの名の通り、同社では様々な拡張基板やモジュールが販売されており、組み合わせることで多くの機能を後付けすることが可能です。

本製品はArduino、UiFlow、MycroPythonを使って開発することができます。

まさに遊ぶには持って来いのモジュールですね。

 

www.switch-science.com

 

Stream Deck

Stream Deckとは、Elgato社から販売されているPCコントローラーです。

液晶付きのボタンが並んでいるのが特徴で、ユーザーは専用ツールで各窓にさまざまな機能を割り振ることができます。

OBS操作等も簡略化できることから、その名の通り、ストリーマーを中心に人気の商品です。しかし、アプリケーションの起動や、キーボードショートカット、その他様々な操作を登録することができるため、一般のユーザーにもその人気は広がっています。


www.elgato.com

とにかく作ってみる

Stream Deckは便利そうな反面、少しお高めです。なので自作します。

 

方針としては、M5Stack Core2上のボタンを押すとボタンに固有の文字列がPCに送信され、それをトリガーとして各種の動作を実行させます。

 

プログラムは

shizenkarasuzon.hatenablog.com

を参考にしています。通信の方針はこちらの記事に沿うものですので、通信に関する内容はこちらを参照してください。

UDP(User Datagram Protocol)

UDP(User Datagram Protocol)は通信方式(プロトコル)の一つです。よく対比されるものとしてTCP(Transmission Control Protocol)があります。UDPTCPに比べパケットロスが発生しやすいものの、TCPよりも速く通信することができます。

 

開発環境

・Windows11

Visual Studio Code 1.63.2 (Python3.10.1)

Arduino IDE 1.8.15

 

PCのIPアドレスの確認

まず、PCのIPアドレスを確認します。

WindowsではコマンドプロンプトMacではターミナル上で、ipconfigと入力して実行します。

実行結果のIPv4 アドレスが今回使用するPCのIPアドレスとして用いる値です。

M5Stack Core2のプログラム(送信側):ArduinoC++

#include <M5Core2.h>
#include <WiFi.h>
#include <WiFiUDP.h>
#include <I2C_AXP192.h>


I2C_AXP192 axp192(I2C_AXP192_DEFAULT_ADDRESS, Wire1);

const char ssid = "WiFiSSID"; //接続するWiFiSSID
const char pass = "WiFiのパスワード"; //接続するWiFiのパスワード

WiFiUDP wifiUdp;
const char *pc_addr = "接続先のIPアドレス"; //接続先のデバイスIPアドレス
const int pc_port = 50007; //送信先のポート
const int my_port = 50008; //自身のポート

//Button Color
ButtonColors cl_on = {0x7BEF, WHITE, WHITE}; // タップ時の色(背景, 文字列, 枠)
ButtonColors cl_off = {BLACK, 0xC618, 0xC618}; //離した時の色(背景, 文字列, 枠)

//ボタン定義(X軸, Y軸, 横幅, 高さ, 回転, ボタンのラベル, 離した時の色, タッチ時の色)
Button btn_UiShig(50, 70, 100, 100, false , "UiShig", cl_off, cl_on);
//UiShig
 
Button btn_End(180, 3, 100, 60, false , "End", cl_off, cl_on);
//PC側のプログラム終了&M5stackの電源OFF
 
Button btn_PCSleep(50, 3, 100, 60, false , "Sleep", cl_off, cl_on);
//PCをスリープ
 
Button btn_TabMove_R(180, 70, 100, 100, false , "TabR", cl_off, cl_on);
//Ctrl+Tab
 

//ボタンイベント
void event_btn_UiShig(Event& e) {
  // ボタンが押された(指が離されたタイミング)時の処理
  wifiUdp.beginPacket(pc_addr, pc_port);
  wifiUdp.write('u');
  wifiUdp.write('i');
  wifiUdp.endPacket();
}

void event_btn_End(Event& e) {
  // ボタンが押された(指が離されたタイミング)時の処理
  //終了コマンド送信
  wifiUdp.beginPacket(pc_addr, pc_port);
  wifiUdp.write('e');
  wifiUdp.write('n');
  wifiUdp.write('d');
  wifiUdp.endPacket();
  delay(1000);
  axp192.powerOff();
}

void event_btn_PCSleep(Event& e) {
  // ボタンが押された(指が離されたタイミング)時の処理
  wifiUdp.beginPacket(pc_addr, pc_port);
  wifiUdp.write('P');
  wifiUdp.write('C');
  wifiUdp.write('S');
  wifiUdp.write('l');
  wifiUdp.write('e');
  wifiUdp.write('e');
  wifiUdp.write('p');
  wifiUdp.endPacket();
}

void event_btn_Twitter(Event& e) {
  // ボタンが押された(指が離されたタイミング)時の処理
  wifiUdp.beginPacket(pc_addr, pc_port);
  wifiUdp.write('t');
  wifiUdp.write('w');
  wifiUdp.write('i');
  wifiUdp.write('t');
  wifiUdp.write('t');
  wifiUdp.write('e');
  wifiUdp.write('r');
  wifiUdp.endPacket();
}

void event_btn_TabMove_R(Event& e) {
  // ボタンが押された(指が離されたタイミング)時の処理
  wifiUdp.beginPacket(pc_addr, pc_port);
  wifiUdp.write('t');
  wifiUdp.write('a');
  wifiUdp.write('b');
  wifiUdp.write('R');
  wifiUdp.endPacket();
}

void setup() {
  M5.begin();

  //---------------Button------------------
  M5.Buttons.setFont(FSSB12); // 全てのボタンのフォント指定
  //btn_UiShig.setFont(FSSB24); //ボタンのフォントを個別指定したい場合
  btn_UiShig.addHandler(event_btn_UiShig, E_RELEASE); //イベント指定
  btn_End.addHandler(event_btn_End, E_RELEASE);
  btn_PCSleep.addHandler(event_btn_PCSleep,  E_LONGPRESSED);
  btn_PCSleep.longPressTime = 2000;
  btn_TabMove_R.addHandler(event_btn_TabMove_R, E_RELEASE);
  M5.Buttons.draw(); // 全てのボタンを描画
  //---------------------------------------

  WiFi.begin(ssid, pass);
  while ( WiFi.status() != WL_CONNECTED) {
    delay(2000);
    WiFi.begin(ssid, pass);
  }
  M5.Lcd.setCursor(0, 230);
  M5.Lcd.setTextSize(1);
  M5.Lcd.println("WiFi connected");
  wifiUdp.begin(my_port); //UDP通信の初期化処理
}

void loop() {
  M5.update();
}

 

PCのプログラム(受信側):Python

import sys
from socket import *
import time
import webbrowser
import ctypes
import pyautogui

print("start network")
addr = ("",50007) #ポートの指定

print("network setup started")
UDPSock = socket(AF_INET, SOCK_DGRAM)
UDPSock.settimeout(0.0001)
print("connected.")
print("Network info -->" + str(addr) )
UDPSock.bind(addr)

print("Setup finished.")

while True:
    try:
        (data, addr) = UDPSock.recvfrom(1024) #UDP受信受けつけ
    except timeout:
        continue

    if addr != 0:
        str_data = data.decode('utf-8')
        print(f"get massage from {addr} --> {str_data} ") #受信情報表示

    if str_data == "ui": #UiShig
        webbrowser.open(url)  
   
    if str_data == "tabR": #ctrl+Tab
        pyautogui.keyDown('ctrl')
        pyautogui.press('tab')
        pyautogui.keyUp('ctrl')

    if str_data == "PCSleep": #PCをスリープする
        ctypes.windll.PowrProf.SetSuspendState(0, 1, 0)
   
    if str_data == "end": #プログラム終了
        break

print("end.")

プログラムの流れ

それぞれのプログラムの流れをざっくりとまとめると

〇送信側(M5Stack Core2)

UDP通信設定

・ボタンの定義

    -UiShig: "ui"と送信

    -End: "end"と送信し、1秒後にM5Stackの電源OFF

    -TabR: "tabR"と送信

    -Sleep: 長押しで"PCSleep"と送信

■setup()

・ボタンのイベント実行タイミング

WiFi接続の開始

■loop()

・画面情報の更新

 

〇受信側(PC)

UDP通信設定

■While

・受信待機

    - "ui" と受信: Uishigを表示

    - "tabR" と受信: CTRL+TAB とキータイプ

    - "PCSleep"と受信: PCをスリープさせる

    - "end"と受信: while文から抜け、プログラムを終わる

 

それでは実行

プログラムが完成したところで実行してみます。

実行画面

M5Stack Core2を起動すると以下のようにボタンが表示されます。

WiFiへの接続が確立するとWiFi connectedと表示されます。

f:id:argoship25:20211222232822j:plain

M5Stack Core2の実行画面

さらに、PC(受信側)のほうでもプログラムを実行します。

start network
network setup started
connected.
Network info -->('', 50007)
Setup finished.

これでPC側も待機状態になりました。

 

動かす

これでいつでも緊急脱出できますね。

おわりに

いかがだったでしょうか。

無事Stream Deckのようにボタンを押してPCを操作することができました。

しかも、本家とは異なり、PC前にいなくともPCを操作することが可能となりました!

(それはもはやStream Deckじゃなくてリモコンなのでは...?)

ただ物理ボタンでなかったり、自由度が高い反面行わせたい動作によっては複雑なプログラムになるなど、やはり既製品にはない困難もあります。

 

しかし、M5Stackはライブラリが充実しているほか、扱いやすいArduinoや事前知識ゼロからでもプログラムできるUiFlowを使用することができるので、手ごろにIoTを体験してみたい方はぜひ挑戦してみてください。

多くの方が様々なものを作っていますので、まずは覗いてみるのもいいかもしれません。

 

今回は、WEBページの表示やシャットダウンという操作だけでしたが、プログラムを書き換えることで様々な動作を実現することができます。

今後は相互的にUDP送受信を行わせたり、マウス開発との連携も行っていきたいです。最も今回作成したようなコントローラーはマウスには不要なのですが。

 

明日、クリスマスイブの記事は、aluminumくんの

CADデータをクラウド管理してみた

です。

CADデータのクラウド管理については考えたことがありませんでしたが、たしかに内蔵メモリに保存して消えたときには目もあてらませんね。精神的ダメージが半端ない...

季節柄きっと絵師様の気持ちを理解できるに違いありません。

 

それでは。

 

蛇足:

ういしぐれはいいぞ

www.youtube.com