# 协议开发常见问题

# 如何在协议包中使用第三方的依赖或者sdk

  1. pom.xml中添加依赖
  2. 使用maven-shade-plugin将依赖的jar打包在一起
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.1.1</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <artifactSet>
                    <includes>
              <!--   添加需要打包在一起的第三方依赖信息  -->
              <!--  <include>com.domain:*</include>  -->
                    </includes>
                </artifactSet>
            </configuration>
        </execution>
    </executions>
</plugin>

提示

请勿将jetlinks,spring等平台自身的依赖打包在一起.

更多maven-shade-plugin功能请查询相关文档 (opens new window)

# 本地开发时如何调试协议包

  1. 将协议项目文件和jetlinks平台代码放在一起(不用添加依赖,只需要放到同一个项目目录即可)
  2. 添加为Maven项目,右键目录-Add as Maven Project,等待加载完成.
  3. 在响应代码打断点即可,和普通项目debug没有区别

debug

温馨提示

linuxmacOS下可以通过软连接的方式将文件链接到平台项目中,不用复制粘贴导致文件重复. 如: ln -s ~/custom-protocol dev/

# 协议包是响应式的,不会写怎么办

协议包编解码等操作是响应式的,如果实际编解码不存在异步操作(获取设备配置信息,下发回复给设备等)的话, 可以单独封装一个非响应式的方法来做解析,最终使用此方法来做解析,然后在编解码方法中调用即可.


public Flux<DeviceMessage> decode(MessageDecodeContext context){

MqttMessage message = (MqttMessage) ctx.getMessage();

List<DeviceMessage> result = doDecode(message);

return Flux.fromIterable(result);

}

private List<DeviceMessage> doDecode(MqttMessage message){
    
    //.... 解码为设备消息
}

温馨提示

如果需要深入了解和开发协议以及JetLinks平台,建议学习一下Project Reactor (opens new window)框架.

# 如何在收到设备的指令后,给设备进行应答

在一些tcp或者udp的设备可能需要平台处理消息后进行应答.

 //解码,回复设备并返回一个消息
  public Mono<DeviceMessage> decode(MessageDecodeContext context){
 
   EncodedMessage message = context.getMessage();
   ByteBuf payload = message.payload();//上报的数据
 
   DeviceMessage message = doEncode(payload); //解码
 
   //强转为FromDeviceMessageContext
   FromDeviceMessageContext ctx = (FromDeviceMessageContext)context;
 
   EncodedMessage msg = createAckMessage(); //构造应答消息
 
   return ctx
      .getSession()
      .send(msg) //发送消息给设备
      .thenReturn(message);
  }
 

特别注意

DeviceSession.send响应式操作,需要和整个响应式流组合在一起,否则可能无法发送到设备.

错误的例子:

ctx.getSession().send(msg);

return Flux.just(message);

正确的例子:

ctx
.getSession()
.send(msg)
.thenReturn(message);

# 如何在解码时获取设备信息

  1. context.getDevice(); 此方法可能返回null,比如在tcp首次发送报文时.

   public Mono<DeviceMessage> decode(MessageDecodeContext context) {
      
      FromDeviceMessageContext ctx = ((FromDeviceMessageContext) context);
      ByteBuf payload = message.payload();
      
      DeviceOperator device =  context.getDevice();
      //device为null说明时首次连接,平台未识别到当前连接属于哪个设备
      //返回了DeviceMessage之后,将会与DeviceMessage对应的设备进行绑定,之后getDevice()则为此设备对应的信息.
      if(device==null){
         //处理认证等逻辑,具体根据实际协议来处理
        return handleFirstMessage(payload);
      } else {
        return handleMessage(payload);
      }

    }

  1. context.getDevice(deviceId);使用已知的设备ID来获取设备信息.

  public Mono<DeviceMessage> decode(MessageDecodeContext context) {
     
     FromDeviceMessageContext ctx = ((FromDeviceMessageContext) context);
     ByteBuf payload = message.payload();
     //从报文中获取
     String deviceId = getDeviceIdFromMessage(payload);

    return context
    .getDevice(deviceId)
    //获取设备的配置
    .flatMap(device-> device.getSelfConfig("des_key"))
    //设备或者设备配置des_key不存在时会走swichIfEmpty的逻辑
    .swichIfEmpty(Mono.defer(()->handleDeviceNotFound(ctx).then(Mono.empty())))
    .mapNotNull(key->{
       //解码
       return doDecodeMessage(key.asString(),payload)
    })
  }
   
   private Mono<Void> handleDeviceNotFound(FromDeviceMessageContext ctx){
       //构造设备不存在的消息
       EncodedMessage ack = ...;
       return ctx
               .getSession()
               .send(ack)
               .then();
   }

   private DeviceMessage doDecodeMessage(String key,ByteBuf payload){
      //解码并返回设备消息
      return  ...;
   }

   private String getDeviceIdFromMessage(ByteBuf buf){
      //根据协议约定从报文中获取设备ID,比如设备sn等.
      return ...;
   }