From dc744f46d6e7b8027383320921044984c1d6d37c Mon Sep 17 00:00:00 2001 From: Paul-Winpenny <92634321+Paul-Winpenny@users.noreply.github.com> Date: Wed, 18 Dec 2024 12:36:32 +0000 Subject: [PATCH] Graph maker is now inside of the ros2 package to make it easier to save the graph. --- ...realtime_location_cli_only.cpython-312.pyc | Bin 0 -> 13269 bytes .../robobin/robobin/helpers/graph_maker.py | 112 ++++++++ .../helpers/realtime_location_cli_only.py | 263 ++++++++++++++++++ 3 files changed, 375 insertions(+) create mode 100644 ros2/src/robobin/robobin/helpers/__pycache__/realtime_location_cli_only.cpython-312.pyc create mode 100644 ros2/src/robobin/robobin/helpers/graph_maker.py create mode 100644 ros2/src/robobin/robobin/helpers/realtime_location_cli_only.py diff --git a/ros2/src/robobin/robobin/helpers/__pycache__/realtime_location_cli_only.cpython-312.pyc b/ros2/src/robobin/robobin/helpers/__pycache__/realtime_location_cli_only.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9190690f3276b2c8cc4075a9a6b33e305014630 GIT binary patch literal 13269 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 N=KsLTz#;`sLjY+FmDK<M literal 0 HcmV?d00001 diff --git a/ros2/src/robobin/robobin/helpers/graph_maker.py b/ros2/src/robobin/robobin/helpers/graph_maker.py new file mode 100644 index 00000000..c197c10b --- /dev/null +++ b/ros2/src/robobin/robobin/helpers/graph_maker.py @@ -0,0 +1,112 @@ +import json +import time +import os +from realtime_location_cli_only import AnchorTagCLI + +class GraphMaker: + def __init__(self, port="COM11", testing=False): + self.app = AnchorTagCLI(port=port, testing=testing) + self.graph = { + "name": "Lab 1 Extended", + "nodes": [], + "connections": [] + } + self.previous_node_index = None + + def call_bpm_and_store_anchors(self): + self.app.call_bpm() + for name, (x, y) in self.app.anchors.items(): + self.graph["nodes"].append({ + "name": name, + "x": x, + "y": y + }) + + # Initialize connections matrix with False + num_nodes = len(self.graph["nodes"]) + self.graph["connections"] = [[False] * num_nodes for _ in range(num_nodes)] + + print("Anchor coordinates stored successfully.") + + def start_session(self): + print("Waiting for 'start' command...") + while True: + user_input = input("Enter 'start' to begin adding nodes or 'quit' to exit: ").strip().lower() + if user_input == "start": + print("Session started. Type 'save' to add nodes, 'finish' to save the graph, or 'quit' to exit.") + break + elif user_input == "quit": + print("Exiting...") + exit() + + def add_node_and_connect(self): + print(self.app.tag_distances) + distances = self.app.serial_buffer.getRangingDistances() + + tag1_x, tag1_y = self.app.update_distances_and_calculate_tags() + print(f"{tag1_x}, {tag1_y}") + if tag1_x is None or tag1_y is None: + print("Failed to determine tag position. Skipping node creation.") + return + + new_node_index = len(self.graph["nodes"]) + + # Add new node + self.graph["nodes"].append({ + "name": f"Node{new_node_index+1}", + "x": tag1_x, + "y": tag1_y + }) + + # Dynamically resize the connections matrix + for row in self.graph["connections"]: + row.append(False) # Add a column for the new node + self.graph["connections"].append([False] * (new_node_index + 1)) # Add a new row + + # Update connections + if self.previous_node_index is not None: + self.graph["connections"][self.previous_node_index][new_node_index] = True + self.graph["connections"][new_node_index][self.previous_node_index] = True + + self.previous_node_index = new_node_index + + print(f"Node {new_node_index+1} added at ({tag1_x:.2f}, {tag1_y:.2f}).") + + def save_graph(self): + directory = r"D:\Github\robobin\ros2\src\robobin\robobin\graphs" #Replace with whereever your graphs folder is in the ros2 workspace + os.makedirs(directory, exist_ok=True) # Create the directory if it doesn't exist + file_path = os.path.join(directory, "graph.json") + with open(file_path, "w") as f: + json.dump(self.graph, f, indent=2) + print(f"Graph saved to '{file_path}'.") + + + def run(self): + while True: + user_input = input("Enter command ('bpm', 'start', 'save', 'finish', 'quit'): ").strip().lower() + if user_input == "bpm": + self.call_bpm_and_store_anchors() + elif user_input == "start": + self.start_session() + while True: + user_input = input("Enter 'save' to add a node, 'finish' to save and exit, or 'quit' to exit: ").strip().lower() + if user_input == "save": + self.add_node_and_connect() + elif user_input == "finish": + self.save_graph() + print("Session finished.") + exit() + elif user_input == "quit": + print("Exiting without saving.") + exit() + else: + print("Invalid command. Try 'save', 'finish', or 'quit'.") + elif user_input == "quit": + print("Exiting...") + break + else: + print("Invalid command. Try 'bpm', 'start', 'save', 'finish', or 'quit'.") + +if __name__ == "__main__": + graph_maker = GraphMaker(port="/dev/tty.usbmodem14101", testing=False) + graph_maker.run() diff --git a/ros2/src/robobin/robobin/helpers/realtime_location_cli_only.py b/ros2/src/robobin/robobin/helpers/realtime_location_cli_only.py new file mode 100644 index 00000000..a314cec3 --- /dev/null +++ b/ros2/src/robobin/robobin/helpers/realtime_location_cli_only.py @@ -0,0 +1,263 @@ +import time +import numpy as np +import serial +from scipy.optimize import least_squares + +# Define test values at the top of the file for easy modification +TEST_MEASURED_DISTANCES = [302, 463, 286, 304, 418, 328] +TEST_TAG1_DISTANCES = [22, 107, 246, 295] +TEST_TAG2_DISTANCES = [100, 100, 100, 100] + +class SerialBuffer: + def __init__(self, port): + self.ser = serial.Serial(port, 115200, timeout=1) + + def readFromDevice(self, expectedLines=1): + lines = [] + # Attempt to read expected lines; if device is slow, you may need retries + while len(lines) < expectedLines: + if self.ser.in_waiting: + line = self.ser.readline().decode().strip() + if line: + lines.append(line) + else: + time.sleep(0.01) + return lines + + def getBeaconPositioningDistances(self): + """Reads the measured distances (A,E,D,B,F,C) from the device.""" + self.writeToDevice("bpm") + buffer = self.readFromDevice(1)[0] + values = list(map(float, buffer.split(" "))) + return values + + def getRangingDistances(self): + """Reads the distances from the tags to the anchors.""" + self.writeToDevice("rng") + lines = self.readFromDevice(2) + print(lines) + distances = [] + # First line: Tag 1 distances + distances.append(list(map(float, lines[0][1:].split(" ")))) + # Second line: Tag 2 distances or "0" + if lines[1] != "1": + distances.append(list(map(float, lines[1][1:].split(" ")))) + else: + distances.append(None) + return distances + + def writeToDevice(self, value): + self.ser.write((value + "\n").encode()) + + def __del__(self): + print("Closing port") + self.ser.close() + + +class AnchorTagCLI: + def __init__(self, port="/dev/tty.usbmodem14101", testing=False): + self.testing = testing + self.serial_buffer = SerialBuffer(port) + + # Distances between anchors (A,E,D,B,F,C) + # Corresponding to the measured_distances in original code. + self.measured_distances = [0.0]*6 + + # Distances from Tag 1 to anchors: A1, A2, A3, A4 + self.tag_distances = {"A1": 0.0, "A2": 0.0, "A3": 0.0, "A4": 0.0} + + # Distances from Tag 2 to anchors: A1, A2, A3, A4 + self.tag2_distances = {"A1": 0.0, "A2": 0.0, "A3": 0.0, "A4": 0.0} + + self.anchors = {} + self.anchors_coords_known = False + + if self.testing: + # Set predefined test distances + for i, dist in enumerate(TEST_MEASURED_DISTANCES): + self.measured_distances[i] = dist + + for (anchor, dist) in zip(self.tag_distances.keys(), TEST_TAG1_DISTANCES): + self.tag_distances[anchor] = dist + + for (anchor, dist) in zip(self.tag2_distances.keys(), TEST_TAG2_DISTANCES): + self.tag2_distances[anchor] = dist + + def determine_anchor_coords(self): + try: + measured_distances = np.array(self.measured_distances) + noise_level = 0.0 + measured_distances_noisy = measured_distances + np.random.uniform(-noise_level, noise_level, size=len(measured_distances)) + + # Variables: x_B, y_B, x_C, y_C, y_A + initial_guess = [120, 100, 150, 200, 50] # [x_B, y_B, x_C, y_C, y_A] + maxBounds = 30000 + bounds = ([0, 0, 0, 0, 0], [maxBounds] * 5) + + def error_function(variables, measured): + x_B, y_B, x_C, y_C, y_A = variables + # measured: [A, E, D, B, F, C] + a_measured = measured[0] + e_measured = measured[1] + d_measured = measured[2] + b_measured = measured[3] + f_measured = measured[4] + c_measured = measured[5] + + # A=(0,y_A), B=(x_B,y_B), C=(x_C,y_C), D=(0,0) + a_calc = np.sqrt((x_B - 0)**2 + (y_B - y_A)**2) # A-B + b_calc = np.sqrt((x_C - x_B)**2 + (y_C - y_B)**2) # B-C + c_calc = np.sqrt(x_C**2 + y_C**2) # C-D + d_calc = y_A # A-D + e_calc = np.sqrt(x_C**2 + (y_C - y_A)**2) # A-C + f_calc = np.sqrt(x_B**2 + y_B**2) # B-D + + return [ + a_calc - a_measured, + b_calc - b_measured, + c_calc - c_measured, + d_calc - d_measured, + e_calc - e_measured, + f_calc - f_measured + ] + + # Run least squares optimization + result_noisy = least_squares( + error_function, + initial_guess, + args=(measured_distances_noisy,), + bounds=bounds, + loss='soft_l1' + ) + optimized_coords_noisy = result_noisy.x + + self.anchors = { + "A1": (0, optimized_coords_noisy[4]), + "A2": (optimized_coords_noisy[0], optimized_coords_noisy[1]), + "A3": (optimized_coords_noisy[2], optimized_coords_noisy[3]), + "A4": (0, 0), + } + return {k: (round(v[0], 2), round(v[1], 2)) for k, v in self.anchors.items()} + except Exception as e: + print(f"Error generating anchors: {e}") + + def calculate_tag_coordinates(self, tag_distances): + if not self.anchors_coords_known or len(self.anchors) < 3: + return None, None + + available_beacons = [] + available_distances = [] + for key in tag_distances.keys(): + d = max(float(tag_distances[key]), 0) + available_distances.append(d) + available_beacons.append(self.anchors[key]) + + if len(available_beacons) < 3: + return None, None + + def error_function(vars): + x, y = vars + residuals = [] + for (bx, by), d_measured in zip(available_beacons, available_distances): + d_computed = np.sqrt((x - bx)**2 + (y - by)**2) + residuals.append(d_computed - d_measured) + return residuals + + beacon_xs = [b[0] for b in available_beacons] + beacon_ys = [b[1] for b in available_beacons] + initial_guess = [np.mean(beacon_xs), np.mean(beacon_ys)] + + x_min = min(beacon_xs) - 100 + x_max = max(beacon_xs) + 100 + y_min = min(beacon_ys) - 100 + y_max = max(beacon_ys) + 100 + bounds = ([x_min, y_min], [x_max, y_max]) + + result = least_squares(error_function, initial_guess, bounds=bounds, loss='soft_l1') + x_tag, y_tag = result.x + return x_tag, y_tag + + def call_bpm(self): + if self.testing: + beacon_distances = TEST_MEASURED_DISTANCES + else: + beacon_distances = self.serial_buffer.getBeaconPositioningDistances() + + for i, distance in enumerate(beacon_distances): + if i < len(self.measured_distances): + self.measured_distances[i] = distance + + determined_anchor_coords = self.determine_anchor_coords() + if determined_anchor_coords: + self.anchors_coords_known = True + print("Anchor coordinates determined:") + for anchor, coords in determined_anchor_coords.items(): + print(f"{anchor}: {coords}") + + def update_distances_and_calculate_tags(self): + if not self.anchors_coords_known: + return + + if self.testing: + ranging_distances = [TEST_TAG1_DISTANCES, TEST_TAG2_DISTANCES] + else: + ranging_distances = self.serial_buffer.getRangingDistances() + + # Update tag 1 distances + if ranging_distances[0] is not None: + for i, distance in enumerate(ranging_distances[0]): + anchor = list(self.tag_distances.keys())[i] + self.tag_distances[anchor] = distance + + # Update tag 2 distances + if ranging_distances[1] is not None: + for i, distance in enumerate(ranging_distances[1]): + anchor = list(self.tag2_distances.keys())[i] + self.tag2_distances[anchor] = distance + + # Now calculate both tags + tag1_x, tag1_y = self.calculate_tag_coordinates(self.tag_distances) + valid_tag2_distances = [dist for dist in self.tag2_distances.values() if dist > 0] + + # Check if there are enough valid distances for Tag 2 + if len(valid_tag2_distances) < 3: + print(f"Insufficient valid distances for Tag 2: {len(valid_tag2_distances)} provided.") + tag2_x, tag2_y = None, None + else: + tag2_x, tag2_y = self.calculate_tag_coordinates(self.tag2_distances) + + print("Tag Positions:") + if tag1_x is not None and tag1_y is not None: + print(f"Tag 1: ({tag1_x:.2f}, {tag1_y:.2f})") + else: + print("Tag 1: Not enough data") + + if tag2_x is not None and tag2_y is not None: + print(f"Tag 2: ({tag2_x:.2f}, {tag2_y:.2f})") + else: + print("Tag 2: Not enough data") + + + if tag1_x is not None and tag1_y is not None and tag2_x is not None and tag2_y is not None: + dx = tag2_x - tag1_x + dy = tag2_y - tag1_y + displacement = np.sqrt(dx**2 + dy**2) + angle_deg = np.degrees(np.arctan2(dy, dx)) + if angle_deg < 0: + angle_deg += 360 + print(f"Direction from Tag1 to Tag2: dx={dx:.2f}, dy={dy:.2f}, displacement={displacement:.2f}, angle={angle_deg:.2f}°") + return tag1_x, tag1_y + +if __name__ == "__main__": + app = AnchorTagCLI(port="/dev/tty.usbmodem14101", testing=False) + while True: + user_input = input("Enter command ('bpm' to set anchors, or 'quit' to exit): ").strip().lower() + if user_input == "bpm": + app.call_bpm() + print("Switching to continuous ranging updates (simulating 'rng' messages)...") + while True: + app.update_distances_and_calculate_tags() + time.sleep(1) + elif user_input == "quit": + print("Exiting program.") + break \ No newline at end of file -- GitLab