Files
obsidian-vault/WORK/Projects/Employee Punch Tracking.md
2026-02-24 18:27:57 -06:00

14 KiB

Project: Employee Punch Tracking

Created: 2026-02-24 Source: #work-assistant Status: Beta Production Phase


Overview

Track employee timeclock punches via RFID cards and capture images via IP camera for verification.

Purpose

  • Track employee punch in/out via RFID card
  • Capture image via IP camera when card is scanned
  • Match captured image with employee database record
  • Integrate with external timeclock software

How It Works

  1. Employee scans RFID card at timeclock
  2. System triggers IP camera capture
  3. Image is matched against MSSQL database record
  4. Time punch is logged in external vendor time software

Architecture

  • IP camera for image capture
  • RFID card reader
  • MSSQL database for employee photos/records
  • External timeclock software (vendor provided)
  • Custom software runs "on top" of vendor system

Current Status

🚀 Beta Production

  • Software functioning well in production
  • Active monitoring ongoing

Known Issues

Latency Problem: Delay between RFID scan and image capture

  • Currently researching alternative solutions
  • Goal: Reduce/remove latency gap

Tasks

  • Research alternative latency solutions
  • Implement latency improvement
  • Continue production monitoring

Notes

(Add camera specs, RFID reader model, MSSQL schema, vendor software details, etc.)


Extracted from Projects to be organized


Research Findings

Research Date: 2026-02-24
Focus: RFID + IP Camera Latency Solutions


Latency Problem Analysis

Current Bottleneck Identification

The primary latency in RFID-to-camera capture systems comes from multiple sequential steps:

  1. RFID Read Latency: 2-5ms (physical card detection + data transfer)
  2. USB/Serial Communication: 1-10ms depending on interface
  3. HTTP Request to Camera: 20-100ms (network round-trip)
  4. Camera Shutter/IPE Frame Capture: 50-200ms (depends on exposure/lighting)
  5. Image Transfer: 50-500ms (depends on resolution)

Total Typical Latency: 123-815ms

Critical Insight from Research

The biggest latency contributor is NOT the RFID reader (which is typically <10ms), but rather:

  • Camera exposure/autofocus delay when triggering from idle
  • Network round-trip for HTTP snapshot requests
  • Image compression and transfer time

RFID Reader Options

  • Cost: $8-15 (Amazon: Dual-frequency 125KHz/13.56MHz readers)
  • Protocols: ISO14443-A/B, EM4100
  • Latency: <5ms from card touch to character output
  • Python Integration: No special drivers - reads as keyboard input
  • Models: Generic USB HID readers (Navfmru, HiLetgo, generic dual-frequency)

Option 2: Serial/UART RFID Reader (For Advanced Control)

  • Cost: $12-25
  • Interface: USB-to-Serial (appears as COM port)
  • Protocols: SPI/I2C/UART
  • Latency: <2ms (interrupt-driven serial)
  • Python Library: pyserial
  • Best for: When you need custom parsing or have multiple readers

Option 3: Ethernet-Connected RFID Reader

  • Cost: $50-150
  • Latency: Depends on network (1-5ms local)
  • Best for: Industrial setups with existing PoE infrastructure

Recommendation: Start with USB HID Keyboard Emulation readers - simplest integration, lowest cost, adequate latency.

IP Camera Recommendations

Low-Latency Trigger-Optimized Cameras

Hikvision / Dahua (Best API Support):

  • Direct HTTP snapshot URLs: http://camera-ip/Streaming/channels/1/picture
  • ONVIF snapshot: http://camera-ip/onvif/snapshot
  • Typical latency: 50-150ms for snapshot
  • Key Feature: Can pre-authenticate and keep session alive

Axis (MQTT Support):

  • Native MQTT event publishing
  • Can subscribe to ONVIF events
  • More expensive but better integration options

INSTAR (Built-in MQTT):

  • Native MQTT for motion triggers
  • Can trigger snapshot via MQTT message

Latency-Reducing Camera Configuration:

  1. Fixed Focus - Disable autofocus to eliminate 100-300ms delay
  2. Fixed Exposure - Set manual exposure to eliminate AE calculation
  3. Low Resolution Stream - Use 720p or lower for snapshot (faster transfer)
  4. Motion JPEG - Prefer MJPEG over H.264 for snapshot endpoints

