更新时间:2024-02-29 GMT+08:00
分享

IoT Device SDK使用指南(Java)

准备工作

  • 开发环境要求:已经安装JDK(版本1.8以上)和maven
  • 访问SDK下载页面,下载SDK,整个工程包含以下子工程:

    iot-device-sdk-java:sdk代码

    iot-device-demo:普通直连设备的demo代码

    iot-gateway-demo:网关设备的demo代码

    iot-bridge-demo:网桥的demo代码,用来演示如何将tcp设备桥接到平台

    iot-device-code-generator:设备代码生成器,可以根据产品模型自动生成设备代码

  • 编译安装:进入到SDK根目录,执行mvn install

创建产品

为了方便体验,我们提供了一个烟感的产品模型,烟感会上报烟雾值、温度、湿度、烟雾报警、还支持响铃报警命令。以烟感例,体验消息上报、属性上报等功能。

  1. 访问设备接入服务,单击“管理控制台”进入设备接入控制台,查看MQTTS设备接入域名,保存该地址。
  2. 单击左侧导航栏“产品”,单击页面右上角的“创建产品”
  3. 根据页面提示填写参数,然后单击“立即创建”。

    基本信息

    所属资源空间

    平台自动将新创建的产品归属在默认资源空间下。如需归属在其他资源空间下,下拉选择所属的资源空间。如无对应的资源空间,请先创建资源空间

    产品名称

    自定义。支持字母、数字、下划线(_)、连字符(-)的字符组合。

    协议类型

    选择“MQTT”

    数据格式

    选择“JSON”

    厂商名称

    自定义。支持中英文字符、数字、下划线(_)、连字符(-)的字符组合。

    模型定义

    选择模型

    此处不使用平台预置的产品模型,使用离线导入的产品模型,详细请参考上传产品模型

    所属行业

    选择产品模型的所属行业。

    设备类型

    自定义。

上传产品模型

  1. 单击下载烟感产品模型smokeDetector,获取产品模型文件。
  2. 找到步骤3创建的产品,单击产品进入产品详情页。
  3. 选择“模型定义”页签,单击“上传模型文件”,上传步骤1获取的产品模型文件。

注册设备

  1. 选择左侧导航栏“设备 > 所有设备”,单击页面右上角的“注册设备”
  2. 根据页面提示信息填写参数,然后单击“确定”

    参数名称

    说明

    所属资源空间

    确保和步骤3创建的产品归属在同一个资源空间。

    所属产品

    选择步骤3创建的产品。

    设备标识码

    即nodeID,设备唯一物理标识。可自定义,由英文字母和数字组成。

    设备名称

    即device_name,可自定义。

    设备认证类型

    选择“密钥”

    密钥

    设备密钥,可自定义。若不填写密钥,物联网平台会自动生成密钥。

    设备注册成功后保存设备标识码、设备ID、密钥。

设备初始化

  1. 创建设备时,需要写入在注册设备时获取的设备ID、密码,以及1中获取的设备对接信息,注意格式为ssl://域名信息:端口号ssl://IP地址:端口号
    1
    2
    3
         //例如在iot-device-demo文件 MessageSample.java中修改以下参数
         IoTDevice device = new IoTDevice("ssl://域名信息:8883",
                    "5e06bfee334dd4f33759f5b3_demo", "mysecret", file);
    
  2. 建立连接。调用init接口,该接口是阻塞调用,如果建立连接成功会返回0。
    1
    2
    3
          if (device.init() != 0) {
                return;
            }
    
    如果连接成功就会打印:
    1
    2023-07-17 17:22:59  INFO MqttConnection:105 - Mqtt client connected. address :ssl://域名信息:8883
    
  3. 创建设备并连接成功后,可以开始使用设备进行通信。调用IoT Device 的getClient接口获取设备客户端,客户端提供了消息、属性、命令等通讯接口。

消息上报

