Skip to content

23114-DDZY2397 对接文档

传感器简介

DDZY2397 单相费控智能电能表,DL/T645-2007 协议透传模式(port 51)通过 EdgeBus DTU 接入,支持远程拉合闸控制和参数设置

项目
项目名称TW-23114-DDZY2397
厂商天威 (TianWei)
型号DDZY2397
业务代码23114
对接模式edgebus-dtu
协议dlt645

模型清单

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

模型类型名称id_name平台模型 ID
RPC[TW ACT] switchtw_act_ddzy2397_switch73787295391551493
RPC[TW SET] paratw_set_ddzy2397_para73783574209236997
Thing Model[TW Emeter] ddzy2397tw_model_ddzy239773775594529099781
Thing Model[TW-23114-DDZY2397资产] 区域聚合tw_model_ddzy2397_asset_23114119677189950959625

产品特点与适用范围

本项目面向“DL/T645-2007 单相费控智能电能表(透传)”场景。能力边界、字段和可部署模型均以当前 descriptor、requirements 与模型源文件为准。

部署与采集信息

| DTU | Not applicable | | 供电 | ac | | LoRaWAN Class | C | | 接口 | RS-485 | | 波特率 | 2400 |

遥测与寄存器定义

字段名称类型单位地址/来源说明
addr645表地址hexLE6DLT645 地址域6字节hexLE电表地址
total_energy组合有功总电能bcdLE4kWhDLT645 DI3 0x00-0x00-0x00组合有功总电能
active_energy正向有功总电能bcdLE4kWhDLT645 DI3 0x00-0x01-0x00正向有功总电能
reverse_active_energy反向有功总电能bcdLE4kWhDLT645 DI3反向有功总电能
voltage当前电压bcdLE2VDLT645 DI3 0x02-0x01-0x01当前电压
current当前电流bcdLE3ADLT645 DI3 0x02-0x02-0x01当前电流
power有功功率bcdLE3kWDLT645 DI3 0x02-0x03-0x01有功功率
frequency电网频率bcdLE2HzDLT645 DI3 0x02-0x08-0x01电网频率
pf功率因数bcdLE2DLT645 DI3功率因数
relay_status继电器状态bitLE4-4DLT645 DI30:通, 1:断

参数定义

参数名称类型默认值范围单位说明
app_70period_upuint32LE0-4294967295s
app_74period_readuint32LE0-4294967295s

Requirements 证据

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

类别来源事实来源
协议/数据- Description: DDZY2397 单相费控智能电能表,DL/T645-2007 协议透传模式(port 51)通过 EdgeBus DTU 接入,支持远程拉合闸控制和参数设置requirement/requirements.md:11
部署- Power: acrequirement/requirements.md:17
协议/数据- Protocol: dlt645requirement/requirements.md:19
协议/数据- Interface: RS-485requirement/requirements.md:20
协议/数据- Baud rate: 2400requirement/requirements.md:21
协议/数据| Field | Name | Type | Unit | Address/Source | Description |requirement/requirements.md:25
部署| power | power | bcdLE3 | kW | DLT645 DI3 0x02-0x03-0x01 | Source field: power |requirement/requirements.md:33
部署| tw_model_ddzy2397 | pf | number | | Power Factor |requirement/requirements.md:42
部署| tw_model_ddzy2397 | power | number | kw | Power |requirement/requirements.md:45
协议/数据| tw_model_ddzy2397 | addr645 | string | | DL/T 645 Address |requirement/requirements.md:46
部署| tw_model_ddzy2397_asset_23114 | zone_pf_avg | number | | Zone Power Factor |requirement/requirements.md:54
部署| tw_model_ddzy2397_asset_23114 | zone_power_sum | number | kw | Zone Power |requirement/requirements.md:57
部署Validate model IDs, identifiers, payload fields, RPC behavior, triggers, and templates against a real device or platform test asset before production deployment.requirement/requirements.md:99
协议/数据- No unresolved identity or protocol facts were detected from the descriptor.requirement/requirements.md:103
部署Every item above must be checked during deployment. No missing value is promoted to a confirmed fact.requirement/requirements.md:105

