PLATFORM_ACK_SERIAL_NO = AttributeKey.valueOf("PLATFORM_ACK_SERIAL_NO");
20 | }
21 |
22 | /**
23 | * 业务数据类型标识
24 | *
25 | * a) 上级平台向下级平台发送的请求消息,一般以 DOWN_ 开头,以后缀 _REQ 结尾;而下级平台向上级平台发送的请求消息一般以 UP_ 开头,以后缀 _REQ 结尾;
26 | * b) 当上下级平台之间有应答消息情况下,应答消息可继续沿用对应的请求消息开头标识符,而通过后缀 RSP 来标识结尾。
27 | */
28 | @SuppressWarnings({"SpellCheckingInspection", "unused"})
29 | public static class DataType {
30 |
31 | /**
32 | * 主链路登录请求消息
33 | */
34 | public final static int UP_CONNECT_REQ = 0x1001;
35 | /**
36 | * 主链路登录应答消息
37 | */
38 | public final static int UP_CONNECT_RSP = 0x1002;
39 | /**
40 | * 主链路注销请求消息
41 | */
42 | public final static int UP_DICONNECE_REQ = 0x1003;
43 | /**
44 | * 主链路注销应答消息
45 | */
46 | public final static int UP_DISCONNECT_RSP = 0x1004;
47 | /**
48 | * 主链路连接保持请求消息
49 | */
50 | public final static int UP_LINKTEST_REQ = 0x1005;
51 | /**
52 | * 主链路连接保持应答消息
53 | */
54 | public final static int UP_LINKTEST_RSP = 0x1006;
55 | /**
56 | * 主链路断开通知
57 | */
58 | public final static int UP_DISCONNECT_INFORM = 0x1007;
59 | /**
60 | * 下级平台主动关闭从链路通知
61 | */
62 | public final static int UP_CLOSELINK_INFORM = 0x1008;
63 | /**
64 | * 主链路登录请求消息
65 | */
66 | public final static int DOWN_CONNECT_REQ = 0x9001;
67 | /**
68 | * 从链路登录应答消息
69 | */
70 | public final static int DOWN_CONNECT_RSP = 0x9002;
71 | /**
72 | * 从链路注销请求消息
73 | */
74 | public final static int DOWN_DISCONNECT_REQ = 0x9003;
75 | /**
76 | * 从链路注销应答消息
77 | */
78 | public final static int DOWN_DISCONNECT_RSP = 0x9004;
79 | /**
80 | * 从链路连接保持请求消息
81 | */
82 | public final static int DOWN_LINKTEST_REQ = 0x9005;
83 | /**
84 | * 从链路连接保持应答消息
85 | */
86 | public final static int DOWN_LINKTEST_RSP = 0x9006;
87 | /**
88 | * 从链路链路断开通知
89 | */
90 | public final static int DOWN_DISCONNECT_INFORM = 0x9007;
91 | /**
92 | * 上级平台主动关闭从链路通知
93 | */
94 | public final static int DOWN_CLOSELINK_INFORM = 0x9008;
95 |
96 |
97 | /**
98 | * 接受定位信息数量通知
99 | */
100 | public final static int DOWN_TOTAL_RECV_BACK_MSG = 0x9101;
101 |
102 |
103 | /**
104 | * 主链路动态信息交换消息
105 | */
106 | public final static int UP_EXG_MSG = 0x1200;
107 | /**
108 | * 从链路动态信息交换
109 | */
110 | public final static int DOWN_EXG_MSG = 0x9200;
111 |
112 |
113 | /**
114 | * 主链路平台间信息交互
115 | */
116 | public final static int UP_PLATFORM_MSG = 0x1300;
117 | /**
118 | * 从链路平台间信息交互
119 | */
120 | public final static int DOWN_PLATFORM_MSG = 0x9300;
121 |
122 |
123 | /**
124 | * 主链路报警信息交互
125 | */
126 | public final static int UP_WARN_MSG = 0x1400;
127 | /**
128 | * 从链路报警信息交互
129 | */
130 | public final static int DOWN_WARN_MSG = 0x9400;
131 |
132 |
133 | /**
134 | * 主链路车辆监管消息
135 | */
136 | public final static int UP_CTRL_MSG = 0x1500;
137 | /**
138 | * 从链路车辆监管消息
139 | */
140 | public final static int DOWN_CTRL_MSG = 0x9500;
141 |
142 |
143 | /**
144 | * 主链路静态信息交换
145 | */
146 | public final static int UP_BASE_MSG = 0x1600;
147 | /**
148 | * 从链路静态信息交换
149 | */
150 | public final static int DOWN_BASE_MSG = 0x9600;
151 |
152 | }
153 |
154 | /**
155 | * 子业务类型标识
156 | *
157 | * a) 对应于业务数据类型下的子业务标识头继续遵循原有归属业务数据类型的标识头,例如业务数据类型 UP_EXG_MSG 下的子业务类型标识头均以 UP_EXG_MSG 开始;
158 | * b) 子业务类型名称标识的主从链路方向遵循原有归属业务数据类型的主从链路方向。
159 | */
160 | @SuppressWarnings({"SpellCheckingInspection", "unused"})
161 | public static class SubDataType {
162 |
163 | /**
164 | * 上传车辆注册信息
165 | */
166 | public final static int UP_EXG_MSG_REGISTER = 0x1201;
167 | /**
168 | * 实时上传车辆定位信息
169 | */
170 | public final static int UP_EXG_MSG_REAL_LOCATION = 0x1202;
171 | /**
172 | * 车辆定位信息自动补报
173 | */
174 | public final static int UP_EXG_MSG_HISTORY_LOCATION = 0x1203;
175 | /**
176 | * 启动车辆定位信息交换应答
177 | */
178 | public final static int UP_EXG_MSG_RETURE_STARTUP_ACK = 0x1205;
179 | /**
180 | * 结束车辆定位信息交换应答
181 | */
182 | public final static int UP_EXG_MSG_RETURE_END_ACK = 0x1206;
183 | /**
184 | * 申请交换指定车辆定位信息请求
185 | */
186 | public final static int UP_EXG_MSG_APPLE_FOR_MONITOR_STAR_TUP = 0x1207;
187 | /**
188 | * 取消交换制定车辆定位信息请求
189 | */
190 | public final static int UP_EXG_MSG_APPLE_FOR_MONITOR_END = 0x1208;
191 | /**
192 | * 补发车辆定位信息请求
193 | */
194 | public final static int UP_EXG_MSG_APPLE_HISGNSSDATA_REQ = 0x1209;
195 | /**
196 | * 上报车辆驾驶员身份识别信息应答
197 | */
198 | public final static int UP_EXG_MSG_REPORT_DRIVER_INFO_ACK = 0x120A;
199 | /**
200 | * 上报车辆电子运单应答
201 | */
202 | public final static int UP_EXG_MSG_TAKE_EWAYBILL_ACK = 0x120B;
203 |
204 |
205 | /**
206 | * 车辆定位信息交换
207 | */
208 | public final static int DOWN_EXG_MSG_CAR_LOCATION = 0x9202;
209 | /**
210 | * 车辆定位信息交换补发
211 | */
212 | public final static int DOWN_EXG_MSG_HISTORY_ARCOSSAREL = 0x9203;
213 | /**
214 | * 车辆静态信息交换
215 | */
216 | public final static int DOWN_EXG_MSG_CAR_INFO = 0x9204;
217 | /**
218 | * 启动车辆定位信息交换请求
219 | */
220 | public final static int DOWN_EXG_MSG_RETURN_STARTUP = 0x9205;
221 | /**
222 | * 关闭车辆定位信息交换请求
223 | */
224 | public final static int DOWN_EXG_MSG_RETURN_END = 0x9206;
225 | /**
226 | * 申请交换制定车辆定位信息
227 | */
228 | public final static int DOWN_EXG_MSG_APPLY_FOR_MONITOR_STARTUP_ACK = 0x9207;
229 | /**
230 | * 取消交换制定车辆定位信息
231 | */
232 | public final static int DOWN_EXG_MSG_APPLY_FOR_MONITOR_END_ACK = 0x9208;
233 | /**
234 | * 补发车辆定位信息应答
235 | */
236 | public final static int DOWN_EXG_MSG_APPLY_HISGNSSDATA_ACK = 0x9209;
237 | /**
238 | * 上报车辆驾驶员身份识别信息请求
239 | */
240 | public final static int DOWN_EXG_MSG_REPORT_DRIVER_INFO = 0x920A;
241 | /**
242 | * 上报车辆电子运单请求
243 | */
244 | public final static int DOWN_EXG_MSG_TAKE_EWAYBILL_REQ = 0x920B;
245 |
246 |
247 | /**
248 | * 平台查岗应答
249 | */
250 | public final static int UP_PLATFORM_MSG_POST_QUERY_ACK = 0x1301;
251 | /**
252 | * 下发平台间报文应答
253 | */
254 | public final static int UP_PLATFORM_MSG_INFO_ACK = 0x1302;
255 |
256 |
257 | /**
258 | * 平台查岗应答
259 | */
260 | public final static int DOWN_PLATFORM_MSG_POST_QUERY_REQ = 0x9301;
261 | /**
262 | * 下发平台间报文应答
263 | */
264 | public final static int DOWN_PLATFORM_MSG_INFO_REQ = 0x9302;
265 |
266 |
267 | /**
268 | * 报警督办应答
269 | */
270 | public final static int UP_WARN_MSG_URGE_TODO_ACK = 0x1401;
271 | /**
272 | * 上报报警信息
273 | */
274 | public final static int UP_WARN_MSG_ADPT_INFO = 0x1402;
275 |
276 |
277 | /**
278 | * 报警督办请求
279 | */
280 | public final static int DOWN_WARN_MSG_URGE_TODO_REQ = 0x9401;
281 | /**
282 | * 报警预警
283 | */
284 | public final static int DOWN_WARN_MSG_INFORM_TIPS = 0x9402;
285 | /**
286 | * 实时交换报警信息
287 | */
288 | public final static int DOWN_WARN_MSG_EXG_INFORM = 0x9403;
289 |
290 |
291 | /**
292 | * 车辆单向监听应答
293 | */
294 | public final static int UP_CTRL_MSG_MONITOR_VEHICLE_ACK = 0x1501;
295 | /**
296 | * 车辆牌照应答
297 | */
298 | public final static int UP_CTRL_MSG_TAKE_PHOTO_ACK = 0x1502;
299 | /**
300 | * 下发车辆报文应答
301 | */
302 | public final static int UP_CTRL_MSG_TEXT_INFO_ACK = 0x1503;
303 | /**
304 | * 上报车辆形式记录应答
305 | */
306 | public final static int UP_CTRL_MSG_TAKE_TRAVEL_ACK = 0x1504;
307 | /**
308 | * 车辆应急接入监管平台应答
309 | */
310 | public final static int UP_CTRL_MSG_EMERGENCY_MONITORING_ACK = 0x1505;
311 |
312 |
313 | /**
314 | * 车辆单向监听请求
315 | */
316 | public final static int DOWN_CTRL_MSG_MONITOR_VEHICLE_REQ = 0x9501;
317 | /**
318 | * 车辆牌照请求
319 | */
320 | public final static int DOWN_CTRL_MSG_TAKE_PHOTO_REQ = 0x9502;
321 | /**
322 | * 下发车辆报文请求
323 | */
324 | public final static int DOWN_CTRL_MSG_TEXT_INFO_REQ = 0x9503;
325 | /**
326 | * 上报车辆形式记录请求
327 | */
328 | public final static int DOWN_CTRL_MSG_TAKE_TRAVEL_REQ = 0x9504;
329 | /**
330 | * 车辆应急接入监管平台请求
331 | */
332 | public final static int UP_CTRL_MSG_EMERGENCY_MONITORING_REQ = 0x9505;
333 |
334 |
335 | /**
336 | * 补报车辆静态信息应答
337 | */
338 | public final static int UP_BASE_MSG_VEHICLE_ADDED_ACK = 0x1601;
339 | /**
340 | * 补报车辆静态信息请求
341 | */
342 | public final static int DOWN_BASE_MSG_VEHICLE_ADDED = 0x9601;
343 |
344 | }
345 |
346 | }
347 |
--------------------------------------------------------------------------------
/src/main/java/org/tucke/jtt809/common/Jtt809Util.java:
--------------------------------------------------------------------------------
1 | package org.tucke.jtt809.common;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.apache.commons.lang3.time.DateUtils;
6 | import org.apache.commons.lang3.time.FastDateFormat;
7 |
8 | import java.nio.charset.Charset;
9 | import java.text.ParseException;
10 | import java.util.Arrays;
11 | import java.util.TimeZone;
12 |
13 | /**
14 | * @author tucke
15 | */
16 | @SuppressWarnings({"AlibabaUndefineMagicConstant", "UnusedReturnValue", "AlibabaLowerCamelCaseVariableNaming", "unused", "BooleanMethodIsAlwaysInverted"})
17 | @Slf4j
18 | public class Jtt809Util {
19 |
20 | /**
21 | * 0x5B, 0x5A, 0x5D, 0x5E 转义处理
22 | */
23 | public static byte[] escape(byte[] bytes) {
24 | // 最极端情况,每个byte都需要转义,所以使用2倍长度
25 | byte[] result = new byte[bytes.length * 2];
26 | int i = 0;
27 | for (byte b : bytes) {
28 | switch (b) {
29 | case 0x5B:
30 | result[i++] = 0x5A;
31 | result[i++] = 0x01;
32 | break;
33 | case 0x5A:
34 | result[i++] = 0x5A;
35 | result[i++] = 0x02;
36 | break;
37 | case 0x5D:
38 | result[i++] = 0x5E;
39 | result[i++] = 0x01;
40 | break;
41 | case 0x5E:
42 | result[i++] = 0x5E;
43 | result[i++] = 0x02;
44 | break;
45 | default:
46 | result[i++] = b;
47 | }
48 | }
49 | // 截取转义后的数据并返回
50 | return Arrays.copyOf(result, i);
51 | }
52 |
53 | /**
54 | * 0x5B, 0x5A, 0x5D, 0x5E 反转义处理
55 | */
56 | public static byte[] unescape(byte[] bytes) {
57 | if (bytes == null || bytes.length <= 1) {
58 | return bytes;
59 | }
60 | // 最极端情况,每个byte都不需要反转义,所以使用1倍长度
61 | byte[] result = new byte[bytes.length];
62 | int ii = 0;
63 | for (int i = 0; i < bytes.length; i++) {
64 | // 当前循环的 byte 数据
65 | byte curr = bytes[i];
66 | // 若最后一条 byte 数据还能进入循环,则它必定不满足反转义
67 | if (i == bytes.length - 1) {
68 | result[ii++] = curr;
69 | break;
70 | }
71 | // 下一条 byte 数据
72 | byte next = bytes[i + 1];
73 | if (curr == 0x5A) {
74 | // 将 0x5A 0x01 反转义为 0x5B,且下一条数据 0x01 不需要参与循环
75 | if (next == 0x01) {
76 | result[ii++] = 0x5B;
77 | i++;
78 | continue;
79 | }
80 | // 0x5A 0x02 反转义结果就是 0x5A,且下一条数据 0x02 不需要参与循环
81 | if (next == 0x02) {
82 | i++;
83 | }
84 | }
85 | if (curr == 0x5E) {
86 | // 将 0x5E 0x01 反转义为 0x5D,且下一条数据 0x01 不需要参与循环
87 | if (next == 0x01) {
88 | result[ii++] = 0x5D;
89 | i++;
90 | continue;
91 | }
92 | // 0x5E 0x02 反转义结果就是 0x5E,且下一条数据 0x02 不需要参与循环
93 | if (next == 0x02) {
94 | i++;
95 | }
96 | }
97 | result[ii++] = curr;
98 | }
99 | // 截取反转义后的数据并返回
100 | return Arrays.copyOf(result, ii);
101 | }
102 |
103 | /**
104 | * 消息校验
105 | */
106 | public static boolean validate(ByteBuf byteBuf) {
107 | int len = byteBuf.readableBytes();
108 | byte[] bytes = new byte[len - 2];
109 | byteBuf.getBytes(0, bytes);
110 | int calc = CRC16CCITT.crc16(bytes);
111 | int code = byteBuf.getUnsignedShort(len - 2);
112 | boolean result = calc == code;
113 | if (!result) {
114 | log.warn("CRC校验失败!计算结果为:{}, 传入值为:{}", calc, code);
115 | }
116 | return result;
117 | }
118 |
119 | /**
120 | * 加密
121 | */
122 | public static byte[] encrypt(int m1, int ia1, int ic1, long key, byte[] bytes) {
123 | if (bytes == null) {
124 | return null;
125 | }
126 | if (key == 0) {
127 | key = 1;
128 | }
129 | for (int i = 0; i < bytes.length; i++) {
130 | key = ia1 * (key % m1) + ic1;
131 | bytes[i] ^= ((key >> 20) & 0xFF);
132 | }
133 | return bytes;
134 | }
135 |
136 | /**
137 | * 解密
138 | */
139 | public static byte[] decrypt(int m1, int ia1, int ic1, long key, byte[] bytes) {
140 | return encrypt(m1, ia1, ic1, key, bytes);
141 | }
142 |
143 | /**
144 | * 解析字符串
145 | *
146 | * @param complement 是否考虑右边补零的情况
147 | */
148 | public static String readString(ByteBuf byteBuf, int length, Charset charset, boolean complement) {
149 | // 是否考虑右补十六进制 0x00
150 | if (complement) {
151 | byte[] bytes = new byte[length];
152 | byteBuf.readBytes(bytes);
153 | int len = 0;
154 | for (int i = bytes.length; i > 0; i--) {
155 | if (bytes[i - 1] != 0x00) {
156 | len = i;
157 | break;
158 | }
159 | }
160 | return new String(Arrays.copyOf(bytes, len), charset);
161 | } else {
162 | return byteBuf.readBytes(length).toString(charset);
163 | }
164 | }
165 |
166 | /**
167 | * 解析GBK字符串
168 | *
169 | * @param complement 是否考虑右边补零的情况
170 | */
171 | public static String readGBKString(ByteBuf byteBuf, int length, boolean complement) {
172 | return readString(byteBuf, length, Charset.forName("GBK"), complement);
173 | }
174 |
175 | /**
176 | * 解析GBK字符串
177 | */
178 | public static String readGBKString(ByteBuf byteBuf, int length) {
179 | return readGBKString(byteBuf, length, true);
180 | }
181 |
182 | /**
183 | * 解析时间
184 | */
185 | public static long parseDateTime(ByteBuf byteBuf) {
186 | String date = byteBuf.readByte() + "-" + byteBuf.readByte() + "-" + byteBuf.readShort() + " " +
187 | byteBuf.readByte() + ":" + byteBuf.readByte() + ":" + byteBuf.readByte();
188 | long time = 0L;
189 | try {
190 | FastDateFormat format = FastDateFormat.getInstance("dd-MM-yyyy HH:mm:ss", TimeZone.getTimeZone("GMT+8:00"));
191 | time = format.parse(date).getTime();
192 | } catch (ParseException e) {
193 | log.warn("日期 [{}] 解析错误", date);
194 | }
195 | return time;
196 | }
197 |
198 | }
199 |
--------------------------------------------------------------------------------
/src/main/java/org/tucke/jtt809/decoder/Jtt809Decoder.java:
--------------------------------------------------------------------------------
1 | package org.tucke.jtt809.decoder;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.buffer.ByteBufUtil;
5 | import io.netty.buffer.Unpooled;
6 | import io.netty.channel.ChannelHandlerContext;
7 | import io.netty.handler.codec.MessageToMessageDecoder;
8 | import io.netty.util.ReferenceCountUtil;
9 | import lombok.extern.slf4j.Slf4j;
10 | import org.tucke.gnsscenter.GnssCenterService;
11 | import org.tucke.jtt809.common.Jtt809Constant;
12 | import org.tucke.jtt809.common.Jtt809Util;
13 | import org.tucke.jtt809.packet.common.OuterPacket;
14 |
15 | import java.util.List;
16 |
17 | /**
18 | * @author tucke
19 | */
20 | @Slf4j
21 | public class Jtt809Decoder extends MessageToMessageDecoder {
22 |
23 | @SuppressWarnings({"AlibabaUndefineMagicConstant", "SpellCheckingInspection"})
24 | @Override
25 | protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List