All work

/ Case study · Jun 2026 · Final-year project

Asfalis

A personal safety system for women: an Android app, an ESP32 safety bracelet and a backend that sends your live location to a verified circle of contacts over WhatsApp and SMS. SOS can start from a button, a shake, a press on the bracelet, walking away from your phone, or a motion model that spots a fall. It was our final-year project. I built most of the backend, the ML pipeline, the app's networking and wearable layers, and the landing site.

Role
Backend lead (72 of 81 commits), ML pipeline, Android networking and wearable integration, landing site
Team
5, with Arpan Mukherjee (Android app and on-device ML), Gairik Ghosh (Flask → FastAPI migration), Sritama (backend fixes and testing) and Anannya Guchait (ONNX export)
Timeline
Feb–Jun 2026 · 72 of 81 backend commits and 7 of 23 Android commits
Stack
FastAPI, PostgreSQL, Kotlin, LightGBM, Twilio, ESP32
Asfalis screenshot

/ 01

The problem

Most safety apps rely on one thing: you unlocking your phone and pressing a button. That's exactly what you can't do if you've fallen, been grabbed, or your phone is in a bag across the room.

Even when an alert does go out, the sender rarely knows whether it arrived. A message to a mistyped number, or to a contact who never opted in, looks the same as one that got through.

/ 02

Approach

We gave SOS several ways to start, so no single failure blocks it: an on-screen button, shaking the phone, a press on the bracelet, the bracelet losing range, and an on-device motion model. Every trigger feeds the same 10-second countdown, and you can cancel it if you're safe.

I built the backend so it finishes the job even if the phone doesn't. The server owns the countdown, sends WhatsApp and SMS through Twilio with a Maps link to your location, and reports back on each contact. Contacts have to confirm an SMS code before they can be added, so alerts go to real, reachable numbers.

Because the app holds people's names, phone numbers and locations, I encrypted those fields in the database and kept a keyed hash beside each one, so the server can still look people up without storing anything readable.

/ 03

What I built

  • SOS that finishes itself

    Starting an SOS creates the alert on the server and starts a 10-second countdown. If the app is killed or loses signal before it confirms, a server thread sends the alert anyway. A separate 30-day SOS token lets alerts through even after the login session has expired.

  • Delivery you can check

    Alerts go out over WhatsApp and SMS through two Twilio accounts, with the sender's message and a Maps link. Twilio's error codes are turned into plain statuses such as 'not opted in' or 'daily limit reached', and the app shows what happened for each contact.

  • Bracelet, phone and server in step

    One press on the ESP32 bracelet starts an SOS, and a double press within 1.5 seconds cancels it. The phone holds a Classic Bluetooth link to the bracelet and also reads its BLE signal strength. Walking about 5 m away from the bracelet triggers a warning, and going further starts an SOS.

  • Auto-SOS motion model

    We logged labelled motion data with our own app, cut it into 300-sample windows and turned each into 17 features. I compared Random Forest, XGBoost, LightGBM and AdaBoost, and the chosen LightGBM model runs on the phone through ONNX Runtime.

  • Personal data encrypted at rest

    Names, phone numbers, emails, coordinates, SOS messages, device IDs and push tokens are encrypted column by column. Searchable fields get an HMAC column for exact-match lookups, and a one-off script back-filled rows written before encryption.

  • Accounts that are hard to hijack

    Phone sign-up with SMS codes and bcrypt passwords, 15-minute access tokens with rotating refresh tokens, lockout after five failed logins, rate limits, and an account bound to one handset with a 12-hour wait before moving to a new phone.

/ 04

How it works

  1. Wearable
    • ESP32 bracelet
    • Classic Bluetooth
    • BLE signal strength
  2. Android
    • Kotlin + Compose
    • Foreground services
    • ONNX Runtime
    • Retrofit + DataStore
  3. API
    • FastAPI
    • Socket.IO
    • JWT + SOS token
    • Docker on Render
  4. Data & messaging
    • PostgreSQL
    • Encrypted columns
    • Twilio WhatsApp + SMS
    • FCM
The phone watches the sensors and the bracelet, and decides when to start an SOS. The FastAPI server owns everything after that: the countdown, dispatch through Twilio, delivery reports and the alert history in PostgreSQL. The model is trained in Python, exported to ONNX and bundled in the app, so detection still works without a network.

/ 05

