Пример #1
0
class Database:
    def __init__(self, database_filename=None):
        self.log = Logs().getLog()

        if (database_filename != None):
            self.database_filename = database_filename
        else:
            self.database_filename = "test.db"

        init_database = True if (
            not os.path.exists(self.database_filename)
            or not os.path.getsize(self.database_filename) > 0) else False
        # path can equal either :memory: or a database file
        try:
            self.conn = sqlite3.connect(self.database_filename,
                                        isolation_level=None,
                                        check_same_thread=False)
            self.conn.row_factory = sqlite3.Row
        except Error as e:
            print(e)
            self.conn.close()
            exception_string = "Connection to database failed. Closing..."
            # self.log.exception( exception_string )
            raise Exception(exception_string)

    def commitChanges(self):
        self.conn.commit()

    def sql_exec(self, sql, values=None):
        if (self.conn != None):
            c = self.conn.cursor()
        try:
            if (values != None):
                c.execute(sql, values)
            else:
                c.execute(sql)

            return (c)

        except Error as e:
            print(e)
            print("Executing sql command was unsuccessful...")

    def getNewestPowerByMac(self, station, MAC):
        ############################################################
        # Returns sql information for given MAC in dictionary form #
        ############################################################
        sql = "SELECT * FROM power WHERE mac=? AND station=? ORDER BY id DESC LIMIT 1"
        self.log.debug("[+] Started getNewestPowerByMac...")
        # self.log.debug("[?] Getting sql entry for mac='{}' and station='{}'...".format(MAC, station))

        MAX_RESPONSES = 1000
        response = self.sql_exec(sql, (MAC, station)).fetchmany(MAX_RESPONSES)

        if (response == None or len(response) == 0):
            self.log.debug(
                "[x] No sql entry found for mac='{}' from station='{}'...".
                format(MAC, station))
            return None

        avg_power = 0
        for r in range(len(response)):
            avg_power += response[r]["power"]
        avg_power = avg_power / len(response)

        final_response = response[0]
        final = {
            "id": final_response["id"],
            "station": final_response["station"],
            "time": final_response["time"],
            "mac": final_response["mac"],
            "power": avg_power
        }

        # self.log.debug("Returning non-None value for for mac='{}' and station='{}' sql request...".format(MAC, station))
        # final["time"] = datetime.fromtimestamp( float( response["time"] ) )

        self.log.debug("[-] Completed getNewestPowerByMac...")
        return (final)

    def insertIntoLocations(self, MAC, x, y):
        sql = "INSERT INTO location( mac, timestamp, x, y ) VALUES ( ?, ?, ?, ? )"
        self.sql_exec(sql, (MAC, time.time(), x, y))

    def getNewestLocationByMac(self, MAC):
        ############################################################
        # Returns sql information for given MAC in dictionary form #
        ############################################################
        self.log.debug("[+] Started getNewestLocationByMac...")
        sql = "SELECT * FROM location WHERE mac=? ORDER BY id DESC LIMIT 1"
        # self.log.debug("Getting sql entry for mac='{}'...".format(MAC))
        response = self.sql_exec(sql, (MAC, ))
        if (response == None):
            self.log.debug("[x] No sql entry found for mac='{}'...".format(
                MAC, station))
            return None

        self.log.debug("[-] Completed getNewestLocationByMac...")
        return (response.fetchone())

    def getNewUnknownMacs(self, known_macs, last_id):
        self.log.debug("[+] Started getNewUnknownMacs...")

        sql = "SELECT * FROM power WHERE id > {} ".format(last_id)

        response = self.sql_exec(sql).fetchall()

        if (response == None or len(response) == 0):
            self.log.debug("[x] No new tracked object entries found...")
            return ([], last_id)

        new_last_id = response[-1]["id"]

        new_macs = []
        for item in response:
            if (item["mac"] not in known_macs + new_macs):
                new_macs.append(item["mac"])

        self.log.debug("[?] Found {} new macs...".format(len(new_macs)))
        self.log.debug("[?] New last id={}".format(new_last_id))

        self.log.debug("[+] Completed getNewUnknownMacs...")
        return (new_macs, new_last_id)
class DuckieTrackingScene(QGraphicsScene):
    def __init__(self, *args):
        self.log = Logs().getLog()
        super().__init__()
        self.point_scale = 20
        self.distance_scale = 40
        # self.label_y_offset = 10

        self.DB = Database()
        self.tracker = Tracker(self.DB)

        self.track_unknown = True

    def update(self):
        self.tracker.getStationObjectsPositions()
        self.tracker.getTrackedObjectsPositions(self.track_unknown)

        self.clear()

        self.updateStations()
        self.updateTrackedObjects()

    def updateStations(self):
        self.log.debug("[+] Started updateStationLayout...")
        positions = []
        for mac in self.tracker.STATION_MACS:
            response = self.DB.getNewestLocationByMac(mac)
            if (response == None):
                self.log.debug("[x] Failed updateStationLayout...")
                return

            positions.append([response["x"], response["y"]])

        self.drawTriangle(positions)
        self.log.debug("[-] Completed updateStationLayout...")

    def updateTrackedObjects(self):
        self.log.debug("[+] Starting updateTrackedObjects...")
        for mac in self.tracker.TRACKING_MACS:
            response = self.DB.getNewestLocationByMac(mac)
            if (response == None):
                continue

            now = time.time()
            if (
                    now - response["timestamp"] > 3
            ):  # If more than 3 seconds has passed since this measurement has been taken, meaning it hasn't been detected again in 3 seconds
                continue

            self.log.debug("[*] Updating tracked objects...")

            pos = [response["x"], response["y"]]
            self.drawLabel(pos, response["mac"])
            self.drawNode(pos)

        self.log.debug("[-] Completed updatingTrackedObjects...")

    def drawTriangle(self, positions):
        self.log.debug("[+] Starting drawTriangle...")
        self.log.debug("[?] Positions -- A: {0}, B: {1}, C: {2}".format(
            positions[0], positions[1], positions[2]))

        # We assume the A node is 0,0 for simplicity
        self.drawLabel(positions[0], "A")
        self.drawNode(positions[0])

        # We assume the B node is north of the A node so the detection can be self organizing
        self.drawLabel(positions[1], "B")
        self.drawNode(positions[1])

        # Draw third node
        self.drawLabel(positions[2], "C")
        self.drawNode(positions[2])

        # Draw lines for all nodes
        [
            self.drawLine(positions[x - 1], positions[x])
            for x in range(len(positions))
        ]
        self.log.debug("[-] Completed drawTriangle...")

    def drawNode(self, xy):
        # xy = [x, y]
        pen = QPen(Qt.black, 2)
        brush = QBrush(Qt.black)
        radius = 0.25 * self.point_scale

        xy = [x * self.distance_scale for x in xy]

        # x1, y1, x2, y2, pen, brush
        self.addEllipse(xy[0] - radius / 2, xy[1] - radius / 2, radius, radius,
                        pen, brush)

    def drawLine(self, node1, node2):
        # node1 = [x, y], node2 = [x,y]
        pen = QPen(QPen(Qt.black, 2, Qt.DashLine))

        # x1, y1, x2, y2, pen
        self.addLine(node1[0] * self.distance_scale,
                     node1[1] * self.distance_scale,
                     node2[0] * self.distance_scale,
                     node2[1] * self.distance_scale, pen)

    def drawLabel(self, pos, text):
        pen = QPen(QPen(Qt.black, 1, Qt.SolidLine))

        t = self.addText(text)
        t.setPos(pos[0] * self.distance_scale, pos[1] * self.distance_scale)
Пример #3
0
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)