1. Sensor Overview
Model catalog
Search for these models in ThinkLink by name or ID.
| Model type | Name | id_name | Platform model ID |
|---|---|---|---|
| RPC | [MPPT1050 GET] 22110 | mppt1050_get_22110 | 107130783759495171 |
| RPC | [MPPT1050 SET] 22110 | mppt1050_set_22110 | 107130783759495170 |
| Template | MPPT1050 | 107130783759495172 | |
| Thing Model | [MPPT1050] | mppt1050_22110 | 107130783759495168 |
| Thing Model | [LUMIAX-22110-MPPT1050资产] 区域聚合 | mppt1050_asset_22110 | 119677174411063309 |
| Parameter Thing Model | [MPPT1050-PARA] | mppt1050_para_22110 | 107130783759495169 |
The MPPT1050 is an MPPT solar charge controller manufactured by Qingdao SKYWISE Technology Co., Ltd. under the Lumiax brand. The device employs MPPT (Maximum Power Point Tracking) technology for efficient solar energy harvesting and manages battery charging/discharging processes. It supports multiple battery types (Gel, AGM, Liquid, Lithium) and is ideal for small off-grid photovoltaic systems.
In this integration solution, the device model is MPPT1050, the business code is 22110, and the template name is MPPT1050.
2. Product Features
- MPPT tracking efficiency >99.9%
- Charge conversion efficiency 96.5%
- Supports Gel, AGM, Liquid, and Lithium battery types
- Aluminum housing, IP67 protection rating
- Operating temperature range -35°C to +60°C
- RS-485 Modbus-RTU communication interface
- Max charge current 10A, system voltage 12V
3. Application Scope
- Remote monitoring of solar street light systems
- Battery charge/discharge status monitoring for off-grid PV systems
- Real-time solar controller working status monitoring
4. Collector Information
4.1 Hardware Information
The data acquisition device used in this solution is KC21 (built-in battery version, CN470 frequency band).
- Device model: KC21 (CN470 frequency band)
- Interface: RS-485
- Power supply: Built-in battery 10800mAH
- LoRaWAN Class: A
- Protection rating: IP65
4.2 Wiring Information
Power Supply and Communication Interface
The MPPT1050 connects to KC21 via RS-485 interface. KC21 is powered by its internal battery — no external power supply is needed.
- MPPT1050 RS-485 A → KC21 RS-485 A
- MPPT1050 RS-485 B → KC21 RS-485 B
The controller itself is powered by the solar panel or battery.
5. Data Acquisition
In this solution, controller data is read via Modbus-RTU (function code 0x04, read input registers). Each collection cycle performs two Modbus queries:
- Query 1 (address 0x3033, 5 registers): Status and temperature data
- Query 2 (address 0x3045, 17 registers): Power and energy data
All analog values use ×100 scale factor (display value = register value / 100). 32-bit power/energy values are combined from two consecutive 16-bit registers (L + H).
Platform-side parameter conventions:
- Upload period parameter address: 70
- Collection period parameter address: 74
- Modbus address parameter address: 150
5.1 Register Definitions
Query 1 — Status + Temperature (5 registers, start address 0x3033):
| Address (HEX) | Address (DEC) | Item Description | Data Type | Notes |
|---|---|---|---|---|
| 0x3033 | 12339 | battery_status | uint16BE | D7-D4: temp status; D3-D0: voltage status |
| 0x3034 | 12340 | charge_status | uint16BE | D3-D2: charge state; D1: fault; D0: charging |
| 0x3035 | 12341 | discharge_status | uint16BE | D13-D12: load level; D11: short circuit; D0: discharging |
| 0x3036 | 12342 | temp_ambient | uint16BE | Ambient temperature, /100 (°C) |
| 0x3037 | 12343 | temp_internal | uint16BE | Internal temperature, /100 (°C) |
Query 2 — Power/Energy Data (17 registers, start address 0x3045):
| Address (HEX) | Address (DEC) | Item Description | Data Type | Notes |
|---|---|---|---|---|
| 0x3045 | 12357 | battery_soc | uint16BE | Battery state of charge (%) |
| 0x3046 | 12358 | battery_voltage | uint16BE | Battery voltage, /100 (V) |
| 0x3047 | 12359 | battery_current | int16BE | Battery current, /100 (A), positive=charge, negative=discharge |
| 0x3048 | 12360 | battery_power_l | uint16BE | Battery power L-register (low 16 bits) |
| 0x3049 | 12361 | battery_power_h | uint16BE | Battery power H-register (high 16 bits) |
| 0x304A | 12362 | load_voltage | uint16BE | Load voltage, /100 (V) |
| 0x304B | 12363 | load_current | uint16BE | Load current, /100 (A) |
| 0x304C | 12364 | load_power_l | uint16BE | Load power L-register |
| 0x304D | 12365 | load_power_h | uint16BE | Load power H-register |
| 0x304E | 12366 | solar_voltage | uint16BE | Solar panel voltage, /100 (V) |
| 0x304F | 12367 | solar_current | uint16BE | Solar panel current, /100 (A) |
| 0x3050 | 12368 | solar_power_l | uint16BE | Solar power L-register |
| 0x3051 | 12369 | solar_power_h | uint16BE | Solar power H-register |
| 0x3052 | 12370 | daily_charge_energy | uint16BE | Daily charge energy, /100 (kWh) |
| 0x3053 | 12371 | total_charge_energy_l | uint16BE | Total charge energy L-register |
| 0x3054 | 12372 | total_charge_energy_h | uint16BE | Total charge energy H-register |
| 0x3055 | 12373 | daily_discharge_energy | uint16BE | Daily discharge energy, /100 (kWh) |
32-bit power/energy calculation:
battery_power = ((battery_power_h << 16) | battery_power_l) / 100 (W)
load_power = ((load_power_h << 16) | load_power_l) / 100 (W)
solar_power = ((solar_power_h << 16) | solar_power_l) / 100 (W)
total_charge_energy = ((total_charge_energy_h << 16) | total_charge_energy_l) / 100 (kWh)Supported Modbus function codes:
- 0x02: Read discrete inputs
- 0x03: Read holding registers
- 0x04: Read input registers
- 0x05: Write single coil
- 0x06: Write single register
- 0x10: Write multiple registers
5.2 Status Bit Definitions
battery_status (0x3033):
| Bits | Value | Meaning |
|---|---|---|
| D7-D4 | 00H | Temperature normal |
| D7-D4 | 01H | High temperature protection |
| D3-D0 | 00H | Voltage normal |
| D3-D0 | 01H | Overvoltage |
| D3-D0 | 02H | Undervoltage |
| D3-D0 | 03H | Over-discharge |
charge_status (0x3034):
| Bits | Value | Meaning |
|---|---|---|
| D6 | 0/1 | Manual charge disable (0=normal, 1=disabled) |
| D5 | 0/1 | Day/Night (0=day, 1=night) |
| D3-D2 | 00 | Not charging |
| D3-D2 | 01 | Float charge |
| D3-D2 | 02 | Boost charge |
| D3-D2 | 03 | Equalize charge |
| D1 | 0/1 | Fault (0=normal, 1=fault) |
| D0 | 0/1 | Charging (1=charging) |
discharge_status (0x3035):
| Bits | Value | Meaning |
|---|---|---|
| D13-D12 | 00 | Light load |
| D13-D12 | 01 | Medium load |
| D13-D12 | 02 | Rated load |
| D13-D12 | 03 | Overload |
| D11 | 0/1 | Short circuit protection (1=active) |
| D0 | 0/1 | Discharging (1=discharging) |
6. EdgeBus Model
6.1 EB Configuration Parameters
Main configuration parameters:
name: "mppt1050"port: 22version: "0x87"dataType: "0x10"upPeriodIndex: 70- Modbus default serial parameters:
BaudRate: 9600StopBits: 1DataBits: 8Checkbit: NONE
BzType: 22110BzVersion: 1Battery: true(built-in battery, Class A)
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: "mppt1050", port: 22, version: "0x87", dataType: "0x10", upPeriodIndex: 70,
quInfo: [
{
protocol: "modbus", addr: "0x01", code: "0x04", periodIndex: 70,
indexAPP: 150, indexCMD: 0, copySize: 1, isLast: false,
listVal: [
{ start: "12339", end: "12343" }
]
},
{
protocol: "modbus", addr: "0x01", code: "0x04", periodIndex: 70,
indexAPP: 150, indexCMD: 0, copySize: 1, isLast: true,
listVal: [
{ start: "12357", end: "12373" }
]
}
]
}
];
let otaConfig = getOtaConfig({
BaudRate: 9600,
StopBits: 1,
DataBits: 8,
Checkbit: CheckbitEnum.NONE,
Battery: true,
ConfirmDuty: 60,
BzType: 22110,
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(import.meta.url, otaConfig, MODBUS_TT);6.3 Explanation
Current EB logic:
- EdgeBus reports service data via Port 22.
- First executes Query 1 (0x04, address 0x3033, 5 registers) to read status and temperature data.
- Then executes Query 2 (0x04, address 0x3045, 17 registers) to read power/energy data. Query 2 has
isLast: truewhich triggers the uplink. - Upload and collection periods are separated:
70is the upload period (upPeriodIndex)74is the collection period (periodIndex)
- Modbus address managed by APP parameter
150. - Analog values use uint16BE/int16BE types with
decimal: 2for /100 scaling. - 32-bit power/energy values are combined from L + H registers in the thing model after parsing.
7. Thing Model
7.1 Basic Information
Telemetry Thing Model
- Name:
[MPPT1050] - id Name:
mppt1050_22110
Parameter Thing Model
- Name:
[MPPT1050-PARA] - id Name:
mppt1050_para_22110
7.2 Uplink Frame Structure
The data uplink frame structure is as follows:
let frameInfo = {
port: 22, dataLen: 50, rssi: true, battery: 4,
tagList: [{index: 0, tag: 0x87}, {index: 1, tag: 0x10}]
};Frame structure:
- Byte 0: tag[0] = 0x87 (version)
- Byte 1: tag[1] = 0x10 (dataType)
- Byte 2: Status byte
- Byte 3: Reserved
- Byte 4: Battery voltage
- Byte 5: Reserved
- Bytes 6-15: Query 1 results (5 registers × 2 bytes = 10 bytes)
- Bytes 16-49: Query 2 results (17 registers × 2 bytes = 34 bytes)
Field definitions:
| Index | Field Name | Type | Description |
|---|---|---|---|
| 6 | battery_status | uint16BE | Battery status (bitmask) |
| 8 | charge_status | uint16BE | Charge status (bitmask) |
| 10 | discharge_status | uint16BE | Discharge status (bitmask) |
| 12 | temp_ambient | uint16BE | Ambient temperature, decimal:2 (°C) |
| 14 | temp_internal | uint16BE | Internal temperature, decimal:2 (°C) |
| 16 | battery_soc | uint16BE | Battery SOC (%) |
| 18 | battery_voltage | uint16BE | Battery voltage, decimal:2 (V) |
| 20 | battery_current | int16BE | Battery current, decimal:2 (A) |
| 22 | battery_power_l | uint16BE | Battery power L-register |
| 24 | battery_power_h | uint16BE | Battery power H-register |
| 26 | load_voltage | uint16BE | Load voltage, decimal:2 (V) |
| 28 | load_current | uint16BE | Load current, decimal:2 (A) |
| 30 | load_power_l | uint16BE | Load power L-register |
| 32 | load_power_h | uint16BE | Load power H-register |
| 34 | solar_voltage | uint16BE | Solar voltage, decimal:2 (V) |
| 36 | solar_current | uint16BE | Solar current, decimal:2 (A) |
| 38 | solar_power_l | uint16BE | Solar power L-register |
| 40 | solar_power_h | uint16BE | Solar power H-register |
| 42 | daily_charge_energy | uint16BE | Daily charge energy, decimal:2 (kWh) |
| 44 | total_charge_energy_l | uint16BE | Total charge energy L-register |
| 46 | total_charge_energy_h | uint16BE | Total charge energy H-register |
| 48 | daily_discharge_energy | uint16BE | Daily discharge energy, decimal:2 (kWh) |
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:50,rssi:true,battery:4,
tagList:[{ index:0, tag:0x87}, { index:1, tag:0x10}]
}
let appInfo = [
{ name: "battery_status", field_name: "battery_status", unit:"", index: 6, type: "uint16BE"},
{ name: "charge_status", field_name: "charge_status", unit:"", index: 8, type: "uint16BE"},
{ name: "discharge_status", field_name: "discharge_status", unit:"", index: 10, type: "uint16BE"},
{ name: "temp_ambient", field_name: "temp_ambient", unit:"°C", index: 12, type: "uint16BE",decimal:2},
{ name: "temp_internal", field_name: "temp_internal", unit:"°C", index: 14, type: "uint16BE",decimal:2},
{ name: "battery_soc", field_name: "battery_soc", unit:"%", index: 16, type: "uint16BE"},
{ name: "battery_voltage", field_name: "battery_voltage", unit:"V", index: 18, type: "uint16BE",decimal:2},
{ name: "battery_current", field_name: "battery_current", unit:"A", index: 20, type: "int16BE",decimal:2},
{ name: "battery_power_l", field_name: "battery_power_l", unit:"", index: 22, type: "uint16BE"},
{ name: "battery_power_h", field_name: "battery_power_h", unit:"", index: 24, type: "uint16BE"},
{ name: "load_voltage", field_name: "load_voltage", unit:"V", index: 26, type: "uint16BE",decimal:2},
{ name: "load_current", field_name: "load_current", unit:"A", index: 28, type: "uint16BE",decimal:2},
{ name: "load_power_l", field_name: "load_power_l", unit:"", index: 30, type: "uint16BE"},
{ name: "load_power_h", field_name: "load_power_h", unit:"", index: 32, type: "uint16BE"},
{ name: "solar_voltage", field_name: "solar_voltage", unit:"V", index: 34, type: "uint16BE",decimal:2},
{ name: "solar_current", field_name: "solar_current", unit:"A", index: 36, type: "uint16BE",decimal:2},
{ name: "solar_power_l", field_name: "solar_power_l", unit:"", index: 38, type: "uint16BE"},
{ name: "solar_power_h", field_name: "solar_power_h", unit:"", index: 40, type: "uint16BE"},
{ name: "daily_charge_energy", field_name: "daily_charge_energy", unit:"kWh", index: 42, type: "uint16BE",decimal:2},
{ name: "total_charge_energy_l", field_name: "total_charge_energy_l", unit:"", index: 44, type: "uint16BE"},
{ name: "total_charge_energy_h", field_name: "total_charge_energy_h", unit:"", index: 46, type: "uint16BE"},
{ name: "daily_discharge_energy", field_name: "daily_discharge_energy", unit:"kWh", index: 48, type: "uint16BE",decimal:2},
]
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
}
}
tdata.battery_power = ((tdata.battery_power_h << 16) | tdata.battery_power_l) / 100
tdata.load_power = ((tdata.load_power_h << 16) | tdata.load_power_l) / 100
tdata.solar_power = ((tdata.solar_power_h << 16) | tdata.solar_power_l) / 100
tdata.total_charge_energy = ((tdata.total_charge_energy_h << 16) | tdata.total_charge_energy_l) / 100
delete tdata.battery_power_l; delete tdata.battery_power_h
delete tdata.load_power_l; delete tdata.load_power_h
delete tdata.solar_power_l; delete tdata.solar_power_h
delete tdata.total_charge_energy_l; delete tdata.total_charge_energy_h
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="mppt1050_set_22110"
let paraDef= {
app_70: {name: "period_up", field_name: "period_up", unit: "s", type: "uint32LE"},
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)
return {
telemetry_data: pdata,
server_attrs: checkData.sdata,
shared_attrs: pdata,
actions: checkData.actions,
}8. Third-Party Data Subscription
8.1 MQTT Topic
/v32/{Organization Account}/tkl/up/telemetry/{eui}
8.2 Example Uplink Data
Based on the template's thing model fields, example data is as follows:
{
"eui": "6353012af10a9429",
"active_time": "2026-05-12T08:35:48.000Z",
"thingModelId": "22110",
"thingModelIdName": "mppt1050_22110",
"telemetry_data": {
"snr": 13.5,
"rssi": -51,
"battery": 3.37,
"battery_status": 0,
"charge_status": 1,
"discharge_status": 1,
"temp_ambient": 25.5,
"temp_internal": 28.3,
"battery_soc": 85,
"battery_voltage": 13.2,
"battery_current": 2.5,
"battery_power": 33.0,
"load_voltage": 13.1,
"load_current": 0.5,
"load_power": 6.55,
"solar_voltage": 18.5,
"solar_current": 2.1,
"solar_power": 38.85,
"daily_charge_energy": 0.25,
"total_charge_energy": 12.5,
"daily_discharge_energy": 0.08
}
}Notes:
thingModelIdshould use the platform-generated value;22110is shown as an example.thingModelIdNamecorresponds to the telemetry thing modelmppt1050_22110.battery_currentpositive = charging, negative = discharging.battery_power,load_power,solar_power,total_charge_energyare 32-bit combined values (L + H registers combined, then /100).
9. RPC
9.1 RPC Names
Set Parameters RPC
- Name:
[MPPT1050 SET] 22110 - id Name:
mppt1050_set_22110
Get Parameters RPC
- Name:
[MPPT1050 GET] 22110 - id Name:
mppt1050_get_22110
9.2 Parameter Definitions
| Parameter Address | Name | field_name | Unit | Type | Description |
|---|---|---|---|---|---|
| app_70 | period_up | period_up | s | uint32LE | Upload period |
| app_150 | addr_modbus | addr_modbus | | uint8 | Modbus address |
9.3 RPC Code
Set Parameters RPC
let classMode = (device && device.shared_attrs && device.shared_attrs.class_mode) || "ClassA";
let intervalms = classMode === "ClassA" ? 0 : 2000;
const rpcName="mppt1050_set_22110"
let paraDef= {
app_70: {name: "period_up", field_name: "period_up", unit: "s", type: "uint32LE"},
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 msgQueGet Parameters 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: "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:sleepMs,
})
return [msg]10. Template Selection
Search for the template in the ThinkLink platform: MPPT1050
Or search by business type: 22110 / MPPT Solar Charge Controller / RS485 Modbus PV Monitoring
Supplementary Notes
- Factory default Modbus parameters:
- Address: 1
- Baud rate: 9600
- Data bits: 8
- Stop bits: 1
- Parity: None
- Battery current is signed (int16BE): positive = charging, negative = discharging.
- 32-bit power/energy values are combined from L-register (low 16 bits) and H-register (high 16 bits) using formula:
(H << 16) | L, then divided by 100. - Battery SOC uses no scaling (/1), unlike other analog values.
- DTU is powered by internal battery (KC21, 10800mAH), Class A mode, default upload period 300 seconds.
- The controller supports four battery types by default: Gel, AGM, Liquid, Lithium.