Hard parts

  1. 1

    A countdown the server finishes

    The app is meant to confirm the alert when its countdown ends, but a phone in trouble may not get that far. So when an SOS starts, the server also starts a background thread that wakes two seconds after the countdown and sends the alert if nobody has cancelled it. Sending checks the alert's status first, so a confirmed alert isn't sent twice. Next I'd lock the alert row in the database, so the app and the thread can't both pass that check at once.

    def _auto_dispatch_after_countdown(aid, delay):
        time.sleep(delay)
        session = ScopedSession()
        alert_obj = session.get(SOSAlert, aid)
        if alert_obj and alert_obj.status == 'countdown':
            dispatch_sos(aid)
    
    grace = COUNTDOWN_SECONDS + 2   # 2-second grace for network latency
  2. 2

    Encrypted, but still searchable

    Encrypting a phone number makes it unreadable, but logging in still means finding a user by phone number. Each encrypted column encrypts on write and decrypts on read automatically, so the rest of the code never sees ciphertext. A keyed HMAC of the normalised value sits beside it, so the server can match a number without storing it in plain text.

    class EncryptedString(TypeDecorator):
        def process_bind_param(self, value, dialect):
            return None if value is None else encrypt(str(value))
        def process_result_value(self, value, dialect):
            return None if value is None else decrypt(value)
    
    def compute_hmac(value: str) -> str:
        normalised = value.strip().lower()
        return hmac.new(key, normalised.encode(), hashlib.sha256).hexdigest()
  3. 3

    Talking to a homemade bracelet

    Our ESP32 dropped connections when the phone tried the fast, reflection-based Bluetooth socket first, because quick repeated failures put it into a cooldown. Trying the standard service lookup first, with reflection as a fallback, made pairing reliable. Distance comes from the BLE signal, where one reading can be off by 15 dBm, so I take the median of several and need two out-of-range readings in a row. A short wait after the first press tells a real single press from switch bounce.

  4. 4

    A model that looked better than it was

    On paper the model was excellent: 97% accuracy and 0.99 ROC-AUC on a 35-window test set. But the whole dataset was 174 windows, with one recording per activity split at random, so the test windows came from the same recordings as the training ones. On a real phone it scored violent shaking as safe. Part of the cause was units: we trained on g, Android reports m/s², and one feature came out almost 100 times too large. We fixed the units and moved scaling out of the model. The real fix is far more data, split by recording.

/ 06

Design

I designed the landing site in a calm, trustworthy palette rather than alarm red everywhere: ivory backgrounds, charcoal for the 'problem' sections, and crimson kept for the SOS moment and calls to action. Playfair Display carries the big promises and Inter carries everything else.

Every phone on the page is built in code, not a screenshot: an SOS screen with a live 10-second ring, a shake alert, a motion graph, the safety circle and the alert history. Each feature has its own mockup. A comparison table and a three-step 'How it works' section explain the system without jargon. The app itself uses Arpan's dark 'Neon Rose' theme.

  • Crimson#C0392B
  • Ivory#FAF9F6
  • Charcoal#2C2C2C
  • Blush#F1948A
  • Teal#1A6B6B

/ 07

Outcome

72 of 81 backend commits · 50+ API endpoints · personal data encrypted at rest

  • Shipped as an Android APK with the backend on Render, and a landing site at asfalis.in. All five SOS triggers run through the same server-side countdown and dispatch.
  • The backend has about 4,600 lines of Python across 50+ endpoints, 11 tables and 17 database migrations, and ended up on FastAPI after starting on Flask.
  • Not finished: the automated tests were removed during the FastAPI move and never replaced, the live map is still a mock-up, and a review after the security pass found gaps in token handling and access checks that I'd close before any wider release.

/ 08

What I learned

  • Design for the moment everything fails. The most important code in a safety app is what still runs when the phone dies, the session expires or a message bounces.
  • Small datasets flatter you. A 97% score from 174 windows told us less than one afternoon of shaking a real phone. Split by recording and test on the device.
  • Writing a security audit isn't the same as doing one. A document listing fixed issues gave us confidence the code hadn't earned, so I now check every claim against the code and test the paths an attacker would try.

/ 09

Screens

Asfalis landing page with the headline 'Your safety. In your hands. Always.', Download APK and See how it works buttons, and a phone showing a 10-second SOS countdown with I Am Safe and Send Now buttons
The landing site I built, with the SOS countdown as the hero.
Two feature rows: a phone showing an ML engine danger-detected graph beside text on the dual-track detection engine, and a phone and bracelet card beside text on the ESP32 wearable and its proximity alerts
Fall detection and the bracelet, each with its own coded phone mock-up.
Asfalis Safety Bracelet section with a photo of the ESP32 prototype in a clear case, a BLE Connected status at -42 dBm, and cards for ESP32 architecture, haptics, fast charging and proximity warning
The ESP32 bracelet prototype and what it does.
Comparison table of basic safety apps against Asfalis across ten features, including shake-to-SOS, AI motion detection, BLE wearable support, WhatsApp and SMS alerts and OTP-verified contacts
How it compares with a basic safety app, feature by feature.

/ Next case study

Police Record Management

A records workspace for police stations. Upload case files or scan handwritten FIRs, fix inconsistent names with rules plus Gemini, and keep an audit trail of every change. Built in a 38-hour sprint, then rebuilt solo with verified accounts and server-side AI limits.