物模型与 RPC 代码

RPC: [TW ACT] switch

id_name: tw_act_ddzy2397_switch · ID: 73787295391551493

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

function rpc_script({ device, params, alarms, logger, org_params }) {
let dnBuffer
    if (params.switch)    {
        dnBuffer=Buffer.from("FEFEFEFE68010000252001681C1035333333636363634F333434343434CCEF16","hex")
    }else {
        dnBuffer=Buffer.from("FEFEFEFE68010000252001681C1035333333636363634D333434343434CCED16","hex")
    }
    if (device.server_attrs?.addr645==undefined) return null
    let addrBuffer=Buffer.from(device.server_attrs.addr645,"hex");
    addrBuffer.copy(dnBuffer,5);
    let crcSum=Utils.crcSum(dnBuffer.subarray(4,-2),dnBuffer.length-6);
    dnBuffer.writeUInt8(crcSum&0xFF,dnBuffer.length-2)
    //console.log(dnBuffer.toString("hex"))
    let msg=RPCHelper.makeMSG({
        msgType:Utils.msgType.user,
        port:2,
        device:device,
        dnBuffer:dnBuffer,
        sleepTime:0,
    })
    return [msg]
}
rpc_script({
    device: { server_attrs: {}, shared_attrs: {} },
    params: {},
    alarms: [],
    logger: console,
    org_params: {}
})

RPC: [TW SET] para

id_name: tw_set_ddzy2397_para · ID: 73783574209236997

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

function rpc_script({ device, params, alarms, logger, org_params }) {
let dnBuffer=Buffer.from("FEFEFEFE68AAAAAAAAAAAA68140E3534A33735333333636363636F33F916","hex")
    dnBuffer.writeUInt16LE(params.period, dnBuffer.length-4);
    dnBuffer[dnBuffer.length-4]+=0x33
    dnBuffer[dnBuffer.length-3]+=0x33
    if (device.server_attrs?.addr645==undefined) return null
    let addrBuffer=Buffer.from(device.server_attrs.addr645,"hex");
    addrBuffer.copy(dnBuffer,5);
    let crcSum=Utils.crcSum(dnBuffer.subarray(4,-2),dnBuffer.length-6);
    dnBuffer.writeUInt8(crcSum&0xFF,dnBuffer.length-2)
    //console.log(dnBuffer.toString("hex"))
    let msg=RPCHelper.makeMSG({
        msgType:Utils.msgType.user,
        port:2,
        device:device,
        dnBuffer:dnBuffer,
        sleepTime:0,
    })
    return [msg]
}
rpc_script({
    device: { server_attrs: {}, shared_attrs: {} },
    params: {},
    alarms: [],
    logger: console,
    org_params: {}
})

Thing Model: [TW Emeter] ddzy2397

id_name: tw_model_ddzy2397 · ID: 73775594529099781

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

