fix(modes): only activate peripherals and show individual seat actions

This commit is contained in:
Miayui
2026-09-22 13:34:29 +08:00
parent 6b9ee0f349
commit 2b365b3922
21 changed files with 176 additions and 215 deletions
+42 -11
View File
@@ -10,7 +10,9 @@ from cognitive_agent.application.romantic_lighting import romantic_light_actions
from cognitive_agent.application.scene_composer import scene_required_peripheral_refs
from cognitive_agent.application.mode_profile import ModeProfileRegistry
from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService
from cognitive_agent.application.self_extension import GatewayError, SelfExtensionService, SimulationGateway
from cognitive_agent.application.self_extension import (
ExtensionIntegrationError, GatewayError, SelfExtensionService, SimulationGateway,
)
from cognitive_agent.server import SeatDemoHandler
@@ -33,22 +35,22 @@ def catalog():
]
def test_romantic_lights_all_use_on_then_red_and_preserve_on_only_candles():
def test_romantic_lights_only_use_power_on():
entries = catalog()
entries += [
light("light5", "落地灯", "红色", 61), # No known power-on command.
light("light6", "投影灯", "打开", 71),
light("light6", "投影灯", "蓝色", 72), # Never substitute blue for red.
light("light6", "投影灯", "蓝色", 72), # Color capability never becomes a mode action.
light("light7", "灯笼", "打开", 81, enabled=False),
light("light7", "灯笼", "红色", 82, enabled=False),
]
before = deepcopy(entries)
assert [item["slot"] for item in romantic_light_actions(entries)] == [2, 3, 22, 23, 41, 51, 52]
assert [item["slot"] for item in romantic_light_actions(entries)] == [2, 22, 41, 51, 71]
assert entries == before
@pytest.mark.parametrize("name", ["浪漫模式", "浪漫影院模式", "约会模式", "Romantic cinema"])
def test_creation_replaces_old_colors_and_orders_all_required_lights(name):
def test_creation_removes_colors_and_only_opens_required_lights(name):
actions = [{
"ref": f"ir:{item['slot']}", "device_key": item["device_id"],
"device_label": item["device_name"], "action_label": item["action"],
@@ -56,7 +58,7 @@ def test_creation_replaces_old_colors_and_orders_all_required_lights(name):
} for item in catalog()]
context = {"peripheral_actions": actions}
required = scene_required_peripheral_refs(name, context)
assert required == ["ir:2", "ir:3", "ir:22", "ir:23", "ir:41", "ir:51", "ir:52"]
assert required == ["ir:2", "ir:22", "ir:41", "ir:51"]
context["scene_semantics"] = {"mode_name": name, "peripheral_policy": {"required_refs": required}}
plan = {"requests": [{
"domain": "self_extension", "capability": "infrared.emit",
@@ -73,10 +75,39 @@ def test_same_named_lights_remain_separate_devices():
"device_label": "灯笼", "action_label": label,
} for device in ["one", "two"] for action, label in [("on", "打开"), ("red", "红色")]]
assert [item["ref"] for item in romantic_light_actions(actions)] == [
"ir:one-on", "ir:one-red", "ir:two-on", "ir:two-red",
"ir:one-on", "ir:two-on",
]
def test_mode_creation_deduplicates_colors_into_authorized_activation():
capabilities = catalog()
handler = object.__new__(SeatDemoHandler)
handler.extension = SimpleNamespace(list_peripheral_capabilities=lambda: capabilities)
entries = [{"capability": capabilities[index]} for index in [3, 2, 1, 5, 4, 6]]
selected = handler._mode_creation_activation_entries(entries)
assert [entry["capability"]["slot"] for entry in selected] == [2, 22, 41]
def test_mode_creation_rejects_color_only_device_or_rebound_power_on():
red = light("light1", "灯笼", "红色", 3)
handler = object.__new__(SeatDemoHandler)
for available in [
[red],
[red, light("light1", "灯笼", "打开", 2, enabled=False)],
[red, light("light1", "灯笼", "打开", 2, card_uid="00000002")],
]:
handler.extension = SimpleNamespace(list_peripheral_capabilities=lambda: available)
with pytest.raises(ExtensionIntegrationError, match="开启指令"):
handler._mode_creation_activation_entries([{"capability": red}])
def test_scene_defaults_never_fall_back_to_color_only_refs():
context = {"peripheral_actions": [{
"ref": "ir:strip-red", "device_label": "灯带", "action_label": "红色",
}]}
assert scene_required_peripheral_refs("中秋模式", context) == []
def test_romantic_import_adds_ordered_lights_without_requiring_cinema_base(tmp_path: Path):
service = SeatDemoService(REPOSITORY_ROOT)
service.mode_profiles = ModeProfileRegistry(tmp_path / "presets", user_root=tmp_path / "modes")
@@ -92,9 +123,9 @@ def test_romantic_import_adds_ordered_lights_without_requiring_cinema_base(tmp_p
profile = service.get_mode_profile_card("romantic-date", "denza_n7")
actions = profile.command_flow["source_scene"]["actions"]
assert actions[0] == climate
assert [item["parameters"]["slot"] for item in actions[1:]] == [2, 3, 22, 23, 41, 51, 52]
assert [item["parameters"]["slot"] for item in actions[1:]] == [2, 22, 41, 51]
scene = profile.command_flow["source_scene"]
assert len(profile.operations) == 8
assert len(profile.operations) == 5
finally:
service.close()
@@ -113,7 +144,7 @@ def test_saved_romantic_mode_orders_real_dispatch_and_stops_on_power_failure(tmp
}})
entries = handler._saved_mode_infrared_entries(profile)
capabilities = [entry["capability"] for entry in entries]
assert [item["slot"] for item in capabilities] == [2, 3, 22, 23, 41, 51, 52]
assert [item["slot"] for item in capabilities] == [2, 22, 41, 51]
sent = []
def send(_command, parameters):
@@ -130,5 +161,5 @@ def test_saved_romantic_mode_orders_real_dispatch_and_stops_on_power_failure(tmp
{"kind": "peripheral", "capability_id": item["id"]} for item in capabilities
]}, capabilities, conversation_id="romantic-order-test", execution_mode="hardware",
)
assert sent == ([2] if fail_power else [2, 3, 22, 23, 41, 51, 52])
assert sent == ([2] if fail_power else [2, 22, 41, 51])
assert result["status"] == ("failed" if fail_power else "succeeded")