Skip to content

SWMx DIDO Controller Integration with ThinkLink

Integration Documentation

1. Sensor Overview

SWMx is a DIDO controller from JSDZ, mainly used for digital input status acquisition and relay output control.

In this solution, SWMx is connected to the KC11 collector through RS-485. The built-in EdgeBus in KC11 executes the Modbus RTU acquisition logic and uploads data to the ThinkLink platform through LoRaWAN.

Basic device information:

ItemDescription
Device ModelSWMx
ManufacturerJSDZ
English NameJSDZ
Device TypeDIDO Controller
Business Code22108
ThinkLink TemplateSWMx-22108

2. Product Features

The SWMx DIDO controller supports the following features:

  1. Digital input status acquisition
  2. Relay output control
  3. RS-485 / Modbus RTU communication
  4. LoRaWAN network access through KC11 + EdgeBus
  5. Status parsing, data visualization, and remote control through ThinkLink
  6. Reporting of complete RY / DI status bytes
  7. Parsing of the first 5 RY and DI channels as independent fields
  8. RPC-based configuration of acquisition period, upload period, and Modbus address
  9. RPC-based control of specified relay output status

3. Application Scenarios

This solution is suitable for the following scenarios:

  • Remote relay control
  • Digital input status monitoring
  • Equipment running status acquisition
  • Access control, magnetic door contact, and switch status monitoring
  • Wireless retrofit of DIDO controllers in industrial sites
  • Connecting traditional RS-485 controllers to LoRaWAN / ThinkLink
  • Remote control through MQTT / RPC from third-party platforms

4. Collector Information

4.1 Hardware Information

This solution uses KC11 as the collector.

ItemDescription
Collector ModelKC11
Acquisition InterfaceRS-485
Communication ProtocolModbus RTU
Uplink MethodLoRaWAN
Power Supply220V / 12V
Edge Computing CapabilityEdgeBus supported
Platform AccessThinkLink

KC11 reads Modbus data from SWMx through RS-485, uploads the acquisition results to the gateway through LoRaWAN, and finally sends the data to the ThinkLink platform.

4.2 Wiring Information

Power and communication interface:

Wiring ItemDescription
KC11 Power220V / 12V power supply
SWMx PowerConnect according to the actual SWMx power supply requirements
RS-485 AKC11 RS-485 A connected to SWMx A
RS-485 BKC11 RS-485 B connected to SWMx B
GNDConnect communication reference ground if required on site

Sensor interface:

SWMx itself is a DIDO controller. Sensors or field devices are connected to the DI input terminals of SWMx, while controlled devices are connected to the RY / relay output terminals of SWMx.

In this solution, KC11 does not directly connect to DI signals or relay loads. Instead, it reads and controls SWMx through RS-485.


5. Data Acquisition

In this solution, the following data is read through Modbus:

Data TypeModbus Function CodeDescription
RY Status0x01Read coil status
DI Status0x02Read discrete input status

This acquisition uses Modbus function codes 01 and 02:

  • 0x01: Read RY / Coil status
  • 0x02: Read DI / Discrete Input status

It should be noted that function codes 01 and 02 read data by bit, unlike function codes 03 and 04, which read register data directly. However, in this solution, EBHelper already handles this at the lower layer and automatically combines bit data into bytes.

Therefore, the EB code configuration:

javascript
{ start: "0", end: "15", covType: "HEX" }

means reading 16 bits from bit 0 to bit 15. The lower layer automatically combines the result into 2 bytes for upload.


5.1 Register Definition

RY Status Acquisition

ItemDescription
ProtocolModbus RTU
Function Code0x01
Read ObjectCoil / relay status
Start Address0
End Address15
Data Length16 bits, combined into 2 bytes by the lower layer
Upload FormatHEX
Change TriggerSupported, covType: "HEX"

Corresponding EB configuration:

javascript
{
  protocol: "modbus",
  code: "0x01",
  periodIndex: 74,
  indexAPP: 150,
  indexCMD: 0,
  copySize: 1,
  isLast: false,
  listVal: [
    { start: "0", end: "15", covType: "HEX" }
  ]
}

DI Status Acquisition

ItemDescription
ProtocolModbus RTU
Function Code0x02
Read ObjectDiscrete Input / digital input status
Start Address0
End Address15
Data Length16 bits, combined into 2 bytes by the lower layer
Upload FormatHEX
Change TriggerSupported, covType: "HEX"

Corresponding EB configuration:

