Skip to content

22117-MAGNETOSTRICTIVE Magnetostrictive Displacement and Level Sensor ​

Model catalog ​

Search for these models in ThinkLink by name or ID.

Model typeNameid_namePlatform model ID
RPC[MAGNETOSTRICTIVE GET] 22117magnetostrictive_get_22117134816011390971904
RPC[MAGNETOSTRICTIVE SET] 22117magnetostrictive_set_22117134816010694717440
TemplateMAGNETOSTRICTIVEmagnetostrictive_22117134816008626925568
Thing Model[MAGNETOSTRICTIVE]magnetostrictive_22117134816009310597120
Parameter Thing Model[MAGNETOSTRICTIVE-PARA]magnetostrictive_para_22117134816010011045888

1. Sensor overview ​

The SX magnetostrictive absolute displacement/level sensor connects to a KC21 over RS-485/Modbus RTU. Its business code is 22117 and its ThinkLink template is magnetostrictive_22117.

2. Product features ​

  1. ±0.001mm repeatability
  2. Absolute-position output without re-homing after a restart
  3. RS-485 Modbus RTU or 4-20mA variants

3. Application scope ​

  1. Reservoir, tank and process-vessel level monitoring
  2. Hydraulic-cylinder, gate and actuator displacement monitoring
  3. Bridge, foundation-pit and structural-deformation monitoring

4. Collector information ​

  • DTU: KC21
  • Interface: RS-485
  • Default mode: Battery / Class A
  • Serial: 9600, 8N1
  • Sensor power-on delay: 1000ms

Pre-deployment power confirmation: Site information was unavailable, so this integration defaults to a battery-powered KC21 in Class A. If the installation is continuously powered, change to Class C, use the MT-EB-POWER base template and review the heartbeat period; otherwise downlinks may be delayed or unavailable and power behaviour may be incorrect.

Connect supply polarity according to the device documentation and nameplate. Connect RS-485 A/B to KC21 485A/485B. Complete wiring with power removed and confirm the actual sensor supply range before deployment.

5. Data acquisition ​

The integration uses FC03. One query event reads low and high words from addresses 0-1 and combines them into millimetres. Collection and upload share app_70=900s; COV is disabled.

AddressFieldData itemTypeScaleUnit
0-1displacement位移/液位 / Displacement or leveluint32WordsLowHigh0.001mm

6. EdgeBus model ​

  • port: 22
  • version/dataType: 0x88/0x01
  • BzType/BzVersion: 22117/1
  • upPeriodIndex: 70
  • addr_modbus: app_150
  • Uplink length: six common-header bytes plus all selected query-result bytes
  • Query logic: One query event reads low and high words from addresses 0-1 and combines them into millimetres

6.2 EdgeBus code ​

typescript
import { Buffer } from "buffer";
import { buildOtaFile } from "@EBSDK/run";
import { EBModel } from "@EBSDK/EBCompiler/EBModel/EBModel";
import { EventInfoItem } from "@EBSDK/EBCompiler/plugins/EBHelper";
import type { UserConfUPItem } from "@EBSDK/EBCompiler/plugins/EBHelper";
import { CheckbitEnum, getOtaConfig } from "@EBSDK/otaConfig";

// SX magnetostrictive displacement/level sensor, RS485/Modbus-RTU FC03.
// Registers 0x0000 and 0x0001 are low word then high word. The ThinkLink
// parser combines them using standard 32-bit binary carry and scales to mm.
const eventInfo: UserConfUPItem[] = [
  {
    name: "sx-magnetostrictive",
    port: 22,
    version: "0x88",
    dataType: "0x01",
    upPeriodIndex: 70,
    quInfo: [
      {
        protocol: "modbus",
        addr: "0x01",
        code: "0x03",
        periodIndex: 70,
        indexAPP: 150,
        indexCMD: 0,
        copySize: 1,
        isLast: true,
        listVal: [{ start: "0x0000", end: "0x0001" }],
      },
    ],
  },
];

const otaConfig = getOtaConfig({
  BaudRate: 9600,
  StopBits: 1,
  DataBits: 8,
  Checkbit: CheckbitEnum.NONE,
  Battery: true,
  ConfirmDuty: 60,
  BzType: 22117,
  BzVersion: 2,
});

const MODBUS_TT = (ebModel: EBModel) => {
  for (const info of eventInfo) {
    const event = new EventInfoItem(info);
    event.upEventSetup();
    event.eventInstall();
  }
  return JSON.stringify(ebModel, null, 2);
};

buildOtaFile(import.meta.url, otaConfig, MODBUS_TT);

7. Thing model ​

  • Telemetry: magnetostrictive_22117
  • Parameters: magnetostrictive_para_22117
  • Uplink: port 22, tags 0x88/0x01

