スライムライトで家族の在宅状況を可視化!木製台座とSwitchBotで作る

SwitchBot

家族の在宅状況を、スマホを開かずに確認できるようにしたい。そこで、4色のクリアフィギュアとSwitchBotプラグミニを組み合わせたスライムライトを作りました。家族それぞれにスライムを割り当て、在宅データと室内の明るさに応じて点灯・消灯します。

この記事では、アカシア集成材の木箱を使ったライトの製作と、既存のPython・InfluxDB環境への連携を紹介します。対象は、市販の電球・ソケットを使った工作と、Linux上の自作スマートホームを組み合わせたい方です。プログラムは既存プロジェクトの共通ライブラリを使うため、単独では実行できません。

在宅判定の準備にはDECOルーターから家族の在宅状況を取得する記事、設定・ログ・データベース連携にはPythonスマートホームの共通基盤設計を参照してください。ルーターの機種やファームウェアによって接続情報の取得方法は異なります。

なぜこの機能を作ったのか?

家族が同じ家にいても、それぞれ別の場所で過ごしていると、誰が帰宅しているのか分かりにくいことがあります。在宅情報をデータベースに保存できても、確認するたびに画面を開く必要があれば、日常のちょっとした確認には手間がかかります。

そこで、データを家族が自然に目にする形へ変えることにしました。4体のスライムを並べれば、アプリの操作を覚えなくても、決めておいた色と点灯状態から在宅状況の目安を読み取れます。木箱の中に電源部品をまとめ、外からはスライムと木目が見える形にしています。

家族が画面を開かずに、スライムの点灯だけで在宅状況が分かればいい!

実現したいこと

  • アプリを開かずに、家族それぞれの在宅状況の目安を確認できる
  • 家族ごとに対応するライトを個別に点灯・消灯できる
  • 室内が暗くなったときは、在宅中でも全てのライトを消灯できる
  • 既存の在宅データを活用し、表示側で新たな位置情報を取得せずに運用できる

この記事でわかること

  • 木箱・天板・クリアフィギュアを使ったライトの組み立て方
  • 4灯を個別に制御するための電源の接続順
  • InfluxDBの在宅データと照度データを組み合わせる方法
  • 5秒周期の制御と差分送信の仕組み
  • 誤判定・通信失敗・データ更新停止を確認するポイント

必要な準備と用意するもの

ハードウェア

ハードウェア
材料・機器数量の目安役割・区分
ドラゴンクエスト ビッグクリアフィギュア4体光を透過する表示部分。必須。色違いを用意
木材:アカシア集成材木箱・天板分部品を収める台座。別の木材・筐体で代替可能
三辺コーナー金具(ブロンズ・長さ2.4cm、12個セット)取り付け位置に応じた個数木箱の角の装飾・保護。任意
ELPA LEDナツメ形 E12 昼白色 LDT1N-G-E12-G1004個フィギュアを下から照らす。必須
オーム電機 HS-LCL12-W(09-1839)4個スイッチ付きE12電球ソケット。必須
SwitchBot プラグミニ(日本向け)4個各電球の電源を個別に切り替える。必須
サンワサプライ TAP-EX4WN1本電源を4系統へ分岐する。適合する電源分岐が必須
Linuxサーバー・家庭内ネットワーク既存環境在宅判定・データ保存・ライト制御。自動連携には必須
照度を取得できるSwitchBotコンタクトセンサーと対応ハブ既存環境室内の明暗判定。掲載ソースのまま使う場合は必須
工具・取り付け用品必要量のこぎり、ドリル、やすり、ねじ、固定具など。木材寸法に応じて用意

製品名は使用材料として提示されたものです。購入リンクやアフィリエイトリンクは含めていません。価格・木材の寸法・穴径は未確認のため、実際の部品を採寸して決めてください。

電球はメーカー仕様でE12・AC100V・0.5W・昼白色相当です。4灯の電球分は合計2Wですが、プラグミニやサーバーの消費電力は別途加わります。ELPAの公式仕様を確認してください。ソケットはE12・10Wまでの電球用です。オーム電機の公式仕様に従って使用します。