Low-Latency Architecture Options

Architecture A: HTTP Snapshot on RFID Event (Simple)

RFID Scan → Python HTTP Request → Camera → Save Image → Match
Latency: 100-300ms

Pros: Simple, no additional infrastructure
Cons: Every scan has network round-trip

Architecture B: Always-Recording with Frame Grab (Best for Speed)

Camera always streaming (RTSP) 
RFID Scan → Python grabs current frame from buffer → Match
Latency: 10-50ms

Pros: Fastest capture, no network request at scan time
Cons: Higher CPU/network usage, requires OpenCV/ffmpeg

Architecture C: MQTT/Event-Driven (Scalable)

RFID Scan → Publish MQTT message → Camera subscribes → Captures
Latency: 50-150ms

Pros: Decoupled, multiple cameras possible
Cons: Requires MQTT broker

Architecture D: ONVIF Events with Pre-trigger Buffer (Professional)

Camera constantly buffers 2-3 seconds
RFID triggers ONVIF event → Camera saves pre/post buffer
Latency: 0ms (image from before trigger!)

Pros: Can capture image BEFORE scan
Cons: Complex setup, requires ONVIF Profile S/G support

Primary: Always-streaming RTSP with OpenCV frame buffer
Fallback: HTTP snapshot if buffer unavailable

Python Libraries and Code Samples

Library Requirements

pip install pyserial opencv-python requests onvif-zeep paho-mqtt

Code Sample 1: USB HID RFID Reader (Keyboard Emulation)

import keyboard  # pip install keyboard
from datetime import datetime
import requests

def on_rfid_scan(event):
    """Capture RFID UID from keyboard emulation"""
    uid = event.name
    timestamp = datetime.now()
    
    # Trigger camera capture immediately
    capture_image(uid, timestamp)
    
# Hook into keyboard events
keyboard.on_press(on_rfid_scan)
keyboard.wait()

Code Sample 2: Serial RFID Reader

import serial
import threading

def read_rfid_serial(port='COM3', baudrate=9600):
    ser = serial.Serial(port, baudrate, timeout=0.1)
    buffer = ""
    
    while True:
        if ser.in_waiting:
            data = ser.read(ser.in_waiting).decode('utf-8')
            buffer += data
            
            # Check for complete UID (usually ends with CR/LF)
            if '\r' in buffer or '\n' in buffer:
                uid = buffer.strip()
                buffer = ""
                capture_image(uid)

# Run in separate thread for non-blocking
threading.Thread(target=read_rfid_serial, daemon=True).start()

Code Sample 3: HTTP Snapshot (Synchronous)

import requests
from datetime import datetime

CAMERA_URL = "http://192.168.1.100/Streaming/channels/1/picture"
CAMERA_AUTH = ("admin", "password")

def capture_http_snapshot(uid):
    start = datetime.now()
    response = requests.get(CAMERA_URL, auth=CAMERA_AUTH, timeout=2)
    
    if response.status_code == 200:
        filename = f"captures/{uid}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jpg"
        with open(filename, 'wb') as f:
            f.write(response.content)
        
        latency = (datetime.now() - start).total_seconds() * 1000
        print(f"Captured in {latency:.1f}ms")
        return filename

Code Sample 4: OpenCV Always-Streaming (Fastest)

import cv2
import threading
import queue

class CameraBuffer:
    def __init__(self, rtsp_url):
        self.cap = cv2.VideoCapture(rtsp_url)
        self.frame = None
        self.lock = threading.Lock()
        
        # Start capture thread
        threading.Thread(target=self._update, daemon=True).start()
    
    def _update(self):
        while True:
            ret, frame = self.cap.read()
            if ret:
                with self.lock:
                    self.frame = frame
    
    def get_frame(self):
        with self.lock:
            return self.frame.copy() if self.frame is not None else None

# Usage
camera = CameraBuffer("rtsp://admin:pass@192.168.1.100:554/Streaming/Channels/1")

def capture_image(uid):
    frame = camera.get_frame()
    if frame is not None:
        filename = f"captures/{uid}_{datetime.now():%Y%m%d_%H%M%S}.jpg"
        cv2.imwrite(filename, frame)
        return filename

