22117-MAGNETOSTRICTIVE Magnetostrictive Displacement and Level Sensor
Model catalog
Search for these models in ThinkLink by name or ID.
| Model type | Name | id_name | Platform model ID |
|---|---|---|---|
| RPC | [MAGNETOSTRICTIVE GET] 22117 | magnetostrictive_get_22117 | 134816011390971904 |
| RPC | [MAGNETOSTRICTIVE SET] 22117 | magnetostrictive_set_22117 | 134816010694717440 |
| Template | MAGNETOSTRICTIVE | magnetostrictive_22117 | 134816008626925568 |
| Thing Model | [MAGNETOSTRICTIVE] | magnetostrictive_22117 | 134816009310597120 |
| Parameter Thing Model | [MAGNETOSTRICTIVE-PARA] | magnetostrictive_para_22117 | 134816010011045888 |
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
- ±0.001mm repeatability
- Absolute-position output without re-homing after a restart
- RS-485 Modbus RTU or 4-20mA variants
3. Application scope
- Reservoir, tank and process-vessel level monitoring
- Hydraulic-cylinder, gate and actuator displacement monitoring
- 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.
| Address | Field | Data item | Type | Scale | Unit |
|---|---|---|---|---|---|
| 0-1 | displacement | 位移/液位 / Displacement or level | uint32WordsLowHigh | 0.001 | mm |
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
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
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
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}
{
"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 slot | Field | Meaning | Unit | Type | Default |
|---|
| 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
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
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
- 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. - The document specifies a 12V/24V, ≤50mA variant. Confirm the exact supply version and whether the KC21 sensor output is acceptable.
- Zero/span and analogue calibration require a password and change device state, so they are not exposed.