Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/media #15

Merged
merged 10 commits into from
May 29, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -16,7 +16,11 @@
"type": "go",
"request": "launch",
"mode": "debug",
"program": "test/client/main.go"
"program": "test/client/main.go",
// "args": [
// "-c",
// "test/client/configs/default.yaml"
// ]
},
{
"name": "Launch Package",
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@

## 项目特点

### 兼容 2019/2013/2011 版本差异
### 兼容 JT808 2019/2013/2011 版本差异

定义版本类型分为 `Version2019 / Version2013 / Version2011`。

@@ -29,6 +29,8 @@
| 终端 ID 编码长度 | 7 字节 | 7 字节 | 30 字节 |
| 从业资格证编码长度 | 40 字节 | 20 字节 | 20 字节 |

### 支持 JT1078 协议的音视频传输控制

### 支持常见消息列表 (WIP)

| 终端侧 | 平台侧 |
@@ -297,7 +299,10 @@ todo

<img src="https://ghproxy.com/https://raw.githubusercontent.com/fakeYanss/imgplace/master/2023/2023-02-18_20230218211153.png" alt="device manage" width="300">

<!--
在早些时候,查询多媒体资源列表会通过 JT808 协议的信令交互。后来在实际应用中发现,视频和音频的传输会长时间占用连接通道,这期间其他的操作啥也干不了,只能等着音视频数据传输完。这样不太好,所以推出了 JT1078 协议,此后在 JT808 协议交互中最多进行图片资源的传输,而音视频的传输则通过 JT1078 中的信令消息。

详细来说,就是在 JT1078 中特别指定了 0x0800/0x0801/0x8802/0x0802/0x8803 这 5 条信令消息中多媒体字段只应包含图片类型。

```plantuml
@startuml

@@ -351,11 +356,25 @@ group 平台下发
c -> s: 通用应答 0x0001
deactivate c
deactivate s

s -> c: 平台查询终端音视频资源列表 0x9205
activate s
activate c
c -> s: 终端上传音视频资源列表 0x1205
deactivate c
deactivate s

s -> c: 平台查询终端音视频资源列表 0x9205
activate s
activate c
c -> s: 终端上传音视频资源列表 0x1205
deactivate c
deactivate s

end

@enduml
```
-->

## 编译和运行

33 changes: 0 additions & 33 deletions internal/codec/hex/hex.go
Original file line number Diff line number Diff line change
@@ -93,17 +93,6 @@ func WriteByte(pkt []byte, num uint8) []byte {
return append(pkt, num)
}

func any2uint8(a any) uint8 {
if b, ok := a.(float64); ok {
return uint8(b)
}
return a.(uint8)
}

func WriteByteAny(pkt []byte, num any) []byte {
return WriteByte(pkt, any2uint8(num))
}

// 对应JT808类型WORD
func ReadWord(pkt []byte, idx *int) uint16 {
ans := binary.BigEndian.Uint16(pkt[*idx : *idx+2])
@@ -118,17 +107,6 @@ func WriteWord(pkt []byte, num uint16) []byte {
return append(pkt, numPkt...)
}

func any2uint16(a any) uint16 {
if b, ok := a.(float64); ok {
return uint16(b)
}
return a.(uint16)
}

func WriteWordAny(pkt []byte, num any) []byte {
return WriteWord(pkt, any2uint16(num))
}

// 对应JT808类型DWORD
func ReadDoubleWord(pkt []byte, idx *int) uint32 {
ans := binary.BigEndian.Uint32(pkt[*idx : *idx+4])
@@ -143,17 +121,6 @@ func WriteDoubleWord(pkt []byte, num uint32) []byte {
return append(pkt, numPkt...)
}

func any2uint32(a any) uint32 {
if b, ok := a.(float64); ok {
return uint32(b)
}
return a.(uint32)
}

func WriteDoubleWordAny(pkt []byte, num any) []byte {
return WriteDoubleWord(pkt, any2uint32(num))
}

// 对应JT808类型BYTE[n]
func ReadBytes(pkt []byte, idx *int, n int) []byte {
ans := pkt[*idx : *idx+n]
4 changes: 2 additions & 2 deletions internal/config/asset.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions internal/protocol/model/device_media.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package model

import (
"time"

"github.com/fakeyanss/jt808-server-go/internal/codec/hex"
)

type DeviceMediaQuery struct {
LogicChannelID uint8 `json:"logicChannelId"` // 逻辑通道号
StartTime *time.Time `json:"startTime"` // 开始时间
EndTime *time.Time `json:"endTime"` // 结束时间
AlarmSign uint32 `json:"alarmSign"` // 报警标志位。bit0-bit31为0x0200的报警标志位,
AlarmSignExt uint32 `json:"alarmSignExt"` // 报警标志位。bit32-bit63?,全0表示无报警类型条件
MediaType uint8 `json:"mediaType"` // 音视频类型。0:音视频;1:音频;2:视频;3:视频或音视频
StreamType uint8 `json:"streamType"` // 码流类型。0:所有码流;1:主码流;2:子码流
StorageType uint8 `json:"storageType"` // 存储器类型。0:所有存储器;1:主存储器;2:灾备存储器
}

func (q *DeviceMediaQuery) Decode(pkt []byte, idx *int) {
q.LogicChannelID = hex.ReadByte(pkt, idx)
q.StartTime = hex.ReadTime(pkt, idx)
q.EndTime = hex.ReadTime(pkt, idx)
q.AlarmSign = hex.ReadDoubleWord(pkt, idx)
q.AlarmSignExt = hex.ReadDoubleWord(pkt, idx)
q.MediaType = hex.ReadByte(pkt, idx)
q.StreamType = hex.ReadByte(pkt, idx)
q.StorageType = hex.ReadByte(pkt, idx)
}

func (q *DeviceMediaQuery) Encode() (pkt []byte) {
pkt = hex.WriteByte(pkt, q.LogicChannelID)
pkt = hex.WriteTime(pkt, *q.StartTime)
pkt = hex.WriteTime(pkt, *q.EndTime)
pkt = hex.WriteDoubleWord(pkt, q.AlarmSign)
pkt = hex.WriteDoubleWord(pkt, q.AlarmSignExt)
pkt = hex.WriteByte(pkt, q.MediaType)
pkt = hex.WriteByte(pkt, q.StreamType)
pkt = hex.WriteByte(pkt, q.StorageType)
return pkt
}

type DeviceMedia struct {
DeviceMediaQuery
Size uint32 // 文件大小,单位Byte
}

func (m *DeviceMedia) Decode(pkt []byte) {
idx := 0
m.DeviceMediaQuery.Decode(pkt, &idx)
m.Size = hex.ReadDoubleWord(pkt, &idx)
}

func (m *DeviceMedia) Encode() (pkt []byte) {
pkt = m.DeviceMediaQuery.Encode()
pkt = hex.WriteDoubleWord(pkt, m.Size)
return pkt
}
94 changes: 75 additions & 19 deletions internal/protocol/model/device_params.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package model

import (
"fmt"
"sort"

"github.com/pkg/errors"
@@ -10,8 +11,9 @@ import (
)

var (
ErrDecodeDeviceParams = errors.New("Fail to decode device params")
ErrEncodeDeviceParams = errors.New("Fail to encode device params")
ErrDecodeDeviceParams = errors.New("Fail to decode device params")
ErrEncodeDeviceParams = errors.New("Fail to encode device params")
ErrParamIDNotSupportted = errors.New("Param id is not supportted")
)

type DeviceParams struct {
@@ -81,9 +83,11 @@ type ParamData struct {
func (p *ParamData) Decode(pkt []byte, idx *int) error {
p.ParamID = hex.ReadDoubleWord(pkt, idx)
p.ParamLen = hex.ReadByte(pkt, idx)
if fn, ok := argTable[p.ParamID]; ok {
p.ParamValue = fn.decode(pkt, idx, int(p.ParamLen))
fn, ok := argTable[p.ParamID]
if !ok {
log.Warn().Str("ParamID", fmt.Sprintf("0x%04x", p.ParamID)).Err(ErrParamIDNotSupportted).Msg("skip it")
}
p.ParamValue = fn.decode(pkt, idx, int(p.ParamLen))
return nil
}

@@ -95,7 +99,8 @@ func (p *ParamData) Encode() (pkt []byte, err error) {
pkt = hex.WriteBytes(pkt, value)
return pkt, nil
}
return nil, ErrEncodeDeviceParams
log.Warn().Str("ParamID", fmt.Sprintf("0x%04x", p.ParamID)).Err(ErrParamIDNotSupportted).Msg("skip it")
return nil, ErrParamIDNotSupportted
}

type paramFn struct {
@@ -114,22 +119,57 @@ type paramFn struct {
//
// 所以需要再encode时,将其按照json默认类型推断,再进行强转

func any2uint8(a any) uint8 {
if b, ok := a.(float64); ok {
return uint8(b)
}
return a.(uint8)
}

func writeByteAny(pkt []byte, num any) []byte {
return hex.WriteByte(pkt, any2uint8(num))
}

func any2uint16(a any) uint16 {
if b, ok := a.(float64); ok {
return uint16(b)
}
return a.(uint16)
}

func writeWordAny(pkt []byte, num any) []byte {
return hex.WriteWord(pkt, any2uint16(num))
}

func any2uint32(a any) uint32 {
if b, ok := a.(float64); ok {
return uint32(b)
}
return a.(uint32)
}

func writeDoubleWordAny(pkt []byte, num any) []byte {
return hex.WriteDoubleWord(pkt, any2uint32(num))
}

var (
decodeByte = func(b []byte, idx *int, paramLen int) any { return hex.ReadByte(b, idx) }
encodeByte = func(a any) (pkt []byte) { return hex.WriteByteAny(pkt, a) }
encodeByte = func(a any) (pkt []byte) { return writeByteAny(pkt, a) }
decodeWord = func(b []byte, idx *int, paramLen int) any { return hex.ReadWord(b, idx) }
encodeWord = func(a any) (pkt []byte) { return hex.WriteWordAny(pkt, a) }
encodeWord = func(a any) (pkt []byte) { return writeWordAny(pkt, a) }
decodeDoubleWord = func(b []byte, idx *int, paramLen int) any { return hex.ReadDoubleWord(b, idx) }
encodeDoubleWord = func(a any) (pkt []byte) { return hex.WriteDoubleWordAny(pkt, a) }
decodeBCD = func(b []byte, idx *int, paramLen int) any { return hex.ReadBCD(b, idx, paramLen) }
encodeBCD = func(a any) (pkt []byte) { return hex.WriteBCD(pkt, a.(string)) }
encodeDoubleWord = func(a any) (pkt []byte) { return writeDoubleWordAny(pkt, a) }
decodeBytes = func(b []byte, idx *int, paramLen int) any { return hex.ReadBCD(b, idx, paramLen) } // transform bytes to string
encodeBytes = func(a any) (pkt []byte) { return hex.WriteBCD(pkt, a.(string)) } // transform string to bytes
decodeString = func(b []byte, idx *int, paramLen int) any { return hex.ReadString(b, idx, paramLen) }
encodeString = func(a any) (pkt []byte) { return hex.WriteString(pkt, a.(string)) }
decodeGBK = func(b []byte, idx *int, paramLen int) any { return hex.ReadGBK(b, idx, paramLen) }
encodeGBK = func(a any) (pkt []byte) { return hex.WriteGBK(pkt, a.(string)) }
)

var argTable = map[uint32]*paramFn{
// JT808 param

// 终端心跳发送间隔,单位为秒(s)
0x0001: {decode: decodeDoubleWord, encode: encodeDoubleWord},
// TCP消息应答超时时间,单位为秒(s)
@@ -160,9 +200,9 @@ var argTable = map[uint32]*paramFn{
0x0016: {decode: decodeGBK, encode: encodeGBK},
// 备份服务器地址,IP或域名(2019版以冒号分割主机和端口,多个服务器使用分号分隔)
0x0017: {decode: decodeGBK, encode: encodeGBK},
// (JTT2013)服务器TCP端口
// (JT808 2013)服务器TCP端口
0x0018: {decode: decodeDoubleWord, encode: encodeDoubleWord},
// (JTT2013)服务器UDP端口
// (JT808 2013)服务器UDP端口
0x0019: {decode: decodeDoubleWord, encode: encodeDoubleWord},
// 道路运输证IC卡认证主服务器IP地址或域名
0x001A: {decode: decodeGBK, encode: encodeGBK},
@@ -178,13 +218,13 @@ var argTable = map[uint32]*paramFn{
0x0021: {decode: decodeDoubleWord, encode: encodeDoubleWord},
// 驾驶员未登录汇报时间间隔,单位为秒(s),>0
0x0022: {decode: decodeDoubleWord, encode: encodeDoubleWord},
// (JTT2019)从服务器APN.该值为空时,终端应使用主服务器相同配置
// (JT808 2019)从服务器APN.该值为空时,终端应使用主服务器相同配置
0x0023: {decode: decodeGBK, encode: encodeGBK},
// (JTT2019)从服务器无线通信拨号用户名.该值为空时,终端应使用主服务器相同配置
// (JT808 2019)从服务器无线通信拨号用户名.该值为空时,终端应使用主服务器相同配置
0x0024: {decode: decodeGBK, encode: encodeGBK},
// (JTT2019)从服务器无线通信拨号密码.该值为空时,终端应使用主服务器相同配置
// (JT808 2019)从服务器无线通信拨号密码.该值为空时,终端应使用主服务器相同配置
0x0025: {decode: decodeGBK, encode: encodeGBK},
// (JTT2019)从服务器备份地址、IP或域名.主服务器IP地址或域名,端口同主服务器
// (JT808 2019)从服务器备份地址、IP或域名.主服务器IP地址或域名,端口同主服务器
0x0026: {decode: decodeGBK, encode: encodeGBK},
// 休眠时汇报时间间隔,单位为秒(s),>0
0x0027: {decode: decodeDoubleWord, encode: encodeDoubleWord},
@@ -204,12 +244,12 @@ var argTable = map[uint32]*paramFn{
0x0030: {decode: decodeDoubleWord, encode: encodeDoubleWord},
// 电子围栏半径,单位为米
0x0031: {decode: decodeWord, encode: encodeWord},
// (JTT2019)违规行驶时段范围,精确到分。
// (JT808 2019)违规行驶时段范围,精确到分。
// byte1:违规行驶开始时间的小时部分;
// byte2:违规行驶开始的分钟部分;
// byte3:违规行驶结束时间的小时部分;
// byte4:违规行驶结束时间的分钟部分。
0x0032: {decode: decodeBCD, encode: encodeBCD},
0x0032: {decode: decodeBytes, encode: encodeBytes},
// 监控平台电话号码
0x0040: {decode: decodeGBK, encode: encodeGBK},
// 复位电话号码,可采用此电话号码拨打终端电话让终端复位
@@ -282,7 +322,7 @@ var argTable = map[uint32]*paramFn{
0x0082: {decode: decodeWord, encode: encodeWord},
// 公安交通管理部门颁发的机动车号牌
0x0083: {decode: decodeGBK, encode: encodeGBK},
// 车牌颜色,按照JT/T415-2006的5.4.12
// 车牌颜色,按照JT415-2006的5.4.12
0x0084: {decode: decodeByte, encode: encodeByte},
// GNSS定位模式,定义如下:
// bit0,0:禁用GPS定位,1:启用 GPS 定位;
@@ -337,4 +377,20 @@ var argTable = map[uint32]*paramFn{
// bit29 表示数据采集方式,0:原始数据,1:采集区间的计算值;
// bit28-bit0 表示 CAN 总线 ID。
0x0110: {decode: decodeString, encode: encodeString},

// JT1078 param
// 音视频参数设置
0x0075: {decode: decodeBytes, encode: encodeBytes},
// 音视频通道列表设置
0x0076: {decode: decodeBytes, encode: encodeBytes},
// 单独通道视频参数设置
0x0077: {decode: decodeBytes, encode: encodeBytes},
// 特殊报警录像参数设置
0x0079: {decode: decodeBytes, encode: encodeBytes},
// 视频相关报警屏蔽字
0x007A: {decode: decodeBytes, encode: encodeBytes},
// 图像分析报警参数设置
0x007B: {decode: decodeBytes, encode: encodeBytes},
// 终端休眠唤醒模式设置
0x007C: {decode: decodeBytes, encode: encodeBytes},
}
Loading