| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- from __future__ import annotations
- from typing import Any, NotRequired, TypedDict
- from pydantic import BaseModel
- from .collector_api import (
- create_bacnet_devices as api_create_bacnet_devices,
- create_bacnet_points as api_create_bacnet_points,
- edit_bacnet_device as api_edit_bacnet_device,
- edit_bacnet_point as api_edit_bacnet_point,
- )
- from .gateway_api import (
- bacnet_bbmd_whois as api_bacnet_bbmd_whois,
- bacnet_point_collect_test as api_bacnet_point_collect_test,
- bacnet_point_search as api_bacnet_point_search,
- )
- from .mcp_app import mcp
- class BacnetDeviceCreateItem(BaseModel):
- name: str
- ip: str
- bacnet_device_id: int
- port: int = 47808
- bacnet_net: int = 0
- asp_ip: str = ""
- timeout: int = 3
- is_persistent: bool = False
- group_id: int = 0
- alarm_interval: int = 90
- collect_interval: int = 5
- class BacnetPointSpec(TypedDict):
- object_type: str
- object_id: int
- name: NotRequired[str]
- description: NotRequired[str]
- class BacnetPointCreateItem(TypedDict):
- device_id: int
- object_type: str
- object_id: int
- name: NotRequired[str]
- object_name: NotRequired[str]
- point_id: NotRequired[str]
- priority: NotRequired[int | None]
- units: NotRequired[str]
- value_type: NotRequired[int]
- group_id: NotRequired[int]
- scale_ratio: NotRequired[float]
- value_offset: NotRequired[float]
- describe: NotRequired[str]
- @mcp.tool(
- name="bacnet.point_collect_test",
- description=(
- "通过采集网关读取 BACnet/IP 点位 present-value。调用 "
- "{base_url}/api/dc-gateway/bacnet/read_points。ip 为 BACnet/IP 设备地址,"
- "bacnet_device_id 为 BACnet 设备对象实例号,也叫设备号,范围 0..4194303;port 默认 47808。"
- "points 每项必须包含 object_type 和 object_id;object_type 支持 AnalogInput 这类格式。"
- "如果传入 analogInput、analog-input 等常见写法,需要先转换为 AnalogInput 这类格式;"
- "网关响应也会规范为 AnalogInput 形式。"
- "响应 data.points[].present_value 为当前值;响应透传上游 JSON,code=0 表示业务成功。"
- ),
- )
- def bacnet_point_collect_test(
- project_key: str,
- ip: str,
- bacnet_device_id: int,
- points: list[BacnetPointSpec],
- port: int = 47808,
- ) -> dict[str, Any]:
- return api_bacnet_point_collect_test(
- project_key,
- ip=ip,
- bacnet_device_id=bacnet_device_id,
- port=port,
- points=points,
- )
- @mcp.tool(
- name="bacnet.point_search",
- description=(
- "通过采集网关搜索 BACnet/IP 设备点位。调用 "
- "{base_url}/api/dc-gateway/bacnet/search_points。ip 为 BACnet/IP 设备地址,"
- "bacnet_device_id 为 BACnet 设备对象实例号,也叫设备号,范围 0..4194303;port 默认 47808。"
- "网关读取设备对象的 object-list,并过滤常见点位对象类型:AnalogInput、AnalogOutput、"
- "AnalogValue、BinaryInput、BinaryOutput、BinaryValue、MultiStateInput、MultiStateOutput、"
- "MultiStateValue。响应 data.points[] 包含 name、description、object_type、object_id、"
- "present_value;单点 description 或 present_value 读取失败时字段为 null。"
- "响应透传上游 JSON,code=0 表示业务成功。"
- ),
- )
- def bacnet_point_search(
- project_key: str,
- ip: str,
- bacnet_device_id: int,
- port: int = 47808,
- ) -> dict[str, Any]:
- return api_bacnet_point_search(
- project_key,
- ip=ip,
- bacnet_device_id=bacnet_device_id,
- port=port,
- )
- @mcp.tool(
- name="bacnet.bbmd_whois",
- description=(
- "通过采集网关执行 BACnet/IP BBMD Who-Is 设备发现。调用 "
- "{base_url}/api/dc-gateway/bacnet/bbmd/whois,MCP 不发送请求体;"
- "BBMD 地址、端口、TTL、Who-Is 等待超时、设备号范围、本机绑定地址等参数由采集网关环境变量配置,"
- "包括 BACNET_BBMD_IP、BACNET_BBMD_PORT、BACNET_BBMD_TTL、"
- "BACNET_BBMD_WHOIS_TIMEOUT、BACNET_BBMD_LOW_LIMIT、BACNET_BBMD_HIGH_LIMIT、"
- "BACNET_BBMD_LOCAL_DEVICE_ID、BACNET_LOCAL_IP、BACNET_LOCAL_PORT。"
- "网关会注册 BBMD Foreign Device,分发 Who-Is,收集 I-Am 响应并在结束前注销;"
- "响应 data.bbmd 为本次 BBMD 请求配置,data.devices[] 为发现的 BACnet 设备,"
- "常见字段包括 bacnet_device_id、ip、port、max_apdu、segmentation、vendor_id;"
- "响应透传上游 JSON,code=0 表示业务成功。"
- ),
- )
- def bacnet_bbmd_whois(project_key: str) -> dict[str, Any]:
- return api_bacnet_bbmd_whois(project_key)
- @mcp.tool(
- name="collector.bacnet_device_create",
- description=(
- "汇采-批量创建 BACnet 设备。依次调用 {data_collector_base_url}/api/collector/device,"
- "请求体固定 type=bacnet 且 device_type=1(ASP 设备)。全部设备创建调用完成后会内部调用"
- "设备列表并匹配每个设备 id;如果匹配到多个设备,选择 id 最大的。devices 每项必须传 "
- "name、ip、bacnet_device_id(设备号);port 默认 47808,bacnet_net 默认 0,asp_ip 默认空字符串。"
- "默认参数: timeout=3, is_persistent=false, group_id=0, alarm_interval=90, collect_interval=5。"
- "响应返回批量结果,state=0 表示全部创建并匹配成功。"
- ),
- )
- def collector_bacnet_device_create(
- project_key: str,
- devices: list[BacnetDeviceCreateItem],
- ) -> dict[str, Any]:
- return api_create_bacnet_devices(project_key, [_dump_model_or_dict(item) for item in devices])
- @mcp.tool(
- name="collector.bacnet_device_edit",
- description=(
- "汇采-编辑 BACnet 设备。调用 {data_collector_base_url}/api/collector/bacnet/device/edit。"
- "必须传 ori_id 原设备 id、name、ip、bacnet_device_id(设备号);MCP 会把 bacnet_device_id 映射为"
- "汇采旧接口的 device_id 字符串,并固定 type=1(ASP 设备)。编辑前设备不能处于已连接状态;"
- "若已连接,请先调用 collector.device_disconnect,并传 device_type=bacnet。该接口是全量更新语义。"
- "默认参数: port=47808, net=0, asp_ip='', timeout=3, is_persistent=false, "
- "device_group_id=0, alarm_interval=90, collect_interval=5。响应透传上游 JSON,state=0 表示业务成功。"
- ),
- )
- def collector_bacnet_device_edit(
- project_key: str,
- ori_id: int,
- name: str,
- ip: str,
- bacnet_device_id: int,
- port: int = 47808,
- bacnet_net: int = 0,
- asp_ip: str = "",
- is_persistent: bool = False,
- device_group_id: int = 0,
- timeout: int = 3,
- alarm_interval: int = 90,
- collect_interval: int = 5,
- ) -> dict[str, Any]:
- return api_edit_bacnet_device(
- project_key,
- {
- "ori_id": ori_id,
- "name": name,
- "ip": ip,
- "bacnet_device_id": bacnet_device_id,
- "port": port,
- "bacnet_net": bacnet_net,
- "asp_ip": asp_ip,
- "is_persistent": is_persistent,
- "device_group_id": device_group_id,
- "timeout": timeout,
- "alarm_interval": alarm_interval,
- "collect_interval": collect_interval,
- },
- )
- @mcp.tool(
- name="collector.bacnet_point_create",
- description=(
- "汇采-批量创建 BACnet 采集点位。依次调用 "
- "{data_collector_base_url}/api/collector/bacnet/point/add_collect_point。"
- "points 每项必须传 device_id、object_type、object_id,并传 name 或 object_name;"
- "object_type 支持 AnalogInput 这类格式;如果传入 analogInput、analog-input 等常见写法,"
- "需要先转换为 AnalogInput 这类格式;"
- "MCP 会将单个点位包装成汇采接口要求的 {device_id, points:[...]}。"
- "每个点位会补齐默认值: point_id='', priority=null, units='', value_type=0, "
- "group_id=0, scale_ratio=1, value_offset=0, describe=''。"
- "常见 value_type: 1=Boolean, 2=Unsigned, 3=Signed, 4=Real, 5=Double, 6=Enumerated。"
- "响应返回批量结果,state=0 表示全部创建成功。"
- ),
- )
- def collector_bacnet_point_create(
- project_key: str,
- points: list[BacnetPointCreateItem],
- ) -> dict[str, Any]:
- return api_create_bacnet_points(project_key, points)
- @mcp.tool(
- name="collector.bacnet_point_edit",
- description=(
- "汇采-编辑 BACnet 采集点位。调用 {data_collector_base_url}/api/collector/bacnet/point/edit。"
- "必须传 ori_id 原点位 id、object_type、object_id,并传 name 或 object_name;ori_id 对应 "
- "collector.device_points 返回的 data.point[].id,MCP 会映射为汇采接口字段 id。"
- "object_type 支持 AnalogInput 这类格式;如果传入 analogInput、analog-input 等常见写法,"
- "MCP 会先转换为 AnalogInput 这类格式再调用汇采接口。"
- "该接口是全量更新语义,未传字段可能被默认值覆盖。默认参数: point_id='', priority=null, "
- "units='', value_type=0, group_id=0, scale_ratio=1, value_offset=0, describe='', "
- "invalid_values='', valid_range_start=null, valid_range_end=null。"
- "响应透传上游 JSON,state=0 表示业务成功。"
- ),
- )
- def collector_bacnet_point_edit(
- project_key: str,
- ori_id: int,
- object_type: str,
- object_id: int,
- name: str = "",
- object_name: str = "",
- point_id: str = "",
- priority: int | None = None,
- units: str = "",
- value_type: int = 0,
- group_id: int = 0,
- scale_ratio: float = 1,
- value_offset: float = 0,
- describe: str = "",
- invalid_values: str = "",
- valid_range_start: float | None = None,
- valid_range_end: float | None = None,
- ) -> dict[str, Any]:
- return api_edit_bacnet_point(
- project_key,
- {
- "ori_id": ori_id,
- "name": name,
- "object_type": object_type,
- "object_id": object_id,
- "object_name": object_name,
- "point_id": point_id,
- "priority": priority,
- "units": units,
- "value_type": value_type,
- "group_id": group_id,
- "scale_ratio": scale_ratio,
- "value_offset": value_offset,
- "describe": describe,
- "invalid_values": invalid_values,
- "valid_range_start": valid_range_start,
- "valid_range_end": valid_range_end,
- },
- )
- def _dump_model_or_dict(item: Any) -> dict[str, Any]:
- if isinstance(item, BaseModel):
- return item.model_dump(exclude_none=True)
- return dict(item)
|