Jan 2024 – May 2024
SWE · EELast edited
MediCare: Temperature, Time, Tracking
Built for the Physical Medicine outpatient clinic at St. Olavs Hospital, where storage of local-anaesthesia vials (lidocaine) was being lost to manual processes: handwritten dates on opened vials, once-daily fridge temperature checks, and easy-to-miss expiration on unopened stock. The three sub-systems each target one of those failure modes.
The temperature sub-system uses a BME680 sensor on a Raspberry Pi sampling every 10 seconds over i2c, writing to MySQL and serving a React + TypeScript dashboard via a NodeJS backend. A Python SMS service pages staff when the fridge drifts outside the 2–6 °C window, and the dashboard offers a downloadable weekly CSV.
The open-vial holder starts a 3-day countdown when a vial is placed and switches the slot's LED from green to red when usability expires, removing the need for handwritten dates entirely. The rotating rack reads expiration dates from QR codes on unopened vials and rotates the next-to-expire vial to the front, with per-slot LEDs flagging anything already expired.
Affiliation
NTNU
Partners
- Elias Drøpping
- Jon Arne Lund
- Sven Kristian Helland
- Henrik Alstadhaug
- Asil Zogby
Report
- Project report
Keywords
- IoT
- Electromechanical Systems
- Embedded Systems
- C
- Python
- Raspberry Pi
- BME680
- MySQL
- Node.js
- React
- TypeScript
▸ Deepdive
Introduction
MediCare is a hospital-logistics system built for the Fysikalsk medisinsk poliklinikk at St. Olavs universitetssykehus as part of NTNU’s TTT4270 Elektronisk systemdesign in spring 2024. The clinic stores 15–20 vials of lidocaine (lokalbedøvelse) in a single refrigerator at any time, each vial a 20 ml dose that, once opened, is reusable for three days and must otherwise be discarded. Two failure modes drive measurable drug waste: the fridge temperature drifting outside the 2–6 °C band without anyone noticing in time, and staff losing track of how long an opened vial has been in use. The system replaces the existing pen-and-paper workflow with three integrated subsystems, a continuous temperature monitor with a web dashboard, a time-tracking rack for opened vials, and a rotating rack for unopened vials, that together eliminate the manual bookkeeping and the silent failures it permits.
Problem Definition
The clinic stores a set of vials , where are opened vials with a per-vial timer and are unopened vials with a printed expiry date. A vial is usable iff three conditions hold simultaneously:
where is the continuous fridge temperature, is the elapsed time since vial was opened with shelf-life cutoff , and is the unopened expiry date stamped by the manufacturer. The clinic’s existing process resolves the first predicate via a single manual reading per day and the second via a handwritten date on the glass, both of which are dominated by human error in a busy clinical environment. The system’s job is to convert these three predicates from manual judgments into continuously-evaluated machine state, alert when any of them flips, and surface the data in a form the clinic’s secretaries can act on.
The need was distilled into ten user requirements (A–J), of which the most load-bearing are: warn staff on temperature excursions (G), log temperature for compliance auditing (I), indicate when an opened vial has expired (E), and surface the unopened vial with the shortest remaining shelf life (C).
Approach
The system decomposes into three subsystems that share only the fridge as a physical context. Each was scoped to its own user requirements and built around a microcontroller appropriate to its compute and I/O profile.
Temperature Monitoring
A Bosch BME680 reads the fridge temperature over I²C and is polled by a Raspberry Pi at intervals (system requirement 1.5). Each sample is written to a MySQL table with a timestamp via an INSERT INTO temperatur (id, temperatur, tid, dato) VALUES (...) query issued from a C client that wraps the Bosch BME680 SensorAPI. The library exposes bme680_get_sensor_data against a struct bme680_dev whose I²C read/write/delay function pointers are filled in with platform-specific implementations, on the Pi these wrap /dev/i2c-1 via open, read, and write, with a sleep(period / 1000) delay shim.
The samples feed a NodeJS back-end that exposes four APIs to a React/TypeScript front-end: a rolling average over the last hour, a downloadable weekly CSV, a graph series, and a Python-script trigger that sends SMS alerts when the last-10-minute average drifts outside the band. The front-end re-fetches every so the dashboard tracks the database in near-real-time.
Sensor accuracy was characterised by treating each reading as a uniform draw on . The mean and variance of one sample are then
and averaging samples per displayed datapoint, the central limit theorem gives
which puts at a negligible level, comfortably inside the tolerance demanded by system requirement 1.6.

