Medical Mechatronics • VCE SAT Project 2026

V.I.S.O.R.

Visual Inspection & Smart Occupational Relief

An automated, multimodal first-aid triage and dispensing station engineered to eliminate the "Panic Gap" in school technology workshops. Integrating a dual-controller architecture (Raspberry Pi 5 + Arduino Uno), Gemini 3.7 Flash vision AI, and custom 3D-printed active rack-and-pinion dispensers.

Developer: Aryan Gupta
School: The Geelong College
Compute: Pi 5 (Rust) + Arduino (C++)
Vision Model: Gemini 3.7 Flash
Verification: 48 / 48 Tests Passed
V.I.S.O.R. Open-Frame Benchtop Rig
Integrated Benchtop Prototype Click to Expand
2.91 ms
Rust Compute
15.1 s
End-to-End Triage
118.5 mm
Rack Stroke

Bridging the Workshop "Panic Gap"

When students or staff sustain minor lacerations, burns, or abrasions in technology workshops, shock and acute disorientation create a dangerous cognitive bottleneck. Passive first-aid kits require stressed users to manually identify and open supplies, causing severe delays in treatment.

Traditional Solution

Passive First-Aid Box

Requires an injured, panicked person to make medical decisions under acute cognitive stress.

  • High Cognitive Stress: User must rummage through chaotic, unorganized supplies while bleeding.
  • Packaging Friction: Standard gravity chutes and spiral vending coils snag on flexible bandage wrappers.
  • No Real-Time Feedback: Lack of instructional demonstration leads to improper antiseptic application.
V.I.S.O.R. Solution

Autonomous Multimodal Station

Automates medical triage and item delivery, bridging the critical treatment window in under 15 seconds.

  • Zero-Friction Voice/Vision Input: Hands-free wake word trigger and camera capture diagnose injuries deterministically.
  • Positive Horizontal Thrust: Servodriven rack-and-pinion pusher forcibly clears flexible packaging without jams.
  • Automated Kiosk Guidance: Pi automatically resolves and autoplays relevant first-aid video instructions.
Panic Gap Timeline Diagram

The 15-Second Emergency Service Level Agreement (SLA)

Every phase of the system is engineered to minimize latency overhead, ensuring that an injured user receives immediate medical relief:

01 Wake Trigger (0.0s): Offline audio spotter recognizes "VISOR help"
02 Capture & AI Triage (2.9ms compute): Gemini evaluates wound severity
03 UART Actuation (<1.0µs): Serial bridge commands Arduino Uno
04 Physical Dispense (<15.0s): 118.5mm rack stroke ejects sterile supply

Physical System Demonstration

Watch the realized open-frame V.I.S.O.R. test-rig perform a closed-loop first-aid triage cycle: from injury capture to AI diagnosis, UART packet framing, and dual FS90R servo ejection.

PHASE 1: 00:00 - 00:04
Standby & Capture
System awaits trigger; camera captures high-resolution reference image of laceration in volatile RAM.
PHASE 2: 00:04 - 00:08
Gemini Vision Triage
Pi 5 compiles structured JSON request; Gemini 3.7 Flash diagnoses injury and flags bandages and alcohol prep pad.
PHASE 3: 00:08 - 00:12
Serial Actuation
Pi 5 transmits <DISP:1,0> over UART; Arduino drives continuous rotation servo forward for 1700ms.
PHASE 4: 00:12 - 00:15
Ejection & Video
Pusher sled ejects secondary packaging onto tray; Chromium kiosk displays localized first-aid tutorial.

Mechatronic Engineering & Production

Click any technical image below to inspect the fabrication details, CAD kinematics, and physical testing setup of the V.I.S.O.R. prototype.

Why Rack & Pinion?

Standard gravity chutes and spiral vending coils fail on soft, flexible medical wrappers due to friction snagging. The rack & pinion applies direct positive horizontal thrust.

Zero-Idle Servo Detach

Continuous rotation servos suffer from idle hunting and jitter. The Arduino firmware executes servo.detach() immediately following stroke completion, eliminating idle heat and coil dissipation.

Modular 3D Printing

Fabricated in PLA bioplastic on Prusa MK3S+/MK4 printers at 0.2mm layer height with 1.5mm gear meshing tolerances, enabling rapid field replacement of cartridges.

Dual-Microcontroller Architecture

V.I.S.O.R. decouples high-level cloud reasoning and multimedia kiosk management from low-level electromechanical PWM timing using a dedicated dual-controller topology.

High-Level Host

Raspberry Pi 5 (8GB)

BCM2712 @ 2.4GHz

Executes multi-threaded compiled Rust application handling high-bandwidth sensory input, encrypted cloud API communication, and Chromium kiosk automation.

