Useimmat kumppanisovellukset ovat ohuita BLE-kaukosäätimiä: napauta painiketta, kirjoita arvo, valmista. Annostelija ei voi olla niin ohut – annostelu on terveys tapahtuma, joka on kirjattava täsmälleen kerran, laite puhuu mukautettua binääriprotokollaa standardin GATT-profiilin sijaan, eikä "älä missaa annosta" voi riippua puhelimen hereillä olosta. Näin rakensimme polun napautuksesta kestävään, sovitettavissa olevaan tilaan, osana wellness & fitness app development -työtämme.
Yleiskatsaus
Toimialue Yhdistetty hyvinvointi — älykäs lisäravinteiden annostelu
Ydinteknologiat React Native + Expo, react-native-ble-plx, Zustand, NestJS, MongoDB 8 (transaktiot)
Avainominaisuudet Mukautettu BLE-komento-/telemetriaprotokolla, SMP/CBOR-laiteohjelmistopäivitykset, transaktiopohjainen annostelun kirjaaminen, aikavyöhyketietoiset muistutukset
Tila Kehityksen vaihe 2 alkaa
Haaste
Asiakkaamme kehitti yhdistetyn hyvinvointituotteen: laitteen, joka annostelee lisäravinteita ja jota ohjataan puhelimella. Tavanomainen tapa rakentaa kumppanisovellus – ja tapa, jolla useimmat on rakennettu – on ohut kaukosäädin, joka kirjoittaa ominaisuuden ja luottaa tulokseen. Se toimii hehkulampussa, mutta tuskin ollenkaan annostelijassa.
Tiesimme sen alusta alkaen, siksi emme rakentaneet sitä sillä tavalla. Ohuen etäohjausmallin rajoitukset ovat rakenteellisia, eivät hienosäätöongelmia:
- Annostelu on kirjattava täsmälleen kerran. Se vähentää fyysistä varastoa, lasketaan päivittäiseen annokseen ja syöttää tietoja ravintoaineanalyyseihin. Kaksinkertainen laskenta tai menetetty kirjoitus vioittaa kaikki kolme.
- Laite puhuu mukautettua protokollaa, ei profiilia. Komennot ovat kehyksellisiä binääripaketteja; ei ole valmiina ominaisuutta, joka tarkoittaisi "annostele yksi tabletti".
- Komennoilla ja telemetrialla ei ole samaa muotoa. Sovellus kirjoittaa tiiviitä binäärikomentoja, mutta lukee takaisin JSON-telemetriaa (akku, lataus, patruunan tunnus, veden määrä) – eri ominaisuuksien kautta.
- Laiteohjelmistopäivitykset käyttävät samaa yhteyttä. Laite on päivitettävissä kentällä, erillisen hallintapalvelun kautta, ilman toista työkalua.
- Muistutukset eivät voi sijaita laitteessa. "Et ole annostellut tänään" on kysymys aikavyöhykkeistä, historiasta ja kaksoiskappaleiden poistamisesta – palvelimen huoli, ei puhelimen ajastin.
Perussyy oli yksinkertainen: annostelu ei ole painikkeen painallus, se on transaktio – sekä tiedonsiirrossa että tietokannassa. Malli, joka käsittelee sitä "lähetä ja unohda" -kirjoituksena, ei voi tehdä kummastakaan puolesta luotettavaa.
Ratkaisumme
Jaoimme vastuualueet niin, että jokainen kerros tekee sen, missä se on paras: puhelin orkestroi, laite suorittaa mukautetun protokollan, joka on määritetty IoT application development -käytäntömme kautta, ja pilvi on transaktiojärjestelmä – muistutukset hoidetaan kokonaan palvelinpuolella.
Arkkitehtuuri
- React Native + Expo + Zustand — BluetoothStore omistaa BLE-yhteyden, annosteluvirran ja telemetrian, joka säilytetään AsyncStorage-varastoon.
- react-native-ble-plx — skannaus, yhteys/uudelleenyhteys ja ominaisuuksien luku/kirjoitus.
- Mukautettu komentokerros — rakentaa kehyksellisiä binäärikomentoja laitteelle ja käyttää CBOR:ia SMP-palvelun kautta laiteohjelmistopäivityksiin.
- NestJS API — annostelu- ja aikataulutusrajapinnat, jotka on rakennettu samalle SaaS application development -perustalle, jota käytämme kaikissa asiakastuotteissa; kirjaa jokaisen annostelun MongoDB-transaktion sisällä.
- MongoDB 8 (Mongoose) — tietojärjestelmä, sekä kirjoitushetkellä luodut yhteenvetokokoelmat, joita alavirran analytiikka lukee.
- NestJS Schedule cron-työt + Expo push (jonotettu ActiveMQ/STOMP:n kautta) — aikavyöhyketietoiset muistutukset, kaksoiskappaleet poistettu Mongo-lokien kautta.
- Sentry (mobiili) — tuotantoympäristön virheiden seuranta sovelluksessa.

