1. obnizを使ってあっちむいてホイ!をしたい。
電子工作楽しいですよね。
ド初心者なので、まだ思うようには作れませんが、
ikeuchiさん @IkeuchiRyota の"M5ABC"シリーズが好きで、よく参考にさせてもらっています。
今回Protopediaを見ていたところ、
電子工作でお子さんとゲームをしているものを見つけました。
動画の最後にお子さんが「面白い!」と言っているのがとても楽しそうで、私も電子工作で誰かと遊べるものを作るのにチャレンジしようと思います。
ちいかわのぬいぐるみをサーボで動かしたら可愛かったので、
これを使ってあっちむいてホイをすれば楽しいのでは?と思いつきました。
2. 制作
2-1. 今回使ったもの
- JavaScript Node.js
- obniz
- LINEMessagingAPI
- リッチメニュー
2-2. 実装計画
単身赴任中の夫とやりたかったので、LINEBotでサーボの通知が出るようにしようと思いました。
前提 実施する二人が通話している状態。
1. 夫がLINE側 あっちむいてホイをいう人 指をさす方
リッチメニューで『あっちむいてホイをしよう』ボタンを押すと、
右か左を選んでねとメッセージが来る。
2. 私がサーボ側 自分の顔の代わりにぬいぐるみを動かす方
ちいかわを括り付けたサーボモーターを準備して、obnizのボタンを押す準備
3. 夫のあっちむいてホイ!と声を合わせて
夫がみぎかひだりのリッチメニューのボタンを押す
私がobnizを使ってにみぎかひだりにサーボを動かしてぬいぐるみをぶん回す
4.同じ方向を向いたら夫の勝ち
違う方向を向いたら私の勝ち
すっごい気軽に考えてたけど、なんか難しそう。
2‐3.リッチメニュー画面
前回の記事でも使用した、リッチメニューエディターを使用して、
リッチメニュー画面を作りました。
LINEで、あっちむいてホイ!を押すと
「あっちむいてホイしよう!」とメッセージがでると
リプライメッセージで「あっちむいてホイ!といいながら『みぎ』か『ひだり』のボタンを押してね。」がきます。
もう一回ボタンつくりましたがリプライメッセージの実装はできてません。
2-4. コード
obnizから動いたらLINEに通知をするために、サーボの角度取得する部分が悪戦苦闘しました。
今回もお手本コードを教えてもらって、ChatGPTに何度も聞いて調整しています。
作成したコード
const Obniz = require('obniz');
const obniz = new Obniz('******');
obniz.onconnect = async function () {
// サーボモータを利用
const servo = obniz.wired('ServoMotor', { signal: 2 });
// 角度を保持する変数
let degrees = 90.0;
// 以下のコードを追加
obniz.switch.onchange = async function (state) {
// スイッチの状態で角度を決め、最後に動かします
let angleText;
if (state === 'right') {
// 右にスイッチを倒したとき
console.log('みぎ');
degrees = 0.0;
angleText = 'みぎ';
} else if (state === 'left') {
// 左にスイッチを倒したとき
console.log('ひだり');
degrees = 180.0;
angleText = 'ひだり';
} else {
// スイッチが押されていない状態
console.log('released');
degrees = 90.0;
angleText = 'released';
}
// ディスプレイに角度を表示
obniz.display.clear();
obniz.display.print(`Current: ${degrees} deg`);
// LINEに通知を送る
await sendResult(angleText);
// サーボを指定の角度まで動かします
servo.angle(degrees);
};
};
const config = {
channelSecret: '***************',
channelAccessToken: '************',
};
const line = require('@line/bot-sdk');
const client = new line.Client(config);
// サーボモータの角度取得
function getServoAngle() {
// 角度を取得する処理を実装する
}
// 角度情報を含めて結果を送信する関数
async function sendResult(angleText) {
try {
if (angleText === 'released') {
// "released"の場合は通知を送信せずに関数を終了する
return;
}
const angle = getServoAngle(); // サーボモータの角度を取得
const angleInfo = angle !== undefined ? `${angle}度` : '取得できませんでした';
await client.pushMessage('*****************', {
type: 'text',
text: `ちいかわは ${angleText}をむいたよ`,
});
console.log('通知が正常に送信されました');
} catch (error) {
console.error('通知の送信に失敗しました:', error);
}
}
// ExpressからMessaging APIイベントを渡されて処理するところ
const handleEvent = async (event) => {
// テキストメッセージ以外を受信したときは何も行わずresolveを返す
if (event.type !== 'message' || event.message.type !== 'text') {
return Promise.resolve(null);
}
// テキストメッセージを受信したとき
const sendToId = event.source.userId;
if (event.message.text === 'あっちむいてホイしよう!') {
// 待ってねというメッセージを「リプライ」で先に返す
await client.replyMessage(event.replyToken, {
type: 'text',
text: 'あっちむいてホイ!と言いながら『みぎ』か『ひだり』のボタンを押してね',
});
} else {
// メッセージの中身が「あっちむいてホイ」以外だったとき
const angle = getServoAngle(); // サーボモータの角度を取得
let replyText = '';
if (event.message.text === 'みぎ' && angle === 0.0) {
replyText = '勝ち!';
} else if (event.message.text === 'ひだり' && angle === 180.0) {
replyText = '勝ち!';
} else {
replyText = '残念、負け!';
}
await client.replyMessage(event.replyToken, {
type: 'text',
text: replyText,
});
}
// 角度情報を含めて結果を送信する
await sendResult(sendToId);
// resolveを返す
return Promise.resolve(null);
};
// ########################################
// Expressサーバー部分
// ########################################
const express = require('express');
const PORT = process.env.PORT || 3000;
const app = express();
// 「(サーバーURL)/webhook」にアクセス(LINEサーバーからのWebhook)があったとき
app.post('/webhook', line.middleware(config), (req, res) => {
// 受信したイベントをターミナルに表示
console.log(req.body.events);
// イベントをhandleEventに渡して1つずつ処理
Promise.all(req.body.events.map(handleEvent)).then(result => res.json(result));
});
// PORT番号のポートでサーバーを開始
app.listen(PORT, () => {
console.log('Express server running on PORT =', PORT);
});
const angle = getServoAngle(); // サーボ
3. 結果
現状はここまで出来ました。
3-1.LINEへに通知が難しかった。
obnizで角度を取得したらひだりかみぎをLINEに通知する部分。

