feat: improve mode and peripheral orchestration

This commit is contained in:
Miayui
2026-09-21 00:13:47 +08:00
parent 89707a42e2
commit 448c8b31a0
39 changed files with 3068 additions and 277 deletions
+122 -4
View File
@@ -1406,6 +1406,15 @@ class SelfExtensionTests(unittest.TestCase):
def test_main_peripheral_sequence_executes_validated_actions_and_delays(self) -> None:
service = SelfExtensionService(SimulationGateway())
catalog = [
{
"id": "on",
"status": "learned",
"device_id": "third-party-light",
"device_name": "第三方氛围灯",
"card_uid": "1A53DD95",
"action": "打开灯光",
"slot": 1,
},
{
"id": "red",
"status": "learned",
@@ -1457,16 +1466,24 @@ class SelfExtensionTests(unittest.TestCase):
self.assertEqual(
result["orchestration_route"], "nfc_peripheral_sequence"
)
self.assertEqual(result["sequence"]["step_count"], 3)
self.assertEqual(result["sequence"]["action_count"], 2)
self.assertEqual(result["sequence"]["emit_count"], 2)
self.assertEqual(result["sequence"]["step_count"], 4)
self.assertEqual(result["sequence"]["action_count"], 3)
self.assertEqual(result["sequence"]["emit_count"], 3)
self.assertEqual(result["sequence"]["total_delay_ms"], 5000)
self.assertEqual(result["sequence"]["steady_blink_fallbacks"], 1)
self.assertEqual(
[item["status"] for item in result["operation_results"]],
["succeeded", "succeeded", "succeeded"],
["succeeded", "succeeded", "succeeded", "succeeded"],
)
self.assertEqual(
[
item["parameters"]["slot"]
for item in result["function_config"]["spec"]["operations"]
],
[1, 3, 4],
)
sleep.assert_any_call(5.0)
sleep.assert_any_call(0.8)
self.assertNotIn(0.5, [call.args[0] for call in sleep.call_args_list])
def test_failed_first_emit_retains_complete_compiled_infrared_plan(self) -> None:
@@ -1630,6 +1647,107 @@ class SelfExtensionTests(unittest.TestCase):
)
)
def test_color_only_request_automatically_executes_power_on_first(self) -> None:
with TemporaryDirectory() as directory:
service = SelfExtensionService(
SimulationGateway(),
catalog_db_path=Path(directory, "infrared.sqlite3"),
)
service.sync_peripheral_catalog(
[
{
"id": "flash-on",
"status": "learned",
"enabled": True,
"device_id": "flash-light",
"device_name": "闪烁灯",
"object_names": ["闪烁灯"],
"card_uid": "2CC8B103",
"action": "打开",
"action_names": ["打开", "开启", "启动"],
"slot": 1,
},
{
"id": "flash-blue",
"status": "learned",
"enabled": True,
"device_id": "flash-light",
"device_name": "闪烁灯",
"object_names": ["闪烁灯"],
"card_uid": "2CC8B103",
"action": "切换为蓝色",
"action_names": ["蓝色", "设置为蓝色"],
"slot": 3,
},
]
)
color = next(
item
for item in service.list_peripheral_capabilities()
if item["id"] == "flash-blue"
)
result = service.execute_peripheral_request(
"把闪烁灯设置为蓝色",
color,
conversation_id="conv-color-power-dependency",
execution_mode="dry_run",
)
self.assertEqual(result["status"], "succeeded")
self.assertEqual(result["sequence"]["action_count"], 2)
self.assertEqual(
[
item["parameters"]["slot"]
for item in result["function_config"]["spec"]["operations"]
],
[1, 3],
)
self.assertEqual(
[
item["signals"]["slot"]
for item in result["command_plan"]["spec"]["commands"]
],
[1, 3],
)
service.close()
def test_color_request_is_rejected_when_power_on_is_not_authorized(self) -> None:
with TemporaryDirectory() as directory:
service = SelfExtensionService(
SimulationGateway(),
catalog_db_path=Path(directory, "infrared.sqlite3"),
)
service.sync_peripheral_catalog(
[
{
"id": "strip-red",
"status": "learned",
"enabled": True,
"device_id": "light-strip",
"device_name": "灯带",
"object_names": ["灯带"],
"card_uid": "1A3AB103",
"action": "切换为红色",
"action_names": ["红色", "设置为红色"],
"slot": 24,
}
]
)
color = service.list_peripheral_capabilities()[0]
with self.assertRaisesRegex(
ExtensionIntegrationError,
"设置颜色前需要先打开",
):
service.execute_peripheral_request(
"把灯带设置为红色",
color,
conversation_id="conv-color-missing-power",
execution_mode="dry_run",
)
service.close()
def test_color_sequence_never_cycles_the_power_off_capability(self) -> None:
service = SelfExtensionService(SimulationGateway())
catalog = [