📚

個人開発で会議中ランプを制作

2023/11/13に公開

はじめに

初めまして、ryotaです。
エックスポイントワンで働き始めて半年が経過した、まだまだ駆け出しのエンジニアryotaです。

日々わからないことだらけの私ですが、今回は会議中ランプを自作してみることにしました。
何かと不慣れな為、歪なところもあるかと思いますが、超絶大目に見てやってください😋

概要

まず今回使用するのがざっとこんな感じです。

  • ON AIR ランプ(よくみるやつ)
  • MaBeee(電池型ワイヤレス電源供給デバイス)

ぶっちゃけローカルアプリなのでそこまで難しくはありません。
というのも当初はAWSにデプロイすべくterraformを使用して作成していたのですが・・・

デプロイまでしたところでMaBeeeはlocalhostにパスをつけて扱う必要がある為、ネットワーク上からAPIを叩けないことがわかりました。よく考えたら当たり前でした(笑)
こういったあたり、まだまだ初心者ですねぇ🙃

そんなこんなで、全てを無かったことにして、一から作り直しました(笑)

MaBeeeとは

まず今回の主役MaBeeeについて。
https://mabeee.mobi/
こんな感じで〜す。読んでくださいませ。

MaBeeeはお子様たちが携帯アプリで、某電車型おもちゃ、某四駆のおもちゃ等にこの商品を搭載し、速度を変えたりして遊べるという商品です。そう、子供の時にあったら即買いの代物なのです。もちろん買うのは親です。

そしてありがたいことに開発者向けにAPIを叩けるアプリを作ってくださった方がいらっしゃいまして。
https://github.com/novars-jp/MaBeeeMacApp
今回はこちらからクローンしてGetしたMaBeee.appに指令を出す形で使用します。
自作アプリからMaBeeeAPPのAPIを叩くって感じです。詳しくは後ほど。

構成

├── app.py
├── static
│   ├── image
│   │   └── back.jpg
│   ├── style.css
│   └── main.js
└── templates
    └── top.html

今回はこのような構成になりました。

内容

MaBeeeアプリを実行すると、http://localhost:11111/でWebサーバーが起動します。
このWebサーバーにHTTPリクエストを送信することで、MaBeeeを通電させたり電圧を変えたりすることが可能です

・・・であれば、このHTTPリクエストを一々コマンド打つのは面倒なのでアプリ化してしまおう!というのが今回の大まかな内容となります。

app.py

import subprocess
import asyncio
import requests

from flask import Flask, render_template, jsonify

app = Flask(__name__)

# 非同期でリクエストを送信する関数
async def send_requests_and_continue(urls):
    for url in urls:
        response = requests.get(url)
        if response.status_code == 200:
            print(f"Accessed URL: {url}")
            await asyncio.sleep(0.3)  # 0.3秒待機
        else:
            print(f"Failed to access URL: {url}")

@app.route("/")
def launch_mabeee():
    try:
        command = "open -a Mabeee"
        subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        return render_template('top.html')
    except Exception as e:
        return f"エラー: {str(e)}"

@app.route("/on")
async def start():
    # URLのリストを定義
    urls = [
        "http://localhost:11111/",
        "http://localhost:11111/scan/start",
        "http://localhost:11111/devices",
        "http://localhost:11111/devices/1",
        "http://localhost:11111/devices/1/connect",
        "http://localhost:11111/scan/stop",
        "http://localhost:11111/devices/1/set?pwm_duty=80",
        # 他のURLをここに追加
    ]
    # 非同期でリクエスト送信
    await send_requests_and_continue(urls)
    return jsonify(result='on')  # 'on'をJSON形式で返す

@app.route("/off")
async def off():
    urls = ["http://localhost:11111/devices/1/disconnect"]
    await send_requests_and_continue(urls)
    return jsonify(result='off') # 'off'をJSON形式で返す

if __name__ == "__main__":
    app.run(port=50000)

