This commit is contained in:
SchrodingerError 2024-08-14 14:43:31 -05:00
parent 482d724e20
commit 9671846e4c
51 changed files with 0 additions and 8189 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Current File",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"env": {
"PYDEVD_DISABLE_FILE_VALIDATION": "1"
},
"args": [
"-Xfrozen_modules=off"
]
}
]
}

View File

@ -1,3 +0,0 @@
{
"python.REPL.enableREPLSmartSend": false
}

View File

@ -1,197 +0,0 @@
import numpy as np
from Physics_Elements import SharedRigidActuator
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from Object_Sharing import SharedFloat
from Physics_Elements import StateSpace, Spring
class RigidActuatorController():
def __init__(self, controller_settings: dict):
self.controller_settings = controller_settings
self.attached_sim_target: SharedRigidActuator = None
self.shared_attributes: SharedRigidActuatorController = None # For plotting if desired
self.parse_settings()
self.input_voltage: float = 0.0
self.digital_command: int = 0 # 0 means hold position, 1 means control using velocity
self.controlled_length: float = 0.0
self.controlled_vel_scalar: float = 0.0
self.controlled_pos = np.array([0.0, 0.0, 0.0])
self.controlled_vel = np.array([0.0, 0.0, 0.0])
def parse_settings(self):
self.name = self.controller_settings["name"]
self.input_channel = self.controller_settings["analog_input_channel"]
self.digital_channel = self.controller_settings["digital_input_channel"]
self.max_length = self.controller_settings["max_length"]
self.min_length = self.controller_settings["min_length"]
self.neutral_length = self.controller_settings.get("neutral_length", 0)
self.controls_pos_vel_accel = self.controller_settings["controls_pos/vel/accel"]
def calc_neutral_pos(self):
delta_pos_from_child = -1 * self.neutral_length * self.r_unit_vector
self.neutral_pos = self.attached_sim_target.get_child_statespace().get_pos() + delta_pos_from_child
def get_input_channel(self) -> int:
return self.input_channel
def get_digital_channel(self) -> int:
return self.digital_channel
def attach_sim_target(self, target: 'SharedRigidActuator'):
if not isinstance(target._getvalue(), SharedRigidActuator):
raise TypeError(f"target for RigidActuatorController {self.name} is not a SharedRigidActuator")
self.attached_sim_target = target
self.r_unit_vector = self.get_r_unit_vector()
self.calc_neutral_pos()
self.controlled_pos = self.neutral_pos
self.attached_sim_target.set_parent_statespace_pos(self.controlled_pos)
def attach_shared_attributes(self, shared_attributes: 'SharedRigidActuatorController'):
if not isinstance(shared_attributes._getvalue(), SharedRigidActuatorController):
raise TypeError(f"shared_attributes for RigidActuatorController {self.name} is not a SharedRigidActuatorController")
self.shared_attributes = shared_attributes
def get_r_unit_vector(self):
return self.attached_sim_target.get_r_unit_vector()
def set_input_voltage(self, voltage: float):
'''After reading a voltage, set it to this controller element'''
self.input_voltage = voltage
def set_digital_command(self, command: int):
self.digital_command = command
def set_controlled_attribute(self):
if self.controls_pos_vel_accel == 0: # Control pos
self.controlled_length = (self.input_voltage - self.controller_settings["neutral_voltage"]) * self.controller_settings["units_per_volt"] + self.neutral_length
self.set_controlled_pos(self.controlled_length)
elif self.controls_pos_vel_accel == 1: # Controls vel
self.set_controlled_vel()
elif self.controls_pos_vel_accel == 2: # Controls accel
self.set_controlled_accel()
def set_controlled_vel(self):
# Check if the controlled vel would put us outside our max or min displacement
# if it is, set controlled vel to 0 and set our controlled pos manually
current_length = self.attached_sim_target.get_length()
self.controlled_vel_scalar = (self.input_voltage - self.controller_settings["neutral_voltage"]) * self.controller_settings["units_per_volt"]
if current_length > self.max_length:
self.set_controlled_pos(self.max_length)
if self.controlled_vel_scalar > 0:
self.controlled_vel_scalar = 0
elif current_length < self.min_length:
self.set_controlled_pos(self.min_length)
if self.controlled_vel_scalar < 0:
self.controlled_vel_scalar = 0
self.r_unit_vector = self.get_r_unit_vector()
controlled_vel = -1*self.r_unit_vector * self.controlled_vel_scalar # -1 * r vector becaus r vector describes child - parent
self.controlled_vel = controlled_vel
def set_controlled_accel(self):
pass
def set_controlled_pos(self, length:float):
self.r_unit_vector = self.get_r_unit_vector()
if length > self.max_length:
self.controlled_length = self.max_length
elif length < self.min_length:
self.controlled_length = self.min_length
delta_pos_from_child = self.controlled_length * -1*self.r_unit_vector # -1 * r vector becaus r vector describes child - parent
child_pos = self.attached_sim_target.get_child_statespace().get_pos()
self.controlled_pos = delta_pos_from_child + child_pos
def update_shared_attributes(self):
if self.shared_attributes is not None:
self.shared_attributes.set_digital_command(self.digital_command)
self.shared_attributes.set_input_voltage(self.input_voltage)
self.shared_attributes.set_controlled_pos(self.controlled_pos)
self.shared_attributes.set_controlled_vel(self.controlled_vel)
self.shared_attributes.set_controlled_length(self.controlled_length)
self.shared_attributes.set_controlled_vel_scalar(self.controlled_vel_scalar)
def update_sim_target(self):
'''Update the sim target with the values we input from the controller'''
if self.controls_pos_vel_accel == 0: # Control pos
if self.digital_command == 1: # We are actively controlling the actuator
self.attached_sim_target.set_parent_statespace_pos(self.controlled_pos)
elif self.controls_pos_vel_accel == 1: # Controls vel
if self.digital_command == 0: # We are NOT actively controlling the actuator
self.attached_sim_target.set_parent_statespace_vel([0,0,0])
else: # We are actively controlling the vel
self.attached_sim_target.set_parent_statespace_vel(self.controlled_vel)
elif self.controls_pos_vel_accel == 2: # Controls accel
pass
class SharedRigidActuatorController():
def __init__(self):
self.input_voltage: float = 0.0
self.digital_command: int = 0
self.controlled_length: float = 0.0
self.controlled_vel_scalar: float = 0.0
self.controlled_pos = np.array([0.0, 0.0, 0.0])
self.controlled_vel = np.array([0.0, 0.0, 0.0])
self.connected_to_plotter: bool = False
self.connected_to_visualization: bool = False
self.connected_to_sensor: bool = False
self.connected_to_controller: bool = False
def set_input_voltage(self, voltage: float):
self.input_voltage = voltage
def set_digital_command(self, command: int):
self.digital_command = command
def set_controlled_pos(self, new_pos):
self.controlled_pos = new_pos
def set_controlled_vel(self, new_vel):
self.controlled_vel = new_vel
def set_controlled_length(self, disp: float) -> None:
self.controlled_length = disp
def set_controlled_vel_scalar(self, vel_scalar: float) -> None:
self.controlled_vel_scalar = vel_scalar
def set_connected_to_plotter(self, state: bool) -> None:
self.connected_to_plotter = state
def set_connected_to_visualization(self, state: bool) -> None:
self.connected_to_visualization = state
def set_connected_to_sensor(self, state: bool) -> None:
self.connected_to_sensor = state
def set_connected_to_controller(self, state: bool) -> None:
self.connected_to_controller = state
def get_connected_to_plotter(self) -> bool:
return self.connected_to_plotter
def get_connected_to_visualization(self) -> bool:
return self.connected_to_visualization
def get_connected_to_sensor(self) -> bool:
return self.connected_to_sensor
def get_connected_to_controller(self) -> bool:
return self.connected_to_controller
def get_controlled_pos(self):
return self.controlled_pos
def get_controlled_vel(self):
return self.controlled_vel
def get_controlled_length(self) -> float:
return self.controlled_length
def get_controlled_vel_scalar(self) -> float:
return self.controlled_vel_scalar
def get_input_voltage(self) -> float:
return self.input_voltage
def get_digital_command(self) -> int:
return self.digital_command

View File

@ -1,147 +0,0 @@
import time
from mcculw.ul import ignore_instacal
import threading
from Physics_Elements import Joint, Spring, StateSpace
from MCC_Interface import MCC3114, MCC202
from typing import List
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from Sensor_Elements import LoadCell, SharedLoadCellJoint, LoadCellSpring, SharedLoadCellSpring, DisplacementSensor
from Controller_Input_Elements import RigidActuatorController
class HITLController():
def __init__(self, stop_event, settings: dict):
self.stop_event = stop_event
self.settings = settings
self.parse_settings()
self.load_cells: List[LoadCell] = []
#self.displacement_actuators: List[DisplacementSensor] = []
self.displacement_sensors: List[DisplacementSensor] = []
self.rigid_actuator_controllers: List[RigidActuatorController] = []
def parse_settings(self):
'''Parse the settings to get the settings for the MCC devices'''
self.pull_from_sim_period = self.settings["pull_from_sim_period"] / 1000.0
self.updated_plotting_shared_attributes_period = self.settings["plotting_update_period"] / 1000.0
def attach_load_cell(self, load_cell: 'LoadCell'):
self.load_cells.append(load_cell)
def attach_displacement_sensor(self, sensor: 'DisplacementSensor'):
self.displacement_sensors.append(sensor)
def attach_rigid_actuator_controller(self, controller: 'RigidActuatorController'):
self.rigid_actuator_controllers.append(controller)
def update_from_sim(self):
# Updates the local sensors based on the sim values
for lc in self.load_cells:
lc.pull_from_sim()
# for rigid_actuator in self.displacement_actuators:
# rigid_actuator.pull_from_sim()
for sensor in self.displacement_sensors:
sensor.pull_from_sim()
def update_from_sim_thread(self):
while self.spare_stop_event.is_set() == False:
self.update_from_sim()
time.sleep(self.pull_from_sim_period / 1000)
def update_internal_sensor_attributes(self):
'''Updates internal attributes. This should be updated as fast as possible because it generates
sensor noise.'''
for lc in self.load_cells:
lc.update_internal_attributes()
# for rigid_actuator in self.displacement_actuators:
# rigid_actuator.update_internal_attributes()
for sensor in self.displacement_sensors:
sensor.update_internal_attributes()
def update_plotting_shared_attributes(self):
for lc in self.load_cells:
lc.update_shared_attributes()
for rigid_actuator_controller in self.rigid_actuator_controllers:
rigid_actuator_controller.update_shared_attributes()
# for rigid_actuator in self.displacement_actuators:
# rigid_actuator.update_shared_attributes()
for sensor in self.displacement_sensors:
sensor.update_shared_attributes()
def update_plotting_thread(self):
while self.spare_stop_event.is_set() == False:
self.update_plotting_shared_attributes()
time.sleep(self.updated_plotting_shared_attributes_period / 1000)
def update_mcc3114(self):
for lc in self.load_cells:
channel = lc.sensor_settings["output_channel"]
voltage = lc.get_noisy_voltage_scalar()
self.mcc3114.voltage_write(channel, voltage)
for sensor in self.displacement_sensors:
channel = sensor.sensor_settings["output_channel"]
voltage = sensor.get_noisy_voltage_scalar()
self.mcc3114.voltage_write(channel, voltage)
def read_from_mcc202(self):
for rigid_actuator_controller in self.rigid_actuator_controllers:
input_channel:int = rigid_actuator_controller.get_input_channel()
voltage = self.mcc202.voltage_read(channel=input_channel)
digital_channel:int = rigid_actuator_controller.get_digital_channel()
digital_command = self.mcc202.digital_read(channel=digital_channel)
rigid_actuator_controller.set_digital_command(digital_command)
rigid_actuator_controller.set_input_voltage(voltage)
rigid_actuator_controller.set_controlled_attribute()
def update_sim_targets(self):
for rigid_actuator_controller in self.rigid_actuator_controllers:
rigid_actuator_controller.update_sim_target()
def controller_loop(self):
while self.stop_event.is_set() == False:
self.loop_time = time.time()
# Update the internal attributes of the sensors
self.update_internal_sensor_attributes()
# Update MCC3114 (analog output)
self.update_mcc3114()
# Get readings from the MCC202
self.read_from_mcc202()
# Update the shared actuators for the sim to respond to
self.update_sim_targets()
def start_mcc(self):
ignore_instacal() # ONLY CALL ONCE
self.mcc3114 = MCC3114()
self.mcc202 = MCC202()
def run_process(self):
self.start_mcc()
# Make the threads for interacting with shared elements
self.spare_stop_event = threading.Event()
self.spare_stop_event.clear()
updating_plotting_elements_thread = threading.Thread(target=self.update_plotting_thread)
update_from_sim_thread = threading.Thread(target=self.update_from_sim_thread)
updating_plotting_elements_thread.start()
update_from_sim_thread.start()
self.controller_loop()
self.spare_stop_event.set()
updating_plotting_elements_thread.join()
update_from_sim_thread.join()

View File

@ -1,200 +0,0 @@
from mcculw import ul as mcculw
from mcculw.enums import (AiChanType, BoardInfo, Status, InterfaceType,
FunctionType, InfoType, ScanOptions,
ULRange, AnalogInputMode, DigitalPortType, DigitalIODirection)
import time
class MCC3114():
def __init__(self):
self.range = ULRange.BIP10VOLTS
self.board_num = self.setup_device()
if self.board_num == -1:
print("Could not setup MCC3114")
self.config_outputs()
self.current_voltage_outputs: list[float] = [0.0]*16
self.set_all_to_zero()
def __del__(self):
self.set_all_to_zero()
def setup_device(self) -> int:
'''Sets up the device without Instacal configuration files'''
board_num = 0
board_index = 0
find_device = "USB-3114"
board_num = -1
dev_list = mcculw.get_daq_device_inventory(InterfaceType.USB)
if len(dev_list) > 0:
for device in dev_list:
if str(device) == find_device:
board_num = board_index
mcculw.create_daq_device(board_num, device)
board_index = board_index + 1
if board_num == -1:
print(f"Device {find_device} not found")
return board_num
else:
print("No devices detected")
return board_num
return board_num
def config_outputs(self):
if self.board_num == -1:
return
for ch in range(16):
mcculw.set_config(
InfoType.BOARDINFO, self.board_num, ch,
BoardInfo.DACRANGE, self.range
)
def set_all_to_zero(self):
'''Make the voltage outputs be 0.0 V when the program exits or we delete the object'''
if self.board_num == -1:
return
for i in range(16):
self.voltage_write(channel=i, voltage=0.0)
def voltage_write(self, channel:int, voltage: float):
if self.board_num == -1:
return
if channel < 0 or channel > 15:
print(f"Channel: {channel} is out of range for the MCC USB-3114. Valid range is [0, 15]")
return
if voltage < -10:
print(f"Voltage: {voltage} is out of range for the MCC USB-3114 in this configuration. Valid range is [-10, 10] V")
voltage = -10
if voltage > 10:
print(f"Voltage: {voltage} is out of range for the MCC USB-3114 in this configuration. Valid range is [-10, 10] V")
voltage = 10
try:
mcculw.v_out(self.board_num, channel, self.range, voltage)
self.current_voltage_outputs[channel] = voltage
except Exception as exc:
print(f"Exception occurred doing voltage write to MCC USB-3114: {exc}")
class MCC202():
def __init__(self):
self.range = ULRange.BIP10VOLTS
self.digital_type = DigitalPortType.AUXPORT
self.board_num = self.setup_device()
if self.board_num == -1:
print("Could not setup MCC202")
self.current_analog_inputs: list[float] = [0.0] * 8
self.current_digital_inputs: list[int] = [0] * 8
def __del__(self):
pass # No specific cleanup required for inputs
def setup_device(self) -> int:
'''Sets up the device without Instacal configuration files'''
board_num = 0
board_index = 0
find_device = "USB-202"
board_num = -1
dev_list = mcculw.get_daq_device_inventory(InterfaceType.USB)
if len(dev_list) > 0:
for device in dev_list:
if str(device) == find_device:
board_num = board_index
mcculw.create_daq_device(board_num, device)
board_index = board_index + 1
if board_num == -1:
print(f"Device {find_device} not found")
return board_num
else:
print("No devices detected")
return board_num
return board_num
def config_inputs(self):
if self.board_num == -1:
return
for ch in range(8):
mcculw.set_config(
InfoType.BOARDINFO, self.board_num, ch,
BoardInfo.RANGE, self.range
)
for ch in range(8):
mcculw.d_config_port(self.board_num, self.digital_type, DigitalIODirection.IN)
def print_all_channels(self):
for i in range(8):
print(f"Analog channel {i} reads {self.current_analog_inputs[i]} Volts")
for i in range(8):
print(f"Digital channel {i} reads {self.current_digital_inputs[i]}")
def voltage_read(self, channel: int) -> float:
if self.board_num == -1:
return 0.0
if channel < 0 or channel > 7:
print(f"Channel: {channel} is out of range for the MCC USB-202. Valid range is [0, 7]")
return 0.0
try:
voltage = mcculw.v_in(self.board_num, channel, self.range)
self.current_analog_inputs[channel] = voltage
return voltage
except Exception as exc:
print(f"Exception occurred doing voltage read from MCC USB-202: {exc}")
return 0.0
def read_all_analog_channels(self):
'''Read all analog input channels and update the current_analog_inputs list'''
for i in range(8):
self.voltage_read(i)
def digital_read(self, channel: int) -> int:
if self.board_num == -1:
return -1
if channel < 0 or channel > 7:
print(f"Digital channel: {channel} is out of range for the MCC USB-202. Valid range is [0, 7]")
return -1
try:
state = mcculw.d_bit_in(self.board_num, self.digital_type, channel)
self.current_digital_inputs[channel] = state
return state
except Exception as exc:
print(f"Exception occurred doing digital read from MCC USB-202: {exc}")
return -1
def read_all_digital_channels(self):
'''Read all digital input channels and update the current_digital_inputs list'''
for i in range(8):
self.digital_read(i)
def read_all_channels(self):
for i in range(8):
self.voltage_read(i)
self.digital_read(i)
def main():
mcculw.ignore_instacal() # ONLY CALL ONCE
#mcc3114 = MCC3114()
mcc202 = MCC202()
while True:
mcc202.read_all_channels()
mcc202.print_all_channels()
time.sleep(3)
#mcc3114.voltage_write(1, 5)
#mcc3114.voltage_write(7, 5)
#time.sleep(100)
if __name__ == "__main__":
main()

View File

@ -1,9 +0,0 @@
class SharedFloat:
def __init__(self, initial_value:float=0.0):
self.value = initial_value
def get(self):
return self.value
def set(self, new_value):
self.value = new_value

View File

