DS1603 Ultrasonic Level Sensor Integration Guide
1. Sensor Overview
DS1603 is an ultrasonic continuous level sensor manufactured by Shenzhen Xingkechuang Technology Co., Ltd. It uses non-contact ultrasonic sensing to detect liquid levels through container walls, and outputs an RS485 (Modbus-RTU) signal. It is suitable for continuous liquid level monitoring of various industrial storage tanks and water treatment pools. The device has an IP67 protection rating, can penetrate non-metallic container walls up to 20 mm thick, and performs measurement without contacting the liquid.
In this integration solution, the device model is DS1603, the business code is 22109, and the template name is DS1603.
2. Product Features
According to the manufacturer's documentation, the DS1603 has the following features:
- Non-contact measurement — penetrates plastic, glass, and ceramic container walls without the need for drilling
- Corrosion resistant — does not contact strong acids or alkalis, ensuring long service life
- Anti-interference — specially designed to handle power frequency and common-mode interference
- High precision — millimeter-level resolution (±1.5 mm), real-time continuous output
- Universal liquid compatibility — unaffected by liquid density, shape, impurities, foam, or scale
- IP67 rated — suitable for humid and outdoor environments
- RS485 Modbus RTU — supports multi-point serial connection (up to 32 nodes)
3. Application Scope
DS1603 can be widely used in the following scenarios:
- Industrial storage tank level monitoring — acid, alkali, and solvent tanks in chemical and electroplating plants
- Water treatment level monitoring — real-time level monitoring for water towers, water tanks, and wastewater treatment pools
- Multi-tank centralized monitoring — RS485 bus master-slave architecture for centralized management of multiple tanks
- Agricultural irrigation water level management — nutrient solution tanks and irrigation water tanks in greenhouses
4. Collector Information
4.1 Hardware Information
The data acquisition device used in this solution is KC21.
- Device model: KC21
- Interface: RS-485
- Power supply: Built-in battery 10800 mAH / DC 5–12V
4.2 Wiring Information
Power Supply and Communication Interface
The RS485 output wiring definition of DS1603 is as follows:
- Pin 1: VCC (12–24V DC power positive)
- Pin 2: GND (power negative)
- Pin 3: RS485 A (+)
- Pin 4: RS485 B (-)
Sensor Interface
In this solution, the DS1603 has a built-in RS485 output interface and can be directly connected to the RS485 interface of KC21. The connector is a 2.54 mm pitch 4-pin header.
5. Data Acquisition
In this solution, the following register is read through Modbus:
- 0x00: Processed liquid level, uint16BE, unit: mm (only this register is collected)
At the platform side, the following parameter address is defined:
- Upload/collection period parameter address: 70 (shared period for both)
5.1 Register Definitions
According to the manufacturer's Modbus register table (function code 03H read / 06H write), the register definitions are as follows:
| Address | Item Description | Data Type | R/W | Description |
|---|---|---|---|---|
| 0x00 | Processed level | uint16BE | R | Stabilized level after multi-sample processing, unit: mm (collected) |
| 0x01 | Realtime level | uint16BE | R | Instantaneous level per sample, unit: mm (not collected) |
| 0x04 | Slave address | uint16BE | R/W | Modbus slave address, default 1, range 1–247, retained on power loss |
| 0x05 | Measurement medium | uint16BE | R/W | 1=water, 2=oil, default 1 |
| 0x06 | Work period | uint16BE | R/W | Works once every N seconds, N=1–60s, default 2s |
Note: the device has no baud-rate register; the serial port is fixed at 9600,N,8,1.
Supported Modbus function codes:
- 03H: Read Holding Registers
- 06H: Write Single Register
5.2 Communication Example
Read processed liquid level (0x00):
Request: 01 03 00 00 00 01 84 0A
Response: 01 03 02 00 DD F9 85 (Example: processed value = 0x00DD = 221 mm)Write measurement medium (0x05) — set to oil:
Request: 01 06 00 05 00 02 18 0A
Response: 01 06 00 05 00 02 18 0A (echo of the request = success)6. EdgeBus Model
6.1 EB Configuration Parameters
The main configuration parameters are as follows:
name:"xkc-ds1603"port:22version:"0x87"dataType:"0x10"upPeriodIndex:70- Default Modbus serial parameters:
BaudRate:9600StopBits:1DataBits:8Checkbit:NONE
BzType:22109BzVersion:1
6.2 EB 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: "xkc-ds1603", port: 22, version: "0x87", dataType: "0x10", upPeriodIndex: 70,
quInfo: [
{
protocol: "modbus", addr: "0x01", code: "0x03", periodIndex: 70,
indexAPP: 150, indexCMD: 0, copySize: 1,
listVal: [
{ start: "0", end: "1" }
]
}
]
}
];
let otaConfig = getOtaConfig({
BaudRate: 9600,
StopBits: 1,
DataBits: 8,
Checkbit: CheckbitEnum.NONE,
Battery: true,
ConfirmDuty: 60,
BzType: 22109,
BzVersion: 1
});
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(__filename, otaConfig, MODBUS_TT);6.3 Description
Current EB logic description:
- EdgeBus reports business data through Port 22.
- It reads register 0x00 (1 register) to obtain the processed liquid level value.
- The upload period and collection period are the same, both managed by APP parameter 70.
- The Modbus address is managed through APP parameter 150.
7. Thing Model
7.1 Basic Information of the Thing Model
Telemetry Thing Model
- Name: [DS1603]
- id Name: ds1603_22109
Parameter Thing Model
- Name: [DS1603-PARA]
- id Name: ds1603_para_22109
7.2 Uplink Frame Structure
The uplink data frame structure is as follows:
let frameInfo = {
port: 22, dataLen: 8, rssi: true, battery: 4,
tagList: [{index: 0, tag: 0x87}, {index: 1, tag: 0x10}]
}Field definitions are as follows:
- index 6:
levelProcessed,uint16BE, processed liquid level (mm)
Note: the device exposes two Modbus registers — processed value (0x00) and realtime value (0x01) — but this integration's EB reads only 0x00 (processed). Therefore the thing model has a single telemetry field levelProcessed; levelRealtime is not collected.
7.3 Thing Model Scripts
7.3.1 Telemetry Thing Model Script
let port = msg?.userdata?.port || null;
if (port !== 22) return null
let frameInfo = {
port: 22, dataLen: 8, rssi: true, battery: 4,
tagList: [{index: 0, tag: 0x87}, {index: 1, tag: 0x10}]
}
let appInfo = [
{name: "levelProcessed", field_name: "levelProcessed", unit: "mm", index: 6, type: "uint16BE"},
]
let payParser = new PayloadParser({
device: device,
msg: msg,
frameInfo: frameInfo,
appInfo: appInfo,
})
let tdata = payParser.telemetry()
if ((tdata?.status & 0x02) === 0x02) {
const status = tdata.status
tdata = {...(device.telemetry_data?.[thingModelId] ?? {})}
tdata.status = status
}
return {
telemetry_data: tdata,
server_attrs: null,
shared_attrs: null
}7.3.2 Parameter Thing Model Script
let port = msg?.userdata?.port || null;
const rpcName = "ds1603_set_22109"
let paraDef = {
app_70: {name: "period_up", field_name: "period_up", unit: "s", type: "uint32LE"},
app_150: {name: "modbus_addr", field_name: "modbus_addr", 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)
return {
telemetry_data: pdata,
server_attrs: checkData.sdata,
shared_attrs: pdata,
actions: checkData.actions,
}8. Third-Party Platform Data Subscription
8.1 MQTT Topic
/v32/{Organization Account}/tkl/up/telemetry/{eui}
8.2 Example Reported Data
According to the fields defined in this template's thing model, the sample formatted data is as follows:
{
"eui": "6353012af10a9429",
"active_time": "2026-05-11T08:35:48.000Z",
"thingModelId": "22109",
"thingModelIdName": "ds1603_22109",
"telemetry_data": {
"snr": 11.8,
"rssi": -37,
"battery": 3.65,
"levelProcessed": 221
}
}Description:
thingModelIdshould actually follow the platform-generated value; here, business code 22109 is used only as an example.thingModelIdNamecorresponds to the telemetry thing model ds1603_22109.levelProcessedis the processed liquid level from the ultrasonic sensor to the surface, in mm.
9. RPC
9.1 RPC Names
Parameter Configuration RPC
- Name: [DS1603 SET] 22109
- id Name: ds1603_set_22109
Parameter Read RPC
- Name: [DS1603 GET] 22109
- id Name: ds1603_get_22109
Measurement Medium RPC (Action)
- Name: [DS1603 SET MEDIUM] 22109
- id Name: ds1603_set_medium_22109
9.2 Parameter Definitions
| Parameter Address | Name | field_name | Unit | Type | Description |
|---|---|---|---|---|---|
| app_70 | period_up | period_up | s | uint32LE | Upload/collection period |
| app_150 | modbus_addr | modbus_addr | uint8 | Modbus address |
9.3 RPC Code
Parameter Configuration RPC
let classMode = (device && device.shared_attrs && device.shared_attrs.class_mode) || "ClassA";
let intervalms = classMode === "ClassA" ? 0 : 2000;
const rpcName = "ds1603_set_22109"
let paraDef = {
app_70: {name: "period_up", field_name: "period_up", unit: "s", type: "uint32LE"},
app_150: {name: "modbus_addr", field_name: "modbus_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);
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 msgQueParameter Read RPC
let classMode = (device && device.shared_attrs && device.shared_attrs.class_mode) || "ClassA";
let sleepMs = classMode === "ClassA" ? 500 : 5000;
let paraDef = {
app_70: {name: "period_up", field_name: "period_up", unit: "s", type: "uint32LE"},
app_150: {name: "modbus_addr", field_name: "modbus_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: sleepMs,
})
return [msg]9.4 Measurement Medium RPC (Action)
Writes the measurement medium to device register 0x05 through the transparent channel (Modbus function code 06H), verifies the device echo, and syncs the shared attribute.
Parameter Definition
| Parameter | field_name | Type | Values | Description |
|---|---|---|---|---|
| medium | medium | number | 1=water / 2=oil | Measurement medium (dropdown), default 1=water |
Read path: after a successful set,
mediumis synced to the shared attribute, and the form auto-fills the current value next time it is opened (no separate get RPC).
RPC Code
let classMode = (device?.shared_attrs?.class_mode) || "ClassA";
let addr_modbus = device.shared_attrs?.addr_modbus ?? device.shared_attrs?.addr ?? 1;
let rpcName = "ds1603_set_medium_22109";
// Measurement medium: register 0x05, FC06 write. 1=water, 2=oil (other values invalid)
if (params.medium === undefined) return null;
let checkVal = Number(params.medium);
if (checkVal !== 1 && checkVal !== 2) return null;
let dnBuffer = RPCHelper.modbusAction(addr_modbus, 6, 0x0005, checkVal);
let checkInfo = {
frameInfo: {port: 51, dataLen: 8},
appInfo: [{field_name: "medium", index: 4, type: "uint16BE", val: checkVal}],
telemetry_data: {medium: checkVal},
server_attrs: null,
shared_attrs: {medium: checkVal}
};
let msgQue = Utils.makeParaSetMSG({
msgType: Utils.msgType.transParent,
checkInfo: checkInfo,
device: device,
classMode: classMode,
rpcName: rpcName,
params: params,
paraDownBuffer: dnBuffer,
timeout: 3000,
maxRetries: 3
});
if (msgQue.length === 0) return null;
return msgQue;Modbus frame example (write medium=oil)
Request: 01 06 00 05 00 02 18 0A
Response: 01 06 00 05 00 02 18 0A (echo of the request = success)10. Template Selection
In the ThinkLink platform, search for the template:
- DS1603
Or search by business type:
- 22109 / Ultrasonic Level Sensor / RS485 Modbus Level Acquisition
Supplementary Notes
- The manufacturer's default Modbus parameters are:
- Address: 1
- Baud rate: 9600
- Data bits: 8
- Stop bits: 1
- Parity: None These parameters can be modified through the host computer tool.
- The DS1603 uses non-contact measurement — the sensor probe is mounted on the outer wall of the container and penetrates non-metallic walls for liquid level detection. It is suitable for monitoring storage tanks containing corrosive liquids such as strong acids and alkalis.
- The device supports RS485 master-slave mode (up to 32 nodes), allowing a single KC21 to centrally manage multiple tank levels.
- The manufacturer's documentation provides the following Modbus frame examples:
- Read processed liquid level:
01 03 00 00 00 01 84 0A - Write slave address (to 0x02):
01 06 00 04 00 02 49 CA - Write measurement medium (to oil):
01 06 00 05 00 02 18 0A
- Read processed liquid level: