Skip to content

MT-Gateway 平台能力对接文档

能力简介

门思科技网关统一模型:gw_heart 心跳物模型(解析网关经 NMS 上行的 handle/固件/IP/连接状态)+ mt_gw_his_get(历史数据查询)+ mt_gw_wifi_list_get(周边 WiFi 扫描)+ mt_gw_locate(GPS优先/腾讯WiFi定位兜底)+ mt_gw_geo_set_config(配腾讯LBS key)。2026-06-16 新增 WiFi 定位闭环:转发器把 nms/up/gw 的 wifiList 回灌 AS → mt_gw_wifi 物模型解析落 shared_attrs.wifi_list → mt_gw_locate_on_wifi 触发器自动派发 mt_gw_locate(绕行 BUG-048)。网关在平台以 device_type:VIRTUAL(资产)登记,eui=网关真实 eui。

项目
项目名称mt-gateway
厂商门思科技 (ManThink)
型号MT-Gateway
业务代码Not applicable
对接模式lorawan-native
协议gateway-raw

模型清单

可使用下列名称或 ID 在 ThinkLink 中搜索对应模型。

模型类型名称id_name平台模型 ID
RPC[GW SET] geo configmt_gw_geo_set_config119427355042017280
RPC[GW ACT] his getmt_gw_his_get120041209778302976
RPC[GW ACT] history getmt_gw_history_get120039711216398337
RPC[GW ACT] locatemt_gw_locate119427355042017281
RPC[GW ACT] wifi list getmt_gw_wifi_list_get119403377514672128
TemplateMT-Gateway120034832892719104
Thing Model[GW HEART]mt_gw_heart95121332907282437
Thing Model[GW WIFI] scan resultmt_gw_wifi119474792070803456
Trigger[GW LOCATE] on wifi scanmt_gw_locate_on_wifi119474793920491520

mt_gw_his_get 是当前模板使用的历史查询 RPC;mt_gw_history_get 仅用于旧绑定识别和迁移。

运维 Skill

部署或调试历史查询、WiFi 扫描、腾讯定位、转发器与定时任务时,使用 create-gateway。该 Skill 会按链路逐段验证 NMS 上行、AS 回灌、触发器和属性读回。

产品特点与适用范围

本项目面向“网关 NMS 心跳物模型 + 运维 RPC + WiFi 定位闭环(一套网关模型)”场景。能力边界、字段和可部署模型均以当前 descriptor、requirements 与模型源文件为准。

部署与采集信息

| DTU | Not applicable | | 供电 | ac | | LoRaWAN Class | C | | 接口 | LoRaWAN | | 波特率 | Not applicable |

遥测与寄存器定义

字段名称类型单位地址/来源说明
handle网关 deviceHandlestring网关 deviceHandle
hwVersion硬件版本string硬件版本
fwVersion固件版本string固件版本
netType回程网络类型(如 mteth0)string回程网络类型(如 mteth0)
validIP当前有效 IPstring当前有效 IP
nsConnectNS 连接状态booleanNS 连接状态
nmsConnectNMS 连接状态booleanNMS 连接状态
pmcConnect1PMC 连接状态booleanPMC 连接状态
wifi_ap_count本次扫到的周边 WiFi AP 数(标量遥测,触发 mt_gw_locate + 观测扫描是否成功)number本次扫到的周边 WiFi AP 数(标量遥测,触发 mt_gw_locate + 观测扫描是否成功)

参数定义

无项目专用参数定义。

Requirements 证据

以下关键事实由 requirements 源文件提取;原始行号用于复核。

