feat(n7web): add pet NFC workflow and glass UI
This commit is contained in:
@@ -48,6 +48,7 @@ from cognitive_agent.application.mode_profile_intent import ( # noqa: E402
|
||||
normalize_mode_profile_name,
|
||||
parse_mode_profile_update,
|
||||
)
|
||||
from cognitive_agent.application.pet_cards import PetCardService # noqa: E402
|
||||
from cognitive_agent.application.seat_service import ( # noqa: E402
|
||||
LOCAL_CONTROL_BUDGET_MS,
|
||||
SeatDemoService,
|
||||
@@ -217,6 +218,10 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
|
||||
gesture = GestureCapabilityBridge(REPOSITORY_ROOT)
|
||||
extension = SelfExtensionService()
|
||||
nfc = NfcReaderService()
|
||||
pets = PetCardService(
|
||||
nfc_provider=nfc.snapshot,
|
||||
publisher=service.publish_pet_message,
|
||||
)
|
||||
calibration = CalibrationService(
|
||||
REPOSITORY_ROOT / "artifacts" / "tmp" / "self-calibration",
|
||||
repository_root=REPOSITORY_ROOT,
|
||||
@@ -393,7 +398,16 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
|
||||
self._json(self._extension_health())
|
||||
return
|
||||
if path == "/api/extension/nfc/status":
|
||||
self._json({"success": True, **self.nfc.snapshot()})
|
||||
self._json(
|
||||
{
|
||||
"success": True,
|
||||
**self.nfc.snapshot(),
|
||||
"pet_cards": self.pets.snapshot(),
|
||||
}
|
||||
)
|
||||
return
|
||||
if path == "/api/extension/pets":
|
||||
self._json({"success": True, **self.pets.snapshot()})
|
||||
return
|
||||
if path == "/api/extension/gscu/slots":
|
||||
try:
|
||||
@@ -2488,6 +2502,41 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
|
||||
return
|
||||
if not self._require_same_origin() or not self._require_json_content_type():
|
||||
return
|
||||
pet_match = re.fullmatch(
|
||||
r"/api/extension/pets/(?P<pet_id>pet-[0-9a-f]{32})",
|
||||
path,
|
||||
)
|
||||
if pet_match is not None:
|
||||
try:
|
||||
payload = self._read_json_body(max_bytes=4096)
|
||||
unexpected = set(payload) - {"pet_name", "action"}
|
||||
if unexpected:
|
||||
if "nfc_id" in unexpected:
|
||||
raise ValueError(
|
||||
"nfc_id 只能通过当前已识别的 NFC 卡片完成绑定。"
|
||||
)
|
||||
raise ValueError("宠物卡片包含不支持的字段。")
|
||||
card = self.pets.update_card(
|
||||
pet_match.group("pet_id"),
|
||||
pet_name=payload.get("pet_name"),
|
||||
action=payload.get("action"),
|
||||
)
|
||||
self._json(
|
||||
{"success": True, "card": card, **self.pets.snapshot()},
|
||||
HTTPStatus.OK,
|
||||
)
|
||||
except KeyError:
|
||||
self._json(
|
||||
{"success": False, "error": "not_found", "message": "未找到宠物卡片。"},
|
||||
HTTPStatus.NOT_FOUND,
|
||||
)
|
||||
except (ValueError, json.JSONDecodeError) as exc:
|
||||
self._json(
|
||||
{"success": False, "error": "invalid_request", "message": str(exc)},
|
||||
HTTPStatus.BAD_REQUEST,
|
||||
)
|
||||
return
|
||||
|
||||
match = re.fullmatch(r"/api/extension/gscu/slots/(?P<slot>\d+)", path)
|
||||
if match is None:
|
||||
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
|
||||
@@ -2524,6 +2573,28 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
|
||||
HTTPStatus.BAD_REQUEST,
|
||||
)
|
||||
return
|
||||
pet_match = re.fullmatch(
|
||||
r"/api/extension/pets/(?P<pet_id>pet-[0-9a-f]{32})",
|
||||
path,
|
||||
)
|
||||
if pet_match is not None:
|
||||
try:
|
||||
deleted = self.pets.delete_card(pet_match.group("pet_id"))
|
||||
self._json(
|
||||
{"success": True, "deleted": deleted, **self.pets.snapshot()},
|
||||
HTTPStatus.OK,
|
||||
)
|
||||
except KeyError:
|
||||
self._json(
|
||||
{"success": False, "error": "not_found", "message": "未找到宠物卡片。"},
|
||||
HTTPStatus.NOT_FOUND,
|
||||
)
|
||||
except ValueError as exc:
|
||||
self._json(
|
||||
{"success": False, "error": "invalid_request", "message": str(exc)},
|
||||
HTTPStatus.BAD_REQUEST,
|
||||
)
|
||||
return
|
||||
prefix = "/api/mode-profiles/"
|
||||
if not path.startswith(prefix):
|
||||
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
|
||||
@@ -2833,6 +2904,47 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
|
||||
}
|
||||
try:
|
||||
payload = self._read_json_body(max_bytes=256 * 1024)
|
||||
if path == "/api/extension/pets":
|
||||
if "nfc_id" in payload:
|
||||
raise ValueError(
|
||||
"nfc_id 只能通过当前已识别的 NFC 卡片完成绑定。"
|
||||
)
|
||||
card = self.pets.create_card(
|
||||
payload.get("pet_name"),
|
||||
payload.get("action"),
|
||||
)
|
||||
self._json(
|
||||
{"success": True, "card": card, **self.pets.snapshot()},
|
||||
HTTPStatus.CREATED,
|
||||
)
|
||||
return
|
||||
pet_binding_match = re.fullmatch(
|
||||
r"/api/extension/pets/(?P<pet_id>pet-[0-9a-f]{32})/"
|
||||
r"(?P<action>bind-current-nfc|unbind)",
|
||||
path,
|
||||
)
|
||||
if pet_binding_match is not None:
|
||||
pet_id = pet_binding_match.group("pet_id")
|
||||
try:
|
||||
if pet_binding_match.group("action") == "bind-current-nfc":
|
||||
card = self.pets.bind_current_nfc(pet_id)
|
||||
else:
|
||||
card = self.pets.unbind_card(pet_id)
|
||||
except KeyError:
|
||||
self._json(
|
||||
{
|
||||
"success": False,
|
||||
"error": "not_found",
|
||||
"message": "未找到宠物卡片。",
|
||||
},
|
||||
HTTPStatus.NOT_FOUND,
|
||||
)
|
||||
return
|
||||
self._json(
|
||||
{"success": True, "card": card, **self.pets.snapshot()},
|
||||
HTTPStatus.OK,
|
||||
)
|
||||
return
|
||||
if path in {
|
||||
"/api/extension/gscu/erase",
|
||||
"/api/extension/gscu/erase-all",
|
||||
@@ -3205,7 +3317,11 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
|
||||
)
|
||||
|
||||
def _extension_health(self) -> dict[str, object]:
|
||||
return {**self.extension.health(), "nfc": self.nfc.snapshot()}
|
||||
return {
|
||||
**self.extension.health(),
|
||||
"nfc": self.nfc.snapshot(),
|
||||
"pet_cards": self.pets.snapshot(),
|
||||
}
|
||||
|
||||
def _handle_hardware_access_post(self, path: str) -> None:
|
||||
local_path = path.startswith("/api/local/")
|
||||
@@ -3848,6 +3964,7 @@ def main() -> None:
|
||||
SeatDemoHandler.service.start()
|
||||
SeatDemoHandler.extension.start()
|
||||
SeatDemoHandler.nfc.start()
|
||||
SeatDemoHandler.pets.start()
|
||||
SeatDemoHandler.calibration.start()
|
||||
SeatDemoHandler.repair.start()
|
||||
if args.host in {"0.0.0.0", "::"}:
|
||||
@@ -3900,9 +4017,10 @@ def main() -> None:
|
||||
server.server_close()
|
||||
SeatDemoHandler.speech.close()
|
||||
SeatDemoHandler.assistant.close()
|
||||
SeatDemoHandler.pets.close()
|
||||
SeatDemoHandler.nfc.close()
|
||||
SeatDemoHandler.service.close()
|
||||
SeatDemoHandler.extension.close()
|
||||
SeatDemoHandler.nfc.close()
|
||||
SeatDemoHandler.calibration.close()
|
||||
SeatDemoHandler.repair.close()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user