Advertisement
Guest User

Untitled

a guest
Mar 14th, 2018
107
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.62 KB | None | 0 0
  1. # -*- coding: utf-8 -*-
  2.  
  3. import argparse
  4. import binascii
  5. import configparser
  6. import socketserver
  7. import time
  8. import datetime
  9.  
  10. import pymssql
  11.  
  12.  
  13. class AvlParser:
  14. """ Parse AVL data packets from Teltonika FMXXXX (FM1100, FM2100, FM2200,
  15. FM4100 and FM4200) devices.
  16. Based on the protocol v2.10 description.
  17. """
  18.  
  19. BITS = 2
  20.  
  21. PRIORITY = {0:"Low",
  22. 1:"High",
  23. 2:"Panic",
  24. 3:"Security"}
  25.  
  26. def __init__(self):
  27. self.rawData = b""
  28. self.data = b""
  29. self.p1 = 0
  30. self.p2 = 0
  31.  
  32. def parse(self, rawData):
  33. self.rawData = rawData
  34. self.data = binascii.hexlify(self.rawData)
  35.  
  36. self.p1 = 0
  37. self.p2 = 0
  38. result = dict()
  39.  
  40. result["packet"] = self._parsePacketHeader()
  41. result["data"] = self._parseData()
  42.  
  43. return result
  44.  
  45. def _parsePacketHeader(self):
  46. """ Data packet header consists of 4 zero bytes followed by
  47. 4 bytes with AVL packet length.
  48. """
  49. self.p1 = self.p2
  50. self.p2 += self.BITS * 4
  51. zeros = self.data[self.p1:self.p2]
  52.  
  53. self.p1 = self.p2
  54. self.p2 += self.BITS * 4
  55. dataLength = self.data[self.p1:self.p2]
  56.  
  57. return {"zeros":str(zeros, "utf8"), "dataLength":int(dataLength, 16)}
  58.  
  59. def _parseData(self):
  60. """Data array starts with 1 byte codec id followed by 1 byte
  61. records count. Then comes encoded data elements.
  62. """
  63. # parse AVL data header
  64. self.p1 = self.p2
  65. self.p2 += self.BITS * 1
  66. codecId = self.data[self.p1:self.p2]
  67.  
  68. self.p1 = self.p2
  69. self.p2 += self.BITS * 1
  70. recordsCount = self.data[self.p1:self.p2]
  71.  
  72. result = dict()
  73. result["header"] = {"codec":str(codecId, "utf-8"), "recordsCount":int(recordsCount, 16)}
  74.  
  75. # parse records
  76. totalRecords = int(recordsCount, 16)
  77. records = dict()
  78. i = 1
  79. while i <= totalRecords:
  80. records[i] = self._parseRecord(i)
  81. i += 1
  82.  
  83. result["records"] = records
  84.  
  85. # check number of processed records
  86. self.p1 = self.p2
  87. self.p2 += self.BITS * 1
  88. tmp = self.data[self.p1:self.p2]
  89. if recordsCount != tmp:
  90. print("Records count mismatch {} != {}".format(recordsCount, tmp))
  91. return None
  92.  
  93. # read CRC
  94. self.p1 = self.p2
  95. self.p2 += self.BITS * 8
  96. crc = self.data[self.p1:self.p2]
  97. # TODO: check CRC
  98.  
  99. return result
  100.  
  101. def _parseRecord(self, record):
  102. """Each data element consists of:
  103. - 8 bytes timestamp
  104. - 1 byte priority
  105. - 15 bytes of GPS data
  106. - and IO elements
  107. """
  108. self.p1 = self.p2
  109. self.p2 += self.BITS * 8
  110. gpsTimestamp = self.data[self.p1:self.p2]
  111.  
  112. self.p1 = self.p2
  113. self.p2 += self.BITS * 1
  114. priority = self.data[self.p1:self.p2]
  115.  
  116. result = dict()
  117. tmp = int(gpsTimestamp, 16) / 1000.0
  118. result["timestamp"] = {"epoch":tmp,
  119. "human":time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(tmp))}
  120.  
  121. tmp = int(priority, 16)
  122. result["priority"] = {"code": tmp, "human": self.PRIORITY[tmp]}
  123.  
  124. result["gps"] = self._parseGpsInformation()
  125.  
  126. # parse sensors block
  127. self.p1 = self.p2
  128. self.p2 += self.BITS * 1
  129. eventId = self.data[self.p1:self.p2]
  130.  
  131. self.p1 = self.p2
  132. self.p2 += self.BITS * 1
  133. propertiesCount = self.data[self.p1:self.p2]
  134.  
  135. sensors = dict()
  136. sensors["eventId"] = int(eventId, 16)
  137. sensors["propertiesCount"] = int(propertiesCount, 16)
  138.  
  139. properties = dict()
  140. # process all properties
  141. self.p1 = self.p2
  142. for i in (1, 2, 4, 8):
  143. properties[i] = self._parseSensors(i)
  144.  
  145. sensors["properties"] = properties
  146. result["sensors"] = sensors
  147.  
  148. return result
  149.  
  150. def _parseGpsInformation(self):
  151. """GPS element contains in encoded form
  152. - 4 bytes longitude
  153. - 4 bytes latitude
  154. - 2 bytes altitude (meters above sea level)
  155. - 2 bytes with angle (0 for north, increasing clock-wise)
  156. - 1 byte with number of visible satellites
  157. - 2 byte with unit speed in km/h
  158. """
  159. self.p1 = self.p2
  160. self.p2 += self.BITS * 4
  161. gpsLongitude = self.data[self.p1:self.p2]
  162.  
  163. self.p1 = self.p2
  164. self.p2 += self.BITS * 4
  165. gpsLatitude = self.data[self.p1:self.p2]
  166.  
  167. self.p1 = self.p2
  168. self.p2 += self.BITS * 2
  169. gpsAltitude = self.data[self.p1:self.p2]
  170.  
  171. self.p1 = self.p2
  172. self.p2 += self.BITS * 2
  173. gpsAngle = self.data[self.p1:self.p2]
  174.  
  175. self.p1 = self.p2
  176. self.p2 += self.BITS * 1
  177. gpsSatellites = self.data[self.p1:self.p2]
  178.  
  179. self.p1 = self.p2
  180. self.p2 += self.BITS * 2
  181. gpsSpeed = self.data[self.p1:self.p2]
  182.  
  183. return {"lon":self._convertCoordinate(gpsLongitude),
  184. "lat":self._convertCoordinate(gpsLatitude),
  185. "altitude":int(gpsAltitude, 16),
  186. "angle":int(gpsAngle, 16),
  187. "satellites":int(gpsSatellites, 16),
  188. "speed":int(gpsSpeed, 16)}
  189.  
  190. def _convertCoordinate(self, rawData):
  191. """Convert encoded coordinate into human-readable format
  192. """
  193. value = int(rawData, 16)
  194. # check coordinate sign by examining its first bit. Negative
  195. # coordinates have first bit equal to 1.
  196. sign = -1 if bin(value)[2:].zfill(32)[:1] == "1" else 1
  197.  
  198. return sign * (value / 10000000.0)
  199.  
  200. def _parseSensors(self, bytesCount):
  201. """Parse data from IO sensors. Properties can be 1, 2, 4 and 8
  202. bytes lenght.
  203.  
  204. For each property lenght we have 1 byte containing number of
  205. properties with length N bytes. Then we have K pairs of 1 byte
  206. sensor code followed by N bytes value.
  207. """
  208. # first we determine number of key-value pairs of bytesCount
  209. # data. This is always 1 byte.
  210. self.p1 = self.p2
  211. self.p2 += self.BITS * 1
  212. pairsNumber = int(self.data[self.p1:self.p2], 16)
  213.  
  214. result = dict()
  215. # now read key-value pairs. Keys are always 1 byte
  216. # data size defined by bytesCount
  217. i = 1
  218. while i <= pairsNumber:
  219. self.p1 = self.p2
  220. self.p2 += self.BITS * 1
  221. sensorCode = self.data[self.p1:self.p2]
  222.  
  223. self.p1 = self.p2
  224. self.p2 += self.BITS * bytesCount
  225. sensorData = self.data[self.p1:self.p2]
  226. result[int(sensorCode, 16)] = int(sensorData, 16)
  227. i += 1
  228.  
  229. return result
  230.  
  231.  
  232. class GpsClientHandler(socketserver.BaseRequestHandler):
  233.  
  234. def handle(self):
  235. self.conn = pymssql.connect(server=self.server.config["database"]["host"],
  236. port=self.server.config["database"]["port"],
  237. user=self.server.config["database"]["user"],
  238. password=self.server.config["database"]["password"],
  239. database=self.server.config["database"]["database"])
  240.  
  241. print("{} - Incoming connection from {}".format(time.strftime("%d.%m.%Y %H:%M:%S", time.localtime()), self.client_address[0]))
  242. data = self.request.recv(8192)
  243.  
  244. # first packet contains only 2 service bits and client IMEI
  245. imei = str(data[2:], "ascii")
  246. print("{} - Checking device IMEI: {}... ".format(time.strftime("%d.%m.%Y %H:%M:%S", time.localtime()), imei), end="")
  247. if len(imei) == 15:
  248. exists, idx = self._checkImei(imei)
  249. if exists:
  250. # we know this IMEI, request data
  251. print("OK. Requesting data...")
  252. self.request.sendall(b"\x01")
  253.  
  254. # read data packet from client, parse it and confirm data reception
  255. data = self.request.recv(8192)
  256. print("{} - Received data packet from {}:\n{}".format(time.strftime("%d.%m.%Y %H:%M:%S", time.localtime()), imei, data))
  257. p = AvlParser()
  258. r = p.parse(data)
  259. self.request.sendall(bytes(r["data"]["header"]["recordsCount"], "ascii"))
  260.  
  261. self._saveToDatabase(imei, r)
  262. else:
  263. print("Unknown IMEI.")
  264. self.request.sendall(b"\x00")
  265. else:
  266. print("{} - Invalid packet {}".format(time.strftime("%d.%m.%Y %H:%M:%S", time.localtime()), data))
  267.  
  268. self.conn.close()
  269.  
  270. self.request.close()
  271.  
  272. def _checkImei(self, imei):
  273. cursor = self.conn.cursor()
  274.  
  275. query = "SELECT TOP 1 _Fld400 FROM {} WHERE _Fld424 = %(imei)s;".format(self.server.config["database"]["imei_table"])
  276. cursor.execute(query, {"imei": imei})
  277. row = cursor.fetchone()
  278. result = True if row is not None else False
  279.  
  280. cursor.close()
  281.  
  282. return result, row[0]
  283.  
  284. def _saveToDatabase(self, device, data):
  285. cursor = self.conn.cursor()
  286.  
  287. 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"])
  288.  
  289. for i, r in data["data"]["records"].items():
  290. ts = datetime.datetime.fromtimestamp(r["timestamp"]["epoch"])
  291. gpsData = {"device": device,
  292. "timest": ts,
  293. "lon": r["gps"]["lon"],
  294. "lat": r["gps"]["lat"],
  295. "speed": r["gps"]["speed"],
  296. "angle": r["gps"]["angle"],
  297. }
  298. cursor.execute(queryGps, gpsData)
  299.  
  300. querySensors = "INSERT INTO {} (_Period, _Fld415, _Fld416, _Fld417) VALUES (%(timest)s, %(device)s, %(sensor)s, %(readings)s);".format(self.server.config["database"]["sensors_table"])
  301.  
  302. sensorData = []
  303. for b, p in sorted(r["sensors"]["properties"].items()):
  304. for s, w in sorted(r["sensors"]["properties"][b].items()):
  305. sensorData.append({"timest": ts, "device":device, "sensor": s, "readings": w})
  306.  
  307. if len(sensorData) > 0:
  308. cursor.executemany(querySensors, sensorData)
  309.  
  310. self.conn.commit()
  311. cursor.close()
  312.  
  313.  
  314. class BiTrekTcpServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
  315.  
  316. daemon_threads = True
  317. allow_reuse_address = True
  318.  
  319. def __init__(self, serverAddress, RequestHandlerClass, config):
  320. socketserver.TCPServer.__init__(self, serverAddress, RequestHandlerClass)
  321. self.config = config
  322.  
  323.  
  324. if __name__ == "__main__":
  325. parser = argparse.ArgumentParser(description="Simple server for BiTrek GPS trackers.")
  326. parser.add_argument("-c", "--config", metavar="FILE", help="location of the config file", required=True)
  327. args = parser.parse_args()
  328.  
  329. config = configparser.ConfigParser()
  330. config.read(args.config)
  331.  
  332. host = config["server"]["host"]
  333. port = int(config["server"]["port"])
  334.  
  335. server = BiTrekTcpServer((host, port), GpsClientHandler, config)
  336. print("BiTrek server started at {}:{}. Press Ctrl+C to stop.". format(host, port))
  337. try:
  338. server.serve_forever()
  339. except KeyboardInterrupt:
  340. pass
  341. finally:
  342. print("\nStopping server... ", end="")
  343. server.shutdown()
  344. server.server_close()
  345. print("OK")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement