プロジェクトについて相談する
MicrocosmWorksデジタルコスモスの革新と設計
会社情報お問い合わせ
MicrocosmWorksデジタルコスモスの革新と設計

重要なITソリューションを提供します。技術、セキュリティ、信頼性のある革新的なITインフラを通じてビジネスの成長を支援することに情熱を持っています。

[email protected]
+91 7011868196
New Delhi, India

ソリューション

構築AIプロダクトエンジニアリングSaaSプロダクトエンジニアリングカスタムソフトウェア開発
モダナイズソフトウェアモダナイゼーションAIモダナイゼーションクラウドアプリモダナイゼーション
スケールバックエンド&分散システムクラウドパフォーマンスエンジニアリング信頼性&パフォーマンスエンジニアリングAIインフラストラクチャ
拡張プロダクトエンジニアリングチーム
すべてのソリューションAIエージェント開発AIビデオプラットフォームウェルネス&フィットネスアプリ

サービス

デジタルコンサルティングクラウドインフラストラクチャSaaS開発AI開発ビデオ技術
ERP開発ZohoカスタマイズOdoo開発Salesforce統合カスタムCRM開発
QuickBooks統合IoTソリューションブロックチェーン開発
サイバーセキュリティコンサルティングITサポート - L3

AI成長ハブ

AIハブスタートアップイノベーションエンタープライズアクセラレーター

リソース

インサイト業界ガイドユースケースブループリントアーキテクチャパターンケーススタディ

会社

私たちについてお問い合わせプロジェクトについて相談する私たちの仕事

© 2026 MicrocosmWorks. 無断複写・転載を禁じます。

プライバシーポリシー利用規約
インサイトに戻る
IoT Development

接続型ディスペンサーアプリの構築

BLE経由で接続されたディスペンサーデバイスとペアリングし、制御するモバイルアプリを構築。

Mayank Joshi.webpMayank Chandra Joshi
•
September 7, 2026
•
更新日 September 24, 2026
•
6 min read
ChatGPT Image Sep 7, 2026, 12_49_11 PM (1).webp
6 min read

ほとんどのコンパニオンアプリは、ボタンをタップして値を書き込むだけのシンプルなBLEリモートです。しかし、ディスペンサーはそれほど単純にはいきません。投与は厳密に1回記録されなければならない健康イベントであり、デバイスは標準のGATTプロファイルではなくカスタムのバイナリプロトコルを使用します。「服用を忘れない」ことは、スマートフォンが起動していることに依存してはならないからです。ここでは、タップから永続的で整合性のある状態へのパスを、弊社のウェルネス&フィットネスアプリ開発の一環としてどのように構築したかをご紹介します。

概要

ドメイン コネクテッドウェルネス — スマートサプリメントディスペンシング

コアテクノロジー React Native + Expo, react-native-ble-plx, Zustand, NestJS, MongoDB 8 (トランザクション)

主要機能 カスタムBLEコマンド/テレメトリープロトコル、SMP/CBORファームウェアアップデート、トランザクション方式による投与記録、タイムゾーン対応リマインダー

ステータス 開発フェーズ2へ移行中

課題

当社のクライアントは、スマートフォンから制御するサプリメントディスペンサーデバイスという、コネクテッドウェルネス製品を開発していました。コンパニオンアプリを構築する従来の一般的な方法は、特性を書き込み、その結果を信頼するだけの薄いリモートとして構築することです。これは電球には有効ですが、ディスペンサーにはほとんど当てはまりません。

私たちはそのことを最初から認識しており、だからこそそのようには構築しませんでした。薄型リモートモデルの限界は、チューニングの問題ではなく、構造的なものです。

  • 投与は厳密に1回記録される必要があります。物理的な在庫を減らし、1日の投与量に計上され、栄養分析に情報を提供します。二重カウントや書き込みの欠落は、これら3つすべてを破損させます。
  • デバイスはプロファイルではなくカスタムプロトコルを使用します。コマンドはフレーム化されたバイナリパケットであり、「1錠を投与する」という意味の既製の特性はありません。
  • コマンドとテレメトリーは形状を共有しません。アプリはコンパクトなバイナリコマンドを書き込みますが、異なる特性を介してJSON形式のテレメトリー(バッテリー、充電状況、カートリッジID、水量)を読み取ります。
  • ファームウェアのアップデートは同じ接続を使用します。デバイスは、別の管理サービスを介して、追加のツールなしで現場でアップグレードできる必要があります。
  • リマインダーはデバイス上に存在できません。「今日はまだ服用していません」という問いは、タイムゾーン、履歴、重複排除に関するものであり、スマートフォンのタイマーではなくサーバーが関与する問題です。

根本原因は単純でした。投与はボタンの押下ではなく、通信上でもデータベース内でもトランザクションです。これを「書き込みっぱなし」と見なすモデルでは、どちらの側面も信頼性を確保できません。

