Kebanyakan aplikasi pendamping adalah alat kawalan jauh BLE yang ringkas: ketik butang, tulis nilai, selesai. Sebuah dispenser tidak boleh sesederhana itu — pengeluaran adalah peristiwa kesihatan yang mesti direkodkan tepat sekali, peranti ini menggunakan protokol binari tersuai dan bukannya profil GATT standard, dan "jangan terlepas dos" tidak boleh bergantung pada telefon yang berjaga. Berikut adalah cara kami membina laluan dari ketikan kepada keadaan yang tahan lama dan boleh diselaraskan, sebagai sebahagian daripada kerja pembangunan aplikasi kesihatan & kecergasan kami.
Sepintas Lalu
Domain Kesihatan Terhubung — pengeluaran suplemen pintar
Teknologi Teras React Native + Expo, react-native-ble-plx, Zustand, NestJS, MongoDB 8 (transactions)
Keupayaan Utama Protokol perintah/telemetri BLE tersuai, kemas kini firmware SMP/CBOR, rakaman pengeluaran secara transaksi, peringatan berdasarkan zon waktu
Status Memasuki Fasa 2 pembangunan
Cabaran
Pelanggan kami sedang mencipta produk kesihatan terhubung: sebuah peranti yang mengeluarkan suplemen, dikawal dari telefon. Cara konvensional untuk membina aplikasi pendamping — dan cara kebanyakan aplikasi dibina — adalah sebagai alat kawalan jauh yang ringkas yang menulis ciri dan mempercayai hasilnya. Itu berfungsi untuk mentol lampu, tetapi hampir tidak sama sekali untuk dispenser.
Kami sudah mengetahui perkara itu dari awal, sebab itulah kami tidak membangunkannya dengan cara tersebut. Had model kawalan jauh ringkas adalah masalah struktur, bukan masalah penalaan:
- Pengeluaran mesti direkodkan tepat sekali. Ia mengurangkan stok fizikal, mengira dos harian, dan membekalkan analisis nutrien. Kiraan berganda atau penulisan yang hilang akan merosakkan ketiga-tiganya.
- Peranti menggunakan protokol tersuai, bukan profil. Perintah adalah paket binari berbingkai; tiada ciri sedia ada yang bermaksud "keluarkan satu tablet."
- Perintah dan telemetri tidak berkongsi bentuk. Aplikasi ini menulis perintah binari padat tetapi membaca telemetri JSON (bateri, pengecasan, ID kartrij, kuantiti air) — melalui ciri-ciri yang berbeza.
- Kemas kini firmware menggunakan sambungan yang sama. Peranti perlu boleh ditingkatkan di lapangan, melalui perkhidmatan pengurusan yang berasingan, tanpa alat kedua.
- Peringatan tidak boleh disimpan pada peranti. "Anda belum mengambil dos hari ini" adalah soalan mengenai zon waktu, sejarah, dan de-duplikasi — ini adalah urusan pelayan, bukan pemasa telefon.
Punca masalahnya mudah: pengeluaran bukanlah satu tekan butang, ia adalah transaksi — baik pada wayar mahupun dalam pangkalan data. Model yang menganggapnya sebagai penulisan tanpa pengesahan tidak boleh menjadikan mana-mana bahagiannya boleh dipercayai.
Penyelesaian Kami
Kami membahagikan tanggungjawab supaya setiap lapisan melakukan apa yang terbaik: telefon menyelaras, peranti melaksanakan protokol tersuai yang ditakrifkan melalui amalan pembangunan aplikasi IoT kami, dan awan adalah sistem rekod transaksi — dengan peringatan dikendalikan sepenuhnya di pihak pelayan.
Seni Bina
- React Native + Expo + Zustand — sebuah BluetoothStore mengawal sambungan BLE, aliran pengeluaran, dan telemetri, yang disimpan ke AsyncStorage.
- react-native-ble-plx — pengimbasan, sambung/sambung semula, dan baca/tulis ciri.
- Lapisan perintah tersuai — membina perintah binari berbingkai untuk peranti, dan menggunakan CBOR melalui perkhidmatan SMP untuk kemas kini firmware.
- API NestJS — titik akhir pengeluaran dan jadual, dibina di atas asas pembangunan aplikasi SaaS yang sama yang kami gunakan merentasi produk pelanggan; merekodkan setiap pengeluaran dalam transaksi MongoDB.
- MongoDB 8 (Mongoose) — sistem rekod, ditambah koleksi roll-up masa penulisan yang dibaca oleh analisis hiliran.
- NestJS Schedule crons + Expo push (dijadikan baris gilir melalui ActiveMQ/STOMP) — peringatan berdasarkan zon waktu, de-duplikasi melalui log Mongo.
- Sentry (mudah alih) — penjejakan ralat pengeluaran dalam aplikasi.

