1. Introduction
The Schneider Electric EasyLogic DM2500 series is a high-accuracy three-phase multi-function power meter available in two variants: DM2510 (3CT, three current transformer inputs) and DM2511 (4CT, four current transformer inputs). The meter communicates over RS-485 using the Modbus RTU protocol and provides 13 key electrical parameters including three-phase voltage, three-phase current, power factor, forward active energy, current/voltage unbalance, and frequency.
This solution connects the Schneider EasyLogic DM2500 series to the ThinkLink platform using a KC11 LoRaWAN data acquisition device. KC11 connects to the meter's RS-485 interface and uses its built-in EdgeBus script to collect Modbus RTU data, encapsulate it, and report it over LoRaWAN — enabling wireless integration of switchboard power data into ThinkLink.
2. Product Features
- Supports Schneider EasyLogic DM2500 series (DM2510 / DM2511) data acquisition
- Supports RS-485 / Modbus RTU interface
- Enables wireless LoRaWAN access for power meter data via KC11
- Collects 13 electrical parameters in a single pass: voltage, current, energy, power factor, frequency, and unbalance
- Supports Modbus slave address configuration for multi-meter bus deployment
- Single acquisition/upload period parameter controls both intervals (upPeriodIndex = periodIndex = app_70)
- Class 0.5S accuracy energy metering (compliant with IEC 62053-22)
- Supports ThinkLink thing model parsing and RPC-based parameter configuration
- DM2510 and DM2511 share one EB codebase and one thing model
3. Application Scope
This solution applies to scenarios where DM2500 series power meters need to be connected to ThinkLink / LoRaWAN systems, including:
- Industrial facility switchboard energy monitoring and zone-level consumption analysis
- Commercial building distribution room remote meter reading, replacing manual inspection
- Energy quality analysis for enterprises (unbalance, power factor monitoring)
- Multi-tenant independent metering and electricity cost allocation
- Bidirectional metering with distributed solar (4-quadrant energy support)
- Wireless retrofit of existing power meters
- MQTT data integration with PLC / SCADA / energy management systems
4. Data Acquisition Device Information
4.1 Hardware Information
| Item | Description |
|---|---|
| Data acquisition device model | KC11 |
| Communication interface | RS-485 |
| Power supply | AC 85–270V mains (DIN rail mount) |
| Communication method | LoRaWAN Class C (always-on) |
| EdgeBus support | Supported |
| Adapted protocol | Modbus RTU |
| Business code | 23102 |
| Template name | DM2500 |
4.2 Wiring Information
Power and Communication Interfaces
| Interface | Description |
|---|---|
| Power input | KC11 is powered by AC 85–270V; can share the same 220V supply as the DM2500 auxiliary power |
| RS-485 A | Connect to the RS+ terminal on the DM2500 rear panel |
| RS-485 B | Connect to the RS- terminal on the DM2500 rear panel |
| 485C/GND | Optional connection to the C (common reference) terminal on DM2500 |
| LoRaWAN | KC11 reports collected data to the gateway / ThinkLink over LoRaWAN |
Sensor Interface
The DM2500 connects to KC11 via the RS-485 bus. Rear terminal reference:
| Terminal | Signal | Connect to |
|---|---|---|
| RS+ | RS-485 data + | KC11 485A |
| RS- | RS-485 data - | KC11 485B |
| C | Common reference | KC11 485C/GND (optional) |
| L+/N/- | Auxiliary power | AC 220V from switchboard |
Wiring notes:
- A 120Ω termination resistor is required at the far end of the RS-485 bus (KC11 provides a 120Ω jumper to 485A)
- Use shielded twisted-pair cable; connect shield at one end only
- Up to 32 DM2500 meters can share one RS-485 bus, differentiated by Modbus slave address
5. Data Acquisition
In this solution, the following data items are read via Modbus RTU function code FC03 (Read Holding Registers):
| No. | Data Item | Field Name | Unit | Register (1-based) |
|---|---|---|---|---|
| 1 | Forward active energy | energy_import | kWh | 2700–2701 |
| 2 | Phase A current | current_a | A | 3000–3001 |
| 3 | Phase B current | current_b | A | 3002–3003 |
| 4 | Phase C current | current_c | A | 3004–3005 |
| 5 | Average three-phase current | current_avg | A | 3010–3011 |
| 6 | Current unbalance | current_unbalance | % | 3012–3013 |
| 7 | Phase A voltage (L-N) | voltage_an | V | 3028–3029 |
| 8 | Phase B voltage (L-N) | voltage_bn | V | 3030–3031 |
| 9 | Phase C voltage (L-N) | voltage_cn | V | 3032–3033 |
| 10 | Average L-N voltage | voltage_ln_avg | V | 3036–3037 |
| 11 | Voltage unbalance | voltage_unbalance | % | 3038–3039 |
| 12 | Total power factor | power_factor_total | — | 3084–3085 |
| 13 | Grid frequency | frequency | Hz | 3110–3111 |
5.1 Register Definition
DM2500 uses 1-based register numbering (Schneider convention). The Modbus PDU address = register number − 1.
All data fields are FLOAT32 Big-Endian (high word first, 4 bytes, IEEE 754).
| Data Item | Register (1-based) | PDU Address | Data Type | Query Group |
|---|---|---|---|---|
| Forward active energy | 2700–2701 | 0x0A8B | FLOAT32 BE | Group 1 (standalone) |
| Phase A current | 3000–3001 | 0x0BB7 | FLOAT32 BE | Group 2 (with B/C) |
| Phase B current | 3002–3003 | 0x0BB9 | FLOAT32 BE | Group 2 |
| Phase C current | 3004–3005 | 0x0BBB | FLOAT32 BE | Group 2 |
| Average three-phase current | 3010–3011 | 0x0BC1 | FLOAT32 BE | Group 3 (with unbalance) |
| Current unbalance | 3012–3013 | 0x0BC3 | FLOAT32 BE | Group 3 |
| Phase A voltage (L-N) | 3028–3029 | 0x0BD3 | FLOAT32 BE | Group 4 (with B/C) |
| Phase B voltage (L-N) | 3030–3031 | 0x0BD5 | FLOAT32 BE | Group 4 |
| Phase C voltage (L-N) | 3032–3033 | 0x0BD7 | FLOAT32 BE | Group 4 |
| Average L-N voltage | 3036–3037 | 0x0BDB | FLOAT32 BE | Group 5 (with unbalance) |
| Voltage unbalance | 3038–3039 | 0x0BDD | FLOAT32 BE | Group 5 |
| Total power factor | 3084–3085 | 0x0C0B | FLOAT32 BE | Group 6 (standalone) |
| Grid frequency | 3110–3111 | 0x0C25 | FLOAT32 BE | Group 7 (standalone) |
5.2 Status Bit Definition
The thing model parsing logic includes an acquisition timeout check:
if ((tdata?.status & 0x02) === 0x02) {
// time out, just update the status.
}| Status Bit | Meaning |
|---|---|
| status & 0x02 | Acquisition timeout |
When an acquisition timeout occurs, the thing model script retains telemetry data from the previous frame and only updates the status field. This prevents business data from being incorrectly cleared after a single acquisition failure.
6. EdgeBus Model
The DM2500 is a wired RS-485 device, not a native LoRaWAN device. It requires KC11 + EdgeBus to collect data using Modbus RTU and report it to ThinkLink over LoRaWAN.
6.1 EB Configuration Parameters
Serial port and business parameters:
| Parameter | Value | Description |
|---|---|---|
| BaudRate | 9600 | RS-485 baud rate (factory default) |
| StopBits | 1 | Stop bit |
| DataBits | 8 | Data bits |
| Checkbit | NONE | No parity (N81) |
| Battery | false | Mains powered, not battery |
| Class mode | Class C | Mains powered, suitable for always-on Class C |
| ConfirmDuty | 60 | Confirmation period |
| BzType | 23102 | Business code |
| BzVersion | 13 | Business version |
Period and parameter addresses:
| Parameter | APP Address | Description |
|---|---|---|
| period_up | 70 | Acquisition and upload period (shared, default 60 s) |
| addr_modbus | 150 | Modbus slave address (default 1) |
Note: In this solution
upPeriodIndex = periodIndex = 70, meaning the acquisition period and upload period are controlled by the same app parameter and cannot be set independently.
6.2 EB Code
import { Buffer } from "buffer";
import { buildOtaFile } from "@EBSDK/run";
import {
ActionAfertExpr, CalcData,
CrcMode,
CvtRule,
EBBuffer,
EBModel,
ExprCondition,
LoraUpEvent,
QueryEvent, SetUpCovDataType,
UserConfUPItem, EventInfoItem
} from "@EBSDK/EBCompiler/all_variable";
import { CheckbitEnum, getOtaConfig, HwTypeEnum, UpgrdTypeEnum } from "@EBSDK/otaConfig";
const eventInfo:UserConfUPItem[]=[
{
name:"dm251x_hk_power",port:22,version:"0x87",dataType:"0x02",upPeriodIndex:70,
quInfo:[
{ protocol:"modbus",addr:"0x01",code:"0x03",periodIndex:70,
indexAPP:150,indexCMD:0,copySize:1,
listVal:[
{ start:"2700", end:"2701"}
]
},
{ protocol:"modbus",addr:"0x01",code:"0x03",periodIndex:70,
indexAPP:150,indexCMD:0,copySize:1,
listVal:[
{ start:"3000", end:"3001"},
{ start:"3002", end:"3003"},
{ start:"3004", end:"3005"}
]
},
{ protocol:"modbus",addr:"0x01",code:"0x03",periodIndex:70,
indexAPP:150,indexCMD:0,copySize:1,
listVal:[
{ start:"3010", end:"3011"},
{ start:"3012", end:"3013"}
]
},
{ protocol:"modbus",addr:"0x01",code:"0x03",periodIndex:70,
indexAPP:150,indexCMD:0,copySize:1,
listVal:[
{ start:"3028", end:"3029"},
{ start:"3030", end:"3031"},
{ start:"3032", end:"3033"}
]
},
{ protocol:"modbus",addr:"0x01",code:"0x03",periodIndex:70,
indexAPP:150,indexCMD:0,copySize:1,
listVal:[
{ start:"3036", end:"3037"},
{ start:"3038", end:"3039"}
]
},
{ protocol:"modbus",addr:"0x01",code:"0x03",periodIndex:70,
indexAPP:150,indexCMD:0,copySize:1,
listVal:[
{ start:"3084", end:"3085"}
]
},
{ protocol:"modbus",addr:"0x01",code:"0x03",periodIndex:70,
indexAPP:150,indexCMD:0,copySize:1,
listVal:[
{ start:"3110", end:"3111"}
]
}
]
}
]
let otaConfig = getOtaConfig({
BaudRate: 9600,
StopBits: 1,
DataBits: 8,
Checkbit: CheckbitEnum.NONE,
Battery: false,
ConfirmDuty: 60,
BzType: 23102,
BzVersion: 13
})
const MODBUS_TT = (ebModel: EBModel) => {
for (let i=0; i<eventInfo.length; i++){
let event=new EventInfoItem(eventInfo[i]);
event.upEventSetup()
event.eventInstall()
}
return JSON.stringify(ebModel, null, 2)
}
buildOtaFile(__filename, otaConfig, MODBUS_TT)6.3 Description
Current EB logic:
KC11 connects to the DM2500 meter via RS-485 and reads holding registers using Modbus RTU FC03.
The EB script contains 7 Modbus query events, all using the same
periodIndex:70— acquisition and upload share a single period parameter.The Modbus slave address is stored in APP parameter slot
150(copySize:1, 1 byte, range 1–247). At each query, EB reads the actual slave address from this slot and writes it into the request frame.Register groups per query event:
Group Register Range Content 1 2700–2701 Forward active energy (1× FLOAT32) 2 3000–3005 Phase A/B/C current (3× FLOAT32) 3 3010–3013 Average current + current unbalance (2× FLOAT32) 4 3028–3033 Phase A/B/C L-N voltage (3× FLOAT32) 5 3036–3039 Average L-N voltage + voltage unbalance (2× FLOAT32) 6 3084–3085 Total power factor (1× FLOAT32, 4Q_FP_PF encoded) 7 3110–3111 Grid frequency (1× FLOAT32) All registers are FLOAT32 Big-Endian. EB extracts data from the
listValranges and concatenates them in order into the uplink frame.Uplink frame layout (port 22, total 58 bytes):
Byte Range Content 0 Tag 0x87 (business version marker) 1 Tag 0x02 (data type: Modbus acquisition) 2–5 RSSI + mains supply status (4 bytes, KC11 AC mode) 6–9 energy_import (FLOAT32 BE) 10–13 current_a 14–17 current_b 18–21 current_c 22–25 current_avg 26–29 current_unbalance 30–33 voltage_an 34–37 voltage_bn 38–41 voltage_cn 42–45 voltage_ln_avg 46–49 voltage_unbalance 50–53 power_factor_total 54–57 frequency COV (change-of-value upload) is disabled. All data is reported on a fixed timer.
KC11 is mains powered, so
Battery: falseis configured, making it suitable for Class C mode (the network can push RPC commands at any time).
7. Thing Model
7.1 Basic Thing Model Information
Data Thing Model
| Item | Description |
|---|---|
| Name | [DM2500] |
| id Name | dm2500_23102 |
| LoRaWAN Port | 22 |
| Data length | 58 bytes |
| RSSI included | Yes |
| Tag 1 | index: 0, tag: 0x87 |
| Tag 2 | index: 1, tag: 0x02 |
Parameter Thing Model
| Item | Description |
|---|---|
| Name | [DM2500-PARA] |
| id Name | dm2500_para_23102 |
| Parameter report Port | 214 |
| Parameter content | Acquisition/upload period (period_up), Modbus slave address (addr_modbus) |
7.2 Uplink Frame Structure
| Field | Position / Type | Description |
|---|---|---|
| port | 22 | Data uplink port |
| dataLen | 58 | Total frame length (bytes) |
| rssi | true | Includes RSSI |
| battery | 4 bytes | KC11 mains power status (4 bytes) |
| tagList[0] | index: 0, tag: 0x87 | Business version identifier |
| tagList[1] | index: 1, tag: 0x02 | Data type identifier (Modbus acquisition) |
| appData | index 6–57 | 13 FLOAT32 BE fields: energy → currents → voltages → power factor → frequency |
7.3 Thing Model Script
Data Thing Model Script
import {PayloadParser} from '#tklHelper'
function payload_parser({device, msg, thingModelId, noticeAttrs}) {
let port=msg?.userdata?.port || null;
let frameInfo = {
port:22,
dataLen:58,
rssi:true,
battery:4,
tagList:[
{ index:0, tag:0x87 },
{ index:1, tag:0x02 }
]
}
let appInfo = [
{ name:"正向有功电能", field_name:"energy_import", unit:"kWh", index:6, type:"floatbe", decimal:2 },
{ name:"A相电流", field_name:"current_a", unit:"A", index:10, type:"floatbe", decimal:2 },
{ name:"B相电流", field_name:"current_b", unit:"A", index:14, type:"floatbe", decimal:2 },
{ name:"C相电流", field_name:"current_c", unit:"A", index:18, type:"floatbe", decimal:2 },
{ name:"三相平均电流", field_name:"current_avg", unit:"A", index:22, type:"floatbe", decimal:2 },
{ name:"电流不平衡度", field_name:"current_unbalance", unit:"%", index:26, type:"floatbe", decimal:2 },
{ name:"A相电压", field_name:"voltage_an", unit:"V", index:30, type:"floatbe", decimal:2 },
{ name:"B相电压", field_name:"voltage_bn", unit:"V", index:34, type:"floatbe", decimal:2 },
{ name:"C相电压", field_name:"voltage_cn", unit:"V", index:38, type:"floatbe", decimal:2 },
{ name:"三相平均相电压", field_name:"voltage_ln_avg", unit:"V", index:42, type:"floatbe", decimal:2 },
{ name:"电压不平衡度", field_name:"voltage_unbalance", unit:"%", index:46, type:"floatbe", decimal:2 },
{ name:"总功率因数", field_name:"power_factor_total", unit:"", index:50, type:"floatbe", decimal:3 },
{ name:"频率", field_name:"frequency", unit:"Hz", index:54, type:"floatbe", decimal:1 }
]
let tdata={}
let payParser=new PayloadParser({
device:device,
msg:msg,
frameInfo:frameInfo,
appInfo:appInfo,
})
tdata= payParser.telemetry()
if((tdata?.status&0x02)===0x02) { // time out , just update the status.
const status=tdata.status
tdata={ ...(device.telemetry_data?.[thingModelId] ?? {})}
tdata.status=status
}
tdata._modelName="dm2500_23102"
return {
telemetry_data: tdata,
server_attrs: null,
shared_attrs: null
}
}Parameter Thing Model Script
import {PayloadParser, Utils} from '#tklHelper'
function payload_parser({device, msg, thingModelId, noticeAttrs}) {
let port=msg?.userdata?.port || null;
const rpcName="dm2500_set_23102"
let paraDef= {
app_20: { name:"TimeOffset", field_name:"TimeOffset", unit:"", index:20, type:"uint32le" },
app_38: { name:"pwron_delay", field_name:"pwron_delay", unit:"ms", index:38, type:"uint16le" },
app_70: { name:"period_up", field_name:"period_up", unit:"s", type:"uint32LE" },
app_150: { name:"addr_modbus", field_name:"addr_modbus", unit:"", type:"uint8" }
}
if (port!==214) {
let checkData=Utils.paraCheck(rpcName,device.server_attrs,device.shared_attrs)
return {
server_attrs: checkData.sdata,
actions: checkData.actions,
}
}
let pdata=(new PayloadParser({
device:device,
msg:msg,
paraInfo:paraDef,
})).paras()
let checkData=Utils.paraCheck(rpcName,device.server_attrs,pdata)
return {
telemetry_data: pdata,
server_attrs: checkData.sdata,
shared_attrs: pdata,
actions: checkData.actions,
}
}8. Third-Party Platform Data Subscription
8.1 MQTT Topic
/v32/{Organization Account}/tkl/up/telemetry/{eui}8.2 Example Reported Data
{
"eui": "6353012af10a9181",
"active_time": "2026-05-24T08:30:00.000Z",
"thingModelId": "111025554139803648",
"thingModelIdName": "dm2500_23102",
"telemetry_data": {
"snr": 9.5,
"rssi": -82,
"status": 0,
"energy_import": 12548.32,
"current_a": 12.45,
"current_b": 11.87,
"current_c": 12.10,
"current_avg": 12.14,
"current_unbalance": 2.38,
"voltage_an": 221.30,
"voltage_bn": 220.85,
"voltage_cn": 221.10,
"voltage_ln_avg": 221.08,
"voltage_unbalance": 0.21,
"power_factor_total": 0.932,
"frequency": 50.0
}
}Note:
thingModelIdreflects the platform-assigned value;thingModelIdNamecorresponds to the data thing model'sid_name.power_factor_totaluses Schneider 4Q_FP_PF encoding (range ±0.0–±2.0); values outside ±1.0 indicate leading (capacitive) load and require quadrant-based conversion before display.
9. RPC
9.1 RPC Names
Parameter Configuration (SET)
| Item | Description |
|---|---|
| Name | [DM2500 SET] 23102 |
| Type | SET |
| id Name | dm2500_set_23102 |
Parameter Reading (GET)
| Item | Description |
|---|---|
| Name | [DM2500 GET] 23102 |
| Type | GET |
| id Name | dm2500_get_23102 |
9.2 Parameter Definition
| Parameter | Field Name | APP Address | Type | Unit | Default | Range | Description |
|---|---|---|---|---|---|---|---|
| Acquisition/upload period | period_up | app_70 | uint32LE | s | 60 | 1–3600 | How often KC11 polls the meter and pushes data to the cloud; acquisition and upload share this single parameter |
| Modbus slave address | addr_modbus | app_150 | uint8 | — | 1 | 1–247 | Modbus slave address KC11 queries; must match the address configured on the meter when using multi-meter bus |
9.3 RPC Code
Parameter Configuration RPC (SET)
let classMode = (device?.shared_attrs?.class_mode) || "ClassC";
const rpcName = "dm2500_set_23102";
let paraDef = {
app_20: { name:"TimeOffset", field_name:"TimeOffset", unit:"", index:20, type:"uint32le" },
app_38: { name:"pwron_delay", field_name:"pwron_delay", unit:"ms", index:38, type:"uint16le" },
app_70: { name:"period_up", field_name:"period_up", unit:"s", type:"uint32LE" },
app_150: { name:"addr_modbus", field_name:"addr_modbus", unit:"", type:"uint8" }
}
let frames = RPCHelper.buildFrame({ paraDef, params });
let dnBuffer = Buffer.alloc(frames.writeBuffer.length + frames.readBuffer.length);
frames.writeBuffer.copy(dnBuffer, 0);
frames.readBuffer.copy(dnBuffer, frames.writeBuffer.length);
let msgQue = Utils.makeParaSetMSG({
device, classMode, rpcName, params,
paraDownBuffer: dnBuffer,
});
if (msgQue.length == 0) return null;
return msgQue;Parameter Reading RPC (GET)
let classMode = (device?.shared_attrs?.class_mode) || "ClassC";
let sleepMs = classMode === "ClassA" ? 500 : 5000;
let paraDef = {
app_20: { name:"TimeOffset", field_name:"TimeOffset", unit:"", index:20, type:"uint32le" },
app_38: { name:"pwron_delay", field_name:"pwron_delay", unit:"ms", index:38, type:"uint16le" },
app_70: { name:"period_up", field_name:"period_up", unit:"s", type:"uint32LE" },
app_150: { name:"addr_modbus", field_name:"addr_modbus", unit:"", type:"uint8" }
}
let frames = RPCHelper.buildFrame({ paraDef, params });
let msg = RPCHelper.makeMSG({
msgType: Utils.msgType.paras,
device,
dnBuffer: frames.readBuffer,
sleepTime: sleepMs,
});
return [msg];10. Template Selection
Search for the following template in the ThinkLink platform:
DM2500Or search by business code:
23102Recommended selection:
| Template Type | Name / id Name |
|---|---|
| Data thing model | [DM2500] / dm2500_23102 |
| Parameter thing model | [DM2500-PARA] / dm2500_para_23102 |
| Set parameter RPC | [DM2500 SET] 23102 / dm2500_set_23102 |
| Read parameter RPC | [DM2500 GET] 23102 / dm2500_get_23102 |
11. Supplementary Notes
Changing Communication Parameters
The DM2500 ships with Modbus slave address 1 and baud rate 9600 bps (N81). To change the slave address, write to register 6501 (requires write access) or use the LCD front panel. Changing communication parameters while in service disrupts the bus until KC11 parameters are updated to match.
Power Factor Encoding
The DM2500 reports power factor using Schneider 4Q_FP_PF encoding with a range of ±0.0 to ±2.0:
- Lagging (inductive, Q1/Q4): PF = raw register value
- Leading (capacitive, Q2/Q3): PF = raw value ± 1
This integration reports the raw float value in power_factor_total. Third-party systems consuming this field must apply quadrant-based conversion before displaying the result.
Voltage Field Validity
voltage_an, voltage_bn, and voltage_cn (L-N phase voltages) are valid only in 3P4W (three-phase four-wire) wiring. In 3P3W installations these fields return invalid readings. Use line voltage registers (3020–3021, 3022–3023, 3024–3025) instead — those are not collected in the current integration; contact your integration provider to update the EB code if needed.
DM2510 vs DM2511
Both models share the same EB code and thing model. The DM2511-specific fourth current input (I4, registers 3008–3009) is not collected in the current integration. To add current_i4, the EB code must be updated and recompiled.
Modbus Frame Examples
Read forward active energy (Reg 2700, slave address 1):
Request: 01 03 0A 8B 00 02 [CRC]
Response: 01 03 04 XX XX XX XX [CRC] (4-byte FLOAT32 BE = kWh value)
Read Phase A/B/C current (Reg 3000–3005, slave address 1):
Request: 01 03 0B B7 00 06 [CRC]
Response: 01 03 0C XX XX XX XX XX XX XX XX XX XX XX XX [CRC]
Read frequency (Reg 3110, slave address 1):
Request: 01 03 0C 25 00 02 [CRC]
Response: 01 03 04 42 42 00 00 [CRC] (example: ~48.5 Hz)