javascript
{
  protocol: "modbus",
  code: "0x02",
  periodIndex: 74,
  indexAPP: 150,
  indexCMD: 0,
  copySize: 1,
  isLast: false,
  listVal: [
    { start: "0", end: "15", covType: "HEX" }
  ]
}

5.2 Status Bit Definition

In this solution, the thing model parses both the complete status word and the first 5 channel statuses.

RY Status Bit Definition

Field Namefield_nameData PositionTypeDescription
RYry_status_hexindex 6hexbe2RY 2-byte status value, HEX string
RY1ry1index 6 bit0bitLE0-0Relay channel 1 status
RY2ry2index 6 bit1bitLE1-1Relay channel 2 status
RY3ry3index 6 bit2bitLE2-2Relay channel 3 status
RY4ry4index 6 bit3bitLE3-3Relay channel 4 status
RY5ry5index 6 bit4bitLE4-4Relay channel 5 status

DI Status Bit Definition

Field Namefield_nameData PositionTypeDescription
DIdi_status_hexindex 8hexbe2DI 2-byte status value, HEX string
DI1di1index 8 bit0bitLE0-0DI channel 1 status
DI2di2index 8 bit1bitLE1-1DI channel 2 status
DI3di3index 8 bit2bitLE2-2DI channel 3 status
DI4di4index 8 bit3bitLE3-3DI channel 4 status
DI5di5index 8 bit4bitLE4-4DI channel 5 status

6. EdgeBus Model

In this solution, SWMx is an RS-485 / Modbus RTU device rather than a native LoRaWAN device. Therefore, the built-in EdgeBus in KC11 is required to execute the acquisition logic.

EdgeBus is responsible for:

  1. Periodically reading the RY status of SWMx
  2. Periodically reading the DI status of SWMx
  3. Reading bit status using Modbus function codes 01 / 02
  4. Encapsulating the reading results into LoRaWAN uplink data
  5. Supporting change-triggered upload logic
  6. Supporting parameter modification through platform RPC
  7. Supporting relay output control through platform RPC

6.1 EB Configuration Parameters

ParameterDescription
port22
dataType0x86
version0x08
upPeriodIndex70
readPeriodIndex74
Modbus address parameter index150
Modbus baud rate9600
Data bits8
Stop bits1
ParityNONE
Business type22108
Business version13
EB software version31
Battery devicefalse

Parameter description:

Parameter IndexFieldDescription
70period_upUpload period, in seconds
74period_readAcquisition period, in seconds
150addr_modbusSWMx Modbus address

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, QuItemBase,
  UserConfUPItem, EventInfoItem, QuItemModBus, UserConfQueryItem, EventConfig, Utils
} from "@EBSDK/EBCompiler/all_variable";
import { CheckbitEnum, getOtaConfig, HwTypeEnum, UpgrdTypeEnum } from "@EBSDK/otaConfig";

////////////////////////////////////////////////////////////////////////////////////////
// Function codes 01 and 02 read data by bit. EBHelper automatically combines bits into bytes.
// start: "0", end: "15" means reading 16 bits, uploaded as 2 bytes.
// If covType is HEX, the acquired data will be compared with the previous data.
// If there is any difference, an upload will be triggered.

const eventInfo: UserConfUPItem[] = [
  {
    name: "swm",
    dataType: "0x86",
    port: 22,
    version: "0x08",
    upPeriodIndex: 70,
    quInfo: [
      {
        protocol: "modbus",
        code: "0x01",
        periodIndex: 74,
        indexAPP: 150,
        indexCMD: 0,
        copySize: 1,
        isLast: false,
        listVal: [
          { start: "0", end: "15", covType: "HEX" }
        ]
      },
      {
        protocol: "modbus",
        code: "0x02",
        periodIndex: 74,
        indexAPP: 150,
        indexCMD: 0,
        copySize: 1,
        isLast: false,
        listVal: [
          { start: "0", end: "15", covType: "HEX" }
        ]
      }
    ]
  }
];

let otaConfig = getOtaConfig({
  SwVersion: 31,
  BaudRate: 9600,
  StopBits: 1,
  DataBits: 8,
  Checkbit: CheckbitEnum.NONE,
  Battery: false,
  ConfirmDuty: 60,
  BzType: 22108,
  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);
};

6.3 Description