取得は出来たのだけども動かしていない時の「released」も入っちゃうので以下のコードを入れて修正
if (angleText === 'released') {
// "released"の場合は通知を送信せずに関数を終了する
return;
3-2.勝ち負けを表示したい
今度はサーボ(ちいかわ)と相手のLINEのメッセージが同じなら勝ち、違う場合は負けというように、
リプライメッセージを設定
} else {
// メッセージの中身が「あっちむいてホイ」以外だったとき
const angle = getServoAngle(); // サーボモータの角度を取得
let replyText = '';
if (event.message.text === 'みぎ' && angle === 0.0) {
replyText = '勝ち!';
} else if (event.message.text === 'ひだり' && angle === 180.0) {
replyText = '勝ち!';
} else {
replyText = '残念、負け!';
}

しかし常に負け表示になってしまう。
3-3.遠隔であっちむいてホイをするのはむすかしい
あまりにも通知を送りすぎてAPIの429エラー状態になってしまったので、今回はここまででです。
ゲームを作る時はゲームのルールをしっかり分解できていないと、上手くいかないということがわかりました。
勝ち負け判定はメッセージと角度の取得タイミングもどうするのか考えないとクリアできなさそうです。
obnizはサンプルコードやコードブロックで動かした経験はあるものの、
LINEへの通知がしたかったので、Qiita記事のお手本を真似してみましたがうまくいかず。
お手本記事
私も本当はLチカまで本当はしたかった。
電子工作初心者にはまだハードルが高かった様子です。
電子工作の道は険しい。
とりあえず、ぶんぶん回る、ちいかわは可愛いかったです。