Çoğu yardımcı uygulama, basit BLE uzaktan kumandalarıdır: bir düğmeye dokun, bir değer yaz, bitti. Bir dağıtıcı bu kadar basit olamaz – bir dağıtım tam olarak bir kez kaydedilmesi gereken bir sağlık olayıdır, cihaz standart bir GATT profili yerine özel bir ikili protokol kullanır ve "dozu kaçırma" telefonun açık olmasına bağlı olamaz. Dokunuştan kalıcı, uzlaştırılabilir duruma giden yolu, sağlık ve fitness uygulaması geliştirme çalışmalarımızın bir parçası olarak nasıl inşa ettiğimiz aşağıda açıklanmıştır.
Bir Bakışta
Alan Bağlantılı sağlık — akıllı takviye dağıtımı
Çekirdek Teknolojiler React Native + Expo, react-native-ble-plx, Zustand, NestJS, MongoDB 8 (işlemler)
Temel Yetenekler Özel BLE komut/telemetri protokolü, SMP/CBOR aygıt yazılımı güncellemeleri, işlemsel dağıtım kaydı, zaman dilimi tabanlı hatırlatıcılar
Durum Geliştirmenin 2. Aşamasına Giriliyor
Zorluk
Müşterimiz, bir telefondan kontrol edilen, takviyeleri dağıtan bir cihaz olan bağlantılı bir sağlık ürünü yaratıyordu. Yardımcı uygulamayı geliştirmenin geleneksel yolu – ve çoğu uygulamanın geliştirildiği yol – bir özelliği yazan ve sonuca güvenen basit bir uzaktan kumanda şeklindedir. Bu, bir ampul için işe yarar, ancak bir dağıtıcı için neredeyse hiç işe yaramaz.
Bunu en başta biliyorduk, bu yüzden bu şekilde inşa etmedik. Basit uzaktan kumanda modelinin sınırları yapısal sorunlardır, ayar sorunları değil:
- Bir dağıtım tam olarak bir kez kaydedilmelidir. Fiziksel stoktan düşer, günlük doza sayılır ve besin analizlerini besler. Çift sayım veya kayıp bir yazma, her üçünü de bozar.
- Cihaz standart bir profil yerine özel bir protokol kullanır. Komutlar çerçeveli ikili paketlerdir; "bir tablet dağıt" anlamına gelen hazır bir characteristic yoktur.
- Komutlar ve telemetri aynı şekli paylaşmaz. Uygulama kompakt ikili komutlar yazar ancak farklı characteristics üzerinden JSON telemetri (pil, şarj, kartuş kimliği, su miktarı) okur.
- Aygıt yazılımı güncellemeleri aynı bağlantı üzerinden yapılır. Cihaz, ikinci bir araca ihtiyaç duymadan, ayrı bir yönetim hizmeti üzerinden sahada yükseltilebilir olmalıdır.
- Hatırlatıcılar cihazda bulunamaz. "Bugün doz almadınız" zaman dilimleri, geçmiş ve tekilleştirme hakkında bir sorudur – bir telefon zamanlayıcısı değil, sunucuyla ilgili bir konudur.
Temel neden basitti: bir dağıtım bir düğmeye basma değil, hem kablo üzerinden hem de veritabanında bir işlemdir. Bunu tek yönlü bir yazma olarak ele alan bir model, her iki tarafı da güvenilir kılamaz.
Çözümümüz
Sorumlulukları, her katmanın en iyi yaptığı işi yapacağı şekilde ayırdık: telefon orkestrasyonu yapar, cihaz IoT uygulama geliştirme pratiğimiz aracılığıyla tanımlanan özel bir protokolü yürütür ve bulut, hatırlatıcıların tamamen sunucu tarafında ele alındığı, işlemsel bir kayıt sistemidir.
Mimari
- React Native + Expo + Zustand — bir BluetoothStore BLE bağlantısını, dağıtım akışını ve telemetriyi yönetir ve AsyncStorage'a kalıcı olarak kaydedilir.
- react-native-ble-plx — tarama, bağlanma/yeniden bağlanma ve characteristic okuma/yazma.
- Özel bir komut katmanı — cihaz için çerçeveli ikili komutlar oluşturur ve aygıt yazılımı güncellemeleri için bir SMP hizmeti üzerinden CBOR kullanır.
- NestJS API — müşteri ürünlerimizde kullandığımız aynı SaaS uygulama geliştirme temeli üzerine kurulu dağıtım ve zamanlama endpoint'leri; her dağıtımı bir MongoDB işlemi içinde kaydeder.
- MongoDB 8 (Mongoose) — kayıt sistemi ve alt akış analitiklerinin okuduğu yazma zamanı toplama koleksiyonları.
- NestJS Schedule crons + Expo push (ActiveMQ/STOMP aracılığıyla sıraya alınmış) — zaman dilimi tabanlı hatırlatıcılar, Mongo günlükleri aracılığıyla tekilleştirilmiş.
- Sentry (mobil) — uygulamadaki üretim hatası izleme.

