Skip to content
Rahul-8283Public

About

Hostel attendance, marked from where you are, within hostel. Zero queues, zero new hardware - secured with biometrics, Wi-Fi geofencing, and digital fingerprint

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

240 Commits

Folders and files

Repository files navigation

NightRoll

Hostel attendance, marked from your bed.

Prove the right person, in the right room, on the right device, inside the warden's window - with no new hardware.

Team Name: Zero_Latency · Team ID: GEN26-A0H-H051

Status Platform License Hackathon Tests


Table of contents


The problem

Every night at 10:00 PM, every hosteller has to walk down to the ground floor to prove they are in the hostel. Students on the first, second, third and higher floors all queue at the same scanner, whatever their situation.

  • A student with a fever or a fall-related injury still has to climb down the stairs to be marked. Some stay in bed and get marked absent instead.
  • A final-year student back from an interview, or in the middle of a video call with a recruiter, loses time and focus on the walk down and back up.
  • Students who are unwell or hurt are the ones most likely to need help, but the system gives the warden no sign of who is in their room.
  • The check proves you were at the ground floor at 10:00 PM. It says nothing about whether you are safe, or whether you stayed.

The solution

NightRoll moves the check to the student's phone and the hostel's existing per-room WiFi. At 10 PM the student opens NightRoll and completes a short face check in bed, and the room tile turns green on the warden's dashboard.

A selfie alone would be easy to fake, so every mark has to prove three things at once:

Proof How What it stops
Right person Face match plus a random liveness challenge Someone else marking for you, photos, replayed video
Right place Request arrives from your room's subnet, backed by a WiFi fingerprint of the room Marking from outside the hostel, VPN tunnelling
Right device, right now A hardware-bound key signs a one-time nonce issued for tonight's window Marking from a friend's phone, replaying yesterday's request

No single factor is trusted on its own. The existing ground-floor scanner stays as the fallback.

flowchart LR
    subgraph Before["Today"]
        direction LR
        A1["Floors 1 to N"] --> A2["Walk down"] --> A3["Queue at one scanner"] --> A4["Walk back up"]
    end
    subgraph After["With NightRoll"]
        direction LR
        B1["Opens app at 22:00"] --> B2["Face check in bed"] --> B3["Marked present"]
    end
    Before -.-> After
Loading

Tech stack

Mobile app

Expo React Native TypeScript Android ML Kit TensorFlow Lite Play Integrity Expo Router

Backend

Python FastAPI Upstash Redis Celery WebSocket APScheduler Pydantic asyncpg Uvicorn

Face and liveness

InsightFace ONNX Anti-spoof

Data

Supabase PostgreSQL pgvector Supabase Auth Supabase Storage

Dashboard and delivery

React Vite React Router TanStack Query Docker Docker Compose nginx

Layer Choice Why
App Expo (React Native, TypeScript), Expo Router, Kotlin Expo Modules, VisionCamera, ML Kit Expo for builds and EAS cloud builds; a development build runs the native code; one codebase for screens
On-device ML ML Kit face detection; TFLite MobileFaceNet planned Quick pre-check so a bad capture fails before upload
Device trust Android Keystore / StrongBox, Play Integrity Key never leaves the chip; blocks root, emulators, virtual cameras
API FastAPI, Pydantic, Uvicorn Async, typed, and every request is validated against a schema; fast to build
State Upstash Redis (hosted, TLS) Nonces, window state and rate limits with native TTL, no local server to run
Face InsightFace buffalo_l on ONNX Runtime, Silent-Face anti-spoofing Strong open ArcFace embeddings and an anti-spoof check, both on CPU
Database Supabase (PostgreSQL with pgvector), asyncpg Relational data and 512-dimension embeddings in one managed database
Auth Supabase Auth Warden and admin login with MFA; student devices are bound by Keystore keys, not passwords
Frame storage Supabase Storage (private bucket) Encrypted, short-lived frames with a 24-hour deletion job
Network verify Subnet check built; controller API or RADIUS accounting behind one interface Starts simple, upgrades without app changes
Dashboard React, Vite, React Router, TanStack Query over WebSocket Room tiles update live, and fall back to polling when the socket drops
Window status Pulled from the API when the app opens No push service; students open the app at window time

System architecture