Mukautettu tiedonsiirtoprotokolla
Mikä tahansa BLE-tuotteen haastava osa on se, että laite ei puhu standardikieltä. Meidän laitteemme tarjoaa komento-/telemetriapalvelun ja erillisen laiteohjelmisto (SMP) -palvelun. Sovellus muodostaa yhteyden react-native-ble-plx:n avulla ja tilaa sitten telemetrian – joka saapuu JSON-muodossa ilmoitusominaisuuden kautta:
// 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 });
});
Annostelu on tiivis, kehyksellinen binäärikomento – aloitustunniste, komennon tunnus, pituus, patruunabitti, luku-/kirjoitustunniste ja lopetustunniste – base64-koodattu ja kirjoitettu komento-ominaisuuteen. (Kirjaimelliset UUID:t ja kehystunnisteet on piilotettu tässä.)
// 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),
);
Sovellus odottaa laitteen kuittausta (se täsmää komennon tunnuksen takaisin vastauskehyksestä) ennen kuin se käsittelee annostelua todellisena – ja vasta silloin kirjaa sen palvelinpuolelle. Laiteohjelmistopäivitykset käyttävät samaa yhteyttä, mutta eri kieltä: CBOR-palautteita (chunked CBOR payloads) SMP-palvelun kautta.
Napautuksesta kirjaamiseen
Vahvistettu annostelu muuttuu pysyväksi tilaksi yhden päätepisteen kautta — POST /dispense/tablet/:cartridgeId — joka kirjoittaa yhden MongoDB-transaktion sisällä, jotta kaikki puoli tusinaa vaikutusta joko tapahtuvat tai eivät ollenkaan:
// 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
});
Tuo yksi transaktio päivittää annostelulokin, kuukausittaisen yhteenvetotiedon, fyysisen varaston, päivittäisen annosmäärän ja kaikki odottavat muistutukset – juuri siksi annostelun on oltava transaktio, ei pelkkä kirjoitus.
Muistutukset ovat palvelimen asia
Kun annostelu on tallennettu pysyvästi, kysymykset, joihin puhelimen ajastin ei voinut vastata, muuttuvat rutiiniksi – ja niihin vastaavat cron-työt, eivät sovellus:
- Onko tämä käyttäjä annostellut tänään, omalla aikavyöhykkeellään?
- Onko patruuna tyhjä (vaihda) vai varasto tyhjä (tilaa lisää), vai molemmat (lopettaminen)?
- Ovatko he etääntyneet terveysalueesta, jota he aiemmin käyttivät?
Kolme @nestjs/schedule -palvelua hoitaa nämä – aikataulu-/annosmuistutus, patruunan tilan muistutus ja terveysalueen ylittävä toimettomuusmuistutus. Jokainen ratkaisee käyttäjän paikallisen ajan date-fns-tz:n avulla, tarkistaa Mongosta, ovatko he annostelleet tänään, poistaa kaksoiskappaleet lokikokoelmaa vastaan ja jonottaa Expo push -ilmoituksen ActiveMQ:n kautta. Laite ei ole koskaan mukana.
Tulokset
Suunnittelu siten, että "annostelu on transaktio" ohuen etäohjauskirjoituksen sijaan, muutti sitä, mitä järjestelmä voi taata:
- Täsmälleen kerran -kirjaaminen. Annostelu päivittää lokin, yhteenvetoja, varaston ja päivittäisen annoksen atomisesti – ei kaksinkertaisia laskentoja, ei osittaisia kirjoituksia.
- Dokumentoitu laiteprotokolla, mukaan lukien kenttäpäivitykset. Komennot ja telemetria ovat ensiluokkaisia, ja laiteohjelmistopäivitykset lähetetään saman yhteyden kautta SMP/CBOR:n avulla.
- Muistutukset, jotka ovat oikein, eivät likimääräisiä. Aikavyöhyke- ja historia-tietoiset, ja palvelinpuolella kaksoiskappaleet poistetut – joten ne laukeavat riippumatta siitä, onko sovellus auki.
- Analytiikka, johon voi luottaa. Koska jokainen annostelu kootaan kirjoitushetkellä, kaavioita ja noudattamista syöttävä data on aina johdonmukaista lokin kanssa.
API, cron-työt ja push-jono pyörivät cloud infrastructure services -palveluidemme kautta hallitussa infrastruktuurissa, joten samat luotettavuustakuut pysyvät voimassa laitekannan ja käyttäjäkunnan kasvaessa.
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)
Lue lisää tiimiltämme
1. Apple Healthin ja Health Connectin synkronointi
2. Personoitu reseptihaku: nouto, joka tietää mitä sinun tulisi syödä seuraavaksi

