Skip to content

23114-DDZY2397 Integration Guide

Sensor Overview

DDZY2397 is integrated with ThinkLink through dlt645 in edgebus-dtu mode. This guide documents the current deployable artifacts and their source-traceable versions.

ItemValue
ProjectTW-23114-DDZY2397
VendorTW
ModelDDZY2397
Business Code23114
Integration Modeedgebus-dtu
Protocoldlt645

Model catalog

Search for these models in ThinkLink by name or ID.

Model typeNameid_namePlatform model 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

Features and Application Scope

This project covers DDZY2397 through dlt645. Its capability boundary, fields, and deployable models follow the current descriptor, requirements, and model sources.

Deployment and Data Acquisition

| DTU | Not applicable | | Power | ac | | LoRaWAN Class | C | | Interface | RS-485 | | Baud Rate | 2400 |

Telemetry and Register Definitions

FieldNameTypeUnitAddress/SourceDescription
addr645addr645hexLE6Source field: addr645
total_energytotal_energybcdLE4kWhDLT645 DI3 0x00-0x00-0x00Source field: total_energy
active_energyactive_energybcdLE4kWhDLT645 DI3 0x00-0x01-0x00Source field: active_energy
reverse_active_energyreverse_active_energybcdLE4kWhDLT645 DI3Source field: reverse_active_energy
voltagevoltagebcdLE2VDLT645 DI3 0x02-0x01-0x01Source field: voltage
currentcurrentbcdLE3ADLT645 DI3 0x02-0x02-0x01Source field: current
powerpowerbcdLE3kWDLT645 DI3 0x02-0x03-0x01Source field: power
frequencyfrequencybcdLE2HzDLT645 DI3 0x02-0x08-0x01Source field: frequency
pfpfbcdLE2DLT645 DI3Source field: pf
relay_statusrelay_statusbitLE4-4DLT645 DI3Source field: relay_status

Parameter Definitions

ParameterNameTypeDefaultRangeUnitDescription
app_70period_upuint32LE0-4294967295sSource parameter: app_70
app_74period_readuint32LE0-4294967295sSource parameter: app_74

Requirements Evidence

The following key facts were extracted from the requirements source; line references support verification.

CategorySource FactSource
Protocol/Data- Description: DDZY2397 DL/T645-2007 (port 51) EdgeBus DTU supportsrequirement/requirements.md:11
Deployment- Power: acrequirement/requirements.md:17
Protocol/Data- Protocol: dlt645requirement/requirements.md:19
Protocol/Data- Interface: RS-485requirement/requirements.md:20
Protocol/Data- Baud rate: 2400requirement/requirements.md:21
Protocol/DataField Name Type Unit Address/Source Descriptionrequirement/requirements.md:25
Deploymentpower power bcdLE3 kW DLT645 DI3 0x02-0x03-0x01 Source field: powerrequirement/requirements.md:33
Deploymenttw_model_ddzy2397 pf number Power Factorrequirement/requirements.md:42
Deploymenttw_model_ddzy2397 power number kw Powerrequirement/requirements.md:45
Protocol/Datatw_model_ddzy2397 addr645 string DL/T 645 Addressrequirement/requirements.md:46
Deploymenttw_model_ddzy2397_asset_23114 zone_pf_avg number Zone Power Factorrequirement/requirements.md:54
Deploymenttw_model_ddzy2397_asset_23114 zone_power_sum number kw Zone Powerrequirement/requirements.md:57
DeploymentValidate model IDs, identifiers, payload fields, RPC behavior, triggers, and templates against a real device or platform test asset before production deployment.requirement/requirements.md:101
Protocol/Data- No unresolved identity or protocol facts were detected from the descriptor.requirement/requirements.md:105
DeploymentEvery item above must be checked during deployment. No missing value is promoted to a confirmed fact.requirement/requirements.md:107

Thing Model and RPC Code

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_model_ddzy2397_asset_23114

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;

Thing Model Field Definitions

Thing ModelFieldTypeUnitName
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 Parameter Definitions

RPCParameterTypeDescriptionDefaultUnit
tw_act_ddzy2397_switchswitchbooleanSwitch (on/off)false
tw_set_ddzy2397_paraperiodnumberUpload/Collect Period15min

Trigger Definitions

No project-specific triggers are defined.

Third-Party Platform Subscription

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
  }
}

Template Selection and Verification

Search ThinkLink by project name, business code, or the id_name and ID values in the model inventory. After deployment, use real uplinks to verify fields, units, RPCs, and trigger behavior.

Supplementary Notes

Requirements and decisions: requirement/requirements.md.