CasperSecurity
| Current Path : /opt/hikvison/ |
|
|
| Current File : //opt/hikvison/face_recognition.py |
import json
import datetime
import logging
from flask import Flask, request, jsonify
from datetime import datetime, timedelta
import paho.mqtt.client as mqtt
import requests # 👈 Added missing import
# ------------------------
# CONFIGURATION
# ------------------------
MQTT_BROKER = "192.168.0.165"
MQTT_PORT = 1883
MQTT_TOPIC = "hikvision/punch"
MQTT_USERNAME = "mqtt_user"
MQTT_PASSWORD = "P@ssw0rd@2026"
ATTENDANCE_API = "https://hrms.uiet.co.in/api/attendance"
# Store last API hit time for each employee
last_api_calls = {}
# 5-minute cooldown
API_COOLDOWN = timedelta(minutes=5)
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
app = Flask(__name__)
# ------------------------
# MQTT SETUP
# ------------------------
try:
mqtt_client = mqtt.Client()
mqtt_client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60)
mqtt_client.loop_start()
logger.info(f"Connected to MQTT Broker at {MQTT_BROKER}:{MQTT_PORT}")
except Exception as e:
logger.error(f"Failed to connect to MQTT Broker: {e}")
mqtt_client = None
def parse_hikvision_payload(req):
"""
Attempts to extract event data from various Hikvision payload formats.
"""
data = {}
# 1. Try JSON Body
if req.is_json:
logger.info("Payload is JSON")
data = req.get_json()
# 2. Try Multipart 'event_log' (Common in newer firmware)
elif 'event_log' in req.files:
logger.info("Payload contains 'event_log' file")
try:
file_content = req.files['event_log'].read()
data = json.loads(file_content)
except Exception as e:
logger.error(f"Failed to parse event_log file: {e}")
# 3. Try standard Form Data (sometimes sent as a string in a field)
elif req.form:
logger.info("Payload is Form Data")
data = req.form.to_dict()
if 'event_log' in data:
try:
data = json.loads(data['event_log'])
except:
pass
# 4. Fallback: Try raw data parse
else:
try:
raw = req.get_data()
if raw:
data = json.loads(raw)
except:
pass
return data
def call_attendance_api(employee_id, date_time):
try:
payload = {
"employee_id": employee_id,
"date_time": date_time.replace("T", " ")[:19]
}
logger.info(f"Sending Attendance: {payload}")
response = requests.post(
ATTENDANCE_API,
json=payload,
timeout=10
)
logger.info(
f"Attendance API Response: {response.status_code} | {response.text}"
)
return response.status_code == 200
except Exception as e:
logger.exception(f"Attendance API Error: {e}")
return False
def should_call_api(employee_id):
now = datetime.now()
if employee_id not in last_api_calls:
last_api_calls[employee_id] = now
return True
elapsed = now - last_api_calls[employee_id]
if elapsed >= API_COOLDOWN:
last_api_calls[employee_id] = now
return True
return False
def extract_punch_data(data):
try:
if isinstance(data, str):
data = json.loads(data)
if not isinstance(data, dict):
logger.error(f"Expected dict, got {type(data)}")
return None
# Get AccessControllerEvent block (can be a dict or json string)
outer = data.get("AccessControllerEvent")
if isinstance(outer, str):
outer = json.loads(outer)
if not isinstance(outer, dict):
logger.error(f"Outer is {type(outer)}")
return None
# Fix: Safely handle both single-nested and double-nested structures
if "AccessControllerEvent" in outer:
event = outer["AccessControllerEvent"]
if isinstance(event, str):
event = json.loads(event)
else:
event = outer
if not isinstance(event, dict):
logger.error(f"Event is {type(event)}")
return None
# Fix: Search for dateTime at the root level first, then fallback
dateTimeStr = data.get("dateTime") or outer.get("dateTime") or event.get("dateTime")
return {
"employee_id": event.get("employeeNoString"),
"name": event.get("name"),
"verify_mode": event.get("currentVerifyMode"),
"major_event": event.get("majorEventType"),
"sub_event": event.get("subEventType") or event.get("minorEventType"),
"card_reader": event.get("cardReaderNo"),
"user_type": event.get("userType"),
"mask": event.get("mask"),
"date_time": dateTimeStr,
"device_ip": data.get("ipAddress") or event.get("ipAddress"),
"serial_no": event.get("serialNo"),
"received_at": datetime.now().isoformat()
}
except Exception as e:
logger.exception(f"Error extracting punch data: {e}")
return None
# ------------------------
# ENDPOINT
# ------------------------
@app.route("/hikvision/event", methods=["POST"])
def hik_event():
try:
# 1. Parse Data
raw_data = parse_hikvision_payload(request)
logger.info(f"📥 Received Raw Data Structure: {json.dumps(raw_data, indent=2, default=str)}")
# 2. Extract Fields
punch = extract_punch_data(raw_data)
if punch:
logger.info(f"✅ Extracted Punch: {punch}")
employee_id = punch.get("employee_id")
if employee_id:
if should_call_api(employee_id):
logger.info(f"📡 Calling Attendance API for Employee {employee_id}")
success = call_attendance_api(employee_id, punch["date_time"])
if success:
logger.info(f"✅ Attendance API Success for Employee {employee_id}")
else:
logger.warning(f"❌ Attendance API Failed for Employee {employee_id}")
else:
logger.info(
f"⏳ Skipping Employee {employee_id}. "
"Attendance already sent within last 5 minutes."
)
else:
logger.warning("Employee ID not found. Skipping Attendance API.")
# 3. Publish to MQTT
if mqtt_client:
mqtt_client.publish(MQTT_TOPIC, json.dumps(punch), qos=1)
else:
logger.warning("MQTT not connected, skipping publish")
return "OK", 200
else:
logger.warning("⚠️ Could not extract valid punch data from payload.")
return "OK", 200
except Exception as e:
logger.error(f"⚠️ Error processing event: {e}")
return "OK", 200
# ------------------------
# HEALTH CHECK
# ------------------------
@app.route("/health")
def health():
return {"status": "running"}
if __name__ == "__main__":
logger.info("🚀 Robust Hikvision Listener started on port 8000")
app.run(host="0.0.0.0", port=8000)