Skip to content

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

ItemDescription
Data acquisition device modelKC11
Communication interfaceRS-485
Power supplyAC 85–270V mains (DIN rail mount)
Communication methodLoRaWAN Class C (always-on)
EdgeBus supportSupported
Adapted protocolModbus RTU
Business code23102
Template nameDM2500

4.2 Wiring Information

Power and Communication Interfaces

InterfaceDescription
Power inputKC11 is powered by AC 85–270V; can share the same 220V supply as the DM2500 auxiliary power
RS-485 AConnect to the RS+ terminal on the DM2500 rear panel
RS-485 BConnect to the RS- terminal on the DM2500 rear panel
485C/GNDOptional connection to the C (common reference) terminal on DM2500
LoRaWANKC11 reports collected data to the gateway / ThinkLink over LoRaWAN

Sensor Interface

The DM2500 connects to KC11 via the RS-485 bus. Rear terminal reference:

TerminalSignalConnect to
RS+RS-485 data +KC11 485A
RS-RS-485 data -KC11 485B
CCommon referenceKC11 485C/GND (optional)
L+/N/-Auxiliary powerAC 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 ItemField NameUnitRegister (1-based)
1Forward active energyenergy_importkWh2700–2701
2Phase A currentcurrent_aA3000–3001
3Phase B currentcurrent_bA3002–3003
4Phase C currentcurrent_cA3004–3005
5Average three-phase currentcurrent_avgA3010–3011
6Current unbalancecurrent_unbalance%3012–3013
7Phase A voltage (L-N)voltage_anV3028–3029
8Phase B voltage (L-N)voltage_bnV3030–3031
9Phase C voltage (L-N)voltage_cnV3032–3033
10Average L-N voltagevoltage_ln_avgV3036–3037
11Voltage unbalancevoltage_unbalance%3038–3039
12Total power factorpower_factor_total3084–3085
13Grid frequencyfrequencyHz3110–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 ItemRegister (1-based)PDU AddressData TypeQuery Group
Forward active energy2700–27010x0A8BFLOAT32 BEGroup 1 (standalone)
Phase A current3000–30010x0BB7FLOAT32 BEGroup 2 (with B/C)
Phase B current3002–30030x0BB9FLOAT32 BEGroup 2
Phase C current3004–30050x0BBBFLOAT32 BEGroup 2
Average three-phase current3010–30110x0BC1FLOAT32 BEGroup 3 (with unbalance)
Current unbalance3012–30130x0BC3FLOAT32 BEGroup 3
Phase A voltage (L-N)3028–30290x0BD3FLOAT32 BEGroup 4 (with B/C)
Phase B voltage (L-N)3030–30310x0BD5FLOAT32 BEGroup 4
Phase C voltage (L-N)3032–30330x0BD7FLOAT32 BEGroup 4
Average L-N voltage3036–30370x0BDBFLOAT32 BEGroup 5 (with unbalance)
Voltage unbalance3038–30390x0BDDFLOAT32 BEGroup 5
Total power factor3084–30850x0C0BFLOAT32 BEGroup 6 (standalone)
Grid frequency3110–31110x0C25FLOAT32 BEGroup 7 (standalone)

5.2 Status Bit Definition

The thing model parsing logic includes an acquisition timeout check:

javascript
if ((tdata?.status & 0x02) === 0x02) {
    // time out, just update the status.
}
Status BitMeaning
status & 0x02Acquisition 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:

ParameterValueDescription
BaudRate9600RS-485 baud rate (factory default)
StopBits1Stop bit
DataBits8Data bits
CheckbitNONENo parity (N81)
BatteryfalseMains powered, not battery
Class modeClass CMains powered, suitable for always-on Class C
ConfirmDuty60Confirmation period
BzType23102Business code
BzVersion13Business version

Period and parameter addresses:

ParameterAPP AddressDescription
period_up70Acquisition and upload period (shared, default 60 s)
addr_modbus150Modbus 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

typescript
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:

  1. KC11 connects to the DM2500 meter via RS-485 and reads holding registers using Modbus RTU FC03.

  2. The EB script contains 7 Modbus query events, all using the same periodIndex:70 — acquisition and upload share a single period parameter.

  3. 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.

  4. Register groups per query event:

    GroupRegister RangeContent
    12700–2701Forward active energy (1× FLOAT32)
    23000–3005Phase A/B/C current (3× FLOAT32)
    33010–3013Average current + current unbalance (2× FLOAT32)
    43028–3033Phase A/B/C L-N voltage (3× FLOAT32)
    53036–3039Average L-N voltage + voltage unbalance (2× FLOAT32)
    63084–3085Total power factor (1× FLOAT32, 4Q_FP_PF encoded)
    73110–3111Grid frequency (1× FLOAT32)
  5. All registers are FLOAT32 Big-Endian. EB extracts data from the listVal ranges and concatenates them in order into the uplink frame.

  6. Uplink frame layout (port 22, total 58 bytes):

    Byte RangeContent
    0Tag 0x87 (business version marker)
    1Tag 0x02 (data type: Modbus acquisition)
    2–5RSSI + mains supply status (4 bytes, KC11 AC mode)
    6–9energy_import (FLOAT32 BE)
    10–13current_a
    14–17current_b
    18–21current_c
    22–25current_avg
    26–29current_unbalance
    30–33voltage_an
    34–37voltage_bn
    38–41voltage_cn
    42–45voltage_ln_avg
    46–49voltage_unbalance
    50–53power_factor_total
    54–57frequency
  7. COV (change-of-value upload) is disabled. All data is reported on a fixed timer.

  8. KC11 is mains powered, so Battery: false is 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

ItemDescription
Name[DM2500]
id Namedm2500_23102
LoRaWAN Port22
Data length58 bytes
RSSI includedYes
Tag 1index: 0, tag: 0x87
Tag 2index: 1, tag: 0x02

Parameter Thing Model

ItemDescription
Name[DM2500-PARA]
id Namedm2500_para_23102
Parameter report Port214
Parameter contentAcquisition/upload period (period_up), Modbus slave address (addr_modbus)

FieldPosition / TypeDescription
port22Data uplink port
dataLen58Total frame length (bytes)
rssitrueIncludes RSSI
battery4 bytesKC11 mains power status (4 bytes)
tagList[0]index: 0, tag: 0x87Business version identifier
tagList[1]index: 1, tag: 0x02Data type identifier (Modbus acquisition)
appDataindex 6–5713 FLOAT32 BE fields: energy → currents → voltages → power factor → frequency

7.3 Thing Model Script

Data Thing Model Script

javascript
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

javascript
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

json
{
    "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: thingModelId reflects the platform-assigned value; thingModelIdName corresponds to the data thing model's id_name. power_factor_total uses 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)

ItemDescription
Name[DM2500 SET] 23102
TypeSET
id Namedm2500_set_23102

Parameter Reading (GET)

ItemDescription
Name[DM2500 GET] 23102
TypeGET
id Namedm2500_get_23102

9.2 Parameter Definition

ParameterField NameAPP AddressTypeUnitDefaultRangeDescription
Acquisition/upload periodperiod_upapp_70uint32LEs601–3600How often KC11 polls the meter and pushes data to the cloud; acquisition and upload share this single parameter
Modbus slave addressaddr_modbusapp_150uint811–247Modbus 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)

javascript
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)

javascript
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:

DM2500

Or search by business code:

23102

Recommended selection:

Template TypeName / 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)