22105-T100 Integration Guide
Sensor Overview
T100 is integrated with ThinkLink through modbus-rtu in edgebus-dtu mode. This guide documents the current deployable artifacts and their source-traceable versions.
| Item | Value |
|---|---|
| Project | TECHAI-22105-T100 |
| Vendor | TECHAI |
| Model | T100 |
| Business Code | 22105 |
| Integration Mode | edgebus-dtu |
| Protocol | modbus-rtu |
Model catalog
Search for these models in ThinkLink by name or ID.
| Model type | Name | id_name | Platform model ID |
|---|---|---|---|
| RPC | [T100 GET] para | t100_get_22105 | 96540340621676549 |
| RPC | [T100 SET] para | t100_set_22105 | 96540592598683653 |
| Template | T100 | 118674122111610880 | |
| Thing Model | [T100] | t100_22105 | 96539321842012165 |
| Thing Model | [TECHAI-22105-T100资产] 区域聚合 | t100_asset_22105 | 119677174411063313 |
| Parameter Thing Model | [T100-PARA] | t100_para_22105 | 111418029618655232 |
Features and Application Scope
This project covers T100 through modbus-rtu. Its capability boundary, fields, and deployable models follow the current descriptor, requirements, and model sources.
Deployment and Data Acquisition
| DTU | KC21 | | Power | battery | | LoRaWAN Class | A | | Interface | RS-485 | | Baud Rate | 9600 |
Telemetry and Register Definitions
| Field | Name | Type | Unit | Address/Source | Description |
|---|---|---|---|---|---|
| temperature | temperature | int16BE | ℃ | 0x0001 | Source field: temperature |
Parameter Definitions
| Parameter | Name | Type | Default | Range | Unit | Description |
|---|---|---|---|---|---|---|
| app_70 | period_up | uint32LE | — | 0-4294967295 | s | Source parameter: app_70 |
| app_74 | period_read | uint32LE | — | 0-4294967295 | s | Source parameter: app_74 |
| app_110 | cov_temperature | int16BE | — | -32768-32767 | ℃ | Source parameter: app_110 |
| app_150 | addr_modbus | uint8 | 1 | 1-247 | Source parameter: app_150 |
Requirements Evidence
The following key facts were extracted from the requirements source; line references support verification.
| Category | Source Fact | Source |
|---|---|---|
| Protocol/Data | - Description: T100 Modbus-RTU KC21 DTU coefficient 0.01 | requirement/requirements.md:11 |
| Deployment | - Power: battery | requirement/requirements.md:17 |
| Protocol/Data | - Protocol: modbus-rtu | requirement/requirements.md:19 |
| Protocol/Data | - Interface: RS-485 | requirement/requirements.md:20 |
| Protocol/Data | - Baud rate: 9600 | requirement/requirements.md:21 |
| Protocol/Data | Field Name Type Unit Address/Source Description | requirement/requirements.md:25 |
| Protocol/Data | t100_para_22105 addr_modbus number Modbus Address | requirement/requirements.md:48 |
| Protocol/Data | t100_set_22105 addr_modbus number Modbus Address (1-247) — | requirement/requirements.md:66 |
| Deployment | Validate model IDs, identifiers, payload fields, RPC behavior, triggers, and templates against a real device or platform test asset before production deployment. | requirement/requirements.md:91 |
| Protocol/Data | - No unresolved identity or protocol facts were detected from the descriptor. | requirement/requirements.md:95 |
| Deployment | Every item above must be checked during deployment. No missing value is promoted to a confirmed fact. | requirement/requirements.md:97 |
EdgeBus Code
The following is the current EdgeBus business 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";
////////////////////////////////////////////////////////////////////////////////////////
const eventInfo:UserConfUPItem[]=[
{
name:"t100",port:22, version:"0x86",dataType:"0x05",upPeriodIndex:70,
quInfo:[
{
protocol:"modbus",code:"0x03", periodIndex:74,//addr:"0x01",
indexAPP:150, indexCMD:0, copySize:1,isLast:false,//period:"900s",payIndex:3,ackAddrIndex:0,
listVal:[
{ start: "1", end: "1" ,covType:"Int16BE",covAppIndex:110}, //冷藏室温度,refrigerator_temp,coefficient=0.1
]
}
]
}
]
let otaConfig = getOtaConfig({
SwVersion:31,
BaudRate: 9600,
StopBits: 1,
DataBits: 8,
Checkbit: CheckbitEnum.NONE,
Battery: true,
ConfirmDuty: 60,
BzType: 22105,
BzVersion: 11
})
const MODBUS_TT = (ebModel: EBModel) => {
for (let i=0; i<eventInfo.length; i++){
let event=new EventInfoItem(eventInfo[i]);
event.upEventSetup()
event.eventInstall()
}
return JSON.stringify(ebModel, null, 2)
}
buildOtaFile(import.meta.url, otaConfig, MODBUS_TT)Thing Model and RPC Code
RPC: [T100 GET] para
id_name: t100_get_22105 · ID: 96540340621676549
import {Buffer} from "buffer";
import { BufferTKL ,PayloadParser,RPCHelper,Utils} from "#tklHelper";
function rpc_script({ device, params, alarms, logger, org_params }) {
let paraDef = {
app_70:{name: "period_up", field_name: "period_up", unit:"S", type: "uint32le"},
app_74:{name: "period_read", field_name: "period_read", unit:"S", type: "uint32le"},
app_110: {name: "cov_temperature", field_name: "cov_temperature", unit: "℃", type: "int16BE",coefficient:0.1,decimal:1},
app_150:{name: "addr_modbus", field_name: "addr_modbus", unit:"", type: "uint8"}
}
let frames=RPCHelper.buildFrame({
paraDef:paraDef,
params:params});
let msg=RPCHelper.makeMSG({
msgType:Utils.msgType.paras,
device:device,
dnBuffer:frames.readBuffer,
sleepTime:0,
})
return [msg]
}
rpc_script(
{
device:"123456",
params:{
period_modbus:900,
addr_modbus:"0x70"
}
}
)RPC: [T100 SET] para
id_name: t100_set_22105 · ID: 96540592598683653
import {Buffer} from "buffer";
import { BufferTKL ,PayloadParser,RPCHelper,Utils} from "#tklHelper";
function rpc_script({ device, params, alarms, logger, org_params }) {
let classMode = (device && device.shared_attrs && device.shared_attrs.class_mode) || "ClassA";
const rpcName="t100_set_22105"
let paraDef = {
app_70:{name: "period_up", field_name: "period_up", unit:"S", type: "uint32le"},
app_74:{name: "period_read", field_name: "period_read", unit:"S", type: "uint32le"},
app_110: {name: "cov_temperature", field_name: "cov_temperature", unit: "℃", type: "int16BE",coefficient:0.1,decimal:1},
app_150:{name: "addr_modbus", field_name: "addr_modbus", unit:"", type: "uint8"}
}
let frames=RPCHelper.buildFrame({
paraDef:paraDef,
params:params});
let redoBuffer=RPCHelper.redo()
let dnBuffer=Buffer.alloc(frames.writeBuffer.length+frames.readBuffer.length);
frames.writeBuffer.copy(dnBuffer,0)
frames.readBuffer.copy(dnBuffer,frames.writeBuffer.length)
logger.info("set para")
let msgQue=Utils.makeParaSetMSG({
device:device,
classMode:classMode,
rpcName:rpcName,
params:params,
paraDownBuffer:dnBuffer,
extraAppBuffer:redoBuffer
})
if (msgQue.length==0) return null
return msgQue
}Thing Model: [T100]
id_name: t100_22105 · ID: 96539321842012165
import {BufferTKL, PayloadParser, RPCHelper} from "#tklHelper";
function payload_parser(device, msg, thingModelId, noticeAttrs, org_params) {
let port=msg?.userdata?.port || null;
if(port!==22) return null
let frameInfo={
port:22, dataLen:-1,rssi:true,battery:4,
tagList:[{ index:0, tag:0x86}, { index:1, tag:0x05}]
}
let appInfo = [
{ name: "temperature", field_name: "temperature", unit:"℃", index: 6, type: "int16BE",coefficient:0.01,decimal:2},
]
let payParser=new PayloadParser({
device:device,
msg:msg,
frameInfo:frameInfo,
appInfo:appInfo,
})
let tdata= payParser.telemetry()
if((tdata?.status&0x02)===0x02) { // time out , just update the status.
const status=tdata.status
tdata={ ...(device.telemetry_data?.[thingModelId] ?? {})}
tdata.status=status
}
let result = {
telemetry_data: tdata,
server_attrs: null,
shared_attrs: null
};
// ── rpcHook (设备对接通用规则) ──
if (typeof RPCHelper.buildRpcHookAction === "function") {
const __a = RPCHelper.buildRpcHookAction({ device, msg, thingModelId, idName: "t100_22105", name: "[T100]", parsed: { telemetry_data: result.telemetry_data, server_attrs: result.server_attrs, shared_attrs: result.shared_attrs } });
if (__a) { result.actions = result.actions || []; result.actions.push(__a); }
}
return result;
}
console.log(payload_parser(
{},
{
"if": "loraWAN",
"gwrx": [
{
"eui": "5a5301250103020d",
"chan": 0,
"lsnr": 12.8,
"rfch": 0,
"rssi": -24,
"time": "2026-02-27T03:21:37.8216721Z",
"tmms": 0,
"tmst": 2799281240,
"ftime": 0
}
],
"type": "data",
"token": 2693015,
"moteTx": {
"codr": "4/5",
"datr": "SF9BW125",
"freq": 470.3,
"modu": "LORA",
"macAck": "",
"macCmd": ""
},
"geoInfo": {
"type": "gw:wifi",
"accuracy": 50,
"altitude": 0,
"latitude": 34.20012,
"longitude": 108.86424
},
"moteeui": "6353012af10a5685",
"version": "3.0",
"userdata": {
"port": 22,
"class": "ClassC",
"seqno": 20001,
"payload": "gzQAAgAAAAAAAAAAAAA=",
"confirmed": false
}
},
"1",
{telemetry_data:true},
{}
))Thing Model: t100_asset_22105
id_name: t100_asset_22105 · ID: 119677174411063313
import {PayloadParser, RPCHelper} from '#tklHelper';
// 资产(VIRTUAL)聚合物模型 — 通用 agg_spec 驱动聚合器,见 docs/references/workflow/asset-aggregation-model.md
// device=资产, msg=源设备快照, thingModelId=本物模型ID, noticeAttrs=变更标记。
// 聚合规格在资产 server_attrs.agg_spec:min_snr=取最小(snr);min_rssi=取最小(rssi);min_battery=取最小(battery);zone_temperature_avg=空间均值(temperature);online_count=在线数(—)
function payload_parser(device, msg, thingModelId, noticeAttrs) {
if (!noticeAttrs || !noticeAttrs.telemetry_data) {
return { telemetry_data: null, server_attrs: null, shared_attrs: null };
}
const sa = device?.server_attrs || {};
const spec = sa.agg_spec || {};
const members = { ...(sa.members || {}) };
const staleMs = sa.stale_ms != null ? sa.stale_ms : 600000;
const now = (typeof Date !== "undefined" && Date.now) ? Date.now() : null;
// 合并源设备各物模型的最新快照为一个扁平对象
const srcTd = {};
const all = msg?.telemetry_data || {};
for (const k in all) { Object.assign(srcTd, all[k]); }
// 收集 spec 引用到的全部源字段,把本帧带到的值并进该成员快照
const want = {};
for (const out in spec) { if (spec[out].src) want[spec[out].src] = true; }
const got = {}; let touched = false;
for (const f in want) { if (srcTd[f] !== undefined && srcTd[f] !== null) { got[f] = srcTd[f]; touched = true; } }
if (touched && msg && msg.eui) {
const prev = (members[msg.eui] && members[msg.eui].v) || {};
const seq = (members[msg.eui] && members[msg.eui].ts) || 0;
members[msg.eui] = { v: Object.assign({}, prev, got), ts: now != null ? now : seq + 1 };
}
const euis = Object.keys(members);
const fresh = euis.filter(function (e) {
if (now == null || staleMs <= 0) return true;
const ts = members[e].ts;
return ts != null && ts <= now && (now - ts) <= staleMs;
});
const num = function (x) { return (x != null && !isNaN(x)) ? Number(x) : null; };
const out = {};
for (const field in spec) {
const op = spec[field].op, src = spec[field].src;
if (op === "count_online") { out[field] = fresh.length; continue; }
const pool = (op === "sum_cumul") ? euis : fresh;
const vals = pool.map(function (e) { return num(members[e].v && members[e].v[src]); }).filter(function (x) { return x != null; });
if (op === "avg") out[field] = vals.length ? Number((vals.reduce(function (a, b) { return a + b; }, 0) / vals.length).toFixed(2)) : null;
else if (op === "sum_snap" || op === "sum_cumul") out[field] = vals.length ? Number(vals.reduce(function (a, b) { return a + b; }, 0).toFixed(3)) : null;
else if (op === "max") out[field] = vals.length ? Math.max.apply(null, vals) : null;
else if (op === "min") out[field] = vals.length ? Math.min.apply(null, vals) : null;
else if (op === "or") out[field] = pool.some(function (e) { const x = members[e].v && members[e].v[src]; return x != null && x != 0; }) ? 1 : 0;
else if (op === "count_on") out[field] = pool.filter(function (e) { const x = members[e].v && members[e].v[src]; return x != null && x != 0; }).length;
else out[field] = null;
}
let result = { telemetry_data: out, server_attrs: { members: members }, shared_attrs: null };
// ── rpcHook (设备对接通用规则) ──
if (typeof RPCHelper.buildRpcHookAction === "function") {
const __a = RPCHelper.buildRpcHookAction({ device, msg, thingModelId, idName: "t100_asset_22105", name: "[TECHAI-22105-T100资产] 区域聚合", parsed: { telemetry_data: result.telemetry_data, server_attrs: result.server_attrs, shared_attrs: result.shared_attrs } });
if (__a) { result.actions = result.actions || []; result.actions.push(__a); }
}
return result;
}
// --- local debug ---(两台成员先后上报;平台会把返回的 members 持久化回 device,这里手动模拟)
const agg_spec = {
"min_snr": {
"op": "min",
"src": "snr"
},
"min_rssi": {
"op": "min",
"src": "rssi"
},
"min_battery": {
"op": "min",
"src": "battery"
},
"zone_temperature_avg": {
"op": "avg",
"src": "temperature"
},
"online_count": {
"op": "count_online",
"src": ""
}
};
const device = { name: "TECHAI-22105-T100资产-1", server_attrs: { agg_spec, stale_ms: 1800000, members: {} } };
const r1 = payload_parser(device, { eui: "MEMBER_A", telemetry_data: { m1: {"snr":1,"rssi":1,"battery":1,"temperature":1} } }, "tmid", { telemetry_data: true });
device.server_attrs.members = r1.server_attrs.members; // 平台持久化
console.log("成员A上报后:", JSON.stringify(r1.telemetry_data));
const r2 = payload_parser(device, { eui: "MEMBER_B", telemetry_data: { m1: {"snr":2,"rssi":2,"battery":2,"temperature":2} } }, "tmid", { telemetry_data: true });
device.server_attrs.members = r2.server_attrs.members;
console.log("成员B上报后(2台聚合):", JSON.stringify(r2.telemetry_data));
void PayloadParser;Parameter Thing Model: [T100-PARA]
id_name: t100_para_22105 · ID: 111418029618655232
import {PayloadParser, RPCHelper, Utils} from "#tklHelper";
function payload_parser(device, msg, thingModelId, noticeAttrs, org_params) {
let port=msg?.userdata?.port || null;
const rpcName="t100_set_22105";
let paraDef = {
app_70:{name: "period_up", field_name: "period_up", unit:"S", type: "uint32le"},
app_74:{name: "period_read", field_name: "period_read", unit:"S", type: "uint32le"},
app_110: {name: "cov_temperature", field_name: "cov_temperature", unit: "℃", type: "int16BE",coefficient:0.1,decimal:1},
app_150:{name: "addr_modbus", field_name: "addr_modbus", unit:"", type: "uint8"}
}
if (port!==214) {
let checkData=Utils.paraCheck(rpcName,device.server_attrs,device.shared_attrs)
return {
server_attrs: checkData.sdata,
actions:checkData.actions,
}
}
let pdata=(new PayloadParser({
device:device,
msg:msg,
paraInfo:paraDef,
})).paras()
let checkData= Utils.paraCheck(rpcName,device.server_attrs,pdata)
let result = {
telemetry_data: pdata,
server_attrs: checkData.sdata,
shared_attrs: pdata,
actions: checkData.actions,
};
// ── rpcHook (设备对接通用规则) ──
if (typeof RPCHelper.buildRpcHookAction === "function") {
const __a = RPCHelper.buildRpcHookAction({ device, msg, thingModelId, idName: "t100_para_22105", name: "[T100-PARA]", parsed: { telemetry_data: result.telemetry_data, server_attrs: result.server_attrs, shared_attrs: result.shared_attrs } });
if (__a) { result.actions = result.actions || []; result.actions.push(__a); }
}
return result;
}
console.log(payload_parser(
{},
{
"if": "loraWAN",
"gwrx": [
{
"eui": "5a5301250103021e",
"chan": 0,
"lsnr": 14.2,
"rfch": 1,
"rssi": -54,
"time": "2026-03-25T01:59:11.8596759Z",
"tmms": 0,
"tmst": 878152180,
"ftime": 0
}
],
"type": "data",
"token": 132751,
"moteTx": {
"codr": "4/5",
"datr": "SF7BW125",
"freq": 471.5,
"modu": "LORA",
"macAck": " [down] 6097990132204c001136b35e",
"macCmd": ""
},
"geoInfo": {
"type": "gw:wifi",
"accuracy": 50,
"altitude": 0,
"latitude": 34.19907,
"longitude": 108.86571
},
"moteeui": "6353012af10b4669",
"version": "3.0",
"userdata": {
"adr": false,
"port": 214,
"class": "ClassA",
"seqno": 1196,
"payload": "LwUEAwAAAC8DCgEBLwpGCBAOAAAQDgAALwOWAQE=",
"confirmed": true
}
},
"1",
{telemetry_data:true},
{}
))Thing Model Field Definitions
| Thing Model | Field | Type | Unit | Name |
|---|---|---|---|---|
| t100_22105 | snr | number | dB | SNR |
| t100_22105 | rssi | number | dBm | RSSI |
| t100_22105 | ratio | number | Ratio | |
| t100_22105 | battery | number | v | Battery |
| t100_22105 | temperature | number | ℃ | Temperature |
| t100_asset_22105 | min_snr | number | dB | Zone SNR |
| t100_asset_22105 | min_rssi | number | dBm | Zone RSSI |
| t100_asset_22105 | min_battery | number | v | Zone Battery |
| t100_asset_22105 | zone_temperature_avg | number | ℃ | Zone Temperature |
| t100_asset_22105 | online_count | number | Zone Online Count | |
| t100_para_22105 | snr | number | dB | SNR |
| t100_para_22105 | rssi | number | dBm | RSSI |
| t100_para_22105 | period_up | number | s | Upload Period |
| t100_para_22105 | period_read | number | s | Collect Period |
| t100_para_22105 | cov_temperature | number | ℃ | Temperature Change Threshold |
| t100_para_22105 | addr_modbus | number | Modbus Address |
RPC Parameter Definitions
| RPC | Parameter | Type | Description | Default | Unit |
|---|---|---|---|---|---|
| t100_set_22105 | period_up | number | Upload Period (s) | — | s |
| t100_set_22105 | period_read | number | Collect Period (s) | — | s |
| t100_set_22105 | cov_temperature | number | Temperature Change Threshold | — | ℃ |
| t100_set_22105 | addr_modbus | number | Modbus Address (1-247) | — |
Trigger Definitions
No project-specific triggers are defined.
Third-Party Platform Subscription
MQTT Topic: /v32/{Organization Account}/tkl/up/telemetry/{eui}
{
"eui": "6353012af10a9331",
"active_time": "2026-07-13T08:35:48.000Z",
"thingModelId": "Use the platform model ID from the model inventory",
"thingModelIdName": "Use the current telemetry model id_name",
"telemetry_data": {
"snr": 13.5,
"rssi": -51,
"battery": 3.37,
"temperature": 1
}
}Template Selection and Verification
Search ThinkLink by project name, business code, or the id_name and ID values in the model inventory. After deployment, use real uplinks to verify fields, units, RPCs, and trigger behavior.
Supplementary Notes
Requirements and decisions: requirement/requirements.md.