@ -1,711 +0,0 @@
from typing import List, Callable
import numpy as np
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from Physics_Elements import StateSpace, Joint, Spring, SharedRigidActuator, SharedSpring
ZERO_VECTOR = np.array([0.0, 0.0, 0.0])
'''
TODO
1. Change spring_force to just force
Include getters and setters, lest important for attributes
2. Add Object_Sharing objects to this file
'''
def stiffness_function_const_then_rigid(length, unstretched_length):
percent_length = 0.9 # For the first bit of length in compression, it follors k*x
k = 1000
if length < (1-percent_length)* unstretched_length:
# It is really compressed, so make k = k *10
k = k + k*10/(1 + np.exp(-10*(unstretched_length - length - percent_length*unstretched_length)))
return k
def damping_force_x(mass: 'Joint', t):
x_vel = mass.statespace.get_vel()[0]
a = 1
b = 0.01
return x_vel*a*-1 + x_vel**2 * b*-1
def damping_force_y(mass: 'Joint', t):
y_vel = mass.statespace.get_vel()[1]
a = 1
b = 0.01
return y_vel*a*-1 + y_vel**2 * b*-1
def damping_force_z(mass: 'Joint', t):
z_vel = mass.statespace.get_vel()[2]
a = 1
b = 0.01
return z_vel*a*-1 + z_vel**2 * b*-1
'''
TODO
'''
class StateSpace():
def __init__(self, pos=np.copy(ZERO_VECTOR), vel=np.copy(ZERO_VECTOR), accel=np.copy(ZERO_VECTOR), force=np.copy(ZERO_VECTOR), integration_method="simple"):
self.pos = pos
self.vel = vel
self.accel = accel
self.force = force
self.integration_method = self.parse_integration_method(integration_method)
self.max_saved = 2
self.previous_positions: list[list[float, float, float]] = []
self.previous_vel: list[list[float, float, float]] = []
self.previous_accel: list[list[float, float, float]] = []
self.verlet_constants = [
[],
[],
[-1, 2, 1],
[0, -1, 2, 1],
[1/11, -4/11, -6/11, 20/11, 12/11],
]
def parse_integration_method(self, method_str):
if method_str == "simple":
return 1
elif method_str == "verlet":
return 2
elif method_str == "adams-bashforth":
return 3
def integrate(self, delta_t):
'''
NOTE: This is run after accel has been updated.
'''
if self.integration_method == 1:
self.vel = self.vel + self.accel * delta_t
self.pos = self.pos + self.vel * delta_t
elif self.integration_method == 2:
# Verlet integration
self.save_last()
if delta_t >= 0.005:
self.vel = self.vel + self.accel * delta_t
self.pos = self.pos + self.vel * delta_t
else:
self.vel = self.vel + self.accel * delta_t
self.verlet_integration(delta_t)
elif self.integration_method == 3: # Adams-Bashforth
self.save_last()
if delta_t >= 0.005:
self.vel = self.vel + self.accel * delta_t
self.pos = self.pos + self.vel * delta_t
else:
self.adams_bashforth_integration(delta_t)
def print(self):
print(f"Pos: {self.pos}, Vel: {self.vel}, Accel: {self.accel}")
def adams_bashforth_integration(self, delta_t):
'''
Testing.
2 points seems to damp the system. 3 does better at maintaining. Above 3 it is worse
'''
num_prev_accel = len(self.previous_accel)
match num_prev_accel:
case 0 | 1:
self.vel = self.vel + self.accel * delta_t
case 2:
self.vel = self.vel + delta_t * (1.5 * self.previous_accel[-1] - 0.5 * self.previous_accel[-2])
case 3:
self.vel = self.vel + delta_t * (23/12 * self.previous_accel[-1] - 4/3 * self.previous_accel[-2] + 5/12 * self.previous_accel[-3])
case 4:
self.vel = self.vel + delta_t * (55/24 * self.previous_accel[-1] - 59/24 * self.previous_accel[-2] + 37/24 * self.previous_accel[-3] - 9/24 * self.previous_accel[-4])
case 5:
self.vel = self.vel + delta_t * ((1901 * self.previous_accel[-1] - 2774 * self.previous_accel[-2] + 2616 * self.previous_accel[-3] - 1274 * self.previous_accel[-4] + 251 * self.previous_accel[-5]) / 720)
case _:
self.vel = self.vel + self.accel * delta_t
self.pos = self.pos + self.vel * delta_t
def verlet_integration(self, delta_t):
num_prev_positions = len(self.previous_positions)
match num_prev_positions:
case 0 | 1:
self.pos = self.pos + self.vel * delta_t
case 2:
new_pos = np.array([0.0, 0.0, 0.0])
for i in range(2):
new_pos += self.verlet_constants[2][i] * self.previous_positions[i]
new_pos += self.verlet_constants[2][-1] * delta_t * self.accel * delta_t
self.pos = new_pos
case 3:
new_pos = np.array([0.0, 0.0, 0.0])
for i in range(3):
new_pos += self.verlet_constants[3][i] * self.previous_positions[i]
new_pos += self.verlet_constants[3][-1] * delta_t * self.accel * delta_t
self.pos = new_pos
case 4:
new_pos = np.array([0.0, 0.0, 0.0])
for i in range(4):
new_pos += self.verlet_constants[4][i] * self.previous_positions[i]
new_pos += self.verlet_constants[4][-1] * delta_t * self.accel * delta_t
self.pos = new_pos
case _:
self.pos = self.pos + self.vel * delta_t
def save_last(self):
self.previous_positions.append(np.copy(self.pos))
self.previous_positions = self.previous_positions[-1*self.max_saved:]
self.previous_vel.append(np.copy(self.vel))
self.previous_vel = self.previous_vel[-1*self.max_saved:]
self.previous_accel.append(np.copy(self.accel))
self.previous_accel = self.previous_accel[-1*self.max_saved:]
def save_last_statespace(self):
if self.last_statespace is None:
self.last_statespace = StateSpace()
self.last_statespace.pos = np.copy(self.pos)
self.last_statespace.vel = np.copy(self.vel)
self.last_statespace.accel = np.copy(self.accel)
def set_pos(self, new_pos):
self.pos = new_pos
def set_vel(self, new_vel):
self.vel = new_vel
def set_accel(self, new_accel):
self.accel = new_accel
def set_force(self, new_force):
self.force = new_force
def get_pos(self):
return self.pos
def get_vel(self):
return self.vel
def get_accel(self):
return self.accel
def get_force(self):
return self.force
class Joint():
def __init__(self, pos=np.copy(ZERO_VECTOR), mass: float=0.1, fixed: bool=False, name: str="Joint", integration_method:str="simple"):
self.name = name
self.statespace = StateSpace(pos, ZERO_VECTOR, ZERO_VECTOR, ZERO_VECTOR, integration_method=integration_method)
self.connected_springs: List[Spring] = []
self.constant_external_forces: List[List[float, float, float]] = []
self.connected_actuators: List[RigidActuator] = []
self.variable_external_forces = [[], [], []]
self.fixed = fixed
self.connected_to_rigid_actuator = False
self.mass = mass
self.parent_joints: List[Joint] = []
self.child_joints: List[Joint] = []
self.damping = False
self.sharing_attributes = False
self.shared_attributes = None
def is_same(self, other_joint: 'Joint'):
if self.name == other_joint.get_name():
return True
return False
def get_statespace(self):
return self.statespace
def get_pos(self):
return self.statespace.get_pos()
def get_vel(self):
return self.statespace.get_vel()
def get_accel(self):
return self.statespace.get_accel()
def set_state_space_pos(self, new_pos):
self.statespace.set_pos(new_pos)
def set_state_space_vel(self, new_vel):
self.statespace.set_vel(new_vel)
def set_connected_to_rigid_actuator(self, state):
self.connected_to_rigid_actuator = state
def get_connected_springs(self):
return self.connected_springs
def get_connected_actuators(self):
return self.connected_actuators
def get_name(self):
return self.name
def add_spring(self, new_spring):
self.connected_springs.append(new_spring)
def add_actuator(self, new_actuator):
self.connected_actuators.append(new_actuator)
def add_constant_force(self, new_force):
self.constant_external_forces.append(new_force)
def integrate_statespace(self, delta_t):
self.statespace.integrate(delta_t)
def add_gravity(self):
gravity_force = np.array([
0,
-9.81 * self.mass,
0
])
self.add_constant_force(gravity_force)
def add_variable_force(self, force_vect: list):
if force_vect[0] is not None:
self.variable_external_forces[0].append(force_vect[0])
if force_vect[1] is not None:
self.variable_external_forces[1].append(force_vect[1])
if force_vect[2] is not None:
self.variable_external_forces[2].append(force_vect[2])
def calc_net_spring_force(self):
net_spring_force = 0
for spring in self.get_connected_springs():
if self.is_same(spring.get_parent_joint()):
spring_force = spring.get_spring_force() # This is the force the spring exerts on the joint ASSUMING self is parent_joint of the spring
else:
spring_force = -1*spring.get_spring_force()
net_spring_force += spring_force
return net_spring_force
def calc_net_constant_external_force(self):
net_external_force = np.copy(ZERO_VECTOR)
for force in self.constant_external_forces:
net_external_force += force
return net_external_force
def calc_net_variable_external_force(self, t):
net_variable_external_force = np.array([0.0, 0.0, 0.0])
for i in range(3):
for func in self.variable_external_forces[i]:
force = func(t)
net_variable_external_force[i] += force
return net_variable_external_force
def calc_net_force(self, t):
net_force = np.copy(ZERO_VECTOR)
if len(self.constant_external_forces) != 0:
net_force += self.calc_net_constant_external_force()
if len(self.variable_external_forces) != 0:
net_force += self.calc_net_variable_external_force(t)
net_spring_force = self.calc_net_spring_force()
if self.damping == True:
net_force += self.calc_damping(net_spring_force)
net_force += net_spring_force
self.statespace.set_force(net_force)
return self.statespace.get_force()
def add_damping(self, vel_ratio=0.25, mom_ratio=None):
self.damping = True
if mom_ratio is not None:
self.damping_vel_ratio = self.mass * mom_ratio
else:
self.damping_vel_ratio = vel_ratio
def calc_damping(self, spring_force):
vel_damping = self.damping_vel_ratio * (self.statespace.get_vel()) * -1
return vel_damping
def calc_accel(self):
if self.fixed == False and self.connected_to_rigid_actuator == False:
self.statespace.set_accel(self.statespace.get_force() / self.mass)
def is_in_list(self, joints: list['Joint']):
for j in joints:
if self.is_same(j):
return True
return False
def find_parent_joints(self):
for spring in self.connected_springs:
if self.is_same(spring.get_child_joint()):
parent_joint = spring.get_parent_joint()
if not parent_joint.is_in_list(self.parent_joints):
self.parent_joints.append(parent_joint)
def find_child_joints(self):
for spring in self.connected_springs:
if self.is_same(spring.get_parent_joint()):
child_joint = spring.get_child_joint()
if not child_joint.is_in_list(self.child_joints):
self.child_joints.append(child_joint)
def print_attributes(self):
print(f"Joint: {self.name}")
print(f"Force: {self.statespace.get_force()}")
print(f"Accel: {self.statespace.get_accel()}")
print(f"Vel: {self.statespace.get_vel()}")
print(f"Pos: {self.statespace.get_pos()}")
def attach_shared_attributes(self, shared_attributes: 'SharedJoint'):
if not isinstance(shared_attributes._getvalue(), SharedJoint):
raise TypeError(f"shared_attributes for Joint {self.name} is not a SharedJoint")
self.sharing_attributes = True
self.shared_attributes = shared_attributes
self.shared_attributes.set_name(f"{self.name}")
self.update_shared_attributes()
def update_shared_attributes(self):
if self.shared_attributes is not None:
self.shared_attributes.set_statespace(self.statespace)
class Spring():
def __init__(self, parent_joint: 'Joint', child_joint: 'Joint', unstretched_length: float=0, constant_stiffness: float = None, stiffness_func: Callable[[float, float], float]=None, name: str="Spring"):
self.name = name
self.parent_joint = parent_joint
self.child_joint = child_joint
self.unstretched_length = unstretched_length
self.stiffness_func = stiffness_func
self.constant_stiffness = constant_stiffness
self.current_stiffness = 0.0
self.vector = self.calc_r_vector()
self.length = self.calc_length()
self.unit_vector: list[float] = self.calc_r_unit_vector()
self.spring_force_scalar: float = 0.0
self.spring_force: list[float] = self.calc_spring_force()
self.parent_joint.add_spring(self)
self.child_joint.add_spring(self)
self.sharing_attributes = False
self.shared_attributes: 'SharedSpring' = None
def get_name(self):
return self.name
def get_parent_joint(self):
return self.parent_joint
def get_child_joint(self):
return self.child_joint
def get_spring_force(self):
return self.spring_force
def calc_r_vector(self):
return self.child_joint.get_statespace().get_pos() - self.parent_joint.get_statespace().get_pos()
def calc_length(self):
return ((self.vector[0])**2 + (self.vector[1])**2 + (self.vector[2])**2) ** 0.5
def calc_r_unit_vector(self):
return self.vector / self.length
def calc_stiffness(self):
if self.stiffness_func is not None:
return self.stiffness_func(self.length, self.unstretched_length)
else:
return self.constant_stiffness
def calc_spring_force(self):
'''Positive force is in tension. Aka, the spring PULLS on the other object'''
self.length = self.calc_length()
del_length = self.length - self.unstretched_length
self.current_stiffness = self.calc_stiffness()
self.spring_force_scalar = del_length * self.current_stiffness
self.vector = self.calc_r_vector()
self.r_unit_vector = self.calc_r_unit_vector()
self.spring_force = self.spring_force_scalar * self.r_unit_vector
return self.spring_force
def print_attributes(self):
print(f"Spring: {self.name}")
print(f"Length: {self.length}")
print(f"Spring Force: {self.spring_force}")
def get_pos(self):
return self.parent_statespace.get_pos()
def get_axis_vector(self):
return self.child_statespace.get_pos() - self.parent_statespace.get_pos()
def attach_shared_attributes(self, shared_attributes: 'SharedSpring'):
if not isinstance(shared_attributes._getvalue(), SharedSpring):
raise TypeError(f"shared_attributes for Spring {self.name} is not a SharedSpring")
self.sharing_attributes = True
self.shared_attributes = shared_attributes
self.update_shared_attributes()
def update_shared_attributes(self):
if self.shared_attributes is not None:
self.shared_attributes.set_child_statespace(self.child_joint.get_statespace())
self.shared_attributes.set_parent_statespace(self.parent_joint.get_statespace())
self.shared_attributes.set_spring_force(self.spring_force)
self.shared_attributes.set_spring_force_scalar(self.spring_force_scalar)
self.shared_attributes.set_length(self.length)
self.shared_attributes.set_unstretched_length(self.unstretched_length)
self.shared_attributes.set_stiffness(self.current_stiffness)
def is_same(self, other_spring: 'Spring'):
if self.name == other_spring.get_name():
return True
return False
def is_in_list(self, springs: list['Spring']):
for j in springs:
if self.is_same(j):
return True
return False
'''
It connects 2 joints.
1 joint is completely fixed. Cannot move. Ever. It will be referred to as "grounded".
The other joint is contrained to the grounded joint. The actuator controls the vector displacement <x,y,z> from the grounded joint
'''
class RigidActuator():
def __init__(self, parent_joint: 'Joint', grounded_joint: 'Joint', name: str = "Rigid Actuator", max_length:float=1, min_length:float=0.1, control_code=0):
self.name = name
self.parent_joint = parent_joint
self.grounded_joint = grounded_joint # Same as child joint
self.parent_joint.add_actuator(self)
self.parent_joint.set_connected_to_rigid_actuator(True)
self.grounded_joint.add_actuator(self)
self.grounded_joint.set_connected_to_rigid_actuator(True)
self.max_length = max_length
self.min_length = min_length
self.control_code = control_code # 0 for pos, 1 for vel, 2 for accel
self.calc_r_unit_vector()
self.shared_attributes: 'SharedRigidActuator' = None
def get_name(self):
return self.name
def get_parent_joint(self):
return self.parent_joint
def get_child_joint(self):
return self.grounded_joint
def is_same(self, other: 'RigidActuator'):
if self.name == other.get_name():
return True
return False
def is_in_list(self, actuators: list['RigidActuator']):
for j in actuators:
if self.is_same(j):
return True
return False
def calc_r_vector(self):
return self.grounded_joint.get_statespace().get_pos() - self.parent_joint.get_statespace().get_pos()
def calc_length(self):
return ((self.vector[0])**2 + (self.vector[1])**2 + (self.vector[2])**2) ** 0.5
def calc_r_unit_vector(self):
self.vector = self.calc_r_vector()
self.length = self.calc_length()
return self.vector / self.length
def get_pos(self):
return self.parent_joint.get_statespace().get_pos()
def get_axis_vector(self):
return self.grounded_joint.get_statespace().get_pos() - self.parent_joint.get_statespace().get_pos()
def get_r_unit_vector(self):
return self.calc_r_unit_vector()
def update_shared_attributes(self):
# These 2 lines are used to make sure we set the internal properties correctly
self.vector = self.calc_r_vector()
self.length = self.calc_length()
# END
if self.shared_attributes is not None:
self.shared_attributes.set_child_statespace(self.grounded_joint.get_statespace())
self.shared_attributes.set_parent_statespace(self.parent_joint.get_statespace())
self.shared_attributes.set_length(self.length)
def attach_shared_attributes(self, shared_attributes: 'SharedRigidActuator'):
if not isinstance(shared_attributes._getvalue(), SharedRigidActuator):
raise TypeError(f"shared_attributes for RigidActuator {self.name} is not a SharedRigidActuator")
self.sharing_attributes = True
self.shared_attributes = shared_attributes
self.update_shared_attributes()
def set_parent_pos(self, new_pos):
'''Set the position of the parent joint's statestpace'''
self.parent_joint.set_state_space_pos(new_pos)
def update_from_controller(self):
if self.control_code == 0: # Controller controls pos
self.set_pos_to_controlled_pos()
elif self.control_code == 1: # Controller controls vel
self.set_vel_to_controlled_vel()
elif self.control_code == 2: # Controls controls accel
pass
def set_vel_to_controlled_vel(self):
controlled_vel = self.shared_attributes.get_vel()
self.parent_joint.set_state_space_vel(controlled_vel)
def set_pos_to_controlled_pos(self):
controlled_pos = self.shared_attributes.get_pos()
self.parent_joint.set_state_space_pos(controlled_pos)
class SharedPhysicsElement():
def __init__(self) -> None:
self.name: str = None
self.statespace: StateSpace = None
self.connected_to_plotter: bool = False
self.connected_to_visualization: bool = False
self.connected_to_sensor: bool = False
self.connected_to_controller: bool = False
def set_name(self, name:str) -> None:
self.name = name
def set_statespace(self, statespace:StateSpace) -> None:
self.statespace = statespace
def set_connected_to_plotter(self, state: bool) -> None:
self.connected_to_plotter = state
def set_connected_to_visualization(self, state: bool) -> None:
self.connected_to_visualization = state
def set_connected_to_sensor(self, state: bool) -> None:
self.connected_to_sensor = state
def set_connected_to_controller(self, state: bool) -> None:
self.connected_to_controller = state
def get_name(self) -> str:
return self.name
def get_statespace(self) -> StateSpace:
return self.statespace
def get_pos(self) -> List[float]:
return self.statespace.get_pos()
def get_vel(self) -> List[float]:
return self.statespace.get_vel()
def get_accel(self) -> List[float]:
return self.statespace.get_accel()
def get_force(self) -> List[float]:
return self.statespace.get_force()
def get_connected_to_plotter(self) -> bool:
return self.connected_to_plotter
def get_connected_to_visualization(self) -> bool:
return self.connected_to_visualization
def get_connected_to_sensor(self) -> bool:
return self.connected_to_sensor
def get_connected_to_controller(self) -> bool:
return self.connected_to_controller
class SharedJoint(SharedPhysicsElement):
def _init__(self) -> None:
super().__init__()
class SharedSpring(SharedPhysicsElement):
'''self.statespace is the statespace of the parent'''
def _init__(self) -> None:
super().__init__()
self.child_statespace: StateSpace = None
self.unstretched_length: float = None
self.length: float = None
self.stiffness: float = None
self.spring_force: float = None
def set_parent_statespace(self, statespace:StateSpace) -> None:
self.statespace = statespace
def set_child_statespace(self, statespace:StateSpace) -> None:
self.child_statespace = statespace
def set_unstretched_length(self, unstretched_length: float) -> None:
self.unstretched_length = unstretched_length
def set_length(self, length: float) -> None:
self.length = length
def set_stiffness(self, stiffness: float) -> None:
self.stiffness = stiffness
def set_spring_force(self, set_spring_force: float) -> None:
self.spring_force = set_spring_force
def set_spring_force_scalar(self, scalar_force: float) -> None:
self.spring_force_scalar = scalar_force
def get_parent_statespace(self) -> StateSpace:
return self.statespace
def get_child_statespace(self) -> StateSpace:
return self.child_statespace
def get_unstretched_length(self) -> float:
return self.unstretched_length
def get_length(self) -> float:
return self.length
def get_stiffness(self) -> float:
return self.stiffness
def get_spring_force(self) -> list[float]:
return self.spring_force
def get_spring_force_scalar(self) -> float:
return self.spring_force_scalar
def get_axis_vector(self) -> List[float]:
return self.child_statespace.get_pos() - self.statespace.get_pos()
class SharedRigidActuator(SharedPhysicsElement):
'''self.statespace is the statespace of the parent'''
def _init__(self) -> None:
super().__init__()
self.child_statespace: StateSpace = None
self.length: float = None
def set_parent_statespace(self, statespace:StateSpace) -> None:
self.statespace = statespace
def set_parent_statespace_pos(self, new):
self.statespace.set_pos(new)
def set_parent_statespace_vel(self, new):
self.statespace.set_vel(new)
def set_child_statespace(self, statespace:StateSpace) -> None:
self.child_statespace = statespace
def set_length(self, length: float) -> None:
self.length = length
def get_parent_statespace(self) -> StateSpace:
return self.statespace
def get_child_statespace(self) -> StateSpace:
return self.child_statespace
def get_length(self) -> float:
return self.length
def get_axis_vector(self) -> List[float]:
return self.child_statespace.get_pos() - self.statespace.get_pos()
def get_r_unit_vector(self):
return self.calc_r_unit_vector()
def calc_r_vector(self):
return self.child_statespace.get_pos() - self.statespace.get_pos()
def calc_length(self):
return ((self.vector[0])**2 + (self.vector[1])**2 + (self.vector[2])**2) ** 0.5
def calc_r_unit_vector(self):
self.vector = self.calc_r_vector()
self.length = self.calc_length()
return self.vector / self.length

View File

@ -1,451 +0,0 @@
import numpy as np
from math import log, copysign
from Physics_Elements import SharedRigidActuator, SharedJoint, SharedSpring
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from Object_Sharing import SharedFloat
from Physics_Elements import StateSpace, Spring
'''
TODO
1.
2. Better noise generation
Non-linearity, hysteresis, background noise, AC noise
'''
class LoadCell():
'''Provides net force feedback.'''
def __init__(self, sensor_settings: dict={}):
self.sensor_settings = sensor_settings
self.attached_sim_source = None
self.shared_attributes: SharedLoadCell = None # For plotting if desired
self.true_force_vector = np.array([0.0, 0.0, 0.0]) # Current real force on the joint
self.true_force_scalar: float = 0.0
self.last_true_force_scalars: list[float] = [] # Stores the last true force for hysteresis reasons
self.true_voltage_scalar:float = 0.0
self.noisy_force_vector = np.array([0.0, 0.0, 0.0])
self.noisy_force_scalar: float = 0.0
self.noisy_voltage_scalar: float = 0.0
self.parse_settings()
self.calc_noise_parameters()
def parse_settings(self):
self.name = self.sensor_settings["name"]
self.scale = self.sensor_settings["mV/V"] / 1000.0
self.excitation = self.sensor_settings["excitation"]
self.full_scale_force = self.sensor_settings["full_scale_force"]
self.output_channel = self.sensor_settings["output_channel"]
self.noise_settings = self.sensor_settings.get("noise_settings", dict())
self.full_scale_voltage = self.scale * self.excitation
def calc_noise_parameters(self):
# Calculate static error
self.static_error_stdev = self.full_scale_voltage * self.noise_settings.get("static_error_band", 0) / 100
# Non-linearity
self.nonlinearity_exponent = self.calc_nonlinearity_exponent()
# Hysteresis
self.hysteresis = self.noise_settings.get("hysteresis", 0) / 100
# Repeatability
self.repeatability_offset = np.random.normal(0, self.full_scale_voltage * self.noise_settings.get("repeatability", 0) / 100)
# Thermal applied at a given true voltage
self.thermal_error = self.noise_settings.get("thermal_error", 0) / 100 * self.noise_settings.get("temperature_offset", 0)
def calc_nonlinearity_exponent(self):
voltage = 10
if self.full_scale_voltage != 1:
self.full_scale_voltage
error = self.noise_settings.get("non-linearity", 0) / 100
b = log(1 + error, voltage)
return b
def attach_sim_source(self, sim_source):
self.attached_sim_source = sim_source
def attach_shared_attributes(self, shared_attributes):
self.shared_attributes = shared_attributes
def update_shared_attributes(self):
if self.shared_attributes is not None:
self.shared_attributes.set_true_force_vector(self.true_force_vector)
self.shared_attributes.set_true_force_scalar(self.true_force_scalar)
self.shared_attributes.set_true_voltage_scalar(self.true_voltage_scalar)
self.shared_attributes.set_noisy_voltage_scalar(self.noisy_voltage_scalar)
def update_internal_attributes(self):
self.calc_sensor_force_scalar()
self.calc_true_voltage_scalar()
# Calc noise
self.calc_noisy_voltage_scalar()
def pull_from_sim(self):
'''Gets the real net force from the shared attribute updated by the sim'''
# Must be overridden
pass
def calc_sensor_force_scalar(self):
self.last_true_force_scalars.append(self.true_force_scalar)
self.last_true_force_scalars = self.last_true_force_scalars[-5:]
self.true_force_scalar = ((self.true_force_vector[0])**2 + (self.true_force_vector[1])**2 + (self.true_force_vector[2])**2)**0.5
def get_sensor_force_scalar(self):
return self.true_force_scalar
def get_sensor_force_vector(self):
return self.true_force_vector
def get_true_voltage_scalar(self) -> float:
return self.true_voltage_scalar
def get_noisy_voltage_scalar(self) -> float:
return self.noisy_voltage_scalar
def calc_true_voltage_scalar(self):
force_fraction = self.true_force_scalar / self.full_scale_force
full_force_voltage = self.excitation * self.scale
self.true_voltage_scalar = force_fraction * full_force_voltage
def calc_noisy_voltage_scalar(self):
# Calculate static error
static_error = np.random.normal(0, self.static_error_stdev)
# Non-linearity
if self.true_voltage_scalar > 1E-5:
nonlinearity_multiplier = self.true_voltage_scalar**(self.nonlinearity_exponent)
else:
nonlinearity_multiplier = 1
# Hysteresis
average_last = np.average(self.last_true_force_scalars)
#hysteresis_error = (self.true_force_scalar - average_last) / self.true_force_scalar * self.hysteresis * self.full_scale_voltage
hysteresis_error = copysign(1, self.true_force_scalar - average_last) * self.hysteresis * self.full_scale_voltage
# Repeatability
# self.repeatability_offset
# Thermal
thermal_error = self.thermal_error * self.true_voltage_scalar
self.noisy_voltage_scalar = self.true_voltage_scalar*nonlinearity_multiplier + static_error + hysteresis_error + self.repeatability_offset + thermal_error
class LoadCellJoint(LoadCell):
def __init__(self, sensor_settings: dict={}):
super().__init__(sensor_settings=sensor_settings)
self.attached_sim_source: SharedJoint = None
def attach_sim_source(self, sim_source: 'SharedJoint'):
if not isinstance(sim_source._getvalue(), SharedJoint):
raise TypeError(f"sim_source for LoadCellJoint {self.name} is not a SharedJoint")
self.attached_sim_source = sim_source
def pull_from_sim(self):
self.true_force_vector = self.attached_sim_source.get_force()
class LoadCellSpring(LoadCell):
'''Provides feedback on the force along the axis of a spring.'''
def __init__(self, sensor_settings: dict={}):
super().__init__(sensor_settings=sensor_settings)
self.attached_sim_source: SharedSpring = None
self.unstretched_length: float = 0.0
self.true_length: float = 0.0
def attach_sim_source(self, sim_source: 'SharedSpring'):
if not isinstance(sim_source._getvalue(), SharedSpring):
raise TypeError(f"sim_source for LoadCellSpring {self.name} is not a SharedSpring")
self.attached_sim_source = sim_source
self.unstretched_length = sim_source.get_unstretched_length()
def pull_from_sim(self):
self.true_force_vector = self.attached_sim_source.get_spring_force()
self.calc_sensor_force_scalar()
self.true_length = self.attached_sim_source.get_length()
def calc_sensor_force_scalar(self):
self.last_true_force_scalars.append(self.true_force_scalar)
self.last_true_force_scalars = self.last_true_force_scalars[-5:]
self.true_force_scalar = ((self.true_force_vector[0])**2 + (self.true_force_vector[1])**2 + (self.true_force_vector[2])**2)**0.5
if self.true_length >= self.unstretched_length: # Tension is positive
self.true_force_scalar *= 1
else:
self.true_force_scalar*= -1
def calc_true_voltage_scalar(self):
'''Include the possibility to be negative if in tension'''
force_fraction = self.true_force_scalar / self.full_scale_force
full_force_voltage = self.excitation * self.scale
self.true_voltage_scalar = force_fraction * full_force_voltage
class SharedLoadCell():
def __init__(self):
self.true_force_vector = np.array([0.0, 0.0, 0.0]) # Current real force on the joint
self.true_force_scalar: float = 0.0
self.true_voltage_scalar: float = 0.0
self.noisy_force_vector = np.array([0.0, 0.0, 0.0]) # Current real force on the joint
self.noisy_force_scalar: float = 0.0
self.noisy_voltage_scalar: float = 0.0
self.connected_to_plotter: bool = False
self.connected_to_visualization: bool = False
self.connected_to_sensor: bool = False
self.connected_to_controller: bool = False
def set_true_force_vector(self, true_force_vector):
self.true_force_vector = true_force_vector
def get_true_force_vector(self) -> float:
return self.true_force_vector
def set_true_force_scalar(self, true_force_scalar):
self.true_force_scalar = true_force_scalar
def get_true_force_scalar(self) -> float:
return self.true_force_scalar
def set_true_voltage_scalar(self, true_voltage_scalar):
self.true_voltage_scalar = true_voltage_scalar
def get_true_voltage_scalar(self) -> float:
return self.true_voltage_scalar
def set_noisy_voltage_scalar(self, noisy_voltage_scalar):
self.noisy_voltage_scalar = noisy_voltage_scalar
def get_noisy_voltage_scalar(self) -> float:
return self.noisy_voltage_scalar
def set_connected_to_plotter(self, state: bool) -> None:
self.connected_to_plotter = state
def set_connected_to_visualization(self, state: bool) -> None:
self.connected_to_visualization = state
def set_connected_to_sensor(self, state: bool) -> None:
self.connected_to_sensor = state
def set_connected_to_controller(self, state: bool) -> None:
self.connected_to_controller = state
def get_connected_to_plotter(self) -> bool:
return self.connected_to_plotter
def get_connected_to_visualization(self) -> bool:
return self.connected_to_visualization
def get_connected_to_sensor(self) -> bool:
return self.connected_to_sensor
def get_connected_to_controller(self) -> bool:
return self.connected_to_controller
class SharedLoadCellJoint(SharedLoadCell):
def __init__(self):
super().__init__()
class SharedLoadCellSpring(SharedLoadCell):
def __init__(self):
super().__init__()
class DisplacementSensor():
'''Measures the displacement between 2 joints.
The output is the non-negative scalar distance between parent and child'''
def __init__(self, parent_joint: 'SharedJoint', child_joint: 'SharedJoint', sensor_settings: dict={}):
self.sensor_settings = sensor_settings
self.parse_settings()
self.calc_noise_parameters()
if not isinstance(parent_joint._getvalue(), SharedJoint):
raise TypeError(f"parent_joint for DisplacementSensor {self.name} is not a SharedJoint")
if not isinstance(child_joint._getvalue(), SharedJoint):
raise TypeError(f"child_joint for DisplacementSensor {self.name} is not a SharedJoint")
parent_joint.set_connected_to_sensor(True)
child_joint.set_connected_to_sensor(True)
self.sim_source_parent:SharedJoint = parent_joint
self.sim_source_child:SharedJoint = child_joint
self.shared_attributes: SharedDisplacementSensor = None # For plotting if desired
self.length: float = 0.0 # distance from child to parent
self.true_disp_scalar: float = 0.0 # length - neutral_length
self.last_true_disp_scalars: list[float] = [] # Stores the last true displacements for hysteresis reasons
self.calc_sensor_true_disp()
self.true_voltage: float = 0.0
self.noisy_voltage: float = 0.0
def parse_settings(self):
self.name = self.sensor_settings["name"]
self.scale = self.sensor_settings["volts_per_meter"]
self.output_channel = self.sensor_settings["output_channel"]
self.neutral_length = self.sensor_settings["neutral_length"]
self.neutral_voltage = self.sensor_settings.get("neutral_voltage", 0)
self.noise_settings = self.sensor_settings.get("noise_settings", dict())
self.full_scale_voltage = self.scale * (self.sensor_settings["max_length"] - self.neutral_length)
def calc_noise_parameters(self):
# Calculate static error
self.static_error_stdev = self.full_scale_voltage * self.noise_settings.get("static_error_band", 0) / 100
# Non-linearity
self.nonlinearity_exponent = self.calc_nonlinearity_exponent()
# Hysteresis
self.hysteresis = self.noise_settings.get("hysteresis", 0) / 100
# Repeatability
self.repeatability_offset = np.random.normal(0, self.full_scale_voltage * self.noise_settings.get("repeatability", 0) / 100)
# Thermal applied at a given true voltage
self.thermal_error = self.noise_settings.get("thermal_error", 0) / 100 * self.noise_settings.get("temperature_offset", 0)
def calc_nonlinearity_exponent(self):
voltage = 10
if self.full_scale_voltage != 1:
self.full_scale_voltage
error = self.noise_settings.get("non-linearity", 0) / 100
b = log(1 + error, voltage)
return b
def attach_shared_attributes(self, shared_attributes):
if not isinstance(shared_attributes._getvalue(), SharedDisplacementSensor):
raise TypeError(f"shared_attributes for DisplacementSensor {self.name} is not a SharedDisplacementSensor")
self.shared_attributes = shared_attributes
self.shared_attributes.set_neutral_length(self.neutral_length)
def pull_from_sim(self):
'''Gets the real displacement from the shared attribute updated by the sim'''
parent_pos = self.sim_source_parent.get_pos()
child_pos = self.sim_source_child.get_pos()
axis_vector = child_pos - parent_pos
self.length = ((axis_vector[0])**2 + (axis_vector[1])**2 + (axis_vector[2])**2)**0.5
def calc_sensor_true_disp(self):
self.last_true_disp_scalars.append(self.true_disp_scalar)
self.last_true_disp_scalars = self.last_true_disp_scalars[-5:]
self.true_disp_scalar = self.length - self.neutral_length
def calc_true_voltage(self):
self.true_voltage = self.true_disp_scalar * self.scale
def calc_noisy_voltage(self):
# Calculate static error
static_error = np.random.normal(0, self.static_error_stdev)
# Non-linearity
if self.true_voltage > 1E-5:
nonlinearity_multiplier = self.true_voltage**(self.nonlinearity_exponent)
else:
nonlinearity_multiplier = 1
# Hysteresis
average_last = np.average(self.last_true_disp_scalars)
#hysteresis_error = (self.true_force_scalar - average_last) / self.true_force_scalar * self.hysteresis * self.full_scale_voltage
hysteresis_error = copysign(1, self.true_disp_scalar - average_last) * self.hysteresis * self.full_scale_voltage
# Repeatability
# self.repeatability_offset
# Thermal
thermal_error = self.thermal_error * self.true_voltage
self.noisy_voltage = self.true_voltage*nonlinearity_multiplier + static_error + hysteresis_error + self.repeatability_offset + thermal_error
def update_shared_attributes(self):
if self.shared_attributes is not None:
self.shared_attributes.set_current_length(self.length)
self.shared_attributes.set_true_voltage(self.true_voltage)
self.shared_attributes.set_noisy_voltage(self.noisy_voltage)
self.shared_attributes.set_displacement(self.true_disp_scalar)
def update_internal_attributes(self):
self.calc_sensor_true_disp()
self.calc_true_voltage()
self.calc_noisy_voltage()
def get_true_voltage_scalar(self) -> float:
return self.true_voltage
def get_noisy_voltage_scalar(self) -> float:
return self.noisy_voltage
class SharedDisplacementSensor():
def __init__(self):
self.neutral_length: float = 0.0
self.length: float = 0.0
self.displacement: float = 0.0
self.true_voltage: float = 0.0
self.noisy_voltage: float = 0.0
self.connected_to_plotter: bool = False
self.connected_to_visualization: bool = False
self.connected_to_sensor: bool = False
self.connected_to_controller: bool = False
def set_neutral_length(self, new:float):
self.neutral_length = new
def set_current_length(self, new:float):
self.length = new
def set_displacement(self, new:float):
self.displacement = new
def set_true_voltage(self, new:float):
self.true_voltage = new
def set_noisy_voltage(self, new:float):
self.noisy_voltage = new
def set_connected_to_plotter(self, state: bool) -> None:
self.connected_to_plotter = state
def set_connected_to_visualization(self, state: bool) -> None:
self.connected_to_visualization = state
def set_connected_to_sensor(self, state: bool) -> None:
self.connected_to_sensor = state
def set_connected_to_controller(self, state: bool) -> None:
self.connected_to_controller = state
def get_connected_to_plotter(self) -> bool:
return self.connected_to_plotter
def get_connected_to_visualization(self) -> bool:
return self.connected_to_visualization
def get_connected_to_sensor(self) -> bool:
return self.connected_to_sensor
def get_connected_to_controller(self) -> bool:
return self.connected_to_controller
def get_neutral_length(self) -> float:
return self.neutral_length
def get_displacement(self) -> float:
return self.displacement
def get_current_length(self) -> float:
return self.length
def get_true_voltage(self) -> float:
return self.true_voltage
def get_noisy_voltage(self) -> float:
return self.noisy_voltage

View File

@ -1,236 +0,0 @@
import time
import threading
import numpy as np
from typing import List
from Visualization import Visualization
from Physics_Elements import Joint, Spring, RigidActuator
class BFSSimulation():
def __init__(self, parent_joint: Joint, settings:dict):
if not isinstance(parent_joint, Joint):
raise TypeError(f"parent_joint for BFSSimulation is not a Joint")
self.parent_joint = parent_joint
self.duration = settings["duration"]
self.delta_t = settings["delta_t"]
self.plotting_update_period = settings["plotting_update_period"]
self.sensor_update_period = settings["sensor_update_period"]
self.controller_pull_period = settings["controller_pull_period"]
self.joints: List[Joint] = []
self.springs: List[Spring] = []
self.actuators: List[RigidActuator] = []
self.rigid_actuators: List[RigidActuator] = []
self.get_joints_bfs()
#self.get_parent_joints_for_every_joint()
# Plotting or viz elements are updated slower than sensor
self.plotting_or_viz_only_shared = []
self.sensor_shared = []
self.controller_shared = []
self.sort_shared_attributes_into_frequencies()
self.vis:Visualization = None
self.attached_shared_time:float = None
self.sim_time:float = 0.0
def is_in_joints(self, joint: Joint):
for j in self.joints:
if joint.is_same(j):
return True
return False
def is_in_queue(self, joint: Joint, queue):
for j in queue:
if joint.is_same(j):
return True
return False
def get_joints_bfs(self):
queue: List[Joint] = []
queue.append(self.parent_joint)
while queue:
parent_joint: Joint
parent_joint = queue.pop(0)
if parent_joint is None:
continue
if not parent_joint.is_in_list(self.joints):
self.joints.append(parent_joint)
connected_springs = parent_joint.get_connected_springs()
for spring in connected_springs:
if not spring.is_in_list(self.springs):
self.springs.append(spring)
child_joint = spring.get_child_joint()
if not self.is_in_joints(child_joint) and not self.is_in_queue(child_joint, queue):
queue.append(child_joint)
connected_actuators = parent_joint.get_connected_actuators()
actuator: RigidActuator = None
for actuator in connected_actuators:
if actuator not in self.actuators:
self.rigid_actuators.append(actuator)
if actuator.get_child_joint() not in self.joints and actuator.get_child_joint() not in queue:
queue.append(actuator.get_child_joint())
def get_parent_joints_for_every_joint(self):
for joint in self.joints:
joint.find_parent_joints()
def get_child_joints_for_every_joint(self):
for joint in self.joints:
joint.find_child_joints()
def update_actuators_from_controller(self):
for rigid_actuator in self.rigid_actuators:
rigid_actuator.update_from_controller()
def pull_actuator_positions_thread(self):
while self.spare_stop_event.is_set() == False:
self.update_actuators_from_controller()
time.sleep(self.controller_pull_period)
def update_sensor_attributes(self):
'''Updates the joints, springs, and actuators that are attached to sensors'''
for obj in self.sensor_shared:
obj.update_shared_attributes()
def update_sensor_attributes_thread(self):
while self.spare_stop_event.is_set() == False:
self.update_sensor_attributes()
time.sleep(self.sensor_update_period)
def update_plotting_or_viz_only_attributes(self):
'''Updates the joints, springs, and actuators that are only attached for plotting or visualization'''
for obj in self.plotting_or_viz_only_shared:
obj.update_shared_attributes()
def update_plotting_or_viz_only_attributes_thread(self):
while self.spare_stop_event.is_set() == False:
self.update_plotting_or_viz_only_attributes()
time.sleep(self.plotting_update_period)
def sort_shared_attributes_into_frequencies(self):
'''For each shared attribute connected to anything, put them in lists so we know how fast we need to update them.'''
for joint in self.joints:
shared = joint.shared_attributes
if shared is None:
continue
plot = shared.get_connected_to_plotter()
vis = shared.get_connected_to_visualization()
sens = shared.get_connected_to_sensor()
contr = shared.get_connected_to_controller()
if sens:
self.sensor_shared.append(joint)
elif plot or vis:
self.plotting_or_viz_only_shared.append(joint)
if contr:
self.controller_shared.append(joint)
for spring in self.springs:
shared = spring.shared_attributes
if shared is None:
continue
plot = shared.get_connected_to_plotter()
vis = shared.get_connected_to_visualization()
sens = shared.get_connected_to_sensor()
contr = shared.get_connected_to_controller()
if sens:
self.sensor_shared.append(spring)
elif plot or vis:
self.plotting_or_viz_only_shared.append(spring)
if contr:
self.controller_shared.append(spring)
for actuator in self.rigid_actuators:
shared = actuator.shared_attributes
if shared is None:
continue
plot = shared.get_connected_to_plotter()
vis = shared.get_connected_to_visualization()
sens = shared.get_connected_to_sensor()
contr = shared.get_connected_to_controller()
if sens:
self.sensor_shared.append(actuator)
elif plot or vis:
self.plotting_or_viz_only_shared.append(actuator)
if contr:
self.controller_shared.append(actuator)
def attach_shared_time(self, attached_shared_time):
self.attached_shared_time = attached_shared_time
def run_process(self):
time_values = []
self.sim_time = 0
#time_last = time.perf_counter()
time_last = time.time()
count = 0
self.spare_stop_event = threading.Event()
self.spare_stop_event.clear()
pull_actuator_thread = threading.Thread(target=self.pull_actuator_positions_thread)
pull_actuator_thread.start()
update_plotting_or_viz_only_thread = threading.Thread(target=self.update_plotting_or_viz_only_attributes_thread)
update_plotting_or_viz_only_thread.start()
update_sensor_thread = threading.Thread(target=self.update_sensor_attributes_thread)
update_sensor_thread.start()
while self.sim_time < self.duration:
count += 1
if self.delta_t is None:
#new_time = time.perf_counter()
new_time = time.time()
delta_time = new_time - time_last
time_last = new_time
for spring in self.springs:
spring.calc_spring_force()
joint: Joint
for joint in self.joints:
if joint.fixed == True:
continue
# Get joint forces
joint.calc_net_force(self.sim_time)
# Get joint accels.
joint.calc_accel()
# Integrate joint vel and pos
if self.delta_t is None:
joint.integrate_statespace(delta_time)
else:
joint.integrate_statespace(self.delta_t)
time_values.append(self.sim_time)
if self.delta_t is None:
self.sim_time += delta_time
else:
self.sim_time += self.delta_t
# Update the shared sim time as fast as possible
self.attached_shared_time.set(self.sim_time)
self.spare_stop_event.set()
pull_actuator_thread.join()
update_plotting_or_viz_only_thread.join()
update_sensor_thread.join()
print(f"Average delta_t = {self.sim_time / count}")

View File

