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.