消息上报是指设备向平台上报消息。

  1. 从device中获取客户端,调用IoTDevice的getClient接口即可获取到客户端。
  2. 调用客户端的reportDeviceMessage接口来上报设备消息。在MessageSample这个例子中我们周期性上报消息:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
            while (true) {
                device.getClient().reportDeviceMessage(new DeviceMessage("hello"), new ActionListener() {
                    @Override
                    public void onSuccess(Object context) {
                        log.info("reportDeviceMessage ok");
                    }
    
                    @Override
                    public void onFailure(Object context, Throwable var2) {
                        log.error("reportDeviceMessage fail: " + var2);
                    }
                });
    
                //上报自定义topic消息,注意需要先在平台配置自定义topic
                String topic = "$oc/devices/" + device.getDeviceId() + "/user/wpy";
                device.getClient().publishRawMessage(new RawMessage(topic, "hello raw message "),
                    new ActionListener() {
                        @Override
                        public void onSuccess(Object context) {
                            log.info("publishRawMessage ok: ");
                        }
    
                        @Override
                        public void onFailure(Object context, Throwable var2) {
                            log.error("publishRawMessage fail: " + var2);
                        }
                    });
    
                Thread.sleep(5000);
            }
    
  3. 修改MessageSample类的main函数,替换自己的设备参数后运行MessageSample类,查看日志打印看到连接成功和发送消息的打印:
    1
    2
    3
    4
    5
    6
    2023-07-17 18:59:20  INFO MqttConnection:105 - Mqtt client connected. address :ssl://域名信息:8883
    2023-07-17 18:59:20  INFO MqttConnection:246 - publish message topic =  $oc/devices/6396f2a998314b7a1c3faa24_java-sdk-test/sys/events/up, msg = {"object_device_id":"6396f2a998314b7a1c3faa24_java-sdk-test","services":[{"paras":{"type":"DEVICE_STATUS","content":"connect complete, the url is ssl://域名信息:8883","timestamp":"1689591560993"},"service_id":"$log","event_type":"log_report","event_time":"20230717T105920Z","event_id":null}]}
    2023-07-17 18:59:20  INFO MqttConnection:246 - publish message topic =  $oc/devices/6396f2a998314b7a1c3faa24_java-sdk-test/sys/messages/up, msg = {"name":null,"id":null,"content":"hello","object_device_id":null}
    2023-07-17 18:59:20  INFO MqttConnection:246 - publish message topic =  $oc/devices/6396f2a998314b7a1c3faa24_java-sdk-test/user/wpy, msg = hello raw message 
    2023-07-17 18:59:21  INFO MessageSample:44 - reportDeviceMessage ok
    2023-07-17 18:59:21  INFO MessageSample:59 - publishRawMessage ok:
    
  4. 在设备接入控制台,选择设备 > 所有设备-查看设备是否在线。

  5. 选择对应设备,单击“查看”,在设备详情页面启动设备消息跟踪。

  6. 平台收到了设备的消息。

:消息跟踪会有一定的延时,如果没有看到数据,请等待后刷新。

属性上报

打开PropertySample类,这个例子中会定时的上报alarm、temperature、humidity、smokeConcentration这四个属性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
      //定时上报属性
        while (true) {

            Map<String ,Object> json = new HashMap<>();
            Random rand = new Random();

            //按照物模型设置属性
            json.put("alarm", 1);
            json.put("temperature", rand.nextFloat()*100.0f);
            json.put("humidity", rand.nextFloat()*100.0f);
            json.put("smokeConcentration", rand.nextFloat() * 100.0f);

            ServiceProperty serviceProperty = new ServiceProperty();
            serviceProperty.setProperties(json);
            serviceProperty.setServiceId("smokeDetector");//serviceId要和物模型一致

            
               device.getClient().reportProperties(Arrays.asList(serviceProperty), new ActionListener() {
                @Override
                public void onSuccess(Object context) {
                    log.info("pubMessage success" );
                }

                @Override
                public void onFailure(Object context, Throwable var2) {
                    log.error("reportProperties failed" + var2.toString());
                }
            });

            Thread.sleep(10000);
        }
    }

修改PropertySample的main函数后直接运行PropertySample类,查看日志看到发送成功的打印

在平台设备详情页面可以看到最新上报的属性值:

属性读写

调用客户端的setPropertyListener方法来设置属性回调接口。在PropertySample这个例子中,我们实现了属性读写接口。

写属性处理:实现了alarm属性的写操作,其他属性不支持写操作。