Current EB logic:

  1. KC11 connects to SWMx through RS-485.
  2. EdgeBus reads SWMx data according to the configured period.
  3. Modbus function code 0x01 is used to read RY status.
  4. Modbus function code 0x02 is used to read DI status.
  5. start: "0", end: "15" means reading 16 bits.
  6. EBHelper automatically combines the 16-bit result into 2 bytes at the lower layer.
  7. The thing model uploads the RY status as ry_status_hex.
  8. The thing model uploads the DI status as di_status_hex.
  9. RY1–RY5 and DI1–DI5 are also parsed as independent status fields.
  10. When covType is set to HEX, the acquisition result is compared with the previous data. If the data changes, an upload is triggered.
  11. RPC can be used to configure the acquisition period, upload period, and Modbus address.
  12. RPC can be used to control specified relay switches.

7. Thing Model

7.1 Basic Thing Model Information

ItemDescription
Thing Model NameSWMx-22108
idNameswm_22108
Business Code22108
Data Uplink Port22
Parameter Port214
Transparent Transmission Port51
Data Length9
Parse RSSIYes
Battery Field Index4
Data Identifier0x86
Version Identifier0x08

In this solution, SWMx data is uploaded through LoRaWAN port 22.

javascript
let frameInfo = {
    port: 22,
    dataLen: 9,
    rssi: true,
    battery: 4,
    tagList: [
        { index: 0, tag: 0x86 },
        { index: 1, tag: 0x08 }
    ]
};

Uplink data structure:

IndexContentDescription
00x86Data type
10x08Protocol version
4batteryBattery or power status field
6~7RY status2-byte HEX
8~9DI status2-byte HEX

Note: According to the provided thing model script, dataLen is configured as 9, RY is parsed from index 6 with 2 bytes, and DI is parsed from index 8 with 2 bytes. During actual deployment, it is recommended to verify whether the frame length needs adjustment based on the real uplink payload.


7.3 Thing Model Script

javascript
let port = msg?.userdata?.port || null;

if (port === 51) {
    let retval = Utils.paraCheck(Utils.msgType.transParent, device.server_attrs, { device: device, msg: msg });
    return {
        telemetry_data: retval.tdata,
        server_attrs: retval.sdata,
        shared_attrs: retval.pdata,
        actions: retval.actions
    };
}

if (port !== 22) return null;

let frameInfo = {
    port: 22,
    dataLen: 9,
    rssi: true,
    battery: 4,
    tagList: [
        { index: 0, tag: 0x86 },
        { index: 1, tag: 0x08 }
    ]
};

let appInfo = [
    { name: "RY", field_name: "ry_status_hex", unit: "", index: 6, type: "hexbe2" },
    { name: "DI", field_name: "di_status_hex", unit: "", index: 8, type: "hexbe2" },

    { name: "RY1", field_name: "ry1", unit: "", index: 6, type: "bitLE0-0" },
    { name: "RY2", field_name: "ry2", unit: "", index: 6, type: "bitLE1-1" },
    { name: "RY3", field_name: "ry3", unit: "", index: 6, type: "bitLE2-2" },
    { name: "RY4", field_name: "ry4", unit: "", index: 6, type: "bitLE3-3" },
    { name: "RY5", field_name: "ry5", unit: "", index: 6, type: "bitLE4-4" },

    { name: "DI1", field_name: "di1", unit: "", index: 8, type: "bitLE0-0" },
    { name: "DI2", field_name: "di2", unit: "", index: 8, type: "bitLE1-1" },
    { name: "DI3", field_name: "di3", unit: "", index: 8, type: "bitLE2-2" },
    { name: "DI4", field_name: "di4", unit: "", index: 8, type: "bitLE3-3" },
    { name: "DI5", field_name: "di5", unit: "", index: 8, type: "bitLE4-4" }
];

let payParser = new PayloadParser({
    device: device,
    msg: msg,
    frameInfo: frameInfo,
    appInfo: appInfo,
});

let tdata = payParser.telemetry();

if ((tdata?.status & 0x02) === 0x02) {
    // time out, only update status
    const status = tdata.status;
    tdata = { ...(device.telemetry_data?.[thingModelId] ?? {}) };
    tdata.status = status;
}

return {
    telemetry_data: tdata,
    server_attrs: null,
    shared_attrs: null
};

8. Third-Party Platform Data Subscription

8.1 MQTT Topic

A third-party platform can subscribe to ThinkLink uplink data through the following topic:

latex
/v32/{Organization Account}/tkl/up/telemetry/{eui}

Where:

ParameterDescription
Organization AccountThinkLink organization account
euiLoRaWAN device EUI
json
{
    "eui": "6353012af10a9331",
    "active_time": "2026-02-05T08:35:48.000Z",
    "thingModelId": "88304368717139973",
    "thingModelIdName": "swm_22108",
    "telemetry_data": {
        "snr": 13.5,
        "rssi": -51,
        "battery": 3.37,
        "ry_status_hex": "001F",
        "di_status_hex": "000B",
        "ry1": 1,
        "ry2": 1,
        "ry3": 1,
        "ry4": 1,
        "ry5": 1,
        "di1": 1,
        "di2": 1,
        "di3": 0,
        "di4": 1,
        "di5": 0,
        "status": 0
    }
}