Four zones: the phone gathers evidence, the hostel network vouches for location, the backend decides, and the outputs inform the student and the warden.

flowchart LR
    subgraph Phone["1. Student phone"]
        direction TB
        P1["Guard checks<br/>VPN, proxy, mock location"]
        P2["Liveness challenge"]
        P3["Face frames"]
        P4["WiFi scan"]
        P5["Play Integrity token"]
        P6["Keystore signer"]
        P7["Signed payload"]
        P1 --> P2 --> P3 --> P4 --> P5 --> P6 --> P7
    end

    subgraph Net["2. Hostel network"]
        direction TB
        N1["Room access point"]
        N2["Room VLAN / subnet"]
        N3["WiFi controller / RADIUS"]
        N1 --> N2
        N1 -.-> N3
    end

    subgraph Back["3. NightRoll backend on campus LAN"]
        direction TB
        B0["Window service"]
        B1["API gateway"]
        B2["Hard gates"]
        B3["Scored checks"]
        B4["Fusion engine"]
        B5[("Supabase PostgreSQL + pgvector")]
        B6[("Upstash Redis")]
        B7["Network adapter"]
        B0 --> B1 --> B2 --> B3 --> B4
        B0 <--> B6
        B4 --> B5
        B7 --> B3
    end

    subgraph Out["4. Outputs"]
        direction TB
        O1["Window status on Home"]
        O2["Student result"]
        O3["Warden dashboard"]
    end

    P7 -- "HTTPS" --> N1
    N2 -- "source IP = room" --> B1
    N3 -.-> B7
    B0 --> O1
    O1 -- "shown when app opens" --> P1
    B4 --> O2
    B4 --> O3
Loading

Backend components

flowchart TB
    GW["API gateway"] --> ORC["Verification orchestrator"]

    ORC --> CV["Crypto verifier<br/>signature, nonce freshness"]
    ORC --> IV["Integrity verifier<br/>Play Integrity decode"]
    ORC --> NV["Network verifier<br/>subnet, controller, RADIUS"]
    ORC --> FV["Face verifier<br/>ArcFace, anti-spoof"]
    ORC --> WV["Fingerprint verifier<br/>room WiFi pattern"]

    CV --> FE["Fusion engine"]
    IV --> FE
    NV --> FE
    FV --> FE
    WV --> FE

    WS["Window service"] --> RD[("Upstash Redis<br/>nonces, window state")]
    CV --> RD
    FV --> PG[("Supabase PostgreSQL + pgvector<br/>students, rooms, embeddings, logs")]
    WV --> PG
    FE --> PG
    FE --> RT["WebSocket hub"] --> DB["Warden dashboard"]
Loading

Verification pipeline

Hard gates run first. Any single failure rejects the mark before a face is ever compared. Only marks that pass every gate are scored.

flowchart TD
    S(["Signed payload arrives"]) --> G1{"Inside window?<br/>server clock"}
    G1 -- no --> R["REJECTED"]
    G1 -- yes --> G2{"Signature valid<br/>and nonce unused?"}
    G2 -- no --> R
    G2 -- yes --> G3{"Play Integrity pass?"}
    G3 -- no --> R
    G3 -- yes --> G4{"No VPN, proxy<br/>or mock location?"}
    G4 -- no --> R
    G4 -- yes --> G5{"Source IP in<br/>room subnet?"}
    G5 -- no --> R
    G5 -- yes --> SC["Score: face, liveness,<br/>room fingerprint, RSSI"]
    SC --> F{"Trust score"}
    F -- "0.80 or above" --> P["PRESENT"]
    F -- "0.60 to 0.80" --> V["NEEDS REVIEW"]
    F -- "below 0.60" --> R
    V --> W["Warden review queue"]
    R --> FB["Fallback: ground-floor scanner"]
Loading
Gate Defeats
Server-clock window Changing the phone's time
Signature and fresh nonce A friend's phone, replayed requests
Play Integrity Rooted devices, emulators, virtual cameras
No VPN, proxy or mock location Tunnelling in from outside
Room subnet source IP Any request from off the hostel network

One night, end to end