@ -1,594 +0,0 @@
import vpython
import time
from copy import deepcopy
import multiprocessing
import threading
import pyqtgraph as pg
from pyqtgraph.Qt import QtWidgets, QtCore
from Physics_Elements import Joint, Spring, SharedRigidActuator, SharedSpring, SharedJoint
from Sensor_Elements import SharedLoadCellJoint, SharedLoadCellSpring, SharedDisplacementSensor
from Controller_Input_Elements import SharedRigidActuatorController
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from Object_Sharing import SharedFloat
'''
TODO
2. Make this in a separate thread
'''
class Visualization():
def __init__(self, stop_event, scene_settings:dict):
self.stop_event = stop_event
self.scene_settings = scene_settings
self.attached_attributes = []
self.shared_attributes_object_settings = []
self.vpython_objects = []
def draw_scene(self):
vpython.scene = vpython.canvas(width=self.scene_settings["canvas_width"], height=self.scene_settings["canvas_height"])
side = self.scene_settings["cube_size"]
thk = self.scene_settings["wall_thickness"]
wall_length = side + 2*thk
wall_bottom = vpython.box(pos=vpython.vector(0, -thk/2, 0), size=vpython.vector(wall_length, thk, wall_length), color=vpython.color.gray(0.9))
wall_left = vpython.box(pos=vpython.vector(-(side + thk)/2, side/2, 0), size=vpython.vector(thk, side, side), color=vpython.color.gray(0.7))
wall_right = vpython.box(pos=vpython.vector((side + thk)/2, side/2, 0), size=vpython.vector(thk, side, side), color=vpython.color.gray(0.7))
wall_back = vpython.box(pos=vpython.vector(0, (side)/2, -(side+ thk)/2), size=vpython.vector(wall_length, side, thk), color=vpython.color.gray(0.7))
def attach_shared_attributes(self, shared_attributes, object_settings):
self.attached_attributes.append(shared_attributes)
self.shared_attributes_object_settings.append(object_settings)
shared_attributes.set_connected_to_visualization(True)
def generate_vpython_objects(self):
for i in range(len(self.attached_attributes)):
object_settings = self.shared_attributes_object_settings[i]
if object_settings["type"] == "sphere":
shared_attribute:Joint = self.attached_attributes[i]
object_pos = deepcopy(shared_attribute.get_pos())
object_pos = vpython.vector(object_pos[0], object_pos[1], object_pos[2])
object_color = self.return_vpython_color(object_settings["color"])
self.vpython_objects.append(vpython.sphere(pos=object_pos, radius=object_settings["radius"], color=object_color))
elif object_settings["type"] == "helix":
shared_attribute:Spring = self.attached_attributes[i]
object_pos = deepcopy(shared_attribute.get_pos())
object_pos = vpython.vector(object_pos[0], object_pos[1], object_pos[2])
object_axis = deepcopy(shared_attribute.get_axis_vector())
object_axis = vpython.vector(object_axis[0], object_axis[1], object_axis[2])
object_color = self.return_vpython_color(object_settings["color"])
self.vpython_objects.append(vpython.helix(pos=object_pos, axis=object_axis, radius=object_settings["radius"], thickness=object_settings["thickness"], color=object_color))
elif object_settings["type"] == "cylinder":
shared_attribute:Spring = self.attached_attributes[i]
object_pos = deepcopy(shared_attribute.get_pos())
object_pos = vpython.vector(object_pos[0], object_pos[1], object_pos[2])
object_axis = deepcopy(shared_attribute.get_axis_vector())
object_axis = vpython.vector(object_axis[0], object_axis[1], object_axis[2])
object_color = self.return_vpython_color(object_settings["color"])
self.vpython_objects.append(vpython.cylinder(pos=object_pos, axis=object_axis, radius=object_settings["radius"], color=object_color))
def return_vpython_color(self, color_str):
if color_str == "blue":
return vpython.color.blue
elif color_str == "black":
return vpython.color.black
elif color_str == "red":
return vpython.color.red
elif color_str == "green":
return vpython.color.green
elif color_str == "white":
return vpython.color.white
def update_scene(self):
for i in range(len(self.vpython_objects)):
element = self.vpython_objects[i]
shared_attributes = self.attached_attributes[i]
if isinstance(element, vpython.sphere):
updated_pos = shared_attributes.get_pos()
element.pos = deepcopy(vpython.vector(*(updated_pos)))
elif isinstance(element, vpython.helix):
updated_pos = shared_attributes.get_pos()
element.pos = deepcopy(vpython.vector(*(updated_pos)))
updated_axis = shared_attributes.get_axis_vector()
element.axis = deepcopy(vpython.vector(*(updated_axis)))
elif isinstance(element, vpython.cylinder):
updated_pos = shared_attributes.get_pos()
element.pos = deepcopy(vpython.vector(*(updated_pos)))
updated_axis = shared_attributes.get_axis_vector()
element.axis = deepcopy(vpython.vector(*(updated_axis)))
def run_process(self):
# Draw scene
self.draw_scene()
# Generate vpython objects from settings
self.generate_vpython_objects()
while self.stop_event.is_set() == False:
# Update pos and axes of all vpython objects
self.update_scene()
class Plotter():
def __init__(self, plot_settings: dict, stop_event: multiprocessing.Event):
self.plot_settings = plot_settings
self.title = plot_settings["title"]
self.window_length = self.plot_settings["window_length"]
self.update_interval = self.plot_settings["update_interval"]
self.pull_interval = self.plot_settings["pull_interval"]
self.stop_event: multiprocessing.Event = stop_event
self.attached_attributes = []
self.shared_attributes_plot_settings = []
self.pull_data_stop_event: threading.Event = None
self.shared_x: SharedFloat = None
self.plot_setup = {}
def attach_shared_attributes(self, shared_attributes, plot_settings):
self.attached_attributes.append(shared_attributes)
self.shared_attributes_plot_settings.append(plot_settings)
shared_attributes.set_connected_to_plotter(True)
def attach_shared_x(self, shared_x):
self.shared_x = shared_x
def pull_data_thread(self):
while self.pull_data_stop_event.is_set() == False:
self.pull_data()
time.sleep(self.pull_interval / 1000)
def pull_data(self):
self.raw_xdata.append(self.shared_x.get())
for i in range(len(self.shared_attributes_plot_settings)):
foo = self.attached_attributes[i]._getvalue()
for key, value in self.shared_attributes_plot_settings[i].items():
if isinstance(foo, SharedLoadCellJoint):
bar: SharedLoadCellJoint = self.attached_attributes[i]
if key == "force":
force_vector = bar.get_true_force_vector()
for subkey, settings in value.items():
if subkey == "x":
ylabel: str = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(force_vector[0])
elif subkey == "y":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(force_vector[1])
elif subkey == "z":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(force_vector[2])
elif key == "true_voltage":
voltage_scalar = bar.get_true_voltage_scalar()
for subkey, settings in value.items():
if subkey == "scalar":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(voltage_scalar)
elif key == "noisy_voltage":
voltage_scalar = bar.get_noisy_voltage_scalar()
for subkey, settings in value.items():
if subkey == "scalar":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(voltage_scalar)
elif isinstance(foo, SharedLoadCellSpring):
bar: SharedLoadCellSpring = self.attached_attributes[i]
if key == "force":
force_vector = bar.get_true_force_vector()
for subkey, settings in value.items():
if subkey == "x":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(force_vector[0])
elif subkey == "y":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(force_vector[1])
elif subkey == "z":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(force_vector[2])
elif subkey == "scalar":
force_scalar = bar.get_true_force_scalar()
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(force_scalar)
elif key == "true_voltage":
voltage_scalar = bar.get_true_voltage_scalar()
for subkey, settings in value.items():
if subkey == "scalar":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(voltage_scalar)
elif key == "noisy_voltage":
voltage_scalar = bar.get_noisy_voltage_scalar()
for subkey, settings in value.items():
if subkey == "scalar":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(voltage_scalar)
elif isinstance(foo, SharedRigidActuator):
bar: SharedRigidActuator = self.attached_attributes[i]
if key == "pos":
pos_vector = bar.get_pos()
for subkey, settings in value.items():
if subkey == "x":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(pos_vector[0])
elif subkey == "y":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(pos_vector[1])
elif subkey == "z":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(pos_vector[2])
if key == "vel":
pos_vector = self.attached_attributes[i].get_vel()
for subkey, settings in value.items():
if subkey == "x":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(pos_vector[0])
elif subkey == "y":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(pos_vector[1])
elif subkey == "z":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(pos_vector[2])
elif isinstance(foo, SharedRigidActuatorController):
bar: SharedRigidActuatorController = self.attached_attributes[i]
if key == "input_voltage":
input_voltage = bar.get_input_voltage()
for subkey, settings in value.items():
if subkey == "scalar":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(input_voltage)
elif key == "digital_input":
digital_input = bar.get_digital_command()
for subkey, settings in value.items():
if subkey == "scalar":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(digital_input)
elif key == "pos":
controlled_vel = bar.get_controlled_pos()
for subkey, settings in value.items():
if subkey == "x":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(controlled_vel[0])
elif subkey == "y":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(controlled_vel[1])
elif subkey == "z":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(controlled_vel[2])
elif key == "vel":
controlled_vel = bar.get_controlled_vel()
for subkey, settings in value.items():
if subkey == "x":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(controlled_vel[0])
elif subkey == "y":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(controlled_vel[1])
elif subkey == "z":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(controlled_vel[2])
if subkey == "scalar":
vel_scalar = bar.get_controlled_vel_scalar()
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(vel_scalar)
elif key == "disp":
disp_scalar = bar.get_controlled_length()
for subkey, settings in value.items():
if subkey == "scalar":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(disp_scalar)
elif isinstance(foo, SharedDisplacementSensor):
bar: SharedDisplacementSensor = self.attached_attributes[i]
if key == "voltage":
for subkey, settings in value.items():
if subkey == "noisy":
voltage_scalar = bar.get_noisy_voltage()
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(voltage_scalar)
elif subkey == "true":
voltage_scalar = bar.get_true_voltage()
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(voltage_scalar)
if key == "disp":
disp_scalar = bar.get_displacement()
for subkey, settings in value.items():
if subkey == "scalar":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(disp_scalar)
elif key == "length":
length_scalar = bar.get_current_length()
for subkey, settings in value.items():
if subkey == "scalar":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(length_scalar)
elif isinstance(foo, SharedSpring):
bar: SharedSpring = self.attached_attributes[i]
if key == "force":
force_vector = bar.get_spring_force()
for subkey, settings in value.items():
if subkey == "x":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(force_vector[0])
elif subkey == "y":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(force_vector[1])
elif subkey == "z":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(force_vector[2])
elif subkey == "scalar":
force_scalar = bar.get_spring_force_scalar()
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(force_scalar)
elif key == "length":
length = bar.get_length()
for subkey, settings in value.items():
if subkey == "scalar":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(length)
elif isinstance(foo, SharedJoint):
bar: SharedJoint = self.attached_attributes[i]
if key == "accel":
accel_vector = bar.get_accel()
for subkey, settings in value.items():
if subkey == "x":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(accel_vector[0])
elif subkey == "y":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(accel_vector[1])
elif subkey == "z":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(accel_vector[2])
elif key == "vel":
vel_vector = bar.get_vel()
for subkey, settings in value.items():
if subkey == "x":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(vel_vector[0])
elif subkey == "y":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(vel_vector[1])
elif subkey == "z":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(vel_vector[2])
elif key == "pos":
pos_vector = bar.get_pos()
for subkey, settings in value.items():
if subkey == "x":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(pos_vector[0])
elif subkey == "y":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(pos_vector[1])
if subkey == "z":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(pos_vector[2])
elif key == "force":
force_vector = bar.get_spring_force()
for subkey, settings in value.items():
if subkey == "x":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(force_vector[0])
elif subkey == "y":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(force_vector[1])
elif subkey == "z":
ylabel = settings["ylabel"]
(subplot, line) = self.raw_data_map[ylabel]
self.raw_ydata[subplot][line].append(force_vector[2])
def update_live_window(self):
if self.stop_event.is_set():
self.timer.stop()
self.pull_data_stop_event.set()
return
final_time = self.raw_xdata[-1]
target_time = final_time - self.window_length
closest_index = min(range(len(self.raw_xdata)), key=lambda i: abs(self.raw_xdata[i] - target_time))
self.window_xdata = self.raw_xdata[closest_index:]
# Attach the data to the appropriate sublines in the subplots
for i in range(self.num_plots):
for j in range(len(self.subplot_keys[i])):
window_ydata = self.raw_ydata[i][j][closest_index:]
if self.plot_settings["step_or_plot"] == "plot":
if len(self.window_xdata) == len(window_ydata):
self.subplot_curves[i][j].setData(self.window_xdata, window_ydata)
elif len(self.window_xdata) > len(window_ydata):
self.subplot_curves[i][j].setData(self.window_xdata[:-1], window_ydata)
elif self.plot_settings["step_or_plot"] == "step":
if len(self.window_xdata) == len(window_ydata):
self.subplot_curves[i][j].setData(self.window_xdata, window_ydata[:-1])
elif len(self.window_xdata) > len(window_ydata):
self.subplot_curves[i][j].setData(self.window_xdata[:-1], window_ydata[:-1])
def generate_plots(self):
# Create the application
self.app = QtWidgets.QApplication([])
# Create a plot window
self.win = pg.GraphicsLayoutWidget(show=True, title=self.title)
self.win.resize(1000, 600)
self.win.setWindowTitle(self.plot_settings["title"])
self.num_plots = self.plot_settings["num_subplots"]
self.plots = []
for i in range(self.num_plots):
plot = self.win.addPlot(title=self.plot_settings["subplots"][i]["ylabel"])
plot.setLabel('left', self.plot_settings["subplots"][i]["ylabel"])
plot.addLegend()
self.plots.append(plot)
# Move us to the next row for the subplot
if i < self.num_plots - 1:
self.win.nextRow()
def generate_curves(self):
self.subplot_keys = [[] for _ in range(self.num_plots)] # list for each subplot
self.subplot_curves = [[] for _ in range(self.num_plots)] # list for each subplot
self.raw_xdata = []
self.raw_ydata = [[] for _ in range(self.num_plots)] # list for each subplot
self.raw_data_map = {}
for setting in self.shared_attributes_plot_settings:
'''
Each setting looks like {
"accel": {
"x": {
"subplot": 1,
"ylabel": "Main Mass x-Accel"
},
"y": {
"subplot": 1,
"ylabel": "Main Mass y-Accel"
}
},
"vel": {
"x": {
"subplot": 2,
"ylabel": "Main Mass x-Vel"
}
},
"pos": {
"x": {
"subplot": 3,
"ylabel": "Main Mass x-Vel"
}
}
}
'''
for key, value in setting.items():
''' key would be like "accel"
value would be like {
"x": {
"subplot": 1,
"ylabel": "Main Mass x-Accel"
},
"y": {
"subplot": 1,
"ylabel": "Main Mass y-Accel"
}
}
'''
for line, line_settings in value.items():
''' line would be like "x"
line_settings would be like {
"subplot": 1,
"ylabel": "Main Mass x-Accel",
"color": "w"
}
'''
subplot = line_settings["subplot"]
label = line_settings["ylabel"]
color = line_settings.get("color", "w") # Default to white if no color is specified
self.subplot_keys[subplot].append(label)
if self.plot_settings["step_or_plot"] == "plot":
self.subplot_curves[subplot].append(self.plots[subplot].plot(name=label, pen=pg.mkPen(color=color)))
elif self.plot_settings["step_or_plot"] == "step":
self.subplot_curves[subplot].append(self.plots[subplot].plot(name=label, stepMode=True, pen=pg.mkPen(color=color)))
self.raw_data_map[label] = (subplot, len(self.raw_ydata[subplot]))
self.raw_ydata[subplot].append([])
self.window_xdata = []
self.window_ydata = [[] for _ in range(self.num_plots)] # list for each subplot
def show_plot(self):
pass
def run_process(self):
# Generate the figure and subplots
self.generate_plots()
# Generate the curves
self.generate_curves()
self.pull_data_stop_event = threading.Event()
self.pull_data_stop_event.clear()
pull_data_thread = threading.Thread(target=self.pull_data_thread)
pull_data_thread.start()
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.update_live_window)
self.timer.start(self.update_interval)
# Start the Qt event loop
QtWidgets.QApplication.instance().exec_()
pull_data_thread.join()

View File

@ -1,268 +0,0 @@
import numpy as np
import time
import multiprocessing
from multiprocessing.managers import BaseManager
from Visualization import Visualization, Plotter
from Simulation import BFSSimulation
from Physics_Elements import Joint, Spring, SharedJoint, SharedSpring
from Object_Sharing import SharedFloat
def main():
'''Setting up the BaseManager so data can be shared between processes'''
base_manager = BaseManager()
BaseManager.register('SharedFloat', SharedFloat)
BaseManager.register('SharedJoint', SharedJoint)
BaseManager.register('SharedSpring', SharedSpring)
base_manager.start()
'''Creating instances for plotter and visualizer'''
# Setup the plotter and visualization processes
stop_event = multiprocessing.Event()
stop_event.clear()
# Create a synchronized time between the processes so the plotter and physics sim are in sync
shared_sim_time:SharedFloat = base_manager.SharedFloat()
shared_sim_time.set(0.0) # Set the initial time = 0.0 seconds
# Create a real-time plotter for physics plotting. 1 for the mass and the other for the spring
mass_plotter = Plotter(
stop_event = stop_event,
plot_settings = {
"title": "Joint Plotter",
"xlabel": "Time (s)",
"num_subplots": 3,
"step_or_plot": "plot",
"subplots": [
{
"ylabel": "Position (m)"
},
{
"ylabel": "Velocity (m/s)"
},
{
"ylabel": "Accel (m/s/s)"
}
],
"window_length": 100, # Keep 100 seconds visible
"pull_interval": 10, # Pull data every 10 millisecond
"update_interval": 100 # Update the graph every 100 milliseconds
}
)
mass_plotter.attach_shared_x(shared_sim_time)
spring_plotter = Plotter(
stop_event = stop_event,
plot_settings = {
"title": "Spring Plotter",
"xlabel": "Time (s)",
"num_subplots": 2,
"step_or_plot": "plot",
"subplots": [
{
"ylabel": "Length (m)"
},
{
"ylabel": "Force (N)"
}
],
"window_length": 100, # Keep 100 seconds visible
"pull_interval": 10, # Pull data every 10 milliseconds
"update_interval": 100 # Update the graph every 100 milliseconds
}
)
spring_plotter.attach_shared_x(shared_sim_time)
# Create a 3D visualization for the entire model
visualizer = Visualization(
stop_event = stop_event,
scene_settings = {
"canvas_width": 1600,
"canvas_height": 1000,
"wall_thickness": 0.1,
"cube_size": 20
}
)
'''Setting up physics simulation'''
# Create the physics elements
main_mass = Joint(
pos = np.array([0, 5, 0]),
mass = 5,
fixed = False,
name = "Main mass",
integration_method = "adams-bashforth"
)
# Before we create a spring, we need to create the Joint on the wall for the Spring to mount to
wall_joint = Joint(
pos = np.array([-10, 5, 0]),
mass = 1, # Does not matter because it is fixed
fixed = True, # We do not want it to ever move
name = "Wall Joint"
)
spring = Spring(
parent_joint = main_mass,
child_joint = wall_joint,
unstretched_length = 13,
constant_stiffness = 100,
name = "Spring"
)
'''Adding items to be plotted'''
# Since we want to plot the physics of the mass and spring, we need shared attributes for them
shared_main_mass: SharedJoint = base_manager.SharedJoint()
main_mass.attach_shared_attributes(shared_main_mass)
shared_spring: SharedSpring = base_manager.SharedSpring()
spring.attach_shared_attributes(shared_spring)
# Attach the shared elements to the plotter
mass_plotter.attach_shared_attributes(
shared_attributes = shared_main_mass,
plot_settings = {
"pos": {
"x": {
"subplot": 0,
"ylabel": "Main Poss x-Pos",
"color": "r"
}
},
"vel": {
"x": {
"subplot": 1,
"ylabel": "Main Poss x-Vel",
"color": "g"
}
},
"accel": {
"x": {
"subplot": 2,
"ylabel": "Main Poss x-Accel",
"color": "b"
}
}
}
)
spring_plotter.attach_shared_attributes(
shared_attributes = shared_spring,
plot_settings = {
"length": {
"scalar": {
"subplot": 0,
"ylabel": "Spring Length",
"color": "r"
}
},
"force": {
"x": {
"subplot": 1,
"ylabel": "Spring x-Force",
"color": "g"
}
}
}
)
'''Adding items to be visualized'''
# We aready have shared_main_mass and shared_spring from the plotting, so there is no need to make more shared elements
visualizer.attach_shared_attributes(
shared_attributes = shared_main_mass,
object_settings = {
"type": "sphere",
"color": "red",
"radius": 0.5,
}
)
visualizer.attach_shared_attributes(
shared_attributes = shared_spring,
object_settings = {
"type": "helix",
"color": "white",
"radius": 0.5,
"thickness": 0.1
}
)
'''Create the physics simulation'''
simulation = BFSSimulation(
parent_joint = main_mass, # Because spring is a child of main_mass, it will be reached by BFS
settings = {
"duration": 1000, # Run the sim for 1000 seconds
"delta_t": None, # Run in real-time
"plotting_update_period": 0.01, # Update the plottable elements every 0.01 seconds
"sensor_update_period": 0.01, # Update the sensor elements every second (because we have none, this doesn't matter)
"controller_pull_period": 0.01 # Update the controller elements every seconds (again, we have none)
}
)
simulation.attach_shared_time(shared_sim_time)
'''Setup the processes and run them'''
mass_plotter_process = multiprocessing.Process(target=mass_plotter.run_process)
spring_plotter_process = multiprocessing.Process(target=spring_plotter.run_process)
visualization_process = multiprocessing.Process(target=visualizer.run_process)
simulation_process = multiprocessing.Process(target=simulation.run_process)
mass_plotter_process.start()
spring_plotter_process.start()
visualization_process.start()
time.sleep(5) # Give the plotters and visualizer time to startup before the physics sim runs.
# Join the process
simulation_process.start()
simulation_process.join() # This blocks until the simulation finishes
mass_plotter_process.join()
spring_plotter_process.join()
visualization_process.join()
# Close the manager
base_manager.shutdown()
if __name__ == "__main__":
main()

