Skip to content

22116-HCA396L-S2 Dual-axis High-precision Inclinometer

Model catalog

Search for these models in ThinkLink by name or ID.

Model typeNameid_namePlatform model ID
RPC[HCA396L-S2 GET] 22116hca396l_s2_get_22116134816007947448320
RPC[HCA396L-S2 SET] 22116hca396l_s2_set_22116134816007255388160
TemplateHCA396L-S2hca396l_s2_22116134816005137264640
Thing Model[HCA396L-S2]hca396l_s2_22116134816005846102016
Parameter Thing Model[HCA396L-S2-PARA]hca396l_s2_para_22116134816006567522304

1. Sensor overview

The RION / 瑞芬科技 HCA396L-S2-MB dual-axis high-precision inclinometer connects to a KC21 over RS-485/Modbus RTU. Its business code is 22116 and its ThinkLink template is hca396l_s2_22116.

2. Product features

  1. Dual-axis ±85° range, 0.001° resolution and 0.01° room-temperature accuracy
  2. 5Hz bandwidth and -40 to 85℃ operating range
  3. 100g shock resistance with azimuth and temperature output

3. Application scope

  1. Foundation-pit, slope and shield-construction monitoring
  2. Bridge, railway and construction-machine attitude monitoring
  3. Geological drilling and underground-equipment attitude sensing

4. Collector information

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

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 FC04. One query event reads eight consecutive registers from 0x0016 for X/Y inclination, azimuth and temperature. Collection and upload share app_70=900s; COV is disabled.

AddressFieldData itemTypeScaleUnit
0x0016-0x0017inclination_xX轴俯仰角 / X inclinationfloat32ABCD1°
0x0018-0x0019inclination_yY轴横滚角 / Y inclinationfloat32ABCD1°
0x001A-0x001Bazimuth方位角 / Azimuthfloat32ABCD1°
0x001C-0x001Dtemperature温度 / Temperaturefloat32ABCD1

6. EdgeBus model

  • port: 22
  • version/dataType: 0x88/0x01
  • BzType/BzVersion: 22116/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 eight consecutive registers from 0x0016 for X/Y inclination, azimuth and temperature

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";

// RION HCA396L-S2-MB, RS485/Modbus-RTU FC04.
// 0x0016-0x001D contains X angle, Y angle, azimuth, and temperature as
// four big-endian IEEE754 float32 values.
const eventInfo: UserConfUPItem[] = [
  {
    name: "rion-hca396l-s2",
    port: 22,
    version: "0x88",
    dataType: "0x01",
    upPeriodIndex: 70,
    quInfo: [
      {
        protocol: "modbus",
        addr: "0x01",
        code: "0x04",
        periodIndex: 70,
        indexAPP: 150,
        indexCMD: 0,
        copySize: 1,
        isLast: true,
        listVal: [{ start: "0x0016", end: "0x001D" }],
      },
    ],
  },
];

const otaConfig = getOtaConfig({
  BaudRate: 9600,
  StopBits: 1,
  DataBits: 8,
  Checkbit: CheckbitEnum.NONE,
  Battery: true,
  ConfirmDuty: 60,
  BzType: 22116,
  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: hca396l_s2_22116
  • Parameters: hca396l_s2_para_22116
  • 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: 22,
        rssi: true,
        battery: 4,
        status: 3,
        tagList: [{index: 0, tag: 0x88}, {index: 1, tag: 0x01}],
    };
    const appInfo = [
        {name: "inclination_x", field_name: "inclination_x", unit: "°", index: 6, type: "floatBE", coefficient: 1, decimal: 3},
        {name: "inclination_y", field_name: "inclination_y", unit: "°", index: 10, type: "floatBE", coefficient: 1, decimal: 3},
        {name: "azimuth", field_name: "azimuth", unit: "°", index: 14, type: "floatBE", coefficient: 1, decimal: 3},
        {name: "temperature", field_name: "temperature", unit: "℃", index: 18, type: "floatBE", coefficient: 1, decimal: 2},
    ];
    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;
    }
    telemetry._modelName = "hca396l_s2_22116";
    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: "hca396l_s2_22116",
            name: "[HCA396L-S2]",
            parsed: result,
        });
        if (action) result.actions = [action];
    }
    return result;
}

const deviceObj = {telemetry_data: {}};
const msgObj = {
    userdata: {
        port: 22,
        payload: Buffer.from("880100006401c08f126f3efae1484316199a42040000", "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 = "hca396l_s2_set_22116";
    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: "hca396l_s2_para_22116",
            name: "[HCA396L-S2-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": "22116",
  "thingModelIdName": "hca396l_s2_22116",
  "telemetry_data": {
    "snr": 13.5,
    "rssi": -51,
    "battery": 3.37,
    "inclination_x": -4.471,
    "inclination_y": 0.49,
    "azimuth": 150.1,
    "temperature": 33
  }
}

9. RPC

  • Set: hca396l_s2_set_22116
  • Get: hca396l_s2_get_22116
APP slotFieldMeaningUnitTypeDefault

| app_20 | TimeOffset | Time offset | | uint32le | 0 | | app_38 | pwron_delay | Power-on delay | ms | uint16le | 2500 | | 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 = "hca396l_s2_set_22116";
    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 < 2000) {
        params.pwron_delay = 2500;
    }

    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 HCA396L-S2 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: 2500},
    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 HCA396L-S2, or use business code 22116.

11. Supplementary notes

  1. This integration is for the HCA396L-S2-MB Modbus variant, not the private-protocol -77 variant.
  2. Reads during the first approximately two seconds may return an exception, so the power-on delay is 2500ms.
  3. Configuration writes require a password and may change the measurement reference; no sensor-register writes are exposed.