function payload_parser(device, msg, thingModelId, noticeAttrs, org_params) {
let frameInfo = {
        port: -1,
        dataLen: -1,
        rssi: true,
     //   subDevice: { index: 5, type: "bcdle6" }, // 对应 6 字节表号地址域
        tagList: [
            { index: 0, tag: 0xFE }, // 前导码校验
            { index: 4, tag: 0x68 }, // 帧起始符
            { index: 11, tag: 0x68 }, // 帧起始符
            { index: 12, tag: 0x91 },  // 控制码
            { index: 13, tag: 0x1D }  // 控制码
        ]
    };

    let appInfo = [
        { name: "addr645", field_name: "addr645", unit: "", index: 5, type: "hexle6"},
        { name: "组合有功总电能", field_name: "total_energy", unit: "kWh", index: 18, type: "bcdle4", coefficient: 0.01, decimal: 2 },
        { name: "正向有功总电能", field_name: "active_energy", unit: "kWh", index: 22, type: "bcdle4", coefficient: 0.01, decimal: 2 },
        { name: "反向有功总电能", field_name: "reverse_active_energy", unit: "kWh", index: 26, type: "bcdle4", coefficient: 0.01, decimal: 2 },
        { name: "当前电压", field_name: "voltage", unit: "V", index: 30, type: "bcdle2", coefficient: 0.1, decimal: 1 },
        { name: "当前电流", field_name: "current", unit: "A", index: 32, type: "bcdle3", coefficient: 0.001, decimal: 3 },
        { name: "有功功率", field_name: "power", unit: "kW", index: 35, type: "bcdle3", coefficient: 0.0001, decimal: 4 },
        { name: "电网频率", field_name: "frequency", unit: "Hz", index: 38, type: "bcdle2", coefficient: 0.01, decimal: 2 },
        { name: "功率因数", field_name: "pf", unit: "", index: 40, type: "bcdle2", coefficient: 0.001, decimal: 3 },
        {
            name: "继电器状态", field_name: "relay_status", unit: "", index: 42, type: "bitle4-4",
            options: {
                0:"通",
                1:"断"
            }
        }
    ];
    let payBuffer=Buffer.from(msg.userdata.payload,"base64")
    appInfo.forEach(para => {
        let typeInfo=BufferTKL.getTypeInfo(para.type)
        if (typeInfo.type.toLowerCase() !== "bcdle" && typeInfo.type.toLowerCase() !== "bcdbe"&&typeInfo.type.toLowerCase() !== "bitle") return
        for (let i = 0; i < typeInfo.dataLen; i++) {
            payBuffer[i+para.index] = payBuffer[i+para.index] - 0x33;
        }

    })
    msg.userdata.payload = payBuffer.toString("base64");
    let payParser = new PayloadParser({
        device: device,
        msg: msg,
        frameInfo: frameInfo,
        appInfo: appInfo
    });
    const tdata = payParser.telemetry();
    let result = {
        sub_device: tdata?.addr === undefined ? null : tdata.addr,
        telemetry_data: tdata,
        server_attrs: {
            addr645:tdata?.addr645
        },
        shared_attrs: { class_mode: msg?.userdata?.class },
    };
    if (result.telemetry_data && typeof result.telemetry_data === "object") result.telemetry_data.adr = msg?.userdata?.adr ?? null;
    // ── rpcHook (设备对接通用规则) ──
    if (typeof RPCHelper.buildRpcHookAction === "function") {
      const __a = RPCHelper.buildRpcHookAction({ device, msg, thingModelId, idName: "tw_model_ddzy2397", name: "[TW Emeter] ddzy2397", 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;
}
console.log(payload_parser(
    { telemetry_data: {} },
    { userdata: { port: 11, payload: "" } },
    "1",
    { telemetry_data: true },
    {}
))

Thing Model: [TW-23114-DDZY2397资产] 区域聚合

id_name: tw_model_ddzy2397_asset_23114 · ID: 119677189950959625

javascript
import {PayloadParser, RPCHelper} from '#tklHelper';

// 资产(VIRTUAL)聚合物模型 — 通用 agg_spec 驱动聚合器,见 docs/references/workflow/asset-aggregation-model.md
// device=资产, msg=源设备快照, thingModelId=本物模型ID, noticeAttrs=变更标记。
// 聚合规格在资产 server_attrs.agg_spec:zone_pf_avg=空间均值(pf);min_snr=取最小(snr);min_rssi=取最小(rssi);zone_power_sum=时点累加(power);zone_current_sum=时点累加(current);zone_voltage_avg=空间均值(voltage);zone_frequency_avg=空间均值(frequency);zone_total_energy_sum=谨慎累加(total_energy);zone_active_energy_sum=谨慎累加(active_energy);zone_reverse_active_energy_sum=谨慎累加(reverse_active_energy);online_count=在线数(—)
function payload_parser(device, msg, thingModelId, noticeAttrs) {
    if (!noticeAttrs || !noticeAttrs.telemetry_data) {
        return { telemetry_data: null, server_attrs: null, shared_attrs: null };
    }
    const sa      = device?.server_attrs || {};
    const spec    = sa.agg_spec || {};
    const members = { ...(sa.members || {}) };
    const staleMs = sa.stale_ms != null ? sa.stale_ms : 600000;
    const now     = (typeof Date !== "undefined" && Date.now) ? Date.now() : null;

    // 合并源设备各物模型的最新快照为一个扁平对象
    const srcTd = {};
    const all = msg?.telemetry_data || {};
    for (const k in all) { Object.assign(srcTd, all[k]); }

    // 收集 spec 引用到的全部源字段,把本帧带到的值并进该成员快照
    const want = {};
    for (const out in spec) { if (spec[out].src) want[spec[out].src] = true; }
    const got = {}; let touched = false;
    for (const f in want) { if (srcTd[f] !== undefined && srcTd[f] !== null) { got[f] = srcTd[f]; touched = true; } }
    if (touched && msg && msg.eui) {
        const prev = (members[msg.eui] && members[msg.eui].v) || {};
        const seq  = (members[msg.eui] && members[msg.eui].ts) || 0;
        members[msg.eui] = { v: Object.assign({}, prev, got), ts: now != null ? now : seq + 1 };
    }

    const euis = Object.keys(members);
    const fresh = euis.filter(function (e) {
        if (now == null || staleMs <= 0) return true;
        const ts = members[e].ts;
        return ts != null && ts <= now && (now - ts) <= staleMs;
    });
    const num = function (x) { return (x != null && !isNaN(x)) ? Number(x) : null; };

    const out = {};
    for (const field in spec) {
        const op = spec[field].op, src = spec[field].src;
        if (op === "count_online") { out[field] = fresh.length; continue; }
        const pool = (op === "sum_cumul") ? euis : fresh;
        const vals = pool.map(function (e) { return num(members[e].v && members[e].v[src]); }).filter(function (x) { return x != null; });
        if (op === "avg")        out[field] = vals.length ? Number((vals.reduce(function (a, b) { return a + b; }, 0) / vals.length).toFixed(2)) : null;
        else if (op === "sum_snap" || op === "sum_cumul") out[field] = vals.length ? Number(vals.reduce(function (a, b) { return a + b; }, 0).toFixed(3)) : null;
        else if (op === "max")   out[field] = vals.length ? Math.max.apply(null, vals) : null;
        else if (op === "min")   out[field] = vals.length ? Math.min.apply(null, vals) : null;
        else if (op === "or")    out[field] = pool.some(function (e) { const x = members[e].v && members[e].v[src]; return x != null && x != 0; }) ? 1 : 0;
        else if (op === "count_on") out[field] = pool.filter(function (e) { const x = members[e].v && members[e].v[src]; return x != null && x != 0; }).length;
        else out[field] = null;
    }
    let result = { telemetry_data: out, server_attrs: { members: members }, shared_attrs: null };
    // ── rpcHook (设备对接通用规则) ──
    if (typeof RPCHelper.buildRpcHookAction === "function") {
      const __a = RPCHelper.buildRpcHookAction({ device, msg, thingModelId, idName: "tw_model_ddzy2397_asset_23114", name: "[TW-23114-DDZY2397资产] 区域聚合", 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;
}

// --- local debug ---(两台成员先后上报;平台会把返回的 members 持久化回 device,这里手动模拟)
const agg_spec = {
  "zone_pf_avg": {
    "op": "avg",
    "src": "pf"
  },
  "min_snr": {
    "op": "min",
    "src": "snr"
  },
  "min_rssi": {
    "op": "min",
    "src": "rssi"
  },
  "zone_power_sum": {
    "op": "sum_snap",
    "src": "power"
  },
  "zone_current_sum": {
    "op": "sum_snap",
    "src": "current"
  },
  "zone_voltage_avg": {
    "op": "avg",
    "src": "voltage"
  },
  "zone_frequency_avg": {
    "op": "avg",
    "src": "frequency"
  },
  "zone_total_energy_sum": {
    "op": "sum_cumul",
    "src": "total_energy"
  },
  "zone_active_energy_sum": {
    "op": "sum_cumul",
    "src": "active_energy"
  },
  "zone_reverse_active_energy_sum": {
    "op": "sum_cumul",
    "src": "reverse_active_energy"
  },
  "online_count": {
    "op": "count_online",
    "src": ""
  }
};
const device = { name: "TW-23114-DDZY2397资产-1", server_attrs: { agg_spec, stale_ms: 1800000, members: {} } };
const r1 = payload_parser(device, { eui: "MEMBER_A", telemetry_data: { m1: {"pf":1,"snr":1,"rssi":1,"power":1,"current":1,"voltage":1,"frequency":1,"total_energy":1,"active_energy":1,"reverse_active_energy":1} } }, "tmid", { telemetry_data: true });
device.server_attrs.members = r1.server_attrs.members;            // 平台持久化
console.log("成员A上报后:", JSON.stringify(r1.telemetry_data));
const r2 = payload_parser(device, { eui: "MEMBER_B", telemetry_data: { m1: {"pf":2,"snr":2,"rssi":2,"power":2,"current":2,"voltage":2,"frequency":2,"total_energy":2,"active_energy":2,"reverse_active_energy":2} } }, "tmid", { telemetry_data: true });
device.server_attrs.members = r2.server_attrs.members;
console.log("成员B上报后(2台聚合):", JSON.stringify(r2.telemetry_data));
void PayloadParser;

物模型字段定义

物模型字段类型单位名称
tw_model_ddzy2397pfnumberPower Factor
tw_model_ddzy2397snrnumberdBSNR
tw_model_ddzy2397rssinumberdBmRSSI
tw_model_ddzy2397powernumberkwPower
tw_model_ddzy2397addr645stringDL/T 645 Address
tw_model_ddzy2397currentnumberACurrent
tw_model_ddzy2397voltagenumberVVoltage
tw_model_ddzy2397frequencynumberHzGrid Frequency
tw_model_ddzy2397relay_statusstringRelay Status
tw_model_ddzy2397total_energynumberkwhTotal Energy
tw_model_ddzy2397active_energynumberkwhActive Energy
tw_model_ddzy2397reverse_active_energynumberkwhReverse Active Energy
tw_model_ddzy2397_asset_23114zone_pf_avgnumberZone Power Factor
tw_model_ddzy2397_asset_23114min_snrnumberdBZone SNR
tw_model_ddzy2397_asset_23114min_rssinumberdBmZone RSSI
tw_model_ddzy2397_asset_23114zone_power_sumnumberkwZone Power
tw_model_ddzy2397_asset_23114zone_current_sumnumberAZone Current
tw_model_ddzy2397_asset_23114zone_voltage_avgnumberVZone Voltage
tw_model_ddzy2397_asset_23114zone_frequency_avgnumberHzZone Grid Frequency
tw_model_ddzy2397_asset_23114zone_total_energy_sumnumberkwhZone Total Energy
tw_model_ddzy2397_asset_23114zone_active_energy_sumnumberkwhZone Active Energy
tw_model_ddzy2397_asset_23114zone_reverse_active_energy_sumnumberkwhZone Reverse Active Energy
tw_model_ddzy2397_asset_23114online_countnumberZone Online Count

RPC 参数定义

RPC参数类型说明默认值单位
tw_act_ddzy2397_switchswitchbooleanSwitch (on/off)false
tw_set_ddzy2397_paraperiodnumberUpload/Collect Period15min

触发器定义

无项目专用触发器。

第三方平台数据订阅

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,
    "addr645": "sample-1",
    "total_energy": 2.5,
    "active_energy": 3.75,
    "reverse_active_energy": 5,
    "voltage": 6.25,
    "current": 7.5,
    "power": 8.75,
    "frequency": 10
  }
}

模板选择与验证

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

补充说明

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