添加NFC绑定红外设备方式,融合车端设备和扩展设备统一控制
This commit is contained in:
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import sqlite3
|
||||
import threading
|
||||
import time
|
||||
import unittest
|
||||
@@ -22,6 +23,7 @@ from cognitive_agent.application.self_extension import (
|
||||
active_peripheral_plan_catalog,
|
||||
build_gateway_command,
|
||||
create_gateway,
|
||||
partition_active_peripheral_intent,
|
||||
resolve_active_peripheral_command,
|
||||
validate_slot,
|
||||
)
|
||||
@@ -78,6 +80,216 @@ class SelfExtensionTests(unittest.TestCase):
|
||||
with self.assertRaises(GatewayError):
|
||||
build_gateway_command("erase", {"confirm": "wrong"})
|
||||
|
||||
def test_learned_peripheral_whitelist_is_durable_and_normalized(self) -> None:
|
||||
with TemporaryDirectory() as directory:
|
||||
database_path = Path(directory, "infrared-slots.sqlite3")
|
||||
service = SelfExtensionService(
|
||||
SimulationGateway(), catalog_db_path=database_path
|
||||
)
|
||||
synced = service.sync_peripheral_catalog(
|
||||
[
|
||||
{
|
||||
"id": "toy-001-light-on",
|
||||
"status": "learned",
|
||||
"enabled": True,
|
||||
"device_id": "toy-001",
|
||||
"device_name": "玩具 001",
|
||||
"device_aliases": ["闪灯玩具", "闪灯玩具"],
|
||||
"card_uid": "2c:c8:b1:03",
|
||||
"action": "开启闪灯",
|
||||
"aliases": ["打开闪灯", "点亮"],
|
||||
"risk": "low",
|
||||
"slot": 1,
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(synced["count"], 1)
|
||||
with sqlite3.connect(database_path) as connection:
|
||||
connection.execute(
|
||||
"UPDATE infrared_slots SET device_id = '', "
|
||||
"object_names_json = '[]', action_names_json = '[]'"
|
||||
)
|
||||
reloaded = SelfExtensionService(
|
||||
SimulationGateway(), catalog_db_path=database_path
|
||||
).list_peripheral_capabilities()
|
||||
self.assertEqual(len(reloaded), 1)
|
||||
self.assertEqual(reloaded[0]["card_uid"], "2CC8B103")
|
||||
self.assertEqual(reloaded[0]["device_aliases"], ["闪灯玩具"])
|
||||
self.assertEqual(reloaded[0]["object_names"], ["玩具 001", "闪灯玩具"])
|
||||
self.assertEqual(
|
||||
reloaded[0]["action_names"], ["开启闪灯", "打开闪灯", "点亮"]
|
||||
)
|
||||
self.assertEqual(reloaded[0]["slot"], 1)
|
||||
slot = service.list_infrared_slots()["slots"][0]
|
||||
self.assertEqual(slot["device_id"], "toy-001")
|
||||
self.assertEqual(slot["object_names"], reloaded[0]["object_names"])
|
||||
self.assertEqual(slot["action_names"], reloaded[0]["action_names"])
|
||||
|
||||
def test_partition_claims_explicit_ir_clause_and_preserves_can_residual(self) -> None:
|
||||
catalog = [
|
||||
{
|
||||
"id": "toy-001-light-on",
|
||||
"status": "learned",
|
||||
"enabled": True,
|
||||
"device_id": "toy-001",
|
||||
"device_name": "玩具 001",
|
||||
"device_aliases": ["闪灯玩具", "氛围灯"],
|
||||
"card_uid": "2CC8B103",
|
||||
"action": "开启闪灯",
|
||||
"aliases": ["打开闪灯", "点亮"],
|
||||
"risk": "low",
|
||||
"slot": 1,
|
||||
}
|
||||
]
|
||||
present = {"card": {"action": "present", "uid": "2CC8B103"}}
|
||||
|
||||
partition = partition_active_peripheral_intent(
|
||||
"打开闪灯玩具,同时把空调调到24度,风量2档",
|
||||
catalog,
|
||||
present,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
[item["capability"]["id"] for item in partition["peripheral_actions"]],
|
||||
["toy-001-light-on"],
|
||||
)
|
||||
self.assertEqual(partition["residual_utterance"], "把空调调到24度,风量2档")
|
||||
self.assertEqual(partition["unresolved_peripheral_clauses"], [])
|
||||
|
||||
def test_partition_inherits_explicit_device_for_followup_ir_action(self) -> None:
|
||||
catalog = [
|
||||
{
|
||||
"id": capability_id,
|
||||
"status": "learned",
|
||||
"enabled": True,
|
||||
"device_id": "tony-light-001",
|
||||
"device_name": "闪灯玩具",
|
||||
"object_names": ["闪灯玩具", "闪烁灯", "闪光灯"],
|
||||
"card_uid": "2CC8B103",
|
||||
"action": action,
|
||||
"action_names": aliases,
|
||||
"slot": slot,
|
||||
}
|
||||
for capability_id, action, aliases, slot in (
|
||||
("light-on", "开启", ["打开", "开启", "启动"], 1),
|
||||
(
|
||||
"light-red",
|
||||
"切换到红色",
|
||||
["切换到红色", "切换为红色", "设置为红色"],
|
||||
3,
|
||||
),
|
||||
("light-off", "关闭", ["关闭", "关掉", "熄灭"], 2),
|
||||
)
|
||||
]
|
||||
|
||||
partition = partition_active_peripheral_intent(
|
||||
"打开闪烁灯,颜色切换到红色,打开空调",
|
||||
catalog,
|
||||
{"card": {"uid": "2CC8B103"}},
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
[item["capability"]["id"] for item in partition["peripheral_actions"]],
|
||||
["light-on", "light-red"],
|
||||
)
|
||||
self.assertEqual(
|
||||
[item["clause"] for item in partition["peripheral_actions"]],
|
||||
["打开闪烁灯", "颜色切换到红色"],
|
||||
)
|
||||
self.assertEqual(partition["residual_utterance"], "打开空调")
|
||||
self.assertEqual(partition["unresolved_peripheral_clauses"], [])
|
||||
|
||||
vehicle_breaks_context = partition_active_peripheral_intent(
|
||||
"打开闪烁灯,打开空调,然后关闭",
|
||||
catalog,
|
||||
{"card": {"uid": "2CC8B103"}},
|
||||
)
|
||||
self.assertEqual(
|
||||
[
|
||||
item["capability"]["id"]
|
||||
for item in vehicle_breaks_context["peripheral_actions"]
|
||||
],
|
||||
["light-on"],
|
||||
)
|
||||
self.assertEqual(
|
||||
vehicle_breaks_context["residual_utterance"], "打开空调,关闭"
|
||||
)
|
||||
|
||||
def test_partition_filters_disabled_followup_without_losing_allowed_work(
|
||||
self,
|
||||
) -> None:
|
||||
catalog = [
|
||||
{
|
||||
"id": capability_id,
|
||||
"status": "learned",
|
||||
"enabled": enabled,
|
||||
"device_id": "tony-light-001",
|
||||
"device_name": "闪灯玩具",
|
||||
"object_names": ["闪灯玩具", "闪烁灯", "闪光灯"],
|
||||
"card_uid": "2CC8B103",
|
||||
"action": action,
|
||||
"action_names": aliases,
|
||||
"slot": slot,
|
||||
}
|
||||
for capability_id, action, aliases, slot, enabled in (
|
||||
("light-on", "开启", ["打开", "开启", "启动"], 1, True),
|
||||
(
|
||||
"light-blue",
|
||||
"切换到蓝色",
|
||||
["切换到蓝色", "切换为蓝色", "设置为蓝色"],
|
||||
5,
|
||||
False,
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
partition = partition_active_peripheral_intent(
|
||||
"打开空调,温度设置为24度,风速调整为3档,开启闪光灯,颜色设置为蓝色",
|
||||
catalog,
|
||||
{"card": {"uid": "2CC8B103"}},
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
[item["capability"]["id"] for item in partition["peripheral_actions"]],
|
||||
["light-on"],
|
||||
)
|
||||
self.assertEqual(
|
||||
partition["residual_utterance"],
|
||||
"打开空调,温度设置为24度,风速调整为3档",
|
||||
)
|
||||
self.assertEqual(partition["unresolved_peripheral_clauses"], [])
|
||||
self.assertEqual(
|
||||
[
|
||||
(item["clause"], item["capability"]["id"], item["reason"])
|
||||
for item in partition["denied_peripheral_actions"]
|
||||
],
|
||||
[("颜色设置为蓝色", "light-blue", "nfc_command_disabled")],
|
||||
)
|
||||
|
||||
def test_partition_never_claims_generic_vehicle_light_request(self) -> None:
|
||||
catalog = [
|
||||
{
|
||||
"id": "toy-001-light-on",
|
||||
"status": "learned",
|
||||
"device_id": "toy-001",
|
||||
"device_name": "玩具 001",
|
||||
"device_aliases": ["闪灯玩具", "氛围灯"],
|
||||
"card_uid": "2CC8B103",
|
||||
"action": "开启闪灯",
|
||||
"slot": 1,
|
||||
}
|
||||
]
|
||||
|
||||
partition = partition_active_peripheral_intent(
|
||||
"打开氛围灯,空调24度",
|
||||
catalog,
|
||||
{"card": {"uid": "2CC8B103"}},
|
||||
)
|
||||
|
||||
self.assertEqual(partition["peripheral_actions"], [])
|
||||
self.assertEqual(partition["residual_utterance"], "打开氛围灯,空调24度")
|
||||
|
||||
def test_infrared_slot_catalog_is_durable_sorted_and_device_safe(self) -> None:
|
||||
with TemporaryDirectory() as directory:
|
||||
database_path = Path(directory, "infrared-slots.sqlite3")
|
||||
@@ -111,6 +323,127 @@ class SelfExtensionTests(unittest.TestCase):
|
||||
[12],
|
||||
)
|
||||
|
||||
def test_infrared_slot_catalog_migrates_legacy_schema_without_data_loss(self) -> None:
|
||||
with TemporaryDirectory() as directory:
|
||||
database_path = Path(directory, "infrared-slots.sqlite3")
|
||||
with sqlite3.connect(database_path) as connection:
|
||||
connection.execute(
|
||||
"""
|
||||
CREATE TABLE infrared_slots (
|
||||
slot INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
learned_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
)
|
||||
"""
|
||||
)
|
||||
connection.execute(
|
||||
"INSERT INTO infrared_slots VALUES (?, ?, ?, ?, ?)",
|
||||
(1, "打开闪烁灯", "tcp", "before", "before"),
|
||||
)
|
||||
|
||||
service = SelfExtensionService(
|
||||
SimulationGateway(), catalog_db_path=database_path
|
||||
)
|
||||
slots = service.list_infrared_slots()["slots"]
|
||||
|
||||
self.assertEqual(slots[0]["name"], "打开闪烁灯")
|
||||
self.assertEqual(slots[0]["device_id"], "")
|
||||
self.assertEqual(slots[0]["object_names"], [])
|
||||
self.assertEqual(slots[0]["action_names"], [])
|
||||
with sqlite3.connect(database_path) as connection:
|
||||
columns = {
|
||||
row[1]
|
||||
for row in connection.execute(
|
||||
"PRAGMA table_info(infrared_slots)"
|
||||
).fetchall()
|
||||
}
|
||||
self.assertIn("object_names_json", columns)
|
||||
self.assertIn("action_names_json", columns)
|
||||
self.assertIn("device_id", columns)
|
||||
|
||||
def test_structured_slot_names_persist_and_update_bound_whitelist(self) -> None:
|
||||
with TemporaryDirectory() as directory:
|
||||
database_path = Path(directory, "infrared-slots.sqlite3")
|
||||
service = SelfExtensionService(
|
||||
SimulationGateway(), catalog_db_path=database_path
|
||||
)
|
||||
service.sync_peripheral_catalog(
|
||||
[
|
||||
{
|
||||
"id": "toy-light-on",
|
||||
"status": "learned",
|
||||
"device_id": "toy-light",
|
||||
"device_name": "灯光玩具",
|
||||
"card_uid": "2CC8B103",
|
||||
"action": "开启灯光",
|
||||
"slot": 1,
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
saved = service.save_infrared_slot(
|
||||
1,
|
||||
{
|
||||
"device_id": "toy-light",
|
||||
"object_names": "闪烁灯,闪光灯、闪灯玩具",
|
||||
"action_names": ["打开", "开启", "启动"],
|
||||
"source": "tcp",
|
||||
},
|
||||
)["slot"]
|
||||
|
||||
self.assertEqual(saved["device_id"], "toy-light")
|
||||
self.assertEqual(saved["object_names"], ["闪烁灯", "闪光灯", "闪灯玩具"])
|
||||
self.assertEqual(saved["action_names"], ["打开", "开启", "启动"])
|
||||
reloaded = SelfExtensionService(
|
||||
SimulationGateway(), catalog_db_path=database_path
|
||||
)
|
||||
self.assertEqual(
|
||||
reloaded.list_infrared_slots()["slots"][0]["object_names"],
|
||||
["闪烁灯", "闪光灯", "闪灯玩具"],
|
||||
)
|
||||
whitelist = reloaded.list_peripheral_capabilities()
|
||||
self.assertEqual(whitelist[0]["object_names"], saved["object_names"])
|
||||
self.assertEqual(whitelist[0]["action_names"], saved["action_names"])
|
||||
resolved = resolve_active_peripheral_command(
|
||||
"打开闪灯玩具",
|
||||
whitelist,
|
||||
{"card": {"uid": "2CC8B103"}},
|
||||
)
|
||||
self.assertEqual(resolved["id"], "toy-light-on")
|
||||
|
||||
changed = service.save_infrared_slot(
|
||||
1,
|
||||
{
|
||||
"device_id": "toy-light-v2",
|
||||
"object_names": ["新版闪烁灯"],
|
||||
"action_names": ["打开"],
|
||||
"source": "tcp",
|
||||
},
|
||||
)["slot"]
|
||||
self.assertEqual(changed["device_id"], "toy-light-v2")
|
||||
self.assertEqual(service.list_peripheral_capabilities(), [])
|
||||
|
||||
def test_structured_slot_rejects_invalid_or_missing_device_id(self) -> None:
|
||||
with TemporaryDirectory() as directory:
|
||||
service = SelfExtensionService(
|
||||
SimulationGateway(),
|
||||
catalog_db_path=Path(directory, "infrared-slots.sqlite3"),
|
||||
)
|
||||
for device_id in (None, "含 空格", "-bad-prefix"):
|
||||
with self.subTest(device_id=device_id), self.assertRaisesRegex(
|
||||
GatewayError, "设备 ID"
|
||||
):
|
||||
service.save_infrared_slot(
|
||||
1,
|
||||
{
|
||||
"device_id": device_id,
|
||||
"object_names": ["闪烁灯"],
|
||||
"action_names": ["打开"],
|
||||
},
|
||||
)
|
||||
|
||||
def test_erase_all_clears_catalog_only_after_valid_device_submission(self) -> None:
|
||||
with TemporaryDirectory() as directory:
|
||||
service = SelfExtensionService(
|
||||
@@ -374,6 +707,47 @@ class SelfExtensionTests(unittest.TestCase):
|
||||
)
|
||||
)
|
||||
|
||||
def test_structured_color_actions_require_a_unique_result_value(self) -> None:
|
||||
present = {"card": {"uid": "2CC8B103"}}
|
||||
catalog = [
|
||||
{
|
||||
"id": capability_id,
|
||||
"status": "learned",
|
||||
"device_name": "灯光玩具",
|
||||
"object_names": ["闪烁灯", "闪灯玩具"],
|
||||
"card_uid": "2CC8B103",
|
||||
"action": color,
|
||||
"action_names": [f"设置为{color}", f"切换到{color}", f"切换为{color}"],
|
||||
"slot": slot,
|
||||
}
|
||||
for capability_id, color, slot in (
|
||||
("blue", "蓝色", 5),
|
||||
("red", "红色", 6),
|
||||
)
|
||||
]
|
||||
|
||||
resolved = resolve_active_peripheral_command(
|
||||
"闪烁灯切换为蓝色", catalog, present
|
||||
)
|
||||
self.assertEqual(resolved["id"], "blue")
|
||||
self.assertIsNone(
|
||||
resolve_active_peripheral_command(
|
||||
"闪烁灯切换为蓝色",
|
||||
[
|
||||
{**item, "enabled": item["id"] != "blue"}
|
||||
for item in catalog
|
||||
],
|
||||
present,
|
||||
)
|
||||
)
|
||||
|
||||
ambiguous = [
|
||||
{**item, "action_names": ["切换为"]}
|
||||
for item in catalog
|
||||
]
|
||||
with self.assertRaisesRegex(ExtensionIntegrationError, "同时匹配多个"):
|
||||
resolve_active_peripheral_command("闪烁灯切换为", ambiguous, present)
|
||||
|
||||
def test_timed_phrase_selects_live_nfc_catalog_for_gpt_plan(self) -> None:
|
||||
catalog = [
|
||||
{
|
||||
@@ -410,6 +784,38 @@ class SelfExtensionTests(unittest.TestCase):
|
||||
[],
|
||||
)
|
||||
|
||||
def test_exact_infrared_sequence_uses_local_rules_without_cloud(self) -> None:
|
||||
service = SelfExtensionService(SimulationGateway())
|
||||
catalog = [
|
||||
{
|
||||
"id": capability_id,
|
||||
"status": "learned",
|
||||
"device_name": "第三方氛围灯",
|
||||
"action": action,
|
||||
"aliases": aliases,
|
||||
"slot": slot,
|
||||
}
|
||||
for capability_id, action, aliases, slot in (
|
||||
("on", "开启灯光", ["打开"], 1),
|
||||
("off", "关闭灯光", ["关掉"], 2),
|
||||
)
|
||||
]
|
||||
|
||||
with patch.dict(os.environ, {"OPENAI_API_KEY": ""}, clear=False):
|
||||
result = service.plan(
|
||||
{
|
||||
"utterance": "打开第三方氛围灯,5秒后关闭第三方氛围灯",
|
||||
"peripherals": catalog,
|
||||
"vehicle_devices": [],
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(result["provider_used"], "infrared_local_rules")
|
||||
self.assertEqual(
|
||||
[step["kind"] for step in result["steps"]],
|
||||
["peripheral", "delay", "peripheral"],
|
||||
)
|
||||
|
||||
def test_main_peripheral_result_uses_gscu_without_vehicle_can(self) -> None:
|
||||
service = SelfExtensionService(SimulationGateway())
|
||||
|
||||
@@ -502,6 +908,151 @@ class SelfExtensionTests(unittest.TestCase):
|
||||
sleep.assert_any_call(5.0)
|
||||
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:
|
||||
service = SelfExtensionService(SimulationGateway())
|
||||
catalog = [
|
||||
{
|
||||
"id": capability_id,
|
||||
"status": "learned",
|
||||
"device_id": "tony-light-001",
|
||||
"device_name": "闪灯玩具",
|
||||
"card_uid": "2CC8B103",
|
||||
"action": action,
|
||||
"slot": slot,
|
||||
}
|
||||
for capability_id, action, slot in (
|
||||
("light-on", "打开", 1),
|
||||
("light-green", "切换到绿色", 4),
|
||||
)
|
||||
]
|
||||
plan_result = {
|
||||
"provider_used": "infrared_local_rules",
|
||||
"summary": "打开闪光灯并切换为绿色",
|
||||
"steps": [
|
||||
{"kind": "peripheral", "capability_id": "light-on"},
|
||||
{"kind": "peripheral", "capability_id": "light-green"},
|
||||
],
|
||||
}
|
||||
|
||||
with patch.object(
|
||||
service,
|
||||
"execute_gateway",
|
||||
side_effect=GatewayError("simulated infrared transport failure"),
|
||||
) as execute_gateway:
|
||||
result = service.execute_peripheral_plan_request(
|
||||
"开启闪光灯,颜色设置为绿色",
|
||||
plan_result,
|
||||
catalog,
|
||||
conversation_id="conv-failed-first-infrared-emit",
|
||||
execution_mode="hardware",
|
||||
)
|
||||
|
||||
execute_gateway.assert_called_once_with("emit", {"slot": 1})
|
||||
self.assertEqual(result["status"], "failed")
|
||||
self.assertEqual(result["sequence"]["action_count"], 2)
|
||||
self.assertEqual(result["sequence"]["emit_count"], 0)
|
||||
self.assertEqual(
|
||||
[
|
||||
item["parameters"]["slot"]
|
||||
for item in result["function_config"]["spec"]["operations"]
|
||||
],
|
||||
[1, 4],
|
||||
)
|
||||
self.assertEqual(
|
||||
[
|
||||
item["signals"]["slot"]
|
||||
for item in result["command_plan"]["spec"]["commands"]
|
||||
],
|
||||
[1, 4],
|
||||
)
|
||||
self.assertEqual(
|
||||
[item["status"] for item in result["operation_results"]],
|
||||
["failed"],
|
||||
)
|
||||
|
||||
def test_submitted_emit_without_ack_continues_complete_infrared_plan(self) -> None:
|
||||
service = SelfExtensionService(SimulationGateway())
|
||||
catalog = [
|
||||
{
|
||||
"id": capability_id,
|
||||
"status": "learned",
|
||||
"device_id": "tony-light-001",
|
||||
"device_name": "闪灯玩具",
|
||||
"card_uid": "2CC8B103",
|
||||
"action": action,
|
||||
"slot": slot,
|
||||
}
|
||||
for capability_id, action, slot in (
|
||||
("light-on", "打开", 1),
|
||||
("light-green", "切换到绿色", 4),
|
||||
)
|
||||
]
|
||||
|
||||
def submit_without_ack(_action, payload):
|
||||
command = build_gateway_command("emit", payload)
|
||||
return {
|
||||
"success": True,
|
||||
"transport": "tcp",
|
||||
"request": command,
|
||||
"device_response": {
|
||||
"success": True,
|
||||
"delivery_status": "submitted",
|
||||
"transport_submitted": True,
|
||||
"gateway_acknowledged": False,
|
||||
},
|
||||
"gateway_acknowledged": False,
|
||||
}
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
service,
|
||||
"execute_gateway",
|
||||
side_effect=submit_without_ack,
|
||||
) as execute_gateway,
|
||||
patch("cognitive_agent.application.self_extension.time.sleep"),
|
||||
):
|
||||
result = service.execute_peripheral_plan_request(
|
||||
"开启闪光灯,颜色设置为绿色",
|
||||
{
|
||||
"provider_used": "infrared_local_rules",
|
||||
"summary": "打开闪光灯并切换为绿色",
|
||||
"steps": [
|
||||
{"kind": "peripheral", "capability_id": "light-on"},
|
||||
{"kind": "peripheral", "capability_id": "light-green"},
|
||||
],
|
||||
},
|
||||
catalog,
|
||||
conversation_id="conv-submitted-unacknowledged-infrared",
|
||||
execution_mode="hardware",
|
||||
)
|
||||
|
||||
self.assertEqual(execute_gateway.call_count, 2)
|
||||
self.assertEqual(result["status"], "succeeded")
|
||||
self.assertEqual(result["sequence"]["emit_count"], 2)
|
||||
self.assertEqual(
|
||||
[item["telemetry"]["slot"] for item in result["operation_results"]],
|
||||
[1, 4],
|
||||
)
|
||||
self.assertEqual(
|
||||
[
|
||||
item["telemetry"]["delivery_status"]
|
||||
for item in result["operation_results"]
|
||||
],
|
||||
["submitted", "submitted"],
|
||||
)
|
||||
self.assertTrue(
|
||||
all(
|
||||
item["telemetry"]["transport_submitted"]
|
||||
for item in result["operation_results"]
|
||||
)
|
||||
)
|
||||
self.assertTrue(
|
||||
all(
|
||||
item["telemetry"]["gateway_acknowledged"] is False
|
||||
for item in result["operation_results"]
|
||||
)
|
||||
)
|
||||
|
||||
def test_color_sequence_never_cycles_the_power_off_capability(self) -> None:
|
||||
service = SelfExtensionService(SimulationGateway())
|
||||
catalog = [
|
||||
@@ -645,6 +1196,9 @@ class SelfExtensionTests(unittest.TestCase):
|
||||
) -> str:
|
||||
self.assertEqual(peripherals[0]["id"], "peripheral_1")
|
||||
self.assertNotIn(opaque_id, [item["id"] for item in peripherals])
|
||||
self.assertNotIn("slot", peripherals[0])
|
||||
self.assertNotIn("card_uid", peripherals[0])
|
||||
self.assertNotIn("transport", peripherals[0])
|
||||
return json.dumps(
|
||||
{
|
||||
"summary": "切换红灯",
|
||||
|
||||
Reference in New Issue
Block a user