From e0eeeab6eb714ab22d0bc6697730b71d2381746e Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 18 May 2026 16:38:29 +0530 Subject: feat: per-board hardware locks (board-a/b/c.lock), fix port assignments - Lock files in physical-router-test-automation/locks/ - Each board has its own lock: board-a.lock, board-b.lock, board-c.lock - All hardware-touching targets (flash, monitor, erase-nvs, reset, serial-log, tests) require the corresponding board lock - Read-only targets (build, cvm-pubkey, lock-status) work without lock - Fixed port assignments: A=ACM1, B=ACM2, C=ACM0 - Router hardware lock kept separate (for OpenWRT operations) --- Makefile | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 141 insertions(+), 14 deletions(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index c84acdf..044ad6b 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,8 @@ export IDF_PATH ?= $(HOME)/esp/esp-idf PROJECT_DIR := $(shell pwd) BUILD_DIR := $(PROJECT_DIR)/build -PORT_A ?= /dev/ttyACM0 -PORT_B ?= /dev/ttyACM1 +PORT_A ?= /dev/ttyACM1 +PORT_B ?= /dev/ttyACM2 PORT ?= $(PORT_A) BAUD ?= 460800 TARGET ?= esp32s3 @@ -19,6 +19,62 @@ PYTHON ?= python3 TOLLGATE_IP ?= 10.192.45.1 +BOARD ?= b + +HARDWARE_LOCK_DIR := /home/c03rad0r/physical-router-test-automation/locks + +RED := \033[31m +GREEN := \033[32m +YELLOW := \033[33m +BOLD := \033[1m +RESET := \033[0m + +define require_lock_a + @if [ ! -f "$(HARDWARE_LOCK_DIR)/board-a.lock" ]; then \ + echo "$(RED)$(BOLD)Board A not locked — run 'make lock-a PHASE=\"description\"' first$(RESET)"; \ + echo "$(YELLOW)Another LLM session may be using Board A.$(RESET)"; \ + exit 1; \ + fi +endef + +define require_lock_b + @if [ ! -f "$(HARDWARE_LOCK_DIR)/board-b.lock" ]; then \ + echo "$(RED)$(BOLD)Board B not locked — run 'make lock-b PHASE=\"description\"' first$(RESET)"; \ + echo "$(YELLOW)Another LLM session may be using Board B.$(RESET)"; \ + exit 1; \ + fi +endef + +define _require_board_lock + @if [ ! -f "$(HARDWARE_LOCK_DIR)/board-$(BOARD).lock" ]; then \ + echo "$(RED)$(BOLD)Board $(BOARD) not locked — run 'make lock-$(BOARD) PHASE=\"description\"' first$(RESET)"; \ + echo "$(YELLOW)Another LLM session may be using Board $(BOARD).$(RESET)"; \ + exit 1; \ + fi +endef + +define _acquire_lock + @if [ -f "$(HARDWARE_LOCK_DIR)/$(1).lock" ]; then \ + echo "$(RED)$(BOLD)Cannot acquire lock — $(1) already locked:$(RESET)"; \ + echo ""; \ + cat $(HARDWARE_LOCK_DIR)/$(1).lock | sed 's/^/ /'; \ + echo ""; \ + echo "$(YELLOW)Use 'make force-unlock-$(1)' to override.$(RESET)"; \ + exit 1; \ + fi; \ + branch=$$(git branch --show-current 2>/dev/null || echo "unknown"); \ + worktree=$$(pwd); \ + echo "locked: true" > $(HARDWARE_LOCK_DIR)/$(1).lock; \ + echo "board: $(1)" >> $(HARDWARE_LOCK_DIR)/$(1).lock; \ + echo "branch: $$branch" >> $(HARDWARE_LOCK_DIR)/$(1).lock; \ + echo "worktree: $$worktree" >> $(HARDWARE_LOCK_DIR)/$(1).lock; \ + echo "session: $$USER@$$HOSTNAME" >> $(HARDWARE_LOCK_DIR)/$(1).lock; \ + echo "timestamp: $$(date -u '+%Y-%m-%dT%H:%M:%SZ')" >> $(HARDWARE_LOCK_DIR)/$(1).lock; \ + echo "phase: $(PHASE)" >> $(HARDWARE_LOCK_DIR)/$(1).lock; \ + echo "$(GREEN)$(BOLD)$(1) lock acquired$(RESET)"; \ + cat $(HARDWARE_LOCK_DIR)/$(1).lock +endef + .PHONY: help setup detect-ports detect-chip detect-all .PHONY: flash flash-a flash-b monitor monitor-a monitor-b .PHONY: test test-unit test-integration test-e2e test-all @@ -27,6 +83,7 @@ TOLLGATE_IP ?= 10.192.45.1 .PHONY: tokens wallet-setup wallet-info wallet-balance mint-token send-token .PHONY: clean erase-nvs reset serial-log bootstrap-config .PHONY: cvm-pubkey cvm-test-tool cvm-announce +.PHONY: lock-a lock-b unlock-a unlock-b force-unlock-a force-unlock-b lock-status help: @echo "TollGate ESP32 — Makefile" @@ -129,13 +186,18 @@ setup: flash: build @echo "=== Flashing to $(PORT) ===" - . $(IDF_PATH)/export.sh && idf.py -p $(PORT) -b $(BAUD) flash + @echo "$(RED)Error: use 'make flash-a' or 'make flash-b' (per-board lock required)$(RESET)" + @exit 1 -flash-a: PORT=$(PORT_A) -flash-a: flash +flash-a: build + $(call require_lock_a) + @echo "=== Flashing to $(PORT_A) (Board A) ===" + . $(IDF_PATH)/export.sh && idf.py -p $(PORT_A) -b $(BAUD) flash -flash-b: PORT=$(PORT_B) -flash-b: flash +flash-b: build + $(call require_lock_b) + @echo "=== Flashing to $(PORT_B) (Board B) ===" + . $(IDF_PATH)/export.sh && idf.py -p $(PORT_B) -b $(BAUD) flash build: @echo "=== Building $(TARGET) ===" @@ -143,14 +205,13 @@ build: idf.py set-target $(TARGET) 2>/dev/null; \ idf.py build -monitor: - . $(IDF_PATH)/export.sh && idf.py -p $(PORT) monitor +monitor-a: + $(call require_lock_a) + . $(IDF_PATH)/export.sh && idf.py -p $(PORT_A) monitor -monitor-a: PORT=$(PORT_A) -monitor-a: monitor - -monitor-b: PORT=$(PORT_B) -monitor-b: monitor +monitor-b: + $(call require_lock_b) + . $(IDF_PATH)/export.sh && idf.py -p $(PORT_B) monitor # ────────────────────────────────────────────── # Testing @@ -164,6 +225,7 @@ test-integration: test-api test-network test-reset-auth test-dns-firewall test-c @echo "=== Integration tests passed ===" test-e2e: + $(call _require_board_lock) @echo "=== Running Playwright E2E tests ===" cd tests/e2e && npx playwright test @@ -174,38 +236,47 @@ test: test-unit test-integration @echo "=== Tests passed ===" test-smoke: + $(call _require_board_lock) @echo "=== Running smoke test (30s) ===" TOLLGATE_IP=$(TOLLGATE_IP) $(NODE) tests/integration/smoke.mjs test-api: + $(call _require_board_lock) @echo "=== Running API tests ===" TOLLGATE_IP=$(TOLLGATE_IP) $(NODE) tests/integration/api.mjs test-network: + $(call _require_board_lock) @echo "=== Running network tests ===" TOLLGATE_IP=$(TOLLGATE_IP) $(NODE) tests/integration/network.mjs test-portal: + $(call _require_board_lock) @echo "=== Running Playwright portal tests ===" cd tests/e2e && npx playwright test captive-portal.spec.mjs test-payment: + $(call _require_board_lock) @echo "=== Running payment tests ===" TOLLGATE_IP=$(TOLLGATE_IP) $(NODE) tests/integration/phase2.mjs test-reset-auth: + $(call _require_board_lock) @echo "=== Running reset auth test ===" TOLLGATE_IP=$(TOLLGATE_IP) $(NODE) tests/integration/test-reset-auth.mjs test-session-expiry: + $(call _require_board_lock) @echo "=== Running session expiry test (65s wait, ~80s total) ===" TOLLGATE_IP=$(TOLLGATE_IP) $(NODE) tests/integration/test-session-expiry.mjs test-dns-firewall: + $(call _require_board_lock) @echo "=== Running DNS + firewall test ===" TOLLGATE_IP=$(TOLLGATE_IP) $(NODE) tests/integration/test-dns-firewall.mjs test-cvm: + $(call _require_board_lock) @echo "=== Running CVM integration test ===" TOLLGATE_IP=$(TOLLGATE_IP) $(NODE) tests/integration/test-cvm.mjs @@ -255,6 +326,7 @@ cvm-announce: curl -s http://$(TOLLGATE_IP):2121/ | head -1 || echo "Board not reachable" cvm-test-tool: + $(call _require_board_lock) @METHOD=$${METHOD:-get_config}; \ PARAMS=$${PARAMS:-{}}; \ echo "=== Calling $$METHOD via CVM ==="; \ @@ -275,16 +347,19 @@ clean: . $(IDF_PATH)/export.sh && idf.py fullclean erase-nvs: + $(call _require_board_lock) @echo "=== Erasing NVS on $(PORT) ===" . $(IDF_PATH)/export.sh && \ partition_offset=$$(idf.py partition-table 2>/dev/null | grep nvs | awk '{print $$2}'); \ python3 -m esptool --port $(PORT) erase_region $$partition_offset 0x6000 reset: + $(call _require_board_lock) @echo "=== Resetting device on $(PORT) ===" python3 -m esptool --port $(PORT) run 2>/dev/null || true serial-log: + $(call _require_board_lock) @echo "=== Capturing serial output from $(PORT) ===" python3 -c "import serial; s=serial.Serial('$(PORT)',115200,timeout=1); \ [print(s.readline().decode(errors='replace'),end='') for _ in iter(lambda: s.readline(), b'')]" @@ -293,3 +368,55 @@ bootstrap-config: @echo "=== Bootstrapping config.json ===" @echo '{"wifi_networks":[{"ssid":"$(WIFI_SSID)","password":"$(WIFI_PASSWORD)"}],"ap_ssid":"$(AP_SSID)","ap_password":"$(AP_PASSWORD)","mint_url":"$(MINT_URL)","lnurl_url":"$(LNURL_URL)","price_per_step":$(PRICE_PER_STEP),"step_size_ms":$(STEP_SIZE)}' > main/config.json @echo "Config written to main/config.json" + +# ────────────────────────────────────────────── +# Per-Board Hardware Locks +# ────────────────────────────────────────────── + +lock-a: ## Acquire Board A lock (set PHASE="description") + $(call _acquire_lock,board-a) + +lock-b: ## Acquire Board B lock (set PHASE="description") + $(call _acquire_lock,board-b) + +unlock-a: ## Release Board A lock + @if [ ! -f "$(HARDWARE_LOCK_DIR)/board-a.lock" ]; then \ + echo "$(YELLOW)Board A not locked.$(RESET)"; exit 0; \ + fi; \ + rm -f $(HARDWARE_LOCK_DIR)/board-a.lock; \ + echo "$(GREEN)Board A lock released.$(RESET)" + +unlock-b: ## Release Board B lock + @if [ ! -f "$(HARDWARE_LOCK_DIR)/board-b.lock" ]; then \ + echo "$(YELLOW)Board B not locked.$(RESET)"; exit 0; \ + fi; \ + rm -f $(HARDWARE_LOCK_DIR)/board-b.lock; \ + echo "$(GREEN)Board B lock released.$(RESET)" + +force-unlock-a: ## Force-release Board A lock + @if [ ! -f "$(HARDWARE_LOCK_DIR)/board-a.lock" ]; then \ + echo "$(YELLOW)Board A not locked.$(RESET)"; exit 0; \ + fi; \ + echo "$(RED)$(BOLD)WARNING: Force-releasing Board A!$(RESET)"; \ + cat $(HARDWARE_LOCK_DIR)/board-a.lock | sed 's/^/ /'; \ + rm -f $(HARDWARE_LOCK_DIR)/board-a.lock; \ + echo "$(GREEN)Board A force-released.$(RESET)" + +force-unlock-b: ## Force-release Board B lock + @if [ ! -f "$(HARDWARE_LOCK_DIR)/board-b.lock" ]; then \ + echo "$(YELLOW)Board B not locked.$(RESET)"; exit 0; \ + fi; \ + echo "$(RED)$(BOLD)WARNING: Force-releasing Board B!$(RESET)"; \ + cat $(HARDWARE_LOCK_DIR)/board-b.lock | sed 's/^/ /'; \ + rm -f $(HARDWARE_LOCK_DIR)/board-b.lock; \ + echo "$(GREEN)Board B force-released.$(RESET)" + +lock-status: ## Show all board lock statuses + @for board in a b; do \ + if [ -f "$(HARDWARE_LOCK_DIR)/board-$$board.lock" ]; then \ + echo "$(YELLOW)Board $$board: LOCKED$(RESET)"; \ + cat $(HARDWARE_LOCK_DIR)/board-$$board.lock | sed 's/^/ /'; \ + else \ + echo "Board $$board: $(GREEN)available$(RESET)"; \ + fi; \ + done -- cgit v1.2.3