feat: add seat firmware workbench and OTA delivery
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,86 @@
|
||||
"""Conservative, local defaults for adding named devices to an existing mode.
|
||||
|
||||
Only a complete *bare device list* uses these defaults. Explicit parameters and
|
||||
actions stay in the normal parser; unrecognised list entries never disappear.
|
||||
Defaults are proposals for a fully validated preview, not executable commands.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from copy import deepcopy
|
||||
from typing import Any
|
||||
|
||||
_ADD_PREFIX = re.compile(
|
||||
r"^(?:(?:请|帮我|帮忙|再|还|也|同时|顺便|继续|并且|并)\s*)*"
|
||||
r"(?:加入|添加|增加|补充|加上|追加|新增)\s*"
|
||||
)
|
||||
_DEVICE_GROUPS = {
|
||||
"空调": "climate", "冷气": "climate",
|
||||
"氛围灯": "ambient_light", "气氛灯": "ambient_light",
|
||||
"阅读灯": "reading", "读书灯": "reading",
|
||||
"车窗": "window", "窗户": "window",
|
||||
"遮阳帘": "roof", "天幕遮阳帘": "roof", "天窗遮阳帘": "roof",
|
||||
"室内灯": "indoor", "顶灯": "indoor",
|
||||
}
|
||||
_LIST_SEPARATOR = re.compile(r"[、,,;;\s]+|以及|还有|和|与|及")
|
||||
_EXPLICIT_ACTION = re.compile(
|
||||
r"\d|[零一二三四五六七八九十百]+(?:度|档|挡|秒|分钟|%)|"
|
||||
r"打开|开启|关闭|关掉|开到|关到|调整|设置|调到|调成|变成|改成|"
|
||||
r"升高|降低|调高|调低|调亮|调暗|全开|全关|半开|一半|"
|
||||
r"红色|蓝色|绿色|白色|暖色|冷色|亮度|温度|风量|内循环|外循环"
|
||||
)
|
||||
|
||||
|
||||
def strip_mode_addition_prefix(change_text: str) -> str:
|
||||
"""Remove only the editing verb, not any device, value or negation."""
|
||||
return _ADD_PREFIX.sub("", change_text.strip(), count=1)
|
||||
|
||||
|
||||
def mode_device_additions(
|
||||
change_text: str,
|
||||
base_operations: list[dict[str, Any]],
|
||||
default_operations: list[dict[str, Any]],
|
||||
) -> list[dict[str, Any]] | None:
|
||||
"""Return missing device defaults, None for non-list/explicit edits.
|
||||
|
||||
Existing groups are already configured and are left completely untouched,
|
||||
including their target zones, power state and custom parameter values.
|
||||
Reading and indoor lights are separate groups within the same domain.
|
||||
"""
|
||||
if not _ADD_PREFIX.match(change_text.strip()):
|
||||
return None
|
||||
body = strip_mode_addition_prefix(change_text).strip("。.!!吧 ")
|
||||
if _EXPLICIT_ACTION.search(body):
|
||||
return None
|
||||
body = re.sub(r"(?:等)?(?:相关)?(?:设备)?(?:的)?(?:控制)?(?:动作|能力|联动)?$", "", body)
|
||||
body = re.sub(r"等$", "", body)
|
||||
devices = [item for item in _LIST_SEPARATOR.split(body) if item]
|
||||
groups: list[str] = []
|
||||
for device in devices:
|
||||
device = re.sub(r"(?:设备)?(?:的)?(?:控制)?(?:动作|能力)$", "", device)
|
||||
group = _DEVICE_GROUPS.get(device)
|
||||
if group is None:
|
||||
raise ValueError(f"未识别或暂不支持增补设备“{device}”;未修改任何模式动作。")
|
||||
if group not in groups:
|
||||
groups.append(group)
|
||||
if not groups:
|
||||
raise ValueError("请说明要加入哪些设备,或提供明确的设备动作。")
|
||||
|
||||
def belongs(operation: dict[str, Any], group: str) -> bool:
|
||||
capability = str(operation.get("capability") or "")
|
||||
if group == "reading":
|
||||
return capability == "interior_light.set_reading_power"
|
||||
if group == "indoor":
|
||||
return capability == "interior_light.set_indoor_power"
|
||||
return operation.get("domain") == group
|
||||
|
||||
additions: list[dict[str, Any]] = []
|
||||
for group in groups:
|
||||
if any(belongs(operation, group) for operation in base_operations):
|
||||
continue
|
||||
candidates = [operation for operation in default_operations if belongs(operation, group)]
|
||||
if not candidates:
|
||||
raise ValueError(f"该设备尚无经过登记的默认编排,请补充明确目标:{group}。")
|
||||
additions.extend(deepcopy(candidates))
|
||||
return additions
|
||||
@@ -170,11 +170,12 @@ _MODE_PROFILE_DELETE_BA = re.compile(
|
||||
)
|
||||
_MODE_PROFILE_UPDATE_NEGATION = re.compile(
|
||||
r"不要|别(?:再)?|不用|无需|不必|禁止|不许|不得|"
|
||||
r"取消(?:修改|调整|更改|更新|变更|操作)|不执行|不下发|不保存"
|
||||
r"取消(?:修改|调整|更改|更新|变更|加入|添加|增加|补充|加上|追加|新增|操作)|"
|
||||
r"撤销|算了|不执行|不下发|不保存"
|
||||
)
|
||||
_MODE_PROFILE_UPDATE_PREFIX_NEGATION = re.compile(
|
||||
r"^(?:请|麻烦)?(?:先|暂时)?(?:不要|别|不用|无需|不必|禁止|不许|不得)"
|
||||
r"(?:把|将|修改|调整|更改|更新|变更)"
|
||||
r"(?:把|将|在|给|为|往|向|修改|调整|更改|更新|变更)"
|
||||
)
|
||||
_MODE_POSSESSIVE_BOUNDARY = r"(?:中的|里的|内的|之中的|的|中|里|内)"
|
||||
_MODE_MEMBER_BOUNDARY = rf"(?:{_MODE_POSSESSIVE_BOUNDARY}|[,,::])"
|
||||
@@ -197,7 +198,19 @@ _CONTEXTUAL_NAME_FIRST_UPDATE = re.compile(
|
||||
rf"^{_LEADING_POLITE}{_QUOTED_MODE_NAME}[,,::]"
|
||||
r"(?P<change>.+?)[。.!!]?$"
|
||||
)
|
||||
_MODE_PROFILE_APPEND_VERB = r"(?:加入|添加|增加|补充|加上|追加|新增)"
|
||||
_APPEND_MODE_UPDATE = re.compile(
|
||||
rf"^{_LEADING_POLITE}(?:在|给|为|往|向)?{_QUOTED_MODE_NAME}"
|
||||
rf"(?:{_MODE_POSSESSIVE_BOUNDARY})?[,,::]?"
|
||||
rf"(?:再|还|也|同时|另外)?(?P<change>{_MODE_PROFILE_APPEND_VERB}.+?)[。.!!]?$"
|
||||
)
|
||||
_APPEND_TO_MODE_UPDATE = re.compile(
|
||||
rf"^{_LEADING_POLITE}(?:把|将)(?P<change>.+?)"
|
||||
rf"(?P<append_verb>{_MODE_PROFILE_APPEND_VERB})(?:到|进)?{_QUOTED_MODE_NAME}"
|
||||
rf"(?:{_MODE_POSSESSIVE_BOUNDARY})?(?:吧|呀|啊|哦|谢谢(?:你)?)?[。.!!]?$"
|
||||
)
|
||||
MODE_PROFILE_CHANGE_PREDICATE = re.compile(
|
||||
rf"{_MODE_PROFILE_APPEND_VERB}|"
|
||||
r"修改|改成|改为|更改|更新|变更|变成|变为|换成|"
|
||||
r"调整|调节|调高|调低|调到|调成|调为|调至|"
|
||||
r"设置|设为|设成|设到|设至|"
|
||||
@@ -208,7 +221,7 @@ MODE_PROFILE_CHANGE_PREDICATE = re.compile(
|
||||
r"(?:重复|循环)(?:[0-9]+(?:\.[0-9]+)?|[零一二两三四五六七八九十]+)次"
|
||||
)
|
||||
MODE_PROFILE_QUERY_CUE = re.compile(
|
||||
r"什么|哪些|多少|几(?:度|档|级|个|项)?|是否|是不是|"
|
||||
r"什么|哪些|多少|几(?:度|档|级|个|项)?|是否|是不是|能否|能不能|可不可以|会不会|"
|
||||
r"合适(?:吗)?|合理(?:吗)?|会(?:怎么样|怎样|如何)|"
|
||||
r"怎么|如何|列(?:出|一下)|介绍(?:一下)?|说明(?:一下)?|"
|
||||
r"告诉我|查询|查看|有什么作用|是何作用|还是|[??]|(?:吗|么|呢)$"
|
||||
@@ -610,6 +623,8 @@ def _candidate_matches(
|
||||
) -> list[tuple[re.Match[str], bool]]:
|
||||
matches: list[tuple[re.Match[str], bool]] = []
|
||||
for pattern, predicate_required in (
|
||||
(_APPEND_MODE_UPDATE, True),
|
||||
(_APPEND_TO_MODE_UPDATE, False),
|
||||
(_PREFIX_UPDATE, False),
|
||||
(_BA_UPDATE, True),
|
||||
(_NAME_FIRST_UPDATE, True),
|
||||
@@ -629,6 +644,14 @@ def _candidate_matches(
|
||||
return matches
|
||||
|
||||
|
||||
def _mode_profile_update_change(match: re.Match[str], name: str) -> str:
|
||||
change = _strip_repeated_mode_update_subject(str(match.group("change") or ""), name)
|
||||
append_verb = str(match.groupdict().get("append_verb") or "")
|
||||
# Keep reverse-order additions on the same service contract as name-first
|
||||
# additions. Device names, explicit targets and parameters remain unchanged.
|
||||
return f"{append_verb}{change}" if append_verb else change
|
||||
|
||||
|
||||
def _strip_repeated_mode_update_subject(change: str, mode_name: str) -> str:
|
||||
"""Remove a repeated ``把<same mode>的`` wrapper from an update body."""
|
||||
|
||||
@@ -708,10 +731,7 @@ def is_mode_profile_update_shape(
|
||||
and _is_unquoted_device_property_mode(match, name)
|
||||
):
|
||||
continue
|
||||
change = _strip_repeated_mode_update_subject(
|
||||
str(match.group("change") or ""),
|
||||
name,
|
||||
)
|
||||
change = _mode_profile_update_change(match, name)
|
||||
if change and (
|
||||
not predicate_required or MODE_PROFILE_CHANGE_PREDICATE.search(change)
|
||||
):
|
||||
@@ -746,10 +766,9 @@ def parse_mode_profile_update(
|
||||
and _is_unquoted_device_property_mode(match, name)
|
||||
):
|
||||
continue
|
||||
change = _strip_repeated_mode_update_subject(
|
||||
str(match.group("change") or ""),
|
||||
name,
|
||||
)
|
||||
change = _mode_profile_update_change(match, name)
|
||||
if re.fullmatch(rf"(?:再|还|也|同时|另外)?{_MODE_PROFILE_APPEND_VERB}(?:一下)?", change):
|
||||
continue
|
||||
if (
|
||||
(not predicate_required or MODE_PROFILE_CHANGE_PREDICATE.search(change))
|
||||
and not _MODE_PROFILE_UPDATE_NEGATION.search(change)
|
||||
|
||||
Reference in New Issue
Block a user