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.
| Item | Value |
|---|---|
| Project | TW-23114-DDZY2397 |
| Vendor | TW |
| Model | DDZY2397 |
| Business Code | 23114 |
| Integration Mode | edgebus-dtu |
| Protocol | dlt645 |
Model catalog
Search for these models in ThinkLink by name or ID.
| Model type | Name | id_name | Platform model ID |
|---|---|---|---|
| RPC | [TW ACT] switch | tw_act_ddzy2397_switch | 73787295391551493 |
| RPC | [TW SET] para | tw_set_ddzy2397_para | 73783574209236997 |
| Thing Model | [TW Emeter] ddzy2397 | tw_model_ddzy2397 | 73775594529099781 |
| Thing Model | [TW-23114-DDZY2397资产] 区域聚合 | tw_model_ddzy2397_asset_23114 | 119677189950959625 |
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
| Field | Name | Type | Unit | Address/Source | Description |
|---|---|---|---|---|---|
| addr645 | addr645 | hexLE6 | — | Source field: addr645 | |
| total_energy | total_energy | bcdLE4 | kWh | DLT645 DI3 0x00-0x00-0x00 | Source field: total_energy |
| active_energy | active_energy | bcdLE4 | kWh | DLT645 DI3 0x00-0x01-0x00 | Source field: active_energy |
| reverse_active_energy | reverse_active_energy | bcdLE4 | kWh | DLT645 DI3 | Source field: reverse_active_energy |
| voltage | voltage | bcdLE2 | V | DLT645 DI3 0x02-0x01-0x01 | Source field: voltage |
| current | current | bcdLE3 | A | DLT645 DI3 0x02-0x02-0x01 | Source field: current |
| power | power | bcdLE3 | kW | DLT645 DI3 0x02-0x03-0x01 | Source field: power |
| frequency | frequency | bcdLE2 | Hz | DLT645 DI3 0x02-0x08-0x01 | Source field: frequency |
| pf | pf | bcdLE2 | DLT645 DI3 | Source field: pf | |
| relay_status | relay_status | bitLE4-4 | DLT645 DI3 | Source field: relay_status |
Parameter Definitions
| Parameter | Name | Type | Default | Range | Unit | Description |
|---|---|---|---|---|---|---|
| app_70 | period_up | uint32LE | — | 0-4294967295 | s | Source parameter: app_70 |
| app_74 | period_read | uint32LE | — | 0-4294967295 | s | Source parameter: app_74 |
Requirements Evidence
The following key facts were extracted from the requirements source; line references support verification.
| Category | Source Fact | Source |
|---|---|---|
| Protocol/Data | - Description: DDZY2397 DL/T645-2007 (port 51) EdgeBus DTU supports | requirement/requirements.md:11 |
| Deployment | - Power: ac | requirement/requirements.md:17 |
| Protocol/Data | - Protocol: dlt645 | requirement/requirements.md:19 |
| Protocol/Data | - Interface: RS-485 | requirement/requirements.md:20 |
| Protocol/Data | - Baud rate: 2400 | requirement/requirements.md:21 |
| Protocol/Data | Field Name Type Unit Address/Source Description | requirement/requirements.md:25 |
| Deployment | power power bcdLE3 kW DLT645 DI3 0x02-0x03-0x01 Source field: power | requirement/requirements.md:33 |
| Deployment | tw_model_ddzy2397 pf number Power Factor | requirement/requirements.md:42 |
| Deployment | tw_model_ddzy2397 power number kw Power | requirement/requirements.md:45 |
| Protocol/Data | tw_model_ddzy2397 addr645 string DL/T 645 Address | requirement/requirements.md:46 |
| Deployment | tw_model_ddzy2397_asset_23114 zone_pf_avg number Zone Power Factor | requirement/requirements.md:54 |
| Deployment | tw_model_ddzy2397_asset_23114 zone_power_sum number kw Zone Power | requirement/requirements.md:57 |
| Deployment | 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:101 |
| Protocol/Data | - No unresolved identity or protocol facts were detected from the descriptor. | requirement/requirements.md:105 |
| Deployment | Every 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
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
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
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
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 Model | Field | Type | Unit | Name |
|---|---|---|---|---|
| tw_model_ddzy2397 | pf | number | Power Factor | |
| tw_model_ddzy2397 | snr | number | dB | SNR |
| tw_model_ddzy2397 | rssi | number | dBm | RSSI |
| tw_model_ddzy2397 | power | number | kw | Power |
| tw_model_ddzy2397 | addr645 | string | DL/T 645 Address | |
| tw_model_ddzy2397 | current | number | A | Current |
| tw_model_ddzy2397 | voltage | number | V | Voltage |
| tw_model_ddzy2397 | frequency | number | Hz | Grid Frequency |
| tw_model_ddzy2397 | relay_status | string | Relay Status | |
| tw_model_ddzy2397 | total_energy | number | kwh | Total Energy |
| tw_model_ddzy2397 | active_energy | number | kwh | Active Energy |
| tw_model_ddzy2397 | reverse_active_energy | number | kwh | Reverse Active Energy |
| tw_model_ddzy2397_asset_23114 | zone_pf_avg | number | Zone Power Factor | |
| tw_model_ddzy2397_asset_23114 | min_snr | number | dB | Zone SNR |
| tw_model_ddzy2397_asset_23114 | min_rssi | number | dBm | Zone RSSI |
| tw_model_ddzy2397_asset_23114 | zone_power_sum | number | kw | Zone Power |
| tw_model_ddzy2397_asset_23114 | zone_current_sum | number | A | Zone Current |
| tw_model_ddzy2397_asset_23114 | zone_voltage_avg | number | V | Zone Voltage |
| tw_model_ddzy2397_asset_23114 | zone_frequency_avg | number | Hz | Zone Grid Frequency |
| tw_model_ddzy2397_asset_23114 | zone_total_energy_sum | number | kwh | Zone Total Energy |
| tw_model_ddzy2397_asset_23114 | zone_active_energy_sum | number | kwh | Zone Active Energy |
| tw_model_ddzy2397_asset_23114 | zone_reverse_active_energy_sum | number | kwh | Zone Reverse Active Energy |
| tw_model_ddzy2397_asset_23114 | online_count | number | Zone Online Count |
RPC Parameter Definitions
| RPC | Parameter | Type | Description | Default | Unit |
|---|---|---|---|---|---|
| tw_act_ddzy2397_switch | switch | boolean | Switch (on/off) | false | |
| tw_set_ddzy2397_para | period | number | Upload/Collect Period | 15 | min |
Trigger Definitions
No project-specific triggers are defined.
Third-Party Platform Subscription
MQTT Topic: /v32/{Organization Account}/tkl/up/telemetry/{eui}
{
"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.