# 自定义协议开发
## 环境准备和开发工具
* JDK:1.8+
* MAVEN:3.1+
::: warning 注意
maven不要使用全局仓库配置,可能导致依赖无法下载
:::
* 开发工具:idea
## 第一步 通过idea创建maven工程:demo-protocol
## 第二步 修改pom文件,添加依赖
```xml
// jetlinks 核心依赖
org.jetlinks
jetlinks-core
1.0.2-BUILD-SNAPSHOT
// jetlinks 协议解析接口包
org.jetlinks
jetlinks-supports
1.0.2-BUILD-SNAPSHOT
// lombok,需要idea安装lombok插件,否则去掉
org.projectlombok
lombok
1.18.10
// vertx核心包,可以用来进行网络模拟测试
io.vertx
vertx-core
3.8.3
test
// 单元测试包
org.junit.jupiter
junit-jupiter
5.5.2
test
// logback日志
ch.qos.logback
logback-classic
1.2.3
// netty组件
io.netty
netty-bom
${netty.version}
pom
import
```
* 修改maven pom文件properties
~~~xml
UTF-8
zh_CN
1.8
${java.version}
4.1.45.Final
~~~
* 添加maven编译规则
~~~xml
org.apache.maven.plugins
maven-compiler-plugin
3.1
${project.build.jdk}
${project.build.jdk}
${project.build.sourceEncoding}
~~~
* 添加hsweb私服和阿里云仓库
~~~xml
hsweb-nexus
Nexus Release Repository
http://nexus.hsweb.me/content/groups/public/
true
always
aliyun-nexus
aliyun
http://maven.aliyun.com/nexus/content/groups/public/
~~~
## 第三步 协议开发
* 新建packag:org.jetlinks.demo.protocol
* 创建协议编码解码类:DemoDeviceMessageCodec
```java
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.jetlinks.core.message.DeviceMessage;
import org.jetlinks.core.message.DeviceOnlineMessage;
import org.jetlinks.core.message.Message;
import org.jetlinks.core.message.codec.*;
import org.jetlinks.core.message.function.FunctionInvokeMessage;
import org.jetlinks.core.message.function.FunctionInvokeMessageReply;
import org.jetlinks.core.server.session.DeviceSession;
import org.jetlinks.wt.protocol.message.NbIotMessage;
import org.jetlinks.wt.protocol.message.data.enums.DataIdEnum;
import org.jetlinks.wt.protocol.message.enums.ControlEnum;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@AllArgsConstructor
@Slf4j
public class DemoDeviceMessageCodec implements DeviceMessageCodec {
// 传输协议定义
@Override
public Transport getSupportTransport() {
return DefaultTransport.TCP;
}
// 把tcp消息解码为平台消息,多用于设备上报消息到平台
@Override
public Mono extends Message> decode(MessageDecodeContext context) {
return Mono.empty();
}
// 把平台消息编码为协议传输消息,多用于平台命令下发到设备
@Override
public Publisher extends EncodedMessage> encode(MessageEncodeContext context) {
retrun Mono.empty();
}
}
```
* 创建协议入口类: DemoProtocolSupportProvider
~~~java
import org.jetlinks.core.ProtocolSupport;
import org.jetlinks.core.Value;
import org.jetlinks.core.defaults.CompositeProtocolSupport;
import org.jetlinks.core.device.AuthenticationResponse;
import org.jetlinks.core.device.DeviceRegistry;
import org.jetlinks.core.device.MqttAuthenticationRequest;
import org.jetlinks.core.message.codec.DefaultTransport;
import org.jetlinks.core.metadata.DefaultConfigMetadata;
import org.jetlinks.core.metadata.types.PasswordType;
import org.jetlinks.core.metadata.types.StringType;
import org.jetlinks.core.spi.ProtocolSupportProvider;
import org.jetlinks.core.spi.ServiceContext;
import org.jetlinks.demo.protocol.tcp.DemoTcpMessageCodec;
import org.jetlinks.supports.official.JetLinksDeviceMetadataCodec;
import reactor.core.publisher.Mono;
public class DemoProtocolSupportProvider implements ProtocolSupportProvider {
@Override
public Mono extends ProtocolSupport> create(ServiceContext context) {
CompositeProtocolSupport support = new CompositeProtocolSupport();
// 协议ID
support.setId("demo-v1");
// 协议名称
support.setName("演示协议v1");
// 协议说明
support.setDescription("演示协议");
// 物模型编解码,固定为JetLinksDeviceMetadataCodec
support.setMetadataCodec(new JetLinksDeviceMetadataCodec());
//TCP消息编解码器
DemoDeviceMessageCodec codec = new DemoDeviceMessageCodec();
// 两个参数,协议支持和编解码类DemoDeviceMessageCodec中保持一致,第二个参数定义使用的编码解码类
support.addMessageCodecSupport(DefaultTransport.TCP, () -> Mono.just(codec));
return Mono.just(support);
}
}
~~~
## 第四步 设备上报消息解码
本部分代码全部写在DemoDeviceMessageCodec.decode方法中
~~~java
@AllArgsConstructor
@Slf4j
public class DemoTcpMessageCodec implements DeviceMessageCodec {
....
// 把tcp消息解码为平台消息,多用于设备上报消息到平台
@Override
public Mono extends Message> decode(MessageDecodeContext context) {
return Mono.defer(() -> {
// 消息上下文
FromDeviceMessageContext ctx = ((FromDeviceMessageContext) context);
// 从上下文中获取消息字节数组
ByteBuf byteBuf = context.getMessage().getPayload();
byte[] payload = ByteBufUtil.getBytes(byteBuf, 0, byteBuf.readableBytes(), false);
// 把字节流转换为字符串,根据不同设备不同协议进行解析,
String text=new String(payload);
ReportPropertyMessage message.setProperties(properties); = new ReportPropertyMessage();
// 设置消息ID为我们获得的消息内容
message.setDeviceId(text);
// 以当前时间戳为消息时间
long time=LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
message.setTimestamp(time);
// 构造上报属性
Map properties = new HashMap<>();
properties.put("text",text);
// 设置上报属性
message.setProperties(properties);
// 获取设备会话信息
DeviceSession session = ((FromDeviceMessageContext) context).getSession();
// 如果session中没有设备信息,则为设备首次上线
if (session.getOperator() == null) {
DeviceOnlineMessage onlineMessage = new DeviceOnlineMessage();
onlineMessage.setDeviceId(text);
onlineMessage.setTimestamp(System.currentTimeMillis());
// 返回到平台上线消息
return Flux.just(message,onlineMessage);
}
// 返回到平台属性上报消息
return Mono.just(message);
});
}
.....
}
~~~
## 第五步 平台发送消息到设备(消息编码)
本部分代码全部写在DemoDeviceMessageCodec.encode方法中
~~~java
@AllArgsConstructor
@Slf4j
public class DemoDeviceMessageCodec implements DeviceMessageCodec {
..........
// 把平台消息编码为协议传输消息,多用于平台命令下发到设备
@Override
public Publisher extends EncodedMessage> encode(MessageEncodeContext context) {
// 从平台消息上下文中获取消息内容
Message message = context.getMessage();
EncodedMessage encodedMessage=EncodedMessage.simple(Unpooled.wrappedBuffer(toBytes());) = null;
// 根据消息类型的不同,构造不同的消息
if (message instanceof ReadPropertyMessage) {
ReadPropertyMessage readPropertyMessage = (ReadPropertyMessage) message;
// 获取需要传输的字节
byte[] bytes=readPropertyMessage.toString().getBytes();
// 构造为平台传输到设备的消息体
encodedMessage=EncodedMessage.simple(Unpooled.wrappedBuffer(bytes));
}
...
retrun encodedMessage != null ? Mono.just(encodedMessage) : Mono.empty();
}
}
~~~
## 第六步 打成jar包上传到平台,并进行调试
以[TCP服务网关接入设备](../best-practices/tcp-connection.md#使用tcp工具接入)为例。
进行协议上传和 配置,并使用TCP工具进行调试。