こんな感じでONの時OFFの時の指示をそれぞれ書いています。
今回は簡易アプリなので、初めてflaskを使ってみました。フロントエンドとバックエンド両方をチャチャっと実装出来るのでこの規模であれば非常に便利だなと思いました。
AWSでパスを設定するのに結構似ていますね。その点の理解が早くできたのはラッキーでした✨
https://msiz07-flask-docs-ja.readthedocs.io/ja/latest/

top.html

<!DOCTYPE html>
<html lang="jp">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>会議ランプ点灯アプリ</title>
    <link rel="stylesheet" href="/static/style.css">
</head>
<body>
    <h1>会議ランプ点灯アプリ</h1>
    <p class="discription">ボタンをクリックして操作してください。</p>
    <br>
    <div class="btn-switch btn-switch-wrap">
        <input type="checkbox" id="onoff" name="onoff" checked>
        <div class="btn-switch-bg">
            <label class="btn-switch-in" for="onoff" onclick="toggleSwitch()" >
                <span class="on">ON</span>
                <span class="off">OFF</span>
            </label>
        </div>
    </div>
</body>
<script src="/static/main.js"></script>
</html>

htmlは極々一般的な作りです。
今回電源のON、OFFはチェックボックスで判断するように構成しています。
flaskの決まり事としてhtmlはtemplatesディレクトリに配置するのがセオリーらしいです。

main.js

function toggleSwitch() {
    // クリックされた要素(label)から関連するcheckboxを取得
    var checkbox = document.getElementById("onoff");

    // Flaskから渡された結果を取得
    var url = checkbox.checked ? "http://127.0.0.1:50000/on" : "http://127.0.0.1:50000/off";

    fetch(url)
        .then(response => response.json())
        .then(data => {
            console.log("Action Result:", data.result);

            console.log("ボタンがクリックされました!");
            if (checkbox.checked && data.result === 'on') {
                console.log("Switching ON");
            } else if (!checkbox.checked && data.result === 'off') {
                console.log("Switching OFF");
            } else {
                console.error("Unexpected result:", data.result);
            }
        })
        .catch(error => console.error("Error:", error));
}

トップページからONとOFFそれぞれを叩いた時(チェックがついた時と外れた時)に、非同期通信を使用することで画面遷移をさせないようにしました。こうすることでページをリロードすることなく、サーバーとのデータの送受信が可能になります。

style.css

AWSを使用できないとわかった時からその鬱憤を晴らすべく、とにかくオシャレにしたかったので(笑)
こちらのサイトにあったコードを使わせていただきました✨
https://jajaaan.co.jp/css/button/
サイトにも

各コードはコピペしてご自由に使っていただいて構いません。

とあったのでありがたく頂戴いたしました。感謝。

こちらのコードはガッツリコピペなので、もし使われる際は上記のサイトからどうぞ。
ちなみに私はこれにプラスして壁紙を貼っつけたり、タイトルを入れたりしております。
そのあたりは皆様ご自由に👌

ちなみに

cssjs,imageディレクトリstaticディレクトリに格納しています。
これもflaskの仕様だそうです。

完成

完成したので起動してみたいと思います。

$ python app.py

こちらをターミナルで叩くと

 * Serving Flask app 'app'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:50000
Press CTRL+C to quit

こんな形で返ってくるのでURLhttp://127.0.0.1:50000に接続します。

そして接続したページがこちら。

いぇーーーーーーーーーーーーーーーーーーい🎊🎉

我ながら激的にオシャレに出来上がりました✨

もちろん会議中ランプもちゃんと点灯します💡やったぜぃ❗️

まとめ

初めてのアプリ制作でしたが当初とは仕様が変わったものの、それなりに作ることができました。
なによりそもそも当初の目的であった

会議中に家族の侵入を防ぐ

これは達成されそうです🙌
次はこちらのアプリをもっと起動しやすいようにデスクトップ上に設置できるようトライしてみたいと思っています。
次も頑張ろう❗️❗️

最後までお読みいただき、ありがとうございました✨

エックスポイントワン技術ブログ

Discussion