1. Sensor Overview
The AQ3485 is a dual-channel temperature sensor manufactured by AoQing (奥青). It uses a Modbus-RTU / RS-485 interface to simultaneously read refrigerator temperature and defrost temperature from a cold-storage unit. This integration connects the sensor to the ThinkLink platform via a KC21 (battery-powered) DTU, with optional COV (change-of-value) triggered uplinks.
Integration details: model AQ3485, business code 21306, template name AQ3485.
2. Product Features
- Dual-channel temperature acquisition: refrigerator temperature + defrost temperature
- RS-485 interface, Modbus-RTU protocol
- Default slave address
0x01, baud rate 9600, 8N1 - Independent COV thresholds for each temperature channel
- Low power design — battery-powered KC21 DTU
3. Applications
The AQ3485 is suited for the following scenarios:
- Cold cabinet monitoring in supermarkets and convenience stores
- Refrigeration room and defrost temperature monitoring in cold-storage warehouses
- Cold chain logistics temperature management
- Cold storage equipment monitoring in food and pharmaceutical industries
4. Data Collector
4.1 Hardware Specifications
This solution uses the KC21 (battery version) DTU.
- Model: KC21
- Interface: RS-485
- Power supply: Battery
- LoRaWAN Class: A
4.2 Wiring
Power and Communication Interface
The KC21 battery version supplies power to the sensor over the RS-485 bus:
- KC21 A terminal → RS-485A (sensor)
- KC21 B terminal → RS-485B (sensor)
Sensor Interface
The AQ3485 uses a standard RS-485 interface with default settings: 9600 baud, 8N1, no parity. Connect directly to the KC21 RS-485 port.
5. Data Acquisition
This solution reads the following Modbus registers:
0x0000: Refrigerator temperature (Int16BE, × 0.1 °C)0x0001: Defrost temperature (Int16BE, × 0.1 °C)
Platform parameter address conventions:
- Upload interval: App address 70
- Acquisition interval: App address 74
- Power-on delay: App address 38
5.1 Register Map
| Address (hex) | Description | Data Type | Notes |
|---|---|---|---|
0x0000 | Refrigerator temperature | Int16BE (signed, big-endian) | Raw value × 0.1 = actual temperature (°C) |
0x0001 | Defrost temperature | Int16BE (signed, big-endian) | Raw value × 0.1 = actual temperature (°C) |
6. EdgeBus Model
6.1 EB Configuration
| Parameter | Value |
|---|---|
name | "AQ3485" |
port | 21 |
version | "0x85" |
dataType | "0x03" |
upPeriodIndex | 70 |
periodIndex (acquisition) | 74 |
BaudRate | 9600 |
StopBits | 1 |
DataBits | 8 |
Checkbit | NONE |
Battery | true |
BzType | 21306 |
BzVersion | 12 |
SwVersion | 31 |
6.2 EB Code
import {Buffer} from "buffer";
import {buildOtaFile} from "@EBSDK/run";
import {EBModel, EventInfoItem, LoraUpEvent, UserConfUPItem, CheckbitEnum, getOtaConfig, HwTypeEnum} from "@EBSDK/EBCompiler/all_variable";
////////////////////////////////////////////////////////////////////////////////////////
const eventInfo:UserConfUPItem[]=[
{
name:"AQ3485",port:21, version:"0x85",dataType:"0x03",upPeriodIndex:70,
quInfo:[
{
protocol:"modbus",code:"0x03", periodIndex:74,//addr:"0x01",
indexAPP:150, indexCMD:0, copySize:1,isLast:false,
listVal:[
{ start: "1", end: "1" ,covType:"Int16BE",covAppIndex:110}, // refrigerator temp, coefficient=0.1
{ start: "2", end: "2" ,covType:"Int16BE",covAppIndex:112} // defrost temp, coefficient=0.1
]
}
]
}
]
let otaConfig = getOtaConfig({
SwVersion:31,
BaudRate: 9600,
StopBits: 1,
DataBits: 8,
Checkbit: CheckbitEnum.NONE,
Battery: true,
ConfirmDuty: 60,
BzType: 21306,
BzVersion: 12
})
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)6.3 Notes
EB logic summary:
- Uplinks are sent on port 21.
- A single query event reads registers 0x0000 and 0x0001 in one Modbus FC03 request (2 contiguous registers, 4 bytes total).
- Upload interval and acquisition interval are separated:
- App address
70= upload interval - App address
74= acquisition interval
- App address
- COV triggers: refrigerator temperature (App
110) and defrost temperature (App112) each have independent COV thresholds. An uplink is triggered immediately when a temperature change exceeds its threshold. - The Modbus slave address is managed by App parameter
150and can be changed at runtime. - Data format: Int16BE (signed, big-endian); divide by 10 to get temperature in °C.
7. Thing Model
7.1 Thing Model Information
Telemetry Thing Model
- Name:
[AQ3485] - id Name:
aq3485_21306
7.2 Uplink Frame Structure
frameInfo: { port: 21, dataLen: -1, rssi: true, battery: 4 }
tagList: [{ index: 0, tag: 0x85 }, { index: 1, tag: 0x03 }]Uplink frame field layout (business data starts at byte 6):
| Offset (index) | Field | Type | Unit | Description |
|---|---|---|---|---|
| 0 | version | uint8 | — | Protocol version, fixed 0x85 |
| 1 | dataType | uint8 | — | Business type, fixed 0x03 |
| 2 | covStatus | uint8 | — | COV trigger status |
| 3 | status | uint8 | — | Query event status |
| 4 | battery | uint8 | — | Battery level (4-byte encoded) |
| 5 | addr | uint8 | — | Modbus slave address |
| 6–7 | humidity | Int16BE | °C × 0.1 | Refrigerator temperature (field_name: humidity) |
| 8–9 | temperature | Int16BE | °C × 0.1 | Defrost temperature (field_name: temperature) |
Field naming note: The
humidityfield contains refrigerator temperature, andtemperaturecontains defrost temperature. These names reflect legacy platform registration and do not affect functionality.
7.3 Thing Model Script
7.3.1 Telemetry Thing Model Script
import {PayloadParser} from '#tklHelper';
function payload_parser({device, msg, thingModelId, noticeAttrs}) {
let port=msg?.userdata?.port || null;
let frameInfo={
port:21, dataLen:-1,rssi:true,battery:4,
tagList:[{ index:0, tag:0x85}, { index:1, tag:0x03}]
}
let appInfo = [
{ name: "humidity", field_name: "humidity", unit:"℃", index: 6, type: "int16BE",coefficient:0.1,decimal:1},
{ name: "temperature", field_name: "temperature", unit:"℃", index: 8, type: "int16BE",coefficient:0.1,decimal:1},
]
let paraInfo= {
app_70: {name: "periodUP", field_name: "periodUP", unit: "s", type: "uint32LE"},
app_74: {name: "periodRead", field_name: "periodRead", unit: "s", type: "uint32LE"},
app_110: {name: "cov_temperature", field_name: "cov_temperature", unit: "℃", type: "int16BE",coefficient:0.1,decimal:1},
app_112: {name: "cov_humidity", field_name: "cov_humidity", unit: "℃", type: "int16BE",coefficient:0.1,decimal:1},
app_150: {name: "addr", field_name: "addr", unit: "", type: "uint8"},
}
let pdata={}
let tdata={}
let payParser=new PayloadParser({
device:device,
msg:msg,
frameInfo:frameInfo,
appInfo:appInfo,
paraInfo:paraInfo,
})
if (port===214) {
pdata=payParser.paras()
tdata=null
}else {
tdata= payParser.telemetry()
pdata=null
}
return {
telemetry_data: tdata,
server_attrs: null,
shared_attrs: pdata
}
}8. Third-Party Platform Data Subscription
8.1 MQTT Topic
/v32/{Organization Account}/tkl/up/telemetry/{eui}
8.2 Example Payload
{
"eui": "6353012af10a9331",
"active_time": "2026-02-05T08:35:48.000Z",
"thingModelId": "87554295540486149",
"thingModelIdName": "aq3485_21306",
"telemetry_data": {
"snr": 10.5,
"rssi": -65,
"battery": 3.21,
"humidity": -5.2,
"temperature": 18.3
}
}Notes:
thingModelIduses the platform-assigned value (87554295540486149).thingModelIdNamemaps to the telemetry thing modelaq3485_21306.humidityrepresents refrigerator temperature (°C); example value-5.2 °Cis a typical cold-storage reading.temperaturerepresents defrost temperature (°C); example value18.3 °Crepresents ambient temperature during a defrost cycle.
9. RPC
9.1 RPC Names
Set Parameters RPC
- Name:
[AQ3485 SET] para - id Name:
AQ3485_21306_set
Get Parameters RPC
- Name:
[AQ3485 GET] para - id Name:
aq3485_21306_get
9.2 Parameter Definition
| App Address | Parameter Name | field_name | Unit | Type | Description |
|---|---|---|---|---|---|
| app_38 | pwron_delay | pwron_delay | ms | uint16le | Power-on delay before querying the sensor |
| app_70 | periodUP | periodUP | s | uint32LE | Upload interval |
| app_74 | periodRead | periodRead | s | uint32LE | Acquisition interval |
| app_110 | cov_temperature | cov_temperature | °C | int16BE | Defrost temp COV threshold (× 0.1 °C) |
| app_112 | cov_humidity | cov_humidity | °C | int16BE | Refrigerator temp COV threshold (× 0.1 °C) |
| app_150 | addr | addr | — | uint8 | Modbus slave address (default: 1) |
9.3 RPC Scripts
Set Parameters RPC
import {RPCHelper, Utils} from '#tklHelper';
import {Buffer} from "buffer";
function rpc_script({device, params, alarms, logger}) {
let paraDef= {
app_38: { name: "pwron_delay", field_name: "pwron_delay", unit: "ms", index: 38, type: "uint16le" },
app_70: {name: "periodUP", field_name: "periodUP", unit: "s", type: "uint32LE"},
app_74: {name: "periodRead", field_name: "periodRead", unit: "s", type: "uint32LE"},
app_110: {name: "cov_temperature", field_name: "cov_temperature", unit: "℃", type: "int16BE",coefficient:0.1,decimal:1},
app_112: {name: "cov_humidity", field_name: "cov_humidity", unit: "℃", type: "int16BE",coefficient:0.1,decimal:1},
app_150: {name: "addr", field_name: "addr", 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+redoBuffer.length);
frames.writeBuffer.copy(dnBuffer,0)
frames.readBuffer.copy(dnBuffer,frames.writeBuffer.length)
redoBuffer.copy(dnBuffer,frames.writeBuffer.length+frames.readBuffer.length);
logger.info("set para")
let msg=RPCHelper.makeMSG({
msgType:Utils.msgType.paras,
device:device,
dnBuffer:dnBuffer,
sleepTime:0,
})
return [msg]
}Get Parameters RPC
import {RPCHelper, Utils} from '#tklHelper';
function rpc_script({device, params, alarms, logger}) {
let paraDef= {
app_38: { name: "pwron_delay", field_name: "pwron_delay", unit: "ms", index: 38, type: "uint16le" },
app_70: {name: "periodUP", field_name: "periodUP", unit: "s", type: "uint32LE"},
app_74: {name: "periodRead", field_name: "periodRead", unit: "s", type: "uint32LE"},
app_110: {name: "cov_temperature", field_name: "cov_temperature", unit: "℃", type: "int16BE",coefficient:0.1,decimal:1},
app_112: {name: "cov_humidity", field_name: "cov_humidity", unit: "℃", type: "int16BE",coefficient:0.1,decimal:1},
app_150: {name: "addr", field_name: "addr", 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]
}10. Template Selection
Search for the following template in the ThinkLink platform: AQ3485
Or browse by business type: 21306 / Dual-Channel Temperature Sensor / RS-485 Modbus Cold-Storage Monitoring
11. Additional Notes
- Field naming: The
humidityfield contains refrigerator temperature;temperaturecontains defrost temperature. These names stem from legacy platform registration. - Manufacturer default communication settings: address
1, baud rate 9600, 8 data bits, no parity, 1 stop bit. - COV thresholds:
cov_temperature(app_110) applies to defrost temperature;cov_humidity(app_112) applies to refrigerator temperature. Both are in units of 0.1 °C — a threshold of10means 1.0 °C. - Power-on delay:
pwron_delay(app_38) is the wait time in milliseconds after KC21 powers on before the first Modbus query is issued.