7.1 Telemetry parser ​

js
import {Buffer} from "buffer";
import {PayloadParser, RPCHelper} from "#tklHelper";

function payload_parser(device, msg, thingModelId, noticeAttrs, org_params) {
    const port = msg?.userdata?.port ?? null;
    if (port !== 22) return null;

    const frameInfo = {
        port: 22,
        dataLen: 10,
        rssi: true,
        battery: 4,
        status: 3,
        tagList: [{index: 0, tag: 0x88}, {index: 1, tag: 0x01}],
    };
    const parser = new PayloadParser({device, msg, frameInfo, appInfo: []});
    let telemetry = parser.telemetry();
    if (telemetry == null) return null;

    if ((telemetry.status & 0x02) === 0x02) {
        const status = telemetry.status;
        telemetry = {...(device.telemetry_data?.[thingModelId] ?? {})};
        telemetry.status = status;
    } else {
        const payload = Buffer.from(msg.userdata.payload, "base64");
        const raw = payload.readUInt16BE(8) * 65536 + payload.readUInt16BE(6);
        telemetry.displacement = Number((raw * 0.001).toFixed(3));
    }
    telemetry._modelName = "magnetostrictive_22117";
    telemetry.adr = msg?.userdata?.adr ?? null;

    const result = {
        telemetry_data: telemetry,
        server_attrs: null,
        shared_attrs: null,
    };
    if (typeof RPCHelper.buildRpcHookAction === "function") {
        const action = RPCHelper.buildRpcHookAction({
            device,
            msg,
            thingModelId,
            idName: "magnetostrictive_22117",
            name: "[MAGNETOSTRICTIVE]",
            parsed: result,
        });
        if (action) result.actions = [action];
    }
    return result;
}

const deviceObj = {telemetry_data: {}};
const msgObj = {
    userdata: {
        port: 22,
        payload: Buffer.from("880100006401b7cd0001", "hex").toString("base64"),
    },
};
const noticeAttrsObj = {telemetry_data: true};
console.log(payload_parser(deviceObj, msgObj, "1", noticeAttrsObj, {}));

7.2 Parameter parser ​

js
import {PayloadParser, RPCHelper, Utils} from "#tklHelper";

function payload_parser(device, msg, thingModelId, noticeAttrs, org_params) {
    const port = msg?.userdata?.port ?? null;
    const rpcName = "magnetostrictive_set_22117";
    const 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_40: {name: "period_heart", field_name: "period_heart", unit: "s", index: 40, type: "uint32LE"},
        app_70: {name: "period_up", field_name: "period_up", unit: "s", index: 70, type: "uint32LE"},
        app_150: {name: "addr_modbus", field_name: "addr_modbus", unit: "", index: 150, type: "uint8"},
    };
    if (port !== 214) {
        const checkData = Utils.paraCheck(rpcName, device.server_attrs, device.shared_attrs);
        return {server_attrs: checkData.sdata, actions: checkData.actions};
    }
    const pdata = (new PayloadParser({device, msg, paraInfo: paraDef})).paras();
    if (pdata == null) return null;
    const checkData = Utils.paraCheck(rpcName, device.server_attrs, pdata);
    const result = {
        telemetry_data: pdata,
        server_attrs: checkData.sdata,
        shared_attrs: {
            TimeOffset: pdata.TimeOffset,
            pwron_delay: pdata.pwron_delay,
            period_heart: pdata.period_heart,
            period_up: pdata.period_up,
            addr_modbus: pdata.addr_modbus,
        },
        actions: checkData.actions,
    };
    if (typeof RPCHelper.buildRpcHookAction === "function") {
        const action = RPCHelper.buildRpcHookAction({
            device,
            msg,
            thingModelId,
            idName: "magnetostrictive_para_22117",
            name: "[MAGNETOSTRICTIVE-PARA]",
            parsed: {
                telemetry_data: result.telemetry_data,
                server_attrs: result.server_attrs,
                shared_attrs: result.shared_attrs,
            },
        });
        if (action) {
            result.actions = result.actions || [];
            result.actions.push(action);
        }
    }
    return result;
}

const deviceObj = {telemetry_data: {}, server_attrs: {}, shared_attrs: {}};
const msgObj = {userdata: {port: 11, payload: ""}};
const noticeAttrsObj = {telemetry_data: true};
console.log(payload_parser(deviceObj, msgObj, "1", noticeAttrsObj, {}));

8. Third-party MQTT subscription ​

Topic: /v32/{Organization Account}/tkl/up/telemetry/{eui}

