九保すこひです(フリーランスのITコンサルタント、エンジニア)
さてさて、ウェブサイトと物理操作の連携が面白くてしょうがない今日この頃です。
前回【Laravel・SwitchBot】たった1万円台で入室管理システムを導入する方法という記事を公開しました。
これって、
ウェブサイト→物理的な動き
って流れだったんです。
つまり、ウェブサイトが主体。
でも、逆もやってみたくなったんですよね。
そう。
物理的な動き→ウェブサイト操作
です。
つまり、リアルな世界でアクションがあったら、その情報をウェブサイトに伝えるという流れです。
そこで購入したのが、
「SwitchBotの開閉センサー」
その名のごとく「開ける/閉めるを検知する」センサーです。

で、この開閉センサーをつかって何かできないか考えたところ思いつきました。
薬箱を開けた時間を記録して「飲み忘れ」を防ぐ機能をつくったら便利じゃないかと。
ということで、今回は以下のような人に向けて記事を書いています。
ぜひ最後まで読んでくださいね!
- 「リアルな動きをきっかけにしたウェブサイト操作がしたい」
- 「IoTとWebを組み合わせて、暮らしをもっと便利にしたい」
- 「SwitchBotで自分のアイデアを実現してみたい」
- 「SwitchBotをLaravelと連携させて、自動化のアイデアを形にしてみたい」
- 「SwitchBotのWebhookの使い方を知りたい」
- 「お薬管理を自動化できたら便利だと思う」
- 「つくったシステムを家族や友人にも使ってもらいたい」
- 「個人プロジェクトから副業に発展させたい」
- 「Laravelで業務アプリを作っているので参考にしたい」
- 「楽しみながら技術を深掘りしたい」

「私自身、お薬は
まったく飲んでません(元気!)」
目次
実装に必要なもの・実行環境
今回は以下2つの機械で実装します。
- SwitchBot開閉センサー:開け閉めを感知するセンサー
- SwitchBotハブミニ:開閉センサーをウェブ連携させるハブ
SwitchBot開閉センサー

SwitchBotハブミニ

また、実行環境は以下のとおりです。
- Laravel 12.x
- Vue 3
- Inertia
- TypeScript
では今回も楽しんでやっていきましょう!
SwitchBot開閉センサーをウェブにつなぐ
開封したらまず電池をいれて(説明書を見ながら)スマホアプリとペアリングしてください。

スマホアプリが開閉センサーを認識したら、手で動かしてみましょう。
以下のように開いた/閉じたを検知し通知してくれるはずです。

では、ハブミニも起動してインターネット経由でも有効になるようにしておいてください。
WebHookをつくる5ステップ
冒頭でも書いたとおり、今回は「物理的アクション→ウェブサイト」という流れになりますので、物理的アクションがあったことを知らせるWebHook(ウェブサイトへのデータ通知)が必要になります。
順を追って見ていきましょう!
1. アクセストークンを取得する
過去記事でまとめていますので、以下記事の「SwitchBot APIのトークンを取得する」を参照して.envにセットしておいてください。
2. デバイスIDを取得する
今回購入した開閉センサーの「デバイスID(機械固有の番号)」を取得します。
ペアリングした機械を選択すると右上に歯車マークがでてくるので、そこから「Device Info」を選択すると以下のようにデバイスIDが表示されます。

では、この文字列を.envにセットしておきましょう。
.env
SWITCHBOT_MOTION_SENSOR_DEVICE_ID="(ここにあなたのデバイスID)"
コンフィグも登録します。
config/services.php
<?php
return [
// 省略
'switchbot' => [
'access_token' => env('SWITCHBOT_ACCESS_TOKEN'),
'secret_token' => env('SWITCHBOT_SECRET_TOKEN'),
'devices' => [
'contact_sensor' => env('SWITCHBOT_CONTACT_SENSOR_DEVICE_ID'),
],
],
];
これで、どこからでもconfig('services.switchbot.devices.contact_sensor');でデバイスIDが取得できるようになりました。
3. Laravelのポート番号を取得する
次のngrokで必要になるので、以下コマンドを実行してポート番号を取得しておきます。
php arsan serve
すると、以下のような表示になるので「:」の後ろにある数字を覚えておいてください。
Server running on [http://127.0.0.1:8000]
※8000が基本だと思いますが、環境によって違うと思います。
4. ngrokでWebHookをローカルで受け取れるようにする
WebHookはインターネット上へのデータ送信のことですが、つまりそれはローカル環境には届かないことを意味します。
しかし、開発中だと毎回ウェブサーバーにアップロードするのは面倒です。
そこでngrokの「トンネル」といわれる技術をつかってWebHookをローカル環境で受信できるようにします。つまり、
- SwitchBotがngrokへデータ送信
- ngrokが橋渡し
- ローカル環境にWebHookが来る
という流れです。詳しくは、過去記事のおまけ: ngrok についてをご覧ください。
そして、インストールが完了したら以下コマンドを実行してください。
ngrok http (php artisan serveで取得したポート番号)
実際の例はこちら。
ngrok http 8000
すると、以下のような表示になるので、「Forwarding」の部分に書いてあるURLを覚えておいてください(キャンセルする場合はCtr + C)です。

※このURLにアクセスすると、ngrokがローカルに転送してくれるわけです。
そして、このURLはLaravelのトップページになるので、以下のようにルートつきのURLにしておきましょう。
【WebHook URL】
https://*********.ngrok-free.app/switchbot/webhook
このURLをWebHookに登録することになります。
5. WebHookを登録する
では、先ほど取得したngrokのURLにWebHookが送信されるよう登録をします。
SwitchBotのWebHookはAPIを通して実行するのですが、いちいちコードを書くのは面倒なので、Postmanをつかってサクッと実行しましょう。
※ちなみに、逆に言うとAPI以外でWebHook登録はできません。2025/11/07現在。
入力するのは、以下の項目です。
【リクエスト】
POST https://api.switch-bot.com/v1.1/webhook/setupWebhook
【Headers】
- Content-type:application/json(注:すでに入っていると思うので変更してください)
- Authorization:(あなたのアクセストークン)
【Body】
- action:setupWebhook
- url:(ngrokで取得したURL)
- deviceList:ALL
※rawタブで以下を貼りつけると楽ですよ👍
{
"action":"setupWebhook",
"url":"(ngrokで取得したWebHook URL)",
"deviceList":"ALL"
}
そして、送信してうまくいくと、以下のように返ってきます。
{
"statusCode": 100,
"body": {},
"message": "success"
}
もしWebHookの削除や変更をしたい場合は、本家の以下ページをご覧ください。
※パラメータが少しずつ違っているので気をつけてください。
これでWebHookの準備は完了です!
LaravelでWebHookを受け取る部分をつくる3ステップ
では、やっとLaravelの作業です!
以下のコマンドを実行してください。
1. DBまわりをつくる
以下のコマンドを実行してください。
php arsan make:model ContactSensorHistory -m
すると「モデル」「マイグレーション」ファイルが作成されるので、中身を変更します。
database/migrations/****_**_**_******_create_contact_sensor_histories_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('contact_sensor_histories', function (Blueprint $table) {
$table->id();
$table->string('device_id')->comment('デバイスID');
$table->string('detection_state')->comment('検知状態');
$table->string('contact_state')->comment('開閉状態');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('contact_sensor_histories');
}
};
2. コントローラーをつくる
次はコントローラーです。
php artisan make:controller SwitchBotWebhookController
中身はこうなります。
app/Http/Controllers/SwitchBotWebhookController.php
<?php
namespace App\Http\Controllers;
use App\Models\ContactSensorHistory;
use Illuminate\Http\Request;
class SwitchBotWebhookController extends Controller
{
public function store(Request $request)
{
$deviceId = $request->input('context.deviceMac'); // デバイスID(コロンなし)
$deviceType = $request->input('context.deviceType'); // デバイスタイプ
if ($deviceType === 'WoContact') { // 開閉センサーの場合(本来Enumを使うべき)
$contactState = $request->input('context.openState'); // 開閉状態
$detectionState = $request->input('context.detectionState'); // 動作検知状態
// 本来はここで中身をデータ確認すべきですが、省略
$contactSensorHistory = new ContactSensorHistory;
$contactSensorHistory->device_id = $deviceId;
$contactSensorHistory->detection_state = $detectionState;
$contactSensorHistory->contact_state = $contactState;
$result = $contactSensorHistory->save();
if ($result === false) {
abort(500);
}
return response(200);
}
abort(404);
}
}
3. ルートをつくる
最後にルートです。
routes/web.php
use App\Http\Controllers\SwitchBotWebhookController;
Route::prefix('switchbot')->group(function () {
// Webhook
Route::post('/webhook', [SwitchBotWebhookController::class, 'store'])->name('switchbot.webhook.store');
});
ちなみに、このままだとCSRFガードが効いているのでWebHookを受信できません。
なので、以下のようにしてswitchbot/webhookだけこのガードを解除しておきましょう。
bootstrap/app.php
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\App\Http\Middleware\HandleInertiaRequests::class,
\Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class,
]);
$middleware->validateCsrfTokens(except: [
'switchbot/webhook',
]);
//
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
※Laravel 12.xでは設定方法が変わりましたね🤔
タイマー実行で薬の飲み忘れチェックをする部分をつくる
続いて、先ほどのWebHookを受信したときに保存したデータをcronやsystemdで定期的にチェックして、もし飲み忘れがあったらメールで通知する部分をつくっていきます。
メール送信部分をつくる
コマンド本体をつくる前にメール部分をつくります。
以下のコマンドを実行してください。
php artisan make:mail NotTakingMedicine
中身は次のようにします。
app/Mail/NotTakingMedicine.php
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class NotTakingMedicine extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*/
public function __construct()
{
//
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'お薬💊ちゃんと飲も!',
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
htmlString: '<p>お薬💊飲み忘れてるよ。ちゃんと飲も!</p>',
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}
Artisanコマンドをつくる
以下のコマンドを実行してください。
php artisan make:command CheckTakingMedicineCommand
中身は次のようにします。
app/Console/Commands/CheckTakingMedicineCommand.php
<?php
namespace App\Console\Commands;
use App\Mail\NotTakingMedicine;
use App\Models\ContactSensorHistory;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;
class CheckTakingMedicineCommand extends Command
{
const TO_EMAIL = 'test@example.com'; // 通知先メールアドレス
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'check:taking-medicine';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Check if the user has taken their medicine or not';
/**
* Execute the console command.
*/
public function handle()
{
Carbon::setTestNow(now()->hour(9)); // テスト用:現在時刻を9時に固定
$base_hours = [8, 12, 18]; // お薬を飲むべき時間帯
$now = now();
$current_hour = $now->format('H');
foreach ($base_hours as $hour) {
if ($current_hour - 1 === $hour) {
$start_dt = Carbon::create($now->year, $now->month, $now->day, $hour);
$end_dt = $start_dt->copy()->addMinutes(59)->addSeconds(59);
$this->info('Checking for time range: ' . $start_dt . ' -> ' . $end_dt->format('H:i:s'));
$exists = ContactSensorHistory::query()
->where('detection_state', 'DETECTED')
->where('contact_state', 'open')
->whereBetween('created_at', [$start_dt, $end_dt])
->exists();
$this->info('$exists: ' . ($exists ? 'true' : 'false'));
if ($exists === false) { // お薬を飲んでいない場合の処理
$to = self::TO_EMAIL;
Mail::to($to)->send(new NotTakingMedicine);
}
}
}
}
}
※ちなみにこのコードはテスト用で時間を固定しているので、毎回チェックすべき時間と判別されるようにしています。
開閉センサーを箱にセットする
私には既往症はなくお薬も飲んでないので、過去記事「【お年玉用】3つの数字をゲットして宝箱を開ける機能をつくる」のために購入した宝箱で試してみることにします。

SwitchBot開閉センサーは30mm(3cm)の距離を開けてセットする必要があります。

で、セットしたのがこちら(注:近づく場所がかわると距離もかわるようでした)

※テストなので、開閉センサーについてる両面テープは使わずガムテープでくっつけてます。
テストしてみる
では実際にテストしてみましょう!
お薬箱(実際は宝箱)の開ける/閉めるを実行してみます。

まずは箱を開ける動作ですね。

どうなるでしょうか・・・・・・

はい!
WebHookを通じて検知されたデータがデータベースに保存されているのがわかりますね。データもopenになっています。
では、開けた蓋を元に戻します。

うまくいくでしょうか・・・・・・

はい!
closeの方もうまくデータ保存できています。
成功です😊
では(データがあるとうまくいかないので)データベースを初期化して、Artisanコマンドも実行してみましょう。
成功するでしょうか・・・・・・

はい!うまく送信されてきましたね。
すべて成功です😊✨
ちなみに:閉め忘れたときのWebHookは来ない
SwitchBot APIのGitHubリポジトリの説明を読むと、開閉センサーのWebHookは以下3つの状態があると書いています。
- open:開いた
- close:閉じた
- timeOutNotClose:閉め忘れてる
でも、何度やっても「timeOutNotClose」でのWebHookが来ることはありませんでした。
スマホアプリ側にはちゃんと通知が来るので、WebHookの説明が間違っているのかもしれません。
※何か情報をお持ちの方がいらっしゃったら、ぜひ教えてください🙇
企業様へのご提案
開閉センサーをつかうと倉庫や資材庫の開閉監視など様々なシーンで活用することができます。
もし何か社内でお困りの内容がありましたら、いつでもお気軽に相談ください。お待ちしております😊
おわりに
ということで、今回はSwitchBot開閉センサーをつかって薬箱の動作を監視してみました。
開閉動作が必要な場所は、世の中にたくさんあるので、薬箱だけでなく色々なシーンで活用できるんじゃないでしょうか。
ちなみに、以前公開した【Laravel・SwitchBot】たった1万円台で入室管理システムを導入する方法で紹介した機能と連携させると、ドアの開け閉めデータも保存できるので、さらに高性能なものをつくれるでしょう。
ぜひみなさんも、色々とアイデアを考えて「不便→便利」してみてくださいね。
ではでは〜!

「午後の紅茶を、午前に飲む。
この背徳感👍」





