# iotos-soft-gateway **Repository Path**: jason34/iotos-soft-gateway ## Basic Information - **Project Name**: iotos-soft-gateway - **Description**: No description available - **Primary Language**: Unknown - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 65 - **Created**: 2021-03-04 - **Last Updated**: 2021-11-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 1. 背景 目前有两类情况可能会导致设备或子系统无法连接至 IoTOS: - IoTOS 目前支持 MQTT、CoAP、LwM2M、HTTP 这四种协议,且认证方式要符合 IoTOS 的规定,但很多存量设备或者子系统使用了 TCP\UDP\WS 等协议,且认证方式多种多样,甚至连产品标识(对应 IoTOS 里的 PK)也有缺失; - IoTOS 作为物联网中台对南向设备只有 Server 的角色,没有 Client 的角色,但很多子系统往往提供的是 Server,因此在 IoTOS 和子系统之间必须有一个程序充当 Client 从子系统拉取数据并传到 IoTOS。 本工程,即**软件网关**,作为 IoTOS 的配套组件,以开源形式提供,研发人员可以基于此代码进行二次开发解决以上2类问题。 # 2. 使用须知 ## 2.1 环境要求 * JDK 1.8及以上版本 * Maven * Git ## 2.2 适用场景 软件网关可用于解决以下2类无法连接 IoTOS 的设备或子系统的情况: - 基于 TCP\UDP\HTTP 私有协议的设备或子系统; - 自带上位机的软硬件一体系统,该类系统可能暴露如 HTTP\TCP\UDP\JDBC\ODBC 等各种接口对外提供数据。 # 3. 简要设计说明 下图是软件网关的基本工作原理,包含三个主要环节: ![](pics/designInstruction.png) 1. 设备接入环节 > 软件网关中内置了 Server 能力,默认支持 TCP、UDP、HTTP 协议的接入。使用者需要自行实现上报数据的拆包/组包(当为 TCP 时)功能。 > > 同时软件网关也内置了 Client 能力,默认支持向子系统发起 TCP、UDP、HTTP 请求,从而实现与子系统的交互。 > > 开发者需要自行实现交互逻辑,还可以自行扩展实现更多协议的支持。 2. 数据转码环节 > 此环节需实现设备原始数据格式(即设备或子系统认识的数据格式)和 KLink (IoTOS 内置的标准数据格式,采用 JSON 标准)的互相转换,使用者需要自行实现 encode(原始数据转 KLink) 和 decode(KLink 转原始数据)这两个 interface。 3. 与 IoTOS 交互环节 > 软件网关使用 MQTT 协议实现与 IoTOS 的交互,使用者只需配置相关参数即可。 > > > IoTOS 与软件网关交互的数据中一定包含 PK 和 devID,若存量设备本身不含 PK 等标识信息,开发者则需自行完成映射。 > > 例如,子设备发送亮度状态值```light```为90,软件网关发送给 IoTOS 的数据格式如下: ```json { "action": "devSend", "msgID": 1, "PK": "3276aa89d25a46b789c7987421396e05", /* 子设备PK */ "devID": "dev-001" /* 子设备ID */ "data": { "cmd": "report", "params": { "light":90 } } } ``` |参数|必填|类型|说明| |---|---|---|---| |action|是|string|动作,固定为 devSend| |PK|否|string|要发送数据的设备产品PK| |devID|否|string|要发送数据的设备ID| |data|是|object|上报的指令和参数数据| |data.cmd|是|string|标识符| |data.params|否|object|参数| # 4. 使用说明 ## 4.1 使用流程 使用者操作流程如下(黄色部分是与软件网关相关的步骤): ![](pics/operationSequence.png) **第一步:注册登录 IoTOS** > 因 IoTOS 以私有化部署为主,绝大部分情况下开发者可以 superadmin(即超级管理员)登录内网里部署的 IoTOS,本文以 [ IoTOS 体验站点](http://IoTOS-demo.hekr.me/)为例。 **第二步:创建软件网关产品及设备** > 进入产品中心-产品开发,点击“创建产品”,建立软件网关,“产品信息”栏目根据实际需求而定,“节点类型”和“联网与数据”栏目配置图如下: > > ![](pics/addProdGateway1.png) > > ![](pics/addProdGateway2.png) > > 进入产品中心-设备管理,点击“创建设备”,其中设备 ID 后续会在软件网关代码里使用,取名方法根据实际需求而定。 > > ![](pics/addDevGateway.png) **第三步:创建子设备产品及设备** > 进入产品中心-产品开发,点击“创建产品”,“产品信息”栏目根据实际需求而定,“节点类型”和“联网与数据”栏目配置图如下: > > ![](pics/addProdSub1.png) > > ![](pics/addProdSub2.png) > > 若使用者要求规范设备 ID,建议进入产品中心-设备管理,点击“批量添加”,使用表格模板实现批量导入。 > > ![](pics/batchImportSub.png) > > *注:此时软件网关的数据转码环节中子设备 ID 和表格应一一对应。* **第四步:查看并记录网关以及子设备信息** > 进入产品中心-设备管理,点击软件网关的右侧“查看”按钮。 > > ![](pics/viewGateway1.png) > > 获取到软件网关 PK、设备 ID 和 devSecret。 > > ![](pics/viewGateway2.png) > > 然后以相同的方式获取到子设备的产品 PK、设备 ID。 **第五步:获取并配置网关设备信息** > 进入 IoTOS -产品中心-产品开发,点击上一步创建的软件网关产品,可以获取到 MQTT 接入方式信息,以此为 HOST 值。 > ![](pics/getGatewayInfo.png) > > 进入项目路径```src/main/resources```,打开配置文件```config.properties```进行参数配置。 > > ![](pics/gatewayConfig.png) > > 以下配置项为软件网关配置的必填信息 ``` ## mqtt配置(必填) #### 进入产品中心-产品开发-软件网关,"MQTT接入方式"栏目即可查询 iotos.host=106.75.50.110:1883 #### 软件网关的产品pk,进入产品中心-设备管理-软件网关,"产品pk"栏目即可查询 gateway.pk=fc5dbdd26fee4688a6ab35b63a294cc1 #### 软件网关的设备id,进入产品中心-设备管理-软件网关,"设备id"栏目即可查询 gateway.devID=gatewaydemo #### 软件网关的设备密钥,进入产品中心-设备管理-软件网关,"devSecret"栏目点击"复制"按钮即可查询 gateway.devSecret=d10d6a46f6b5462b88f0d07207479bd2 ``` **第六步:程序运行** > 进入项目路径并打开入口程序```src/main/java/hekr/me/iotos/softgateway/IoTGatewayApplication.java``` > > 以下注释部分分别为HttpClient、HttpServer、TcpClient、TcpServer、UdpClient、UdpServer的入口,使用者可根据实际需求自行打开需要的部分。 ``` public static void main(String[] args) throws Exception { // 获取配置文件中的相关参数 P.use("config.properties"); // 软件网关初始化,完成软件网关参数读取、登陆操作 ProxyService.init(); // 软件网关对云端下发指令或回复指令进行相应处理的processor注册 ProxyCallbackService.processorManager.register(new CloudSendProcessor()); // 若要启用http则将下行注释打开 // HttpServerInit.init(); // 使用http client示例,此处example方法调用的接口在HttpServer中,因此若要启动此示例方法务必也将HttpServerInit启动 // Thread.sleep(5000); // HttpClient.example(); // 若要启用TCP client则将下行注释打开 // TcpClientStarter.start(); // 若要启用TCP server则将下行注释打开 // TcpServerStarter.start(); // 若要启用UDP client则将下行注释打开 // UdpClientStarter.start(); // 若要启用UDP server则将下行注释打开 // UdpService.init(); } ``` **第七步:数据信息上报** > 本项目调用6种上报的方法 addSub、subLogin、subLogout、subTopo、delSub 以及 devSend,在```SubKLink```类中定义了上述方法,使用者可根据具体情况自行添加或修改方法。 > > 若开发者需要将存量设备中的设备 ID 和规范化的设备 ID 进行映射,则需要自行代码实现。 > |方法|需要参数|说明| |---|---|---| |addSub|pk, devId|配置的软件网关添加子设备| |subLogin|pk, devId|子设备上线| |subLogout|pk, devId|子设备下线| |subTopo|无|查看软件网关下关联的子设备拓扑| |delSub|pk, devId|配置的软件网关删除子设备| |devSend|pk, devId|发送指定数据信息| **第八步:IoTOS 收到下发命令并发送给设备** > 在```ProxyConnectService```类中添加了如下代码,用于软件网关订阅 IoTOS 下发的信息。 > > ```client.subscribe(DOWN_TOPIC,0);``` > > 示例:首先调用 addSub 方法向网关添加设备,再调用 subTopo 方法查看网关下子设备绑定情况,得到 KLink 返回信息,可以看到成功绑定了一台设备。 ``` - 接收消息主题 : down/dev/fc5dbdd26fee4688a6ab35b63a294cc1/gatewaydemo - 接收消息Qos : 0 - 接收消息内容 : { "action":"getTopoResp", "msgId":0, "pk":"fc5dbdd26fee4688a6ab35b63a294cc1", "devId":"gatewaydemo", "code":0, "subs":[{ "pk":"3276aa89d25a46b789c7987421396e05", "devId":"dynamic" }] } ``` > 可以看到软件网关下的设备 PK 和设备 ID,为成功添加至软件网关的子设备信息。 ## 4.2 代码结构说明 ### 4.2.1 网关总体代码结构 如下图所示,网关总体的相关代码在项目中的```src/main/java/hekr/me/iotos/softgateway```路径下: ![](pics/structureOverall.png) 其中: > ```common``` 包含各种常量以及枚举数据,例如 KLink 相关协议都在此包中; > > ```northProxy```为软件网关核心代码部分,负责软件网关与 IoTOS 的连接以及数据的收发; > > ```pluginAsClient```包含 HTTP、TCP、UDP 三种协议下软件网关作为客户端启用的代码; > > ```pluginAsSever```包含 HTTP、TCP、UDP 三种协议下作为服务端启用的代码; > > ```utils```为工具类,提供 json 数据处理、hash 加密等相关工具类,以便于开发者进行二次开发。 > > ```config.properties```为程序配置文件,开发者可在此配置所有与连接相关的参数。 > ### 4.2.2 软件网关时序图结构 以下展示了软件网关分别作为客户端(pluginAsClient)和服务端(pluginAsServer)时的时序图。 作为客户端时: ![](pics/pluginAsClient.png) 作为服务端时: ![](pics/pluginAsServer.png) ### 4.2.3 软件网关主要组成 如下图所示,软件网关的相关代码在项目中的```src/main/java/hekr/me/iotos/softgateway/northProxy```路径下: ![](pics/northProxy.png) 其中: > ```ProxyCallbackService```负责 MQTT 回调指令的相应操作; > > ```ProxyConnectService```负责软件网关初始化连接等相应操作; > > ```ProxyServer```负责软件网关发送指令; > > ```processor```包主要由 ```Processor```接口和```ProcessorManager```组成,开发者需要实现```Processor```类来自行开发具备操作云端下发的各种指令的功能。上图中的```CloudSendProcessor```类为 cloudSend 指令操作的示例代码。 > > 注:开发者通常无需关注```Proxy*```的类,只需要关注```processor```的实现。 ### 4.2.4 httpClient 如下图所示,httpClient的相关代码在项目中的```src/main/java/hekr/me/iotos/softgateway/pluginAsClient/http```路径下: ![](pics/httpClient.png) > ```HttpUtils```实现了常用 HTTP 请求(GET、POST 请求),以及相关 hearder 和 body 的生成方法。开发者可通过新建```HttpUtils```对象并调用相应的方法,用以对接提供 HTTP Server 能力的子系统。 > > ```HttpClient```为示例代码,可供开发者参考。 ### 4.2.5 工具类 如下图所示,```httpClient```的相关代码在项目中的```src/main/java/hekr/me/iotos/softgateway/utils```路径下,软件网关内置三个工具类以便于开发者进行二次开发。 ![](pics/utils.png) > ```JsonUtil```负责对象与 json 格式之间得转换,对于转换 KLink 格式有较大的用处。 > > ```mapUtils```负责将对象转成 map 格式,便于在 HTTP 请求中构建 header 和 body。 > > ```parseUtil```负责进行 hash 算法及其相关数据格式的转换等功能,主要用于登陆注册校验部分。 ## 4.3 二次开发 软件网关中内置了 Server 能力,当为 TCP Server 时需要开发者实现 TCP 层面的拆包和组包,以避免粘包问题。在```TcpServerMsgHandler```类中,开发者可实现```PacketCodec```来进行适配,如```LinePacketCodec```示例是当设备以换行符作为分割标识的具体代码,开发者可进行参考。 ``` /** 拆包部分 */ @Override public Packet decode( ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws AioDecodeException { return packetCodec.decode(buffer, limit, position, readableLength, channelContext); } /** 组包部分 */ @Override public ByteBuffer encode(Packet packet, TioConfig tioConfig, ChannelContext channelContext) { return packetCodec.encode(packet, tioConfig, channelContext); } ``` 软件网关中内置了 Client 能力,示例代码提供了软件网关和子设备简单的交互逻辑,用户可以根据自己的需求自定义开发。 在```src/main/java/hekr/me/iotos/softgateway/northProxy/processor```包中,开发者需要实现```Processor```接口来对应 IoTOS 下发的指令,完成其具体功能。 软件网关内置了数据协议转换的能力,即将原始数据根据一定的规则转换成 KLink 形式,开发者需要实现```DataCodec```中的```encode```和```decode```两个方法。其中```RawDataCodec```是一个具体的示例。假如设备采用了如下的十六进制数据格式: |透传消息类型|类型描述|消息格式|备注| |---|---|---|---| |0x00|动态注册|0x00+pk_length(2字节)+pk+devId_length(2字节)+devId+productSecret_length(2字节)+productSecret+0x0a|productSecret为产品秘钥| |0x01|登录请求|0x01+pk_length(2字节)+pk+devId_length(2字节)+devId+devSecret_length(2字节)+devSecret+0x0a|devSecret为设备秘钥| |0x02|上行数据报文|0x02+pk_length(2字节)+pk+devId_length(2字节)+devId+data_length(2字节)+data+0x0a| |0x03|心跳|0x03+0x0a|IoTOS 心跳周期为5分钟,设备需在5分钟内发送心跳报文| |0x04|查看拓扑|0x04+0x0a|查看当前软件网关的拓扑情况| 示例: - 登录请求: ``` pk:3276aa89d25a46b789c7987421396e05 devId:dynamic devSecret:8cb192d8bbde469b8cd2b100fe02f042 登录请求编码为:01 0020 3332373661613839643235613436623738396337393837343231333936653035 0007 64796e616d6963 0020 3863623139326438626264653436396238636432623130306665303266303432 0a ``` - 上行数据: ``` pk:3276aa89d25a46b789c7987421396e05 devId:dynamic 业务数据:{"cmd":"reportFrame","params":{"STS":0}} 上行数据报文编码为:02 0020 3332373661613839643235613436623738396337393837343231333936653035 0007 64796e616d6963 0028 7b22636d64223a227265706f72744672616d65222c22706172616d73223a7b22535453223a307d7d 0a ``` # 5. 产品测试 测试工具下载地址: [windows端TCP/UDP测试工具下载地址](http://online.wall.99downer.com/download/TCP%2FUDP%20Socket%E8%B0%83%E8%AF%95%E5%B7%A5%E5%85%B7_14@55038.exe) [mac端TCP/UDP测试工具下载地址](http://code.cocoachina.com/uploads/attachments/20160314/129953/cbb6a73e00500058a7bea6d5f5635b62.zip) ## 5.1 TCP Server测试 > 使用 TCP 模拟器对于 TCP Server 进行测试,设备登录测试(本地测试,端口设置为7000,0a为结束标志): > > ![](pics/tcpServerLoginSend.png) > > 通过 IoTOS 接收到回复信息: > > ![](pics/tcpServerLoginRecv.png) > > 从 IoTOS 回复的信息可以看出,设备成功登陆。 > 上行数据测试: > > ![](pics/tcpServerSendMsg.png) > > 通过 IoTOS 接受到回复信息: > > ![](pics/tcpServerRecvMsg.png) > > 从回复的信息看出设备成功发送了信息。 ## 5.2 TCP Client测试 > 首先在配置文件中设置服务器IP以及端口: ``` ## tcp 客户端配置 tcp.client.connect.ip=192.168.5.47 tcp.client.connect.port=7000 ``` > 使用 TCP 模拟器对于 TCP Client 进行测试,设备登录测试 > > ![](pics/tcpClientLoginSend.png) > > 从日志里查看 IoTOS 的回复信息: > ``` desc":"success","sub":{"pk":"3276aa89d25a46b789c7987421396e05","devId":"dynamic"}} [Proxy Call: dev:fc5dbdd26fee4688a6ab35b63a294cc1:gatewaydemo] INFO hekr.me.iotos.softgateway.ProxyCallbackService - 接收消息主题 : down/dev/fc5dbdd26fee4688a6ab35b63a294cc1/gatewaydemo [Proxy Call: dev:fc5dbdd26fee4688a6ab35b63a294cc1:gatewaydemo] INFO hekr.me.iotos.softgateway.ProxyCallbackService - 接收消息Qos : 0 [Proxy Call: dev:fc5dbdd26fee4688a6ab35b63a294cc1:gatewaydemo] INFO hekr.me.iotos.softgateway.ProxyCallbackService - 接收消息内容 : {"action":"devLoginResp","msgId":0,"pk":"3276aa89d25a46b789c7987421396e05","devId":"dynamic","code":0,"desc":"success, "} ``` > > 从回复信息可以看出网关成功绑定设备并且设备成功上线。 > 上行数据测试 > > ![](pics/tcpClientSendMsg.png) > > 从日志里查看 IoTOS 的回复信息: > ``` [Proxy Call: dev:fc5dbdd26fee4688a6ab35b63a294cc1:gatewaydemo] INFO hekr.me.iotos.softgateway.ProxyCallbackService - 接收消息主题 : down/dev/fc5dbdd26fee4688a6ab35b63a294cc1/gatewaydemo [Proxy Call: dev:fc5dbdd26fee4688a6ab35b63a294cc1:gatewaydemo] INFO hekr.me.iotos.softgateway.ProxyCallbackService - 接收消息Qos : 0 [Proxy Call: dev:fc5dbdd26fee4688a6ab35b63a294cc1:gatewaydemo] INFO hekr.me.iotos.softgateway.ProxyCallbackService - 接收消息内容 : {"action":"devSendResp","msgId":1,"pk":"3276aa89d25a46b789c7987421396e05","devId":"dynamic","code":0,"desc":"success"} ``` > 从回复的信息看出设备成功发送了信息。 ## 5.3 HTTP Server测试 > + 在```config.properties```中将```http server```端口配置为8070,默认```ip```配置为```"localhost"```。 > ``` ## http 服务端配置(按需选填) http.server.port=8070 ``` > > + 在主程序的```main```方法中开启 HTTP Server > > ![](pics/runHttpServer.png) > > + 在```HttpController```中定义接口 > ``` /** 此接口用来配合测试HttpClient 此处模拟"http server的设备"将指令进行编码后发送给客户端 */ @RequestPath(value = "/test") public HttpResponse sendCommand(HttpRequest request) throws Exception { // 此处new DevSend()来模拟云端下发 DevSend devSend = new DevSend(); devSend.setAction(SubKLinkAction.HEARTBEAT); // 编码后发送 Object resp = dataCodec.encode(devSend); HttpResponse ret = Resps.bytes(request, (byte[]) resp, "ok"); return ret; } ``` > > + 运行后使用 postman 进行测试访问 > > ![](pics/postmanTest.png) > > 从上图中可以看到测试成功,HTTP Server 成功启动。 > # 附录 ## KLink 使用范例 以设备接入软件网关,软件网关与 IoTOS 进行数据交互为例,以下为 KLink 形式示例: - 设备绑定网关: ``` { "action": "addTopo", "msgID": 1, "PK": "fc5dbdd26fee4688a6ab35b63a294cc1", /*网关设备PK*/ "devID": "gatewaydemo", /*网关设备ID*/ "sub": { "PK": "3276aa89d25a46b789c7987421396e05", /*设备PK*/ "devID": "dynamic" /*设备ID*/ } } ``` - 设备上/下线: ``` { "action": "devLogin", /* 下线:"action": "devLogout" */ "msgID": 1, "PK": "3276aa89d25a46b789c7987421396e05", /*设备PK*/ "devID": "dynamic" /*设备ID*/ } ``` - 展示网关拓扑关系: ``` { "action": "getTopo", "msgID": 1, "PK": "fc5dbdd26fee4688a6ab35b63a294cc1", /*网关设备PK*/ "devID": "gatewaydemo" /*网关设备ID*/ } ``` - 若周围烟雾浓度达到预警,烟雾传感器发送告警上报: ``` { "action": "devSend", "msgID": 1, "PK": "3276aa89d25a46b789c7987421396e05", /*设备PK*/ "devID": "dynamic" /*设备ID*/ "data": { "cmd": "reportDev", "params": {} } ``` ## 密码生成规则 |参数|说明|构造方式| |---|---|---| |username|用户名|{hashMethod}:{random}| |password|密码|hash(pk+devId+devSecret+random),加密密钥:devSecret。| 【说明】 > > + 参数间使用“:”隔开。 > + {hashMethod} 支持:HmacMD5、HmacSHA1、HmacSHA256 和 HmacSHA512。 > + password 中加密使用的 hashMethod、random 需跟 username 中一致。 【参数构造实例】 > > Eg:pk=’pk123\$’,devId=’1001\$’,devSecret=’secret123\$’,使用 HashMD5方式进行加密,随机字符(random)为’20191108’。 > > clientId = dev:pk123$:1001$ > > username = HashMD5:20191108 > > password = c0608e3abe2f058df1d33020f963dbaf > > password 是通过 HashMD5 方法加密后得到。 > > 要加密的字符串:pk123$1001$secret123$20191108,加密密钥:secret123\$,加密后得到“c0608e3abe2f058df1d33020f963dbaf”。 【在线工具】 > > https://tool.oschina.net/encrypt?type=2 > > ![](pics/toolOnline.png) 【加密内容】 > 开发者在进行加密时,可以直接使用工具包中```utils.ParseUtil```工具类,其中的```HmacSHA1Encrypt(String encryptText, String encryptKey)```方法实现了加密,```encryptText```为加密内容,```encryptKey```为加密密钥,计算后返回```byte[]```,开发者可以调用此类下的```parseByte2HexStr(byte[] buf)```方法将其转换成字符串。如下图所示: > ![](pics/encryptUtil.png)