22115-AXIAL-FORCE Axial Force Transmitter
Model catalog
Search for these models in ThinkLink by name or ID.
| Model type | Name | id_name | Platform model ID |
|---|---|---|---|
| RPC | [AXIAL-FORCE GET] 22115 | axial_force_get_22115 | 134816004424232960 |
| RPC | [AXIAL-FORCE SET] 22115 | axial_force_set_22115 | 134816003702812672 |
| Template | AXIAL-FORCE | axial_force_22115 | 134816001572106240 |
| Thing Model | [AXIAL-FORCE] | axial_force_22115 | 134816002285137920 |
| Parameter Thing Model | [AXIAL-FORCE-PARA] | axial_force_para_22115 | 134816002998169600 |
1. Sensor overview
The SX strain-gauge axial-force/load transmitter connects to a KC21 over RS-485/Modbus RTU. Its business code is 22115 and its ThinkLink template is axial_force_22115.
2. Product features
- 24-bit ADC with typical 1/10000 stability
- 10Hz/40Hz sampling and up to ten-point nonlinear correction
- Digital measurement plus monitoring for optional 4-20mA and 0-10V outputs
3. Application scope
- Axial-force monitoring for pits, bridges and structural supports
- Long-term load monitoring for cables, anchors and weighing members
- Industrial test rigs and machine load acquisition
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. Three query events: 0-3 (force and signal), 10-14 (raw force, current and voltage), and 19 (software version). Collection and upload share app_70=900s; COV is disabled.
| Address | Field | Data item | Type | Scale | Unit |
|---|---|---|---|---|---|
| 0-1 | axial_force | 实时轴力 / Axial force | int32CDAB | 1 | |
| 2-3 | signal_voltage_raw | 信号电压原始值 / Raw signal voltage | int32CDAB | 1 | |
| 10-11 | axial_force_raw | 原始轴力 / Raw axial force | int32CDAB | 1 | |
| 13 | output_current | 模拟输出电流 / Output current | uint16BE | 0.01 | mA |
| 14 | output_voltage | 模拟输出电压 / Output voltage | uint16BE | 0.01 | V |
| 19 | software_version | 软件版本 / Software version | uint16BE | 1 |
6. EdgeBus model
- port: 22
- version/dataType:
0x88/0x01 - BzType/BzVersion:
22115/1 - upPeriodIndex: 70
- addr_modbus: app_150
- Uplink length: six common-header bytes plus all selected query-result bytes
- Query logic: Three query events: 0-3 (force and signal), 10-14 (raw force, current and voltage), and 19 (software version)
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 axial-force transmitter, RS485/Modbus-RTU FC03.
// 32-bit values use low-word/high-word register order and are decoded by the
// ThinkLink payload parser. Three windows avoid undocumented registers.
const eventInfo: UserConfUPItem[] = [
{
name: "sx-axial-force",
port: 22,
version: "0x88",
dataType: "0x01",
upPeriodIndex: 70,
quInfo: [
{
protocol: "modbus",
addr: "0x01",
code: "0x03",
periodIndex: 70,
indexAPP: 150,
indexCMD: 0,
copySize: 1,
isLast: false,
listVal: [
{ start: "0x0000", end: "0x0001" },
{ start: "0x0002", end: "0x0003" },
],
},
{
protocol: "modbus",
addr: "0x01",
code: "0x03",
periodIndex: 70,
indexAPP: 150,
indexCMD: 0,
copySize: 1,
isLast: false,
listVal: [
{ start: "0x000A", end: "0x000B" },
{ start: "0x000D", end: "0x000E" },
],
},
{
protocol: "modbus",
addr: "0x01",
code: "0x03",
periodIndex: 70,
indexAPP: 150,
indexCMD: 0,
copySize: 1,
isLast: true,
listVal: [{ start: "0x0013", end: "0x0013" }],
},
],
},
];
const otaConfig = getOtaConfig({
BaudRate: 9600,
StopBits: 1,
DataBits: 8,
Checkbit: CheckbitEnum.NONE,
Battery: true,
ConfirmDuty: 60,
BzType: 22115,
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:
axial_force_22115 - Parameters:
axial_force_para_22115 - 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: 24,
rssi: true,
battery: 4,
status: 3,
tagList: [{index: 0, tag: 0x88}, {index: 1, tag: 0x01}],
};
const appInfo = [
{name: "output_current", field_name: "output_current", unit: "mA", index: 18, type: "uint16BE", coefficient: 0.01, decimal: 2},
{name: "output_voltage", field_name: "output_voltage", unit: "V", index: 20, type: "uint16BE", coefficient: 0.01, decimal: 2},
{name: "software_version", field_name: "software_version", unit: "", index: 22, type: "uint16BE", coefficient: 1, decimal: 0},
];
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 int32Cdab = (offset) => payload.readInt16BE(offset + 2) * 65536 + payload.readUInt16BE(offset);
telemetry.axial_force = int32Cdab(6);
telemetry.signal_voltage_raw = int32Cdab(10);
telemetry.axial_force_raw = int32Cdab(14);
}
telemetry._modelName = "axial_force_22115";
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: "axial_force_22115",
name: "[AXIAL-FORCE]",
parsed: result,
});
if (action) result.actions = [action];
}
return result;
}
const deviceObj = {telemetry_data: {}};
const msgObj = {
userdata: {
port: 22,
payload: Buffer.from("880100006401e2400001fc18ffffd4c0000104d202ee3039", "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 = "axial_force_set_22115";
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: "axial_force_para_22115",
name: "[AXIAL-FORCE-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": "22115",
"thingModelIdName": "axial_force_22115",
"telemetry_data": {
"snr": 13.5,
"rssi": -51,
"battery": 3.37,
"axial_force": 123456,
"signal_voltage_raw": -1000,
"axial_force_raw": 120000,
"output_current": 12.34,
"output_voltage": 7.5,
"software_version": 12345
}
}9. RPC
- Set:
axial_force_set_22115 - Get:
axial_force_get_22115
| 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-250) | | 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 = "axial_force_set_22115";
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 > 250)) {
logger.info("addr_modbus must be between 1 and 250");
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 axial-force 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 AXIAL-FORCE, or use business code 22115.
11. Supplementary notes
- 32-bit registers are decoded in low-word/high-word (CDAB) order.
- The manual conflicts on supply range and default baud rate. This firmware uses 9600; verify the nameplate and live serial settings.
- The engineering unit depends on site calibration and is intentionally left blank.