ソフトウェア/サービス

ソフトウェア/サービス
  • Python 3系と既存の仮想環境(実機のバージョンは未確認)
  • InfluxDBとプロジェクトのinflux.v2クライアント・Writer(サーバーの版は未確認)
  • Bootstrap、smarthomelog、switchbotの自作共通ライブラリ
  • SwitchBotアプリとSwitchBot Open API v1.1
  • Linuxのsystemd(常駐・自動再起動に使用)

在宅データの保存先はfamily_device、照度はcontact_sensorです。データ収集についてはSwitchBotのデータをInfluxDBに記録する記事を参照してください。Open APIの公式資料では1日10,000回の呼び出し制限が案内されています(2026年10月12日確認)。他のプログラムも含めた利用量を管理します。サービスの契約・課金条件は各アカウントで確認してください。

完成イメージ

木製の台座に青・黄・赤・緑のクリアスライム4体を並べた完成外観
4色のスライムを並べた完成外観。左から青:僕、黄色:妻、赤:娘、緑:息子です。
区分スライムの色対応する家族
A青僕
B黄色妻
C赤娘
D緑息子

フィギュアの色は固定です。下の昼白色LEDをオン・オフすることで、対応するスライムを照らします。1つのカラー電球で色を変える方式とは異なり、4体をそれぞれ独立して制御する構成です。

在宅データ室内の明るさライト
直近1分以内に接続ありの記録があるbright点灯
直近1分以内に接続ありの記録がないbright消灯
在宅記録の有無を問わないdark または unknown消灯

消灯しているから外出中、とは限りません。夜間、スマホの電池切れ、Wi-Fi切断、データ収集停止でも消灯します。在宅ライトは生活の補助表示として使い、安否確認や防犯上の判断は別の手段で確かめます。

システムの仕組み

家庭内端末の接続確認(DECO・ping・ARP)
  → 在宅判定プログラム
  → InfluxDB:family_device

室内の照度センサー
  → センサーデータ収集
  → InfluxDB:contact_sensor

ライト制御(5秒ごとに参照)
  → 在宅の目安 + 室内が明るい
  → SwitchBotクラウドAPI
  → 対応するプラグミニ
  → E12電球 → クリアスライム

在宅判定側は、DECOの接続情報・ping疎通・ARP確認のいずれかで接続が確認できれば、is_lan_connectedを真として記録します。ライト側は、直近1分の記録に真があるメンバーを抽出します。スマホの接続状態を人の在宅の目安として扱う仕組みです。

接続判定とInfluxDBは家庭内のサーバーで扱い、ライトの操作は認証付きのSwitchBotクラウドAPIへ送ります。照度センサーもクラウド経由で取得する場合は、その通信が家庭内ネットワークの外へ出ます。在宅DBをインターネットに公開する必要はありません。クラウドにはプラグの操作時刻が伝わるため、家族と用途・表示場所を共有したうえで使います。

実装のポイント

1人につき1つのプラグを割り当てる

共通の電源を1つのスマートプラグで切ると、4灯が一斉に点灯・消灯します。個別表示には、分岐した先へそれぞれプラグミニを配置します。接続順は家庭用コンセント → 4分岐コード → 各プラグミニ → 各ソケット → 各電球です。

表示の判定と帰宅・外出イベントを分ける

この制御が参照するのは接続状態であり、帰宅・外出イベントの確定結果ではありません。また「直近1分に真の記録がある」という判定なので、最後の接続記録は最大約1分残り、更新の遅延も加わります。ARPキャッシュやWi-Fiの接続情報の遅れも影響するため、5秒周期で確認していても5秒で必ず切り替わるわけではありません。

ほかの方式との比較

市販部品で制御回路を作らずに済む
E12電球・ソケット・スマートプラグの組み合わせで実装でき、独自のLED制御回路は不要です

点灯・消灯の2状態のみ
RGB LEDとマイコンを使う方法なら点滅や「判定不明」の表現も追加しやすくなりますが、この構成では対応できません