Özel Bir Kablolu Protokol
Herhangi bir BLE ürününün zor kısmı, cihazın standart bir dil konuşmamasıdır. Bizimki bir komut/telemetri hizmeti ve ayrı bir aygıt yazılımı (SMP) hizmeti sunar. Uygulama, react-native-ble-plx ile bağlanır, ardından telemetriye abone olur – bu, notify characteristic üzerinde JSON olarak gelir:
// 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 });
});
Bir dağıtım, kompakt, çerçeveli bir ikili komuttur — bir başlangıç işaretleyici, bir komut kimliği, bir uzunluk, kartuş baytı, bir okuma/yazma bayrağı ve bir bitiş işaretleyici — base64 ile kodlanmış ve komut characteristic'ine yazılmıştır. (Buradaki literal UUID'ler ve çerçeve işaretleyicileri redakte edilmiştir.)
// 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),
);
Uygulama, dağıtımı gerçek olarak kabul etmeden önce cihazın onayını (komut kimliğini yanıt çerçevesinden geri eşleştirir) bekler – ve ancak o zaman sunucu tarafında kaydeder. Aygıt yazılımı güncellemeleri aynı bağlantıyı kullanır ancak farklı bir dil: SMP hizmeti üzerinden parçalı CBOR yükleri.
Dokunuştan Kayda
Onaylanmış bir dağıtım, tek bir endpoint — POST /dispense/tablet/:cartridgeId — aracılığıyla kalıcı bir duruma gelir ve bu, tek bir MongoDB işlemi içinde yazılır, böylece yarım düzine etki ya tamamen gerçekleşir ya da hiçbiri gerçekleşmez:
// 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
});
Bu tek işlem, dağıtım günlüğünü, aylık toplamayı, fiziksel stoğu, günlük doz sayısını ve bekleyen herhangi bir hatırlatıcıyı günceller — bu da bir dağıtımın neden bir yazma değil, bir işlem olması gerektiğinin tam nedenidir.
Hatırlatıcılar Sunucuyla İlgili Bir Konudur
Bir dağıtım kalıcı hale geldiğinde, bir telefon zamanlayıcısının yanıtlayamadığı sorular rutin hale gelir — ve bunlar uygulama tarafından değil, cron işleri tarafından yanıtlanır:
- Bu kullanıcı bugün, kendi zaman diliminde doz aldı mı?
- Kartuş boş mu (değiştirme) yoksa stok mu boş (yeniden sipariş), yoksa her ikisi mi (sonlandırma)?
- Daha önce ilgilendikleri bir sağlık alanından uzaklaştılar mı?
Üç adet @nestjs/schedule hizmeti bunları yönetir — bir zamanlama/doz hatırlatıcısı, bir kartuş durumu hatırlatıcısı ve çapraz sağlık hareketsizlik hatırlatıcısı. Her biri kullanıcının yerel saatini date-fns-tz ile çözümler, bugün doz alıp almadığını Mongo'dan kontrol eder, bir günlük koleksiyonuna göre tekilleştirir ve ActiveMQ aracılığıyla bir Expo push'u sıraya alır. Cihaz asla dahil olmaz.
Sonuçlar
Basit bir uzaktan yazma yerine "bir dağıtım bir işlemdir" ilkesiyle tasarım yapmak, sistemin neleri garanti edebileceğini değiştirdi:
- Tam olarak bir kez kayıt. Bir dağıtım, günlüğü, toplamaları, stoğu ve günlük dozu atomik olarak günceller — çift sayım yok, kısmi yazma yok.
- Alan yükseltmeleri de dahil olmak üzere belgelenmiş bir cihaz protokolü. Komutlar ve telemetri birinci sınıftır ve aygıt yazılımı güncellemeleri SMP/CBOR aracılığıyla aynı bağlantı üzerinden gönderilir.
- Yaklaşık değil, doğru hatırlatıcılar. Zaman dilimi tabanlı, geçmişi bilen ve sunucu tarafında tekilleştirilmiş — böylece uygulama açık olsun ya da olmasın tetiklenirler.
- Güvenebileceği analitikler. Her dağıtım yazma zamanında toplandığı için, grafikleri besleyen ve uyumu gösteren veriler her zaman günlüğe uygun olur.
API, cron'lar ve push kuyruğu, bulut altyapı hizmetlerimiz aracılığıyla yönetilen altyapı üzerinde çalışır, böylece cihaz filosu ve kullanıcı tabanı ölçeklendikçe aynı güvenilirlik garantileri devam eder.
Teknoloji Yığını: React Native · Expo · TypeScript · react-native-ble-plx · Zustand · CBOR (SMP firmware) · NestJS · MongoDB 8 · Mongoose (işlemler) · @nestjs/schedule · ActiveMQ / STOMP · Expo Server SDK · JWT · AWS S3 · AWS SES · Sentry (mobil)
Ekibimizden daha fazlasını okuyun
1. Apple Health ve Health Connect'i Senkronize Etme
2. Kişiselleştirilmiş Tarif Araması: Ne Yemeniz Gerektiğini Bilen Bilgi Getirme
3. Farklı Video Çözünürlükleri İçin Kanal Logosunu Optimize Etme

