スタックチャン コントローラー作ってみた!
作ったもの
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のハンドラー、感情更新リクエストのハンドラーの処理を記載します。
// 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として実装しました。
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,
}
{
"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アプリケーションからサーバー経由でスタックチャンにデータ送信する実装を紹介しました。
個人的には役割ごとに分割し、それぞれがシンプルな実装にできたかと思います。
参考
-
スタックチャンはししかわさんが開発、公開している、 手乗りサイズのスーパーカワイイコミュニケーションロボットです。 作品ページ:https://github.com/stack-chan/stack-chan ↩︎
Discussion