json
{
  "eui": "6353012af10a9331",
  "active_time": "2026-07-29T08:35:48.000Z",
  "thingModelId": "22117",
  "thingModelIdName": "magnetostrictive_22117",
  "telemetry_data": {
    "snr": 13.5,
    "rssi": -51,
    "battery": 3.37,
    "displacement": 112.589
  }
}

9. RPC ​

  • Set: magnetostrictive_set_22117
  • Get: magnetostrictive_get_22117
APP slotFieldMeaningUnitTypeDefault

| app_20 | TimeOffset | Time offset | | uint32le | 0 | | app_38 | pwron_delay | Power-on delay | ms | uint16le | 1000 | | app_40 | period_heart | Heartbeat period | s | uint32LE | 86400 | | app_70 | period_up | Collect and upload period (≥60) | s | uint32LE | 900 | | app_150 | addr_modbus | Modbus address (1-247) | | uint8 | 1 |

9.1 Set RPC ​

js
import {RPCHelper, Utils} from '#tklHelper';
import {Buffer} from 'buffer';

function rpc_script({device, params, alarms, logger, org_params}) {
    const classMode = device?.shared_attrs?.class_mode || "ClassA";
    const rpcName = "magnetostrictive_set_22117";
    params = params && typeof params === "object" ? {...params} : {};

    if (params.period_up != null && params.period_up < 60) {
        logger.info("period_up must be at least 60 seconds");
        return null;
    }
    if (params.addr_modbus != null && (params.addr_modbus < 1 || params.addr_modbus > 247)) {
        logger.info("addr_modbus must be between 1 and 247");
        return null;
    }
    if (params.period_heart == null || params.period_heart < 3600) {
        params.period_heart = 86400;
    }
    if (params.pwron_delay == null || params.pwron_delay < 1000) {
        params.pwron_delay = 1000;
    }

    const 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_40:  {name: "period_heart", field_name: "period_heart", unit: "s", index: 40, type: "uint32LE"},
        app_70:  {name: "period_up", field_name: "period_up", unit: "s", index: 70, type: "uint32LE"},
        app_150: {name: "addr_modbus", field_name: "addr_modbus", unit: "", index: 150, type: "uint8"}
    };
    const frames = RPCHelper.buildFrame({paraDef, params});
    const redoBuffer = RPCHelper.redo();
    const dnBuffer = Buffer.alloc(frames.writeBuffer.length + frames.readBuffer.length);
    frames.writeBuffer.copy(dnBuffer, 0);
    frames.readBuffer.copy(dnBuffer, frames.writeBuffer.length);
    logger.info("set magnetostrictive KC21 app parameters");
    const msgQue = Utils.makeParaSetMSG({
        device,
        classMode,
        rpcName,
        params,
        paraDownBuffer: dnBuffer,
        extraAppBuffer: redoBuffer
    });
    return msgQue.length == 0 ? null : msgQue;
}

rpc_script({
    device: {server_attrs: {}, shared_attrs: {}},
    params: {period_up: 900, addr_modbus: 1, period_heart: 86400, pwron_delay: 1000},
    alarms: [],
    logger: console,
    org_params: {}
});

9.2 Get RPC ​

js
import {RPCHelper, Utils} from '#tklHelper';

function rpc_script({device, params, alarms, logger, org_params}) {
    const classMode = device?.shared_attrs?.class_mode || "ClassA";
    const sleepMs = classMode === "ClassA" ? 500 : 5000;
    const 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_40:  {name: "period_heart", field_name: "period_heart", unit: "s", index: 40, type: "uint32LE"},
        app_70:  {name: "period_up", field_name: "period_up", unit: "s", index: 70, type: "uint32LE"},
        app_150: {name: "addr_modbus", field_name: "addr_modbus", unit: "", index: 150, type: "uint8"}
    };
    const frames = RPCHelper.buildFrame({paraDef, params: params || {}});
    const msg = RPCHelper.makeMSG({
        msgType: Utils.msgType.paras,
        device,
        dnBuffer: frames.readBuffer,
        sleepTime: sleepMs
    });
    return [msg];
}

rpc_script({
    device: {server_attrs: {}, shared_attrs: {}},
    params: {},
    alarms: [],
    logger: console,
    org_params: {}
});

These RPCs configure KC21 application parameters only; they do not write sensor registers.

10. Template selection ​

Search ThinkLink for template MAGNETOSTRICTIVE, or use business code 22117.

11. Supplementary notes ​

  1. The implementation uses the standard binary formula high*65536+low. The manual's 65535 example differs by 0.001mm and must be checked against a known physical displacement.
  2. The document specifies a 12V/24V, ≤50mA variant. Confirm the exact supply version and whether the KC21 sensor output is acceptable.
  3. Zero/span and analogue calibration require a password and change device state, so they are not exposed.