1. 项目概述
NB-IoT(窄带物联网)作为低功耗广域网络技术,在智能表计、环境监测等领域应用广泛。移动OneNET平台作为国内主流物联网云平台之一,为NB-IoT设备提供了完整的接入方案。本文将基于实际项目经验,详细解析Java语言对接OneNET平台的全流程技术细节。
与电信AEP平台相比,OneNET在鉴权机制、命令状态管理等方面存在显著差异。例如在智能水表项目中,我们发现OneNET特有的两阶段命令状态(send_status + confirm_status)能更精准反映NB-IoT设备的响应情况,这对需要确保指令可靠到达的计费类设备尤为重要。
2. 核心流程解析
2.1 鉴权机制实现
OneNET采用动态Token鉴权机制,其核心是通过HMAC-SHA1算法生成签名。具体实现包含三个关键步骤:
- 参数准备:
java复制String version = "2022-05-01"; // API版本
String expirationTime = System.currentTimeMillis() / 1000 + 8640000; // 100天有效期
String signatureMethod = "sha1"; // 签名算法
- 签名生成:
java复制public static String generatorSignature(String version, String resourceName,
String expirationTime, String accessKey, String signatureMethod) {
String encryptText = expirationTime + "\n" + signatureMethod + "\n"
+ resourceName + "\n" + version;
byte[] bytes = HmacUtils.hmacSha1(
Base64.decodeBase64(accessKey),
encryptText.getBytes(StandardCharsets.UTF_8)
);
return Base64.encodeBase64String(bytes);
}
- Token组装:
java复制public static String assembleToken(String version, String resourceName,
String expirationTime, String signatureMethod, String accessKey) {
String res = URLEncoder.encode(resourceName, "UTF-8");
String sig = URLEncoder.encode(
generatorSignature(version, resourceName, expirationTime, accessKey, signatureMethod),
"UTF-8"
);
return "version=" + version + "&res=" + res
+ "&et=" + expirationTime + "&method=" + signatureMethod
+ "&sign=" + sig;
}
注意事项:
- AccessKey需要先进行Base64解码后再用于HMAC计算
- 所有URL参数必须进行UTF-8编码
- Token有效期建议设置为业务周期的2-3倍(如智能水表每月抄表,可设置90天有效期)
2.2 设备生命周期管理
2.2.1 设备注册
NB-IoT设备注册需提供IMEI和IMSI双标识:
java复制public AddDeviceResult addDevice(String title, String imei, String imsi) {
AddDevice addDevice = new AddDevice();
addDevice.setProductId(onenetConfig.getProductId());
addDevice.setDeviceName(title);
addDevice.setImei(imei);
addDevice.setImsi(imsi);
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", Token.getToken());
return httpUtil.postJson(
"https://iot-api.heclouds.com/device/create",
addDevice,
headers,
AddDeviceResult.class
);
}
2.2.2 设备删除
根据设备类型使用不同标识符:
java复制public Result deleteDevice(String sn, boolean isNbiot) {
Map<String, String> params = new HashMap<>();
params.put("product_id", onenetConfig.getProductId());
params.put(isNbiot ? "imei" : "device_name", sn);
return httpUtil.postJson(
"https://iot-api.heclouds.com/device/delete",
params,
new HashMap<>() {{ put("Authorization", Token.getToken()); }},
Result.class
);
}
实操技巧:
- 批量操作时建议使用OneNET的批量接口(/device/batch)
- 删除设备后关联的资源(如数据流)不会自动清除,需单独调用清理接口
3. 设备通信实现
3.1 命令下发机制
OneNET为NB-IoT设备提供离线命令缓存功能,关键参数说明:
| 参数 | 必选 | 说明 |
|---|---|---|
| imei | 是 | 设备IMEI |
| obj_id | 是 | LwM2M对象ID |
| valid_time | 否 | 命令有效期(秒) |
| retry | 否 | 重试次数(默认3次) |
实现示例:
java复制public CacheWriteResult cacheWrite(CacheWriteBody body) {
String url = "https://iot-api.heclouds.com/nb-iot/offline";
url = appendParams(url, Map.of(
"imei", body.getImei(),
"obj_id", body.getObjId(),
"retry", body.getRetry() != null ? body.getRetry() : 3
));
return httpUtil.postJson(
url,
Map.of("data", body.getData()),
new HashMap<>() {{ put("Authorization", Token.getToken()); }},
CacheWriteResult.class
);
}
3.2 数据接收处理
3.2.1 回调验证
OneNET要求先验证回调地址有效性:
java复制public void handleVerification(ChannelHandlerContext ctx, HttpRequest request) {
String msg = request.getRequestParams().getString("msg");
String nonce = request.getRequestParams().getString("nonce");
String signature = URLDecoder.decode(
request.getRequestParams().getString("signature")
);
String calculatedSig = HmacUtils.hmacSha1Hex(
onenetConfig.getToken().getBytes(),
(msg + nonce).getBytes()
);
if (calculatedSig.equals(signature)) {
NettyHttpUtil.writeTextResponse(ctx, HttpResponseStatus.OK, msg);
} else {
NettyHttpUtil.writeTextResponse(ctx, HttpResponseStatus.FORBIDDEN, "");
}
}
3.2.2 消息解析
处理加密消息的完整流程:
java复制private void parseMessage(String encryptedBody) {
// 1. 解析基础信息
JSONObject obj = JSON.parseObject(encryptedBody);
String nonce = obj.getString("nonce");
String signature = obj.getString("msg_signature");
// 2. 验证签名
String localSign = HmacUtils.hmacSha1Hex(
(onenetConfig.getToken() + nonce).getBytes(),
obj.getString("msg").getBytes()
);
if (!localSign.equals(signature)) {
throw new SecurityException("Invalid signature");
}
// 3. AES解密
String decryptMsg = AesUtil.decrypt(
obj.getString("msg"),
onenetConfig.getAesKey()
);
// 4. 处理业务数据
processMessage(JSON.parseObject(decryptMsg));
}
4. 状态管理与异常处理
4.1 命令状态机
OneNET采用两阶段状态管理:
mermaid复制graph TD
A[命令创建] --> B[send_status=1 等待下发]
B --> C[send_status=3 已发往基站]
C --> D[send_status=5 下发成功]
D -->|设备响应| E[confirm_status=0 执行成功]
D -->|超时未响应| F[confirm_status=5 超时]
C -->|下发失败| G[send_status=6 失败]
Java实现示例:
java复制public void handleCmdResult(CacheCmdResult cmdResult) {
if (cmdResult.getConfirmStatus() != null) {
// 设备已响应状态处理
handleFinalStatus(cmdResult.getConfirmStatus());
} else {
// 中间状态处理
handleIntermediateStatus(cmdResult.getSendStatus());
}
}
private void handleFinalStatus(int status) {
switch (status) {
case 0: // 成功
updateDeviceState(DeviceState.ACTIVE);
break;
case 5: // 超时
log.warn("Command timeout");
break;
case 12: // 设备未注册
registerRetry();
break;
}
}
4.2 异常代码对照表
常见错误及解决方案:
| 错误码 | 含义 | 处理建议 |
|---|---|---|
| 400101 | 参数错误 | 检查IMEI/IMSI格式 |
| 401001 | Token过期 | 重新生成Token |
| 404002 | 设备不存在 | 检查设备注册状态 |
| 500000 | 服务端错误 | 联系OneNET技术支持 |
5. 性能优化实践
5.1 连接池配置
推荐使用Apache HttpClient连接池:
java复制PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager();
manager.setMaxTotal(200); // 最大连接数
manager.setDefaultMaxPerRoute(50); // 每路由最大连接数
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(5000) // 连接超时
.setSocketTimeout(30000) // 读写超时
.build();
HttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(manager)
.setDefaultRequestConfig(config)
.build();
5.2 消息处理优化
采用异步处理提升吞吐量:
java复制@Bean
public ThreadPoolTaskExecutor onenetExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(100);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("onenet-handler-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
@Async("onenetExecutor")
public void asyncProcessMessage(Message message) {
// 消息处理逻辑
}
6. 安全防护措施
6.1 请求验证
双重签名验证机制:
java复制public boolean verifyRequest(String signature, String nonce, String msg) {
// 1. 验证msg_signature
String localSign1 = HmacUtils.hmacSha1Hex(
(token + nonce).getBytes(),
msg.getBytes()
);
// 2. 验证body签名
String localSign2 = HmacUtils.hmacSha1Hex(
token.getBytes(),
(msg + nonce).getBytes()
);
return localSign1.equals(signature) && localSign2.equals(signature);
}
6.2 敏感数据保护
建议配置:
- 启用OneNET消息加密功能
- 定期轮换AES加密密钥
- 敏感日志脱敏处理:
java复制public String maskImei(String imei) {
if (imei == null || imei.length() < 8) return imei;
return imei.substring(0, 3) + "****"
+ imei.substring(imei.length() - 4);
}
7. 实战问题排查
7.1 常见问题速查
-
Token失效
现象:401错误
检查:- 系统时间是否同步(NTP服务)
- Token有效期是否过短
- AccessKey是否被重置
-
命令未到达设备
排查步骤:- 检查send_status状态
- 确认设备最近上报时间
- 验证基站覆盖情况
-
数据解析异常
处理方法:- 打印原始16进制报文
- 对照设备文档检查数据格式
- 使用OneNET的数据调试工具
7.2 监控指标建议
关键监控项:
- 命令平均响应时间
- 消息处理队列积压量
- 每日鉴权失败次数
- 设备在线率波动
Prometheus配置示例:
yaml复制- job_name: 'onenet'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['localhost:8080']
8. 平台对比总结
电信AEP与移动OneNET功能对比:
| 功能项 | 电信AEP | 移动OneNET |
|---|---|---|
| 协议支持 | CoAP/LwM2M | LwM2M/MQTT |
| 命令超时 | 固定24小时 | 可配置 |
| 数据存储 | 30天 | 90天 |
| 计费方式 | 按消息数 | 按设备数 |
| 地域覆盖 | 全国 | 重点城市 |
选型建议:
- 对命令可靠性要求高的场景选择OneNET
- 需要全国覆盖且设备量大的项目考虑AEP
- 混合组网时可双平台接入
在实际的智能水表项目中,我们最终采用OneNET作为主平台,主要基于以下考量:
- 两阶段命令状态可精确掌握指令到达情况
- 更长的命令有效期(最长7天)适应NB-IoT特性
- 完善的区域服务支持体系