Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # -*- coding: utf-8 -*-
- import argparse
- import binascii
- import configparser
- import socketserver
- import time
- import datetime
- import pymssql
- class AvlParser:
- """ Parse AVL data packets from Teltonika FMXXXX (FM1100, FM2100, FM2200,
- FM4100 and FM4200) devices.
- Based on the protocol v2.10 description.
- """
- BITS = 2
- PRIORITY = {0:"Low",
- 1:"High",
- 2:"Panic",
- 3:"Security"}
- def __init__(self):
- self.rawData = b""
- self.data = b""
- self.p1 = 0
- self.p2 = 0
- def parse(self, rawData):
- self.rawData = rawData
- self.data = binascii.hexlify(self.rawData)
- self.p1 = 0
- self.p2 = 0
- result = dict()
- result["packet"] = self._parsePacketHeader()
- result["data"] = self._parseData()
- return result
- def _parsePacketHeader(self):
- """ Data packet header consists of 4 zero bytes followed by
- 4 bytes with AVL packet length.
- """
- self.p1 = self.p2
- self.p2 += self.BITS * 4
- zeros = self.data[self.p1:self.p2]
- self.p1 = self.p2
- self.p2 += self.BITS * 4
- dataLength = self.data[self.p1:self.p2]
- return {"zeros":str(zeros, "utf8"), "dataLength":int(dataLength, 16)}
- def _parseData(self):
- """Data array starts with 1 byte codec id followed by 1 byte
- records count. Then comes encoded data elements.
- """
- # parse AVL data header
- self.p1 = self.p2
- self.p2 += self.BITS * 1
- codecId = self.data[self.p1:self.p2]
- self.p1 = self.p2
- self.p2 += self.BITS * 1
- recordsCount = self.data[self.p1:self.p2]
- result = dict()
- result["header"] = {"codec":str(codecId, "utf-8"), "recordsCount":int(recordsCount, 16)}
- # parse records
- totalRecords = int(recordsCount, 16)
- records = dict()
- i = 1
- while i <= totalRecords:
- records[i] = self._parseRecord(i)
- i += 1
- result["records"] = records
- # check number of processed records
- self.p1 = self.p2
- self.p2 += self.BITS * 1
- tmp = self.data[self.p1:self.p2]
- if recordsCount != tmp:
- print("Records count mismatch {} != {}".format(recordsCount, tmp))
- return None
- # read CRC
- self.p1 = self.p2
- self.p2 += self.BITS * 8
- crc = self.data[self.p1:self.p2]
- # TODO: check CRC
- return result
- def _parseRecord(self, record):
- """Each data element consists of:
- - 8 bytes timestamp
- - 1 byte priority
- - 15 bytes of GPS data
- - and IO elements
- """
- self.p1 = self.p2
- self.p2 += self.BITS * 8
- gpsTimestamp = self.data[self.p1:self.p2]
- self.p1 = self.p2
- self.p2 += self.BITS * 1
- priority = self.data[self.p1:self.p2]
- result = dict()
- tmp = int(gpsTimestamp, 16) / 1000.0
- result["timestamp"] = {"epoch":tmp,
- "human":time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(tmp))}
- tmp = int(priority, 16)
- result["priority"] = {"code": tmp, "human": self.PRIORITY[tmp]}
- result["gps"] = self._parseGpsInformation()
- # parse sensors block
- self.p1 = self.p2
- self.p2 += self.BITS * 1
- eventId = self.data[self.p1:self.p2]
- self.p1 = self.p2
- self.p2 += self.BITS * 1
- propertiesCount = self.data[self.p1:self.p2]
- sensors = dict()
- sensors["eventId"] = int(eventId, 16)
- sensors["propertiesCount"] = int(propertiesCount, 16)
- properties = dict()
- # process all properties
- self.p1 = self.p2
- for i in (1, 2, 4, 8):
- properties[i] = self._parseSensors(i)
- sensors["properties"] = properties
- result["sensors"] = sensors
- return result
- def _parseGpsInformation(self):
- """GPS element contains in encoded form
- - 4 bytes longitude
- - 4 bytes latitude
- - 2 bytes altitude (meters above sea level)
- - 2 bytes with angle (0 for north, increasing clock-wise)
- - 1 byte with number of visible satellites
- - 2 byte with unit speed in km/h
- """
- self.p1 = self.p2
- self.p2 += self.BITS * 4
- gpsLongitude = self.data[self.p1:self.p2]
- self.p1 = self.p2
- self.p2 += self.BITS * 4
- gpsLatitude = self.data[self.p1:self.p2]
- self.p1 = self.p2
- self.p2 += self.BITS * 2
- gpsAltitude = self.data[self.p1:self.p2]
- self.p1 = self.p2
- self.p2 += self.BITS * 2
- gpsAngle = self.data[self.p1:self.p2]
- self.p1 = self.p2
- self.p2 += self.BITS * 1
- gpsSatellites = self.data[self.p1:self.p2]
- self.p1 = self.p2
- self.p2 += self.BITS * 2
- gpsSpeed = self.data[self.p1:self.p2]
- return {"lon":self._convertCoordinate(gpsLongitude),
- "lat":self._convertCoordinate(gpsLatitude),
- "altitude":int(gpsAltitude, 16),
- "angle":int(gpsAngle, 16),
- "satellites":int(gpsSatellites, 16),
- "speed":int(gpsSpeed, 16)}
- def _convertCoordinate(self, rawData):
- """Convert encoded coordinate into human-readable format
- """
- value = int(rawData, 16)
- # check coordinate sign by examining its first bit. Negative
- # coordinates have first bit equal to 1.
- sign = -1 if bin(value)[2:].zfill(32)[:1] == "1" else 1
- return sign * (value / 10000000.0)
- def _parseSensors(self, bytesCount):
- """Parse data from IO sensors. Properties can be 1, 2, 4 and 8
- bytes lenght.
- For each property lenght we have 1 byte containing number of
- properties with length N bytes. Then we have K pairs of 1 byte
- sensor code followed by N bytes value.
- """
- # first we determine number of key-value pairs of bytesCount
- # data. This is always 1 byte.
- self.p1 = self.p2
- self.p2 += self.BITS * 1
- pairsNumber = int(self.data[self.p1:self.p2], 16)
- result = dict()
- # now read key-value pairs. Keys are always 1 byte
- # data size defined by bytesCount
- i = 1
- while i <= pairsNumber:
- self.p1 = self.p2
- self.p2 += self.BITS * 1
- sensorCode = self.data[self.p1:self.p2]
- self.p1 = self.p2
- self.p2 += self.BITS * bytesCount
- sensorData = self.data[self.p1:self.p2]
- result[int(sensorCode, 16)] = int(sensorData, 16)
- i += 1
- return result
- class GpsClientHandler(socketserver.BaseRequestHandler):
- def handle(self):
- self.conn = pymssql.connect(server=self.server.config["database"]["host"],
- port=self.server.config["database"]["port"],
- user=self.server.config["database"]["user"],
- password=self.server.config["database"]["password"],
- database=self.server.config["database"]["database"])
- print("{} - Incoming connection from {}".format(time.strftime("%d.%m.%Y %H:%M:%S", time.localtime()), self.client_address[0]))
- data = self.request.recv(8192)
- # first packet contains only 2 service bits and client IMEI
- imei = str(data[2:], "ascii")
- print("{} - Checking device IMEI: {}... ".format(time.strftime("%d.%m.%Y %H:%M:%S", time.localtime()), imei), end="")
- if len(imei) == 15:
- exists, idx = self._checkImei(imei)
- if exists:
- # we know this IMEI, request data
- print("OK. Requesting data...")
- self.request.sendall(b"\x01")
- # read data packet from client, parse it and confirm data reception
- data = self.request.recv(8192)
- print("{} - Received data packet from {}:\n{}".format(time.strftime("%d.%m.%Y %H:%M:%S", time.localtime()), imei, data))
- p = AvlParser()
- r = p.parse(data)
- self.request.sendall(bytes(r["data"]["header"]["recordsCount"], "ascii"))
- self._saveToDatabase(imei, r)
- else:
- print("Unknown IMEI.")
- self.request.sendall(b"\x00")
- else:
- print("{} - Invalid packet {}".format(time.strftime("%d.%m.%Y %H:%M:%S", time.localtime()), data))
- self.conn.close()
- self.request.close()
- def _checkImei(self, imei):
- cursor = self.conn.cursor()
- query = "SELECT TOP 1 _Fld400 FROM {} WHERE _Fld424 = %(imei)s;".format(self.server.config["database"]["imei_table"])
- cursor.execute(query, {"imei": imei})
- row = cursor.fetchone()
- result = True if row is not None else False
- cursor.close()
- return result, row[0]
- def _saveToDatabase(self, device, data):
- cursor = self.conn.cursor()
- queryGps = "INSERT INTO {} (_Period, _Fld419, _Fld420, _Fld421, _Fld422, _Fld423) VALUES (%(timest)s, %(device)s, %(lon)s, %(lat)s, %(speed)s, %(angle)s);".format(self.server.config["database"]["gps_table"])
- for i, r in data["data"]["records"].items():
- ts = datetime.datetime.fromtimestamp(r["timestamp"]["epoch"])
- gpsData = {"device": device,
- "timest": ts,
- "lon": r["gps"]["lon"],
- "lat": r["gps"]["lat"],
- "speed": r["gps"]["speed"],
- "angle": r["gps"]["angle"],
- }
- cursor.execute(queryGps, gpsData)
- querySensors = "INSERT INTO {} (_Period, _Fld415, _Fld416, _Fld417) VALUES (%(timest)s, %(device)s, %(sensor)s, %(readings)s);".format(self.server.config["database"]["sensors_table"])
- sensorData = []
- for b, p in sorted(r["sensors"]["properties"].items()):
- for s, w in sorted(r["sensors"]["properties"][b].items()):
- sensorData.append({"timest": ts, "device":device, "sensor": s, "readings": w})
- if len(sensorData) > 0:
- cursor.executemany(querySensors, sensorData)
- self.conn.commit()
- cursor.close()
- class BiTrekTcpServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
- daemon_threads = True
- allow_reuse_address = True
- def __init__(self, serverAddress, RequestHandlerClass, config):
- socketserver.TCPServer.__init__(self, serverAddress, RequestHandlerClass)
- self.config = config
- if __name__ == "__main__":
- parser = argparse.ArgumentParser(description="Simple server for BiTrek GPS trackers.")
- parser.add_argument("-c", "--config", metavar="FILE", help="location of the config file", required=True)
- args = parser.parse_args()
- config = configparser.ConfigParser()
- config.read(args.config)
- host = config["server"]["host"]
- port = int(config["server"]["port"])
- server = BiTrekTcpServer((host, port), GpsClientHandler, config)
- print("BiTrek server started at {}:{}. Press Ctrl+C to stop.". format(host, port))
- try:
- server.serve_forever()
- except KeyboardInterrupt:
- pass
- finally:
- print("\nStopping server... ", end="")
- server.shutdown()
- server.server_close()
- print("OK")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement