Advertisement
Guest User

Untitled

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