クリアフィギュアを台座に載せる方法は、電球との接触・放熱・適合を確認したうえで採用してください。

事前準備

木材の加工と電球の取り付けは、電源プラグを抜いた状態で行います。部品を仮置きして、箱の内寸・穴の位置・配線の取り回しを決めてから加工してください。写真から正確な寸法は読み取れないため、数値は指定しません。ソケットやスマートプラグを分解したり、電源コードを切って接続したりする手順は含みません。

市販の足元灯を木箱に収め、上に樹脂製フィギュアを載せる用途は、各製品が想定する使用方法と一致するか確認が必要です。電球と樹脂・木材が接触しないこと、放熱・交換・電源遮断ができることを確認し、適合を判断できない場合はこの収容方法で使用しないでください。

ソフトウェアは既存の設定と制御ファイルをバックアップし、まず手動で4灯を試します。自動制御を止めればSwitchBotアプリでの手動操作に戻せます。既存の在宅判定・センサー収集は停止せず、ライト用のサービスだけを操作してください。

実装方法

木箱と天板を作る

アカシア集成材の木箱と4つの穴を開けた天板
木箱と、電球の光を通す4つの穴を開けた天板。

箱の中に4組の電球・ソケット・プラグミニと分岐コードが収まるように設計します。天板では、4体のフィギュアがぶつからない間隔で穴の中心を決めます。電球の高さ、フィギュア底面の形状、光の通り方を実物で確認し、穴を開けたら縁をやすりで整えます。

木箱の角に取り付けたブロンズ色の三辺コーナー金具
コーナー金具で木箱の角を装飾。ねじ先が内部の配線に触れない長さを選びます。

4組の電球とプラグを仮配置する

木箱内に仮配置した4組のE12電球・ソケット・スマートプラグ
部品の大きさと天板の穴位置を合わせます。機器表示は予防的にモザイク処理しています。

E12電球をソケットに取り付け、プラグミニと分岐コードの接続順を確認します。ソケットの手動スイッチは自動制御時にオンにします。取り付け位置は、天板を外した状態で電球とプラグの操作部へアクセスできるように決めます。

配線を収め、天板を合わせる

木箱内に4灯分のソケット、プラグミニ、分岐コードを収めた配置
内部配置の参考。ケーブルを挟まず、部品が動かないように固定します。

コードを無理に曲げず、差し込みが緩まない位置に収めます。配線の出口は角を滑らかにし、必要に応じて保護材を使います。放熱のための空間を確保し、ねじや固定具がコードを傷つけないようにしてください。分岐コードの公式仕様では4口合計1500W以下とされていますが、接続機器のうち最も厳しい定格・使用条件を守ります。

内部の電球位置を天板の4穴に合わせた木製台座
天板の穴と電球の位置を合わせた状態。電球が天板へ接触しないことを確認します。

元の製作写真には電球の周囲に金属箔を置いた試作もありますが、この記事ではその工程を採用していません。導電性の反射材が端子や金具に触れたり、放熱を妨げたりしない構成にしてください。

フィギュアを並べ、1灯ずつ確認する

フィギュアを天板へ並べ、落下・転倒しないように設置します。我が家では、青を僕、黄色を妻、赤を娘、緑を息子に割り当てています。名前を外側へ書かず、色と並び順で見分ける形です。写真は外観の記録であり、撮影時の在宅状況を示すものではありません。

4体のスライムライトを棚に設置した外観
棚に設置した外観。周囲の生活背景は切り取っています。

在宅・照度データと設定を用意する

既存環境でfamily_deviceにtag_ownerと真偽値のis_lan_connectedが入り、contact_sensorにbrightnessが入っていることを確認します。設定キーだけを下に示します。実際の設定ファイルへ追加する際は、他の項目を消さずに統合してください。

{
  "switchbot": {
    "device_id": {
      "member_a_slime_plug": "<plug-a-device-id>",
      "member_b_slime_plug": "<plug-b-device-id>",
      "member_c_slime_plug": "<plug-c-device-id>",
      "member_d_slime_plug": "<plug-d-device-id>",
      "room_brightness_sensor": "<brightness-sensor-device-id>"
    }
  }
}