View File

@ -1,430 +0,0 @@
import numpy as np
import time
import multiprocessing
from multiprocessing.managers import BaseManager
from Visualization import Visualization, Plotter
from Simulation import BFSSimulation
from Physics_Elements import Joint, Spring, RigidActuator, SharedJoint, SharedSpring, SharedRigidActuator
from HITL_Controller import HITLController
from Controller_Input_Elements import RigidActuatorController, SharedRigidActuatorController
from Sensor_Elements import DisplacementSensor, LoadCellSpring, SharedDisplacementSensor, SharedLoadCellSpring
from Object_Sharing import SharedFloat
def main():
'''Setting up the BaseManager so data can be shared between processes'''
base_manager = BaseManager()
BaseManager.register('SharedFloat', SharedFloat)
BaseManager.register('SharedJoint', SharedJoint)
BaseManager.register('SharedSpring', SharedSpring)
BaseManager.register('SharedRigidActuator', SharedRigidActuator)
BaseManager.register('SharedRigidActuatorController', SharedRigidActuatorController)
BaseManager.register('SharedDisplacementSensor', SharedDisplacementSensor)
BaseManager.register('SharedLoadCellSpring', SharedLoadCellSpring)
base_manager.start()
'''Creating instances for plotter and visualizer'''
# Setup the plotter and visualization processes
stop_event = multiprocessing.Event()
stop_event.clear()
# Create a synchronized time between the processes so the plotter and physics sim are in sync
shared_sim_time:SharedFloat = base_manager.SharedFloat()
shared_sim_time.set(0.0) # Set the initial time = 0.0 seconds
# Create a real-time plotter for physics plotting.
physics_plotter = Plotter(
stop_event = stop_event,
plot_settings = {
"title": "Physics Plotter",
"xlabel": "Time (s)",
"num_subplots": 4,
"step_or_plot": "plot",
"subplots": [
{
"ylabel": "Position (m)"
},
{
"ylabel": "Velocity (m/s)"
},
{
"ylabel": "Accel (m/s/s)"
},
{
"ylabel": "Force (N)"
},
],
"window_length": 10, # Keep 100 seconds visible
"pull_interval": 10, # Pull data every 10 millisecond
"update_interval": 100 # Update the graph every 100 milliseconds
}
)
physics_plotter.attach_shared_x(shared_sim_time)
# Create a real-time plotter for the controller input plotting.
controller_plotter = Plotter(
stop_event = stop_event,
plot_settings = {
"title": "Controller Plotter",
"xlabel": "Time (s)",
"num_subplots": 3,
"step_or_plot": "plot",
"subplots": [
{
"ylabel": "Controller Input (V)",
},
{
"ylabel": "Digital Input"
},
{
"ylabel": "Controlled Pos (m)",
},
],
"window_length": 10, # Keep 100 seconds visible
"pull_interval": 10, # Pull data every 10 millisecond
"update_interval": 100 # Update the graph every 100 milliseconds
}
)
controller_plotter.attach_shared_x(shared_sim_time)
# Create a real-time plotter for the sensor output plotting
sensor_plotter = Plotter(stop_event=stop_event, plot_settings={
"title": "Sensor Plotter",
"xlabel": "Time (s)",
"num_subplots": 2,
"step_or_plot": "plot",
"subplots": [
{
"ylabel": "Actuator Length (m)",
},
{
"ylabel": "Sensor Output (V)",
},
],
"window_length": 10, # Max data points to keep on the plot
"pull_interval": 10, # in milliseconds
"update_interval": 100, # In milliseconds.
})
sensor_plotter.attach_shared_x(shared_sim_time)
# Create a 3D visualization for the entire model
visualizer = Visualization(
stop_event = stop_event,
scene_settings = {
"canvas_width": 1600,
"canvas_height": 1000,
"wall_thickness": 0.1,
"cube_size": 20
}
)
'''Creating an instance for the HITLController'''
hitl_controller = HITLController(
stop_event = stop_event,
settings = {
"pull_from_sim_period": 5,
"plotting_update_period": 10
}
)
'''Setting up physics simulation'''
# Create the physics elements
main_mass = Joint(
pos = np.array([0, 5, 0]),
mass = 5,
fixed = False,
name = "Main mass",
integration_method = "adams-bashforth"
)
# Since we want to plot the physics of the mass we need shared attributes for it
shared_main_mass: SharedJoint = base_manager.SharedJoint()
main_mass.attach_shared_attributes(shared_main_mass)
physics_plotter.attach_shared_attributes(
shared_attributes = shared_main_mass,
plot_settings = {
"pos": {
"x": {
"subplot": 0,
"ylabel": "Main Poss x-Pos",
"color": "r"
}
},
"vel": {
"x": {
"subplot": 1,
"ylabel": "Main Poss x-Vel",
"color": "g"
}
},
"accel": {
"x": {
"subplot": 2,
"ylabel": "Main Poss x-Accel",
"color": "b"
}
}
}
)
visualizer.attach_shared_attributes(
shared_attributes = shared_main_mass,
object_settings = {
"type": "sphere",
"color": "red",
"radius": 0.5,
}
)
# Before we create a spring, we need to create the Joint on the wall for the Spring to mount to
north_wall_joint = Joint(
pos = np.array([10, 5, 0]),
mass = 1, # Does not matter because it is fixed
fixed = True, # We do not want it to ever move
name = "North Wall Joint"
)
north_spring = Spring(
parent_joint = main_mass,
child_joint = north_wall_joint,
unstretched_length = 13,
constant_stiffness = 100,
name = "North Spring"
)
shared_north_spring: SharedSpring = base_manager.SharedSpring()
north_spring.attach_shared_attributes(shared_north_spring)
visualizer.attach_shared_attributes(
shared_attributes = shared_north_spring,
object_settings = {
"type": "helix",
"color": "white",
"radius": 0.5,
"thickness": 0.1
}
)
# Setting up the actuator. This requires the "grounded_joint" to fix to. We also want a spring in between the actuator and main_mass
actuator_joint = Joint(
pos = np.array([-5, 5, 0]),
mass = 1, # Does not matter because it is fixed to an actuator
fixed = False, # We do not want it to ever move
name = "Actuator Joint"
)
actuator_spring = Spring(
parent_joint = main_mass,
child_joint = actuator_joint,
unstretched_length = 5,
constant_stiffness = 150,
name = "Actuator Spring"
)
shared_actuator_spring: SharedSpring = base_manager.SharedSpring()
actuator_spring.attach_shared_attributes(shared_actuator_spring)
physics_plotter.attach_shared_attributes(
shared_attributes = shared_actuator_spring,
plot_settings = {
"force": {
"scalar": {
"subplot": 3,
"ylabel": "Actuator Spring Force",
"color": "r"
}
},
}
)
visualizer.attach_shared_attributes(
shared_attributes = shared_actuator_spring,
object_settings = {
"type": "helix",
"color": "white",
"radius": 0.5,
"thickness": 0.1
}
)
south_wall_joint = Joint(
pos = np.array([-10, 5, 0]),
mass = 1, # Does not matter because it is fixed
fixed = True, # We do not want it to ever move
name = "South Wall Joint"
)
rigid_actuator = RigidActuator(
parent_joint = actuator_joint,
grounded_joint = south_wall_joint,
name = "Rigid Actuator",
max_length = 8,
min_length = 2,
control_code = 0 # Position Control
)
shared_rigid_actuator: SharedRigidActuator = base_manager.SharedRigidActuator()
rigid_actuator.attach_shared_attributes(shared_rigid_actuator)
visualizer.attach_shared_attributes(
shared_attributes = shared_rigid_actuator,
object_settings = {
"type": "cylinder",
"color": "black",
"radius": 0.5,
}
)
# We have an actuator, but it currently does nothing without the RigidActuatorController
# We will add the Controller and a DisplacementSensor for something in the real-world to control with
rigid_actuator_controller = RigidActuatorController(
controller_settings = {
"name": "Rigid Actuator Controller",
"analog_input_channel": 0,
"digital_input_channel": 0,
"units_per_volt": 2, # 2 meters per volt
"neutral_length": 5,
"neutral_voltage": 2.5, # At 2.5V, go to neutral_length of 5
"controls_pos/vel/accel": 0, # Controls position
"max_length": 8,
"min_length": 2
}
)
rigid_actuator_controller.attach_sim_target(shared_rigid_actuator)
hitl_controller.attach_rigid_actuator_controller(rigid_actuator_controller)
shared_rigid_actuator_controller: SharedRigidActuatorController = base_manager.SharedRigidActuatorController()
rigid_actuator_controller.attach_shared_attributes(shared_rigid_actuator_controller)
controller_plotter.attach_shared_attributes(
shared_attributes = shared_rigid_actuator_controller,
plot_settings = {
"input_voltage": {
"scalar": {
"subplot": 0,
"ylabel": f"Actuator Controller Voltage",
"color": "b"
}
},
"digital_input": {
"scalar": {
"subplot": 1,
"ylabel": f"Actuator Controller CTRL Input",
"color": "b"
}
},
"disp": {
"scalar": {
"subplot": 2,
"ylabel": f"Actuator Controller Displacement",
"color": "b"
}
}
}
)
# For the displacement sensor, we need 2 shared joints. 1 is the grounded joint of the actuator. the other is the parent
shared_south_wall_joint: SharedJoint = base_manager.SharedJoint()
south_wall_joint.attach_shared_attributes(shared_south_wall_joint)
shared_actuator_joint: SharedJoint = base_manager.SharedJoint()
actuator_joint.attach_shared_attributes(shared_actuator_joint)
rigid_actuator_displacement_sensor = DisplacementSensor(
parent_joint = shared_actuator_joint,
child_joint = shared_south_wall_joint,
sensor_settings = {
"name": f"Actuator Displacement Sensor",
"output_channel": 0,
"volts_per_meter": 1,
"neutral_length": 0,
"neutral_voltage": 0, # Voltage at neutral length
"max_length": 8,
}
)
shared_rigid_actuator_displacement_sensor: SharedDisplacementSensor = base_manager.SharedDisplacementSensor()
rigid_actuator_displacement_sensor.attach_shared_attributes(shared_rigid_actuator_displacement_sensor)
sensor_plotter.attach_shared_attributes(shared_attributes=shared_rigid_actuator_displacement_sensor,
plot_settings = {
"length": {
"scalar": {
"subplot": 0,
"ylabel": f"Actuator Displacement Length",
"color": "b"
}
},
"voltage": {
"true": { # true or noisy because we didn't add noise
"subplot": 1,
"ylabel": f"Actuator Displacement Voltage",
"color": "b"
}
},
}
)
hitl_controller.attach_displacement_sensor(rigid_actuator_displacement_sensor)
'''Create the physics simulation'''
simulation = BFSSimulation(
parent_joint = main_mass, # Because spring is a child of main_mass, it will be reached by BFS
settings = {
"duration": 1000, # Run the sim for 1000 seconds
"delta_t": None, # Run in real-time
"plotting_update_period": 0.01, # Update the plottable elements every 0.01 seconds
"sensor_update_period": 0.01, # Update the sensor elements every second (because we have none, this doesn't matter)
"controller_pull_period": 0.01 # Update the controller elements every seconds (again, we have none)
}
)
simulation.attach_shared_time(shared_sim_time)
'''Setup the processes and run them'''
physics_plotter_process = multiprocessing.Process(target=physics_plotter.run_process)
controller_plotter_process = multiprocessing.Process(target=controller_plotter.run_process)
sensor_plotter_process = multiprocessing.Process(target=sensor_plotter.run_process)
visualization_process = multiprocessing.Process(target=visualizer.run_process)
simulation_process = multiprocessing.Process(target=simulation.run_process)
hitl_controller_process = multiprocessing.Process(target=hitl_controller.run_process)
physics_plotter_process.start()
controller_plotter_process.start()
sensor_plotter_process.start()
visualization_process.start()
hitl_controller_process.start()
time.sleep(5) # Give the plotters and visualizer time to startup before the physics sim runs.
# Join the process
simulation_process.start()
simulation_process.join() # This blocks until the simulation finishes
physics_plotter_process.join()
controller_plotter_process.join()
sensor_plotter_process.join()
visualization_process.join()
hitl_controller_process.join()
# Close the manager
base_manager.shutdown()
if __name__ == "__main__":
main()

View File

@ -1,649 +0,0 @@
import numpy as np
import time
import multiprocessing
from multiprocessing.managers import BaseManager
from Visualization import Visualization, Plotter
from Simulation import BFSSimulation
from Physics_Elements import Joint, Spring, RigidActuator, SharedJoint, SharedSpring, SharedRigidActuator
from HITL_Controller import HITLController
from Controller_Input_Elements import RigidActuatorController, SharedRigidActuatorController
from Sensor_Elements import DisplacementSensor, LoadCellSpring, SharedDisplacementSensor, SharedLoadCellSpring
from Object_Sharing import SharedFloat
def main():
'''Setting up the BaseManager so data can be shared between processes'''
base_manager = BaseManager()
BaseManager.register('SharedFloat', SharedFloat)
BaseManager.register('SharedJoint', SharedJoint)
BaseManager.register('SharedSpring', SharedSpring)
BaseManager.register('SharedRigidActuator', SharedRigidActuator)
BaseManager.register('SharedRigidActuatorController', SharedRigidActuatorController)
BaseManager.register('SharedDisplacementSensor', SharedDisplacementSensor)
BaseManager.register('SharedLoadCellSpring', SharedLoadCellSpring)
base_manager.start()
'''Creating instances for plotter and visualizer'''
# Setup the plotter and visualization processes
stop_event = multiprocessing.Event()
stop_event.clear()
# Create a synchronized time between the processes so the plotter and physics sim are in sync
shared_sim_time:SharedFloat = base_manager.SharedFloat()
shared_sim_time.set(0.0) # Set the initial time = 0.0 seconds
# Create a real-time plotter for physics plotting.
physics_plotter = Plotter(
stop_event = stop_event,
plot_settings = {
"title": "Physics Plotter",
"xlabel": "Time (s)",
"num_subplots": 4,
"step_or_plot": "plot",
"subplots": [
{
"ylabel": "Position (m)"
},
{
"ylabel": "Velocity (m/s)"
},
{
"ylabel": "Accel (m/s/s)"
},
{
"ylabel": "Force (N)"
},
],
"window_length": 10, # Keep 100 seconds visible
"pull_interval": 10, # Pull data every 10 millisecond
"update_interval": 100 # Update the graph every 100 milliseconds
}
)
physics_plotter.attach_shared_x(shared_sim_time)
# Create a real-time plotter for the controller input plotting.
controller_plotter = Plotter(
stop_event = stop_event,
plot_settings = {
"title": "Controller Plotter",
"xlabel": "Time (s)",
"num_subplots": 3,
"step_or_plot": "plot",
"subplots": [
{
"ylabel": "Controller Input (V)",
},
{
"ylabel": "Digital Input"
},
{
"ylabel": "Controlled Pos (m)",
},
],
"window_length": 10, # Keep 100 seconds visible
"pull_interval": 10, # Pull data every 10 millisecond
"update_interval": 100 # Update the graph every 100 milliseconds
}
)
controller_plotter.attach_shared_x(shared_sim_time)
# Create a real-time plotter for the sensor output plotting
sensor_plotter = Plotter(stop_event=stop_event, plot_settings={
"title": "Sensor Plotter",
"xlabel": "Time (s)",
"num_subplots": 3,
"step_or_plot": "plot",
"subplots": [
{
"ylabel": "Actuator Length (m)",
},
{
"ylabel": "Load Cell Force (N)",
},
{
"ylabel": "Sensor Output (V)",
},
],
"window_length": 10, # Max data points to keep on the plot
"pull_interval": 10, # in milliseconds
"update_interval": 100, # In milliseconds.
})
sensor_plotter.attach_shared_x(shared_sim_time)
# Create a 3D visualization for the entire model
visualizer = Visualization(
stop_event = stop_event,
scene_settings = {
"canvas_width": 1600,
"canvas_height": 1000,
"wall_thickness": 0.1,
"cube_size": 20
}
)
'''Creating an instance for the HITLController'''
hitl_controller = HITLController(
stop_event = stop_event,
settings = {
"pull_from_sim_period": 5,
"plotting_update_period": 10
}
)
'''Setting up physics simulation'''
# Create the physics elements
main_mass = Joint(
pos = np.array([0, 10, 0]),
mass = 5,
fixed = False,
name = "Main mass",
integration_method = "adams-bashforth"
)
main_mass.add_damping(mom_ratio=0.0)
# Since we want to plot the physics of the mass we need shared attributes for it
shared_main_mass: SharedJoint = base_manager.SharedJoint()
main_mass.attach_shared_attributes(shared_main_mass)
physics_plotter.attach_shared_attributes(
shared_attributes = shared_main_mass,
plot_settings = {
"pos": {
"x": {
"subplot": 0,
"ylabel": "Main Poss x-Pos",
"color": "r"
}
},
"vel": {
"x": {
"subplot": 1,
"ylabel": "Main Poss x-Vel",
"color": "g"
}
},
"accel": {
"x": {
"subplot": 2,
"ylabel": "Main Poss x-Accel",
"color": "b"
}
}
}
)
visualizer.attach_shared_attributes(
shared_attributes = shared_main_mass,
object_settings = {
"type": "sphere",
"color": "red",
"radius": 0.5,
}
)
# Before we create a spring, we need to create the Joint on the wall for the Spring to mount to
north_wall_joint = Joint(
pos = np.array([10, 15, 0]),
mass = 1, # Does not matter because it is fixed
fixed = True, # We do not want it to ever move
name = "North Wall Joint"
)
north_spring = Spring(
parent_joint = main_mass,
child_joint = north_wall_joint,
unstretched_length = 13,
constant_stiffness = 100,
name = "North Spring"
)
shared_north_spring: SharedSpring = base_manager.SharedSpring()
north_spring.attach_shared_attributes(shared_north_spring)
visualizer.attach_shared_attributes(
shared_attributes = shared_north_spring,
object_settings = {
"type": "helix",
"color": "white",
"radius": 0.5,
"thickness": 0.1
}
)
west_wall_joint = Joint(
pos = np.array([0, 15, -10]),
mass = 1, # Does not matter because it is fixed
fixed = True, # We do not want it to ever move
name = "West Wall Joint"
)
west_spring = Spring(
parent_joint = main_mass,
child_joint = west_wall_joint,
unstretched_length = 9,
constant_stiffness = 100,
name = "West Spring"
)
shared_west_spring: SharedSpring = base_manager.SharedSpring()
west_spring.attach_shared_attributes(shared_west_spring)
visualizer.attach_shared_attributes(
shared_attributes = shared_west_spring,
object_settings = {
"type": "helix",
"color": "white",
"radius": 0.5,
"thickness": 0.1
}
)
# Setting up the actuator. This requires the "grounded_joint" to fix to. We also want a spring in between the actuator and main_mass
actuator_joint = Joint(
pos = np.array([-5, 10, 0]),
mass = 1, # Does not matter because it is fixed to an actuator
fixed = False, # We do not want it to ever move
name = "Actuator Joint"
)
actuator_spring = Spring(
parent_joint = main_mass,
child_joint = actuator_joint,
unstretched_length = 7,
constant_stiffness = 150,
name = "Actuator Spring"
)
shared_actuator_spring: SharedSpring = base_manager.SharedSpring()
actuator_spring.attach_shared_attributes(shared_actuator_spring)
physics_plotter.attach_shared_attributes(
shared_attributes = shared_actuator_spring,
plot_settings = {
"force": {
"scalar": {
"subplot": 3,
"ylabel": "Actuator Spring Force",
"color": "b"
}
},
}
)
visualizer.attach_shared_attributes(
shared_attributes = shared_actuator_spring,
object_settings = {
"type": "helix",
"color": "white",
"radius": 0.5,
"thickness": 0.1
}
)
south_wall_joint = Joint(
pos = np.array([-10, 10, 0]),
mass = 1, # Does not matter because it is fixed
fixed = True, # We do not want it to ever move
name = "South Wall Joint"
)
rigid_actuator = RigidActuator(
parent_joint = actuator_joint,
grounded_joint = south_wall_joint,
name = "Rigid Actuator",
max_length = 8,
min_length = 2,
control_code = 0 # Position Control
)
shared_rigid_actuator: SharedRigidActuator = base_manager.SharedRigidActuator()
rigid_actuator.attach_shared_attributes(shared_rigid_actuator)
visualizer.attach_shared_attributes(
shared_attributes = shared_rigid_actuator,
object_settings = {
"type": "cylinder",
"color": "black",
"radius": 0.5,
}
)
# We have an actuator, but it currently does nothing without the RigidActuatorController
# We will add the Controller and a DisplacementSensor for something in the real-world to control with
rigid_actuator_controller = RigidActuatorController(
controller_settings = {
"name": "Rigid Actuator Controller",
"analog_input_channel": 0,
"digital_input_channel": 0,
"units_per_volt": 2, # 2 meters per volt
"neutral_length": 5,
"neutral_voltage": 2.5, # At 2.5V, go to neutral_length of 5
"controls_pos/vel/accel": 0, # Controls position
"max_length": 8,
"min_length": 2
}
)
rigid_actuator_controller.attach_sim_target(shared_rigid_actuator)
hitl_controller.attach_rigid_actuator_controller(rigid_actuator_controller)
shared_rigid_actuator_controller: SharedRigidActuatorController = base_manager.SharedRigidActuatorController()
rigid_actuator_controller.attach_shared_attributes(shared_rigid_actuator_controller)
controller_plotter.attach_shared_attributes(
shared_attributes = shared_rigid_actuator_controller,
plot_settings = {
"input_voltage": {
"scalar": {
"subplot": 0,
"ylabel": f"Actuator Controller Voltage",
"color": "r"
}
},
"digital_input": {
"scalar": {
"subplot": 1,
"ylabel": f"Actuator Controller CTRL Input",
"color": "r"
}
},
"disp": {
"scalar": {
"subplot": 2,
"ylabel": f"Actuator Controller Displacement",
"color": "r"
}
}
}
)
# For the displacement sensor, we need 2 shared joints. 1 is the grounded joint of the actuator. the other is the parent
shared_south_wall_joint: SharedJoint = base_manager.SharedJoint()
south_wall_joint.attach_shared_attributes(shared_south_wall_joint)
shared_actuator_joint: SharedJoint = base_manager.SharedJoint()
actuator_joint.attach_shared_attributes(shared_actuator_joint)
rigid_actuator_displacement_sensor = DisplacementSensor(
parent_joint = shared_actuator_joint,
child_joint = shared_south_wall_joint,
sensor_settings = {
"name": f"Actuator Displacement Sensor",
"output_channel": 0,
"volts_per_meter": 1,
"neutral_length": 0,
"neutral_voltage": 0, # Voltage at neutral length
"max_length": 8,
}
)
shared_rigid_actuator_displacement_sensor: SharedDisplacementSensor = base_manager.SharedDisplacementSensor()
rigid_actuator_displacement_sensor.attach_shared_attributes(shared_rigid_actuator_displacement_sensor)
sensor_plotter.attach_shared_attributes(shared_attributes=shared_rigid_actuator_displacement_sensor,
plot_settings = {
"length": {
"scalar": {
"subplot": 0,
"ylabel": f"Actuator Displacement Length",
"color": "r"
}
},
"voltage": {
"true": { # true or noisy because we didn't add noise
"subplot": 2,
"ylabel": f"Actuator Displacement Voltage",
"color": "r"
}
},
}
)
hitl_controller.attach_displacement_sensor(rigid_actuator_displacement_sensor)
'''Second actuator'''
bottom_actuator_joint = Joint(
pos = np.array([0, 5, 0]),
mass = 1, # Does not matter because it is fixed to an actuator
fixed = False, # We do not want it to ever move
name = "Bottom Actuator Joint"
)
bottom_actuator_spring = Spring(
parent_joint = main_mass,
child_joint = bottom_actuator_joint,
unstretched_length = 7.5,
constant_stiffness = 1000,
name = "Bottom Actuator Spring"
)
shared_bottom_actuator_spring: SharedSpring = base_manager.SharedSpring()
bottom_actuator_spring.attach_shared_attributes(shared_bottom_actuator_spring)
physics_plotter.attach_shared_attributes(
shared_attributes = shared_bottom_actuator_spring,
plot_settings = {
"force": {
"scalar": {
"subplot": 3,
"ylabel": "Bottom Actuator Spring Force",
"color": "r"
}
},
}
)
visualizer.attach_shared_attributes(
shared_attributes = shared_bottom_actuator_spring,
object_settings = {
"type": "helix",
"color": "white",
"radius": 0.5,
"thickness": 0.1
}
)
# LoadCell on the bottom spring
bottom_spring_loadcell = LoadCellSpring(sensor_settings = {
"name": "Bottom Spring LC",
"mV/V": 1000,
"excitation": 10,
"full_scale_force": 5000, # What is the max force on the load cell
"output_channel": 8 # The output channel that the load cell outputs through the MCC3114. Must be between [0, 15]
}
)
bottom_spring_loadcell.attach_sim_source(shared_bottom_actuator_spring) # Point the LoadCellSpring to pull physics values from the SharedSpring
shared_bottom_spring_loadcell: SharedLoadCellSpring = base_manager.SharedLoadCellSpring()
bottom_spring_loadcell.attach_shared_attributes(shared_bottom_spring_loadcell)
hitl_controller.attach_load_cell(bottom_spring_loadcell)
sensor_plotter.attach_shared_attributes(shared_attributes = shared_bottom_spring_loadcell,
plot_settings = {
"force": {
"scalar": {
"subplot": 1, # Must be in range [0, num_subplots-1]
"ylabel": "Bottom Spring LC-Force",
"color": "g" # White is default if not listed. "b", "g", "r", "c", "m", "y", "k", "w"
},
},
"noisy_voltage": {
"scalar": {
"subplot": 2, # Must be in range [0, num_subplots-1]
"ylabel": "Bottom Spring LC-Voltage",
"color": "g" # White is default if not listed. "b", "g", "r", "c", "m", "y", "k", "w"
}
}
}
)
bottom_wall_joint = Joint(
pos = np.array([0, 0, 0]),
mass = 1, # Does not matter because it is fixed
fixed = True, # We do not want it to ever move
name = "Bottom Wall Joint"
)
bottom_rigid_actuator = RigidActuator(
parent_joint = bottom_actuator_joint,
grounded_joint = bottom_wall_joint,
name = "Bottom Rigid Actuator",
max_length = 10,
min_length = 1,
control_code = 1 # Position Control
)
shared_bottom_rigid_actuator: SharedRigidActuator = base_manager.SharedRigidActuator()
bottom_rigid_actuator.attach_shared_attributes(shared_bottom_rigid_actuator)
visualizer.attach_shared_attributes(
shared_attributes = shared_bottom_rigid_actuator,
object_settings = {
"type": "cylinder",
"color": "black",
"radius": 0.5,
}
)
# We have an actuator, but it currently does nothing without the RigidActuatorController
# We will add the Controller and a DisplacementSensor for something in the real-world to control with
bottom_rigid_actuator_controller = RigidActuatorController(
controller_settings = {
"name": "Bottom Rigid Actuator Controller",
"analog_input_channel": 1,
"digital_input_channel": 2,
"units_per_volt": 2, # 2 meters per volt
"neutral_length": 1.5,
"neutral_voltage": 2.5, # At 2.5V, go to neutral_length of 5
"controls_pos/vel/accel": 1, # Controls position
"max_length": 10,
"min_length": 1
}
)
bottom_rigid_actuator_controller.attach_sim_target(shared_bottom_rigid_actuator)
hitl_controller.attach_rigid_actuator_controller(bottom_rigid_actuator_controller)
shared_bottom_rigid_actuator_controller: SharedRigidActuatorController = base_manager.SharedRigidActuatorController()
bottom_rigid_actuator_controller.attach_shared_attributes(shared_bottom_rigid_actuator_controller)
controller_plotter.attach_shared_attributes(
shared_attributes = shared_bottom_rigid_actuator_controller,
plot_settings = {
"input_voltage": {
"scalar": {
"subplot": 0,
"ylabel": f"Bottom Actuator Controller Voltage",
"color": "b"
}
},
"digital_input": {
"scalar": {
"subplot": 1,
"ylabel": f"Bottom Actuator Controller CTRL Input",
"color": "b"
}
},
"disp": {
"scalar": {
"subplot": 2,
"ylabel": f"Bottom Actuator Controller Displacement",
"color": "b"
}
}
}
)
# For the displacement sensor, we need 2 shared joints. 1 is the grounded joint of the actuator. the other is the parent
shared_bottom_wall_joint: SharedJoint = base_manager.SharedJoint()
bottom_wall_joint.attach_shared_attributes(shared_bottom_wall_joint)
shared_bottom_actuator_joint: SharedJoint = base_manager.SharedJoint()
bottom_actuator_joint.attach_shared_attributes(shared_bottom_actuator_joint)
bottom_rigid_actuator_displacement_sensor = DisplacementSensor(
parent_joint = shared_bottom_actuator_joint,
child_joint = shared_bottom_wall_joint,
sensor_settings = {
"name": f"Bottom Actuator Displacement Sensor",
"output_channel": 1,
"volts_per_meter": 1,
"neutral_length": 0,
"neutral_voltage": 0, # Voltage at neutral length
"max_length": 8,
}
)
shared_bottom_rigid_actuator_displacement_sensor: SharedDisplacementSensor = base_manager.SharedDisplacementSensor()
bottom_rigid_actuator_displacement_sensor.attach_shared_attributes(shared_bottom_rigid_actuator_displacement_sensor)
sensor_plotter.attach_shared_attributes(shared_attributes=shared_bottom_rigid_actuator_displacement_sensor,
plot_settings = {
"length": {
"scalar": {
"subplot": 0,
"ylabel": f"Bottom Actuator Displacement Length",
"color": "b"
}
},
"voltage": {
"true": { # true or noisy because we didn't add noise
"subplot": 2,
"ylabel": f"Bottom Actuator Displacement Voltage",
"color": "b"
}
},
}
)
hitl_controller.attach_displacement_sensor(bottom_rigid_actuator_displacement_sensor)
'''Create the physics simulation'''
simulation = BFSSimulation(
parent_joint = main_mass, # Because spring is a child of main_mass, it will be reached by BFS
settings = {
"duration": 1000, # Run the sim for 1000 seconds
"delta_t": None, # Run in real-time
"plotting_update_period": 0.01, # Update the plottable elements every 0.01 seconds
"sensor_update_period": 0.01, # Update the sensor elements every second (because we have none, this doesn't matter)
"controller_pull_period": 0.01 # Update the controller elements every seconds (again, we have none)
}
)
simulation.attach_shared_time(shared_sim_time)
'''Setup the processes and run them'''
physics_plotter_process = multiprocessing.Process(target=physics_plotter.run_process)
controller_plotter_process = multiprocessing.Process(target=controller_plotter.run_process)
sensor_plotter_process = multiprocessing.Process(target=sensor_plotter.run_process)
visualization_process = multiprocessing.Process(target=visualizer.run_process)
simulation_process = multiprocessing.Process(target=simulation.run_process)
hitl_controller_process = multiprocessing.Process(target=hitl_controller.run_process)
physics_plotter_process.start()
controller_plotter_process.start()
sensor_plotter_process.start()
visualization_process.start()
hitl_controller_process.start()
time.sleep(5) # Give the plotters and visualizer time to startup before the physics sim runs.
# Join the process
simulation_process.start()
simulation_process.join() # This blocks until the simulation finishes
physics_plotter_process.join()
controller_plotter_process.join()
sensor_plotter_process.join()
visualization_process.join()
hitl_controller_process.join()
# Close the manager
base_manager.shutdown()
if __name__ == "__main__":
main()

View File

@ -1,514 +0,0 @@
import numpy as np
import multiprocessing
from multiprocessing.managers import BaseManager
import time
# if TYPE_CHECKING:
from Visualization import Visualization, Plotter
from Simulation import BFSSimulation
from Physics_Elements import Joint, Spring, RigidActuator, SharedRigidActuator, SharedJoint, SharedSpring, SharedRigidActuator
from Object_Sharing import SharedFloat
from HITL_Controller import HITLController
from Sensor_Elements import SharedLoadCellJoint, LoadCellSpring, SharedLoadCellSpring, DisplacementSensor, SharedDisplacementSensor
from Controller_Input_Elements import RigidActuatorController, SharedRigidActuatorController
steel_elastic_modulus = 200E9 # 200GPa
gravity = 9.81 # m/s^2
# Stage setup
stage_mass = 500_000 # Mass in kilograms
stage_weight = stage_mass * gravity
max_thrust = 1.5E7 # Max thrust in Newtons. Currently 15_000_000 N
stage_diameter = 5.5 # meters
stage_height = 60 # Meters
wall_thickness = 0.03 # Meters
# Actuator setup
rope_area = 5E-4 # Area in m^2. Currently 5 cm^2
rope_force_at_neutral_actuator = (max_thrust - stage_weight) / 4.0
lateral_offset = 2 # Meters away from the stage wall
actuator_neutral_length = 2
actuator_min_length = 0.5
actuator_max_length = 2.5
actuator_controller_neutral_voltage = 0
actuator_controller_mps_per_volt = 1 # meters per second per volt for the RMS controller to command the sim
actuator_displacement_sensor_neutral_voltage = 0
actuator_displacement_sensor_neutral_length = 0
actuator_displacement_sensor_volts_per_meter = 3 # Volts per meter for the actuator displacement sensor to output
rope_volts_per_newton = 1E-6 # 1 volt is 1 million newtons
# Stage calculations for stiffness of rope supporting main mass
stage_wall_area = np.pi / 4.0 * (stage_diameter**2 - (stage_diameter - wall_thickness*2)**2)
stage_stiffness = steel_elastic_modulus * stage_wall_area / stage_height # k = EA/l
stage_unstretched_length = stage_height - stage_weight / stage_stiffness
# Actuator calculations
rope_stiffness = steel_elastic_modulus * rope_area / stage_height # Estimate due to stageheight != rope length but it is close enough
rope_unstretched_length = rope_force_at_neutral_actuator / rope_stiffness + 56.45
actuator_lateral_offset = stage_diameter / 2 + lateral_offset
actuator_angle = np.arctan(stage_height/actuator_lateral_offset)
actuator_parent_height = actuator_neutral_length * np.sin(actuator_angle)
actuator_parent_lateral_offset = actuator_neutral_length * np.cos(actuator_angle)
def sinusoid_force1(time):
return 100_000*np.sin(0.1*time)
def sinusoid_force2(time):
return 125_000*np.sin(0.2*time+0.3)
def rope_stiffness_func(length, unstretched_length):
if length > unstretched_length:
return rope_stiffness
else:
return 0
def stage_setup_1(_max_run_time=100):
max_run_time = _max_run_time
# SIM SETUP #############################################
manager = BaseManager()
BaseManager.register('SharedFloat', SharedFloat)
BaseManager.register('SharedJoint', SharedJoint)
BaseManager.register('SharedSpring', SharedSpring)
BaseManager.register('SharedRigidActuator', SharedRigidActuator)
BaseManager.register('SharedLoadCellJoint', SharedLoadCellJoint)
BaseManager.register('SharedLoadCellSpring', SharedLoadCellSpring)
BaseManager.register('SharedRigidActuatorController', SharedRigidActuatorController)
BaseManager.register('SharedDisplacementSensor', SharedDisplacementSensor)
manager.start()
stop_event = multiprocessing.Event()
stop_event.clear()
shared_sim_time = manager.SharedFloat()
shared_sim_time.set(0.0)
physics_plotter = Plotter(stop_event=stop_event, plot_settings={
"title": "Physics Plotter",
"xlabel": "Time (s)",
"num_subplots": 5,
"step_or_plot": "plot",
"subplots": [
{
"ylabel": "Accel (m/s/s)",
},
{
"ylabel": "Vel (m/s)",
},
{
"ylabel": "Pos (m)",
},
{
"ylabel": "Pos 2 (m)",
},
{
"ylabel": "Force (N)",
},
],
"window_length": 100,
"pull_interval": 10, # in milliseconds
"update_interval": 100, # In milliseconds
})
physics_plotter.attach_shared_x(shared_sim_time)
sensor_plotter = Plotter(stop_event=stop_event, plot_settings={
"title": "Sensor Plotter",
"xlabel": "Time (s)",
"num_subplots": 4,
"step_or_plot": "plot",
"subplots": [
{
"ylabel": "Spring Force (N)",
},
{
"ylabel": "Actuator Length (m)",
},
{
"ylabel": "Actuator Position Output (V)",
},
{
"ylabel": "Load Cell Output (V)",
},
],
"window_length": 100, # Max data points to keep on the plot
"pull_interval": 10, # in milliseconds
"update_interval": 100, # In milliseconds.
})
sensor_plotter.attach_shared_x(shared_sim_time)
controller_plotter = Plotter(stop_event=stop_event, plot_settings={
"title": "Controller Plotter",
"xlabel": "Time (s)",
"num_subplots": 3,
"step_or_plot": "plot",
"subplots": [
{
"ylabel": "Controller Input (V)",
},
{
"ylabel": "Digital Input"
},
{
"ylabel": "Controlled Vel (m/s)",
},
],
"window_length": 100, # Max data points to keep on the plot
"pull_interval": 10, # in milliseconds
"update_interval": 100, # In milliseconds.
})
controller_plotter.attach_shared_x(shared_sim_time)
visualizer = Visualization(stop_event=stop_event, scene_settings={
"canvas_width": 1600,
"canvas_height": 1000,
"wall_thickness": 0.1,
"cube_size": 100
})
hitl_controller = HITLController(stop_event=stop_event, settings={
"pull_from_sim_period": 5, # Pull values for the sensors every XXX milliseconds. Shouldn't be faster than the update_period for the sim
"plotting_update_period": 10, # Update the shared attributes (for plotting) every YYY milliseconds
})
# SIM ELEMENTS SETUP
# Changes height to 59.78258707863 rather than 60m to that it is in equilibrium
main_mass = Joint(np.array([0, 60, 0]), mass=stage_mass, fixed=False, name="Stage Mass", integration_method="adams-bashforth")
main_mass_shared:SharedJoint = manager.SharedJoint()
main_mass.attach_shared_attributes(main_mass_shared)
main_mass.add_damping(mom_ratio=1.5)
main_mass.add_gravity()
main_mass.add_variable_force([sinusoid_force1, None, sinusoid_force2])
physics_plotter.attach_shared_attributes(main_mass_shared, plot_settings={
"accel": {
"x": {
"subplot": 0,
"ylabel": "Main Mass x-Accel",
"color": "r"
},
"y": {
"subplot": 0,
"ylabel": "Main Mass y-Accel",
"color": "g"
},
"z": {
"subplot": 0,
"ylabel": "Main Mass z-Accel",
"color": "b"
},
},
"vel": {
"x": {
"subplot": 1,
"ylabel": "Main Mass x-Vel",
"color": "r"
},
"y": {
"subplot": 1,
"ylabel": "Main Mass y-Vel",
"color": "g"
},
"z": {
"subplot": 1,
"ylabel": "Main Mass z-Vel",
"color": "b"
}
},
"pos": {
"x": {
"subplot": 2,
"ylabel": "Main Mass x-Pos",
"color": "r"
},
"y": {
"subplot": 3,
"ylabel": "Main Mass y-Pos",
"color": "g"
},
"z": {
"subplot": 2,
"ylabel": "Main Mass z-Pos",
"color": "b"
}
}
})
visualizer.attach_shared_attributes(main_mass_shared, object_settings={
"type": "sphere",
"color": "red",
"radius": stage_diameter/2.0
})
support_spring_joint = Joint(np.array([0, stage_height*2, 0]), mass=0, fixed=True, name="Support Spring Joint")
support_spring = Spring(parent_joint=main_mass, child_joint=support_spring_joint, unstretched_length=stage_unstretched_length, constant_stiffness=stage_stiffness, name="Support Spring")
support_spring_shared_attributes:SharedSpring = manager.SharedSpring()
support_spring.attach_shared_attributes(support_spring_shared_attributes)
physics_plotter.attach_shared_attributes(support_spring_shared_attributes, plot_settings={
"force": {
"y": {
"subplot": 4,
"ylabel": "Support Spring y-Force",
"color": "w"
}
}
})
# Setting up the actuators and ropes
for i in range(4):
if i == 0:
floor_x_pos = actuator_lateral_offset
floor_z_pos = 0
parent_x_pos = actuator_lateral_offset - actuator_parent_lateral_offset
parent_z_pos = 0
name = "East Actuator"
name2 = "East Spring"
color = "r"
elif i == 1:
floor_x_pos = 0
floor_z_pos = actuator_lateral_offset
parent_x_pos = 0
parent_z_pos = actuator_lateral_offset - actuator_parent_lateral_offset
name = "South Actuator"
name2 = "South Spring"
color = "g"
elif i == 2:
floor_x_pos = -1*actuator_lateral_offset
floor_z_pos = 0
parent_x_pos = -1*actuator_lateral_offset + actuator_parent_lateral_offset
parent_z_pos = 0
name = "West Actuator"
name2 = "West Spring"
color = "b"
elif i == 3:
floor_x_pos = 0
floor_z_pos = -1*actuator_lateral_offset
parent_x_pos = 0
parent_z_pos = -1*actuator_lateral_offset + actuator_parent_lateral_offset
name = "North Actuator"
name2 = "North Spring"
color = "w"
actuator_spring_joint = Joint(np.array([parent_x_pos, actuator_parent_height, parent_z_pos]), mass=0.001, fixed=False, name=f"{name} Spring Joint")
actuator_floor_joint = Joint(np.array([floor_x_pos, 0, floor_z_pos]), mass=0.001, fixed=False, name=f"{name} Floor Joint")
#rope = Spring(parent_joint=main_mass, child_joint=actuator_spring_joint, unstretched_length=rope_unstretched_length, constant_stiffness=rope_stiffness, name=name2)
rope = Spring(parent_joint=main_mass, child_joint=actuator_spring_joint, unstretched_length=rope_unstretched_length, stiffness_func=rope_stiffness_func, name=name2)
rope_shared:SharedSpring = manager.SharedSpring()
rope.attach_shared_attributes(rope_shared)
visualizer.attach_shared_attributes(rope_shared, object_settings={
"type": "helix",
"color": "white",
"radius": 0.1,
"thickness": 0.1
})
rope_lc = LoadCellSpring(sensor_settings={
"name": f"{name2} LC",
"mV/V": 1000,
"excitation": 10,
"full_scale_force": (1/rope_volts_per_newton * 10),
"output_channel": i*4 + 3,
"noise_settings": {
"static_error_band": 0.15, # % of FSO
"non-linearity": 0.15, # % of FSO
"hysteresis": 0.07, # % of FSO
"repeatability": 0.02, # % of FSO
"thermal_error": 0.005, # % of reading per degree F
"temperature_offset": 10 # Degrees F off from calibration temperature
}
})
rope_lc.attach_sim_source(rope_shared)
rope_lc_shared_attributes = manager.SharedLoadCellSpring()
rope_lc.attach_shared_attributes(rope_lc_shared_attributes)
hitl_controller.attach_load_cell(rope_lc)
sensor_plotter.attach_shared_attributes(rope_lc_shared_attributes, plot_settings={
"force": {
"scalar": {
"subplot": 0,
"ylabel": f"{name2} LC Force",
"color": color
}
},
"noisy_voltage": {
"scalar": {
"subplot": 3,
"ylabel": f"{name2} LC Noisy-Voltage",
"color": color
}
},
})
actuator = RigidActuator(actuator_spring_joint, actuator_floor_joint, name=name, max_length=actuator_max_length, min_length=actuator_min_length, control_code=1)
actuator_shared_attributes:SharedRigidActuator = manager.SharedRigidActuator()
actuator.attach_shared_attributes(actuator_shared_attributes)
visualizer.attach_shared_attributes(actuator_shared_attributes, object_settings={
"type": "cylinder",
"color": "black",
"radius": 0.3
})
shared_actuator_spring_joint: SharedJoint = manager.SharedJoint()
actuator_spring_joint.attach_shared_attributes(shared_actuator_spring_joint)
shared_actuator_floor_joint: SharedJoint = manager.SharedJoint()
actuator_floor_joint.attach_shared_attributes(shared_actuator_floor_joint)
actuator_disp_sensor = DisplacementSensor(parent_joint=shared_actuator_spring_joint, child_joint=shared_actuator_floor_joint, sensor_settings={
"name": f"{name} Sensor",
"output_channel": i*4 + 2,
"volts_per_meter": actuator_displacement_sensor_volts_per_meter,
"neutral_length": actuator_displacement_sensor_neutral_length, # Length at neutral voltage
"neutral_voltage": actuator_displacement_sensor_neutral_voltage,
"max_length": actuator_max_length,
"noise_settings": {
"static_error_band": 0.15, # % of FSO
"non-linearity": 0.15, # % of FSO
"hysteresis": 0.07, # % of FSO
"repeatability": 0.02, # % of FSO
"thermal_error": 0.005, # % of reading per degree F
"temperature_offset": 10 # Degrees F off from calibration temperature
}
})
actuator_disp_sensor_shared:SharedDisplacementSensor = manager.SharedDisplacementSensor()
actuator_disp_sensor.attach_shared_attributes(actuator_disp_sensor_shared)
sensor_plotter.attach_shared_attributes(actuator_disp_sensor_shared, plot_settings={
"length": {
"scalar": {
"subplot": 1,
"ylabel": f"{name} Length",
"color": color
}
},
"voltage": {
"noisy": {
"subplot": 2,
"ylabel": f"{name} Noisy-Voltage",
"color": color
}
},
})
hitl_controller.attach_displacement_sensor(actuator_disp_sensor)
actuator_controller = RigidActuatorController(controller_settings={
"name": "Main Actuator Controller",
"analog_input_channel": i,
"digital_input_channel": i,
"units_per_volt": actuator_controller_mps_per_volt, # How far the actuator moves per input volt (m/s or m/s/s if controlling vel or accel)
"neutral_length": actuator_neutral_length, # Length at neutral voltage
"neutral_voltage": actuator_controller_neutral_voltage, # Voltage to go to neutral length
"controls_pos/vel/accel": 1, # 0 = controls pos, 1 = controls vel, 2 = controls accel,
"max_length": actuator_max_length,
"min_length": actuator_min_length
})
actuator_controller_shared_attributes = manager.SharedRigidActuatorController() # For plotting
actuator_controller.attach_shared_attributes(actuator_controller_shared_attributes)
actuator_controller.attach_sim_target(actuator_shared_attributes) # So the controller can update sim elements
hitl_controller.attach_rigid_actuator_controller(actuator_controller)
controller_plotter.attach_shared_attributes(actuator_controller_shared_attributes, plot_settings={
"input_voltage": {
"scalar": {
"subplot": 0,
"ylabel": f"{name} Controller Voltage",
"color": color
}
},
"digital_input": {
"scalar": {
"subplot": 1,
"ylabel": f"{name} Controller CTRL Input",
"color": color
}
},
"vel": {
"x": {
"subplot": 2,
"ylabel": f"{name} Controlled x-Vel",
"color": color
}
}
})
# PROCESSES #############################################################
# Make the simulation process
simulation = BFSSimulation(parent_joint=main_mass, settings={
"duration": max_run_time,
"delta_t": None,
"shared_update_period": 0.1, # not used
"plotting_update_period": 0.01,
"sensor_update_period": 0.01,
"controller_pull_period": 0.01
})
simulation.attach_shared_time(shared_sim_time)
simulation_process = multiprocessing.Process(target=simulation.run_process)
# Start the processes
# Start the visualization process
visualization_process = multiprocessing.Process(target=visualizer.run_process)
visualization_process.start()
# Start the physics_plotter process
physics_plotter_process = multiprocessing.Process(target=physics_plotter.run_process)
physics_plotter_process.start()
# Start the sensor physics_plotter process
sensor_plotter_process = multiprocessing.Process(target=sensor_plotter.run_process)
sensor_plotter_process.start()
# Start the sensor physics_plotter process
controller_plotter_process = multiprocessing.Process(target=controller_plotter.run_process)
controller_plotter_process.start()
# Start the HITL interface process
hitl_process = multiprocessing.Process(target=hitl_controller.run_process)
hitl_process.start()
time.sleep(5)
simulation_process.start()
# Join the processes
simulation_process.join()
stop_event.set()
visualization_process.join()
physics_plotter_process.join()
sensor_plotter_process.join()
controller_plotter_process.join()
hitl_process.join()
# Close the manager
manager.shutdown()
def main():
max_run_time = 10000
#multiprocessed_double_mass_spring(100)
#single_spring(max_run_time)
stage_setup_1(max_run_time)
if __name__ == "__main__":
main()

