You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
468 lines
13 KiB
468 lines
13 KiB
/* |
|
Package for parsing devicePrs byte stream to struct. |
|
*/ |
|
|
|
package parser |
|
|
|
import ( |
|
"bytes" |
|
"encoding/binary" |
|
"fmt" |
|
"time" |
|
"math" |
|
//"encoding/hex" |
|
"strconv" |
|
"sort" |
|
"github.com/ipsusila/opt" |
|
|
|
"../model" |
|
) |
|
|
|
const ( |
|
ALLOWED_TIME_MINUTES = -5 |
|
) |
|
|
|
type ruptelaTchHeader struct { |
|
Length uint16 |
|
IMEI uint64 |
|
CommandID byte |
|
PortID byte |
|
Reserved uint16 |
|
Timestamp uint32 |
|
} |
|
|
|
type DeviceRecordHeader struct { |
|
Imei uint64 |
|
Tstamp time.Time |
|
NumRec uint16 |
|
DeviceRecords []DeviceRecord |
|
} |
|
|
|
type DeviceRecord struct { |
|
Tstamp time.Time |
|
TstampInt int64 |
|
Imei uint64 |
|
Longitude float64 |
|
Latitude float64 |
|
Altitude uint16 |
|
Angle uint16 |
|
Satelites uint16 |
|
Speed uint16 |
|
TagDevices map[string]TagDevice |
|
} |
|
|
|
type TagDevice struct { |
|
TagName string |
|
TagId string |
|
TagDataType string |
|
TagVal string |
|
} |
|
|
|
// ByTstamp implements sort.Interface for []DeviceRecord based on |
|
// the TstampInt field. |
|
type ByTstamp []DeviceRecord |
|
|
|
func (a ByTstamp) Len() int { return len(a) } |
|
func (a ByTstamp) Swap(i, j int) { a[i], a[j] = a[j], a[i] } |
|
func (a ByTstamp) Less(i, j int) bool { return a[i].TstampInt < a[j].TstampInt } |
|
|
|
/* |
|
Verify that content of the byte stream match defined format |
|
*/ |
|
func verifyStream(data []byte, minSize int, maxSize int, tagOption *opt.Options) (imei uint64, cmd byte, rec DeviceRecordHeader, err error) { |
|
dlen := len(data) |
|
if dlen < minSize { |
|
return 0, 0,rec, fmt.Errorf("Packet size is too small (< %d)", dlen) |
|
} else if dlen > maxSize { |
|
return 0, 0,rec, fmt.Errorf("Packet size is greater than maximum allowed size (%d)", dlen) |
|
} |
|
|
|
//Extract packet size |
|
sz := int(binary.BigEndian.Uint16(data[:2])) |
|
if sz != (dlen - 4) { |
|
return 0, 0,rec, fmt.Errorf("Size field mismatched (!%d)", dlen) |
|
} |
|
|
|
//extract IMEI |
|
imei = binary.BigEndian.Uint64(data[2:10]) |
|
|
|
//extract and verify CRC |
|
crc := binary.BigEndian.Uint16(data[dlen-2:]) |
|
ccrc := crc16CCITT(data[2 : dlen-2]) |
|
if crc != ccrc { |
|
return 0, 0,rec, fmt.Errorf("Invalid Ruptela packet (CRC-16 does not match)") |
|
} |
|
|
|
//parse data |
|
rec = parseData(data,imei,tagOption) |
|
|
|
//extract command |
|
cmd = data[10] |
|
|
|
fmt.Printf("cmd %v\n",cmd) |
|
|
|
return imei, cmd, rec, nil |
|
} |
|
|
|
/* |
|
Parse byte stream and convert to standard record format |
|
*/ |
|
func parseData(data []byte, imei uint64, tagOption *opt.Options) (rec DeviceRecordHeader) { |
|
//allocate records |
|
rec.Tstamp = time.Now() |
|
rec.Imei = imei |
|
|
|
tagOption_ := tagOption.GetObjectArray("tags") |
|
lengthTags := len(tagOption_) |
|
tags := make(map[string]TagDevice) |
|
for i:=0; i<lengthTags; i++ { |
|
var tag TagDevice |
|
tag.TagName = tagOption_[i].GetString("tagName","") |
|
tag.TagId = tagOption_[i].GetString("tagId","") |
|
tag.TagDataType = tagOption_[i].GetString("tagDataType","") |
|
tag.TagVal = "" |
|
|
|
tags[tag.TagId] = tag |
|
} |
|
|
|
//number of records |
|
currByte := 12 |
|
plusByte := currByte + 1 |
|
numRec := convBinaryToUint16(addOneByteInTwoByte(data[currByte:plusByte]),2,"numRec") |
|
//fmt.Printf("Number of records %d\n", numRec) |
|
rec.NumRec = numRec |
|
deviceRecords := make([]DeviceRecord, 0) |
|
for j := 0; j < int(numRec); j++ { |
|
var deviceRecord DeviceRecord |
|
tags_ := tags |
|
|
|
//imei |
|
deviceRecord.Imei = imei |
|
|
|
//Timestamp |
|
currByte = plusByte |
|
plusByte = currByte + 4 |
|
tstampInt := convBinaryToInt32(data[currByte:plusByte],8,"tstampInt") |
|
tstamp := time.Unix(int64(tstampInt), 0) |
|
//fmt.Printf("Timestamp %v\n",tstamp.String()) |
|
deviceRecord.TstampInt = int64(tstampInt) |
|
deviceRecord.Tstamp = tstamp |
|
|
|
//Priority |
|
currByte = plusByte |
|
plusByte = currByte + 2 |
|
|
|
//Longitude |
|
currByte = plusByte |
|
plusByte = currByte + 4 |
|
lonInt := convBinaryToInt32(data[currByte:plusByte],4,"lonInt") |
|
lon :=float64(lonInt)/10000000 |
|
//fmt.Printf("lon %f\n",lon) |
|
deviceRecord.Longitude = lon |
|
|
|
//Latitude |
|
currByte = plusByte |
|
plusByte = currByte + 4 |
|
latInt := convBinaryToInt32(data[currByte:plusByte],4,"latInt") |
|
lat :=float64(latInt)/10000000 |
|
//fmt.Printf("lat %f\n",lat) |
|
deviceRecord.Latitude = lat |
|
|
|
//Altitude |
|
currByte = plusByte |
|
plusByte = currByte + 2 |
|
alt := convBinaryToUint16(data[currByte:plusByte],2,"alt") |
|
//fmt.Printf("alt %d\n",alt) |
|
deviceRecord.Altitude = alt/10 |
|
|
|
//Angle |
|
currByte = plusByte |
|
plusByte = currByte + 2 |
|
angle := convBinaryToUint16(data[currByte:plusByte],2,"angle") |
|
//fmt.Printf("angle %d\n",angle) |
|
deviceRecord.Angle = angle/100 |
|
|
|
//Satelite |
|
currByte = plusByte |
|
plusByte = currByte + 1 |
|
satelites := convBinaryToUint16(addOneByteInTwoByte(data[currByte:plusByte]),2,"satelites") |
|
//fmt.Printf("satelites %d\n",satelites) |
|
deviceRecord.Satelites = satelites |
|
|
|
//Speed |
|
currByte = plusByte |
|
plusByte = currByte + 2 |
|
speed := convBinaryToUint16(data[currByte:plusByte],2,"speed") |
|
//fmt.Printf("speed %d\n",speed) |
|
deviceRecord.Speed = speed |
|
|
|
//HDOP - 09 (HEX) 9 (DEC) need to be divided by 10 |
|
currByte = plusByte |
|
plusByte = currByte + 1 |
|
|
|
//IO data cause record - 09 (HEX) 9 (DEC) |
|
currByte = plusByte |
|
plusByte = currByte + 1 |
|
|
|
//-------------------------------------------------------------------- |
|
//read 1 Byte I/O values |
|
//-------------------------------------------------------------------- |
|
//Total IO elements, which length is 1 Byte |
|
currByte = plusByte |
|
plusByte = currByte + 1 |
|
totalOneByteIO := convBinaryToUint16(addOneByteInTwoByte(data[currByte:plusByte]),2,"totalOneByteIO") |
|
//fmt.Printf("total IO1 %d\n",totalOneByteIO) |
|
for i := 0; i < int(totalOneByteIO); i++ { |
|
|
|
currByte = plusByte |
|
plusByte = currByte + 1 |
|
ioID := convBinaryToUint16(addOneByteInTwoByte(data[currByte:plusByte]),2,"io1ID") |
|
//fmt.Printf("io1ID %d\n",ioID) |
|
|
|
currByte = plusByte |
|
plusByte = currByte + 1 |
|
ioVal := convBinaryToUint16(addOneByteInTwoByte(data[currByte:plusByte]),2,"io1Val") |
|
//fmt.Printf("io1Val %d\n",ioVal) |
|
|
|
tagDevice_ := tags_[strconv.Itoa(int(ioID))] |
|
tagDevice_.TagId = strconv.Itoa(int(ioID)) |
|
tagDevice_.TagVal = strconv.Itoa(int(ioVal)) |
|
if tagDevice_.TagName != ""{ |
|
//tags_[strconv.Itoa(int(ioID))] = tagDevice_ |
|
tags_[tagDevice_.TagName] = tagDevice_ |
|
delete(tags_, strconv.Itoa(int(ioID))) |
|
} |
|
|
|
} |
|
|
|
//-------------------------------------------------------------------- |
|
//read 2 Byte I/O values |
|
//-------------------------------------------------------------------- |
|
//Total IO elements, which length is 2 Byte |
|
currByte = plusByte |
|
plusByte = currByte + 1 |
|
totalTwoByteIO := convBinaryToUint16(addOneByteInTwoByte(data[currByte:plusByte]),2,"totalTwoByteIO") |
|
//fmt.Printf("total IO2 %d\n",totalTwoByteIO) |
|
for i := 0; i < int(totalTwoByteIO); i++ { |
|
|
|
currByte = plusByte |
|
plusByte = currByte + 1 |
|
ioID := convBinaryToUint16(addOneByteInTwoByte(data[currByte:plusByte]),2,"io2ID") |
|
//fmt.Printf("io2ID %d\n",ioID) |
|
|
|
currByte = plusByte |
|
plusByte = currByte + 2 |
|
ioVal := convBinaryToUint16(data[currByte:plusByte],2,"io2Val") |
|
//fmt.Printf("io2Val %d\n",ioVal) |
|
|
|
tagDevice_ := tags_[strconv.Itoa(int(ioID))] |
|
tagDevice_.TagVal = strconv.Itoa(int(ioVal)) |
|
if tagDevice_.TagName != ""{ |
|
tags_[tagDevice_.TagName] = tagDevice_ |
|
} |
|
} |
|
|
|
//-------------------------------------------------------------------- |
|
//read 4 Byte I/O values |
|
//-------------------------------------------------------------------- |
|
//Total IO elements, which length is 4 Byte |
|
currByte = plusByte |
|
plusByte = currByte + 1 |
|
totalFourByteIO := convBinaryToUint16(addOneByteInTwoByte(data[currByte:plusByte]),2,"totalFourByteIO") |
|
//fmt.Printf("total IO4 %d\n",totalFourByteIO) |
|
for i := 0; i < int(totalFourByteIO); i++ { |
|
|
|
currByte = plusByte |
|
plusByte = currByte + 1 |
|
ioID := convBinaryToUint16(addOneByteInTwoByte(data[currByte:plusByte]),2,"io4ID") |
|
//fmt.Printf("io4ID %d\n",ioID) |
|
|
|
currByte = plusByte |
|
plusByte = currByte + 4 |
|
ioVal := convBinaryToInt32(data[currByte:plusByte],4,"io4Val") |
|
//fmt.Printf("io4Val %d\n",ioVal) |
|
|
|
tagDevice_ := tags_[strconv.Itoa(int(ioID))] |
|
tagDevice_.TagVal = strconv.FormatInt(int64(ioVal), 10) |
|
if tagDevice_.TagName != ""{ |
|
tags_[tagDevice_.TagName] = tagDevice_ |
|
} |
|
} |
|
|
|
//-------------------------------------------------------------------- |
|
//read 8 Byte I/O values |
|
//-------------------------------------------------------------------- |
|
//Total IO elements, which length is 8 Byte |
|
currByte = plusByte |
|
plusByte = currByte + 1 |
|
total8ByteIO := convBinaryToUint16(addOneByteInTwoByte(data[currByte:plusByte]),2,"total8ByteIO") |
|
//fmt.Printf("total IO8 %d\n",total8ByteIO) |
|
for i := 0; i < int(total8ByteIO); i++ { |
|
|
|
currByte = plusByte |
|
plusByte = currByte + 1 |
|
ioID := convBinaryToUint16(addOneByteInTwoByte(data[currByte:plusByte]),2,"io8ID") |
|
//fmt.Printf("io8ID %d\n",ioID) |
|
|
|
currByte = plusByte |
|
plusByte = currByte + 8 |
|
ioVal := convBinaryToInt64(data[currByte:plusByte],8,"io8Val") |
|
//fmt.Printf("io8Val %d\n",ioVal) |
|
|
|
tagDevice_ := tags_[strconv.Itoa(int(ioID))] |
|
tagDevice_.TagVal = strconv.FormatInt(int64(ioVal), 10) |
|
if tagDevice_.TagName != ""{ |
|
tags_[tagDevice_.TagName] = tagDevice_ |
|
} |
|
} |
|
|
|
//Delete unset tags value |
|
for tagKey := range tags { |
|
tagDevice_ := tags_[tagKey] |
|
if tagDevice_.TagId == tagKey { |
|
delete(tags_, tagKey) |
|
} |
|
} |
|
|
|
deviceRecord.TagDevices = tags_ |
|
|
|
tstampToNow := time.Since(deviceRecord.Tstamp) |
|
hCurr := math.Round(tstampToNow.Minutes()*100)/100 |
|
if hCurr >= ALLOWED_TIME_MINUTES { |
|
deviceRecords = append(deviceRecords, deviceRecord) |
|
} |
|
|
|
|
|
} |
|
|
|
rec.NumRec = uint16(len(deviceRecords)) |
|
|
|
//sort ascending by date |
|
sort.Sort(ByTstamp(deviceRecords)) |
|
|
|
rec.DeviceRecords = deviceRecords |
|
|
|
//CRC-16 |
|
currByte = plusByte |
|
plusByte = currByte + 2 |
|
|
|
return rec |
|
|
|
} |
|
|
|
/* |
|
Convert Binary to uint16 |
|
*/ |
|
func addOneByteInTwoByte(data []byte) (resultData []byte){ |
|
|
|
dataNew := make([]byte, 2) |
|
dataNew[0] = 0x0 |
|
copy(dataNew[1:], data) |
|
|
|
return dataNew |
|
} |
|
|
|
/* |
|
Convert Binary to uint16 |
|
*/ |
|
func convBinaryToUint16(data []byte, byteLength int, desc string) (reslt uint16){ |
|
var dataInt uint16 |
|
dataNew := make([]byte, byteLength) |
|
copy(dataNew[0:], data) |
|
buf1 := bytes.NewReader(dataNew) |
|
err := binary.Read(buf1, binary.BigEndian, &dataInt) |
|
if err != nil { |
|
fmt.Printf("binary.Read failed %v :%v\n", desc, err) |
|
dataInt = 0 |
|
} |
|
return dataInt |
|
} |
|
|
|
/* |
|
Convert Binary to int32 |
|
*/ |
|
func convBinaryToInt32(data []byte, byteLength int, desc string) (reslt int32){ |
|
var dataInt int32 |
|
dataNew := make([]byte, byteLength) |
|
copy(dataNew[0:], data) |
|
buf1 := bytes.NewReader(dataNew) |
|
err := binary.Read(buf1, binary.BigEndian, &dataInt) |
|
if err != nil { |
|
fmt.Printf("binary.Read failed %v :%v\n", desc, err) |
|
dataInt = 0 |
|
} |
|
return dataInt |
|
} |
|
|
|
/* |
|
Convert Binary to int64 |
|
*/ |
|
func convBinaryToInt64(data []byte, byteLength int, desc string) (reslt int64){ |
|
var dataInt int64 |
|
dataNew := make([]byte, byteLength) |
|
copy(dataNew[0:], data) |
|
buf1 := bytes.NewReader(dataNew) |
|
err := binary.Read(buf1, binary.BigEndian, &dataInt) |
|
if err != nil { |
|
fmt.Printf("binary.Read failed %v :%v\n", desc, err) |
|
dataInt = 0 |
|
} |
|
return dataInt |
|
} |
|
|
|
func parseTchStream(data []byte, minSize int, maxSize int) (*model.DisplayRequest, error) { |
|
rec := &model.DisplayRequest{} |
|
|
|
//check length |
|
dataLen := len(data) |
|
if dataLen < minSize { |
|
return rec, fmt.Errorf("Packet size is too small (< %d)", dataLen) |
|
} |
|
if dataLen > maxSize { |
|
return rec, fmt.Errorf("Packet size is greater than maximum allowed size (%d)", dataLen) |
|
} |
|
|
|
//create buffer |
|
buf := bytes.NewBuffer(data) |
|
|
|
//read packet header part |
|
pktHdr := &ruptelaTchHeader{} |
|
err := binary.Read(buf, binary.BigEndian, pktHdr) |
|
if err != nil { |
|
return rec, fmt.Errorf("Failed to read packet header (%v)", err) |
|
} |
|
//2+8+1+1+2+4 (len, imei, cmd, port, reserved, timestamp) |
|
idx := 2 + 8 + 1 + 1 + 2 + 4 |
|
|
|
rec.IMEI = pktHdr.IMEI |
|
rec.PortID = pktHdr.PortID |
|
rec.Timestamp = time.Unix(int64(pktHdr.Timestamp), 0) |
|
rec.Data = data[idx:(dataLen - 2)] |
|
|
|
return rec, nil |
|
} |
|
|
|
// Calculate CRC16 with CCITT algorithm |
|
// Based on C/C++ code in DevicePrs protocol documents |
|
// CRC calculation omits Length(2-bytes) and CRC itself (2-bytes) |
|
func crc16CCITT(data []byte) uint16 { |
|
//-------------------------------------------------------------------- |
|
var ucBit, ucCarry uint16 |
|
//-------------------------------------------------------------------- |
|
var usPoly uint16 = 0x8408 //reversed 0x1021 |
|
var usCRC uint16 //initialized as zero |
|
//-------------------------------------------------------------------- |
|
for _, d := range data { |
|
usCRC ^= uint16(d) |
|
for ucBit = 0; ucBit < 8; ucBit++ { |
|
ucCarry = usCRC & 1 |
|
usCRC >>= 1 |
|
if ucCarry != 0 { |
|
usCRC ^= usPoly |
|
} |
|
} |
|
} |
|
//-------------------------------------------------------------------- |
|
return usCRC |
|
//-------------------------------------------------------------------- |
|
}
|
|
|