トークン・シークレット・デバイスIDは公開せず、各自の環境で設定します。実名やデバイスIDとの対応は家庭内だけで管理してください。以下の掲載コードは匿名化しているため、DB側のtag_ownerが実名のままなら、対応表の左側をローカルで合わせる必要があります。

ライト制御の主要部分を確認する

should_be_on = member in present_members and is_living_bright

if plug_state[member] != should_be_on:
    command = json_data_turnOn if should_be_on else json_data_turnOff
    switchbot_instance.execute_command(device_id, command)
    plug_state[member] = should_be_on

在宅の目安と室内の明るさを組み合わせ、前回送信した状態と変わった場合にだけAPIを呼び出します。この変数は実際のプラグ状態を読み戻したものではありません。通信失敗や手動操作後のずれについては、後述の制約を確認してください。

制御コード(既存プロジェクト用)

以下は提示されたfamily_slime_light_control.pyを基に、家族名を「僕・妻・娘・息子」に置き換え、設定キー・ログ内の個人識別情報を匿名化した掲載版です。制御ロジックは元ソースに合わせています。Bootstrap、InfluxDBクライアント、Writer、ログ、SwitchBotラッパーを事前に用意する必要があり、このコードだけでは動きません。

# -*- coding: utf-8 -*-
__group__ = '省エネ'
__description__ = '家族の在宅状況に応じてスライムプラグのON/OFFを制御'
# ------ 全Pythonプログラム共通の宣言 ----------------------------
from pathlib import Path
import sys
# プロジェクトのルートフォルダを取得
PROJECT_ROOT = Path(__file__).resolve()
while PROJECT_ROOT.name != "smarthome":
    if PROJECT_ROOT.parent == PROJECT_ROOT:
        raise RuntimeError("smarthome project root not found")
    PROJECT_ROOT = PROJECT_ROOT.parent
# Bootstrapをimportするためだけにプロジェクトルートをパスに追加
sys.path.insert(0, str(PROJECT_ROOT))
# BootstrapでPathの設定と設定ファイルの取得
from libs.bootstrap import Bootstrap
bootstrap = Bootstrap(__file__)
settings = bootstrap.settings
# ------------------------------------------------------------
import time
from influx.v2 import client as influxclient
from influx.v2 import writer as influxwriter
import switchbot
import smarthomelog
from typing import Dict, Optional, Set

try:
    influx_client = influxclient
    influx = influxwriter.Writer()
    success_log = smarthomelog.Log(__file__, smarthomelog.LogType.SUCCESS)
    influx.insert_execution_log(__file__, 'Start Slime Plug Control!')
    switchbot_instance = switchbot.Switchbot()

    # 家族メンバー(InfluxDB上のowner名)とスライムプラグのDeviceIDの対応
    MEMBER_PLUG_MAP = {
        '僕':  settings['switchbot']['device_id']['member_a_slime_plug'],
        '妻':  settings['switchbot']['device_id']['member_b_slime_plug'],
        '娘':  settings['switchbot']['device_id']['member_c_slime_plug'],
        '息子': settings['switchbot']['device_id']['member_d_slime_plug'],
    }

    json_data_turnOn = {
        'command': 'turnOn',
        'parameter': 'default',
        'commandType': 'command'
    }
    json_data_turnOff = {
        'command': 'turnOff',
        'parameter': 'default',
        'commandType': 'command'
    }

    def get_present_family_members() -> Set[str]:
        # 直近1分以内にis_lan_connected==Trueのレコードが存在するメンバーを在宅とみなす
        res = influx_client.query_dict_by_influxql(
            'family_device', ['is_lan_connected'], None,
            query_mode=influxclient.QueryMode.RAW,
            start_time='-1m',
            groupby=['tag_owner']
        )
        return {r["tag_owner"] for r in res if r.get("is_lan_connected") and "tag_owner" in r}

    # 前回送信したプラグ状態を記録 (None = 初回未送信)
    plug_state: Dict[str, Optional[bool]] = {member: None for member in MEMBER_PLUG_MAP}

    while True:
        present_members = get_present_family_members()

        # 室内の明るさを取得(消灯中はスライムプラグもOFF)
        res_lav = influx_client.query_dict_by_influxql(
            'contact_sensor', {'brightness'},
            {'deviceId': settings['switchbot']['device_id']['room_brightness_sensor']},
            query_mode=influxclient.QueryMode.LAST
        )
        brightness = res_lav[0].get('brightness') if res_lav else 'unknown'
        is_living_bright = (brightness == 'bright')

        for member, device_id in MEMBER_PLUG_MAP.items():
            should_be_on = member in present_members and is_living_bright
            current_state = plug_state[member]

            if current_state != should_be_on:
                if should_be_on:
                    switchbot_instance.execute_command(device_id, json_data_turnOn)
                    influx.insert_execution_log(__file__, f'{member}が在宅 → スライムプラグをONにしました。')
                else:
                    switchbot_instance.execute_command(device_id, json_data_turnOff)
                    if member not in present_members:
                        influx.insert_execution_log(__file__, f'{member}が外出 → スライムプラグをOFFにしました。')
                    else:
                        influx.insert_execution_log(__file__, f'室内消灯 → スライムプラグをOFFにしました。')
                plug_state[member] = should_be_on

        success_log.write('処理が正常に終了しました。', __file__)
        time.sleep(5)

except Exception as e:
    log = smarthomelog.Log(__file__, smarthomelog.LogType.SYSTEM_ERROR)
    log.write_error(sys.exc_info())

実行場所は、共通ライブラリを読み込める既存のLinuxサーバーです。仮想環境のPythonパスとプロジェクトの保存場所を自分の環境へ変更してから実行します。成功時はエラーなく処理が続き、条件に対応するライトが切り替わります。標準出力を抑えているため、定期ログが画面に出るとは限りません。

/home/<username>/Projects/venv/bin/python -u /home/<username>/Projects/smarthome/core/display/family_status/family_slime_light_control.py

コードの解説

直近1分の「接続あり」を抽出する

get_present_family_members()はstart_time="-1m"で生データを取得し、真のis_lan_connectedがある所有者を集合にします。最新レコードだけを見る処理ではないため、直近の真から偽へ変化しても、真の記録が期間内に残っている間は点灯候補になります。

明暗と差分送信

照度がbrightのときだけ点灯を許可します。データが空ならunknownとなり全灯を消灯する判定です。初回は前回状態がNoneなので4灯へ指示を送り、その後は変化のあるプラグだけを操作します。照度データは最新値を取得しますが、元コードには記録時刻の鮮度確認がありません。

通信失敗と現状の制約

元ソースと共通ラッパーには改善の余地があります。コマンド送信はHTTP応答を返しますが、呼び出し側では成功を確認せずに状態を更新しています。そのため、失敗していても送信済みと見なし、同じ条件のままでは再送しない場合があります。現状を「確実に状態が同期する」とは扱わないでください。

  • タイムアウト:現在のコマンド送信に明示的なtimeoutがないため、HTTP待ちが長引く可能性がある。運用を強化する際は共通ラッパー側で上限を設ける
  • 応答確認:HTTPステータスとAPIのstatusCodeを確認し、成功後にだけ前回状態を更新する
  • 再試行:通信エラーは上限付きで間隔を空け、認証エラーは設定を直してから再開する。無制限の再送は避ける
  • データ鮮度:在宅記録の停止は約1分後に消灯判定につながる。一方、古いbrightはそのまま使われるので、照度の時刻も確認する
  • 実状態のずれ:プラグを手動操作した場合や停電後は、状態の読み戻しや再送による復帰を検討する

例外が発生すると外側のexceptでエラーログを書き、プログラムは終了します。既存サービスにはRestart=alwaysとRestartSec=60が設定されていますが、例外処理まで到達しない通信待ちは、この設定だけでは解消しません。ログには在宅パターンが残るため、アクセス権と保存期間を管理し、公開時には氏名・ID・実時刻を除きます。