读属性处理:将本地属性值按照接口格式进行拼装。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
            device.getClient().setPropertyListener(new PropertyListener() {

            //处理写属性
            @Override
            public void onPropertiesSet(String requestId, List<ServiceProperty> services) {
                // 遍历service
                for (ServiceProperty serviceProperty : services) {

                    log.info("OnPropertiesSet, serviceId is {}", serviceProperty.getServiceId());

                    // 遍历属性
                    for (String name : serviceProperty.getProperties().keySet()) {
                        log.info("property name is {}", name);
                        log.info("set property value is {}", serviceProperty.getProperties().get(name));
                    }

                }
                // 修改本地的属性值
                device.getClient().respondPropsSet(requestId, IotResult.SUCCESS);
            }

            /**
             * 处理读属性。多数场景下,用户可以直接从平台读设备影子,此接口不用实现。
             * 但如果需要支持从设备实时读属性,则需要实现此接口。
             */
            @Override
            public void onPropertiesGet(String requestId, String serviceId) {
                log.info("OnPropertiesGet, the serviceId is {}", serviceId);
                Map<String, Object> json = new HashMap<>();
                Random rand = new SecureRandom();
                json.put("alarm", 1);
                json.put("temperature", rand.nextFloat() * 100.0f);
                json.put("humidity", rand.nextFloat() * 100.0f);
                json.put("smokeConcentration", rand.nextFloat() * 100.0f);

                ServiceProperty serviceProperty = new ServiceProperty();
                serviceProperty.setProperties(json);
                serviceProperty.setServiceId("smokeDetector");

                device.getClient().respondPropsGet(requestId, Arrays.asList(serviceProperty));
            }
        });

  1. 属性读写接口需要调用respondPropsGet和respondPropsSet接口来上报操作结果。
  2. 如果设备不支持平台主动到设备读,onPropertiesGet接口可以空实现

运行PropertySample类,然后在平台上设备影子页面查看当前alarm属性值为1:

我们把alarm属性修改为0:

查看设备侧日志,看到设备收到属性设置,alarm被修改为0:

命令下发

设置命令监听器用来接收平台下发的命令,在回调接口里,需要对命令进行处理,并上报响应。

在CommandSample例子中实现了命令的处理,收到命令后仅进行打印,然后调用respondCommand上报响应。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
	device.getClient().setCommandListener(new CommandListener() {
            @Override
            public void onCommand(String requestId, String serviceId, String commandName, Map<String, Object> paras) {
                log.info("onCommand, serviceId = {}", serviceId);
                log.info("onCommand , name = {}", commandName);
                log.info("onCommand, paras =  {}", paras.toString());

                //处理命令

                //发送命令响应
                device.getClient().respondCommand(requestId, new CommandRsp(0));
            }

        });
		
		

直接运行CommandSample类,然后在平台上下发命令,命令的serviceId填smokeDetector、命令名填ringAlarm、参数携带duration为整数20。

查看日志,看到设备收到命令并上报了响应:

面向物模型编程

前面介绍了直接调用设备客户端的接口和平台进行通讯的方法,这种方式比较灵活,但用户需要妥善处理每一个接口,实现比较复杂。

SDK提供了一种更简单的方式,即面向物模型编程。面向物模型编程指基于SDK提供的物模型抽象能力,设备代码按照物模型定义设备服务,然后可以直接访问设备服务(即调用设备服务的属性读写接口),SDK就能自动和平台通讯,完成属性的同步和命令的调用。

相比直接调用客户端接口和平台进行通讯,面向物模型编程更简单,它简化了设备侧代码的复杂度,让设备代码只需要关注业务,而不用关注和平台的通讯过程。这种方式适合多数场景。