Time-Tracking Rack for Opened Vials
Each slot in the opened-vial rack is a three-state machine that tracks how long a vial has been in residence:
Transitions are driven by a Zippy DF-series micro-switch signal (vial present), a reset push-button, and an elapsed-time variable derived from the ESP-32’s millis():
A WS2812B addressable LED under each slot encodes : off in , a green-to-blue gradient through proportional to , and red in . The gradient is computed every iteration of the main loop as
so a staff member glancing at the rack sees not just which vials must be discarded, but which of the remaining vials has the most life left, directly addressing user requirement C without an additional UI. The microswitches were chosen for their light actuation force (a 20 ml vial doesn’t weigh much), and a 3D-printed casing extends the lever arm to increase the moment from the vial’s weight onto the switch.
The implementation has to defend against millis() rollover, which on the ESP-32 occurs after about 49 days when the underlying unsigned long saturates at . The elapsed-time check is therefore written as
so a vial inserted near the rollover boundary still ages correctly. The full logic, replicated per slot, lives in a single infinite loop on the ESP-32 with the per-slot variables knapp_Pin, Reset_knapp, start_time, and leds[].

Rotating Rack for Unopened Vials
The unopened-vial subsystem is a rotating wheel with twelve slots, driven by an SG90 9G servo from a Raspberry Pi 4 Model B. Each slot carries an expiry date loaded into a Python list at startup, and the wheel is continuously rotated so that the slot with the smallest non-expired sits at angular position (closest to the user):
Slots whose vial has been removed are skipped (GPIO reads HIGH from the slot’s micro-switch), expired slots are marked red, and the remaining valid slots are blue, with highlighted by being rotated into front position rather than colour-coded. All electronics, Pi, servo, BME680, switch bank, and a WS2812B strip, sit in the upper rotating half of the wheel and are mechanically isolated from the stationary base via a tooth-gear coupling, which dodges the need for a slip ring in the prototype. The custom interface board is a perfboard with one pull-down per switch line, fan-out to the Pi’s GPIO header, and a resistor on the LED data line as recommended by the WS2812B datasheet.


Results
Verification was structured around the 14 system requirements derived from user requirements A–J, each checked against an explicit pass/fail criterion. The temperature subsystem passed every requirement: SMS alerts fire on excursions, the dashboard updates every , weekly CSVs are downloadable, the temperature text on the dashboard switches between green/blue/red on the band, and the central-limit-theorem analysis above bounds the worst-case display deviation well below .
The opened-vial rack was timed against a stopwatch at , , and second intervals as a proxy for the three-day countdown (the real countdown is impractical to time end-to-end on a verification deadline). Across three trials per interval, the maximum deviation was and the mean deviation , dominated by stopwatch reaction time. Extrapolated to , the timing error is well inside any clinically meaningful tolerance.
| Interval | Trial 1 | Trial 2 | Trial 3 |
|---|---|---|---|
The rotating rack met all of its requirements: diameter and height (both inside the envelope set by the clinic fridge), correct rotation to the shortest-expiry vial across repeated tests, and a red-light indication for already-expired slots.
Validation was conducted by surveying five healthcare-sector respondents (four physical, one nursing student who evaluated the web dashboard only) against the ten user requirements on a 1–100 scale. Mean scores were , , , , , , , , , . The weakest areas were (the weekly table currently reads back exactly samples, which slides the window by minutes rather than aligning to calendar days) and (the perfboard wiring is fragile under repeated handling).
Future Work
The largest remaining risk in the temperature subsystem is operational rather than electrical: the SMS alerting pipeline runs as a Python script invoked by the NodeJS back-end, so an alert is only fired if the website happens to be up. A natural fix is to lift the alerting logic out of the front-end stack entirely and run it as a standalone service on a server that is independent of the dashboard’s uptime, with a cron-triggered query against the MySQL table and the existing SMS gateway. The same service should also be hardened against fridge-power loss, which currently puts the ESP-32 rack back into on cold boot and silently loses every active countdown.
The opened-vial rack would benefit from migrating from jumper-wired perfboard onto a custom PCB, which would simultaneously address the -score complaint about wiring fragility and free up the underside of the rack for a per-slot E-paper display showing , , and the absolute opening date. Adding a screen would also remove the only colour-only failure mode in the design, a colour-blind user currently cannot distinguish the green/blue gradient from red in poor lighting, which is the single biggest accessibility hole in the current build.
The rotating rack is the subsystem with the most headroom. The current rotation is open-loop on time, so cumulative position drift is the dominant error term; closing the loop with an encoder or a Hall sensor on the base would bring the rotation under a proper controller and eliminate the residual angular drift. The rack should also gain a network link to the temperature dashboard, so that the secretaries who manage stock can see, in one place, which unopened vials are closest to expiry and how many of each lidocaine batch are left. Finally, vendor packaging delivers lidocaine in cassettes of five vials with identical expiry, which means twelve independent slots is wasteful in practice; a redesigned wheel with five-slot cassette holders, sketched in the recommendations section of the original report, is a strictly better fit for the actual SKU shape and would push the achievable capacity from vials to the – vials the clinic actually holds.
