Add Olimex ESP32-C6-EVB + PN532 NFC driver and web UI
- New driver: app/drivers/olimex_esp32_c6_evb_pn532/ - Full relay/input control (inherits base behaviour) - get_nfc_status(), get_nfc_config(), set_nfc_config() methods - manifest.json with NFC hardware metadata (UEXT1 pins, card standard) - NFC management routes (boards.py): - GET /boards/<id>/nfc — management page - GET /boards/<id>/nfc/status_json — live JSON (polled every 3 s) - POST /boards/<id>/nfc/config — save auth UID / relay / timeout - POST /boards/<id>/nfc/enroll — enrol last-seen card with one click - New template: templates/boards/nfc.html - Live reader status (PN532 ready, access state, last UID) - Quick Enroll: present card → Refresh → Enrol in one click - Manual Settings: type/paste UID, pick relay, set absence timeout - detail.html: NFC Access Control button shown for pn532 board type
This commit is contained in:
@@ -9,6 +9,8 @@ from app.services.board_service import poll_board, register_webhook, set_relay
|
||||
from app.drivers.registry import registry
|
||||
from flask import current_app
|
||||
|
||||
_NFC_DRIVER_ID = "olimex_esp32_c6_evb_pn532"
|
||||
|
||||
boards_bp = Blueprint("boards", __name__)
|
||||
|
||||
|
||||
@@ -276,3 +278,101 @@ def edit_entities(board_id: int):
|
||||
@login_required
|
||||
def edit_labels(board_id: int):
|
||||
return redirect(url_for("boards.edit_entities", board_id=board_id))
|
||||
|
||||
|
||||
# ── NFC management (PN532 boards only) ────────────────────────────────────────
|
||||
|
||||
@boards_bp.route("/<int:board_id>/nfc")
|
||||
@login_required
|
||||
def nfc_management(board_id: int):
|
||||
board = db.get_or_404(Board, board_id)
|
||||
if board.board_type != _NFC_DRIVER_ID:
|
||||
abort(404)
|
||||
driver = registry.get(_NFC_DRIVER_ID)
|
||||
nfc_status = driver.get_nfc_status(board) if driver else None
|
||||
return render_template(
|
||||
"boards/nfc.html",
|
||||
board=board,
|
||||
nfc=nfc_status or {},
|
||||
)
|
||||
|
||||
|
||||
@boards_bp.route("/<int:board_id>/nfc/status_json")
|
||||
@login_required
|
||||
def nfc_status_json(board_id: int):
|
||||
board = db.get_or_404(Board, board_id)
|
||||
if board.board_type != _NFC_DRIVER_ID:
|
||||
abort(404)
|
||||
driver = registry.get(_NFC_DRIVER_ID)
|
||||
data = driver.get_nfc_status(board) if driver else None
|
||||
if data is None:
|
||||
return jsonify({"error": "Board unreachable"}), 502
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@boards_bp.route("/<int:board_id>/nfc/config", methods=["POST"])
|
||||
@login_required
|
||||
def nfc_config_save(board_id: int):
|
||||
if not current_user.is_admin():
|
||||
abort(403)
|
||||
board = db.get_or_404(Board, board_id)
|
||||
if board.board_type != _NFC_DRIVER_ID:
|
||||
abort(404)
|
||||
driver = registry.get(_NFC_DRIVER_ID)
|
||||
if not driver:
|
||||
flash("NFC driver not available.", "danger")
|
||||
return redirect(url_for("boards.nfc_management", board_id=board_id))
|
||||
|
||||
auth_uid = request.form.get("auth_uid", "").strip().upper()
|
||||
relay_num = int(request.form.get("relay_num", 1))
|
||||
pulse_ms = int(request.form.get("pulse_ms", 3000))
|
||||
|
||||
if relay_num < 1 or relay_num > 4:
|
||||
flash("Relay number must be 1–4.", "danger")
|
||||
return redirect(url_for("boards.nfc_management", board_id=board_id))
|
||||
if pulse_ms < 100 or pulse_ms > 60000:
|
||||
flash("Absence timeout must be between 100 and 60 000 ms.", "danger")
|
||||
return redirect(url_for("boards.nfc_management", board_id=board_id))
|
||||
|
||||
ok = driver.set_nfc_config(board, auth_uid=auth_uid, relay_num=relay_num, pulse_ms=pulse_ms)
|
||||
if ok:
|
||||
uid_display = auth_uid if auth_uid else "(any card)"
|
||||
flash(f"NFC config saved — authorized UID: {uid_display}, relay: {relay_num}, timeout: {pulse_ms} ms", "success")
|
||||
else:
|
||||
flash("Failed to push NFC config — board unreachable.", "danger")
|
||||
return redirect(url_for("boards.nfc_management", board_id=board_id))
|
||||
|
||||
|
||||
@boards_bp.route("/<int:board_id>/nfc/enroll", methods=["POST"])
|
||||
@login_required
|
||||
def nfc_enroll(board_id: int):
|
||||
"""Read the last-seen UID from the board and immediately authorize it."""
|
||||
if not current_user.is_admin():
|
||||
abort(403)
|
||||
board = db.get_or_404(Board, board_id)
|
||||
if board.board_type != _NFC_DRIVER_ID:
|
||||
abort(404)
|
||||
driver = registry.get(_NFC_DRIVER_ID)
|
||||
if not driver:
|
||||
flash("NFC driver not available.", "danger")
|
||||
return redirect(url_for("boards.nfc_management", board_id=board_id))
|
||||
|
||||
status = driver.get_nfc_status(board)
|
||||
if not status:
|
||||
flash("Board unreachable — could not read card UID.", "danger")
|
||||
return redirect(url_for("boards.nfc_management", board_id=board_id))
|
||||
|
||||
uid = (status.get("last_uid") or "").strip().upper()
|
||||
if not uid:
|
||||
flash("No card has been presented to the reader yet.", "warning")
|
||||
return redirect(url_for("boards.nfc_management", board_id=board_id))
|
||||
|
||||
relay_num = int(request.form.get("relay_num", status.get("relay_num", 1)))
|
||||
pulse_ms = int(request.form.get("pulse_ms", status.get("pulse_ms", 3000)))
|
||||
|
||||
ok = driver.set_nfc_config(board, auth_uid=uid, relay_num=relay_num, pulse_ms=pulse_ms)
|
||||
if ok:
|
||||
flash(f"Card enrolled — UID: {uid}, relay: {relay_num}, timeout: {pulse_ms} ms", "success")
|
||||
else:
|
||||
flash("Card read OK but failed to push config to board.", "danger")
|
||||
return redirect(url_for("boards.nfc_management", board_id=board_id))
|
||||
|
||||
Reference in New Issue
Block a user