Field description:

FieldDescription
ry_status_hexRY status 2-byte HEX string
di_status_hexDI status 2-byte HEX string
ry1ry5Relay channel 1–5 status
di1di5DI input channel 1–5 status
batteryBattery or power status field
rssiLoRaWAN RSSI
snrLoRaWAN SNR
statusData status

9. RPC

This solution supports the following RPC functions:

  1. Parameter configuration
  2. Parameter reading
  3. Relay control

9.1 RPC Names

RPC FunctionRPC Name
Parameter configurationswm_set_22108
Parameter readingswm_get_22108
Relay controlswm_action_22108

9.2 Parameter Definition

Parameter configuration definition:

javascript
let paraDef = {
    app_70: {
        name: "period_up",
        field_name: "period_up",
        unit: "s",
        type: "uint32LE"
    },
    app_74: {
        name: "period_read",
        field_name: "period_read",
        unit: "s",
        type: "uint32LE"
    },
    app_150: {
        name: "addr_modbus",
        field_name: "addr_modbus",
        unit: "",
        type: "uint8"
    }
};

Parameter table:

Parameterfield_nameTypeUnitDescription
app_70period_upuint32LEsUpload period
app_74period_readuint32LEsAcquisition period
app_150addr_modbusuint8-SWMx Modbus address

Relay control parameters — each relay channel has its own boolean parameter; any one or several channels can be controlled in a single call. When the form opens, each field is auto-populated from the device shared attributes (ry1~ry5) with the last commanded state.

ParameterTypeDescription
switch_ry1booleanRelay channel 1: true close / turn on, false open / turn off
switch_ry2booleanRelay channel 2
switch_ry3booleanRelay channel 3
switch_ry4booleanRelay channel 4
switch_ry5booleanRelay channel 5

Only the fields actually supplied are sent (one Modbus FC05 frame each, coil address = channel number − 1); unsupplied channels are left untouched. On success, each channel's state is written back to both telemetry ryN and shared attribute ryN.

Example (close channel 1, open channel 3):

json
{
    "switch_ry1": true,
    "switch_ry3": false
}

9.3 RPC Code

9.3.1 Parameter Configuration RPC

javascript
let classMode = (device && device.shared_attrs && device.shared_attrs.class_mode) || "ClassA";
let intervalms = classMode === "ClassA" ? 0 : 2000;

const rpcName = "swm_set_22108";

let paraDef = {
    app_70: { name: "period_up", field_name: "period_up", unit: "s", type: "uint32LE" },
    app_74: { name: "period_read", field_name: "period_read", unit: "s", type: "uint32LE" },
    app_150: { name: "addr_modbus", field_name: "addr_modbus", unit: "", type: "uint8" }
};

let frames = RPCHelper.buildFrame({
    paraDef: paraDef,
    params: params
});

let redoBuffer = RPCHelper.redo();

let dnBuffer = Buffer.alloc(frames.writeBuffer.length + frames.readBuffer.length);
frames.writeBuffer.copy(dnBuffer, 0);
frames.readBuffer.copy(dnBuffer, frames.writeBuffer.length);

logger.info("set para");

let msgQue = Utils.makeParaSetMSG({
    device: device,
    classMode: classMode,
    rpcName: rpcName,
    params: params,
    paraDownBuffer: dnBuffer,
    extraAppBuffer: redoBuffer
});

if (msgQue.length == 0) return null;

return msgQue;

9.3.2 Parameter Reading RPC

javascript
let classMode = (device && device.shared_attrs && device.shared_attrs.class_mode) || "ClassA";
let sleepMs = classMode === "ClassA" ? 500 : 5000;

let paraDef = {
    app_70: { name: "period_up", field_name: "period_up", unit: "s", type: "uint32LE" },
    app_74: { name: "period_read", field_name: "period_read", unit: "s", type: "uint32LE" },
    app_150: { name: "addr_modbus", field_name: "addr_modbus", unit: "", type: "uint8" }
};

let frames = RPCHelper.buildFrame({
    paraDef: paraDef,
    params: params
});

let msg = RPCHelper.makeMSG({
    msgType: Utils.msgType.paras,
    device: device,
    dnBuffer: frames.readBuffer,
    sleepTime: sleepMs,
});

return [msg];

