🎮

スタックチャン コントローラー作ってみた!

2024/10/07に公開

作ったもの

https://x.com/tenugui_taro/status/1839320237013188800

Webアプリケーションからスタックチャン[1]を操作するものを作ってみました!
現在、出来ることは次の通りです。

  • 感情の切り替え
  • 左右の動き
  • テキストの読み上げ
  • 物体検出

技術的な話

次のような構成で開発しています。

  • ① Webアプリケーション
  • ② サーバー
  • ③ スタックチャン

通信としては、①-②はREST API、②-③はWebSocketで行っています。

本記事では実装のコアな箇所を紹介します。
やっていることはシンプルなので、それぞれお好みで実装いただけるかと思います :)

① Webアプリケーション

やっていることはシンプルで、サーバーに対してPOSTリクエストを行なっています。
一例として、感情の切り替えのリクエストを行う処理を記載します。

/** サーバーのURL */
const SERVER_URL = "http://localhost:8080";

/** スタックチャンの感情リスト */
const EMOTIONS = ["NEUTRAL", "HAPPY", "ANGRY", "SAD", "SLEEPY"] as const;
type Emotion = (typeof EMOTIONS)[number];

/** スタックチャンの感情を更新する */
const postUpdateEmotion = async (emotion: Emotion) => {
  const url = `${SERVER_URL}/update-emotion`;
  const data = { emotion };

  try {
    const response = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(data),
    });

    const result = await response.json();
    console.log("Response:", result);
  } catch (error) {
    console.error("Error:", error);
  }
};

② サーバー

「REST APIかつWebSocketを扱える、なるべくシンプルに」を考えて、Denoで実装することにしました🦕
MDNのドキュメント、WebSocket サーバーを JavaScript (Deno) で書くを参考に、単一スクリプトで実装しました。

一例として、WebSocketのハンドラー、感情更新リクエストのハンドラーの処理を記載します。

server.ts
// deno run --allow-net server.ts

let sockets: WebSocket[] = [];

/** WebSocket通信を処理する */
function handleWebSocket(request: Request) {
  const { socket, response } = Deno.upgradeWebSocket(request);

  socket.onopen = () => {
    console.log("CONNECTED");
    sockets.push(socket);
  };

  socket.onmessage = (event) => {
    console.log(`RECEIVED: ${event.data}`);
  };

  socket.onclose = () => {
    console.log("DISCONNECTED");
    sockets = sockets.filter((s) => s !== socket);
  };

  socket.onerror = (error) => console.error("ERROR:", error);

  return response;
}

/** 感情の更新リクエストを処理する */
async function handleUpdateEmotionRequest(request: Request) {
  const { emotion } = await request.json();

  const message = { type: "update-emotion", emotion };
  for (const socket of sockets) {
    socket.send(JSON.stringify(message));
  }

  return new Response(JSON.stringify({ status: "emotion updated" }), {
    status: 200,
    headers: {
      "Content-Type": "application/json",
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Headers": "Content-Type",
    },
  });
}

function handleRequest(request: Request) {
  if (request.headers.get("upgrade") === "websocket") {
    return handleWebSocket(request);
  } else if (
    request.method === "POST" &&
    request.url === "http://localhost:8080/update-emotion"
  ) {
    return handleUpdateEmotionRequest(request);
  } else {
    return new Response("Hello, Deno!", {
      status: 200,
      headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Headers": "Content-Type",
      },
    });
  }
}

Deno.serve({
  port: 8080,
  handler: handleRequest,
});

Denoをインストール後、下記コマンドでサーバーが起動します。

$ deno run --allow-net server.ts

③ スタックチャン

Moddable版で実装していますが、WebSocket通信で受け取ったデータを処理しているだけなので、他言語でも実装できるかもしれません ;)

Cheerup: スタックチャン応援団のWebSocket版を参考に、modとして実装しました。

firmware/mods/ws-client/mod.js
import WebSocket from 'WebSocket'

/** スタックチャンの感情リスト */
const EMOTIONS = ['NEUTRAL', 'HAPPY', 'ANGRY', 'SAD', 'SLEEPY']

function onRobotCreated(robot) {
  /** NOTE: PCのIPアドレスに置き換えてください */
  const ws = new WebSocket('ws://192.168.179.7:8080')
  ws.addEventListener('open', () => {
    trace('connected\n')
  })
  ws.addEventListener('message', (event) => {
    const data = JSON.parse(event.data)
    const type = data['type']

    // typeからリクエストの種類を判定
    if (type === 'update-expression') {
      const expression = data['emotion']
      if (EMOTIONS.includes(expression)) {
        trace('received: ' + expression + '\n')
        robot.setEmotion(expression)
      }
    }
  })
  ws.addEventListener('close', () => {
    trace('disconnected\n')
  })
}

export default {
  onRobotCreated,
}
firmware/mods/ws-client/manifest.json
{
  "include": ["$(MODDABLE)/examples/manifest_mod.json"],
  "modules": {
    "*": ["./mod"]
  }
}

Wifi接続する必要があるので、基本プログラムの書き込みでWifi情報を指定します。

$ npm run debug --target=esp32/m5stack_core2 ssid=your_ssid password=your_passaword 
$ npm run mod --target=esp32/m5stack_core2 ./mods/ws-client/manifest.json 

振り返り

Webアプリケーションからサーバー経由でスタックチャンにデータ送信する実装を紹介しました。
個人的には役割ごとに分割し、それぞれがシンプルな実装にできたかと思います。

参考

脚注
  1. スタックチャンはししかわさんが開発、公開している、 手乗りサイズのスーパーカワイイコミュニケーションロボットです。 作品ページ:https://github.com/stack-chan/stack-chan ↩︎

Discussion