|
|
|
/*
|
|
|
|
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_HOUR = -1
|
|
|
|
)
|
|
|
|
|
|
|
|
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.Hours()*100)/100
|
|
|
|
if hCurr >= ALLOWED_TIME_HOUR {
|
|
|
|
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
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
}
|