class Tracker: def __init__(self, DB): self.log = Logs().getLog() # Initializing MAC table self.STATION_MACS = {} self.TRACKING_MACS = [] # 10 Second timeout self.timeout = 10 # Dictionary of things to be tracked with MACs as key and coordinantes as values self.tracked_objects = {} self.DB = DB # Running actual program self.initMacs() self.last_id = 0 def initMacs(self): ########################################################## # This function gets the necessary MAC addresses for the # # required objects in the demonstration # ########################################################## self.log.debug("[+] Started initMACs...") current_dict = {} stations = 0 tracking = 0 with open('MACS.ini', 'r') as f: for line in f: if (line[0] == "\n" or line[0] == "\n"): continue if (line[0] == "["): if ("Stations" in line): stations = 1 tracking = 0 if (current_dict): # Checks if not empty self.TRACKING_MACS = current_dict elif ("Tracking" in line): stations = 0 tracking = 1 if (current_dict): # Checks if not empty self.stations = current_dict else: self.log.error( "[x] Unknown ini header: ({})".format(line)) raise continue if (stations == 1): obj, MAC = [ x.strip("\n") for x in line.split("=") ] # Splits line and puts them in correct variable while stripping newline self.STATION_MACS[obj] = MAC elif (tracking == 1): self.TRACKING_MACS.append(line.strip("\n")) self.log.debug("[?] Station Macs Found: {}".format(self.STATION_MACS)) self.log.debug("[?] Tracking Macs Found: {}".format( self.TRACKING_MACS)) self.log.debug("[-] Completed initMACs...") def getStationObjectsPositions(self): ############################################################################################# # This function inserts the positional data of each station into the locations table in the # # shared SQL database. Also ensures that all stations have been found due to while loop not # # exiting until all are found. # ############################################################################################# self.log.debug("[+] Started getStationLayout...") stations = {} while (True): for recv_Station in self.STATION_MACS.keys( ): # Station that detects sample packet for emit_Station in self.STATION_MACS.keys( ): # Station that emits sample packet if (recv_Station == emit_Station): continue # One of these on each run should not find anything since it's trying to find its own blutooth packets entry = self.DB.getNewestPowerByMac( recv_Station, self.STATION_MACS[emit_Station]) if (entry == None): break if (recv_Station not in stations.keys()): stations[recv_Station] = {} if (emit_Station not in stations[recv_Station].keys()): stations[recv_Station][emit_Station] = {} stations[recv_Station][emit_Station] = { "power": entry["power"] } done = True for recv_Station in self.STATION_MACS.keys( ): # Station that detects sample packet if recv_Station not in stations.keys(): done = False break for emit_Station in self.STATION_MACS.keys( ): # Station that emits sample packet if (recv_Station == emit_Station): continue if emit_Station not in stations[recv_Station].keys(): done = False break if (done == True): break # Gets the keys in an array so we can manually pull out the 3 distances separately nodes = [x for x in self.STATION_MACS.keys()] self.log.debug("[?] Nodes: {}".format(nodes)) # Get the distances between the 3 stations ## I feel like it would be possible to average the 2 measurements (AB & BA) together to get a better reading distances = [ self.PowerToDistance( stations[nodes[0]][nodes[1]]["power"]), # A->B self.PowerToDistance( stations[nodes[0]][nodes[2]]["power"]), # A->C self.PowerToDistance( stations[nodes[1]][nodes[2]]["power"]), # B->C ] self.log.debug("[?] Distances found: {}".format(distances)) for i, station in enumerate(stations.keys()): if (i == 0): pos = [0, 0] elif (i == 1): pos = [0, distances[1]] elif (i == 2): angles = self.getTriangle(distances) # Distances[1] is AC because C is third point # Angle[0] is angle A pos = self.getThirdPointCoordinate(distances[2], angles[0]) self.DB.insertIntoLocations(station, *pos) self.log.debug("[-] Completed getStationLayout...") return def getTrackedObjectsPositions(self, track_unknown=False): self.log.debug("[+] Started getTrackedObjectsPositions...") if (track_unknown): new_macs, self.last_id = self.DB.getNewUnknownMacs( self.TRACKING_MACS + list(self.STATION_MACS.values()), self.last_id) self.TRACKING_MACS += new_macs for tracking_mac in self.TRACKING_MACS: # MAC we are tracking args = self.getTrilaterationArguments(tracking_mac) if (args == None): continue tracked_position = self.Trilaterate(*args) self.DB.insertIntoLocations(tracking_mac, *tracked_position) self.log.debug("[-] Completed getTrackedObjectsPositions...") def getTrilaterationArguments(self, tracking_mac): args = [] # Arguments to be passed into the trilateration method for recv_Station in self.STATION_MACS.keys( ): # Station that detects sample packet # One of these on each run should not find anything since it's trying to find its own blutooth packets entry = self.DB.getNewestPowerByMac(recv_Station, tracking_mac) if (entry == None): return (None) now = time.time() if ( now - entry["time"] > 3 ): # If more than 3 seconds has passed since this measurement has been taken, meaning it hasn't been detected again in 3 seconds return (None) response = self.DB.getNewestLocationByMac(recv_Station) args.append(response["x"]) args.append(response["y"]) args.append(self.PowerToDistance(entry["power"])) return (args) @staticmethod def PowerToDistance(power): if (power == None): return None if power == 0: power = 1 return (2**((255 - power) / 32)) @staticmethod def getTriangle(distances): # Input 3 distances in an array and this function returns 3 angles a = distances[0] b = distances[1] c = distances[2] # Acos has bounds -1 to 1 so i mod it to keep it in bounds innerA = (((b * b + c * c - a * a) / (2 * b * c) + 1) % 2) - 1 innerB = (((a * a + c * c - b * b) / (2 * a * c) / (2 * b * c) + 1) % 2) - 1 innerC = (((a * a + b * b - c * c) / (2 * a * b) / (2 * b * c) + 1) % 2) - 1 return ([ math.acos(innerA), # Angle A math.acos(innerB), # Angle B math.acos(innerC), # Angle C ]) @staticmethod def getThirdPointCoordinate(AC, A): # # Thank you sir # # https://math.stackexchange.com/questions/543961/determine-third-point-of-triangle-when-two-points-and-all-sides-are-known # coord = [0,0] # if( AB > 0 ): # coord[0] = (BC*BC - AC*AC + AB*AB)/(2*AB) # coord[1] = math.sqrt( abs( BC*BC - coord[0]*coord[0] ) ) # return( coord ) # Trying this from user Shivam Agrawal # https://math.stackexchange.com/questions/143932/calculate-point-given-x-y-angle-and-distance # 0 or x1 but since calculating from A station at 0,0 can leave as 0 x3 = 0 + (AC * math.cos(A)) y3 = 0 + (AC * math.sin(A)) return ([x3, y3]) @staticmethod def Trilaterate(x1, y1, r1, x2, y2, r2, x3, y3, r3): A = 2 * x2 - 2 * x1 B = 2 * y2 - 2 * y1 C = r1**2 - r2**2 - x1**2 + x2**2 - y1**2 + y2**2 D = 2 * x3 - 2 * x2 E = 2 * y3 - 2 * y2 F = r2**2 - r3**2 - x2**2 + x3**2 - y2**2 + y3**2 x = (C * E - F * B) / (E * A - B * D) y = (C * D - A * F) / (B * D - A * E) return [x, y] @staticmethod def getAngle(a, b, c): # The order of a and b dont matter so long as both a and b are the sides adjacent to the angle you are trying to measure # c needs to be on the side furthest from the angle. return (math.cos((a * a + b * b - c * c) / (2 * a * b))) def getNodeTriangle(self): return (self.node_triangle)