类别来源事实来源
协议/数据- 心跳物模型 mt_gw_heart:解析网关经 NMS 上行的心跳帧(deviceHandle/固件/IP/ns/nms/pmc 连接状态)→ 遥测 + shared_attrs。requirement/requirements.md:15
协议/数据→ [物模型 mt_gw_wifi (CUSTOMER)] 解析 AS 帧requirement/requirements.md:41
协议/数据· telemetry_data.wifi_ap_count = 本帧扫到的 AP 数(标量)requirement/requirements.md:42
协议/数据→ [触发器 mt_gw_locate_on_wifi] 绑 mt_gw_wifi:本帧 wifi_ap_count>0 → dispatch mt_gw_locaterequirement/requirements.md:44
协议/数据shared_attrs.gw_latitude/gw_longitude/gw_accuracy/gw_address/gw_city/geo_source/geo_fetched_atrequirement/requirements.md:46
协议/数据| 物模型 | thinklink/thingModel/mt_gw_wifi.{js,json} | CUSTOMER 解析 AS 回灌帧 → wifi_ap_count(遥测) + wifi_list(shared_attrs) | 119474792070803456 |requirement/requirements.md:53
协议/数据| 转发器 | forwarder/mt-gw-wifilist-to-as.js | 订阅 nms/up/gw/+ → 滤 wifiList → 归一 AP → 回灌 AS 上行(paste-ready, 无 import) | —(脚本,无模型 id) |requirement/requirements.md:55
部署| 定时任务 | descriptor.yaml scheduled_task(部署期建) | 每 24h 下发 mt_gw_wifi_list_get 启动闭环 | —(平台任务) |requirement/requirements.md:56
协议/数据| gw_address / gw_city | shared_attrs | mt_gw_locate 写:地址 / 城市 |requirement/requirements.md:68
协议/数据- 解析器边界mt_gw_wifi parser 保持同步且不发起 HTTP 请求;HTTP 定位只由 mt_gw_locate RPC 执行。requirement/requirements.md:73
协议/数据- 触发器判别用本帧业务字段 wifi_ap_count(KB-30:禁用 _modelName,避免生产环境静默失效)。requirement/requirements.md:75
协议/数据- 双向不污染:heart(NMS, 有 msg.data)与 wifi(AS, 有 msg.userdata.payload)两类帧各自被对应 parser 处理、对方 parser 守卫返回 null。requirement/requirements.md:78
未确认事项- TBD-1(nms/up/gw 回复 AP 字段名)data 内各 AP 的字段名(ssid/bssid/rssi vs ESSID/Address/Signal 等)以真机一帧抓包为准。转发器已对常见命名容错归一(bssid|mac|Addressrssi|signalssid|essid),抓包后核对一次。requirement/requirements.md:84
未确认事项- TBD-2(转发器 broker 类型):源 nms/up/gw 与目标 as/up/data 的 broker 类型(from/to emqx type)部署期确认;脚本本身 broker 无关。requirement/requirements.md:85
未确认事项- TBD-3(设备绑定):网关设备需在 mt_gw_heart 之外追加绑定 mt_gw_wifi,并关联 mt_gw_locate_on_wifi 触发器,才能解析 AS 回灌帧并派发定位。requirement/requirements.md:86
协议/数据1. 推送 4 产物到 PUBLIC(SYSADMIN,/thinklink/system/... 路由,带 _commitMessage):mt_gw_wifi(物模型) + mt_gw_locate_on_wifi(触发器);转发器经转发器配置接口建。requirement/requirements.md:92
未确认事项3. 建转发器:from /v32/{tenant}/nms/up/gw/+ → to /v32/{tenant}/as/up/data/{eui},脚本 = forwarder/mt-gw-wifilist-to-as.js(broker 类型见 TBD-2)。requirement/requirements.md:94
未确认事项6. 验证:等一轮(或手动触发一次扫描)→ 回读 shared_attrs.wifi_list 非空、telemetry wifi_ap_count>0、shared_attrs.gw_latitude/gw_longitude 落库;抓一帧 nms/up/gw 核对 TBD-1。requirement/requirements.md:97
协议/数据| 2026-06-16 | wifi 数据:AP 列表进 shared_attrs,额外加标量遥测 wifi_ap_count | 触发器须 gate 在本帧遥测(device.telemetry_data[id])才时序新鲜+合 KB-30;AP 列表仍只在 shared_attrs 不污染历史。用户"不入 telemetry"本意(别让冗长列表入历史)不违背 | mt_gw_wifi | 再讨论 |requirement/requirements.md:107
部署| 2026-06-16 | 扫描周期 = 每 24h(cron_task);非上电检测 | 网关近静止、低频定位即可,最省;无需上电事件判定状态 | scheduled_task | 再讨论 |requirement/requirements.md:108
协议/数据| 2026-06-16 | 修复 complete-js-local.sh glob over-match(*/thinklink/*.js→收紧到 thingModel/rpc/trigger 子目录) | 工作区根目录名为 thinklink/,旧 glob 误匹配工作区所有 .js(含 paste-ready 的 forwarder/),把无 import 的转发器误报 | scripts/complete-js-local.sh | 修复 |requirement/requirements.md:109

物模型与 RPC 代码

RPC: [GW SET] geo config

id_name: mt_gw_geo_set_config · ID: 119427355042017280

javascript
import {BufferTKL, RPCHelper, Utils} from "#tklHelper";
import {Buffer} from "buffer";

// 配置网关定位服务(腾讯位置服务)的 apikey 与 base_url,写入网关设备 server_attrs。
// 供 mt_gw_locate 调用腾讯 WiFi 定位 API 时读取(key 必填;base_url 默认 https://apis.map.qq.com)。
// 腾讯定位 API: POST {base_url}/ws/location/v1/network  body {key, device_id, wifiinfo:[{mac,rssi}]}
//   文档 https://lbs.qq.com/service/webService/webServiceGuide/position/location
function rpc_script({ device, params, alarms, logger, org_params }) {
    const sa = {};
    if (params.tencent_lbs_key != null)      sa.tencent_lbs_key = params.tencent_lbs_key;
    if (params.tencent_lbs_base_url != null)  sa.tencent_lbs_base_url = params.tencent_lbs_base_url;
    if (Object.keys(sa).length === 0) return null;   // 无有效配置项不下发
    return [{ sleepTimeMs: 0, type: "modifyAttrs", dnMsg: { server_attrs: sa } }];
}
rpc_script({
    device: { eui: "5a53012a000002e9", server_attrs: {} },
    params: { tencent_lbs_key: "TEST_KEY", tencent_lbs_base_url: "https://apis.map.qq.com" },
    logger: console,
    org_params: {}
})

RPC: [GW ACT] his get

id_name: mt_gw_his_get · ID: 120041209778302976

javascript
import {BufferTKL, RPCHelper, Utils} from "#tklHelper";
import {Buffer} from "buffer";

function rpc_script({ device, params, alarms, logger, org_params }) {
    logger.info("read history")
    if (params.start==undefined||params.end==undefined) return null
    if (params.start.length<=8 || params.end.length<=8) return null
    let whereInfo = `pgsql://creat >= '${params.start}' AND creat <= '${params.end}'`;
    let msg={
        type:"nsDnGwc",
        sleepTimeMs: 0,
        target:device.eui,
        dnMsg: {
            version: "2",
            EUI: device.eui,
            token: new Date().getTime(),
            action:"gwHis",
            mode:"rxdataRead",
            data: {
                whereInfo: whereInfo,
                gwToken: Date.now()%(0xFFFF),
                intervalms:10
            }
        }
    }
    return [msg]
}
if (process.env.TKL_SELF_TEST === "1") {
    rpc_script({
        device: { eui: "5a53012a000002e9" },
        params: {
            start: "2026-07-29 00:00:00",
            end: "2026-07-29 01:00:00"
        },
        logger: console,
        org_params: {}
    })
}

RPC: [GW ACT] locate

id_name: mt_gw_locate · ID: 119427355042017281

javascript
import {BufferTKL, RPCHelper, Utils} from "#tklHelper";
import {Buffer} from "buffer";

// 网关定位(输出位置信息到 shared_attrs)。来源优先级:① GPS(网关 GPS 模块直接给经纬度,最准);
// ② 取不到 GPS 时用 WiFi——把 wifiList 扫到的 AP(bssid/rssi)送腾讯位置服务 WiFi 定位换经纬度。
//   腾讯 API: POST {base_url}/ws/location/v1/network  body {key, device_id, wifiinfo:[{mac,rssi}]}
//   → {status:0, result:{location:{latitude,longitude,accuracy}, address, ad_info:{city}}}
//   文档 https://lbs.qq.com/service/webService/webServiceGuide/position/location
//   key / base_url 经 mt_gw_geo_set_config 写入 server_attrs(base_url 默认 https://apis.map.qq.com)。
//
// 数据来源(按「对应方式」就位即可,二者皆从 shared_attrs 读,缺则用入参兜底测试):
//   GPS  ← device.shared_attrs.gps = {lat, lon, accuracy?, valid?}(网关 GPS 经 DMS geoGet 上报落库后)
//   WiFi ← device.shared_attrs.wifi_list = [{bssid|mac, rssi, essid?}](wifiList 响应落库后)
async function rpc_script({ device, params, alarms, logger, org_params }) {
    const sa = device?.server_attrs || {};
    const sh = device?.shared_attrs || {};
    const num = (v) => (v == null || isNaN(Number(v)) ? null : Number(v));

    // ── ① GPS 优先 ──────────────────────────────────────────────
    const gps = params?.gps || sh.gps || null;
    const gLat = gps ? num(gps.lat != null ? gps.lat : gps.latitude) : null;
    const gLon = gps ? num(gps.lon != null ? gps.lon : gps.longitude) : null;
    const gpsValid = gps && (gps.valid !== false) && gLat != null && gLon != null && !(gLat === 0 && gLon === 0);
    if (gpsValid) {
        return [{ sleepTimeMs: 0, type: "modifyAttrs", dnMsg: { shared_attrs: {
            gw_latitude: gLat, gw_longitude: gLon,
            gw_accuracy: num(gps.accuracy) != null ? num(gps.accuracy) : 1,
            gw_address: null, gw_city: null,
            geo_source: "gps", geo_fetched_at: new Date().toISOString()
        } } }];
    }

    // ── ② WiFi 兜底(腾讯 WiFi 定位)─────────────────────────────
    const key = params?.tencent_lbs_key || sa.tencent_lbs_key;
    if (!key) { logger && logger.systemWarn && logger.systemWarn("mt_gw_locate: no GPS fix and missing tencent_lbs_key"); return null; }
    const baseUrl = (params?.tencent_lbs_base_url || sa.tencent_lbs_base_url || "https://apis.map.qq.com").replace(/\/+$/, "");

    let wifiinfo = Array.isArray(params?.wifiinfo) ? params.wifiinfo : null;
    if (!wifiinfo) {
        const wl = sh.wifi_list;
        if (Array.isArray(wl)) wifiinfo = wl.map((a) => ({ mac: a.mac || a.bssid, rssi: a.rssi }));
    }
    wifiinfo = (wifiinfo || []).map((a) => ({ mac: String(a.mac), rssi: num(a.rssi) })).filter((a) => a.mac && a.mac !== "null");
    if (!wifiinfo.length) { logger && logger.systemWarn && logger.systemWarn("mt_gw_locate: no GPS and no wifi list"); return null; }

    const body = { key, device_id: device.eui, wifiinfo };
    let resp;
    try {
        resp = await axios.post(`${baseUrl}/ws/location/v1/network`, body, { headers: { "Content-Type": "application/json" } });
    } catch (e) {
        logger && logger.systemError && logger.systemError(`mt_gw_locate tencent http error: ${e && e.message}`);
        return null; // 取数失败/超时 → 不写脏数据
    }
    const d = resp && resp.data;
    if (!d || d.status !== 0 || !d.result || !d.result.location) {
        logger && logger.systemWarn && logger.systemWarn(`mt_gw_locate tencent status=${d && d.status} msg=${d && d.message}`);
        return null;
    }
    const loc = d.result.location;
    const adi = d.result.ad_info || {};
    return [{ sleepTimeMs: 0, type: "modifyAttrs", dnMsg: { shared_attrs: {
        gw_latitude: num(loc.latitude), gw_longitude: num(loc.longitude), gw_accuracy: num(loc.accuracy),
        gw_address: d.result.address != null ? d.result.address : null,
        gw_city: adi.city != null ? adi.city : null,
        geo_source: "tencent_wifi", geo_fetched_at: new Date().toISOString()
    } } }];
}
// 本地测试:无 GPS、无 key、无 wifi → 早返回 null(不触达 axios,node 可直接跑通)
rpc_script({
    device: { eui: "5a53012a000002e9", server_attrs: {}, shared_attrs: {} },
    params: {},
    logger: console,
    org_params: {}
})

RPC: [GW ACT] wifi list get

id_name: mt_gw_wifi_list_get · ID: 119403377514672128

javascript
import {BufferTKL, RPCHelper, Utils} from "#tklHelper";
import {Buffer} from "buffer";

// 获取网关周围的 WiFi 列表(SSID/BSSID/RSSI)。
// 走 NMS 下行通道:平台据 type:"nmsDnGw" 把 dnMsg 原样发到
//   /v32/<tenant>/nms/dn/gw/<eui>   (NMS gateway downlink topic)
// 网关执行 `iwlist <wlan0> scan`,结果异步回到上行 topic
//   /v32/<tenant>/nms/up/gw/<eui>   并用 token 回显匹配本次请求。
// 注意:①网关需启用 WiFi 接口(boardInfo.Wifi0.Enable)否则 data 返回 "";
//       ②扫描底层超时 30s,响应可能延迟数秒到数十秒。
function rpc_script({ device, params, alarms, logger, org_params }) {
    logger.info("wifiList scan")
    let msg = {
        type: "nmsDnGw",
        sleepTimeMs: 0,
        target: device.eui,
        dnMsg: {
            version: "3.0",
            eui: device.eui,
            action: "wifiList",
            token: Date.now(),   // 任意 uint64,原样回显在响应里用于匹配请求
            mode: "read",        // wifiList 不校验 mode/data,填上更规范
            data: null
        }
    }
    return [msg]
}
rpc_script({
    device: { eui: "5a53012501030066" },
    params: {},
    logger: console,
    org_params: {}
})

Thing Model: [GW HEART]

id_name: mt_gw_heart · ID: 95121332907282437

javascript
import {Buffer} from "buffer";
import {BufferTKL, PayloadParser, RPCHelper, Utils} from "#tklHelper";

// 网关心跳物模型 [GW HEART] —— 平台 id_name: mt_gw_heart(与线上一致)。
// 解析网关经 NMS 上行的心跳帧(msg.data:deviceHandle/hwVersion/fwVersion/netType/
// validIP/nsConnect/nmsConnect/pmcConnect1),写入遥测 + shared_attrs。
// 心跳间隔 > 5min 时,顺带触发 mt_gw_his_get 补拉断档期间的历史数据。
// 配套 RPC(同一套网关模型):mt_gw_his_get(历史查询)、mt_gw_wifi_list_get(周边 WiFi 扫描)。
function payload_parser(device, msg, thingModelId, noticeAttrs, org_params) {
    if (msg?.data == undefined) return null;
    let data = msg.data;
    let pdata = {};
    let changed = false;
    let lasttime = device.shared_attrs?.updatems;
    let action = [];
    if (lasttime != undefined) {
        const diff = Date.now() - lasttime;
        if (diff > 300000) {
            action.push({
                method: "mt_gw_his_get",
                params: {
                    start: device.shared_attrs?.update_time,
                    end: new Date().toLocaleTimeString()
                },
            });
        }
    }
    pdata.updatems = Date.now();
    pdata.handle = data.deviceHandle;
    pdata.hwVersion = data.hwVersion;
    pdata.fwVersion = data.fwVersion;
    pdata.netType = data.netType;
    pdata.validIP = data.validIP;
    pdata.nsConnect = data.nsConnect;
    pdata.nmsConnect = data.nmsConnect;
    pdata.pmcConnect1 = data.pmcConnect1;
    if (pdata.handle !== device.shared_attrs?.handle) changed = true;
    if (pdata.hwVersion !== data.hwVersion) changed = true;
    if (pdata.fwVersion !== data.fwVersion) changed = true;
    if (pdata.netType !== data.netType) changed = true;
    if (pdata.validIP !== data.validIP) changed = true;
    if (pdata.nsConnect !== data.nsConnect) changed = true;
    if (pdata.nmsConnect !== data.nmsConnect) changed = true;
    if (pdata.pmcConnect1 !== data.pmcConnect1) changed = true;
    let result = {
        telemetry_data: changed ? pdata : null,
        server_attrs: null,
        shared_attrs: pdata,
        action: action,
    };
    // ── rpcHook (设备对接通用规则) ──
    if (typeof RPCHelper.buildRpcHookAction === "function") {
      const __a = RPCHelper.buildRpcHookAction({ device, msg, thingModelId, idName: "mt_gw_heart", name: "[GW HEART]", parsed: { telemetry_data: result.telemetry_data, server_attrs: result.server_attrs, shared_attrs: result.shared_attrs } });
      if (__a) { result.actions = result.actions || []; result.actions.push(__a); }
    }
    return result;
}
payload_parser(
    { eui: "5a53012a000002e9", shared_attrs: {} },
    {
        action: "heart",
        data: {
            deviceHandle: "GDI514-AGCN470-G-2",
            hwVersion: "3.1.3",
            fwVersion: "3.2.01",
            netType: "mteth0",
            validIP: "192.168.1.72",
            nsConnect: true,
            nmsConnect: true,
            pmcConnect1: true
        }
    },
    "95121332907282437",
    { telemetry_data: true }
)

Thing Model: [GW WIFI] scan result

id_name: mt_gw_wifi · ID: 119474792070803456

javascript
import {Buffer} from "buffer";
import {BufferTKL, PayloadParser, RPCHelper, Utils} from "#tklHelper";

// 网关周边 WiFi 扫描结果物模型 [GW WIFI] —— 平台 id_name: mt_gw_wifi。
// 数据来源:mt_gw_wifi_list_get(nmsDnGw) 触发网关 iwlist 扫描,回复落 /v32/<tenant>/nms/up/gw/<eui>;
// 平台 tkl-main 仅落库 heart 分支(不落 wifiList,BUG-048),故由转发器 mt-gw-wifilist-to-as 订阅 nms/up/gw、
// 把 AP 列表重封装成 AS 上行(/v32/<tenant>/as/up/data/<eui>, userdata.payload=base64(JSON {w:[...]})),
// 本 CUSTOMER 解析器解 AS 帧 → 遥测 wifi_ap_count(本帧标量,供触发器判别+观测) + shared_attrs.wifi_list(供腾讯定位) + wifi_scanned_at。
// AP 列表只进 shared_attrs(每次扫描整体刷新、不入历史趋势);遥测只留计数标量。
function payload_parser(device, msg, thingModelId, noticeAttrs, org_params) {
    let o;
    try {
        const raw = msg && msg.userdata && msg.userdata.payload;
        if (raw == null) return null;                       // 非 AS 注入帧(如 heart 经 NMS 上行)→ 让位
        o = typeof raw === "string" ? JSON.parse(Buffer.from(raw, "base64").toString("utf8")) : raw;
    } catch (e) {
        return null;                                        // 解析失败不写脏数据
    }
    if (!o || typeof o !== "object" || !Array.isArray(o.w)) return null;  // 非 wifi 帧(无 w 数组)→ 让位
    const num = (v) => (v == null || isNaN(Number(v)) ? null : Number(v));
    const aps = o.w
        .map((a) => ({
            bssid: a.bssid != null ? String(a.bssid) : null,
            rssi: num(a.rssi),
            essid: a.essid != null ? String(a.essid) : null
        }))
        .filter((a) => a.bssid);                            // 丢弃无 bssid 的项(腾讯定位以 bssid/mac 为键)
    if (!aps.length) return null;                           // 无有效 AP 不写脏数据
    let result = {
        telemetry_data: { wifi_ap_count: aps.length },
        server_attrs: null,
        shared_attrs: { wifi_list: aps, wifi_scanned_at: o.ts != null ? o.ts : new Date().toISOString() }
    };
    // ── rpcHook (设备对接通用规则) ──
    if (typeof RPCHelper.buildRpcHookAction === "function") {
      const __a = RPCHelper.buildRpcHookAction({ device, msg, thingModelId, idName: "mt_gw_wifi", name: "[GW WIFI] scan result", parsed: { telemetry_data: result.telemetry_data, server_attrs: result.server_attrs, shared_attrs: result.shared_attrs } });
      if (__a) { result.actions = result.actions || []; result.actions.push(__a); }
    }
    return result;
}
// 本地测试:转发器注入的 AS 帧(base64 JSON {w:[...]}) → 期望 telemetry wifi_ap_count + shared_attrs.wifi_list
payload_parser(
    { eui: "5a53012a000002e9", shared_attrs: {} },
    {
        userdata: {
            port: 2,
            payload: Buffer.from(JSON.stringify({
                w: [
                    { bssid: "34:ce:00:11:22:33", rssi: -45, essid: "GDI514-AGCN470" },
                    { bssid: "9c:21:6a:aa:bb:cc", rssi: -67, essid: "office-2.4G" }
                ]
            })).toString("base64")
        }
    },
    "119474792070803456",
    { telemetry_data: true }
)

Trigger: [GW LOCATE] on wifi scan

id_name: mt_gw_locate_on_wifi · ID: 119474793920491520

javascript
import {Utils} from "#tklHelper";

// 网关定位触发器 [GW LOCATE] —— 平台 id_name: mt_gw_locate_on_wifi。
// 绑定到 mt_gw_wifi 物模型:每次 wifiList 扫描结果经转发器回灌 AS、被 mt_gw_wifi 解析出
// telemetry wifi_ap_count(>0) + shared_attrs.wifi_list 后,本触发器派发 mt_gw_locate,
// 由其读 shared_attrs.wifi_list → 调腾讯 WiFi 定位 → 写经纬度到 shared_attrs。
function trigger_script(device, thingModelId) {
    const td = device?.telemetry_data?.[thingModelId];
    if (!td) return null;
    if (td.wifi_ap_count == null || td.wifi_ap_count <= 0) return null;   // 无有效扫描结果不定位(KB-30:业务字段判别,非_modelName)
    return {
        should_dispatch: true,                 // 相同 AP 计数的相邻扫描仍重新定位
        delayms: 1000,                         // 给 shared_attrs.wifi_list 落库留余量后再派发 locate
        abort_previous_timer: true,
        actions: [
            { method: "mt_gw_locate", params: { _eui: device.eui } }
        ]
    };
}
// 用例:扫到 2 个 AP → 期望派发 mt_gw_locate 带 _eui
console.log(JSON.stringify(trigger_script(
    { eui: "5a53012a000002e9", telemetry_data: { "119474792070803456": { wifi_ap_count: 2 } } },
    "119474792070803456"
)));
// 反例:无 AP(wifi_ap_count=0) → 期望 null
console.log(JSON.stringify(trigger_script(
    { eui: "5a53012a000002e9", telemetry_data: { "119474792070803456": { wifi_ap_count: 0 } } },
    "119474792070803456"
)));

物模型字段定义

物模型字段类型单位名称
mt_gw_hearthandlestringDevice Handle
mt_gw_hearthwVersionstringHardware Version
mt_gw_heartfwVersionstringFirmware Version
mt_gw_heartnetTypestringNetwork Type
mt_gw_heartvalidIPstringValid IP
mt_gw_heartnsConnectbooleanNS Connection
mt_gw_heartnmsConnectbooleanNMS Connection
mt_gw_heartpmcConnect1booleanPMC Connection
mt_gw_wifiwifi_ap_countnumberWiFi AP Count

RPC 参数定义

RPC参数类型说明默认值单位
mt_gw_geo_set_configtencent_lbs_keystringTencent LBS Key
mt_gw_geo_set_configtencent_lbs_base_urlstringTencent LBS Base URLhttps://apis.map.qq.com

触发器定义

触发器条件信号延迟表达式RPC 动作取消前序定时器
mt_gw_locate_on_wifi1000mt_gw_locatetrue

第三方平台数据订阅

MQTT Topic: /v32/{Organization Account}/tkl/up/telemetry/{eui}

json
{
  "eui": "6353012af10a9331",
  "active_time": "2026-07-13T08:35:48.000Z",
  "thingModelId": "Use the platform model ID from the model inventory",
  "thingModelIdName": "Use the current telemetry model id_name",
  "telemetry_data": {
    "snr": 13.5,
    "rssi": -51,
    "battery": 3.37,
    "handle": "sample-1",
    "hwVersion": "sample-2",
    "fwVersion": "sample-3",
    "netType": "sample-4",
    "validIP": "sample-5",
    "nsConnect": true,
    "nmsConnect": true,
    "pmcConnect1": true
  }
}

模板选择与验证

在 ThinkLink 平台按项目名称、业务代码或模型清单中的 id_name、ID 搜索并绑定模型。部署后使用真实上行帧核对字段、单位、RPC 和触发器行为。

补充说明

需求与决策依据:requirement/requirements.md