sequenceDiagram
    autonumber
    actor W as Warden
    participant D as Dashboard
    participant S as Backend
    participant R as Upstash Redis
    participant A as NightRoll app
    actor U as Student
    participant AP as Room AP

    W->>D: Set window, default 22:00 to 22:30
    D->>S: Save window and off days
    Note over S: 22:00 window opens
    S->>R: Store one nonce per student with TTL
    U->>A: Opens NightRoll in bed
    A->>A: Guard checks for VPN, proxy, mock location
    A->>U: Random liveness challenge
    U->>A: Blink, turn head
    A->>A: Capture frames, scan WiFi, fetch integrity token
    A->>A: Sign payload with Keystore key
    A->>AP: POST verify over HTTPS
    AP->>S: Forward, source IP is the room subnet
    S->>R: Check and consume nonce
    S->>S: Hard gates, then scoring, then fusion
    S-->>A: Verdict with reason
    S-->>D: Room tile updates live
    Note over S: 22:30 window closes
    S->>R: Nonces expire
    S-->>D: Absentee list and review queue
    W->>D: Review flagged marks, override if needed
Loading

Attendance states

stateDiagram-v2
    [*] --> Pending: window opens
    Pending --> Present: gates pass and score is high
    Pending --> NeedsReview: gates pass and score is borderline
    Pending --> Rejected: a gate fails or score is low
    Pending --> Absent: window closes with no mark
    NeedsReview --> Present: warden approves
    NeedsReview --> Absent: warden declines
    Rejected --> Pending: student retries inside the window
    Rejected --> Present: ground-floor scanner or warden override
    Absent --> Present: warden override
    Present --> [*]
    Absent --> [*]
Loading

Enrollment

Enrollment happens once, in person, at the warden's office. That is what makes the later remote checks trustworthy.

flowchart TD
    A(["Student at warden office"]) --> B["Consent recorded"]
    B --> C["Controlled face capture"]
    C --> D["ArcFace embedding, 512 dimensions"]
    D --> E[("Store vector in Supabase pgvector<br/>photo discarded")]
    B --> F["App generates keypair in Keystore"]
    F --> G[("Register public key to student")]
    B --> H["3 to 5 WiFi scans in the room<br/>bed, desk, door"]
    H --> I[("Room fingerprint")]
    J["Network admin"] --> K[("Room to BSSID to subnet map")]
    E --> L(["Enrolled"])
    G --> L
    I --> L
    K --> L
Loading
  • Face: only the embedding is stored. The enrollment photo is discarded.
  • Device: the private key is generated inside the phone's secure hardware and never leaves it. A new phone means re-enrolling the device.
  • Room: one student scans the room; roommates share the same room fingerprint.

Trust score

Marks that pass every gate are scored from five signals.

pie showData
    title Trust score weights (provisional)
    "Face match" : 30
    "Room WiFi fingerprint" : 30
    "Liveness and anti-spoof" : 20
    "Own-AP signal strength" : 15
    "AP-side signal agreement" : 5
Loading
Signal Weight What it measures
Face match 0.30 Cosine similarity between tonight's embedding and the enrolled one
Room WiFi fingerprint 0.30 How closely tonight's scan matches the room's saved pattern
Liveness and anti-spoof 0.20 Challenge completed, and the face is not a photo or a screen
Own-AP signal strength 0.15 The room's own router is seen at room-level strength
AP-side signal agreement 0.05 The router's view of the phone agrees with the phone's report
Score Verdict What happens
0.80 or above PRESENT Recorded; room tile turns green
0.60 to 0.80 NEEDS REVIEW Goes to the warden's review queue
Below 0.60 REJECTED Student sees the reason and can retry or use the ground-floor scanner

Note

These weights and cutoffs are starting values. They will be tuned against real measurements from the hostel (see the walk test below) before anyone relies on them.


Geofencing with WiFi

GPS cannot tell one room from the next indoors, and it is easy to fake. NightRoll uses the WiFi itself as the fence.

Every spot in the hostel sees the surrounding routers differently. Inside room 203, the room's own router is very strong, the neighbours are medium, the corridor is weak. Outside the compound wall, most hostel routers vanish and unfamiliar networks appear. That pattern is the room's fingerprint.