SmokeDetector例子演示了如何面向物模型编程:

  1. 按照物模型定义服务类和服务的属性(如果有多个服务,则需要定义多个服务类):
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    public static class SmokeDetectorService extends AbstractService {
    
            //按照设备模型定义属性,注意属性的name和类型需要和模型一致,writeable表示属性知否可写,name指定属性名
            @Property(name = "alarm", writeable = true)
            int smokeAlarm = 1;
    
            @Property(name = "smokeConcentration", writeable = false)
            float concentration = 0.0f;
    
            @Property(writeable = false)
            int humidity;
    
            @Property(writeable = false)
            float temperature;
    

    用@Property注解来表示是一个属性,可以用name指定属性名,如果不指定则使用字段名。

    属性可以加上writeable来控制权限,如果属性只读,则加上writeable = false,如果不加,默认认为可读写。

  2. 定义服务的命令。设备收到平台下发的命令时,SDK会自动调用这里定义的命令。

    接口入参和返回值的类型是固定的不能修改,否则会出现运行时错误。

    这里定义的是一个响铃报警命令,命令名为ringAlarm。

    1
    2
    3
    4
    5
    6
    7
    //定义命令,注意接口入参和返回值类型是固定的不能修改,否则会出现运行时错误
            @DeviceCommand(name = "ringAlarm")
            public CommandRsp alarm(Map<String, Object> paras) {
                int duration = (int) paras.get("duration");
                log.info("ringAlarm  duration = " + duration);
                return new CommandRsp(0);
            }
    
  3. 定义getter和setter接口
    • 当设备收到平台下发的查询属性以及设备上报属性时,会自动调用getter方法。getter方法需要读取设备的属性值,可以实时到传感器读取或者读取本地的缓存
    • 当设备收到平台下发的设置属性时,会自动调用setter方法。setter方法需要更新设备本地的值。如果属性不支持写操作,setter保留空实现。
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
     //setter和getter接口的命名应该符合java bean规范,sdk会自动调用这些接口
            public int getHumidity() {
    
                //模拟从传感器读取数据
                humidity = new Random().nextInt(100);
                return humidity;
            }
    
            public void setHumidity(int humidity) {
                //humidity是只读的,不需要实现
            }
    
            public float getTemperature() {
    
                //模拟从传感器读取数据
                temperature = new Random().nextInt(100);
                return temperature;
            }
    
            public void setTemperature(float temperature) {
                //只读字段不需要实现set接口
            }
    
            public float getConcentration() {
    
                //模拟从传感器读取数据
                concentration = new Random().nextFloat()*100.0f;
                return concentration;
            }
    
            public void setConcentration(float concentration) {
                //只读字段不需要实现set接口
            }
    
            public int getSmokeAlarm() {
                return smokeAlarm;
            }
    
            public void setSmokeAlarm(int smokeAlarm) {
    
                this.smokeAlarm = smokeAlarm;
                if (smokeAlarm == 0){
                    log.info("alarm is cleared by app");
                }
            }
    
  4. 在main函数中创建服务实例并添加到设备。
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
          //创建设备
            IoTDevice device = new IoTDevice(serverUri, deviceId, secret);
    
            //创建设备服务
            SmokeDetectorService smokeDetectorService = new SmokeDetectorService();
            device.addService("smokeDetector", smokeDetectorService);
    
            if (device.init() != 0) {
                return;
            }
    
  5. 开启周期上报:
    1
    2
    //启动自动周期上报
    smokeDetectorService.enableAutoReport(10000);
    

    备注:如果不想周期上报,也可以调用firePropertiesChanged接口手工触发上报。

    直接运行SmokeDetector类,查看日志在上报属性:

    在平台侧查看设备影子:

    在平台上修改属性alarm,查看设备日志收到属性设置:

    在平台下发ringAlarm命令:

    查看设备日志看到ringAlarm命令被调用,并且上报了响应:

使用代码生成器

sdk提供了设备代码生成器,用户只需要提供产品模型文件,就能自动生成设备代码框架。代码生成器可以解析设备模型文件,然后对模型里定义的每个服务,生成对应的service类,然后生成一个设备主类,在main函数中创建设备并注册设备服务实例。

使用代码生成器生成设备代码的步骤:

  1. 下载huaweicloud-iot-device-sdk-java工程,解压缩后进入huaweicloud-iot-device-sdk-java目录执行“mvn install”

  2. 执行完成会在iot-device-code-generator的target下生成可执行jar包。

  3. 将产品模型文件保存到本地,比如我的模型文件“smokeDetector.zip”放到D盘。
  4. 访问SDK根目录,执行“java -jar .\iot-device-code-generator\target\iot-device-code-generator-1.2.0-with-deps.jar D:\smokeDetector.zip”

  5. 在huaweicloud-iot-device-sdk-java目录下会生成generated-demo包。

    至此,设备代码已经生成。

编译运行生成的代码:

  1. 访问“huaweicloud-iot-device-sdk-java\generated-demo”,执行“mvn install”,在target下生成jar包。

  2. 执行java -jar .\target\iot-device-demo-ganerated-1.2.0-with-deps.jar your-access-address your-deivce-id your-device-secrect,三个参数分别为设备接入地址、设备id和密码,运行生成的demo

修改扩展生成的代码:

生成的代码已经完成了服务的定义和注册,用户只需要进行少量的修改即可。

  1. 命令接口,需要添加具体的实现逻辑

  2. getter方法,生成的代码是返回随机值,需要改为从传感器读取数据。
  3. setter方法,生成的代码只完成了属性的修改保存,还需要添加真实的逻辑处理,比如向传感器下发指令。

如何开发网关

网关是一个特殊的设备,除具备一般设备功能之外,还具有子设备管理、子设备消息转发的功能。SDK提供了AbstractGateway抽象类来简化网关的实现。该类提供了子设备管理功能,需要从平台获取子设备信息并保存(需要子类提供子设备持久化接口)、子设备下行消息转发功能(需要子类实现转发处理接口)、以及上报子设备列表、上报子设备属性、上报子设备状态、上报子设备消息等接口。

  • 使用AbstractGateway类

    继承该类,在构造函数里提供子设备信息持久化接口,实现其下行消息转发的抽象接口:

    1
    2
    3
    4
    5
    6
    7
        public abstract void onSubdevCommand(String requestId, Command command);
    
        public abstract void onSubdevPropertiesSet(String requestId, PropsSet propsSet);
    
        public abstract void onSubdevPropertiesGet(String requestId, PropsGet propsGet);
    
        public abstract void onSubdevMessage(DeviceMessage message);
    
  • iot-gateway-demo代码介绍

    工程iot-gateway-demo基于AbstractGateway实现了一个简单的网关,它提供tcp设备接入能力。关键类:

    SimpleGateway:继承自AbstractGateway,实现子设备管理和下行消息转发

    StringTcpServer:基于netty实现一个TCP server,本例中子设备采用TCP协议,并且首条消息为鉴权消息

    SubDevicesFilePersistence:子设备信息持久化,采用json文件来保存子设备信息,并在内存中做了缓存

    Session:设备会话类,保存了设备id和TCP的channel的对应关系

  • SimpleGateway类

    添加或删除子设备处理

    添加子设备:AbstractGateway的onAddSubDevices接口已经完成了子设备信息的保存。我们不需要再增加额外处理,因此SimpleGateway不需要重写onAddSubDevices接口

    删除子设备:我们不仅需要修改持久化信息,还需要断开当前子设备的连接。所以我们重写了onDeleteSubDevices接口,增加了拆链处理,然后调用父类的onDeleteSubDevices。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     
     
     @Override
        public int onDeleteSubDevices(SubDevicesInfo subDevicesInfo) {
    
            for (DeviceInfo subdevice : subDevicesInfo.getDevices()) {
                Session session = nodeIdToSesseionMap.get(subdevice.getNodeId());
                if (session != null) {
                    if (session.getChannel() != null) {
                        session.getChannel().close();
                        channelIdToSessionMap.remove(session.getChannel().id().asLongText());
                        nodeIdToSesseionMap.remove(session.getNodeId());
                    }
                }
            }
            return super.onDeleteSubDevices(subDevicesInfo);
    
        }
    
  • 下行消息处理
    网关收到平台下行消息时,需要转发给子设备。平台下行消息分为三种:设备消息、属性读写、命令。
    • 设备消息:这里我们需要根据deviceId获取nodeId,从而获取session,从session里获取channel,就可以往channel发送消息。在转发消息时,可以根据需要进行一定的转换处理。
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
       @Override
          public void onSubdevMessage(DeviceMessage message) {
      
              //平台接口带的都是deviceId,deviceId是由nodeId和productId拼装生成的,即
              //deviceId = productId_nodeId
              String nodeId = IotUtil.getNodeIdFromDeviceId(message.getDeviceId());
              if (nodeId == null) {
                  return;
              }
      
              //通过nodeId获取session,进一步获取channel
              Session session = nodeIdToSesseionMap.get(nodeId);
              if (session == null) {
                  log.error("subdev is not connected " + nodeId);
                  return;
              }
              if (session.getChannel() == null){
                  log.error("channel is null " + nodeId);
                  return;
              }
      
              //直接把消息转发给子设备
              session.getChannel().writeAndFlush(message.getContent());
              log.info("writeAndFlush " + message);
          }
      
    • 属性读写:

      属性读写包括属性设置和属性查询。

      属性设置:

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
       @Override
          public void onSubdevPropertiesSet(String requestId, PropsSet propsSet) {
      
              if (propsSet.getDeviceId() == null) {
                  return;
              }
      
              String nodeId = IotUtil.getNodeIdFromDeviceId(propsSet.getDeviceId());
              if (nodeId == null) {
                  return;
              }
      
              Session session = nodeIdToSesseionMap.get(nodeId);
              if (session == null) {
                  return;
              }
      
              //这里我们直接把对象转成string发给子设备,实际场景中可能需要进行一定的编解码转换
              session.getChannel().writeAndFlush(JsonUtil.convertObject2String(propsSet));
      
              //为了简化处理,我们在这里直接回响应。更合理做法是在子设备处理完后再回响应
              getClient().respondPropsSet(requestId, IotResult.SUCCESS);
      
              log.info("writeAndFlush " + propsSet);
      
          }
      
      属性查询:
      1
      2
      3
      4
      5
      6
      7
       @Override
          public void onSubdevPropertiesGet(String requestId, PropsGet propsGet) {
      
              //不建议平台直接读子设备的属性,这里直接返回失败
              log.error("not supporte onSubdevPropertiesGet");
              deviceClient.respondPropsSet(requestId, IotResult.FAIL);
          }
      

    • 命令:处理流程和消息类似,实际场景中可能需要不同的编解码转换。
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      @Override
          public void onSubdevCommand(String requestId, Command command) {
      
              if (command.getDeviceId() == null) {
                  return;
              }
      
              String nodeId = IotUtil.getNodeIdFromDeviceId(command.getDeviceId());
              if (nodeId == null) {
                  return;
              }
      
              Session session = nodeIdToSesseionMap.get(nodeId);
              if (session == null) {
                  return;
              }
      
              //这里我们直接把command对象转成string发给子设备,实际场景中可能需要进行一定的编解码转换
              session.getChannel().writeAndFlush(JsonUtil.convertObject2String(command));
      
              //为了简化处理,我们在这里直接回命令响应。更合理做法是在子设备处理完后再回响应
              getClient().respondCommand(requestId, new CommandRsp(0));
              log.info("writeAndFlush " + command);
          }
      

  • 上行消息处理

    上行处理在StringTcpServer的channelRead0接口里。如果会话不存在,需要先创建会话:

    如果子设备信息不存在,这里会创建会话失败,直接拒绝连接

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     @Override
            protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {
                Channel incoming = ctx.channel();
                log.info("channelRead0" + incoming.remoteAddress() + " msg :" + s);
    
                //如果是首条消息,创建session
    //如果是首条消息,创建session
                Session session = simpleGateway.getSessionByChannel(incoming.id().asLongText());
                if (session == null) {
                    String nodeId = s;
                    session = simpleGateway.createSession(nodeId, incoming);
    
                    //创建会话失败,拒绝连接
                    if (session == null) {
                        log.info("close channel");
                        ctx.close();
                    }
                } 
    

    如果会话存在,则进行消息转发:

    1
    2
    3
    4
    5
    6
    7
    else {
                    //如果需要上报属性则调用reportSubDeviceProperties
                    DeviceMessage deviceMessage = new DeviceMessage(s);
                    deviceMessage.setDeviceId(session.getDeviceId());
                    simpleGateway.reportSubDeviceMessage(deviceMessage, null);
    
                }	
    

    到这里,网关的关键代码介绍完了,其他的部分看源代码。整个demo是开源的,用户可以根据需要进行扩展。比如修改持久化方式、转发中增加消息格式的转换、实现其他子设备接入协议。

  • iot-gateway-demo的使用
    1. 在平台上为网关注册开户。
    2. 修改StringTcpServer的main函数,替换构造参数,然后运行该类。
      1
      2
      3
       simpleGateway = new SimpleGateway(new SubDevicesFilePersistence(),
                      "ssl://iot-acc.cn-north-4.myhuaweicloud.com:8883",
                      "5e06bfee334dd4f33759f5b3_demo", "mysecret");
      
    3. 在平台上看到该网关在线后,添加子设备。

      此时网关上日志打印:

      2023-01-05 19:14:32 INFO SubDevicesFilePersistence:83 - add subdev: 456gefw3fh

    4. 运行TcpDevice类,建立连接后,输入子设备的nodeId。

      此时网关设备日志打印:

      2023-01-05 19:15:13 INFO StringTcpServer:118 - channelRead0/127.0.0.1:60535 msg :subdev2

      2023-01-05 19:15:13 INFO SimpleGateway:68 - create new session okSession{nodeId='456gefw3fh', channel=[id: 0x42c9dc24, L:/127.0.0.1:8080 - R:/127.0.0.1:60535], deviceId='5e06bfee334dd4f337589c1de_subdev2'}

    5. 在平台上看到子设备上线。

    6. 子设备上报消息

      查看日志看到上报成功

    7. 查看消息跟踪

      在平台上找到网关,选择 设备详情-消息跟踪,打开消息跟踪。继续让子设备发送数据,等待片刻后看到消息跟踪:

分享:

    相关文档

    相关产品