Language & Runtime: Rust 2024 Edition (Tokio Async)
Audio Pipeline: 16kHz Mono CPAL + Rustpotter
Computer Vision AI: Google Gemini 3.7 Flash VLM
Kiosk Display: Chromiumoxide Headless Driver
Internal Compute Latency: 2.91 ms (0.019% overhead)
Low-Level Driver

Arduino Uno R3

ATmega328P @ 16MHz

Guarantees deterministic, jitter-free hardware PWM servo timing without thread preemption or operating system scheduling delays.

Language & Firmware: C++ (Arduino Framework)
Actuator Drivers: 2x FEETECH FS90R Continuous Rotation
PWM Timing: 1700ms Push / 150ms Dwell / 1700ms Retract
Power Management: Dynamic servo.detach() Idle Sleep
Packet Build Latency: 0.87 µs (Sub-microsecond)

USB Serial Framing Protocol (9600 / 115200 Baud)

Zero-Overflow State Machine
Host → Controller
<DISP:1,0>\n
Framed ASCII command delimited by < and >. Flags: b=1 (Dispense Bandage), a=0 (Hold Alcohol Pad).
UART
Controller → Host
ACK:DISP:1,0\n
Arduino echoes validated payload before initiating servo stroke, followed by STATUS:DISPENSE_COMPLETE upon reset.

Core Software & Firmware Architecture

Inspect key implementation modules across the Rust high-level control pipeline, Arduino actuator firmware, and parametric CAD gear generation script.

use serde::{Deserialize, Serialize};
use serde_json::json;