Code Sample 5: ONVIF Event Subscription

from onvif import ONVIFCamera

def setup_onvif_events(camera_ip, user, password):
    camera = ONVIFCamera(camera_ip, 80, user, password)
    
    # Create events service
    events = camera.create_events_service()
    
    # Subscribe to events
    subscription = events.CreatePullPointSubscription()
    
    return camera, subscription

MSSQL Optimization for Fast Employee Photo Matching

Schema Recommendations

-- Store image hashes for fast comparison (not full images in DB)
CREATE TABLE EmployeePhotos (
    EmployeeID INT PRIMARY KEY,
    PhotoHash VARBINARY(64) INDEXED,  -- SHA-256 hash
    PhotoPath NVARCHAR(500),          -- Path to actual image
    FaceEncoding VARBINARY(MAX),      -- If using face recognition
    LastUpdated DATETIME2
);

-- Index for hash lookups
CREATE NONCLUSTERED INDEX IX_PhotoHash ON EmployeePhotos(PhotoHash);

Optimization Strategies

  1. Don't Store Images in MSSQL

    • Store file paths only
    • Store perceptual hashes for comparison
    • Keep images on fast local SSD
  2. Pre-compute Face Embeddings

    import face_recognition
    
    # One-time: Store face encoding
    image = face_recognition.load_image_file("employee.jpg")
    encoding = face_recognition.face_encodings(image)[0]
    # Save encoding to DB as binary blob
    
  3. Use Memory-Optimized Tables (SQL Server)

    CREATE TABLE dbo.EmployeePhotos (
        EmployeeID INT PRIMARY KEY NONCLUSTERED,
        PhotoHash VARBINARY(64)
    ) WITH (MEMORY_OPTIMIZED=ON, DURABILITY=SCHEMA_AND_DATA);
    
  4. Query Pattern for Matching

    -- Fast hash lookup first
    SELECT EmployeeID FROM EmployeePhotos 
    WHERE PhotoHash = @capturedHash;
    
    -- If using face recognition, compare embeddings
    -- in Python (not SQL) for algorithm flexibility
    

Implementation Recommendations

Phase 1: Quick Win (HTTP Snapshot)

  1. Purchase USB HID RFID reader ($10-15)
  2. Use keyboard library to detect scans
  3. Trigger Hikvision/Dahua HTTP snapshot
  4. Measure actual latency

Phase 2: Optimization (RTSP Buffer)

  1. If latency >100ms is problematic, implement OpenCV RTSP buffer
  2. Capture frame from buffer instead of HTTP request
  3. Measure improvement

Phase 3: Advanced (Face Recognition)

  1. Add face_recognition library
  2. Pre-compute employee face embeddings
  3. Match captured image against embeddings
  4. Log match confidence with each punch

Latency Targets by Phase:

Phase Expected Latency Implementation Time
Phase 1 100-300ms 1-2 days
Phase 2 10-50ms 3-5 days
Phase 3 10-50ms + match 1-2 weeks

Real-World Latency Benchmarks (from Research)

Component Typical Optimized Notes
RFID Read 2-5ms 1-2ms HID faster than serial
USB Transfer 1-10ms 1-2ms Interrupt vs polling
HTTP Request 20-100ms 10-20ms Keep-alive connections
Camera Shutter 50-200ms 0ms Fixed exposure eliminates
Image Transfer 50-500ms 5-20ms Lower resolution, MJPEG
Total HTTP 123-815ms 17-46ms With optimizations
RTSP Buffer 10-50ms 5-15ms Fastest option

RFID & Communication Latency

Python RFID Integration

Camera APIs & ONVIF

MQTT Camera Control

RFID Hardware Options


Summary

Key Takeaway: The fastest solution for RFID-to-camera latency is combining:

  1. USB HID RFID reader for <5ms card detection
  2. OpenCV RTSP stream buffer for <20ms image capture
  3. Skip HTTP requests entirely - grab from buffer

This yields 15-25ms total latency from scan to image capture - well within acceptable limits for timeclock verification.

Alternative Simple Path: If RTSP complexity is undesirable, using HTTP snapshots with fixed-exposure camera settings can achieve 50-100ms latency with minimal implementation effort.


Research compiled by AI assistant. Hardware links are examples only - verify compatibility before purchase.