Protokol Wayar Tersuai
Bahagian yang sukar bagi mana-mana produk BLE ialah peranti tersebut tidak menggunakan bahasa standard. Produk kami mendedahkan perkhidmatan perintah/telemetri dan perkhidmatan firmware (SMP) yang berasingan. Aplikasi ini bersambung dengan react-native-ble-plx, kemudian melanggan telemetri — yang tiba sebagai JSON pada ciri pemberitahuan:
// 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 });
});
Pengeluaran adalah perintah binari berbingkai padat — penanda permulaan, ID perintah, panjang, bait kartrij, bendera baca/tulis, dan penanda tamat — dikodkan base64 dan ditulis ke ciri perintah. (UUID literal dan penanda bingkai dirahsiakan di sini.)
// 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),
);
Aplikasi menunggu pengesahan peranti (ia memadankan ID perintah kembali dari bingkai respons) sebelum ia menganggap pengeluaran itu sah — dan hanya kemudian merekodkannya di pihak pelayan. Kemas kini firmware menggunakan sambungan yang sama tetapi bahasa yang berbeza: muatan CBOR bersegmen melalui perkhidmatan SMP.
Dari Ketikan ke Rekod
Pengeluaran yang disahkan menjadi keadaan yang tahan lama melalui satu titik akhir — POST /dispense/tablet/:cartridgeId — yang menulis dalam satu transaksi MongoDB supaya enam kesan itu sama ada berlaku semua atau tidak berlaku sama sekali:
// 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
});
Transaksi tunggal itu mengemas kini log pengeluaran, ringkasan bulanan, stok fizikal, kiraan dos harian, dan sebarang peringatan tertunda — sebab itulah pengeluaran perlu menjadi transaksi, bukan penulisan.
Peringatan Adalah Urusan Pelayan
Setelah pengeluaran kekal, soalan-soalan yang tidak dapat dijawab oleh pemasa telefon menjadi rutin — dan dijawab oleh cron jobs, bukan aplikasi:
- Adakah pengguna ini telah mengambil dos hari ini, dalam zon waktu mereka?
- Adakah kartrij kosong (ganti) atau stok kosong (pesan semula), atau kedua-duanya (pemberhentian)?
- Adakah mereka telah menyimpang dari kawasan kesihatan yang biasa mereka ambil?
Tiga perkhidmatan @nestjs/schedule mengendalikan ini — peringatan jadual/dos, peringatan status kartrij, dan peringatan ketidakaktifan kesihatan silang. Setiap satunya menyelesaikan waktu tempatan pengguna dengan date-fns-tz, memeriksa Mongo sama ada mereka telah mengambil dos hari ini, melakukan de-duplikasi terhadap koleksi log, dan menambahkan Expo push ke dalam baris gilir melalui ActiveMQ. Peranti tidak pernah terlibat.
Hasil
Merekabentuk dengan premis "pengeluaran adalah transaksi" dan bukannya penulisan kawalan jauh ringkas mengubah apa yang sistem boleh jamin:
- Rakaman tepat sekali. Pengeluaran mengemas kini log, ringkasan, stok, dan dos harian secara atomik — tiada kiraan berganda, tiada penulisan separa.
- Protokol peranti yang didokumentasikan, termasuk naik taraf lapangan. Perintah dan telemetri adalah kelas pertama, dan kemas kini firmware dihantar melalui sambungan yang sama melalui SMP/CBOR.
- Peringatan yang tepat, bukan anggaran. Peka zon waktu, peka sejarah, dan de-duplikasi di pihak pelayan — jadi ia diaktifkan sama ada aplikasi dibuka atau tidak.
- Analitik yang boleh dipercayai. Oleh kerana setiap pengeluaran diringkaskan pada masa penulisan, data yang membekalkan carta dan kepatuhan sentiasa konsisten dengan log.
API, crons, dan baris gilir push berjalan di atas infrastruktur yang diuruskan melalui perkhidmatan infrastruktur awan kami, jadi jaminan kebolehpercayaan yang sama kekal apabila kumpulan peranti dan pangkalan pengguna meningkat.
Technology Stack: 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)
Baca lebih lanjut daripada pasukan kami
1. Penyelarasan Apple Health & Health Connect
2. Carian Resipi Peribadi: Pengambilan Semula Yang Tahu Apa Yang Perlu Anda Makan Seterusnya