9.3.3 Relay Control RPC

The form exposes five boolean parameters switch_ry1~switch_ry5, one per relay channel. The script iterates over the fields supplied in the call, sending one Modbus function-code-05 frame per channel (coil address = channel number − 1); the frames are concatenated into a single downlink. Each frame's coil value is verified against the device echo, and on success every channel's state is written back to both telemetry ryN and shared attribute ryN (used to auto-populate the form next time it is opened).

javascript
let classMode = (device?.shared_attrs?.class_mode) || "ClassA";
let addr_modbus = device.shared_attrs?.addr_modbus ?? 1;
let rpcName = "swm_action_22108";

// 5 relays: form fields switch_ry1..switch_ry5, each populated from shared_attrs ry1..ry5.
// A single submit may contain any subset; each present field sends one Modbus FC05 frame (coil address = channel-1).
let frames = [];
let appInfo = [];
let telemetry_data = {};
let shared_attrs = {};
for (let n = 1; n <= 5; n++) {
    let key = "switch_ry" + n;
    if (params[key] === undefined) continue;
    let on = params[key] ? 1 : 0;
    let checkVal = params[key] ? 0xFF00 : 0x0000;
    frames.push(RPCHelper.modbusAction(addr_modbus, 5, n - 1, checkVal));
    let tdataKey = "ry" + n;
    // Each echo is an 8-byte FC05 frame: [addr][05][coilH][coilL][valH][valL][crcL][crcH]
    // Echoed coil value = uint16BE @ (frameStart+4); frameStart+6 is the CRC, never use it for verification.
    appInfo.push({field_name: tdataKey, index: appInfo.length * 8 + 4, type: "uint16BE", val: checkVal});
    telemetry_data[tdataKey] = on;
    shared_attrs[tdataKey] = on;
}
if (frames.length === 0) return null;

let dnBuffer = Buffer.alloc(frames.length * 8);
frames.forEach((f, i) => f.copy(dnBuffer, i * 8));

let checkInfo = {
    frameInfo: {port: 51, dataLen: dnBuffer.length},
    appInfo: appInfo,
    telemetry_data: telemetry_data,
    server_attrs: null,
    shared_attrs: shared_attrs
};
let msgQue = Utils.makeParaSetMSG({
    msgType: Utils.msgType.transParent,
    checkInfo: checkInfo,
    device: device,
    classMode: classMode,
    rpcName: rpcName,
    params: params,
    paraDownBuffer: dnBuffer,
    timeout: 3000,
    maxRetries: 3
});
if (msgQue.length === 0) return null;
return msgQue;

Control logic description:

ItemDescription
Modbus Function Code05 (one frame per channel)
Control ObjectAny one or several relay channels (single call)
ON Value0xFF00
OFF Value0x0000
Form Fieldsswitch_ry1 ~ switch_ry5 (boolean)
Coil Addresschannel − 1 (switch_ry1→0 … switch_ry5→4)
Write-back on Successtelemetry ryN + shared attribute ryN (for form populate)

10. Template Selection

Search for the following template in the ThinkLink platform:

latex
SWMx-22108

Or search by business type:

latex
22108

Recommended template information:

ItemDescription
Template NameSWMx-22108
Device TypeDIDO Controller
ManufacturerJSDZ
Business Code22108
Access MethodKC11 + RS-485 + EdgeBus + LoRaWAN
Data UploadRY status, DI status
Control CapabilityRelay RPC control supported
Parameter ConfigurationUpload period, acquisition period, and Modbus address configuration supported

11. Integration Summary

This solution connects the SWMx DIDO controller to the IoT platform through KC11 + EdgeBus + LoRaWAN + ThinkLink.

Key features:

  1. KC11 connects to SWMx through RS-485.
  2. EdgeBus uses Modbus function code 01 to read RY status.
  3. EdgeBus uses Modbus function code 02 to read DI status.
  4. Function codes 01 / 02 read data by bit, and EBHelper already handles the differences from function codes 03 / 04 at the lower layer.
  5. RY and DI statuses are uploaded as 2-byte HEX strings.
  6. The platform parses RY1–RY5 and DI1–DI5 status at the same time.
  7. RPC is supported for modifying the upload period, acquisition period, and Modbus address.
  8. RPC is supported for controlling any one or several relay channels (switch_ry1~switch_ry5) through Modbus function code 05, with state written back to shared attributes for form populate.

This solution is suitable for quickly upgrading traditional RS-485 DIDO controllers into LoRaWAN wireless devices and connecting them to ThinkLink for remote monitoring, remote control, and third-party platform data subscription.