프로젝트를 상담하세요
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 리모컨입니다: 버튼을 누르고, 값을 작성하고, 끝. 디스펜서는 그렇게 얇을 수 없습니다 — 디스펜스는 정확히 한 번 기록되어야 하는 건강 이벤트이며, 장치는 표준 GATT 프로필이 아닌 맞춤형 이진 프로토콜을 사용하며, "복용을 놓치지 마세요"는 전화기가 깨어 있는지에 의존할 수 없습니다. 여기서는 탭에서 내구성 있는 조정 가능한 상태로의 경로를 구축한 방법을 설명합니다, 우리의 웰니스 & 피트니스 앱 개발 작업의 일환으로.

한눈에 보기

도메인 연결된 웰니스 — 스마트 보충제 디스펜싱

핵심 기술 React Native + Expo, react-native-ble-plx, Zustand, NestJS, MongoDB 8 (트랜잭션)

주요 기능 맞춤형 BLE 명령/텔레메트리 프로토콜, SMP/CBOR 펌웨어 업데이트, 트랜잭션 디스펜스 기록, 시간대 인식 알림

상태 개발 2단계 진입

도전 과제

우리의 고객은 연결된 웰니스 제품을 만들고 있었습니다: 보충제를 분배하는 장치로, 전화기로 제어됩니다. 동반 앱을 구축하는 일반적인 방법 — 대부분이 그렇게 구축됩니다 — 은 특성에 기록하고 결과를 신뢰하는 얇은 리모컨으로 만드는 것입니다. 이는 전구에는 작동하지만, 디스펜서에는 거의 작동하지 않습니다.

우리는 그것을 알고 있었기 때문에 그렇게 만들지 않았습니다. 얇은 리모컨 모델의 한계는 구조적이지, 조정 문제가 아닙니다:

  • 디스펜스는 정확히 한 번 기록되어야 합니다. 이는 물리적 재고를 감소시키고, 일일 복용량에 포함되며, 영양 분석에 피드됩니다. 이중 계산이나 기록 손실은 이 세 가지 모두를 손상시킵니다.
  • 장치는 프로필이 아닌 맞춤형 프로토콜을 사용합니다. 명령은 프레임화된 이진 패킷이며, "하나의 태블릿을 분배"라는 의미의 기성 특성은 없습니다.
  • 명령과 텔레메트리는 형태를 공유하지 않습니다. 앱은 압축된 이진 명령을 작성하지만 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 크론 + 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

});

 

그 단일 트랜잭션은 디스펜스 로그, 월별 롤업, 물리적 재고, 일일 복용량 카운트, 그리고 모든 대기 중인 알림을 업데이트합니다 — 이것이 바로 디스펜스가 기록이 아닌 트랜잭션이어야 하는 이유입니다.

알림은 서버의 문제입니다

디스펜스가 내구성을 가지면, 전화 타이머가 답할 수 없는 질문들이 일상적이 됩니다 — 그리고 이는 앱이 아닌 크론 작업에 의해 답변됩니다:

  • 이 사용자는 오늘, 그들의 시간대에서 복용했습니까?
  • 카트리지가 비어 있습니까 (교체) 아니면 재고가 비어 있습니까 (재주문), 아니면 둘 다 (중단)?
  • 그들은 이전에 복용했던 건강 영역에서 멀어졌습니까?

세 가지 @nestjs/schedule 서비스가 이를 처리합니다 — 일정/복용 알림, 카트리지 상태 알림, 교차 건강 비활성 알림. 각 서비스는 사용자의 현지 시간을 date-fns-tz와 함께 해결하고, 오늘 복용했는지 여부를 Mongo에서 확인하고, 로그 컬렉션에 대해 중복 제거하며, ActiveMQ를 통해 Expo 푸시를 큐잉합니다. 장치는 절대 관여하지 않습니다.

결과

"디스펜스는 트랜잭션이다"로 설계하는 것은 시스템이 보장할 수 있는 것을 변화시켰습니다:

  • 정확히 한 번 기록. 디스펜스는 로그, 롤업, 재고, 일일 복용량을 원자적으로 업데이트합니다 — 이중 계산 없음, 부분 기록 없음.
  • 문서화된 장치 프로토콜, 필드 업그레이드 포함. 명령과 텔레메트리는 일급이며, 펌웨어 업데이트는 SMP/CBOR을 통해 동일한 연결로 전송됩니다.
  • 정확한 알림, 근사치가 아닌. 시간대 인식, 역사 인식, 서버 측 중복 제거 — 앱이 열려 있든 아니든 알림이 작동합니다.
  • 신뢰할 수 있는 분석. 모든 디스펜스가 작성 시점에 롤업되기 때문에, 차트와 준수에 피드되는 데이터는 항상 로그와 일치합니다.

API, 크론, 푸시 큐는 우리의 클라우드 인프라 서비스를 통해 관리되는 인프라에서 실행되므로, 장치 플릿과 사용자 기반이 확장됨에 따라 동일한 신뢰성 보장이 유지됩니다.

기술 스택: 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. Apple Health & Health Connect 동기화

2. 개인화된 레시피 검색: 다음에 무엇을 먹어야 할지 아는 검색

3. 다양한 비디오 해상도를 위한 채널 로고 최적화

 

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!