更新文档和代码

This commit is contained in:
chenchi 2022-03-22 12:09:52 +08:00
parent 3f935faeaf
commit 1d86858284
8 changed files with 878 additions and 336 deletions

View File

@ -190,6 +190,20 @@
| pubTopic | str | true | 发布主题 |
| serialD | int | true | MQTT通道捆绑的串口ID (1~3) |
##### 通道类型:移远云
![](./media/gui_quecthing.png)
| **字段** | **type** | **Required** | **含义** |
| --- | --- | --- | --- |
| keepAlive | int | false | 通信之间允许的最长时间段(以秒为单位),默认为120范围60-1200可不填 |
| ProductKey | str | true | 产品key |
| ProductSecret | str | false | 产品密钥|
| QOS | int | false | MQTT消息服务质量默认0可选择0或10发送者只发送一次消息不进行重试 1发送者最少发送一次消息确保消息到达Broker |
| SessionFlag | bool | true | 配置与云平台通信的数据是否采用session加密默认值为FalseTrue加密False加密 |
| sendMode | str | true | 移远云数据收发模式phy物模型pass透传 |
| serialD | int | true | MQTT通道捆绑的串口ID (1~3) |
##### APN设置
APN功能暂未上线

View File

@ -4,6 +4,7 @@
| :------ | ---------- | ---------- | --------------------- |
| 1.0 | 2021-11-25 | 陈驰 | 初始版本 |
| 1.1 | 2021-11-30 | 陈驰 | 增加对DTU配套组件和服务的描述 |
| 1.2 | 2022-01-14 | 陈驰 | 增加DTU代码和文档链接的描述 |
## DTU介绍
@ -178,3 +179,25 @@ QPYCom的使用文档参见安装目录下的`docs`文件夹。
### modbus模式
modbus模式下严格遵守modbus协议规范且遵守modbus协议的DTU产品在行业内的应用规则DTU作为主机根据用户配置周期性向从机设备索要数据推送至云端。
## DTU代码下载和使用文档
### 代码下载
`git clone https://gitee.com/qpy-solutions/dtu.git`
`git clone git@gitee.com:qpy-solutions/dtu.git`
### 使用文档
- DTU产品介绍[点此进入][1]
- DTU用户指导[点此进入][2]
- DTU协议规范[点此进入][3]
- DTU上位机工具用户指导[点此进入][4]
[1]: https://python.quectel.com/doc/doc/Product_case/zh/dtu/DTU_Product_Introduction.html
[2]: https://python.quectel.com/doc/doc/Product_case/zh/dtu/DTU_User_Guides.html
[3]: https://python.quectel.com/doc/doc/Product_case/zh/dtu/DTU_Protocol_Specification.html
[4]: https://python.quectel.com/doc/doc/Product_case/zh/dtu/DTU_GUI_User_Guides.html

View File

@ -17,9 +17,9 @@ DTU与云端通信报文使用json格式
- 云端下行报文
命令模式与modbus模式
命令模式:
`{“msg_id”: msg_id, “data”: “1234”[, “cmd_code”: 0X40, “topic_id”: 1]}`
`{“msg_id”: msg_id, “data”: “1234”[, “cmd_code”: 40, “topic_id”: 1]}`
透传模式:
@ -35,11 +35,37 @@ cmd_code可选字段填写对应功能码并又DTU执行相应的操作
topic_id可选字段填写mqtt返回需要publish的topic_id此字段仅在命令模式与使用MQTT/Aliyun/Txyun时生效
- modbus模式
`{“msg_id”: msg_id, “modbus”: {"groups": {"num": 0, "cmd": ["0x03", "0x00", "0x00", "0x00", "0x02"]}}}`
字段说明:
msg_id报文id一般为时间戳+3位随机数
data报文消息字段
cmd_code可选字段填写对应功能码并又DTU执行相应的操作此字段仅在命令模式下生效
topic_id可选字段填写mqtt返回需要publish的topic_id此字段仅在命令模式与使用MQTT/Aliyun/Txyun时生效
modbus可选字段此字段仅在modbus模式使用此字段下有3个子字段groupstask与command
    groups可选字段在modbus模式下向指定的地址组发送消息
        num配置文件中的地址组编号
        cmd向地址组发送的modbus命令
    task可选字段在modbus模式下执行代码中预置的task任务
    command可选字段在modbus下直接向UART口写入指定modbus命令