View File

@ -1,112 +0,0 @@
import matplotlib.pyplot as plt
from copy import deepcopy
class StateSpace():
def __init__(self, pos, vel, accel):
self.pos = pos
self.vel = vel
self.accel = accel
def print(self):
print(f"Pos: {self.pos}, Vel: {self.vel}, Accel: {self.accel}")
class Block():
def __init__(self):
self.mass = 5
self.statespace = StateSpace(5,0,0)
self.net_force = 0
class Spring():
def __init__(self):
self.mass = 1
self.pos1 = 0
self.pos2 = 10
self.vel = 0
self.accel = 0
self.length = self.pos2 - self.pos1
self.zero_length = 10
self.k = 10
self.del_l = self.length - self.zero_length
self.force = self.del_l * self.k
def get_force(self):
self.length = self.pos2 - self.pos1
self.del_l = self.length - self.zero_length
self.force = self.del_l * self.k
return self.force
applied_force = 0
block = Block()
spring = Spring()
# Initialize lists to store data for plotting
t_values = []
pos_values = []
vel_values = []
accel_values = []
force_net_values = []
spring_force_values = []
del_l_values = []
del_t = 0.1
t = 0
while t < 10:
spring.pos1 = block.statespace.pos
spring.force = spring.get_force()
block.net_force = applied_force + spring.force
block.statespace.accel = block.net_force / block.mass
block.statespace.vel += block.statespace.accel * del_t
block.statespace.pos += block.statespace.vel * del_t
# Store data for plotting
t_values.append(t)
pos_values.append(block.statespace.pos)
vel_values.append(block.statespace.vel)
accel_values.append(block.statespace.accel)
force_net_values.append(block.net_force)
spring_force_values.append(spring.force)
del_l_values.append(spring.del_l)
t += del_t
print(max(pos_values))
# Plot the data
plt.figure(figsize=(12, 8))
plt.subplot(3, 1, 1)
plt.plot(t_values, pos_values)
plt.plot(t_values, del_l_values)
plt.title('Position vs Time')
plt.legend(['Position', 'Delta Length'])
plt.xlabel('Time (s)')
plt.ylabel('Position')
plt.subplot(3, 1, 2)
plt.plot(t_values, vel_values)
plt.title('Velocity vs Time')
plt.xlabel('Time (s)')
plt.ylabel('Velocity')
plt.subplot(3, 1, 3)
plt.plot(t_values, accel_values)
plt.plot(t_values, force_net_values)
plt.plot(t_values, spring_force_values)
plt.legend(['Acceleration', 'Net Force', 'Spring Force'])
plt.title('Acceleration, Net Force, and Spring Force vs Time')
plt.xlabel('Time (s)')
plt.ylabel('Value')
plt.tight_layout()
plt.show()

View File

@ -1,436 +0,0 @@
import math
import numpy as np
from typing import List
import matplotlib.pyplot as plt
ZERO_VECTOR = np.array([0.0, 0.0, 0.0])
class StateSpace():
def __init__(self, pos=np.copy(ZERO_VECTOR), vel=np.copy(ZERO_VECTOR), accel=np.copy(ZERO_VECTOR), force=np.copy(ZERO_VECTOR)):
self.pos = pos
self.vel = vel
self.accel = accel
self.force = force
def get_pos(self):
return self.pos
def get_vel(self):
return self.vel
def get_accel(self):
return self.accel
def get_force(self):
return self.force
def set_pos(self, new_pos):
self.pos = new_pos
def set_vel(self, new_vel):
self.vel = new_vel
def set_accel(self, new_accel):
self.accel = new_accel
def set_force(self, new_force):
self.force = new_force
def integrate(self, delta_t):
self.vel = self.vel + self.accel * delta_t
self.pos = self.pos + self.vel * delta_t
def print(self):
print(f"Pos: {self.pos}, Vel: {self.vel}, Accel: {self.accel}")
class Joint():
def __init__(self, pos=np.copy(ZERO_VECTOR), fixed: bool=False):
self.statespace = StateSpace(pos, ZERO_VECTOR, ZERO_VECTOR, ZERO_VECTOR)
self.connected_springs: List[Spring] = []
self.connected_masses: List[Mass] = []
self.connected_rigid_bodies: List[RigidBody] = []
self.external_forces: List[List[float, float, float]] = []
self.fixed = fixed
self.parent_joints: List[Joint] = []
self.child_joints: List[Joint] = []
def get_pos(self):
return self.statespace.get_pos()
def get_vel(self):
return self.statespace.get_vel()
def get_accel(self):
return self.statespace.get_accel()
def get_force(self):
return self.statespace.get_force()
def get_connected_springs(self):
return self.connected_springs
def get_connected_masses(self):
return self.connected_masses
def get_connected_rigid_bodies(self):
return self.connected_rigid_bodies
def is_fixed(self):
return self.fixed
def add_spring(self, new_spring):
self.connected_springs.append(new_spring)
def add_mass(self, new_mass):
self.connected_masses.append(new_mass)
def add_rigid_body(self, new_rigid_body):
self.connected_rigid_bodies.append(new_rigid_body)
def add_extenal_force(self, new_force = ZERO_VECTOR):
self.external_forces.append(new_force)
def calc_net_spring_force(self):
net_spring_force = 0
for spring in self.connected_springs:
spring_force = spring.calc_spring_force() # This is the force the spring exerts on the joint ASSUMING self is parent_joint of the spring
net_spring_force += spring_force
return net_spring_force
def calc_net_external_force(self):
net_external_force = np.copy(ZERO_VECTOR)
for force in self.external_forces:
net_external_force += force
return net_external_force
def calc_net_force(self):
net_external_force = self.calc_net_external_force()
net_spring_force = self.calc_net_spring_force()
self.statespace.force = net_external_force + net_spring_force
return self.statespace.force
def integrate_accel_vel(self, delta_t: float=0.1):
self.statespace.integrate(delta_t)
def set_accel(self, accel):
self.statespace.set_accel(accel)
def find_parent_joints(self):
for spring in self.connected_springs:
if spring.child_joint == self:
if spring.parent_joint not in self.parent_joints:
self.parent_joints.append(spring.parent_joint)
for mass in self.connected_masses:
if mass.child_joint == self:
if mass.parent_joint not in self.parent_joints:
self.parent_joints.append(mass.parent_joint)
def find_child_joints(self):
for spring in self.connected_springs:
if spring.parent_joint == self:
if spring.child_joint not in self.child_joints:
self.child_joints.append(spring.child_joint)
for mass in self.connected_masses:
if mass.parent_joint == self:
if mass.child_joint not in self.child_joints:
self.child_joints.append(mass.child_joint)
class Spring():
def __init__(self, parent_joint: Joint, child_joint: Joint, zero_length: float=0, stiffness: float=0):
self.parent_joint = parent_joint
self.child_joint = child_joint
self.zero_length = zero_length
self.stiffness = stiffness
self.vector = self.calc_r_vector()
self.length = self.calc_length()
self.unit_vector = self.calc_r_unit_vector()
self.spring_force = self.calc_spring_force()
self.parent_joint.add_spring(self)
self.child_joint.add_spring(self)
def calc_r_vector(self):
self.vector = self.child_joint.get_pos() - self.parent_joint.get_pos()
return self.vector
def calc_length(self):
self.vector = self.calc_r_vector()
return np.linalg.norm(self.vector)
def calc_r_unit_vector(self):
self.vector = self.calc_r_vector()
self.length = self.calc_length()
self.unit_vector = self.vector / self.length
return self.unit_vector
def calc_spring_force(self):
'''Positive force is in tension. Aka, the spring PULLS on the other object'''
self.length = self.calc_length()
del_length = self.length - self.zero_length
spring_force = del_length * self.stiffness
self.r_unit_vector = self.calc_r_unit_vector()
self.spring_force = spring_force * self.r_unit_vector
return self.spring_force
class Mass():
'''
A mass is a point mass located in the center of 2 joints.
It cannot exert a force
'''
def __init__(self, parent_joint: Joint, child_joint: Joint, mass: float=0):
self.parent_joint = parent_joint
self.child_joint = child_joint
self.mass = mass
self.parent_joint.add_mass(self)
self.child_joint.add_mass(self)
self.statespace = StateSpace((child_joint.get_pos() + parent_joint.get_pos()) / 2.0,
(child_joint.get_vel() + parent_joint.get_vel()) / 2.0,
(child_joint.get_accel() + parent_joint.get_accel()) / 2.0,
(child_joint.get_force() + parent_joint.get_force()) / 2.0)
def set_accel(self, accel):
self.statespace.set_accel(accel)
def integrate_accel_vel(self, delta_t: float=0.1):
self.statespace.integrate(delta_t)
class Simulation():
def __init__(self, parent_joint: Joint, duration: float, delta_t: float):
self.parent_joint = parent_joint
self.duration = duration
self.delta_t = delta_t
self.joints: List[Joint] = []
self.masses: List[Mass] = []
self.springs: List[Spring] = []
self.rigid_bodies: List[RigidBody] = []
def get_all_nodes_and_edges(self):
'''
Do a BFS to get all of the joints (nodes) and springs/masses (edges) in the system
'''
queue: List[Joint] = []
queue.append(self.parent_joint)
while queue:
node: Joint
node = queue.pop(0)
if node not in self.joints:
self.joints.append(node)
connected_springs = node.get_connected_springs()
connected_masses = node.get_connected_masses()
connected_rigid_bodies = node.get_connected_rigid_bodies()
for spring in connected_springs:
if spring not in self.springs:
self.springs.append(spring)
if spring.child_joint not in self.joints and spring.child_joint not in queue:
queue.append(spring.child_joint)
for mass in connected_masses:
if mass not in self.masses:
self.masses.append(mass)
if mass.child_joint not in self.joints and mass.child_joint not in queue:
queue.append(mass.child_joint)
for rigid_body in connected_rigid_bodies:
if rigid_body not in self.rigid_bodies:
self.rigid_bodies.append(rigid_body)
if rigid_body.child_joint not in self.joints and rigid_body.child_joint not in queue:
queue.append(rigid_body.child_joint)
def print_components(self):
print("Joints:")
count = 0
for joint in self.joints:
print(f"Accel: {joint.get_accel()}")
print(f"Vel: {joint.get_vel()}")
print(f"Pos: {joint.get_pos()}")
print()
print("Masses:")
count = 0
for mass in self.masses:
print(f"Accel: {mass.statespace.get_accel()}")
print(f"Vel: {mass.statespace.get_vel()}")
print(f"Pos: {mass.statespace.get_pos()}")
print()
print("Springs:")
count = 0
for spring in self.springs:
print(f"Spring Force: {spring.spring_force}")
print(f"Spring Length: {spring.length}")
print()
print("Rigid Bodies:")
count = 0
for rigid_body in self.rigid_bodies:
print(f"Transfer Force: {rigid_body.force}")
print()
def calc_force_at_every_joint(self):
'''
At every joint, calculate the net force at each joint (this accounts for springs and external forces).
'''
for joint in self.joints:
joint.calc_net_force()
def calc_rigid_body_force(self):
for body in self.rigid_bodies:
body.force = 0
def calc_accel_at_every_mass(self):
'''
Using the sum of the forces at the 2 joints on each mass, we calc the accel of the mass
'''
for mass in self.masses:
net_force = mass.parent_joint.get_force() + mass.child_joint.get_force()
accel = net_force / mass.mass
mass.set_accel(accel)
def assign_joint_accel(self):
'''
If the joint is fixed, accel = 0
'''
net_joint_accel = np.copy(ZERO_VECTOR)
for joint in self.joints:
if joint.is_fixed() == True:
continue
for mass in joint.get_connected_masses():
net_joint_accel += mass.statespace.get_accel()
joint.set_accel(net_joint_accel)
def integrate_timestep(self):
self.calc_force_at_every_joint()
self.calc_accel_at_every_mass()
self.assign_joint_accel()
for joint in self.joints:
joint.integrate_accel_vel(self.delta_t)
for mass in self.masses:
mass.integrate_accel_vel(self.delta_t)
def run(self):
mass_pos_values = []
time_values = []
t = 0
while t < self.duration:
self.integrate_timestep()
self.print_components()
mass_pos_values.append(self.masses[0].statespace.get_pos())
time_values.append(t)
print("*"*100)
t += self.delta_t
plt.close()
# Plot the data
plt.figure(figsize=(12, 8))
plt.plot(time_values, mass_pos_values)
plt.title('Position vs Time')
plt.xlabel('Time (s)')
plt.ylabel('Position')
plt.show()
class BFSSimulation():
def __init__(self, parent_joint: Joint, duration: float, delta_t: float):
self.parent_joint = parent_joint
self.duration = duration
self.delta_t = delta_t
self.joints: List[Joint] = []
self.get_joints_bfs()
self.get_parent_joints_for_every_joint()
def get_joints_bfs(self):
queue: List[Joint] = []
queue.append(self.parent_joint)
visited_springs: List[Spring] = []
visited_masses: List[Spring] = []
while queue:
node: Joint
node = queue.pop(0)
if node not in self.joints:
self.joints.append(node)
connected_springs = node.get_connected_springs()
connected_masses = node.get_connected_masses()
for spring in connected_springs:
if spring not in visited_springs:
visited_springs.append(spring)
if spring.child_joint not in self.joints and spring.child_joint not in queue:
queue.append(spring.child_joint)
for mass in connected_masses:
if mass not in visited_masses:
visited_masses.append(mass)
if mass.child_joint not in self.joints and mass.child_joint not in queue:
queue.append(mass.child_joint)
def get_parent_joints_for_every_joint(self):
for joint in self.joints:
joint.find_parent_joints()
def get_child_joints_for_every_joint(self):
for joint in self.joints:
joint.find_child_joints()
def integrate_joints(self):
for joint in self.joints:
joint.calc_net_force()
def main():
joint_left_mass1 = Joint(np.array([0,0,0]), fixed=False)
joint_mass1_spring1 = Joint(np.array([0,0,0]), fixed=False)
joint_spring1_rigidbody1 = Joint(np.array([10,0,0]), fixed=False)
joint_rigidbody1_spring2 = Joint(np.array([10,0,0]), fixed=False)
joint_right_spring2 = Joint(np.array([20,0,0]), fixed=True)
mass1 = Mass(joint_left_mass1, joint_mass1_spring1, 5)
spring1 = Spring(joint_mass1_spring1, joint_spring1_rigidbody1, 10, 10)
#rigidbody1 = RigidBody(joint_spring1_rigidbody1, joint_rigidbody1_spring2, 0.0)
spring2 = Spring(joint_rigidbody1_spring2, joint_right_spring2, 10, 100)
joint_left_mass1.add_extenal_force(np.array([5, 0, 0]))
simulation = BFSSimulation(joint_left_mass1, 1, 0.1)
simulation.get_all_nodes_and_edges()
simulation.run()
if __name__ == "__main__":
main()

View File

