Iterative Live PLOTTING of our localisation data

Hi,

my name is Nick De Leenheer, and i am finishing my thesis on indoor localisation with CHANNEL SOUNDING. i currently got these outputs in my venv using SERIAL:



so i use the ranging data from 4 different devices, and then i use this multilateration algorithm:

def multilateration_2d(anchors: list, ranges: list, z_true: float = 0, dynamic: bool = False, anchors_pos: dict = None) -> np.ndarray:
    ranges_ = {}
    for a, r in zip(anchors, ranges):
        if a not in ranges_:
            ranges_[a] = [r]
        else:
            ranges_[a].append(r)
    anchors = list(ranges_.keys())
    ranges = [np.median(r) for r in ranges_.values()]

    if len(ranges) < 3:
        return None
    if dynamic:
        anchors = anchors[-3:]
        ranges = ranges[-3:]
    A = np.zeros((len(anchors), 3))
    b = np.zeros(len(anchors))
    for i, (anchor, range_val) in enumerate(zip(anchors, ranges)):
        anchor_pos = anchors_pos[anchor]
        range_sq = range_val**2 - (anchor_pos[2] - z_true)**2
        A[i, :] = 1, -2 * anchor_pos[0], -2 * anchor_pos[1]
        b[i] = range_sq - anchor_pos[0]**2 - anchor_pos[1]**2
    if np.linalg.cond(A.T @ A) < 1 / np.finfo(A.dtype).eps:
        return (np.linalg.inv(A.T @ A) @ A.T @ b)[1:]
    return None

afterwards, i want to plot 4 ranging circles from the 4 reflector (beacons) to estimate the location of the tag...

But this plt.show() in my code doesnt work iteratively... so i am wondering what would work best to update and show my plot. Captured data i was able to plot in an animation with animate from matplotlib. But now i don't know what would work on my LINUX computer.



(here is my total code)

import serial
import re
import numpy as np
import matplotlib.pyplot as plt


# --- Parameters ---
MAX_DEVICES = 4
RANGING_ROUNDS = 5

# Reflectorposities
reflector_positions_ugent = {
    "reflector921": (2.4, -4.2, 0.0),
    "reflector494": (3.0, 0.6, 0.0),
    "reflector355": (-3.0, -1.2, 0.0),
    "reflector651": (3.98, 5.05, 0.0),
    "reflector604": (-3.62, 5.05, 0.0),
    "reflector771": (-3.26, -4.93, 0.0),
}

# Reflectorposities
reflector_positions = {
    "reflector771": (0.4, 0.0, 0.0),
    "reflector921": (0.8, 0.0, 0.0),
    "reflector494": (1.1, 0.0, 0.0),
    "reflector604": (1.5, 0.0, 0.0),
}

min_x = min(pos[0] for pos in reflector_positions.values()) - 1
max_x = max(pos[0] for pos in reflector_positions.values()) + 1
min_y = min(pos[1] for pos in reflector_positions.values()) - 1
max_y = max(pos[1] for pos in reflector_positions.values()) + 1

dx = 0.1
x = np.arange(min_x, max_x + dx, dx)
y = np.arange(min_y, max_y + dx, dx)
X, Y = np.meshgrid(x, y)
grid_points = np.vstack([X.ravel(), Y.ravel()]).T


def error(point, beacons, measured):
    return sum((np.linalg.norm(np.array(point) - np.array(beacons[b])) - measured[b])**2 for b in measured)

def trilaterate(beacons, measured):
    if len(measured) < 3:
        return None
    best = min(grid_points, key=lambda p: error(p, beacons, measured))
    best = np.asarray(best).astype(float).tolist()  # <-- expliciet converteren
    return tuple(best)

def multilateration_2d(anchors: list, ranges: list, z_true: float = 0, dynamic: bool = False, anchors_pos: dict = None) -> np.ndarray:
    ranges_ = {}
    for a, r in zip(anchors, ranges):
        if a not in ranges_:
            ranges_[a] = [r]
        else:
            ranges_[a].append(r)
    anchors = list(ranges_.keys())
    ranges = [np.median(r) for r in ranges_.values()]

    if len(ranges) < 3:
        return None
    if dynamic:
        anchors = anchors[-3:]
        ranges = ranges[-3:]
    A = np.zeros((len(anchors), 3))
    b = np.zeros(len(anchors))
    for i, (anchor, range_val) in enumerate(zip(anchors, ranges)):
        anchor_pos = anchors_pos[anchor]
        range_sq = range_val**2 - (anchor_pos[2] - z_true)**2
        A[i, :] = 1, -2 * anchor_pos[0], -2 * anchor_pos[1]
        b[i] = range_sq - anchor_pos[0]**2 - anchor_pos[1]**2
    if np.linalg.cond(A.T @ A) < 1 / np.finfo(A.dtype).eps:
        return (np.linalg.inv(A.T @ A) @ A.T @ b)[1:]
    return None