- 云端上行报文
命令模式与modbus模式
命令模式:
`{“msg_id”: msg_id, “data”: “1234”[, “cmd_code”: 0X40, “status”: 1]}`
`{"msg_id": msg_id, "data": "1234"[, "cmd_code": 40, "status": 1, "password": "123"]}`
透传模式:
@ -65,42 +91,38 @@ status可选字段仅在命令模式下生效用于反馈命令是否
### 功能码表
| 功能码 | 功能 |
| --- | --- |
| 0x00-0x3f | 查询指令 |
| 0x00 | 查询IMEI |
| 0x01 | 查询本机号码 |
| 0x02 | 查询固件版本号 |
| 0x03 | 查询信号强度 |
| 0x04 | 查询当前配置参数 |
| 0x05 | 诊断查询 |
| 0X06 | 查询ICCID |
| 0X07 | 查询ADC电压 |
| 0X08 | 查询GPIO信息 |
| 0X10 | 查询温湿度 |
| 0X11 | 查询网络连接信息 |
| 0X12 | 查询网络状态 |
| 0X13 | 查询基站定位信息 |
| 0x50~0x8f | 设置指令 |
| 0x50 | 协议短信透传 |
| 0x51 | 配置密码 |
| 0x52 | 添加设备识别码IMEI |
| 0x53 | 登录服务器发送注册信息 |
| 0x54 | 固件版本号 |
| 0x55 | 是否启用自动更新 |
| 0x56 | 日志输出 |
| 0x57 | 服务器获取配置参数 |
| 0x58 | 串口参数 |
| 0x59 | 通道配置参数 |
| 0x60 | Apn设置 |
| 0x61 | GPIO设置 |
| 0x62 | GPS |
| 0x63 | 数据流 |
| 0x64 | 预警 |
| 0x65 | 任务 |
| 0xfd | 协议终止指令 |
| 0xfe | DTU启动中无法接收指令 |
| 0xff | 复位指令 |
| 功能码 | 功能 |
|--------|---------------|
| 0-49 | 查询指令 |
| 0 | 查询IMEI |
| 1 | 查询本机号码 |
| 2 | 查询固件版本号 |
| 3 | 查询信号强度 |
| 4 | 查询当前配置参数 |
| 5 | 诊断查询 |
| 6 | 查询ICCID |
| 7 | 查询ADC电压 |
| 8 | 查询GPIO信息 |
| 10 | 查询温湿度 |
| 11 | 查询网络连接信息 |
| 12 | 查询网络状态 |
| 13 | 查询基站定位信息 |
| 50~143 | 设置指令 |
| 50 | 协议短信透传 |
| 51 | 配置密码 |
| 52 | 添加设备识别码IMEI |
| 53 | 登录服务器发送注册信息 |
| 54 | 固件版本号 |
| 55 | 是否启用自动更新 |
| 56 | 日志输出 |
| 57 | 服务器获取配置参数 |
| 58 | 串口参数 |
| 59 | 通道配置参数 |
| 60 | Apn设置 |
| 61 | GPIO设置 |
| 62 | OTA |
| 63 | 参数设置 |
| 255 | 复位指令 |
## 查询指令
@ -114,7 +136,7 @@ DTU的IMEI号
返回的数据内容:
`{"code": 0x00 , "data": "123456789012345" , "success":1}`
`{"code": 0 , "data": "123456789012345" , "success":1}`
字段说明:
@ -130,11 +152,11 @@ DTU的IMEI号
查询SIM卡的号码
功能码: 0x01
功能码: 1
返回的数据内容:
`{"code": 0x01 , "data": "17201593988" , "success":1}`
`{"code": 1 , "data": "17201593988" , "success":1}`
| **字段** | **类型** | **含义** |
| --- | --- | --- |
@ -150,11 +172,11 @@ DTU的IMEI号
固件版本号格式为: v 1
功能码: 0x02
功能码: 2
返回的数据内容:
`{"code": 0x02 , "data": "v 1" , "success":1}`
`{"code": 2 , "data": "v 1" , "success":1}`
| **字段** | **类型** | **含义** |
| --- | --- | --- |
@ -168,33 +190,31 @@ DTU的IMEI号
网络信号强度值范围0~31值越大表示信号强度越好。
功能码: 0x03
功能码: 3
返回的数据内容:
`{"code": 0x03 , "data": " CSQ17 " , "success":1}`
`{"code": 3 , "data": " CSQ17 " , "success":1}`
| **字段** | **字符串** | **含义** |
| --- | --- | --- |
| code | str | 状态码 |
| data | str | CSQ1~CSQ31 |
| success | int | 0 失败 1成功 |
| **字段** | **类型** | **含义** |
| --- |--------| --- |
| code | str | 状态码 |
| data | str | CSQ1~CSQ31 |
| success | int | 0 失败 1成功 |
### 查询当前配置参数
功能码: 0x04
功能码: 4
数据内容:
```
{ "password": "012345",
"data":{}
{"password": "012345",
"cmd_code": 4,
}
```
返回的数据内容:
`{"code": 0x04 , "data": " req config " , "success":1}`
`{"code": 4 , "data": " req config " , "success":1}`
| **字段** | **字符串** | **含义** |
| --- | --- | --- |
@ -206,13 +226,13 @@ DTU的IMEI号
说明: 查询当前DTU运行的错误上报信息
功能码: 0x05
功能码: 5
返回的数据内容:
```
{"code":0x05,
{"code":5,
"data":[{"func_code": "0x01" , "error_code": " 6001"}],
"data":[{"func_code": "5" , "error_code": " 6001"}],
"success":1}
```
@ -228,11 +248,11 @@ DTU的IMEI号
说明: 查询iccid
功能码: 0x06
功能码: 6
返回的数据内容:
```
{"code":0x06,
{"code":6,
"data": "12456465486561516515153",
@ -249,11 +269,11 @@ DTU的IMEI号
说明: 查询adc
功能码: 0x07
功能码: 7
返回的数据内容:
```
{"code":0x07,
{"code":7,
"data": "3.7",
@ -269,11 +289,11 @@ DTU的IMEI号
说明: 查询gpio
功能码: 0x08
功能码: 8
返回的数据内容:
```
{"code":0x08,
{"code":8,
"data": "gpio_msg",
@ -286,15 +306,35 @@ DTU的IMEI号
| data | str | gpio获取的信息 |
| status | str | 0 失败 1成功 |
### 电池电压查询
说明: 查询gpio
功能码: 9
返回的数据内容:
```
{"code":9,
"data": "3590",
"status":1}
```
| **字段** | **类型** | **含义** |
| --- | --- | --- |
| code | int | 状态码 |
| data | str | gpio获取的信息 |
| status | str | 0 失败 1成功 |
### 查询温湿度
说明: 查询温湿度
功能码: 0x010
功能码: 10
返回的数据内容:
```
{"code":0x10,
{"code":10,
"data": {"temperature": 26.0, "humidity": 60.0},
"status":1}
```
@ -302,18 +342,18 @@ DTU的IMEI号
| **字段** | **类型** | **含义** |
| --- | --- | --- |
| code | int | 状态码 |
| data | dict | 温湿度信息{"temperature": temp, &##39;humidity&##39;: humid} |
| data | dict | 温湿度信息{"temperature": temp, 'humidity': humid} |
| status | str | 0 失败 1成功 |
### 查询网络连接信息
说明: 查询网络连接信息,每种连接类型返回对应连接状态
功能码: 0x11
功能码: 11
返回的数据内容:
```
{"code":0x11,
{"code":11,
"data": "200",
"status":1}
```
@ -324,36 +364,98 @@ DTU的IMEI号
| data | str | 网络连接状态 |
| status | str | 0 失败 1成功 |
网络连接状态说明
| **连接类型** | **含义** |
|-----------|--------------------------------|
| http | 返回http状态码 |
| tcp/udp | 参照套接字状态表 |
| mqtt | 0连接成功 1连接中 2服务端连接关闭 -1连接异常 |
| aliyun | 0连接成功 1连接中 2服务端连接关闭 -1连接异常 |
| txyun | 0连接成功 1连接中 2服务端连接关闭 -1连接异常 |
| quecthing | 参照quecthing连接状态表 |
套接字状态表
| **状态值** | **状态** | **描述** |
|------|------------|---------------------------------------------------------------|
|0 | CLOSED | 套接字创建了,但没有使用这个套接字 |
|1 | LISTEN | 套接字正在监听连接 |
|2 | SYN_SENT | 套接字正在试图主动建立连接即发送SYN后还没有收到ACK |
|3 | SYN_RCVD | 套接字正在处于连接的初始同步状态即收到对方的SYN但还没收到自己发过去的SYN的ACK |
|4 | ESTABLISHED | 连接已建立 |
|5 | FIN_WAIT_1 | 套接字已关闭正在关闭连接即发送FIN没有收到ACK也没有收到FIN |
|6 | FIN_WAIT_2 | 套接字已关闭正在等待远程套接字关闭即在FIN_WAIT_1状态下收到发过去FIN对应的ACK |
|7 | CLOSE_WAIT | 远程套接字已经关闭正在等待关闭这个套接字被动关闭的一方收到FIN |
|8 | CLOSING | 套接字已关闭远程套接字正在关闭暂时挂起关闭确认即在FIN_WAIT_1状态下收到被动方的FIN |
|9 | LAST_ACK | 远程套接字已关闭正在等待本地套接字的关闭确认被动方在CLOSE_WAIT状态下发送FIN |
|10 | TIME_WAIT | 套接字已经关闭正在等待远程套接字的关闭即FIN、ACK、FIN、ACK都完毕经过2MSL时间后变为CLOSED状态 |
quecthing连接状态表
|**整型**| **状态编号** |
|---|------------|
|0 | 未初始化 |
|1 | 已初始化 |
|2 | 正在认证 |
|3 | 认证成功 |
|4 | 认证失败 |
|5 | 正在注册 |
|6 | 注册成功,等待订阅 |
|7 | 注册失败 |
|8 | 已订阅,数据可发送 |
|9 | 订阅失败 |
|10 | 正在注销 |
|11 | 注销成功 |
|12 | 注销失败 |
### 查询网络状态
说明: 查询网络连接状态,返回基站信息
功能码: 0x12
功能码: 12
返回的数据内容:
```
{"code":0x12,
{"code":12,
"data": ([], [], [(0, 14071232, 1120, 0, 123….),
"data": {"voice_state": 1, "data_state": 1},
"status":1}
```
| **字段** | **类型** | **含义** |
| --- | --- | --- |
| code | int | 状态码 |
| data | turple | 基站连接状态 |
| status | str | 0 失败 1成功 |
| **字段** | **类型** | **含义** |
| --- | --- |---------------------------------------|
| code | int | 状态码 |
| data | turple | voice_state:语音连接状态, data_state:数据连接状态 |
| status | str | 0 失败 1成功 |
状态说明
| **值** | **状态说明** |
| --- | --- |
| 0 | not registered, MT is not currently searching an operator to register to |
| 1 | registered, home network |
| 2 | not registered, but MT is currently trying to attach or searching an operator to register to |
| 3 | registration denied |
| 4 | unknown |
| 5 | registered, roaming |
| 6 | egistered for “SMS only”, home network (not applicable) |
| 7 | registered for “SMS only”, roaming (not applicable) |
| 8 | attached for emergency bearer services only |
| 9 | registered for “CSFB not preferred”, home network (not applicable) |
| 10 | registered for “CSFB not preferred”, roaming (not applicable) |
| 11 | emergency bearer services only |
### 查询基站定位信息
说明: 查询基站定位信息
功能码: 0x13
功能码: 13
返回的数据内容:
```
{"code":0x13,
{"code":13,
"data": (117.1138, 31.82279, 550) ,
@ -368,12 +470,14 @@ DTU的IMEI号
## 复位指令
功能码: 0xff
功能码: 255
数据内容:
```
{ Password: "012345",
"code":255,
"data":{}
}
@ -381,7 +485,7 @@ DTU的IMEI号
返回的数据内容:
`{"code": 0x06 , "data": " reset dtu " , "success":1}`
| **字段** | **类型** | **含义** |
| --- | --- | --- |
@ -395,7 +499,7 @@ DTU的IMEI号
#### 协议短信(SMS)透传 message
功能码: 0x50
功能码: 50
数据内容:
@ -410,7 +514,7 @@ DTU的IMEI号
message: {"number":"12123123", -- 目标号码
"data:" " -- 发送短信
"sms_msg:" " -- 发送短信
}
}
@ -419,7 +523,7 @@ message: {"number":"12123123", -- 目标号码
返回的数据内容:
`{"code": 0x50 , "data": " " , "success":1}`
`{"code": 50 , "data": " " , "success":1}`
| **字段** | **类型** | **含义** |
| --- | --- | --- |
@ -437,13 +541,13 @@ message: {"number":"12123123", -- 目标号码
是否开启自动更新需要密码
功能码: 0x51
功能码: 51
数据内容:
```
{
"password":" ",
"data":{ "password": "012345"}
"data":{"new_password": "012345"}
}
```
说明初始密码为固件IMEI的后六位
@ -457,7 +561,7 @@ message: {"number":"12123123", -- 目标号码
返回的数据内容:
`{"code": 0x51 , "data": " " , "success":1}`
`{"code": 51 , "data": " " , "success":1}`
| **字段** | **含义** |
| --- | --- |
@ -472,7 +576,7 @@ message: {"number":"12123123", -- 目标号码
首次登陆服务器发送注册信息
功能码: 0x53
功能码: 53
数据内容:
```
@ -490,7 +594,7 @@ message: {"number":"12123123", -- 目标号码
返回的数据内容:
`{"code": 0x53 , "data": " " , "success":1}`
`{"code": 53 , "data": " " , "success":1}`
| **字段** | **类型** | **含义** |
| --- | --- | --- |
@ -504,21 +608,23 @@ message: {"number":"12123123", -- 目标号码
修改固件版本号用于fota升级当开启fota升级版本号小于服务器端的固件版本号就会进行fota升级
功能码: 0x54
固件版本号仅支持整数
功能码: 54
数据内容:
```
{
"password":"",
"data":{
" version ": "100" --- 版本号(使用数字字符串)
"version ": "100" --- 版本号(使用数字字符串)
}
}
```
返回的数据内容:
`{"code": 0x54 , "data": " " , "success":1}`
`{"code": 54 , "data": " " , "success":1}`
| **字段** | **类型** | **含义** |
| --- | --- | --- |
@ -532,7 +638,7 @@ message: {"number":"12123123", -- 目标号码
Fota升级开关
功能码: 0x55
功能码: 55
数据内容:
```
@ -545,7 +651,7 @@ Fota升级开关
```
返回的数据内容:
`{"code": 0x55 , "data": " fota" , "success":1}`
`{"code": 55 , "data": " fota" , "success":1}`
| **字段** | **类型** | **含义** |
| --- | --- | --- |
@ -559,7 +665,7 @@ Fota升级开关
串口打印日志记录,目前不支持。日志输出连接Debug口
功能码: 0x56
功能码: 56
数据内容:
```
@ -582,7 +688,7 @@ Fota升级开关
#### 服务器获取配置参数
功能码: 0x57
功能码: 57
数据内容:
```
@ -611,7 +717,9 @@ Fota升级开关
#### 串口参数 uconf
功能码: 0x58
功能码: 58
**在透传模式下无法设置串口参数**
数据内容:
```
@ -639,27 +747,30 @@ Fota升级开关
#### 通道配置参数 conf
功能码: 0x59
功能码: 59
**在透传模式下无法设置串口参数**
数据内容:
```
{"password":"",
"data":{
"conf":"1": {
"protocol": "aliyun",
"type": "mos",
"keepAlive": "",
"clientID": "0",
"Devicename": "ec600n",
"ProductKey": "gbh26bFEA4M",
"DeviceSecret": "b7ff5acc0671d40adfd0eff57e7605f6",
"ProductSecret": "",
"cleanSession": true,
"qos": "1",
"subscribe": {"0": "/gbh26bFEA4M/ec600n/user/subtest"},
"publish": {"0": "/gbh26bFEA4M/ec600n/user/pubtest"},
"serialID": "0"}
}}
"data":{
"conf":{
"1": {
"protocol": "aliyun",
"type": "mos",
"keepAlive": "",
"clientID": "0",
"Devicename": "ec600n",
"ProductKey": "gbh26bFEA4M",
"DeviceSecret": "b7ff5acc0671d40adfd0eff57e7605f6",
"ProductSecret": "",
"cleanSession": true,
"qos": "1",
"subscribe": {"0": "/gbh26bFEA4M/ec600n/user/subtest"},
"publish": {"0": "/gbh26bFEA4M/ec600n/user/pubtest"},
"serialID": "0"}
}}}
```
**对应通道的配置参数详见6.1.10.1的通道配置详解 **
@ -719,7 +830,7 @@ Fota升级开关
###### SOCKET udp 参数
```
{
"protocol": "tcp",
"protocol": "udp",
"ping": "",
"heartbeat": 30,
"url": "220.180.239.212",
@ -827,8 +938,7 @@ Fota升级开关
| keepAlive | int | 通信之间允许的最长时间段(以秒为单位),默认为300范围60-1200使用默认值就填""或者" "。 |
| clientID | str | clientID ,自定义字符不超过64 |
| Devicename | str | 设备名称 |
| ProductKey | str |
|
| ProductKey | str |产品密钥|
| DeviceSecret | str | 设备密钥(使用一型一密认证此参数传入"") |
| ProductSecret | str | 产品密钥(使用一机一密认证时此参数传入"") |
| cleanSession | int | MQTT 保存会话标志位( 0则客户端是持久客户端当客户端断开连接时订阅信息和排队消息将被保留, 1代理将在其断开连接时删除有关此客户端的所有信息 ) |
@ -837,11 +947,42 @@ Fota升级开关
| pubTopic | str | 发布主题 |
| serialD | int | MQTT通道捆绑的串口ID (1~3) |
###### 移远云参数
```
{
"protocol": "quecthing",
"keepAlive": "", // lifetime
"ProductKey": " a1QNbCDxIWM ",
"ProductSecret": "",
"qos": "1",
"SessionFlag": "",
"sendMode": "phy",
"serialID": "1",
}
```
| **字段** | **类型** | **含义** |
| --- | --- | --- |
| quecthing | str | 腾讯云IOT的标识 |
| keepAlive | int | 通信之间允许的最长时间段(以秒为单位),默认为120范围60-1200使用默认值就填""或者" "。 |
| ProductKey | str |产品id|
| ProductSecret | str | 产品密钥|
| QOS | int | MQTT消息服务质量默认0可选择0或10发送者只发送一次消息不进行重试 1发送者最少发送一次消息确保消息到达Broker |
| SessionFlag | bool | 配置与云平台通信的数据是否采用session加密默认值为FalseTrue加密False加密 |
| sendMode | str | 移远云数据收发模式phy物模型pass透传 |
| serialD | int | MQTT通道捆绑的串口ID (1~3) |
移远云开发说明请点击以下连接获取文档:
[Quectel_移远通信物联网设备管理平台设备接入_应用指导_(Python)_2.9.0.pdf](https://quec-pro-oss.oss-cn-shanghai.aliyuncs.com/documentCenter/Quectel_%E7%A7%BB%E8%BF%9C%E9%80%9A%E4%BF%A1%E7%89%A9%E8%81%94%E7%BD%91%E8%AE%BE%E5%A4%87%E7%AE%A1%E7%90%86%E5%B9%B3%E5%8F%B0%E8%AE%BE%E5%A4%87%E6%8E%A5%E5%85%A5_%E5%BA%94%E7%94%A8%E6%8C%87%E5%AF%BC_(Python)_2.9.0.pdf)
## 设置APN
说明:这个指令只适合配置和使用不是同一张卡的场景
功能码: 0x60
**在透传模式下无法设置串口参数**
功能码: 60
数据内容:
```
@ -860,7 +1001,7 @@ apn对应列表说明:
返回的数据内容:
`{"code": 0x60 , "status":1}`
`{"code": 60 , "status":1}`
| **字段** | **类型** | **含义** |
| --- | --- | --- |
@ -870,20 +1011,24 @@ apn对应列表说明:
## GPIO pins
功能码: 0x61
功能码: 61
**在透传模式下无法设置串口参数**
pins的长度必须为3
数据内容:
```
{"password": " ",
"data":{"pins":[
"pio2", -- 网路指示灯的GPIO (pio1~pio128)
"pio4", -- 与服务器连上后通知GPIO (pio1~pio128)
"pio4" -- 重置DTU参数的GPIO (pio1~pio128)
"1", -- 网路指示灯的GPIO (pio1~pio128)
"2", -- 与服务器连上后通知GPIO (pio1~pio128)
"3" -- 重置DTU参数的GPIO (pio1~pio128)
]}}
```
返回的数据内容:
`{"code": 0x61 , "status":1}`
`{"code": 61 , "status":1}`
| **字段** | **类型** | **含义** |
| --- | --- | --- |
@ -893,7 +1038,7 @@ apn对应列表说明:
## OTA
功能码: 0x62
功能码: 62
数据内容:
```
@ -903,30 +1048,33 @@ apn对应列表说明:
```
返回的数据内容:
`{"code": 0x62 , "status":1}`
`{"code": 62 , "status":1}`
| **字段** | **类型** | **含义** |
| --- | --- | --- |
| code | Str | 状态码 |
| data | str | OTA状态 |
| success | int | 0 失败 1成功 |
| status | int | 0 失败 1成功 |
## 参数设置
功能码: 0x63
功能码: 63
数据内容:
```
{"password": " ",
"data":{ 完整配置文件(省略)
"data":{"dtu_config":{完整配置文件内容}
}
```
完整配置文件参照《DTU上手说明》
返回的数据内容:
`{"code": 0x63 ,"status":1}`
`{"code": 63 , "status":1}`
| **字段** | **类型** | **含义** |
| --- | --- | --- |
| code | Str | 状态码 |
| data | dict | 完整的配置文件 |
| status | int | 0 失败 1成功 |
| status | int | 0 失败 1成功 |

View File

@ -34,70 +34,81 @@
"plate": 1, //是否在报文中添加IMEI
"password": "123", //设置密码
"conf": { //设置在线连接
"1": { //通道序号
//HTTP连接设置
"protocol": "http", //连接类型固定为http
"method": "get", //请求方式
"url": "http://httpbin.org/get", //请求url
"reg_data": "", //附带的固定文本
"timeout": "", //超时
"serialID": 1 //捆绑的串口(1-2)
"1": { //通道序号
//HTTP连接设置
"protocol": "http", //连接类型固定为http
"method": "get", //请求方式
"url": "http://httpbin.org/get", //请求url
"reg_data": "", //附带的固定文本
"timeout": "", //超时
"serialID": 1 //捆绑的串口(1-2),需要为配置文件中uconf中存在的key
},
"2": {
//TCP&UDP连接设置
"protocol": "tcp", //连接类型,TCP填写”tcp”,UDP填写”udp”
"ping": "", //ping
"heartbeat": 30, //心跳时间
"url": "220.180.239.212", //请求url
"port": "8305", //端口
"keepAlive": 300, //保持连接时间
"serialID": 2 //捆绑串口
"2": {
//TCP&UDP连接设置
"protocol": "tcp", //连接类型,TCP填写”tcp”,UDP填写”udp”
"ping": "", //ping
"heartbeat": 30, //心跳时间
"url": "220.180.239.212", //请求url
"port": "8305", //端口
"keepAlive": 300, //保持连接时间
"serialID": 2 //捆绑串口,需要为配置文件中uconf中存在的key
},
"3": {
//mqtt连接设置
"protocol": "mqtt", //连接类型
"clientID": "test_mqtt", //客户端id
"keepAlive": "", //keep alive超时
"url": "mq.tongxinmao.com", //url
"port": "18830", //端口
"cleanSession": "0", //clean session
"subscribe": {"0": "/public/TEST/python"}, //订阅的主题(支持多个)
"publish": {"0": "/public/TEST/python"}, //发布的主题(支持多个)
"qos": "0", //QoS
"retain": "1", //是否使用内部重连
"serialID": "1" //捆绑的串口
"3": {
//mqtt连接设置
"protocol": "mqtt", //连接类型
"clientID": "test_mqtt", //客户端id
"keepAlive": "", //keep alive超时
"url": "mq.tongxinmao.com", //url
"port": "18830", //端口
"cleanSession": "0", //clean session
"subscribe": {"0": "/public/TEST/python"}, //订阅的主题(支持多个)
"publish": {"0": "/public/TEST/python"}, //发布的主题(支持多个)
"qos": "0", //QoS
"retain": "1", //是否使用内部重连
"serialID": "1" //捆绑的串口
},
//阿里云连接设置
"4": {
"protocol": "aliyun", //连接类型
"type": "mos", //一机一密/一型一密设置
"keepAlive": "", // keep alive超时
"clientID": " test_mos ", //客户端id
"Devicename": " light01", //设备名称
"ProductKey": " a1QNbCDxIWM ", //product key
"DeviceSecret": "0bceb8010ade0df2e6989982e63f7601", //device secret
"ProductSecret": "", //product secret
"cleanSession": "0", //clean session
"qos": "1", //QoS
"subscribe": {"0": "/a1QNbCDxIWM/light01/user/get"}, //订阅的主题(支持多个)
"publish": {"0": "/a1QNbCDxIWM/light01/user/update"}, //发布的主题(支持多个)
"serialID": "1" //捆绑的串口
//阿里云连接设置
"protocol": "aliyun", //连接类型
"type": "mos", //一机一密/一型一密设置
"keepAlive": "", // keep alive超时
"clientID": " test_mos ", //客户端id
"Devicename": " light01", //设备名称
"ProductKey": " a1QNbCDxIWM ", //product key
"DeviceSecret": "0bceb8010ade0df2e6989982e63f7601", //device secret
"ProductSecret": "", //product secret
"cleanSession": "0", //clean session
"qos": "1", //QoS
"subscribe": {"0": "/a1QNbCDxIWM/light01/user/get"}, //订阅的主题(支持多个)
"publish": {"0": "/a1QNbCDxIWM/light01/user/update"}, //发布的主题(支持多个)
"serialID": "1" //捆绑的串口
},
"5": {
//腾讯云设置
"protocol": "txyun",
"type": "mos",
"keepAlive": "",
"clientID": "test_tx_mos",
"Devicename": "Smart_test01",
"ProductKey": "H7MBLRYXN9",
"DeviceSecret": "89c7tXT3s3grZTr/YFjxSg==",
"ProductSecret": "",
"cleanSession": "0",
"qos": "1",
"subscribe": {"0": "H7MBLRYXN9/Smart_test01/control"},
"publish": {"0": "H7MBLRYXN9/Smart_test01/event"},
"serialID": "1"
"5": {
//腾讯云设置
"protocol": "txyun",
"type": "mos",
"keepAlive": "",
"clientID": "test_tx_mos",
"Devicename": "Smart_test01",
"ProductKey": "H7MBLRYXN9",
"DeviceSecret": "89c7tXT3s3grZTr/YFjxSg==",
"ProductSecret": "",
"cleanSession": "0",
"qos": "1",
"subscribe": {"0": "H7MBLRYXN9/Smart_test01/control"},
"publish": {"0": "H7MBLRYXN9/Smart_test01/event"},
"serialID": "1"
},
"6": {
//移远云设置
"protocol": "quecthing", //连接类型
"keepAlive": "", //心跳时间
"ProductKey": "p1118c", //product key
"ProductSecret": "c3Jzd3ZaNzVrV2Vj", //product secret
"qos": "1", //qos
"SessionFlag": false, //是否采用session加密
"sendMode": "phy", //发送模式
"serialID": "1" //捆绑的串口
}
},
"reg": 1, //发送登陆消息
@ -106,7 +117,8 @@
"nolog": 0, //是否输出log
"message": {}, //协议短信透传
"uconf": { //串口设置
"1": {
"1": { //key为UART编号,使用UART0填入“0”使用UART1则填入“1”以此类推同时
//该值同时在通道配置的serialID项中使用
"baudrate": "115200",
"databits": "8",
"parity": "0",
@ -136,14 +148,31 @@
"service_acquire": 0, //是否开启服务器获取参数
"work_mode": "command", //工作模式
"auto_connect": 1, //自动连接
"offline_storage": false //离线存储
"offline_storage": false, //离线存储
"modbus": { //modbus配置
"groups": [ //设备分组,分组编号为其在列表中的下标,从0开始,以此类推
{
"device_type": "temp_humid_sensor", //设备类型
"device_model": "TH10S-B", //设备型号
"slave_address":["0x01"] //群组modbus地址,可以填入多个地址
},
{
"device_type": "light_sensor",
"device_model": "YGC-BG-M",
"slave_address":["0x02", "0x03"]
}
]
}
}
```
配置文件中"serialID"字段配置说明:
conf通道配置中的“serialID”字段为捆绑的UART口编号serialID中的数值必须是uconf串口配置中已经配置的UART口编号
![](./media/config_serial_id.png)
按需求编写配置文件后将配置文件保存为"dtu_config.json"并保存至DTU代码库中的"dtu"文件夹内
@ -183,7 +212,7 @@ DTU运行成功下面为读取的配置文件。
## 报文格式
### 命令模式/modbus模式
### 命令模式
支持多通道透传通过配置文件中的serialID字段可以对通道与串口进行绑定每个串口均支持绑定多个通道。在发送数据时需要传入通道idDTU会向指定的通道发送数据。
@ -215,13 +244,13 @@ msg_data消息体当msg_len为0时此项可省略
**发送报文:**
`“1,6,376e6e7,abcedf”` (msg_len不为0)
`“1,6,1398235801,abcedf”` (msg_len不为0)
`“1,0”` (msg_len为0)
**返回报文:**
`“5,2e46f5,20001”`
`“1,6,2584251182,ijklmn”`
#### MQTT/Aliyun/Txyun
@ -247,13 +276,76 @@ msg_data消息体当msg_len为0时此项也不可省略
- 示例报文:
**发送报文:**
- 发送报文:
`“1,1,6,376e6e7,abcedf”`
`“1,1,6,1398235801,abcedf”`
**返回报文:**
- 返回报文:
`“1,1,5,2e46f5,20002”`
`“1,1,6,2584251182,ijklmn”`
#### quecthing
- 上行数据报文格式:
`"<channel_id>,<pkgid>,<msg_len>","<crc32>",<msg_data>"`
- 下行数据报文格式:
`"<channel_id>,<pkgid>,<msg_len>","<crc32>",<msg_data>"`
- 字段说明:
channel_id通道id配置文件中通道id
pkgidquecthing物模型pkgid透传模式上行、下行报文与物模型上行非应答报文时该字段固定值为0
msg_len消息体长度字符串格式可以为0
crc32消息数据crc32校验码当msg_len为0时此项也不可省略
msg_data消息体当msg_len为0时此项也不可省略
**示例报文:**
- 发送报文:
`“1,0,6,1398235801,abcedf”` (透传&物模型非应答)
`“1,16929,6,1398235801,abcedf”` (物模型应答)
- 返回报文:
`“1,0,6,2584251182,ijklmn”` (透传报文)
`“1,16930,6,2584251182,ijklmn”` (物模型报文)
#### 从串口执行控制命令
- 发送命令报文格式:
`"<identify>,<msg_len>","<crc32>",<msg_data>"`
- 返回命令报文格式:
`"<msg_len>","<crc32>",<msg_data>"`
- 字段说明:
identify: 串口控制命令识别码,固定值为"99"
msg_len消息体长度字符串格式可以为0
crc32消息数据crc32校验码当msg_len为0时此项也不可省略
msg_data消息体当msg_len为0时此项也不可省略
- 上行报文:
`“99,6,1398235801,abcedf”`
- 下行报文:
`“99,6,2584251182,ijklmn”`
### 透传模式
@ -285,13 +377,13 @@ msg_data消息体当msg_len为0时此项可省略
**发送报文:**
`“6,376e6e7,abcedf”` (msg_len不为0)
`“6,1398235801,abcedf”` (msg_len不为0)
`“0”` (msg_len为0)
**返回报文:**
`“5,2e46f5,20001”`
`“6,2584251182,ijklmn”`
#### MQTT/Aliyun/Txyun
@ -317,19 +409,66 @@ msg_data消息体当msg_len为0时此项也不可省略
**发送报文:**
`“6,376e6e7,abcedf”`
`“6,1398235801,abcedf”`
**返回报文:**
`“5,2e46f5,20002”`
`“6,2584251182,ijklmn”`
#### quecthing
- 发送数据报文格式:
`"<pkgid>,<msg_len>","<crc32>",<msg_data>"`
- 返回数据报文格式:
`"<pkgid>,<msg_len>","<crc32>",<msg_data>"`
- 字段说明:
pkgidquecthing物模型pkgid透传模式上行、下行报文与物模型上行非应答报文时该字段固定值为0
msg_len消息体长度字符串格式可以为0
crc32消息数据crc32校验码当msg_len为0时此项也不可省略
msg_data消息体当msg_len为0时此项也不可省略
**示例报文:**
- 上行报文:
`“0,6,1398235801,abcedf”` (透传&物模型非应答)
`“16929,6,1398235801,abcedf”` (物模型应答)
- 下行报文:
`“0,6,2584251182,ijklmn”` (透传报文)
`“16930,6,2584251182,ijklmn”` (物模型报文)
### modbus模式
modbus模式会完整透传上行与上行报文上行与下行都为bytes类型
**示例报文:**
- 发送报文
`b'\x01\x03\x04\x01\x01\x02\x0c\xabj'`
- 返回报文
`b'\x01\x03\x04\x01\x01\x02\rj\xaa'`
### 与云端通信报文
DTU与云端通信报文使用json格式
#### 云端下行报文
- 命令模式与modbus模式
- 命令模式:
`{“msg_id”: msg_id, “data”: “1234”[, “cmd_code”: 0X40, “topic_id”: 1]}`
@ -347,13 +486,36 @@ cmd_code可选字段填写对应功能码并又DTU执行相应的操作
topic_id可选字段填写mqtt返回需要publish的topic\_id此字段仅在命令模式与使用MQTT/Aliyun/Txyun时生效
- modbus模式
`{“msg_id”: msg_id, “modbus”: {"groups": {"num": 0, "cmd": ["0x03", "0x00", "0x00", "0x00", "0x02"]}}}`
字段说明:
msg_id报文id一般为时间戳+3位随机数
data报文消息字段
cmd_code可选字段填写对应功能码并又DTU执行相应的操作此字段仅在命令模式下生效
topic_id可选字段填写mqtt返回需要publish的topic_id此字段仅在命令模式与使用MQTT/Aliyun/Txyun时生效
modbus可选字段此字段仅在modbus模式使用此字段下有2个子字段groups与command
&ensp;&ensp;&ensp;&ensp;groups可选字段在modbus模式下向指定的地址组发送消息
&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;num配置文件中的地址组编号
&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;cmd向地址组发送的modbus命令
&ensp;&ensp;&ensp;&ensp;command可选字段在modbus下直接向UART口写入指定modbus命令
#### 云端上行报文
命令模式与modbus模式
- 命令模式与modbus模式
`{“msg_id”: msg_id, “data”: “1234”[, “cmd_code”: 0X40, “status”: 1]}`
透传模式:
- 透传模式:
`{“msg_id”: msg_id, “data”: “1234”}`

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -3,14 +3,17 @@
"password": "123",
"conf": {
"1": {
"protocol": "quecthing",
"keepAlive": "",
"ProductKey": "p1118c",
"ProductSecret": "c3Jzd3ZaNzVrV2Vj",
"qos": "1",
"SessionFlag": false,
"sendMode": "pass",
"serialID": "2"
"protocol": "hwyun",
"url": "a1621ed65c.iot-mqtts.cn-north-4.myhuaweicloud.com",
"port": "1883",
"device_id": "621ece4cc4e6a958e354301e_869537055499330",
"secret": "a306255686a71e56ad53965fc2771bf8",
"keep_alive": 10,
"cleanSession": true,
"subscribe": {"0": "$oc/devices/621ece4cc4e6a958e354301e_869537055499330/sys/messages/down"},
"publish": {"0": "$oc/devices/621ece4cc4e6a958e354301e_869537055499330/sys/messages/up"},
"qos": 0,
"serialID": 2
}
},
"reg": 0,
@ -40,7 +43,8 @@
""
],
"service_acquire": 0,
"work_mode": "command",
"work_mode": "though",
"auto_connect": 1,
"offline_storage": false
}
"offline_storage": false,
"modbus": {}
}

View File

@ -8,7 +8,6 @@ from aLiYun import aLiYun
from TenCentYun import TXyun
from umqtt import MQTTClient
from machine import UART
import uos
uos.chdir('/usr/')
from singleton import Singleton
from t_h import SensorTH
@ -42,6 +41,7 @@ class RET:
PROTOCOLERR = "4008"
REQERR1 = "4009"
QUECIOTERR = "4010"
HWYUNERR = "4011"
REQERR2 = "5000"
# 功能错误
PASSWORDERR = "5001"
@ -89,6 +89,7 @@ error_map = {
RET.TXYUNMQTTERR: u"txyun connect failed",
RET.PROTOCOLERR: u"protocol parse error",
RET.QUECIOTERR: u"quecthing connect failed",
RET.HWYUNERR: u"huaweiyun connect failed",
# 功能错误
RET.PASSWORDERR: u"password not found",
RET.PASSWDVERIFYERR: u"password verify error",
@ -120,6 +121,8 @@ CONFIG = {
HISTORY_ERROR = []
SERIAL_MAP = dict()
CHANNELS = dict()
"""================================================= singleton ===================================================="""
@ -132,6 +135,65 @@ class DTUException(Exception):
self.message = message
class ProdDocumentParse(object):
def __init__(self):
self.document = ""
def read(self, config_path):
if not self.document:
self.refresh_document(config_path)
def refresh_document(self, config_path):
try:
with open(config_path, mode="r") as f:
self.document = f.read()
return self.document # new
except Exception as e:
# 加载旧版本文件
try:
with open(config_path + ".bak", mode="r") as f:
self.document = f.read()
return self.document
except Exception as e:
# 加载出厂文件
try:
with open(CONFIG['config_backup_path'], mode="r") as f:
self.document = f.read()
return self.document
except:
print("'dtu_config.json', last version and default config not exist")
raise Exception(RET.READFILEERR)
def _parse_document(self, parser_obj):
try:
document_loader = ujson.loads(self.document)
except Exception as e:
print(error_map.get(RET.JSONLOADERR))
raise RET.JSONLOADERR
try:
dtu_data_obj = parser_obj.reload(**document_loader)
except Exception as e:
# print("e = {}".format(e))
print("{}: {}".format(error_map.get(RET.JSONLOADERR), e))
raise RET.JSONPARSEERR
return dtu_data_obj
def parse(self, parser_obj):
config_path = CONFIG["config_path"]
if not self.exist_config_file(config_path):
# 从uart口读取数据
print(error_map.get(RET.CONFIGNOTEXIST))
else:
self.read(config_path=config_path)
return self._parse_document(parser_obj=parser_obj)
@staticmethod
def exist_config_file(config_path):
config_split = config_path.rsplit("/", 1)
return config_split[1] in uos.listdir(config_split[0])
"""=================================================== dtu object ==================================================="""
@ -247,7 +309,7 @@ class ProdDtu(object):
# 升级包下载地址的请求
version = self.parse_data.version
moduleType = ota[1]
download_url = "https://cloudota.quectel.com:8100/v2/fota/fw"
download_url = "https://cloudota.quectel.com:8100/v1/fota/fw"
headers = {"access_token": access_token, "Content-Type": "application/json"}
acquire_data = {
"version": str(version),
@ -342,12 +404,13 @@ class ProdDtu(object):
return data
def server_filter(self):
if self.parse_data == 'command':
if self.parse_data.work_mode == 'command':
for cid, channel in self.parse_data.conf.items():
if int(channel.serialID) in self.channel.serial_channel_dict:
self.channel.serial_channel_dict[int(channel.serialID)].append(cid)
serial_id = int(channel.get("serialID"))
if serial_id in self.channel.serial_channel_dict:
self.channel.serial_channel_dict[serial_id].append(cid)
else:
self.channel.serial_channel_dict[int(channel.serialID)] = [cid]
self.channel.serial_channel_dict[serial_id] = [cid]
return self.parse_data.conf
else:
serv_map = dict()
@ -369,6 +432,7 @@ class ProdDtu(object):
# 透传与modbus服务器筛选
serv_maps = self.server_filter()
self._serv_connect(serv_maps, reg_data)
print("SERV conn success")
_thread.start_new_thread(self.uart.read, ())
if self.parse_data.offline_storage:
_thread.start_new_thread(self.offline_storage.retry_offline_handler, ())
@ -495,10 +559,26 @@ class ProdDtu(object):
print("quecthing connect waiting server...")
else:
logger.error(error_map.get(RET.QUECIOTERR))
elif protocol.startswith("hwyun"):
hw_req = HuaweiCloudTransfer()
status = hw_req.serialize(data)
try:
_thread.start_new_thread(hw_req.connect, ())
utime.sleep_ms(100)
except Exception as e:
logger.error("{}: {}".format(error_map.get(RET.HWYUNERR), e))
else:
if status == RET.OK:
self.channel.channel_dict[cid] = hw_req
hw_req.channel_id = cid
print("hwyun conn succeed")
else:
logger.error(error_map.get(RET.HWYUNERR))
else:
continue
@Singleton
class ProdGPIO(object):
def __init__(self):
# self.gpio1 = Pin(Pin.GPIO1, Pin.OUT, Pin.PULL_DISABLE, 0)
@ -530,65 +610,6 @@ class ProdGPIO(object):
self.gpio1.write(1)
class ProdDocumentParse(object):
def __init__(self):
self.document = ""
def read(self, config_path):
if not self.document:
self.refresh_document(config_path)
def refresh_document(self, config_path):
try:
with open(config_path, mode="r") as f:
self.document = f.read()
return self.document # new
except Exception as e:
# 加载旧版本文件
try:
with open(config_path + ".bak", mode="r") as f:
self.document = f.read()
return self.document
except Exception as e:
# 加载出厂文件
try:
with open(CONFIG['config_backup_path'], mode="r") as f:
self.document = f.read()
return self.document
except:
logger.info("'dtu_config.json', last version and default config not exist")
raise Exception(RET.READFILEERR)
def _parse_document(self, parser_obj):
try:
document_loader = ujson.loads(self.document)
except Exception as e:
logger.error(error_map.get(RET.JSONLOADERR))
raise RET.JSONLOADERR
try:
dtu_data_obj = parser_obj.reload(**document_loader)
except Exception as e:
# logger.info("e = {}".format(e))
logger.error("{}: {}".format(error_map.get(RET.JSONLOADERR), e))
raise RET.JSONPARSEERR
return dtu_data_obj
def parse(self, parser_obj):
config_path = CONFIG["config_path"]
if not self.exist_config_file(config_path):
# 从uart口读取数据
logger.error(error_map.get(RET.CONFIGNOTEXIST))
else:
self.read(config_path=config_path)
return self._parse_document(parser_obj=parser_obj)
@staticmethod
def exist_config_file(config_path):
config_split = config_path.rsplit("/", 1)
return config_split[1] in uos.listdir(config_split[0])
"""===================================================socket protocol==================================================="""
@ -794,6 +815,8 @@ class AbstractDtuMqttTransfer(object):
self.product_secret = ""
self.device_name = ""
self.device_secret = ""
self.user = ""
self.password = ""
# self.control_channel = False
self.pub_topic_map = dict()
self.sub_topic_map = dict()
@ -816,6 +839,11 @@ class AbstractDtuMqttTransfer(object):
rec = self.cli.publish(topic, send_msg, qos=self.qos)
def send(self, data, topic_id=None, *args):
if topic_id is None:
topic_list = self.pub_topic.keys()
for topic in topic_list:
self.publish(data, topic)
print("send data:", data)
try:
topic = self.pub_topic.get(str(topic_id))
self.publish(data, topic)
@ -828,8 +856,12 @@ class AbstractDtuMqttTransfer(object):
# 写入uart/远程控制
rec = self.uart.output(msg.decode(), self.serial, mqtt_id=self.channel_id)
if isinstance(rec, dict):
topic_id = rec.pop('topic_id')
self.send(rec, topic_id)
if isinstance(rec, dict):
if "topic_id" in rec:
topic_id = rec.pop('topic_id')
else:
topic_id = list(self.pub_topic.keys())[0]
self.send(rec, topic_id)
def disconnect(self):
self.cli.disconnect()
@ -843,6 +875,8 @@ class AbstractDtuMqttTransfer(object):
self.client_id = data.get("clientID")
self.device_name = data.get("Devicename")
self.product_key = data.get("ProductKey")
self.user = data.get("user")
self.password = data.get("password")
if self.iot_type == "mos":
self.device_secret = data.get('DeviceSecret') if data.get("DeviceSecret") else None
self.product_secret = None
@ -875,7 +909,14 @@ class DtuMqttTransfer(AbstractDtuMqttTransfer):
# self.code = code
def connect(self):
self.cli = MQTTClient(self.client_id, self.url, self.port, keepalive=self.keep_alive)
print("mqt connect")
print(self.url)
print(self.port)
print(self.client_id)
print(self.user)
print(self.password)
self.cli = MQTTClient(client_id=self.client_id, server=self.url, port=self.port,
user=self.user, password=self.password, keepalive=self.keep_alive)
self.cli.set_callback(self.callback)
self.cli.connect(clean_session=self.clean_session)
for tid, s_topic in self.sub_topic.items():
@ -1045,7 +1086,10 @@ class QuecthingDtuTransfer:
quecIot.setEventCB(self.callback)
def send(self, data, pkgid=None, *args):
send_data = data['data']
if isinstance(data, str):
send_data = data
else:
send_data = ujson.dumps(data)
if self.send_mode == "pass":
quecIot.passTransSend(self.qos, send_data)
else:
@ -1115,6 +1159,91 @@ class QuecthingDtuTransfer:
return quecIot.getWorkState()
class HuaweiCloudTransfer(DtuMqttTransfer):
def __init__(self):
super().__init__()
self.conn_type = "hwyun"
self.device_id = ""
self.client_id = ""
self.user = ""
self.password = ""
@staticmethod
def hmac_sha256_digest(key_K, data):
def xor(x, y):
return bytes(x[i] ^ y[i] for i in range(min(len(x), len(y))))
if len(key_K) > 64:
raise ValueError('The key must be <= 64 bytes in length')
padded_K = key_K + b'\x00' * (64 - len(key_K))
ipad = b'\x36' * 64
opad = b'\x5c' * 64
h_inner = uhashlib.sha256(xor(padded_K, ipad))
h_inner.update(data)
h_outer = uhashlib.sha256(xor(padded_K, opad))
h_outer.update(h_inner.digest())
return ubinascii.hexlify(h_outer.digest()).decode()
def register(self):
local_time = utime.localtime()
time_sign = "%s%s%s%s" % (local_time[0], "%02d" % local_time[1], "%02d" % local_time[2], "%02d" % local_time[3])
self.client_id = self.device_id + "_0_0_" + time_sign
print("client id")
print(self.client_id)
self.user = self.device_id
# self.password = hmac.new(time_sign.encode("utf-8"), self.device_secret.encode("utf-8"), digestmod=uhashlib.sha256).hexdigest()
self.password = self.hmac_sha256_digest(time_sign.encode("utf-8"), self.device_secret.encode("utf-8"))
print("pw")
print(self.password)
def serialize(self, data):
print("hwy data")
print(data)
try:
self.url = data.get("url")
self.port = data.get("port")
self.device_id = data.get("device_id")
self.device_secret = data.get("secret")
self.keep_alive = int(data.get("keepAlive")) if data.get("keepAlive") else 60
clr_ses = data.get('cleanSession')
if clr_ses in ["1", 1, True, 'true']:
self.clean_session = True
else:
self.clean_session = False
self.sub_topic = data.get('subscribe')
self.pub_topic = data.get('publish')
self.qos = int(data.get('qos')) if data.get('qos') else 0
# self.retain = int(data.get('retain')) if data.get('retain') else 0
self.serial = int(data.get('serialID'))
except Exception as e:
print("SERIAL ERR")
print(e)
return RET.PARSEERR
else:
return RET.OK
def connect(self):
self.register()
print("hw connect")
print(self.url)
print(self.port)
print(self.client_id)
print(self.user)
print(self.password)
self.cli = MQTTClient(client_id=self.client_id, server=self.url, port=self.port,
user=self.user, password=self.password, keepalive=self.keep_alive, ssl=False)
self.cli.set_callback(self.callback)
self.cli.connect(clean_session=self.clean_session)
for tid, s_topic in self.sub_topic.items():
self.cli.subscribe(s_topic, qos=self.qos)
for tid, p_topic in self.pub_topic.items():
self.cli.publish(p_topic, "hello world", qos=self.qos)
logger.info("hw set successful")
"""===================================================data document protocol==================================================="""
@ -1135,7 +1264,7 @@ class DTUDocumentData(object):
self.conf = dict()
self.pins = []
self.apn = []
self.modbus = []
self.modbus = dict()
self.work_mode = "command"
self.auto_connect = True
self.offline_storage = False
@ -1295,7 +1424,7 @@ class DtuUart(object):
config_path = CONFIG["config_path"]
config_params = ProdDocumentParse().refresh_document(config_path)
uconf = ujson.loads(config_params)["uconf"]
self.serial_map = dict()
self.serial_map = SERIAL_MAP
for sid, conf in uconf.items():
uart_conn = UART(getattr(UART, 'UART%d' % int(sid)),
int(conf.get("baudrate")),
@ -1306,9 +1435,10 @@ class DtuUart(object):
self.serial_map[sid] = uart_conn
# 初始化方向gpio
self._direction_pin(config_params)
self.exec_cmd = DtuExecCommand()
self.protocol = DtuProtocolData()
self.channels = ChannelTransfer()
self.exec_cmd = DtuExecCommand()
self.exec_modbus = ModbusCommand()
self.protocol = DtuProtocolData()
self.concat_buffer = ""
self.wait_length = 0
self.wait_retry_count = 0
@ -1332,7 +1462,7 @@ class DtuUart(object):
print(data)
if isinstance(data, (int, float)):
data = str(data)
if self.dtu_d.work_mode == 'command':
if self.dtu_d.work_mode in ['command', "modbus"]:
print("CMD START")
try:
if isinstance(data, str):
@ -1356,7 +1486,12 @@ class DtuUart(object):
rec['topic_id'] = msg_data.get("topic_id")
return rec
elif modbus_data is not None:
pass
uart_port = self.serial_map.get(str(serial_id))
if uart_port is None:
print("UART serial id error")
return False
rec = self.exec_modbus.exec_modbus_cmd(modbus_data, uart_port)
return rec
except Exception as e:
logger.info("{}: {}".format(error_map.get(RET.CMDPARSEERR), e))
# package_data
@ -1384,6 +1519,8 @@ class DtuUart(object):
if len(msg_data) < data_len:
self.concat_buffer = str_msg
self.wait_length = data_len - len(msg_data)
print("wait length")
print(self.wait_length)
return False
elif len(msg_data) > data_len:
self.concat_buffer = ""
@ -1396,14 +1533,6 @@ class DtuUart(object):
# 设备to云端
def unpackage_datas(self, str_msg, channels, sid):
# str_msg = bytestream.decode()
if self.concat_buffer:
if len(str_msg) > self.wait_length:
self.concat_buffer = ""
self.wait_length = 0
else:
str_msg = self.concat_buffer + str_msg
self.concat_buffer = ""
# 移动gui判断逻辑
gui_flag = self.gui_tools_parse(str_msg, sid)
# gui命令主动终止
@ -1411,6 +1540,8 @@ class DtuUart(object):
return False, []
# 避免后续pop操作影响已有数据
channels_copy = [x for x in channels]
print("dtu word mode")
print(self.dtu_d.work_mode)
try:
if self.dtu_d.work_mode == 'command':
params_list = str_msg.split(",", 4)
@ -1462,6 +1593,23 @@ class DtuUart(object):
return {'data': msg_data}, [channel, topic_id]
else:
return False, []
elif self.dtu_d.work_mode == 'modbus':
channel_id = channels_copy.pop()
channel = self.channels.channel_dict.get(str(channel_id))
if not channel:
print("Channel id not exist. Check serialID config.")
return False, []
print("modbus str_msg")
print(type(str_msg))
print(str_msg)
modbus_data_list = str_msg.split(",")
hex_list = ["0x" + x for x in modbus_data_list]
# 返回channel
if channel.conn_type in ['http', 'tcp', 'udp']:
return hex_list, [channel]
else:
topics = list(channel.pub_topic.keys())
return hex_list, [channel, topics[0]]
# 透传模式
else:
params_list = str_msg.split(",", 3)
@ -1586,7 +1734,13 @@ class DtuUart(object):
if not channels:
logger.error("Serial Config not exist!")
return False
str_msg = data.decode()
try:
if self.dtu_d.work_mode == "modbus":
str_msg = ubinascii.hexlify(data, ',').decode()
else:
str_msg = data.decode()
except:
return False
read_msg, send_params = self.unpackage_datas(str_msg, channels, sid)
if read_msg is False:
return False
@ -1759,16 +1913,13 @@ class BasicSettingCommand(object):
return {'code': code, 'status': 1}
def set_plate(self, code, data):
self.set_int_data(code, data, 'plate')
return {'code': code, 'status': 1}
return self.set_int_data(code, data, 'plate')
def set_reg(self, code, data):
self.set_int_data(code, data, 'reg')
return {'code': code, 'status': 1}
return self.set_int_data(code, data, 'reg')
def set_version(self, code, data):
self.set_int_data(code, data, 'version')
return {'code': code, 'status': 1}
return self.set_int_data(code, data, 'version')
def set_passwd(self, code, data):
try:
@ -1782,8 +1933,7 @@ class BasicSettingCommand(object):
return {'code': code, 'status': 1}
def set_fota(self, code, data):
self.set_int_data(code, data, 'fota')
return {'code': code, 'status': 1}
return self.set_int_data(code, data, 'fota')
def set_ota(self, code, data):
print("set_ota: ", code, data)
@ -1805,12 +1955,10 @@ class BasicSettingCommand(object):
return {'code': code, 'status': 1}
def set_nolog(self, code, data):
self.set_int_data(code, data, 'nolog')
return {'code': code, 'status': 1}
return self.set_int_data(code, data, 'nolog')
def set_service_acquire(self, code, data):
self.set_int_data(code, data, 'service_acquire')
return {'code': code, 'status': 1}
return self.set_int_data(code, data, 'service_acquire')
def set_uconf(self, code, data):
# 透传模式不能配置
@ -1958,14 +2106,71 @@ class BasicSettingCommand(object):
return {'code': code, 'status': 0}
return {'code': code, 'status': 1}
@Singleton
class ModbusCommand:
def __init__(self):
print("modbusCMD start")
config_params = ProdDocumentParse().refresh_document(CONFIG["config_path"])
mode = ujson.loads(config_params)['work_mode']
if mode == "modbus":
self.modbus_conf = ujson.loads(config_params)['modbus']
print(self.modbus_conf)
self.groups = dict()
self._load_groups()
def _load_groups(self):
print("modbus load groups")
groups_conf = self.modbus_conf.get("groups", [])
idx = 0
print(groups_conf)
for group in groups_conf:
print(group)
self.groups[idx] = [int(x, 16) for x in group['slave_address']]
idx += 1
def exec_modbus_cmd(self, data, uart_port):
print("exec modbus cmd")
if "groups" in data:
groups_num = data['groups'].get("num")
cmd = data['groups'].get("cmd")
try:
int_cmd = [int(x, 16) for x in cmd]
except Exception as e:
print("modbus command error: %s" % e)
return {"status": 0, "error": e}
groups_addr = self.groups.get(int(groups_num))
for addr in groups_addr:
modbus_cmd = [addr]
modbus_cmd.extend(int_cmd)
crc_cmd = modbus_crc(bytearray(modbus_cmd))
print("modbus uart write")
print(crc_cmd)
uart_port.write(crc_cmd)
utime.sleep(1)
return {'code': cmd, 'status': 1}
elif "command" in data:
command = data['command']
try:
int_cmd = [int(x, 16) for x in command]
crc_cmd = modbus_crc(bytearray(int_cmd))
except Exception as e:
print("modbus command error: %s" % e)
return {"status": 0, "error": e}
print("modbus write cmd")
print(crc_cmd)
uart_port.write(crc_cmd)
return {'code': command, 'status': 1}
else:
err_msg = "can't get any modbus params"
print(err_msg)
return {'code': 0, "status": 0, "error": err_msg}
@Singleton
class ChannelTransfer(object):
def __init__(self):
self.dtu_c = DTUDocumentData()
self.channel_dict = {}
self.channel_dict = CHANNELS
self.serial_channel_dict = dict()
# self.control_code = None
@ -2050,22 +2255,6 @@ class DtuExecCommand(object):
return {'code': cmd_code, 'status': 0, 'error': error_map.get(RET.POINTERR)}
return rec
def exec_modbus_cmd(self, modbus_cmd, data):
if isinstance(modbus_cmd, list):
mod_array = bytearray(modbus_cmd)
crc_val = modbus_crc(mod_array)
for num in crc_val:
mod_array.append(num)
elif isinstance(modbus_cmd, int):
pass
elif isinstance(modbus_cmd, str):
pass
else:
return {'code': modbus_cmd, 'status': 0, 'error': error_map.get(RET.MODBUSERR)}
return {'code': modbus_cmd, 'status': 1}
@Singleton
class DTUOfflineHandler:
@ -2099,7 +2288,9 @@ def modbus_crc(string_byte):
gen_crc = hex(((crc & 0xff) << 8) + (crc >> 8))
int_crc = int(gen_crc, 16)
high, low = divmod(int_crc, 0x100)
return high, low
string_byte.append(high)
string_byte.append(low)
return string_byte
"""=================================================== run ============================================================"""