├── README.md └── GPS.py /README.md: -------------------------------------------------------------------------------- 1 | # zx303-GPS 2 | change zx303 server ip with sms server#YOURIPADDRESS#PORT# or domain#YOURIPADDRESS#PORT# to gps device 3 | 4 | compelete your database informations for connection and tcp server ip, change your mapbox geocoding api 5 | run it and enjoy. 6 | -------------------------------------------------------------------------------- /GPS.py: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | 3 | from dotenv import load_dotenv 4 | from socket import AF_INET, socket, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR 5 | from threading import Thread 6 | from datetime import datetime 7 | from dateutil import tz 8 | import googlemaps 9 | import math 10 | import os 11 | import requests 12 | import json 13 | import psycopg2 14 | 15 | #TODO 16 | def logdb(imei, protocol_name, terminal_server, server_terminal, ip): 17 | connection = psycopg2.connect(user=, 18 | password=, 19 | host=, 20 | port=, 21 | database=) 22 | cursor = connection.cursor() 23 | cursor.execute(f"""INSERT INTO public.log( 24 | imei, protocol_name, terminal_server, server_terminal, time_st, ip) 25 | VALUES ('{imei}', '{protocol_name}', '{terminal_server}', '{server_terminal}','{str(datetime.now())}', '{ip}');""") 26 | connection.commit() 27 | connection.close() 28 | 29 | def accept_incoming_connections(): 30 | """ 31 | Accepts any incoming client connexion 32 | and starts a dedicated thread for each client. 33 | """ 34 | 35 | while True: 36 | client, client_address = SERVER.accept() 37 | print('%s:%s has connected.' % client_address) 38 | 39 | # Initialize the dictionaries 40 | addresses[client] = {} 41 | status[client] = {} 42 | positions[client] = {} 43 | 44 | # Add current client address into adresses 45 | addresses[client]['address'] = client_address 46 | logdb('null', 'null', 'null', 'null', addresses[client]['address'][0]) 47 | 48 | Thread(target=handle_client, args=(client,)).start() 49 | 50 | def LOGGER(event, filename, ip, client, type, data): 51 | """ 52 | A logging function to store all input packets, 53 | as well as output ones when they are generated. 54 | 55 | There are two types of logs implemented: 56 | - a general (info) logger that will keep track of all 57 | incoming and outgoing packets, 58 | - a position (location) logger that will write to a 59 | file contianing only results og GPS or LBS data. 60 | """ 61 | 62 | with open(os.path.join('./logs/', filename), 'a+') as log: 63 | if (event == 'info'): 64 | # TSV format of: Timestamp, Client IP, IN/OUT, Packet 65 | logMessage = datetime.now().strftime('%Y/%m/%d %H:%M:%S') + '\t' + ip + '\t' + client + '\t' + type + '\t' + data + '\n' 66 | elif (event == 'location'): 67 | # TSV format of: Timestamp, Client IP, Location DateTime, GPS/LBS, Validity, Nb Sat, Latitude, Longitude, Accuracy, Speed, Heading 68 | logMessage = datetime.now().strftime('%Y/%m/%d %H:%M:%S') + '\t' + ip + '\t' + client + '\t' + '\t'.join(list(str(x) for x in data.values())) + '\n' 69 | log.write(logMessage) 70 | 71 | 72 | def handle_client(client): 73 | """ 74 | Takes client socket as argument. 75 | Handles a single client connection, by listening indefinitely for packets. 76 | """ 77 | 78 | # Initialize dictionaries for that client 79 | positions[client]['wifi'] = [] 80 | positions[client]['gsm-cells'] = [] 81 | positions[client]['gsm-carrier'] = {} 82 | positions[client]['gps'] = {} 83 | 84 | # Keep receiving and analyzing packets until end of time 85 | # or until device sends disconnection signal 86 | keepAlive = True 87 | while (True): 88 | 89 | # Handle socket errors with a try/except approach 90 | try: 91 | packet = client.recv(BUFSIZ) 92 | print("pkpkpk :::::::::",packet) 93 | # Only process non-empty packets 94 | if (len(packet) > 0): 95 | print('[', addresses[client]['address'][0], ']', 'IN Hex :', packet.hex(), '(length in bytes =', len(packet), ')') 96 | keepAlive = read_incoming_packet(client, packet) 97 | LOGGER('info', 'server_log.txt', addresses[client]['address'][0], addresses[client]['imei'], 'IN', packet.hex()) 98 | logdb(addresses[client]['imei'], 'null', packet.hex(), 'null',addresses[client]['address'][0]) 99 | # Disconnect if client sent disconnect signal 100 | #if (keepAlive is False): 101 | # print('[', addresses[client]['address'][0], ']', 'DISCONNECTED: socket was closed by client.') 102 | # client.close() 103 | # break 104 | 105 | # Close socket if recv() returns 0 bytes, i.e. connection has been closed 106 | else: 107 | 108 | logdb(addresses[client]['imei'], 'null', 'DISCONNECTED', 'DISCONNECTED',addresses[client]['address'][0]) 109 | print('[', addresses[client]['address'][0], ']', 'DISCONNECTED: socket was closed for an unknown reason.') 110 | client.close() 111 | break 112 | 113 | # Something went sideways... close the socket so that it does not hang 114 | except Exception as e: 115 | logdb(addresses[client]['imei'], 'null', 'DISCONNECTED', 'DISCONNECTED',addresses[client]['address'][0]) 116 | print('[', addresses[client]['address'][0], ']', 'ERROR: socket was closed due to the following exception:') 117 | print(e) 118 | client.close() 119 | break 120 | print("This thread is now closed.") 121 | 122 | 123 | def read_incoming_packet(client, packet): 124 | """ 125 | Handle incoming packets to identify the protocol they are related to, 126 | and then redirects to response functions that will generate the apropriate 127 | packet that should be sent back. 128 | Actual sending of the response packet will be done by an external function. 129 | """ 130 | 131 | print("packet :::::::::::::: > ",packet ) 132 | try: 133 | connection = psycopg2.connect(user=, 134 | password=, 135 | host=, 136 | port=, 137 | database=) 138 | cursor = connection.cursor() 139 | cursor.execute(f""" 140 | select message from sendmsg where done = False and imei = '{addresses[client]['imei']}' 141 | """) 142 | 143 | a = cursor.fetchall() 144 | for i in a: 145 | print(i[0]) 146 | #send i[0] 147 | client.send(bytes.fromhex(i[0])) 148 | cursor.execute(f""" 149 | update sendmsg set done = True where message = '{i[0]}' and imei = '{addresses[client]['imei']}' 150 | """) 151 | print("SEND : ",i[0]) 152 | connection.commit() 153 | connection.close() 154 | except Exception as e: 155 | print("IN SEND DATA : ",e) 156 | pass 157 | 158 | 159 | 160 | 161 | packet_list = [packet.hex()[i:i+2] for i in range(4, len(packet.hex())-4, 2)] 162 | 163 | pklist = ', ' 164 | pklist.join(packet_list) 165 | 166 | 167 | protocol_name = protocol_dict['protocol'][packet_list[1]] 168 | protocol = protocol_name 169 | protocol_method = protocol_dict['response_method'][protocol_name] 170 | print('The current packet is for protocol:', protocol_name, 'which has method:', protocol_method) 171 | # Get the protocol name and react accordingly 172 | if (protocol_name == 'login'): 173 | r = answer_login(client, packet_list) 174 | logdb(addresses[client]['imei'], protocol_name, pklist, str(r), addresses[client]['address'][0]) 175 | 176 | elif (protocol_name == 'gps_positioning' or protocol_name == 'gps_offline_positioning'): 177 | r = answer_gps(client, packet_list) 178 | logdb(addresses[client]['imei'], protocol_name, pklist, str(r), addresses[client]['address'][0]) 179 | 180 | elif (protocol_name == 'status'): 181 | # Status can sometimes carry signal strength and sometimes not 182 | if (packet_list[0] == '06'): 183 | print('[', addresses[client]['address'][0], ']', 'STATUS : Battery =', int(packet_list[2], base=16), '; Sw v. =', int(packet_list[3], base=16), '; Status upload interval =', int(packet_list[4], base=16)) 184 | elif (packet_list[0] == '07'): 185 | print('[', addresses[client]['address'][0], ']', 'STATUS : Battery =', int(packet_list[2], base=16), '; Sw v. =', int(packet_list[3], base=16), '; Status upload interval =', int(packet_list[4], base=16), '; Signal strength =', int(packet_list[5], base=16)) 186 | # Exit function without altering anything 187 | status[client]['status'] ='Battery =:' + str(int(packet_list[2], base=16)) + ': Sw v. =:' + str(int(packet_list[3], base=16)) + ': Status upload interval =:' + str(int(packet_list[4], base=16) )+ ': Signal strength =:'+ str(int(packet_list[5], base=16)) 188 | logdb(addresses[client]['imei'], protocol_name, pklist, '', addresses[client]['address'][0]) 189 | return(True) 190 | 191 | elif (protocol_name == 'hibernation'): 192 | # Exit function returning False to break main while loop in handle_client() 193 | print('[', addresses[client]['address'][0], ']', 'STATUS : Sent hibernation packet. Disconnecting now.') 194 | logdb(addresses[client]['imei'], protocol_name, pklist, 'Sent hibernation packet. Disconnecting now.', addresses[client]['address'][0]) 195 | return(False) 196 | 197 | elif (protocol_name == 'setup'): 198 | # TODO: HANDLE NON-DEFAULT VALUES 199 | r = answer_setup(packet_list, '0300', '00110001', '000000', '000000', '000000', '00', '000000', '000000', 200 | '000000', '00', '0000', '0000', ['', '', '']) 201 | logdb(addresses[client]['imei'], protocol_name, pklist, str(r), addresses[client]['address'][0]) 202 | #r = answer_setup(packet_list, '0010', '00110001', '000000', '000000', '000000', '00', '000000', '000000', '000000', '00', '0000', '0000', ['', '', '']) 203 | #7878 1E 57 0006 31 000000 000000 000000 00 000000000000000000 00 00000000 3B3B 0D0A 204 | #7878 1E 57 0300 31 000000 000000 000000 00 000000000000000000 00 00000000 3B3B 0D0A 205 | #7878 1E 57 0006 31 000000 000000 000000 00 000000000000000000 00 00000000 3B3B 0D0A 206 | elif (protocol_name == 'time'): 207 | r = answer_time(packet_list) 208 | logdb(addresses[client]['imei'], protocol_name, pklist, str(r), addresses[client]['address'][0]) 209 | 210 | elif (protocol_name == 'wifi_positioning' or protocol_name == 'wifi_offline_positioning'): 211 | r = answer_wifi_lbs(client, packet_list) 212 | logdb(addresses[client]['imei'], protocol_name, pklist, str(r), addresses[client]['address'][0]) 213 | 214 | elif (protocol_name == 'position_upload_interval'): 215 | r = answer_upload_interval(client, packet_list) 216 | logdb(addresses[client]['imei'], protocol_name, pklist, str(r), addresses[client]['address'][0]) 217 | 218 | else: 219 | r = generic_response(packet_list[1]) 220 | logdb(addresses[client]['imei'], '?', pklist, str(r), addresses[client]['address'][0]) 221 | 222 | # Send response to client 223 | print('------->[', addresses[client]['address'][0], ']', 'OUT Hex :', r, '(length in bytes =', len(bytes.fromhex(r)), ')') 224 | if r[6:8] != '80': 225 | send_response(client, r) 226 | logdb(addresses[client]['imei'], '?', 'NULL', str(r), addresses[client]['address'][0]) 227 | else: 228 | pass 229 | # Return True to avoid failing in main while loop in handle_client() 230 | return(True) 231 | 232 | 233 | def answer_login(client, query): 234 | protocol = query[1] 235 | addresses[client]['imei'] = ''.join(query[2:10])[1:] 236 | addresses[client]['software_version'] = int(query[10], base=16) 237 | 238 | # DEBUG: Print IMEI and software version 239 | print("Detected IMEI :", addresses[client]['imei'], "and Sw v. :", addresses[client]['software_version']) 240 | 241 | # Prepare response: in absence of control values, 242 | # always accept the client 243 | response = '01' 244 | # response = '44' 245 | r = make_content_response(hex_dict['start'] + hex_dict['start'], protocol, response, hex_dict['stop_1'] + hex_dict['stop_2']) 246 | return(r) 247 | 248 | def answer_setup(query, uploadIntervalSeconds, binarySwitch, alarm1, alarm2, alarm3, dndTimeSwitch, dndTime1, dndTime2, dndTime3, gpsTimeSwitch, gpsTimeStart, gpsTimeStop, phoneNumbers): 249 | 250 | protocol = query[1] 251 | binarySwitch = format(int(binarySwitch, base=2), '02X') 252 | 253 | # Convert phone numbers to 'ASCII' (?) by padding each digit with 3's and concatenate 254 | for n in range(len(phoneNumbers)): 255 | phoneNumbers[n] = bytes(phoneNumbers[n], 'UTF-8').hex() 256 | phoneNumbers = '3B'.join(phoneNumbers) 257 | 258 | # Build response 259 | response = uploadIntervalSeconds + binarySwitch + alarm1 + alarm2 + alarm3 + dndTimeSwitch + dndTime1 + dndTime2 + dndTime3 + gpsTimeSwitch + gpsTimeStart + gpsTimeStop + phoneNumbers 260 | r = make_content_response(hex_dict['start'] + hex_dict['start'], protocol, response, hex_dict['stop_1'] + hex_dict['stop_2']) 261 | return(r) 262 | 263 | 264 | def answer_time(query): 265 | """ 266 | Time synchronization is initiated by the device, which expects a response 267 | contianing current datetime over 7 bytes: YY YY MM DD HH MM SS. 268 | This function is a wrapper to generate the proper response 269 | """ 270 | 271 | # Read protocol 272 | protocol = query[1] 273 | 274 | # Get current date and time into the pretty-fied hex format 275 | response = get_hexified_datetime(truncatedYear=False) 276 | 277 | # Build response 278 | r = make_content_response(hex_dict['start'] + hex_dict['start'], protocol, response, hex_dict['stop_1'] + hex_dict['stop_2']) 279 | return(r) 280 | 281 | 282 | def answer_gps(client, query): 283 | positions[client]['gps'] = {} 284 | 285 | # Read protocol 286 | protocol = query[1] 287 | 288 | # Extract datetime from incoming query to put into the response 289 | # Datetime is in HEX format here, contrary to LBS packets... 290 | # That means it's read as HEX(YY) HEX(MM) HEX(DD) HEX(HH) HEX(MM) HEX(SS)... 291 | dt = ''.join([ format(int(x, base = 16), '02d') for x in query[2:8] ]) 292 | # GPS DateTime is at UTC timezone: we need to convert it to local, while keeping the same format as a string 293 | if (dt != '000000000000'): 294 | dt = datetime.strftime(datetime.strptime(dt, '%y%m%d%H%M%S').replace(tzinfo=tz.tzutc()).astimezone(tz.tzlocal()), '%y%m%d%H%M%S') 295 | # Read in the incoming GPS positioning 296 | # Byte 8 contains length of packet on 1st char and number of satellites on 2nd char 297 | gps_data_length = int(query[8][0], base=16) 298 | gps_nb_sat = int(query[8][1], base=16) 299 | # Latitude and longitude are both on 4 bytes, and were multiplied by 30000 300 | # after being converted to seconds-of-angle. Let's convert them back to degree 301 | gps_latitude = int(''.join(query[9:13]), base=16) / (30000 * 60) 302 | gps_longitude = int(''.join(query[13:17]), base=16) / (30000 * 60) 303 | # Speed is on the next byte 304 | gps_speed = int(query[17], base=16) 305 | # Last two bytes contain flags in binary that will be interpreted 306 | gps_flags = format(int(''.join(query[18:20]), base=16), '0>16b') 307 | position_is_valid = gps_flags[3] 308 | # Flip sign of GPS latitude if South, longitude if West 309 | if (gps_flags[4] == '1'): 310 | gps_latitude = -gps_latitude 311 | if (gps_flags[5] == '0'): 312 | gps_longitude = -gps_longitude 313 | gps_heading = int(''.join(gps_flags[6:]), base = 2) 314 | 315 | # Store GPS information into the position dictionary and print them 316 | positions[client]['gps']['method'] = 'GPS' 317 | # In some cases dt is empty with value '000000000000': let's avoid that because it'll crash strptime 318 | positions[client]['gps']['datetime'] = datetime.strptime(datetime.now().strftime('%y%m%d%H%M%S') if dt == '000000000000' else dt, '%y%m%d%H%M%S').strftime('%Y/%m/%d %H:%M:%S') 319 | positions[client]['gps']['valid'] = position_is_valid 320 | positions[client]['gps']['nb_sat'] = gps_nb_sat 321 | positions[client]['gps']['latitude'] = gps_latitude 322 | positions[client]['gps']['longitude'] = gps_longitude 323 | positions[client]['gps']['accuracy'] = 0.0 324 | positions[client]['gps']['speed'] = gps_speed 325 | positions[client]['gps']['heading'] = gps_heading 326 | a = open('./gpsdataGPS','w') 327 | a.write("GPSlat:{}:GPSlon:{}".format(gps_latitude,gps_longitude)) 328 | print('***************************[', addresses[client]['address'][0], ']', "POSITION/GPS : Valid =", position_is_valid, "; Nb Sat =", gps_nb_sat, "; Lat =", gps_latitude, "; Long =", gps_longitude, "; Speed =", gps_speed, "; Heading =", gps_heading,'***********') 329 | LOGGER('location', 'location_log.txt', addresses[client]['address'][0], addresses[client]['imei'], '', positions[client]['gps']) 330 | connection = psycopg2.connect(user=, 331 | password=, 332 | host=, 333 | port=, 334 | database=) 335 | cursor = connection.cursor() 336 | try: 337 | cursor.execute(f"""insert into public.gpsdata(address,imei, lat_long, time_st, lbs,status,protocol) 338 | VALUES ('{addresses[client]['address'][0]}','{addresses[client]['imei']}', '{str(gps_latitude)+":"+str(gps_longitude)}','{str(datetime.now())}', False,'{"Speed: "+{str(gps_speed)}+status[client]['status']}','{protocol}');""") 339 | except: 340 | cursor.execute(f"""insert into public.gpsdata(address,imei, lat_long, time_st, lbs,status,protocol) 341 | VALUES ('{addresses[client]['address'][0]}','{addresses[client]['imei']}', '{str(gps_latitude)+":"+str(gps_longitude)}','{str(datetime.now())}', False,'NOT RECIEVED','{protocol}');""") 342 | 343 | # print("PUBLISH TO from DEV") 344 | connection.commit() 345 | connection.close() 346 | 347 | 348 | 349 | 350 | 351 | # Get current datetime for answering 352 | response = get_hexified_datetime(truncatedYear=True) 353 | r = make_content_response(hex_dict['start'] + hex_dict['start'], protocol, response, hex_dict['stop_1'] + hex_dict['stop_2']) 354 | 355 | #send_response(client, '787801800D0A' ) 356 | return(r) 357 | 358 | 359 | def answer_wifi_lbs(client, query): 360 | 361 | positions[client]['wifi'] = [] 362 | positions[client]['gsm-cells'] = [] 363 | positions[client]['gsm-carrier'] = {} 364 | positions[client]['gps'] = {} 365 | 366 | # Read protocol 367 | protocol = query[1] 368 | 369 | # Datetime is BCD-encoded in bytes 2:7, meaning it's read *directly* as YY MM DD HH MM SS 370 | # and does not need to be decoded from hex. YY value above 2000. 371 | dt = ''.join(query[2:8]) 372 | # WiFi DateTime seems to be UTC timezone: convert it to local, while keeping the same format as a string 373 | if (dt != '000000000000'): 374 | dt = datetime.strftime(datetime.strptime(dt, '%y%m%d%H%M%S').replace(tzinfo=tz.tzutc()).astimezone(tz.tzlocal()), '%y%m%d%H%M%S') 375 | 376 | # WIFI 377 | n_wifi = int(query[0]) 378 | if (n_wifi > 0): 379 | for i in range(n_wifi): 380 | current_wifi = {'macAddress': ':'.join(query[(8 + (7 * i)):(8 + (7 * (i + 1)) - 2 + 1)]), # That +1 is because l[start:stop] returnes elements from start to stop-1... 381 | 'signalStrength': -int(query[(8 + (7 * (i + 1)) - 1)], base = 16)} 382 | positions[client]['wifi'].append(current_wifi) 383 | 384 | # Print Wi-Fi hotspots into the logs 385 | print('[', addresses[client]['address'][0], ']', "POSITION/WIFI : BSSID =", current_wifi['macAddress'], "; RSSI =", current_wifi['signalStrength']) 386 | 387 | # GSM Cell towers 388 | n_gsm_cells = int(query[(8 + (7 * n_wifi))]) 389 | # The first three bytes after n_lbs are MCC(2 bytes)+MNC(1 byte) 390 | gsm_mcc = int(''.join(query[((8 + (7 * n_wifi)) + 1):((8 + (7 * n_wifi)) + 2 + 1)]), base=16) 391 | gsm_mnc = int(query[((8 + (7 * n_wifi)) + 3)], base=16) 392 | positions[client]['gsm-carrier']['n_gsm_cells'] = n_gsm_cells 393 | positions[client]['gsm-carrier']['MCC'] = gsm_mcc 394 | positions[client]['gsm-carrier']['MNC'] = gsm_mnc 395 | print("--------------------------------WIFI: ", positions[client]['wifi']) 396 | 397 | if (n_gsm_cells > 0): 398 | for i in range(n_gsm_cells): 399 | current_gsm_cell = {'locationAreaCode': int(''.join(query[(((8 + (7 * n_wifi)) + 4) + (5 * i)):(((8 + (7 * n_wifi)) + 4) + (5 * i) + 1 + 1)]), base=16), 400 | 'cellId': int(''.join(query[(((8 + (7 * n_wifi)) + 4) + (5 * i) + 1 + 1):(((8 + (7 * n_wifi)) + 4) + (5 * i) + 2 + 1 + 1)]), base=16), 401 | 'signalStrength': -int(query[(((8 + (7 * n_wifi)) + 4) + (5 * i) + 2 + 1 + 1)], base=16)} 402 | positions[client]['gsm-cells'].append(current_gsm_cell) 403 | 404 | # Print LBS data into logs as well 405 | print('[', addresses[client]['address'][0], ']', "POSITION/LBS : LAC =", current_gsm_cell['locationAreaCode'], "; CellID =", current_gsm_cell['cellId'], "; MCISS =", current_gsm_cell['signalStrength']) 406 | 407 | r_1 = make_content_response(hex_dict['start'] + hex_dict['start'], protocol, dt, hex_dict['stop_1'] + hex_dict['stop_2']) 408 | print('[', addresses[client]['address'][0], ']', 'OUT Hex :', r_1, '(length in bytes =', len(bytes.fromhex(r_1)), ')') 409 | send_response(client, r_1) 410 | print("*/*/*/*/*/*/*/*/*/ " , positions[client]['gsm-cells']) 411 | #response = '2C'.join( 412 | # [ bytes(positions[client]['gps']['latitude'][0] + str(round(float(positions[client]['gps']['latitude'][1:]), 6)), 'UTF-8').hex(), 413 | # bytes(positions[client]['gps']['longitude'][0] + str(round(float(positions[client]['gps']['longitude'][1:]), 6)), 'UTF-8').hex() ]) 414 | #TODO 415 | #TODO 416 | #TODO 417 | mmc = gsm_mcc 418 | mnc = gsm_mnc 419 | print ("<> : ",mmc,mnc) 420 | url = "https://us1.unwiredlabs.com/v2/process.php" 421 | 422 | cc = str(positions[client]['wifi']) 423 | lbs = str(positions[client]['gsm-cells']) 424 | payload = "{\"token\": \"09ce33ba16479e\",\"radio\": \"gsm\",\"mcc\": %s,\"mnc\": %s,"%(mmc,mnc) + "\"cells\": " + lbs.replace("locationAreaCode","lac").replace("cellId","cid").replace("signalStrength","signal")+ ", \"wifi\": "+cc.replace("macAddress","bssid").replace("signalStrength","signal") + "}" 425 | print(payload.replace("\'","\"")) 426 | payload = payload.replace("\'","\"") 427 | #payload = ("{\"token\": \"09ce33ba16479e\",\"radio\": \"gsm\",\"mcc\": %s,\"mnc\": %s,\"cells\":"%(mmc,mnc) ) + format_gps_data + ",\"address\": 1}" 428 | #payload = str(payload).replace("'","\"") 429 | response = requests.request("POST", url, data=payload) 430 | print(response.text) 431 | 432 | aa = response.text 433 | lat = aa.split("\"lat\":")[1].split("\"lon\":")[0].split(",")[0] 434 | lon = aa.split("\"lat\":")[1].split("\"lon\":")[1].split(",")[0] 435 | print(lat) 436 | print(lon) 437 | response = '2C'.join( 438 | [ bytes(lat + str(round(float(lat[1:]), 6)), 'UTF-8').hex(), 439 | bytes(lon + str(round(float(lon[1:]), 6)), 'UTF-8').hex() ]) 440 | # r_2 = make_content_response(hex_dict['start'] + hex_dict['start'], protocol, response, hex_dict['stop_1'] + hex_dict['stop_2']) 441 | print(response) 442 | f = open('./dataLBS','w') 443 | f.write('LBSLAT:{}:LBSlon:{}'.format(lat,lon)) 444 | 445 | connection = psycopg2.connect(user=, 446 | password=, 447 | host=, 448 | port=, 449 | database=) 450 | cursor = connection.cursor() 451 | try: 452 | cursor.execute(f"""insert into public.gpsdata(address,imei, lat_long, time_st, lbs,status,protocol) 453 | VALUES ('{addresses[client]['address'][0]}','{addresses[client]['imei']}', '{str(lat)+":"+str(lon)}','{str(datetime.now())}', True,'{status[client]['status']}','{protocol}');""") 454 | except: 455 | try: 456 | cursor.execute(f"""insert into public.gpsdata(address,imei, lat_long, time_st, lbs,status,protocol) 457 | VALUES ('{addresses[client]['address'][0]}','{addresses[client]['imei']}', '{str(lat)+":"+str(lon)}','{str(datetime.now())}', True,'NOT RECIEVED','{protocol}');""") 458 | except Exception as e : 459 | pass 460 | 461 | connection.commit() 462 | connection.close() 463 | 464 | 465 | 466 | r_2 = make_content_response(hex_dict['start'] + hex_dict['start'], protocol, response, hex_dict['stop_1'] + hex_dict['stop_2']) 467 | return(r_2) 468 | 469 | 470 | def answer_upload_interval(client, query): 471 | """ 472 | Whenever the device received an SMS that changes the value of an upload interval, 473 | it sends this information to the server. 474 | The server should answer with the exact same content to acknowledge the packet. 475 | """ 476 | 477 | # Read protocol 478 | protocol = query[1] 479 | 480 | # Response is new upload interval reported by device (HEX formatted, no need to alter it) 481 | response = ''.join(query[2:4]) 482 | 483 | r = make_content_response(hex_dict['start'] + hex_dict['start'], protocol, response, hex_dict['stop_1'] + hex_dict['stop_2']) 484 | return(r) 485 | 486 | 487 | def generic_response(protocol): 488 | """ 489 | Many queries made by the device do not expect a complex 490 | response: most of the times, the device expects the exact same packet. 491 | Here, we will answer with the same value of protocol that the device sent, 492 | not using any content. 493 | """ 494 | r = make_content_response(hex_dict['start'] + hex_dict['start'], protocol, None, hex_dict['stop_1'] + hex_dict['stop_2']) 495 | return(r) 496 | 497 | 498 | def make_content_response(start, protocol, content, stop): 499 | """ 500 | This is just a wrapper to generate the complete response 501 | to a query, goven its content. 502 | It will apply to all packets where response is of the format: 503 | start-start-length-protocol-content-stop_1-stop_2. 504 | Other specific packets where length is replaced by counters 505 | will be treated separately. 506 | """ 507 | return(start + format((len(bytes.fromhex(content)) if content else 0)+1, '02X') + protocol + (content if content else '') + stop) 508 | 509 | 510 | def send_response(client, response): 511 | """ 512 | Function to send a response packet to the client. 513 | """ 514 | LOGGER('info', 'server_log.txt', addresses[client]['address'][0], addresses[client]['imei'], 'OUT', response) 515 | client.send(bytes.fromhex(response)) 516 | 517 | 518 | def get_hexified_datetime(truncatedYear): 519 | """ 520 | Make a fancy function that will return current GMT datetime as hex 521 | concatenated data, using 2 bytes for year and 1 for the rest. 522 | The returned string is YY YY MM DD HH MM SS if truncatedYear is False, 523 | or just YY MM DD HH MM SS if truncatedYear is True. 524 | """ 525 | 526 | # Get current GMT time into a list 527 | if (truncatedYear): 528 | dt = datetime.utcnow().strftime('%y-%m-%d-%H-%M-%S').split("-") 529 | else: 530 | dt = datetime.utcnow().strftime('%Y-%m-%d-%H-%M-%S').split("-") 531 | print("DTTTTTTTTT in func :",dt) 532 | # Then convert to hex with 2 bytes for year and 1 for the rest 533 | dt = [ format(int(x), '0'+str(len(x))+'X') for x in dt ] 534 | return(''.join(dt)) 535 | 536 | 537 | def GoogleMaps_geolocation_service(gmapsClient, positionDict): 538 | """ 539 | This wrapper function will query the Google Maps API with the list 540 | of cell towers identifiers and WiFi SSIDs that the device detected. 541 | It requires a Google Maps API key. 542 | 543 | For now, the radio_type argument is forced to 'gsm' because there are 544 | no CDMA cells in France (at least that's what I believe), and since the 545 | GPS device only handles 2G, it's the only option available. 546 | The carrier is forced to 'Free' since that's the one for the SIM card 547 | I'm using, but again this would need to be tweaked (also it probbaly 548 | doesn't make much of a difference to feed it to the function or not!) 549 | 550 | These would need to be tweaked depending on where you live. 551 | 552 | A nice source for such data is available at https://opencellid.org/ 553 | """ 554 | print('Google Maps Geolocation API queried with:', positionDict) 555 | geoloc = gmapsClient.geolocate(home_mobile_country_code=positionDict['gsm-carrier']['MCC'], 556 | home_mobile_network_code=positionDict['gsm-carrier']['MCC'], 557 | radio_type='gsm', 558 | carrier='Free', 559 | consider_ip='true', 560 | cell_towers=positionDict['gsm-cells'], 561 | wifi_access_points=positionDict['wifi']) 562 | 563 | print('Google Maps Geolocation API returned:', geoloc) 564 | return(geoloc) 565 | 566 | """ 567 | This is a debug block to test the GeoLocation API 568 | 569 | gmaps.geolocate(home_mobile_country_code='208', home_mobile_network_code='01', radio_type=None, carrier=None, consider_ip=False, cell_towers=cell_towers, wifi_access_points=None) 570 | 571 | ## DEBUG: USE DATA FROM ONE PACKET 572 | # Using RSSI 573 | cell_towers = [ 574 | { 575 | 'locationAreaCode': 832, 576 | 'cellId': 51917, 577 | 'signalStrength': -90 578 | }, 579 | { 580 | 'locationAreaCode': 768, 581 | 'cellId': 64667, 582 | 'signalStrength': -100 583 | }, 584 | { 585 | 'locationAreaCode': 1024, 586 | 'cellId': 24713, 587 | 'signalStrength': -100 588 | }, 589 | { 590 | 'locationAreaCode': 768, 591 | 'cellId': 53851, 592 | 'signalStrength': -100 593 | }, 594 | { 595 | 'locationAreaCode': 1024, 596 | 'cellId': 8021, 597 | 'signalStrength': -100 598 | }, 599 | { 600 | 'locationAreaCode': 1024, 601 | 'cellId': 62216, 602 | 'signalStrength': -100 603 | } 604 | ] 605 | 606 | # Using dummy values in dBm 607 | cell_towers = [ 608 | { 609 | 'locationAreaCode': 832, 610 | 'cellId': 51917, 611 | 'signalStrength': -50 612 | }, 613 | { 614 | 'locationAreaCode': 768, 615 | 'cellId': 64667, 616 | 'signalStrength': -30 617 | }, 618 | { 619 | 'locationAreaCode': 1024, 620 | 'cellId': 24713, 621 | 'signalStrength': -30 622 | }, 623 | { 624 | 'locationAreaCode': 768, 625 | 'cellId': 53851, 626 | 'signalStrength': -30 627 | }, 628 | { 629 | 'locationAreaCode': 1024, 630 | 'cellId': 8021, 631 | 'signalStrength': -30 632 | }, 633 | { 634 | 'locationAreaCode': 1024, 635 | 'cellId': 62216, 636 | 'signalStrength': -30 637 | } 638 | ] 639 | """ 640 | 641 | # Declare common Hex codes for packets 642 | hex_dict = { 643 | 'start': '78', 644 | 'stop_1': '0D', 645 | 'stop_2': '0A' 646 | } 647 | 648 | protocol_dict = { 649 | 'protocol': { 650 | '01': 'login', 651 | '05': 'supervision', 652 | '08': 'heartbeat', 653 | '10': 'gps_positioning', 654 | '11': 'gps_offline_positioning', 655 | '13': 'status', 656 | '14': 'hibernation', 657 | '15': 'reset', 658 | '16': 'whitelist_total', 659 | '17': 'wifi_offline_positioning', 660 | '30': 'time', 661 | '43': 'mom_phone_WTFISDIS?', 662 | '56': 'stop_alarm', 663 | '57': 'setup', 664 | '58': 'synchronous_whitelist', 665 | '67': 'restore_password', 666 | '69': 'wifi_positioning', 667 | '80': 'manual_positioning', 668 | '81': 'battery_charge', 669 | '82': 'charger_connected', 670 | '83': 'charger_disconnected', 671 | '94': 'vibration_received', 672 | '98': 'position_upload_interval', 673 | 'b3': 'B3', 674 | '99': '99', 675 | '92': '92', 676 | '49': '49', 677 | '64': 'recording_request' 678 | }, 679 | 'response_method': { 680 | 'login': 'login', 681 | 'logout': 'logout', 682 | 'supervision': '', 683 | 'heartbeat': '', 684 | 'gps_positioning': 'datetime_response', 685 | 'gps_offline_positioning': 'datetime_response', 686 | 'status': '', 687 | 'hibernation': '', 688 | 'reset': '', 689 | 'whitelist_total': '', 690 | 'wifi_offline_positioning': 'datetime_position_response', 691 | 'time': 'time_response', 692 | 'stop_alarm': '', 693 | 'setup': 'setup', 694 | 'synchronous_whitelist': '', 695 | 'restore_password': '', 696 | 'wifi_positioning': 'datetime_position_response', 697 | 'manual_positioning': '', 698 | 'battery_charge': '', 699 | 'charger_connected': '', 700 | 'charger_disconnected': '', 701 | 'vibration_received': '', 702 | 'position_upload_interval': 'upload_interval_response', 703 | 'B3': '', 704 | '99': '', 705 | '92': '', 706 | '49' : '', 707 | 'recording_request':'' 708 | } 709 | } 710 | 711 | 712 | # Import dotenv with API keys and initialize API connections 713 | 714 | # Details about host server 715 | HOST = 716 | PORT = 717 | BUFSIZ = 10240 #4096 718 | ADDR = (HOST, PORT) 719 | 720 | # Initialize socket 721 | SERVER = socket(AF_INET, SOCK_STREAM) 722 | SERVER.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) 723 | SERVER.bind(ADDR) 724 | 725 | # Store client data into dictionaries 726 | addresses = {} 727 | positions = {} 728 | status = {} 729 | protocol = '' 730 | if __name__ == '__main__': 731 | SERVER.listen(5) 732 | print("Waiting for connection...") 733 | ACCEPT_THREAD = Thread(target=accept_incoming_connections) 734 | ACCEPT_THREAD.start() 735 | ACCEPT_THREAD.join() 736 | SERVER.close() 737 | --------------------------------------------------------------------------------