当社のソリューション

各レイヤーが最も得意なことを行うように責任を分担しました。スマートフォンがオーケストレーションを行い、デバイスは弊社の IoTアプリケーション開発プラクティスを通じて定義されたカスタムプロトコルを実行し、クラウドがトランザクション処理の記録システムとなります。リマインダーは完全にサーバー側で処理されます。

アーキテクチャ

  • React Native + Expo + Zustand — BluetoothStoreがBLE接続、投与フロー、およびテレメトリーを所有し、AsyncStorageに永続化します。
  • react-native-ble-plx — スキャン、接続/再接続、および特性の読み書き。
  • カスタムコマンドレイヤー — デバイス用のフレーム化されたバイナリコマンドを構築し、ファームウェアアップデートにはSMPサービスを介してCBORを使用します。
  • NestJS API — 投与およびスケジュールエンドポイント。これは、クライアント製品全体で使用しているSaaSアプリケーション開発と同じ基盤の上に構築されています。各投与はMongoDBトランザクション内で記録されます。
  • MongoDB 8 (Mongoose) — 記録システムであり、下流の分析が読み取る書き込み時ロールアップコレクションも含まれます。
  • NestJS Schedule cronジョブ + Expoプッシュ (ActiveMQ/STOMP経由でキューイング) — タイムゾーン対応リマインダー。Mongoログを通じて重複排除されます。
  • Sentry (モバイル) — アプリ内の本番環境エラー追跡。


architecture-ble-dispenser.webp

カスタムワイヤプロトコル

どんなBLE製品でも難しいのは、デバイスが標準的な言語を話さないことです。当社のデバイスは、コマンド/テレメトリーサービスと、別途ファームウェア(SMP)サービスを公開しています。アプリはreact-native-ble-plxで接続し、テレメトリーを購読します。これは、通知特性としてJSON形式で届きます。

// BluetoothStore.ts — react-native-ble-plx + Zustand

const device = await bleManager.connectToDevice(deviceId, { autoConnect: true });

await device.discoverAllServicesAndCharacteristics();

// Telemetry: JSON over the notify characteristic (UUIDs are app-side constants).

bleManager.monitorCharacteristicForDevice(device.id, CH_SERVICE_UUID, TX_UUID, (_err, c) => {

  if (!c?.value) return;

  const t = JSON.parse(base64ToString(c.value));

  // { battery_percentage, charging_status, cartridge_id, water_qty, sequence, timestamp }

  set({ batteryPercentage: Number(t.battery_percentage), chargingStatus: t.charging_status });

});

 

投与は、開始マーカー、コマンドID、長さ、カートリッジバイト、読み書きフラグ、および終了マーカーからなるコンパクトなフレーム化されたバイナリコマンドであり、base64エンコードされてコマンド特性に書き込まれます。(ここでのリテラルUUIDとフレームマーカーは編集されています。)

// Frame shape: [SOF] [CMD] [LEN] [DATA] [RW] [EOF]  — built by the command service.

const command = SNBCommandService.buildDispenseNutritionCommand(cartridgeId); // CMD = DISPENSE

await bleManager.writeCharacteristicWithResponseForDevice(

  device.id, CH_SERVICE_UUID, RX_UUID,

  SNBCommandService.uint8ArrayToBase64(command),
 
);

 

アプリは、デバイスからの確認応答(応答フレームからコマンドIDが一致することを確認)を待ってから、その投与を有効なものとして扱い、その後にのみサーバー側で記録します。ファームウェアのアップデートは同じ接続を使用しますが、異なる言語、つまりSMPサービスを介したチャンク化されたCBORペイロードを使用します。

タップから記録まで

確認された投与は、単一のエンドポイント(POST /dispense/tablet/:cartridgeId)を介して永続的な状態となります。これは、単一のMongoDBトランザクション内で書き込まれるため、半ダースの効果がすべて発生するか、または何も発生しないかのどちらかになります。


// dispense.service.ts — record exactly once, atomically

await session.withTransaction(async () => {

  await this.dispensed.create([{ cartridgeId, cartridgeModalId, dispensedBy: userId, dispensedAt }], { session });

  // write-time roll-up: totalDaysConsumed only increments on the first dose of the day

  await this.monthly.findOneAndUpdate(

    { userId, cartridgeModalId, month, year },

    { $inc: { totalTablets: 1, ...(firstDoseToday ? { totalDaysConsumed: 1 } : {}) } },

    { upsert: true, session },

  );

  await this.cartridge.updateOne({ cartridgeId }, { $inc: { tablets: -1 } }, { session });

  await this.dailyDose.updateOne({ userId, dateLocal }, { $inc: { tabletsTaken: 1 } }, { upsert: true, session });

  await this.notifications.cancelPending(userId, 'SUPPLEMENT_REMINDER', { session }); // mark IS_MISSED

});

 

この単一のトランザクションは、投与ログ、月次ロールアップ、物理在庫、1日の投与量、および保留中のリマインダーを更新します。これこそが、投与が単なる書き込みではなくトランザクションである必要がある理由です。

リマインダーはサーバーの関心事

投与が永続的になると、スマートフォンのタイマーでは答えられなかった疑問が日常的なものとなり、アプリではなくcronジョブによって解決されます。

  • このユーザーは今日、自分のタイムゾーンで投与しましたか?
  • カートリッジは空ですか(交換)?それとも在庫は空ですか(再注文)?あるいはその両方ですか(中止)?
  • 以前服用していた健康分野から離れてしまいましたか?

これらの処理は、@nestjs/scheduleの3つのサービス(スケジュール/投与リマインダー、カートリッジ状態リマインダー、クロスヘルス非活動リマインダー)によって行われます。それぞれがdate-fns-tzを使用してユーザーの現地時間を解決し、Mongoで今日の投与状況を確認し、ログコレクションに対して重複排除を行い、ActiveMQ経由でExpoプッシュをキューに入れます。デバイスが関与することはありません。

結果

「投与はトランザクションである」という設計を、シンプルなリモート書き込みの代わりに採用することで、システムが保証できることが変わりました。

  • 厳密に1回記録。投与は、ログ、ロールアップ、在庫、および1日の投与量をアトミックに更新します。二重カウントや部分的な書き込みは発生しません。
  • フィールドアップグレードを含む文書化されたデバイスプロトコル。コマンドとテレメトリーは第一級オブジェクトであり、ファームウェアのアップデートはSMP/CBORを介して同じ接続で配信されます。
  • 正確で近似ではないリマインダー。タイムゾーン対応、履歴対応、サーバー側での重複排除により、アプリが開いているかどうかにかかわらず通知されます。
  • 信頼できる分析。すべての投与は書き込み時にロールアップされるため、チャートや服薬遵守状況に提供されるデータは常にログと一貫しています。

API、cronジョブ、プッシュキューは、弊社のクラウドインフラサービスを通じて管理されるインフラ上で動作するため、デバイスフリートとユーザーベースが拡大しても同じ信頼性保証が維持されます。

テクノロジースタック: React Native · Expo · TypeScript · react-native-ble-plx · Zustand · CBOR (SMP firmware) · NestJS · MongoDB 8 · Mongoose (transactions) · @nestjs/schedule · ActiveMQ / STOMP · Expo Server SDK · JWT · AWS S3 · AWS SES · Sentry (mobile)


私たちのチームからのその他の記事

1. Syncing Apple Health & Health Connect

2. Personalized Recipe Search: Retrieval That Knows What You Should Eat Next

3. Optimizing Channel Logo for Different Video Resolutions

 

IoTBLEMobile AppConnectivity
Mayank Joshi.webp

著者について

Mayank Chandra Joshi

AI & Cloud Solutions Expert at MicrocosmWorks

Building innovative AI-powered solutions and helping businesses transform through cutting-edge technology.

もっと詳しく知りたいですか?

ビジネス向けにこれらのソリューションを導入する方法についてお問い合わせください。

お問い合わせ

よくある質問

The app uses react-native-ble-plx to connect to the dispenser and communicate through its BLE characteristics. Dispense commands are built as framed binary packets containing fields such as the command ID, cartridge information, length, and read/write flag, while device telemetry is received separately as JSON.

A dispense is recorded only after the device acknowledges the corresponding command. The backend then records the confirmed dispense inside a MongoDB transaction, atomically updating the dispense log, stock, daily dose count, monthly roll-up, and pending reminder so the related state changes cannot be partially committed.

The dispenser uses a separate firmware management service based on the Simple Management Protocol (SMP). Firmware update data is transferred as chunked CBOR payloads over the same BLE connection, allowing the device to receive field updates without requiring a separate application or connection mechanism.

Dose reminders are handled server-side rather than by relying on a phone timer. NestJS scheduled services determine the user's local time, check MongoDB for the user's dosing history, de-duplicate reminder events, and queue Expo push notifications through ActiveMQ/STOMP. This allows reminders to be generated even when the mobile app is not open.

The system uses React Native, Expo, TypeScript, react-native-ble-plx, Zustand, NestJS, MongoDB 8 with Mongoose transactions, CBOR over SMP for firmware updates, @nestjs/schedule, ActiveMQ/STOMP, Expo Server SDK, JWT, AWS S3, AWS SES, and Sentry. Together, these components support BLE communication, transactional dispense recording, firmware management, reminders, storage, and mobile error tracking.

Comments (0)

Share your thoughts and join the conversation

Leave a Comment

Your email will not be published

No comments yet

Be the first to share your thoughts!