/// Strict schema returned by Gemini 3.7 Flash multimodal vision triage
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DispenseItems {
    pub bandage: bool,
    pub alcohol_pad: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct VisorAnalysis {
    pub can_help: bool,
    pub reasoning: String,
    pub dispense: DispenseItems,
    pub video_search_query: Option<String>,
}

/// Constructs the JSON payload with structured response format schema
pub fn build_request_body(base64_string: &str) -> serde_json::Value {
    let prompt_text = "Analyze this image to evaluate the user's first-aid needs. \
        Available supplies: Bandage (Normal Size), Alcohol Prep Pad. \
        Determine if minor condition can be treated using ONLY available items. \
        For each item, specify true to dispense or false to hold. If severe, set can_help to false.";

    json!({
        "model": "gemini-3.7-flash",
        "input": [
            { "type": "text", "text": prompt_text },
            { "type": "image", "data": base64_string, "mime_type": "image/jpeg" }
        ],
        "response_format": {
            "type": "text",
            "mime_type": "application/json",
            "schema": {
                "type": "object",
                "properties": {
                    "can_help": { "type": "boolean" },
                    "reasoning": { "type": "string" },
                    "dispense": {
                        "type": "object",
                        "properties": {
                            "bandage": { "type": "boolean" },
                            "alcohol_pad": { "type": "boolean" }
                        },
                        "required": ["bandage", "alcohol_pad"]
                    }
                }
            }
        }
    })
}
/// Parses incoming serial frames from the Arduino Uno into typed responses
pub fn parse_serial_response(raw: &str) -> ArduinoResponse {
    let clean = raw.trim();
    if clean.is_empty() { return ArduinoResponse::Empty; }
    if clean == "STATUS:READY" { return ArduinoResponse::Ready; }
    if clean == "STATUS:HOLD_ALL" { return ArduinoResponse::StatusHoldAll; }
    if clean == "STATUS:DISPENSE_COMPLETE" { return ArduinoResponse::StatusComplete; }
    if clean == "PONG" { return ArduinoResponse::Pong; }
    
    // Parse ACK:DISP:b,a frame acknowledgment
    if let Some(parts) = clean.strip_prefix("ACK:DISP:") {
        let tokens: Vec<&str> = parts.split(',').collect();
        if tokens.len() == 2 {
            return ArduinoResponse::AckDispense {
                bandage: tokens[0] == "1",
                alcohol: tokens[1] == "1",
            };
        }
    }
    ArduinoResponse::Unknown(clean.to_string())
}
// --- Continuous Rotation Servo Tuning (FEETECH FS90R) ---
const int SERVO_STOP = 90;
const int SERVO_FORWARD = 48;   // ~50 RPM forward push
const int SERVO_REVERSE = 132;  // ~50 RPM reverse retract

// --- Dispense Cycle Timing (120mm rack travel) ---
const unsigned long TIME_PUSH_MS = 1700;     // 1.7s push stroke
const unsigned long TIME_PAUSE_MS = 150;     // Dwell buffer
const unsigned long TIME_RETRACT_MS = 1700;  // 1.7s retract reset

void runDispenserCycle(Servo& servo, uint8_t pin) {
    servo.attach(pin);
    
    // 1. Forward Push Stroke
    servo.write(SERVO_FORWARD);
    delay(TIME_PUSH_MS);
    
    // 2. Dwell Pause
    servo.write(SERVO_STOP);
    delay(TIME_PAUSE_MS);
    
    // 3. Reverse Retract Stroke
    servo.write(SERVO_REVERSE);
    delay(TIME_RETRACT_MS);
    
    // 4. Auto-Detach: Stop PWM to eliminate idle jitter & current draw
    servo.write(SERVO_STOP);
    servo.detach();
}
# Fusion 360 Parametric Involute Gear Generation
import math

MODULE = 1.273            # Pitch Module (mm)
PRESSURE_ANGLE = math.radians(20.0)
PINION_TEETH = 12         # 12-Tooth Drive Pinion
RACK_TEETH = 26           # Extended 120mm Rack (26 Teeth)
CIRCULAR_PITCH = math.pi * MODULE  # 4.0mm Circular Pitch

# Pitch Diameter: D = m * z
pitch_diameter = MODULE * PINION_TEETH      # 15.28 mm
base_diameter = pitch_diameter * math.cos(PRESSURE_ANGLE)
effective_stroke = (RACK_TEETH - 1) * CIRCULAR_PITCH  # 100.0mm + 18.5mm sled

print(f"Calculated Linear Stroke: {effective_stroke:.2f} mm")

Diagnostic Test Procedures & Data

Empirical testing data collected across 48 automated test suites, subsystem latency benchmarks, multimodal AI triage trials, and electromechanical packaging ejection tests.

Local Rust Compute
2.91 ms
0.019% of total latency budget
Mean End-to-End Latency
15.1 s
Bridges the 15s Panic Gap SLA
AI Focused Triage Accuracy
100%
8/8 clear lacerations classified
Automated Test Pass Rate
48 / 48
100% unit, integration & protocol
Pusher Travel Stroke
118.5 mm
Measured with digital vernier calipers
Internal Logic Voltage
5.02 V
Isolated SELV DC common-ground

Diagnostic Test Matrix (Criterion 6 Compliance)

VCE Study Design Phase 5
Parameter Observed Diagnostic Measurement & Analysis
Purpose: Measure internal compute overhead and verify compliance with the <15.0s emergency triage SLA.
Subsystem Benchmarks: Audio downmix: 2.90 ms | JSON parse: 4.52 µs | UART build: 0.87 µs. Local compute overhead: 0.019%.
Operational Trials: Trial 1: 14.2s | Trial 2: 15.8s | Trial 3: 14.9s | Trial 4: 16.0s | Trial 5: 14.6s (Mean: 15.1s).
Engineering Finding: Rust code execution is negligible; external school 2.4GHz Wi-Fi congestion and TLS handshake drive latency variance.
Parameter Observed Diagnostic Measurement & Analysis
Protocol Test Suite: 14 packet variations streamed to Arduino Uno (valid frames, holds, pings, buffer overruns); 14/14 passed (100%) with zero byte corruptions.
AI Triage Trials: 8 focused laceration images: 100% correct (8/8). 2 motion-blurred photos: defaulted safely to can_help: false. Aggregate: 80%.
Engineering Action: Implement OpenCV/Rust Laplacian variance filter to prompt users to hold hand steady before making cloud API dispatch.
Parameter Observed Diagnostic Measurement & Analysis
Linear Stroke Length: Target: ≥110.0mm. Measured Vernier Caliper Travel: 118.5mm (verified adequate reach to clear chute).
Dispensing Cycles: 10 operational cycles: 8 successful drops, 2 packaging jams (80% reliability against >90% target).
Mechanical Cause: Flexible packaging crinkles vertically under forward thrust, catching on 3D-print layer lines at the 2.5mm exit threshold.
Action Plan: Widen exit threshold to 3.2mm in CAD; post-process cartridge bed with 400-grit wet sanding and PTFE dry lubricant.

Engineering Documentation & Reports

Download the complete VCE Systems Engineering SAT folio, technical evaluation document, and chronological engineering logbook.

Part A Portfolio

Criteria 1 to 3

Problem definition, user need analysis, design constraints, alternative solution evaluation, and Gantt production schedule.

Format: PDF Document Size: 14.2 MB
Download Part A (PDF)

Part B Technical Report

Criteria 4 to 8

Production logs, mechatronic subsystem integration, diagnostic test procedures, data calculations, and Systems Engineering Process (SEP) review.

Format: PDF Document Size: 24.5 MB
Download Part B (PDF)

Engineering Logbook

Chronological Log

Full production diary recording machine tool usage, risk assessment checks, circuit iterations, and testing milestones.

Format: CSV Dataset Size: 8.8 KB
Download Logbook (CSV)