flowchart TD
    A(["Tonight's WiFi scan"]) --> B{"Own room router<br/>present and strong?"}
    B -- no --> X["Low fingerprint score"]
    B -- yes --> C{"Expected neighbour<br/>routers present?"}
    C -- "few" --> X
    C -- "most" --> D{"Relative strengths<br/>match the usual order?"}
    D -- no --> Y["Medium fingerprint score"]
    D -- yes --> E{"Unknown outside<br/>networks visible?"}
    E -- "several" --> Y
    E -- "none or few" --> Z["High fingerprint score"]
    X --> F(["Feeds trust score"])
    Y --> F
    Z --> F
Loading

Calibrating with a walk test

Limits are measured, not guessed.

flowchart LR
    A["Scan inside<br/>several rooms"] --> C["Compare the<br/>two groups"]
    B["Scan outside:<br/>compound wall, corridor,<br/>next room, floor below"] --> C
    C --> D["Set limits<br/>between them"]
    D --> E["Stricter limits for<br/>rooms near the wall"]
Loading

Keeping fingerprints fresh

  • Slow drift: confident "present" marks are blended into the saved fingerprint. Borderline marks never are, so a cheater cannot teach the system a wrong pattern.
  • Router replaced: the warden triggers a re-scan for that room.
  • Stale fingerprint: repeated reviews from one room flag it on the dashboard.

Supporting layers

Layer Role
Router minimum-signal setting The router drops weak clients, so distant phones cannot stay connected
Router-side signal check A large gap between the router's reading and the phone's report suggests a faked scan
5 GHz signal Blocked by walls more than 2.4 GHz; used as evidence if the walk test shows it helps, not as a rule
GPS A warning only, and only when accurate and clearly outside the hostel

Threat model

flowchart LR
    T1["Friend marks for me<br/>on their phone"] --> D1["Device key signature"]
    T2["Friend marks for me<br/>on my phone"] --> D2["Face match"]
    T3["Photo or video<br/>of my face"] --> D3["Liveness and anti-spoof"]
    T4["Replay last<br/>night's request"] --> D4["One-time nonce"]
    T5["Mark from outside<br/>over VPN"] --> D5["VPN guard and<br/>room subnet gate"]
    T6["Catch weak WiFi from<br/>outside the wall"] --> D6["Room fingerprint<br/>and signal strength"]
    T7["Rooted phone, emulator<br/>or fake camera"] --> D7["Play Integrity"]
    T8["Change the<br/>phone clock"] --> D8["Server-clock window"]
Loading
Limitation Handling
Student marks present, then leaves Out of scope for the first release; random spot-checks are on the roadmap
Phone dead, no WiFi, router down Ground-floor scanner stays as the fallback
iPhones cannot do full WiFi scans Android first; iPhone users keep the ground-floor scanner
Hostel network does not expose subnets or a controller API Needs the network admin; the design degrades to fingerprint plus review

Data model