# --- Serial ---
ser1 = serial.Serial('/dev/ttyACM1', 115200, timeout=1)
ser3 = serial.Serial('/dev/ttyACM3', 115200, timeout=1)

device_data = {}
current_device = None
current_reflector = None

# --- Plot ---
fig, ax = plt.subplots(figsize=(8, 6))
ax.set_xlim(min_x - 0.5, max_x + 0.5)
ax.set_ylim(min_y - 0.5, max_y + 0.5)
ax.set_aspect('equal')
ax.set_title("Live Trilateratie")

ax.arrow(0, 0, 0.5, 0, head_width=0.05, head_length=0.05, fc='green', ec='green')
ax.arrow(0, 0, 0, 0.5, head_width=0.05, head_length=0.05, fc='green', ec='green')
ax.text(0.55, 0.02, 'x', color='green', fontsize=10, fontweight='bold')
ax.text(0.02, 0.55, 'y', color='green', fontsize=10, fontweight='bold')

rect = plt.Rectangle((min_x, min_y), max_x - min_x, max_y - min_y,
                     linewidth=2, edgecolor='blue', facecolor='none', linestyle='--')
ax.add_patch(rect)

# Beacons tekenen
for name, (bx, by, _) in reflector_positions.items():
    ax.plot(bx, by, 'ro')
    ax.text(bx + 0.05, by + 0.05, name, fontsize=8)


position_dot, = ax.plot([], [], 'bx', markersize=12, label='Geschatte positie')
range_circles = []



################################################ MAIN LOOP ##########################################################################
while True:
    try:
        # KIES 1 VAN DE 2 (soms ACM1 -> ser1, soms ACM3->ser3):
        line = ser1.readline().decode("utf-8").strip()

        # Sla alles over wat niet relevant is
        if not line or "NULL" in line or line.startswith("W:") or "Disconnected" in line:
            continue

        print(line)

        # Device-regel
        if line.startswith("I: Device") and "Reflector" in line:
            match = re.search(r"Device (\d+).*Reflector(\d+)", line)
            if match:
                current_device = int(match.group(1))
                current_reflector = "reflector" + match.group(2)
                if current_device not in device_data:
                    device_data[current_device] = {"name": current_reflector, "ifft": []}

        # IFFT-regel
        elif line.startswith("I: IFFT:") and current_device in device_data:
            try:
                val = float(line.split("IFFT:")[1].strip())
                data = device_data[current_device]["ifft"]
                if len(data) < RANGING_ROUNDS:
                    data.append(val)
            except ValueError:
                continue

        # Check of we een volledige localisatie hebben
        complete_devices = {d: info for d, info in device_data.items() if len(info["ifft"]) == RANGING_ROUNDS}
        if len(complete_devices) == MAX_DEVICES:
            # Gemiddelden berekenen
            measurements = {}
            print("\n📡 Inkomende meting:")
            for dev_id, info in complete_devices.items():
                reflector = info["name"]
                if reflector in reflector_positions:
                    avg_ifft = np.mean(info["ifft"])
                    measurements[reflector] = avg_ifft
                    print(f"  Device {dev_id}  ({reflector})  →  Afstanden: {info['ifft']}  → Gemiddelde: {avg_ifft:.3f}")


            # Trilateratie
            # estimated_pos = trilaterate(reflector_positions, measurements)

            # Multilateratie
            estimated_pos = multilateration_2d(
                anchors=list(measurements.keys()),
                ranges=list(measurements.values()),
                anchors_pos=reflector_positions
            )

            # Oude cirkels wissen
            for c in range_circles:
                c.remove()
            range_circles.clear()

            # Nieuwe cirkels tekenen
            for name, dist in measurements.items():
                if name in reflector_positions:
                    bx, by, _ = reflector_positions[name]
                    circle = plt.Circle((bx, by), dist, color='r', fill=False, linestyle='--', alpha=0.5)
                    ax.add_patch(circle)
                    range_circles.append(circle)

            # Geschatte positie
            position_dot.set_data([], [])
            if estimated_pos is not None:
                x, y = float(estimated_pos[0]), float(estimated_pos[1])
                position_dot.set_data([x], [y])
                print(f"\nLokalisatie: {estimated_pos}, met {len(measurements)} devices")

            fig.canvas.draw()


            # Reset voor volgende meting
            device_data = {}
            current_device = None
            current_reflector = None

            
        plt.show()

    except KeyboardInterrupt:
        print("LOCALISATIE gestopt.")
        break

if someone could help me with this, i would be VERY happy!

kind regards,

Nick De Leenheer

Related