@ -1,30 +0,0 @@
import pickle
from PhysicsElements import StateSpace, ZERO_VECTOR, Joint, Spring
import numpy as np
from Object_Sharing import SharedJointList
def is_picklable(obj):
try:
pickle.dumps(obj)
return True
except pickle.PicklingError:
return False
from multiprocessing.managers import BaseManager
def func(x,y):
return 10
if __name__ == '__main__':
BaseManager.register('Joint', Joint)
BaseManager.register('Spring', Spring)
manager = BaseManager()
manager.start()
north_wall_joint:Joint = manager.Joint(np.array([10,0,0]), mass=0.001, fixed=True, name="North Wall Joint")
main_mass:Joint = manager.Joint(np.array([0,0,0]), mass=3, fixed=False, name="Blue Mass")
middle_mass:Joint = manager.Joint(np.array([5,0,0]), mass=5, fixed=False, name="White Mass")
spring1 = manager.Spring(parent_joint=middle_mass, child_joint=north_wall_joint, unstretched_length=7.5, stiffness_func=func, name="Spring 1")
print(is_picklable(spring1))

View File

@ -1,111 +0,0 @@
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import random
from multiprocessing import Process, Manager
class LivePlot:
def __init__(self, mult=1, max_data_points=100):
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(8, 6))
self.fig = fig
self.ax1 = ax1
self.ax2 = ax2
self.ax3 = ax3
self.mult = mult
self.ax1.set_ylabel('Plot 1')
self.ax2.set_ylabel('Plot 2')
self.ax3.set_ylabel('Plot 3')
self.ax3.set_xlabel('Time')
self.max_data_points = max_data_points
self.t = 0
self.x_data = []
self.y_data1 = []
self.y_data1_2 = []
self.y_data2 = []
self.y_data3 = []
# Initialize lines (empty initially)
self.line1, = self.ax1.plot([], [], label='Plot 1')
self.line1_2, = self.ax1.plot([], [], label='Plot 1.2')
self.line2, = self.ax2.plot([], [], label='Plot 2')
self.line3, = self.ax3.plot([], [], label='Plot 3')
self.ax1.legend()
self.ax2.legend()
self.ax3.legend()
def generate_random_data(self):
return random.randint(1, 100)
def update_data_external(self):
# Simulate external updates (replace with your actual data update mechanism)
new_x = self.t
self.t += 1
new_y1 = self.generate_random_data() * self.mult
new_y2 = self.generate_random_data() * self.mult
new_y3 = self.generate_random_data() * self.mult
self.x_data.append(new_x)
self.y_data1.append(new_y1)
self.y_data1_2.append(new_y1 * -1) # Example modification of data
self.y_data2.append(new_y2)
self.y_data3.append(new_y3)
# Keep only the last `max_data_points` data points
self.x_data = self.x_data[-self.max_data_points:]
self.y_data1 = self.y_data1[-self.max_data_points:]
self.y_data1_2 = self.y_data1_2[-self.max_data_points:]
self.y_data2 = self.y_data2[-self.max_data_points:]
self.y_data3 = self.y_data3[-self.max_data_points:]
def update_plot(self, i):
self.update_data_external()
# Update plot data
self.line1.set_data(self.x_data, self.y_data1)
self.line1_2.set_data(self.x_data, self.y_data1_2)
self.line2.set_data(self.x_data, self.y_data2)
self.line3.set_data(self.x_data, self.y_data3)
# Adjust plot limits (x-axis)
if len(self.x_data) > 1:
self.ax1.set_xlim(self.x_data[0], self.x_data[-1])
self.ax2.set_xlim(self.x_data[0], self.x_data[-1])
self.ax3.set_xlim(self.x_data[0], self.x_data[-1])
# Adjust plot limits (y-axis) - optional
self.ax1.relim()
self.ax1.autoscale_view()
self.ax2.relim()
self.ax2.autoscale_view()
self.ax3.relim()
self.ax3.autoscale_view()
#return self.line1, self.line2, self.line3
def animate(self):
anim = animation.FuncAnimation(self.fig, self.update_plot, frames=1000, interval=10, blit=False)
plt.show()
#anim.save(filename="test.mp4")
# Function to run animation in a separate process
def run_animation(fig, ax1, ax2, ax3):
#live_plot = LivePlot(fig, ax1, ax2, ax3)
#live_plot.animate()
pass
# Example usage with multiprocessing
if __name__ == '__main__':
plot1 = LivePlot(mult=1)
plot2 = LivePlot(mult=5)
process1 = Process(target=plot1.animate)
process2 = Process(target=plot2.animate)
process1.start()
process2.start()
process1.join()
process2.join()

View File

@ -1,59 +0,0 @@
from mcculw import ul
from mcculw.enums import DigitalIODirection
from mcculw.ul import ULError
# Define the board number
BOARD_NUM = 0
# Define the ports and the direction of data flow
PORT_A = 10
PORT_B = 11
PORT_C_LOW = 12
PORT_C_HIGH = 13
# Configure ports as input or output
def configure_ports():
try:
ul.d_config_port(BOARD_NUM, PORT_A, DigitalIODirection.OUT)
ul.d_config_port(BOARD_NUM, PORT_B, DigitalIODirection.IN)
ul.d_config_port(BOARD_NUM, PORT_C_LOW, DigitalIODirection.OUT)
ul.d_config_port(BOARD_NUM, PORT_C_HIGH, DigitalIODirection.IN)
print("Ports configured successfully.")
except ULError as e:
print(f"Error configuring ports: {e}")
# Write data to a port
def write_data(port, data):
try:
ul.d_out(BOARD_NUM, port, data)
print(f"Data {data} written to port {port} successfully.")
except ULError as e:
print(f"Error writing data to port {port}: {e}")
# Read data from a port
def read_data(port):
try:
data = ul.d_in(BOARD_NUM, port)
print(f"Data read from port {port}: {data}")
return data
except ULError as e:
print(f"Error reading data from port {port}: {e}")
return None
def main():
# Configure the ports
configure_ports()
# Write some data to PORT_A and PORT_C_LOW
write_data(PORT_A, 0xFF) # Write 0xFF (255) to PORT_A
write_data(PORT_C_LOW, 0xAA) # Write 0xAA (170) to PORT_C_LOW
# Read data from PORT_B and PORT_C_HIGH
data_b = read_data(PORT_B)
data_c_high = read_data(PORT_C_HIGH)
# Perform additional processing as needed
# For example, you might want to perform some logic based on the input data
if __name__ == "__main__":
main()

View File

@ -1,8 +0,0 @@
import cProfile
import pstats
# Load the profiling data from the file
stats = pstats.Stats('profile_results.txt')
# Print the top 10 functions by cumulative time
stats.sort_stats('cumulative').print_stats(25)

View File

@ -1,78 +0,0 @@
import pyqtgraph as pg
from pyqtgraph.Qt import QtWidgets, QtCore
import numpy as np
class RealTimePlot:
def __init__(self, update_interval=1, max_display_time=10, num_subplots=2, line_styles=None):
# Create the application
self.app = QtWidgets.QApplication([])
# Create a plot window
self.win = pg.GraphicsLayoutWidget(show=True, title="Real-Time Plot")
self.win.resize(1000, 600)
# Add plots
self.plots = []
self.curves = []
self.line_styles = line_styles if line_styles is not None else ['line'] * num_subplots
for i in range(num_subplots):
plot = self.win.addPlot(title=f"Subplot {i+1}")
plot.setLabel('left', f'Subplot {i+1} Y-Axis')
plot.addLegend() # Add a legend to each plot
self.plots.append(plot)
if self.line_styles[i] == 'step':
curve = plot.plot(pen='y', name=f'Subplot {i+1} Data', stepMode=True)
else:
curve = plot.plot(pen='y', name=f'Subplot {i+1} Data')
self.curves.append(curve)
if i < num_subplots - 1:
self.win.nextRow()
# Data buffers
self.xdata = [np.empty(0) for _ in range(num_subplots)]
self.ydata = [np.empty(0) for _ in range(num_subplots)]
# Parameters
self.update_interval = update_interval
self.max_display_time = max_display_time
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.update_live_window)
self.timer.start(update_interval)
def update_live_window(self):
for i in range(len(self.plots)):
# Generate new data
t = self.xdata[i][-1] + self.update_interval / 1000.0 if len(self.xdata[i]) > 0 else 0
y = np.sin(2 * np.pi * t + i) # Different phase for each subplot
# Append new data to buffers
self.xdata[i] = np.append(self.xdata[i], t)
self.ydata[i] = np.append(self.ydata[i], y)
# Remove old data to keep the buffer size within max_display_time
if t > self.max_display_time:
self.xdata[i] = self.xdata[i][self.xdata[i] > t - self.max_display_time]
self.ydata[i] = self.ydata[i][-len(self.xdata[i]):]
# Ensure correct lengths for step mode
if self.line_styles[i] == 'step':
xdata_step = np.append(self.xdata[i], self.xdata[i][-1] + self.update_interval / 1000.0)
self.curves[i].setData(xdata_step, self.ydata[i])
else:
self.curves[i].setData(self.xdata[i], self.ydata[i])
def run(self):
# Start the Qt event loop
QtWidgets.QApplication.instance().exec_()
# Parameters
update_interval = 100 # milliseconds
max_display_time = 10 # seconds
num_subplots = 2 # number of subplots
line_styles = ['line', 'step'] # specify 'line' or 'step' for each subplot
# Create and run the real-time plot
rt_plot = RealTimePlot(update_interval, max_display_time, num_subplots, line_styles)
rt_plot.run()

View File

@ -1,99 +0,0 @@
import multiprocessing
from multiprocessing.managers import BaseManager
import numpy as np
import time
import random
from PhysicsElements import Joint, Spring, SpringWithMass, Actuator, StateSpace
class CustomManager(BaseManager):
# nothing
pass
def worker_process(shared_state_space):
while True:
print("Worker Process:")
shared_state_space.print()
time.sleep(0.1)
def worker_process2(shared_joint: Joint):
while True:
print("Worker Process:")
statespace = shared_joint.get_state_space()
print(statespace.print())
time.sleep(0.1)
def worker_process3(shared_joint_list: list):
while True:
print("Worker Process:")
statespace:StateSpace = shared_joint_list.at(0).get_state_space()
statespace.print()
time.sleep(0.1)
def statespace_changer(shared_joint_list: list):
while True:
rand_int = random.randint(0, 10)
shared_joint_list.at(0).set_state_space_pos(np.array([rand_int,0,0]))
print("Changed pos")
time.sleep(1)
class SharedJointList():
def __init__(self):
self.list: list = []
def append(self, new_joint: Joint):
self.list.append(new_joint)
def at(self, index: int):
return self.list[index]
if __name__ == "__main__":
# Create a multiprocessing manager
# CustomManager.register('StateSpace', StateSpace)
# CustomManager.register('Joint', Joint)
# CustomManager.register('SharedJointList', SharedJointList)
#BaseManager.register('StateSpace', StateSpace)
BaseManager.register('Joint', Joint)
BaseManager.register('SharedJointList', SharedJointList)
with BaseManager() as manager:
#shared_statespace = manager.StateSpace()
shared_joint:Joint = manager.Joint(pos=np.array([0,0,-4]), mass=0.001, fixed=True, name="North Actuator Joint")
joint_list:SharedJointList = manager.SharedJointList()
rand_joint = manager.Joint(pos=np.array([0,2,15]),
mass=0.001,
fixed=True,
name="Random Joint")
joint_list.append(rand_joint)
#proc = multiprocessing.Process(target=worker_process, args=(shared_statespace,))
#proc = multiprocessing.Process(target=worker_process2, args=(shared_joint,))
proc = multiprocessing.Process(target=worker_process3, args=(joint_list,))
proc.start()
changer_proc = multiprocessing.Process(target=statespace_changer, args=(joint_list,))
changer_proc.start()
time.sleep(2)
#shared_statespace.set_pos(np.array([-8,-4,-2]))
#shared_joint.set_state_space_pos((np.array([-8,-4,-2])))
while True:
print("Main: ", end="")
joint_list.at(0).get_state_space().print()
time.sleep(1)
proc.join()
changer_proc.join()

View File

@ -1,3 +0,0 @@
{
"python.REPL.enableREPLSmartSend": false
}

View File

@ -1,23 +0,0 @@
import subprocess
import sys
def install_packages(packages):
for package in packages:
print(f"Installing {package}...")
try:
result = subprocess.run([sys.executable, "-m", "pip", "install", package], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
print(result.stdout)
if result.stderr:
print(result.stderr)
except subprocess.CalledProcessError as e:
print(f"Failed to install {package}: {e}")
sys.exit(1)
if __name__ == "__main__":
packages = ['vpython', 'numpy', 'typing', 'pyqtgraph', 'pyqt5' , 'mcculw']
source_dir = "./mcculw_files" # Relative path to the local directory
dest_dir = "C:/absolute/path/to/directory" # Absolute path to the destination directory
install_packages(packages)
print("Installation and file copy complete.")

Binary file not shown.

View File

@ -1,29 +0,0 @@
import numpy as np
num_points = 7
num_columns = num_points + 1
RHS = np.zeros(num_columns)
RHS[2] = 1
taylor_coef_matrix = np.zeros((num_columns, num_columns))
bases = [j for j in range(-num_points+1, 2)]
for i in range(num_columns):
row = []
for base in bases:
row.append(base**i)
taylor_coef_matrix[i] = row
x = np.linalg.solve(taylor_coef_matrix, RHS)
multiplier = 1 / x[-1]
x = x[0:-1]
x = -1*x
x = np.append(x, [0.5])
x = x * multiplier
print(x)

View File

@ -1,149 +0,0 @@
<mxfile host="Electron" modified="2024-06-11T19:31:46.378Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/24.0.4 Chrome/120.0.6099.109 Electron/28.1.0 Safari/537.36" etag="YiW67u71xuPxX2CrrYUi" version="24.0.4" type="device">
<diagram name="Page-1" id="e7e014a7-5840-1c2e-5031-d8a46d1fe8dd">
<mxGraphModel dx="2074" dy="1204" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="826" background="none" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="2" value="Thread 1" style="swimlane;whiteSpace=wrap" parent="1" vertex="1">
<mxGeometry x="164.5" y="128" width="280" height="570" as="geometry" />
</mxCell>
<mxCell id="5" value="" style="ellipse;shape=startState;fillColor=#000000;strokeColor=#ff0000;" parent="2" vertex="1">
<mxGeometry x="100" y="40" width="30" height="30" as="geometry" />
</mxCell>
<mxCell id="6" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;verticalAlign=bottom;endArrow=open;endSize=8;strokeColor=#FF0000;endFill=1;rounded=0" parent="2" source="5" target="7" edge="1">
<mxGeometry x="100" y="40" as="geometry">
<mxPoint x="115" y="110" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="7" value="idle" style="" parent="2" vertex="1">
<mxGeometry x="60" y="110" width="110" height="50" as="geometry" />
</mxCell>
<mxCell id="8" value="user action" style="" parent="2" vertex="1">
<mxGeometry x="60" y="220" width="110" height="50" as="geometry" />
</mxCell>
<mxCell id="9" value="" style="endArrow=open;strokeColor=#FF0000;endFill=1;rounded=0" parent="2" source="7" target="8" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="10" value="post command" style="" parent="2" vertex="1">
<mxGeometry x="60" y="325" width="110" height="50" as="geometry" />
</mxCell>
<mxCell id="11" value="" style="endArrow=open;strokeColor=#FF0000;endFill=1;rounded=0" parent="2" source="8" target="10" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="12" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;strokeColor=#FF0000;endArrow=open;endFill=1;rounded=0" parent="2" source="10" target="7" edge="1">
<mxGeometry width="100" height="100" relative="1" as="geometry">
<mxPoint x="160" y="290" as="sourcePoint" />
<mxPoint x="260" y="190" as="targetPoint" />
<Array as="points">
<mxPoint x="30" y="250" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="3" value="Thread 2" style="swimlane;whiteSpace=wrap" parent="1" vertex="1">
<mxGeometry x="444.5" y="128" width="280" height="570" as="geometry" />
</mxCell>
<mxCell id="13" value="" style="ellipse;shape=startState;fillColor=#000000;strokeColor=#ff0000;" parent="3" vertex="1">
<mxGeometry x="60" y="40" width="30" height="30" as="geometry" />
</mxCell>
<mxCell id="14" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;verticalAlign=bottom;endArrow=open;endSize=8;strokeColor=#FF0000;endFill=1;rounded=0" parent="3" source="13" target="15" edge="1">
<mxGeometry x="40" y="20" as="geometry">
<mxPoint x="55" y="90" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="15" value="idle" style="" parent="3" vertex="1">
<mxGeometry x="20" y="110" width="110" height="50" as="geometry" />
</mxCell>
<mxCell id="16" value="check for &#xa;new commands" style="" parent="3" vertex="1">
<mxGeometry x="20" y="220" width="110" height="50" as="geometry" />
</mxCell>
<mxCell id="17" value="" style="endArrow=open;strokeColor=#FF0000;endFill=1;rounded=0" parent="3" source="15" target="16" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="18" value="command queue" style="" parent="3" vertex="1">
<mxGeometry x="20" y="325" width="110" height="50" as="geometry" />
</mxCell>
<mxCell id="19" value="" style="endArrow=open;strokeColor=#FF0000;endFill=1;rounded=0" parent="3" source="16" target="18" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="21" value="queue empty" style="rhombus;fillColor=#ffffc0;strokeColor=#ff0000;" parent="3" vertex="1">
<mxGeometry x="150" y="225" width="80" height="40" as="geometry" />
</mxCell>
<mxCell id="22" value="yes" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;align=left;verticalAlign=bottom;endArrow=open;endSize=8;strokeColor=#FF0000;exitX=0.5;exitY=0;endFill=1;rounded=0;entryX=0.75;entryY=0.5;entryPerimeter=0" parent="3" source="21" target="25" edge="1">
<mxGeometry x="-1" relative="1" as="geometry">
<mxPoint x="160" y="150" as="targetPoint" />
<Array as="points">
<mxPoint x="190" y="180" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="23" value="no" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;align=left;verticalAlign=top;endArrow=open;endSize=8;strokeColor=#FF0000;endFill=1;rounded=0" parent="3" source="21" target="30" edge="1">
<mxGeometry x="-1" relative="1" as="geometry">
<mxPoint x="190" y="305" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="24" value="" style="endArrow=open;strokeColor=#FF0000;endFill=1;rounded=0" parent="3" source="16" target="21" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="25" value="" style="shape=line;strokeWidth=6;strokeColor=#ff0000;rotation=90" parent="3" vertex="1">
<mxGeometry x="130" y="127.5" width="50" height="15" as="geometry" />
</mxCell>
<mxCell id="26" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;verticalAlign=bottom;endArrow=open;endSize=8;strokeColor=#FF0000;endFill=1;rounded=0" parent="3" source="25" target="15" edge="1">
<mxGeometry x="130" y="90" as="geometry">
<mxPoint x="230" y="140" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="30" value="dispatch&#xa;command&#xa;worker thread" style="" parent="3" vertex="1">
<mxGeometry x="140" y="325" width="110" height="50" as="geometry" />
</mxCell>
<mxCell id="31" value="critical&#xa;section" style="shape=note;whiteSpace=wrap;size=17" parent="3" vertex="1">
<mxGeometry x="105" y="490" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="32" value="" style="endArrow=none;strokeColor=#FF0000;endFill=0;rounded=0;dashed=1" parent="3" source="18" target="31" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="34" value="" style="whiteSpace=wrap;strokeColor=#FF0000;fillColor=#FF0000" parent="3" vertex="1">
<mxGeometry x="245" y="395" width="5" height="45" as="geometry" />
</mxCell>
<mxCell id="41" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;entryX=0;entryY=0.5;strokeColor=#FF0000;endArrow=open;endFill=1;rounded=0" parent="3" source="30" target="34" edge="1">
<mxGeometry width="100" height="100" relative="1" as="geometry">
<mxPoint x="60" y="480" as="sourcePoint" />
<mxPoint x="160" y="380" as="targetPoint" />
<Array as="points">
<mxPoint x="195" y="400" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="4" value="Thread 3" style="swimlane;whiteSpace=wrap" parent="1" vertex="1">
<mxGeometry x="724.5" y="128" width="280" height="570" as="geometry" />
</mxCell>
<mxCell id="33" value="process&#xa;command" style="" parent="4" vertex="1">
<mxGeometry x="90" y="405" width="110" height="50" as="geometry" />
</mxCell>
<mxCell id="35" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;strokeColor=#FF0000;endArrow=open;endFill=1;rounded=0;entryX=0.25;entryY=0.5;entryPerimeter=0" parent="4" target="25" edge="1">
<mxGeometry width="100" height="100" relative="1" as="geometry">
<mxPoint x="-30" y="410" as="sourcePoint" />
<mxPoint x="-120" y="120" as="targetPoint" />
<Array as="points">
<mxPoint x="-10" y="135" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="37" value="" style="edgeStyle=none;strokeColor=#FF0000;endArrow=open;endFill=1;rounded=0" parent="4" target="33" edge="1">
<mxGeometry width="100" height="100" relative="1" as="geometry">
<mxPoint x="-30" y="429.5" as="sourcePoint" />
<mxPoint x="90" y="429.5" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="38" value="" style="ellipse;shape=endState;fillColor=#000000;strokeColor=#ff0000" parent="4" vertex="1">
<mxGeometry x="130" y="500" width="30" height="30" as="geometry" />
</mxCell>
<mxCell id="39" value="" style="endArrow=open;strokeColor=#FF0000;endFill=1;rounded=0" parent="4" source="33" target="38" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="20" value="" style="endArrow=open;strokeColor=#FF0000;endFill=1;rounded=0" parent="1" source="10" target="18" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

BIN
code/setup/mccdaq.exe Normal file

Binary file not shown.