動作確認

まず自動制御を止めた状態で、SwitchBotアプリから1灯ずつオン・オフします。対応する電球だけが切り替わること、異常な熱・臭い・ちらつきがないことを確認してください。安全な収容・放熱を確認できた場合に自動制御へ進みます。

確認する状況確認方法期待する結果
接続あり・室内brightDBに対象メンバーの新しい真の記録があることを確認対象ライトが点灯する
接続なし・室内bright真の記録が直近1分からなくなったことを確認対象ライトが消灯する
室内dark照度の更新を確認在宅中でも全灯が消灯する
異常系:在宅データ収集停止収集プログラムをテスト環境で止め、約1分以上経過後に確認消灯しうることを確認し、外出と区別できない制約を把握する
異常系:プラグをオフラインにするアプリの接続状態・制御ログと実灯を比較指示と実灯の不一致が発生しうる。接続復旧後、制御を再起動して再確認

既存のサービスを登録済みの場合、Linuxサーバーで次のコマンドを実行します。停止・再起動には管理者権限が必要です。これらはライト制御だけを対象にします。サービス未登録の場合は、まず直接実行で確認してください。

systemctl status smh-family-slime-light-control.service
journalctl -u smh-family-slime-light-control.service -n 50 --no-pager

# ライトの自動制御を停止する
sudo systemctl stop smh-family-slime-light-control.service

# 接続や設定を直した後、再開する
sudo systemctl restart smh-family-slime-light-control.service

エラーは自作ログ側に出る場合もあるため、journalに表示されなければsmarthomelogの出力先も確認します。直接実行中はCtrl+Cで停止できます。停止してもプラグは直前の状態を保持する場合があるので、消灯したいときはアプリで各プラグをオフにしてください。設定を戻す際は、停止後にバックアップを復元します。

トラブルシューティング

症状確認方法・コマンド想定原因対処
4灯とも光らないアプリで手動オン・オフ/照度データ確認ソケットのスイッチがオフ、brightでない、電源未接続電源・スイッチ・照度収集を順に確認
違うスライムが点灯する1灯ずつ手動操作し、対応表と照合デバイスIDと家族の対応が逆設定の対応を直して制御を再起動
外出しても点灯が残るfamily_deviceの時刻と真の記録を確認1分間の保持、接続情報やARPの遅れ保持期間と接続判定を見直す。更新時刻も確認
帰宅中なのに消灯する端末のWi-Fiと照度・DB更新を確認Wi-Fi切断、端末不携帯、dark、収集停止接続と収集を復旧し、ライトを在宅の補助表示として扱う
プログラムが終了するsystemctl status/自作エラーログ共通ライブラリ不足、設定キー不一致、DB接続失敗共通基盤と設定を確認し、使用中の仮想環境で実行
通信復旧後も表示が戻らないアプリの実状態と前回送信状態を比較失敗後も状態を更新、手動操作によるずれ再起動で初回送信を行い、応答確認・実状態同期を改善

まとめ

  • 4体のクリアスライムを、市販のE12電球とプラグミニで個別に照らす台座を製作できる
  • 既存の在宅データと室内の明暗から、家族が見て分かる表示へつなげられる
  • 消灯は外出の確定を意味しない。データ鮮度・通信失敗・設置方法を確認して運用する

在宅データの準備から取り組む場合は次の記事を参照してください。

照度などのセンサーデータを収集するには次の記事を参照してください。

共通ライブラリの構成を整えるには次の記事が参考になります。

光で情報を伝える別の例として、消費電力でキングスライムの色を変える取り組みも紹介しています。在宅表示と電力表示を組み合わせると、画面を開く頻度を減らすスマートホームの工夫につながります。

公式資料:SwitchBot Open API/Plug Mini(JP)の仕様。APIのコマンド・認証・利用制限は、導入時にも現行資料を確認してください。

タイトルとURLをコピーしました