erDiagram
    STUDENT ||--o| DEVICE : registers
    STUDENT ||--|| FACE_EMBEDDING : has
    STUDENT }o--|| ROOM : lives_in
    ROOM ||--o{ ACCESS_POINT : served_by
    ROOM ||--o| ROOM_FINGERPRINT : has
    ATTENDANCE_WINDOW ||--o{ NONCE : issues
    STUDENT ||--o{ NONCE : receives
    STUDENT ||--o{ ATTENDANCE_MARK : makes
    ATTENDANCE_WINDOW ||--o{ ATTENDANCE_MARK : contains
    ATTENDANCE_MARK ||--o| REVIEW : may_need

    STUDENT {
        uuid id PK
        string roll_number
        string name
        uuid room_id FK
        timestamp consent_at
    }
    DEVICE {
        uuid id PK
        uuid student_id FK
        text public_key
        timestamp enrolled_at
    }
    FACE_EMBEDDING {
        uuid student_id PK
        vector embedding
    }
    ROOM {
        uuid id PK
        string number
        int floor
        string subnet
    }
    ACCESS_POINT {
        string bssid PK
        uuid room_id FK
        string band
    }
    ROOM_FINGERPRINT {
        uuid room_id PK
        json pattern
        timestamp updated_at
    }
    ATTENDANCE_WINDOW {
        uuid id PK
        date night
        timestamp opens_at
        timestamp closes_at
    }
    NONCE {
        string value PK
        uuid student_id FK
        uuid window_id FK
        boolean used
    }
    ATTENDANCE_MARK {
        uuid id PK
        uuid student_id FK
        uuid window_id FK
        float trust_score
        string verdict
        string reason
        timestamp marked_at
    }
    REVIEW {
        uuid mark_id PK
        string decision
        string reviewed_by
        timestamp reviewed_at
    }
Loading

Nonces live in Upstash Redis with a TTL that ends when the window closes. They are shown here for the relationships only.


Deployment

The backend sits on the campus LAN so the source IP of every request is the student's real room address.

flowchart TB
    subgraph Rooms["Hostel rooms"]
        PH["Student phones"]
        AP["Room access points"]
        PH --> AP
    end

    subgraph LAN["Campus LAN"]
        CTRL["WiFi controller / RADIUS"]
        subgraph Host["NightRoll host"]
            API["FastAPI service"]
            WK["Face worker"]
            RDS[("Upstash Redis")]
            PGS[("Supabase PostgreSQL + pgvector")]
            WEB["Dashboard"]
            API --> RDS
            API --> PGS
            API --> WK
            WK --> PGS
        end
        WD["Warden desk"]
    end

    subgraph Cloud["Google services"]
        PIS["Play Integrity API"]
    end

    AP --> API
    CTRL -.-> API
    WD --> WEB
    WEB <--> API
    API --> PIS
Loading

Privacy

Face data is sensitive, so the rules are fixed from the start.

  • Consent first. Enrollment requires explicit, recorded consent.
  • Embeddings, not photos. Only the numeric face embedding is stored.
  • Short-lived frames. Frames captured for a nightly check are deleted within 24 hours.
  • No biometric logging. Embeddings and raw frames never appear in logs.
  • Reasons shown. A student always sees why a mark was rejected or sent to review.
  • A human decides the edge cases. The warden can override any automatic verdict.

Demo plan

Two ordinary routers stand in for Room 101 and Room 102, each on its own subnet.

# Action Expected result
1 Mark from the Room 101 router PRESENT; the tile turns green
2 Switch to the Room 102 router REJECTED at the room subnet gate
3 Hold up a photo or a video of the face REJECTED by liveness
4 Mark from a teammate's phone REJECTED; signature does not match
5 Walk out of the building while still on WiFi REJECTED; WiFi pattern does not match the room

Production plugs into the hostel's existing WiFi controller or RADIUS. No new hardware is required.


Roadmap

flowchart LR
    subgraph M1["MVP: hackathon"]
        direction TB
        a1["Signed nonce per student"]
        a2["Room subnet gate"]
        a3["Face match and liveness challenge"]
        a4["Dashboard with room tiles"]
    end
    subgraph M2["Next"]
        direction TB
        b1["WiFi fingerprint and walk test"]
        b2["Passive anti-spoof model"]
        b3["Controller or RADIUS adapter"]
    end
    subgraph M3["Later"]
        direction TB
        c1["Random spot-checks"]
        c2["Anomaly detection"]
        c3["ERP export, iOS support"]
    end
    M1 --> M2 --> M3
Loading

Getting started

Prerequisites

Install these once. The commands are for macOS with Homebrew; Windows and Linux users can use the official installers.

Tool Version Needed for Install
Git any recent Source control brew install git
uv current Backend and face worker: Python versions, dependencies brew install uv
Python 3.12 Backend, face worker (uv installs it, so this is optional) brew install python@3.12
Node.js 20 LTS Dashboard, app tooling brew install node@20
JDK 17 Android build brew install openjdk@17
Android Studio current stable Android SDK, emulator developer.android.com/studio
Docker Desktop current stable Later services (Phase 12); not needed for Redis docker.com, then start it
EAS CLI current Development and production builds of the app npx eas-cli (no install needed)
Supabase CLI current stable Migrations, linking the project brew install supabase/tap/supabase

First-time setup

  1. Clone the repo and switch to the branch you are working on:

    git clone https://github.com/Rahul-8283/NightRoll.git
    cd NightRoll
    git checkout base
  2. Create your local env files from the examples. Ask the team for the real values; never commit them.

    cp backend/.env.example backend/.env
    cp face/.env.example face/.env
    cp dashboard/.env.example dashboard/.env
    cp app/.env.example app/.env
    cp config/rooms.example.json config/rooms.local.json
  3. Put the Google service account file at backend/secrets/google-service-account.json (from the team password manager).

  4. Paste your Upstash rediss:// URL into backend/.env as REDIS_URL, then check it (see below).

  5. Link Supabase: follow supabase/README.md.

Running each service

Service Port Start with Available from
Upstash Redis hosted, TLS set REDIS_URL in backend/.env Phase 0 (now)
Supabase hosted supabase link --project-ref <ref> Phase 0 (now)
Backend API 8000 cd backend && uv run uvicorn app.main:app --reload --port 8000 Health route now; full API in Phase 2
Window scheduler none cd backend && python -m app.scheduler Phase 3
Face worker 8100 cd face && uvicorn worker.main:app --port 8100 Phase 5
Warden dashboard 5173 cd dashboard && npm run dev Shell now; screens in Phase 10
Student app Metro 8081 cd app && npx expo start Shell now; features in Phases 4 to 7 (Expo Go for screens; development build for the rest)

Start order once everything exists: face worker, backend API, scheduler, dashboard, then the phone app. Upstash Redis is always on.

Redis (works now, hosted on Upstash)

Redis runs in the cloud, so there is no server to start. Check the connection with the Redis client in a container, so nothing needs installing on the laptop:

docker run --rm redis:7 redis-cli --tls -u "<your REDIS_URL>" ping   # expect PONG

Use the same rediss:// URL as in backend/.env. Do not commit it.

Backend API (set up now with uv)

cd backend
uv sync
uv run uvicorn app.main:app --reload --port 8000
curl http://localhost:8000/v1/health    # expect {"status":"ok"}

Window scheduler (from Phase 3) opens and closes attendance windows at the right time, and records housekeeping tasks. Run it in its own terminal:

cd backend
uv run python -m app.scheduler

The scheduler writes a heartbeat to Redis every 15 seconds. If the heartbeat goes stale (older than 60 seconds), the dashboard shows a critical alert. To restart a stalled scheduler:

# In Docker
docker compose -f infra/docker-compose.yml restart scheduler

# Or locally
cd backend && uv run python -m app.scheduler

Face worker (from Phase 5)

cd face
python3.12 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
python scripts/download_models.py
uvicorn worker.main:app --port 8100

Warden dashboard (shell available now)

cd dashboard
cp .env.example .env     # fill in the Supabase URL and anon key
npm install
npm run dev              # open http://localhost:5173

The pages are placeholders until Phase 10. The dashboard shows which variables are missing if .env is incomplete.

Student app (from Phase 4)

Expo Go shows the screens that don't use native code. Anything that uses the Keystore, Wi-Fi or Play Integrity needs a development build.

cd app
npm install
npx expo start                 # Metro, scan the QR code with Expo Go for screens
npx eas-cli build --profile development --platform android   # once: a dev build to install on the phone
npx expo start --dev-client    # then open the dev build on the phone

The phone and the laptop must be on the same Wi-Fi. Set EXPO_PUBLIC_API_BASE_URL to the laptop's LAN address, not 10.0.2.2 (that address only works in the Android emulator).

Stopping everything

Press Ctrl+C in each terminal. Upstash Redis keeps running in the cloud, so there is nothing to stop for it.

Repository layout

NightRoll/
├── app/          Expo student app (Android, Kotlin Expo Modules)
├── backend/      FastAPI service: windows, nonces, gates, scoring, review
├── face/         Face worker: embeddings and anti-spoof (internal)
├── dashboard/    React warden dashboard
├── supabase/     Supabase CLI project and SQL migrations
├── infra/        Docker Compose and deployment files
├── config/       Room map example (real map stays local, git-ignored)
├── scripts/      One-off tools and Phase 0 risk spikes
├── docs/         PRD, TRD, architecture
└── phase/        Build plan, one file per phase

Project status

The design is settled (see docs/ and phase/). Phase 0, base setup, is in progress: folders, env examples, the Upstash Redis setup and the setup steps are in place. No feature code exists yet.

License

Released under the Apache License 2.0.

Team

Team Zero_Latency

Rahul L S
Rahul L S
@Rahul-8283
Jivites
Jivites
@djivites
Sukanthan
Sukanthan
@sukanthan06
Kesav Satya Sai Nimmagadda
Kesav Satya Sai Nimmagadda
@kesavvvvvv

About

Hostel attendance, marked from where you are, within hostel. Zero queues, zero new hardware - secured with biometrics, Wi-Fi geofencing, and digital fingerprint

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages