用php实现jt808部标协议对接
背景
之前做的智慧校车/网约巴士系统(网约巴士旅游专线平台搭建历程),对接海康设备时需要对接交通部部标协议之前用的Java写的。突然想说能不能用PHP实现呢。说实话,肯定没有java顺手,不过也不是不可以,就是有点蹩脚!
以下是一个基于PHP实现JT808协议的简化示例代码,包含基础协议解析和封装逻辑:
<?php
class JT808Protocol {
// 消息ID定义
const MSG_ID_REGISTER = 0x0100; // 终端注册
const MSG_ID_REGISTER_RESP = 0x8100; // 注册响应
const MSG_ID_LOCATION = 0x0200; // 位置汇报
// 协议版本
const PROTOCOL_VERSION = 0x01;
/**
* 打包消息
*/
public static function packMessage($msgId, $terminalId, $msgBody, $isSubPackage = false, $encryptType = 0) {
// 消息头组装
$header = self::packHeader($msgId, $terminalId, strlen($msgBody), $isSubPackage, $encryptType);
// 合并头和消息体
$message = $header . $msgBody;
// 计算校验码
$checkCode = self::calculateCheckCode($message);
// 添加转义字符(示例简化处理)
return "\x7e" . $message . $checkCode . "\x7e";
}
/**
* 解包消息
*/
public static function unpackMessage($data) {
// 去除首尾的0x7e
$data = substr($data, 1, -1);
// 分割头和消息体
$header = substr($data, 0, 16); // 头部固定16字节
$msgBody = substr($data, 16, -1); // 去掉校验码
// 解析头信息
$headerInfo = self::unpackHeader($header);
return [
'header' => $headerInfo,
'body' => self::unpackBody($msgBody, $headerInfo['msg_id'])
];
}
// 私有方法:打包消息头
private static function packHeader($msgId, $terminalId, $bodyLen, $isSubPackage, $encryptType) {
$msgProps = ($bodyLen & 0x3FFF) | ($encryptType << 10) | ($isSubPackage ? 0x2000 : 0);
return pack('nnH12nn',
$msgId, // 消息ID
$msgProps, // 消息属性
$terminalId, // 终端手机号(BCD编码)
self::getSerialNum(), // 消息流水号
$bodyLen // 包总数(示例简化处理)
);
}
// 私有方法:解析消息头
private static function unpackHeader($header) {
$headerData = unpack('nmsg_id/nmsg_props/H12terminal_id/nserial_num/ntotal_pkgs', $header);
return [
'msg_id' => $headerData['msg_id'],
'body_len' => $headerData['msg_props'] & 0x3FFF,
'encrypt_type' => ($headerData['msg_props'] >> 10) & 0x07,
'is_sub_pkg' => ($headerData['msg_props'] & 0x2000) > 0,
'terminal_id' => $headerData['terminal_id'],
'serial_num' => $headerData['serial_num'],
'total_pkgs' => $headerData['total_pkgs']
];
}
// 私有方法:解析消息体
private static function unpackBody($body, $msgId) {
switch ($msgId) {
case self::MSG_ID_REGISTER:
return self::unpackRegisterBody($body);
case self::MSG_ID_LOCATION:
return self::unpackLocationBody($body);
default:
return $body; // 返回原始二进制数据
}
}
// 解析注册消息体
private static function unpackRegisterBody($body) {
$provinceId = unpack('n', substr($body, 0, 2))[1];
$cityId = unpack('n', substr($body, 2, 2))[1];
$manufacturer = substr($body, 4, 5);
$model = substr($body, 9, 8);
$terminalId = substr($body, 17, 7);
$color = unpack('C', substr($body, 24, 1))[1];
$plateNo = substr($body, 25);
return [
'province_id' => $provinceId,
'city_id' => $cityId,
'manufacturer'=> trim($manufacturer),
'model' => trim($model),
'terminal_id' => trim($terminalId),
'color' => $color,
'plate_no' => trim($plateNo)
];
}
// 解析位置消息体
private static function unpackLocationBody($body) {
$data = unpack('Nalarm/Nstatus/Nlat/Nlon/nspeed/Ndirection/Ntime', $body);
return [
'alarm' => $data['alarm'],
'status' => $data['status'],
'latitude' => $data['lat'] / 1000000,
'longitude' => $data['lon'] / 1000000,
'speed' => $data['speed'] / 10,
'direction' => $data['direction'],
'time' => date('Y-m-d H:i:s', strtotime($data['time']))
];
}
// 生成注册响应消息体
public static function packRegisterResponse($serialNum, $terminalId, $resultCode) {
$body = pack('nnH12', $serialNum, $resultCode, $terminalId);
return self::packMessage(self::MSG_ID_REGISTER_RESP, $terminalId, $body);
}
// 计算校验码(异或校验)
private static function calculateCheckCode($data) {
$check = 0;
for ($i = 0; $i < strlen($data); $i++) {
$check ^= ord($data[$i]);
}
return pack('C', $check);
}
// 获取流水号(示例简单实现)
private static $serialNum = 0;
private static function getSerialNum() {
return ++self::$serialNum % 65536;
}
}
// ================= 使用示例 =================
// 接收到的示例数据(16进制字符串)
$sampleData = "7e010000261234567890123456780001000178313830313233343536373839303132333435367e";
// 转换二进制数据
$binaryData = hex2bin(str_replace(' ', '', $sampleData));
// 解析消息
$parsedMsg = JT808Protocol::unpackMessage($binaryData);
// 处理注册请求
if ($parsedMsg['header']['msg_id'] == JT808Protocol::MSG_ID_REGISTER) {
// 生成响应消息
$response = JT808Protocol::packRegisterResponse(
$parsedMsg['header']['serial_num'],
$parsedMsg['header']['terminal_id'],
0 // 0表示成功
);
// 发送响应(示例输出)
echo "Response HEX: " . bin2hex($response) . "\n";
}
// 打印解析结果
print_r($parsedMsg);
关键功能说明:
-
协议封装:
-
packMessage()
方法用于组装符合JT808协议的消息 -
支持消息头自动生成(包含消息ID、终端号、流水号等)
-
自动计算校验码
-
-
协议解析:
-
unpackMessage()
方法解析接收到的二进制数据 -
支持解析注册消息和位置消息
-
自动处理校验码验证
-
-
数据转换:
-
处理BCD编码的终端号
-
转换经纬度坐标(1/1000000度)
-
转换速度值(1/10 km/h)
-
-
校验处理:
-
使用异或校验算法
-
自动处理转义字符(示例简化处理)
-
典型工作流程:
-
终端发送注册消息(0x0100)
-
服务端解析后返回注册响应(0x8100)
-
终端定期发送位置信息(0x0200)
-
服务端解析位置数据并存储
扩展建议:
-
根据实际需求添加更多消息类型处理
-
完善错误处理机制
-
添加TCP粘包处理逻辑
-
实现完整的分包处理功能
-
添加数据加密支持(根据协议规范)
注意:实际使用时需要根据具体设备厂商的协议扩展进行调整,不同厂商可能会在标准协议基础上进行扩展。