修正命名错误
|
# NewLife.JT808 使用文档
> 版本:v1.0 | 日期:2026-07-15
本文档é¢å‘å议库使用者,涵盖åè®®åº“çš„æ ¸å¿ƒ API 使用ã€ç²¤æ ‡æ‰©å±•集æˆå’Œ RocketMQ 消æ¯é˜Ÿåˆ—é…置。
---
## 目录
1. [快速开始](/NewLife/NewLife.JT808/Blob/master/Doc/#1-快速开始)
2. [æ ¸å¿ƒ API 使用](/NewLife/NewLife.JT808/Blob/master/Doc/#2-æ ¸å¿ƒ-api-使用)
3. [ç²¤æ ‡æ‰©å±•](/NewLife/NewLife.JT808/Blob/master/Doc/#3-ç²¤æ ‡æ‰©å±•)
4. [RocketMQ 消æ¯é˜Ÿåˆ—](/NewLife/NewLife.JT808/Blob/master/Doc/#4-rocketmq-消æ¯é˜Ÿåˆ—)
5. [客户端使用](/NewLife/NewLife.JT808/Blob/master/Doc/#5-客户端使用)
6. [常è§é—®é¢˜](/NewLife/NewLife.JT808/Blob/master/Doc/#6-常è§é—®é¢˜)
---
## 1. 快速开始
### 1.1 安装 NuGet 包
```bash
dotnet add package NewLife.JT808
```
### 1.2 åˆå§‹åŒ–消æ¯å·¥åŽ‚
```csharp
using NewLife.JT808.Protocols;
// åˆå§‹åŒ–消æ¯å·¥åŽ‚ï¼Œæ‰«æç¨‹åºé›†æ³¨å†Œæ‰€æœ‰æ¶ˆæ¯ç±»åž‹
var factory = MessageFactory.Instance;
factory.Init();
```
### 1.3 è§£æžç»ˆç«¯ä¸ŠæŠ¥æ¶ˆæ¯
```csharp
using NewLife.JT808.Protocols;
using NewLife.JT808.Models;
// è§£æžåŽŸå§‹äºŒè¿›åˆ¶å¸§
var raw = new Byte[] { 0x7E, 0x02, 0x00, /* ... */, 0x7E };
var msg = new JTMessage();
msg.Read(new MemoryStream(raw), null);
// ååºåˆ—化为业务模型
var body = factory.Parse(msg);
if (body is T0200 position)
{
Console.WriteLine($"纬度: {position.Latitude / 1_000_000.0}");
Console.WriteLine($"ç»åº¦: {position.Longitude / 1_000_000.0}");
Console.WriteLine($"速度: {position.Speed} km/h");
// 读å–ä½ç½®é™„åŠ ä¿¡æ¯
foreach (var kv in position.Additionals)
Console.WriteLine($"{kv.Key}: {kv.Value}");
}
```
### 1.4 æž„å»ºä¸‹å‘æ¶ˆæ¯
```csharp
using NewLife.JT808.Protocols;
using NewLife.JT808.Models;
// 构建平å°é€šç”¨åº”ç”
var t8001 = new T8001
{
Sequence = msg.Sequence,
Kind = msg.Kind,
Result = ResultKinds.æˆåŠŸ
};
// åºåˆ—化消æ¯ä½“
var ms = new MemoryStream();
var writer = Helper.CreateWriter(ms, msg);
writer.TryWrite(null, ref t8001);
// å°è£…为 JTMessage 帧
var reply = new JTMessage
{
Kind = MessageKinds.å¹³å°é€šç”¨åº”ç”,
Mobile = msg.Mobile,
Sequence = (UInt16)(msg.Sequence + 1),
Payload = new Packet(ms.ToArray()),
Reply = false
};
// 输出完整帧
var outMs = new MemoryStream();
reply.Write(outMs, null);
var frameBytes = outMs.ToArray();
```
---
## 2. æ ¸å¿ƒ API 使用
### 2.1 消æ¯å¸§ï¼ˆJTMessage)
`JTMessage` 是åè®®çš„æ ¸å¿ƒæ¶ˆæ¯å¸§å¯¹è±¡ï¼Œè´Ÿè´£ï¼š
- **帧解æž**:从原始å—节æµä¸æå– 0x7E 帧边界ã€0x7D 转义
- **æ ¡éªŒ**ï¼šå¼‚æˆ–æ ¡éªŒéªŒè¯
- **分包**ï¼šè¯†åˆ«åˆ†åŒ…æ ‡å¿—ï¼Œç¼“å˜åˆå¹¶åˆ†åŒ…
- **版本识别**:自动识别 2011/2013/2019 版本
```csharp
// 手动创建消æ¯å¸§
var msg = new JTMessage
{
Kind = MessageKinds.ä½ç½®ä¿¡æ¯æ±‡æŠ¥, // 消æ¯ID
Mobile = "01380000001", // 终端手机å·ï¼ˆBCDç¼–ç )
Sequence = 1, // æµæ°´å·
Version = 0, // 0=2013版, 1=2019版
Payload = new Packet(bodyBytes), // 消æ¯ä½“è½½è·
};
```
### 2.2 消æ¯å·¥åŽ‚ï¼ˆMessageFactory)
消æ¯å·¥åŽ‚ç»´æŠ¤æ¶ˆæ¯ç±»åž‹åˆ°å®žä½“ç±»çš„æ˜ å°„ï¼Œæ”¯æŒè‡ªåŠ¨æ‰«ææ³¨å†Œå’Œæ‰‹åŠ¨æ³¨å†Œã€‚
```csharp
// è‡ªåŠ¨æ‰«ææ³¨å†Œï¼ˆInit è‡ªåŠ¨æ‰«ææ•´ä¸ªç¨‹åºé›†çš„ [MessageKind] 特性)
var factory = MessageFactory.Instance;
factory.Init();
// 手动注册å•个类型
factory.Register<MyCustomMessage>();
factory.Register(MessageKinds.自定义类型, typeof(MyCustomMessage));
// 注册外部程åºé›†ï¼ˆç”¨äºŽæ‰©å±•包)
factory.Register(typeof(MyExtension).Assembly);
// è¦†ç›–å·²æœ‰æ˜ å°„ï¼ˆç”¨äºŽåŽ‚å•†è‡ªå®šä¹‰å议)
factory.SetMap<TMyOverride>();
factory.SetMap(MessageKinds.ä½ç½®ä¿¡æ¯æ±‡æŠ¥, typeof(T0200_V2));
```
### 2.3 消æ¯ä½“模型
#### ç®€å•æ¶ˆæ¯ä½“(å射自动åºåˆ—化)
å¯¹äºŽå±žæ€§ç±»åž‹ä¸ºåŸºç¡€ç±»åž‹ã€æ— 特殊编解ç 逻辑的消æ¯ä½“,使用å射自动åºåˆ—化:
```csharp
[MessageKind(MessageKinds.å¹³å°é€šç”¨åº”ç”)]
public class T8001
{
public UInt16 Sequence { get; set; }
public MessageKinds Kind { get; set; }
public ResultKinds Result { get; set; }
}
```
#### 夿‚消æ¯ä½“(IAccessor 手动åºåˆ—化)
对于需è¦ç²¾ç¡®æŽ§åˆ¶ç¼–è§£ç é€»è¾‘çš„å¤æ‚消æ¯ä½“,实现 `IAccessor` 接å£ï¼š
```csharp
[MessageKind(MessageKinds.ä½ç½®ä¿¡æ¯æ±‡æŠ¥)]
public class T0200 : IAccessor
{
public PositionAlarms Alarm { get; set; }
public PositionStatus Status { get; set; }
public Int32 Latitude { get; set; }
public Int32 Longitude { get; set; }
public Boolean Read(Stream stream, Object? context)
{
var reader = Helper.CreateReader(stream);
Alarm = (PositionAlarms)reader.ReadUInt32();
Status = (PositionStatus)reader.ReadUInt32();
Latitude = reader.ReadInt32();
Longitude = reader.ReadInt32();
// ... åŽç»å—段
return true;
}
public Boolean Write(Stream stream, Object? context)
{
var writer = Helper.CreateWriter(stream);
writer.Write((UInt32)Alarm);
writer.Write((UInt32)Status);
writer.Write(Latitude);
writer.Write(Longitude);
// ... åŽç»å—段
return true;
}
}
```
### 2.4 åºåˆ—化特性
| 特性 | 用途 |
|------|------|
| `[BCDString(Length)]` | BCD ç¼–ç å—ç¬¦ä¸²å—æ®µ |
| `[BCDTime(Length, Format)]` | BCD ç¼–ç æ—¶é—´å—段,如 `[BCDTime(6, "yyMMddHHmmss")]` |
| `[FieldSize(N)]` | å›ºå®šé•¿åº¦å—æ®µ |
| `[FullBytes]` / `[FullString2]` | æ•°æ®æµå‰©ä½™éƒ¨åˆ†æ•´ä½“è¯»å– |
| `[JT2011]` / `[JT2019]` | 2011/2019 版本æ¡ä»¶å—段,éžå¯¹åº”版本自动跳过 |
| `[MessageKind(Kind)]` | 消æ¯ç±»åž‹æ ‡è®°ï¼Œç”¨äºŽå·¥åŽ‚æ³¨å†Œ |
### 2.5 消æ¯é˜Ÿåˆ—接å£
æ ¸å¿ƒåº“å®šä¹‰äº†æ¶ˆæ¯é˜Ÿåˆ—抽象接å£ï¼š
```csharp
// 消æ¯ç”Ÿäº§è€…:将设备上行消æ¯ç”Ÿäº§åˆ°é˜Ÿåˆ—
public interface IMsgProducer<TMessage>
{
Task<Boolean> ProduceAsync(String mobile, TMessage message, CancellationToken ct = default);
String Topic { get; }
}
// æ¶ˆæ¯æ¶ˆè´¹è€…:消费设备上行消æ¯
public interface IMsgConsumer<TMessage>
{
Task SubscribeAsync(Func<String, TMessage, Task> onMessage, CancellationToken ct = default);
Task UnsubscribeAsync(CancellationToken ct = default);
String Topic { get; }
}
// 会è¯é€šçŸ¥ï¼šç»ˆç«¯ä¸Šä¸‹çº¿äº‹ä»¶
public interface ISessionProducer { /* ProduceOnline / ProduceOffline */ }
public interface ISessionConsumer { /* Subscribe 上下线回调 */ }
// 下行指令处ç†å™¨
public interface IDownMessageHandler
{
Task<Byte[]> HandleAsync(String mobile, Byte[] data);
}
```
内置实现:
| 实现 | ä½ç½® | 说明 |
|------|------|------|
| `DefaultMsgQueue<T>` | `NewLife.JT808.Protocols` | 内å˜é˜Ÿåˆ—ï¼Œæ— éœ€å¤–éƒ¨ä¾èµ– |
| `RocketMQProducer<T>` / `RocketMQConsumer<T>` | `NewLife.JT808.Protocols.RocketMQ` | RocketMQ 适é…器 |
| `QueueService` | `JT808.Server.Services` | Redis Stream 实现 |
---
## 3. ç²¤æ ‡æ‰©å±•
### 3.1 概述
ç²¤æ ‡ï¼ˆå¹¿ä¸œçœä¸»åŠ¨å®‰å…¨æ ‡å‡†ï¼‰æ˜¯ JT/T 808 åœ¨å¹¿ä¸œåœ°åŒºçš„åŒºåŸŸæ€§æ‰©å±•ï¼Œä¸Žè‹æ ‡å…±ç”¨ ADAS/DSM/TPMS/BSD æŠ¥è¦æ•°æ®ç»“构,但å˜åœ¨ä»¥ä¸‹å·®å¼‚:
| 差异点 | è‹æ ‡ | ç²¤æ ‡ |
|--------|------|------|
| 终端 ID 长度 | 7 å—节 | 30 å—节 |
| æŠ¥è¦æ ‡è¯†å·é•¿åº¦ | 16 å—节 | 40 å—节 |
| æ ¸å¿ƒæŠ¥è¦æ¶ˆæ¯ | ä½ç½®é™„åŠ ä¿¡æ¯ 0x64~0x67 | ç‹¬ç«‹æ¶ˆæ¯ 0x1FC4 |
| USB 外设é€ä¼ | 䏿”¯æŒ | 0x0900/0x8900 å类型 0xF7/0xF8 |
| 傿•° ID | æ ‡å‡†èŒƒå›´ | 0xF364~0xF370 |
### 3.2 ç²¤æ ‡æ¶ˆæ¯ä½“列表
| æ¶ˆæ¯ | 消æ¯ID | æ–¹å‘ | 说明 |
|------|--------|------|------|
| `T1FC4` | 0x1FC4 | 上行 | 主动安全报è¦ä¸ŠæŠ¥ï¼ˆæ ¸å¿ƒï¼‰ |
| `T0900_F7` | 0x0900+0xF7 | 上行 | USB 外设状æ€ä¿¡æ¯ä¸ŠæŠ¥ |
| `T0900_F8` | 0x0900+0xF8 | 上行 | USB 外设信æ¯ä¸ŠæŠ¥ |
| `T8900_F7` | 0x8900+0xF7 | 下行 | USB å¤–è®¾çŠ¶æ€æŸ¥è¯¢ |
| `T8900_F8` | 0x8900+0xF8 | 下行 | USB å¤–è®¾ä¿¡æ¯æŸ¥è¯¢ |
| `T0200_F1` | 0x0200+0xF1 | 上行 | ä½ç½®é™„åŠ ä¿¡æ¯-安装异常 |
| `T0200_F2` | 0x0200+0xF2 | 上行 | ä½ç½®é™„åŠ ä¿¡æ¯-算法异常 |
| `T8103_F364` | 0x8103+0xF364 | 下行 | å‰å‘碰撞报è¦å‚æ•° |
| `T8103_F370` | 0x8103+0xF370 | 下行 | ç›²åŒºç›‘æµ‹å‚æ•° |
### 3.3 ä½¿ç”¨ç²¤æ ‡æ‰©å±•
ç²¤æ ‡æ¶ˆæ¯ä½“ä¸Žæ ¸å¿ƒæ¶ˆæ¯ä½“在åŒä¸€ä¸ªç¨‹åºé›†ä¸ï¼Œåˆå§‹åŒ–消æ¯å·¥åŽ‚åŽè‡ªåŠ¨æ³¨å†Œï¼š
```csharp
MessageFactory.Instance.Init();
// ç²¤æ ‡æ¶ˆæ¯ä½“éšç¨‹åºé›†æ‰«æè‡ªåŠ¨æ³¨å†Œï¼Œæ— éœ€é¢å¤–é…ç½®
```
è§£æžç²¤æ ‡ä¸»åŠ¨å®‰å…¨æŠ¥è¦ï¼š
```csharp
using NewLife.JT808.Protocols;
using NewLife.JT808.YueBiao;
// è§£æž 0x1FC4 消æ¯
if (body is T1FC4 alarm)
{
Console.WriteLine($"终端ID: {alarm.TerminalId}");
Console.WriteLine($"报è¦ç±»åž‹: 0x{alarm.AlarmType:X2}");
Console.WriteLine($"报è¦å类型: 0x{alarm.AlarmKind:X2}");
Console.WriteLine($"级别: {alarm.Level}");
Console.WriteLine($"ä½ç½®: {alarm.Latitude / 1_000_000.0}, {alarm.Longitude / 1_000_000.0}");
}
```
è§£æž USB 外设状æ€ï¼ˆ0x0900 é€ä¼ 消æ¯ï¼‰ï¼š
```csharp
// å‡è®¾å·²è§£æžå‡º T0900 é€ä¼ 消æ¯
if (body is T0900 passthrough && passthrough.Type == 0xF7)
{
var usbStatus = T0900_F7.Parse(passthrough.Data);
Console.WriteLine($"USB设备总数: {usbStatus.TotalCount}");
foreach (var item in usbStatus.Items ?? [])
{
Console.WriteLine($" ID: {item.Id}, 状æ€: {item.Status}, 在线: {item.Online}");
}
}
```
### 3.4 ç²¤æ ‡æŠ¥è¦æ ‡è¯†å·
`AlarmDevice` ç±»å·²å†…ç½®è‹æ ‡/ç²¤æ ‡è‡ªé€‚åº”é€»è¾‘ï¼š
```csharp
// AlarmDevice è‡ªåŠ¨æ ¹æ®æ•°æ®æµå‰©ä½™é•¿åº¦åˆ¤æ–
// æµå‰©ä½™ ≥ 40 å—节 → ç²¤æ ‡ï¼ˆ30 å—节终端 ID + 16 å—节扩展)
// æµå‰©ä½™ < 40 å—节 → è‹æ ‡ï¼ˆ7 å—节终端 ID)
```
---
## 4. RocketMQ 消æ¯é˜Ÿåˆ—
### 4.1 安装
```bash
# æ ¸å¿ƒåº“å·²å¼•ç”¨ NewLife.RocketMQï¼Œæ— éœ€é¢å¤–安装
```
### 4.2 RocketMQ 生产者适é…器
```csharp
using NewLife.JT808.Protocols.RocketMQ;
// 创建生产者
var producer = new RocketMQProducer<PositionData>(
nameServer: "127.0.0.1:9876",
topic: "PositionData",
group: "PID_PositionData"
);
// 生产消æ¯
await producer.ProduceAsync("01380000001", new PositionData
{
Latitude = 22300000,
Longitude = 114000000,
Speed = 60,
});
// 释放
producer.Dispose();
```
### 4.3 RocketMQ 消费者适é…器
```csharp
using NewLife.JT808.Protocols.RocketMQ;
// 创建消费者
var consumer = new RocketMQConsumer<PositionData>(
nameServer: "127.0.0.1:9876",
topic: "PositionData",
group: "CG_PositionData"
);
// 订阅消æ¯
await consumer.SubscribeAsync(async (mobile, data) =>
{
Console.WriteLine($"收到终端 {mobile} çš„ä½ç½®æ•°æ®:");
Console.WriteLine($" 纬度: {data.Latitude / 1_000_000.0}");
Console.WriteLine($" ç»åº¦: {data.Longitude / 1_000_000.0}");
Console.WriteLine($" 速度: {data.Speed} km/h");
});
// å–æ¶ˆè®¢é˜…
await consumer.UnsubscribeAsync();
consumer.Dispose();
```
### 4.4 会è¯é€šçŸ¥é€‚é…器
```csharp
// 生产者端(Server)
var sessionProducer = new RocketMQSessionAdapter(
nameServer: "127.0.0.1:9876",
topic: "SessionEvent"
);
await sessionProducer.ProduceOnlineAsync("01380000001");
await sessionProducer.ProduceOfflineAsync("01380000001");
```
### 4.5 完整集æˆç¤ºä¾‹ï¼ˆæ›¿ä»£ Redis Stream)
```csharp
// 在 Server å¯åŠ¨æ—¶æ³¨å†Œ RocketMQ 适é…器
services.AddSingleton<IMsgProducer<PositionData>>(_ =>
new RocketMQProducer<PositionData>("127.0.0.1:9876", "PositionData"));
services.AddSingleton<IMsgProducer<ADASAlarm>>(_ =>
new RocketMQProducer<ADASAlarm>("127.0.0.1:9876", "ADASAlarm"));
```
### 4.6 RocketMQ é…ç½®è¦ç‚¹
| 傿•° | 说明 | 默认值 |
|------|------|--------|
| `NameServerAddress` | RocketMQ NameServer åœ°å€ | å¿…å¡« |
| `Topic` | 主题åç§° | å¿…å¡« |
| `Group` | 生产者组/消费组 | è‡ªåŠ¨ç”Ÿæˆ |
| `AccessKey` / `SecretKey` | 阿里云/åŽä¸ºäº‘ç‰äº‘å®žä¾‹è®¤è¯ | å¯é€‰ |
| `InstanceId` | 阿里云实例 ID | å¯é€‰ |
云厂商适é…说明:
```csharp
// 阿里云
Producer.AccessKey = "xxx";
Producer.SecretKey = "xxx";
Producer.Aliyun = new AliyunProvider { InstanceId = "MQ_INST_xxx" };
// åŽä¸ºäº‘
Producer.AccessKey = "xxx";
Producer.SecretKey = "xxx";
// 自动识别 InstanceId
// 腾讯云
Producer.AccessKey = "xxx";
Producer.SecretKey = "xxx";
// 自动识别 Namespace
```
---
## 5. 客户端使用
### 5.1 TCP 客户端
```csharp
using NewLife.JT808.Protocols;
// 创建客户端
var client = new JT808Client
{
Server = "127.0.0.1",
Port = 808,
Mobile = "01380000001",
AutoReconnect = true, // 自动é‡è¿ž
HeartbeatInterval = 30, // 心跳间隔(秒)
};
// 设置消æ¯å¤„ç†å™¨
client.AddHandler<T0200>(msg =>
{
Console.WriteLine($"收到ä½ç½®: {msg.Latitude}, {msg.Longitude}");
});
// 连接æœåС噍
await client.ConnectAsync();
```
### 5.2 å‘逿¶ˆæ¯
```csharp
// å‘逿¶ˆæ¯å¹¶ç‰å¾…应ç”
var result = await client.SendJT808(new T0200
{
Latitude = 22300000,
Longitude = 114000000,
});
```
---
## 6. 常è§é—®é¢˜
### 6.1 å¦‚ä½•æ·»åŠ è‡ªå®šä¹‰æ¶ˆæ¯ä½“?
1. 创建消æ¯ä½“ç±»ï¼Œæ·»åŠ `[MessageKind]` 特性
2. 实现 `IAccessor`ï¼ˆå¤æ‚消æ¯ä½“)或ä¾èµ–åå°„åºåˆ—åŒ–ï¼ˆç®€å•æ¶ˆæ¯ä½“)
3. 调用 `MessageFactory.Instance.Register<T>()` 注册
4. 或在外部程åºé›†ä¸å®šä¹‰ï¼Œä½¿ç”¨ `factory.Register(Assembly)` æ‰¹é‡æ³¨å†Œ
### 6.2 å¦‚ä½•åˆ‡æ¢æ¶ˆæ¯é˜Ÿåˆ—实现?
消æ¯é˜Ÿåˆ—通过ä¾èµ–注入解耦,在 `Program.cs` 䏿›¿æ¢å®žçްå³å¯ï¼š
```csharp
// 开呿µ‹è¯•:内å˜é˜Ÿåˆ—
services.AddSingleton<IMsgProducer<PositionData>, DefaultMsgQueue<PositionData>>();
// 生产环境:RocketMQ
services.AddSingleton<IMsgProducer<PositionData>>(_ =>
new RocketMQProducer<PositionData>("127.0.0.1:9876", "PositionData"));
// 生产环境:Redis Stream(在 Server 项目ä¸ä½¿ç”¨ QueueService)
```
### 6.3 å¦‚ä½•æ”¯æŒ 2011 版本?
```csharp
JT808Config.Version = JT808Version.JT2011;
```
设置 `JT808Config.Version` åŽï¼Œæ¶ˆæ¯åºåˆ—化时自动跳过 `[JT2011]` ç‰¹æ€§æ ‡è®°çš„å—æ®µã€‚
### 6.4 性能建议
- 使用 `ArrayPool<Byte>` 或 `Pool.StringBuilder` å‡å°‘内å˜åˆ†é…
- çƒç‚¹è·¯å¾„é¿å…å射,优先实现 `IAccessor`
- 使用 `FileCodec` 处ç†å¤§é‡æŠ¥è¦é™„ä»¶æ–‡ä»¶ä¼ è¾“
- 生产环境建议使用 RocketMQ 或 Redis Stream 替代默认内å˜é˜Ÿåˆ—
|