diff --git a/Data/emeterDecode.py b/Data/emeterDecode.py index 9f93c99..3743894 100644 --- a/Data/emeterDecode.py +++ b/Data/emeterDecode.py @@ -1,5 +1,6 @@ from nptdms import TdmsFile import polars as pl +import numpy as np import matplotlib.pyplot as plt from Data.FSLib.IntegralsAndDerivatives import * from Data.FSLib.AnalysisFunctions import * @@ -9,10 +10,10 @@ autoxDaniel12File = "FS-3/compEmeterData/autoxDaniel12.tdms" accel1 = "FS-3/compEmeterData/216_univ-of-calif---santa-cruz-_250620-203307_ ACCEL-EV.tdms" accel2 = "FS-3/compEmeterData/216_univ-of-calif---santa-cruz-_250620-205609_ ACCEL-EV.tdms" -endur1 = "FS-3/compEmeterData/216_univ-of-calif---santa-cruz-_250621-154731_ ENDUR-EV.tdms" -endur2 = "FS-3/compEmeterData/216_univ-of-calif---santa-cruz-_250621-160530_ ENDUR-EV.tdms" +endur1 = "../fs-data/FS-3/compEmeterData/216_univ-of-calif---santa-cruz-_250621-154731_ ENDUR-EV.tdms" +endur2 = "../fs-data/FS-3/compEmeterData/216_univ-of-calif---santa-cruz-_250621-160530_ ENDUR-EV.tdms" -dfLaptimes = pl.read_csv("FS-3/compLapTimes.csv") +dfLaptimes = pl.read_csv("../fs-data/FS-3/compLapTimes.csv") firstHalf = dfLaptimes.filter(pl.col("Lap") < 12)["Time"].sum() secondHalf = dfLaptimes.filter(pl.col("Lap") > 11)["Time"].sum() @@ -93,7 +94,37 @@ def fileTodf(path): pl.Series(arr).cast(pl.Int64).alias("Lap") ) +l = [] +for i in np.unique(dfendur1["Lap"]): + # plt.plot(dfendur1.filter(pl.col("Lap") == i)[I]) + l.append(dfendur1.filter(pl.col("Lap") == i)[I]) +plt.show() + +shortest = min([len(x) for x in l]) +l2 = [x[:shortest].alias(f"Current_Lap_{i}") for i, x in enumerate(l)] +df2 = pl.DataFrame(l2) +plt.plot(df2.mean_horizontal()) +plt.show() + +from scipy.fft import fft, ifft + +f = fft(df2.mean_horizontal().to_numpy()) +# freq = np.fft.fftfreq(len(df2.mean_horizontal()), d=0.01) + +plt.plot(np.append(np.log(f[-len(f)//2:]), np.log(f[:len(f)//2]))) +plt.show() + +fFiltered = np.where(np.log(f) > 10, f, 0) +invF = ifft(fFiltered) +plt.plot(invF) +plt.plot(df2.mean_horizontal()) +plt.show() + +dfTableCurrOut = pl.DataFrame({"Current": df2.mean_horizontal().gather_every(100), "Time": np.arange(0, df2.height/100)}) +dfTableCurrOut.write_csv("endur1Curr.csv") + +df2.mean_horizontal() dfendur2 = fileTodf(endur2).filter(pl.col(t) > endur2_StartTime).filter(pl.col(t) < endur2_EndTime) diff --git a/Data/temp.py b/Data/temp.py index 587d9e0..9b5c197 100644 --- a/Data/temp.py +++ b/Data/temp.py @@ -3,8 +3,8 @@ import cantools.database as db from Data.DataDecoding_N_CorrectionScripts.dataDecodingFunctions import * -from Data.AnalysisFunctions import * -from Data.integralsAndDerivatives import * +from Data.FSLib.AnalysisFunctions import * +from Data.FSLib.IntegralsAndDerivatives import * from scipy.interpolate import CubicSpline dbcPath = "../fs-3/CANbus.dbc" @@ -70,38 +70,7 @@ etcRTDButton = "ETC_STATUS_RTD_BUTTON" etcBrakeVoltage = "ETC_STATUS_BRAKE_SENSE_VOLTAGE" -df = read("C:/Projects/FormulaSlug/fs-data/FS-3/10112025/firstDriveMCError30.parquet") -df = df.with_columns( - df["timestamp"].alias("Time") -) +df = read("C:/Projects/FormulaSlug/fs-data/FS-3/10082025/fixed_wheels_nathaniel_inv_test_w_fault.parquet") -df = read("C:/Projects/FormulaSlug/fs-data/FS-3/10112025/firstDriveMCError30-filled-null.parquet") -df = df.with_columns( - simpleTimeCol(df) -) - -fig = plt.figure() -ax = fig.add_subplot(111) - -ax.plot(df[t], df[frT], label=frT, c="blue") -ax.plot(df[t], df[flT], label=flT, c="red") -ax.plot(df[t], df[brT], label=brT, c="orange") -ax.plot(df[t], df[blT], label=blT, c="cyan") -ax.set_title("Suspension Travel during First Drive with MC Fault") -ax.set_xlabel("Time") -ax.set_ylabel("Suspension Travel (mm)") -ax.legend() -plt.show() - - -dfNullless = df.drop_nulls(subset=[frT, flT, brT, blT]) - -cs = CubicSpline(dfNullless[t], dfNullless[frT]) - -fig = plt.figure() -ax = fig.add_subplot(111) - -ax.scatter(dfNullless[t], cs(dfNullless[t]), label=frT, s=0.5) -ax.scatter(dfNullless[t], in_place_derive(cs(dfNullless[t])), label=f"Derived {frT}", s=0.5) -ax.legend() +plt.plot(df[busV]) plt.show() diff --git a/Docs/SimulationTodo.md b/Docs/SimulationTodo.md new file mode 100644 index 0000000..ef03388 --- /dev/null +++ b/Docs/SimulationTodo.md @@ -0,0 +1,82 @@ +# Simulation Todo + +1. Better drag model that takes into account aeropackage (FS-3) +1. Individual wheel models + 1. Suspension + 1. Travel (x) + 1. Velocity (v) + 1. How will this react under acceleration in any direction (steering causes lateral acceleration) (throttle/brakes causes longitudinal acceleration) + 1. Wheel + 1. Brake temp (more complex soon) + 1. Wheel temp (more complex soon) + 1. Wheel rpm/speed +1. Differential + Drivetrain losses + 1. Energy loss due to chain, tripods, axle, hub/upright rubbing + 1. Model rolling resistance better + 1. Model limited slip differential + 1. Log losses so we have an idea of energy loss in the drivetrain +1. Motor Efficiency + Heating + 1. Function of temp and current draw + 1. Keep track of losses + 1. Efficiency loss goes into heat of motor. Need an estimate of its thermal mass and then change in temp. (Motor temp new var) +1. Cleaner logging + 1. Log everything without having to add more rows constantly + 1. Keep efficiency in mind +1. Tractive system heat generation (Not acc) + 1. Estimate how much heat is generated in the accumulator + 1. Not high priority unless we can get more data. +1. Steering model +1. Suspension Model + + +# New simulation architecture idea + + +```python +# Dynamic Vars +posX = 0 +posY = 1 +velX = 2 +velY = 3 +accelX = 4 +accelY = 5 + +arr = np.array((simSteps, 6+1)) +arr[0] = step0 + +def step(): + newPosX = step[posX] + step[velX] * t + +for i in range(simSteps): + arr[i+1] = step(arr[i]) +``` + +```python +# Dictionary Idea #1 +# Array of dictionaries where each time step gets its own dictionary +# Trivial to access a specific thing from any row + +arr = np.array((simSteps)) +arr[0] = step0 + +def step(): + newPosX = arr[posX] + arr[velX] * t + +for i in range(simSteps): + arr[i+1] = step(arr[i]) +``` + +```python +# Dictionary Idea #2 +# Dictionary of arrays. Each key is a column and each array is the length of the simulation +# Trivial to access columns which is typically how we access data + +arr = np.array((simSteps)) +arr[0] = step0 + +def step(): + newPosX = arr[posX] + arr[velX] * t + +for i in range(simSteps): + arr[i+1] = step(arr[i]) +``` \ No newline at end of file diff --git a/FullVehicleSim/Electrical/powertrain.py b/FullVehicleSim/Electrical/powertrain.py index 53d4bd9..f583df7 100644 --- a/FullVehicleSim/Electrical/powertrain.py +++ b/FullVehicleSim/Electrical/powertrain.py @@ -1,24 +1,23 @@ -from state import VehicleState -from paramLoader import Parameters, Magic +import numpy as np +from paramLoader import * -def calcMaxMotorTorque(worldPrev:VehicleState, resistiveForces:float, maxPower:float, maxTractionTorqueAtWheel:float): +def calcMaxMotorTorque(worldArray:np.ndarray, step:int, resistiveForces:float, maxPower:float, maxTractionTorqueAtWheel:float): ''' Motor Torque at the wheel minimum(rpm limited torque, power limited torque, perfect traction torque) ''' ## RPM Limited Torque (Motor Controller limits it to ~ this in practice. Maybe something more like 7490ish) - if worldPrev.motorRPM > 7490: + if worldArray[step-1, varMotorRPM] > 7490: return -1 * resistiveForces * Parameters["wheelRadius"] - if worldPrev.motorRotationsHZ != 0: ## If rolling, torque may be power limited. - maxPowerTorque = maxPower / worldPrev.motorRotationsHZ * Parameters["gearRatio"] + if worldArray[step-1, varMotorRotationsHZ] != 0: ## If rolling, torque may be power limited. + maxPowerTorque = maxPower / worldArray[step-1, varMotorRotationsHZ] * Parameters["gearRatio"] else: ## Avoid divide by 0 error but it's just the same as the max torque that the motor can deliver (180 Nm) maxPowerTorque = 180.0 # Nm at 0 rpm - perfectTractionTorque = Parameters["maxTorque"] - torque = min(perfectTractionTorque, maxPowerTorque, maxTractionTorqueAtWheel/Parameters["gearRatio"]) + torque = min(Parameters["maxTorque"], maxPowerTorque, maxTractionTorqueAtWheel/Parameters["gearRatio"]) return torque -def calcCurrent(power, voltage): +def calcCurrent(power:float, voltage:float) -> float: if (power / voltage) > Parameters["tractiveIMax"]: return Parameters["tractiveIMax"] return power / voltage @@ -29,10 +28,10 @@ def calcMaxWheelTorque(maxMotorTorque): ''' return maxMotorTorque * Parameters["gearRatio"] -def calcMotorForce(maxWheelTorque): +def calcMotorForce(maxWheelTorque:float) -> float: return (maxWheelTorque / Parameters["wheelRadius"]) -def calcMaxPower(voltage): +def calcMaxPower(voltage:float) -> float: return Parameters["tractiveIMax"] * voltage def calcVoltage(): diff --git a/FullVehicleSim/Electrical/tractiveBattery.py b/FullVehicleSim/Electrical/tractiveBattery.py index 7a10b44..d069f2f 100644 --- a/FullVehicleSim/Electrical/tractiveBattery.py +++ b/FullVehicleSim/Electrical/tractiveBattery.py @@ -1,2 +1,2 @@ -from FullVehicleSim.paramLoader import Parameters, Magic +from FullVehicleSim.paramLoader import * diff --git a/FullVehicleSim/Mech/aero.py b/FullVehicleSim/Mech/aero.py index 8be586d..6b5ecbc 100644 --- a/FullVehicleSim/Mech/aero.py +++ b/FullVehicleSim/Mech/aero.py @@ -1,9 +1,8 @@ import numpy as np -from paramLoader import Parameters, Magic -from state import VehicleState +from paramLoader import * -def calcDrag(prevWorld:VehicleState) -> float: - return 0.5 * Parameters["airDensity"] * Parameters["dragCoeffAreaCombo"] * prevWorld.speed**2 +def calcDrag(worldArray:np.ndarray, step:int) -> float: + return 0.5 * Parameters["airDensity"] * Parameters["dragCoeffAreaCombo"] * worldArray[step-1, varSpeed]**2 -def calcDownForce(prevWorld:VehicleState) -> np.ndarray: +def calcDownForce(worldArray:np.ndarray, step:int) -> np.ndarray: return np.asarray([0,0,0,0], dtype=float) diff --git a/FullVehicleSim/Mech/braking.py b/FullVehicleSim/Mech/braking.py index 688d8e4..4a15070 100644 --- a/FullVehicleSim/Mech/braking.py +++ b/FullVehicleSim/Mech/braking.py @@ -1,7 +1,6 @@ from Mech import brakepadFrictionModel -from paramLoader import Parameters, Magic +from paramLoader import * import numpy as np -from state import VehicleState # Docs: # https://docs.google.com/document/d/1oGsGDnY0DEKWpE3S6481A9yZ0F9qUEwWkSXJwTSz4E4/edit?tab=t.2rmbsj26c7w # The goal of these functions are to calculate the net force on the brakes, applied reverse to heading @@ -9,46 +8,47 @@ def brakePSI_toNewtons(psi:float) -> float: return psi * Parameters["brakeCaliperArea"] * 4.448222 # lb force to Newtons -def calcBrakeForce(prevWorld:VehicleState, inputs) -> tuple[float,float]: +def calcBrakeForce(worldArray:np.ndarray, step:int) -> tuple[float,float]: """ Calculate the brake force. FrictionCoeff(temp) * maxBrakeForce * 4 (for 4 wheels) - :param prevWorld: World State Previous + :param worldArray: World State Array + :param step: Current step index :return: Brake Force """ - frontBrakePSI = inputs[1] - rearBrakePSI = inputs[2] + frontBrakePSI = worldArray[step, varBrakePressureFront] + rearBrakePSI = worldArray[step, varBrakePressureRear] frontBrakeForce = brakePSI_toNewtons(frontBrakePSI) rearBrakeForce = brakePSI_toNewtons(rearBrakePSI) # Calculate Brake Force - frontBrakeForce:float = brakepadFrictionModel.calcFrictionCoeff(prevWorld.frontBrakeTemperature) * frontBrakeForce * 2 * Parameters["brakeDiscRadius"] / Parameters["wheelRadius"] - rearBrakeForce:float = brakepadFrictionModel.calcFrictionCoeff(prevWorld.rearBrakeTemperature) * rearBrakeForce * 2 * Parameters["brakeDiscRadius"] / Parameters["wheelRadius"] + frontBrakeForce:float = brakepadFrictionModel.calcFrictionCoeff(worldArray[step-1, varFrontBrakeTemperature]) * frontBrakeForce * 2 * Parameters["brakeDiscRadius"] / Parameters["wheelRadius"] + rearBrakeForce:float = brakepadFrictionModel.calcFrictionCoeff(worldArray[step-1, varRearBrakeTemperature]) * rearBrakeForce * 2 * Parameters["brakeDiscRadius"] / Parameters["wheelRadius"] return frontBrakeForce, rearBrakeForce -def calcBrakeCooling(prevWorld:VehicleState) -> tuple[float,float]: +def calcBrakeCooling(worldArray:np.ndarray, step:int) -> tuple[float,float]: """ Calculate the cooled brake temperature. :param prevWorld: World State :return: Change in Temperature """ - frontBrakeCooling = Parameters["ambientTemperature"] + (prevWorld.frontBrakeTemperature - Parameters["ambientTemperature"]) * np.e ** (-1 / Parameters["stepsPerSecond"]/50.2) - rearBrakeCooling = Parameters["ambientTemperature"] + (prevWorld.rearBrakeTemperature - Parameters["ambientTemperature"]) * np.e ** (-1 / Parameters["stepsPerSecond"]/50.2) + frontBrakeCooling = Parameters["ambientTemperature"] + (worldArray[step-1, varFrontBrakeTemperature] - Parameters["ambientTemperature"]) * np.e ** (-1 / Parameters["stepsPerSecond"]/50.2) + rearBrakeCooling = Parameters["ambientTemperature"] + (worldArray[step-1, varRearBrakeTemperature] - Parameters["ambientTemperature"]) * np.e ** (-1 / Parameters["stepsPerSecond"]/50.2) return frontBrakeCooling, rearBrakeCooling #q = (initTemperature - parameters["ambientTemperature"]) * parameters["brakeMass"] * parameters["brakeSpecificHeatCapacity"] #change = (q * parameters["brakepadThickness"])/(initTemperature * parameters["brakeThermalConductivity"] * parameters["brakeSurfaceArea"] #return initTemperature - change -def calcBrakeHeating(prevWorld:VehicleState, inputs) -> tuple[float,float]: +def calcBrakeHeating(worldArray:np.ndarray, step:int) -> tuple[float,float]: # Calculate Brake Force - frontBrakeForce, rearBrakeForce = calcBrakeForce(prevWorld, inputs) + frontBrakeForce, rearBrakeForce = calcBrakeForce(worldArray, step) # Guess energy increase based on kinetic energy decrease of the vehicle. # Assumption is 100% of kinetic energy lost goes into brake heating. speedChange = (frontBrakeForce + rearBrakeForce) / Parameters["Mass"] / Parameters["stepsPerSecond"] # momentum impulse - energyChange = 0.5 * Parameters["Mass"] * (prevWorld.speed - (prevWorld.speed - speedChange)) + energyChange = 0.5 * Parameters["Mass"] * (worldArray[step-1, varSpeed] - (worldArray[step-1, varSpeed] - speedChange)) tempChange = energyChange/(Parameters["brakeMass"] * Parameters["brakeSpecificHeatCapacity"]) # While this doesn't seem physically intuitive, it is based on the idea that the front and rear brakes share heat based on their contribution to total braking force. diff --git a/FullVehicleSim/Mech/general.py b/FullVehicleSim/Mech/general.py index ea4bb5b..5bad33b 100644 --- a/FullVehicleSim/Mech/general.py +++ b/FullVehicleSim/Mech/general.py @@ -1,20 +1,19 @@ from Mech.traction import calcCorneringStiffness -from paramLoader import Parameters, Magic -from state import VehicleState +from paramLoader import * from Mech.braking import calcBrakeForce from Mech.aero import calcDrag from Mech.steering import calcSlipAngle, calcYawRate from Mech.tireLoad import calcLoadTransfer import numpy as np -def calcResistiveForces(worldPrev:VehicleState, inputs): - if worldPrev.speed <= 1e-5: # Floating point error +def calcResistiveForces(worldArray:np.ndarray, step:int): + if worldArray[step-1, varSpeed] <= 1e-5: # Floating point error return 0 else: - frontBrakeForce, rearBrakeForce = calcBrakeForce(worldPrev, inputs) - return -1 * (calcDrag(worldPrev) + frontBrakeForce + rearBrakeForce) + frontBrakeForce, rearBrakeForce = calcBrakeForce(worldArray, step) + return -1 * (calcDrag(worldArray, step) + frontBrakeForce + rearBrakeForce) -def calculateYawRate(prevWorld:VehicleState, steerAngle:float, initAcceleration:float, heading:np.ndarray, initYawRate:float, timeSinceLastSteer:float): +def calculateYawRate(worldArray:np.ndarray, step:int, initAcceleration:float, initYawRate:float, timeSinceLastSteer:float): """Calculate the yaw rate of the vehicle at the current state. This function computes the yaw rate by calculating tire loads, slip angles, cornering stiffness, and then applying the vehicle dynamics equations. @@ -32,9 +31,9 @@ def calculateYawRate(prevWorld:VehicleState, steerAngle:float, initAcceleration: ----- Slip ratio is fixed at 0.15. """ - tireLoad = calcLoadTransfer(initAcceleration * heading[0], initAcceleration * heading[1], initYawRate) - slipAngle = calcSlipAngle(initYawRate, prevWorld.velocity, steerAngle, Parameters) + tireLoad = calcLoadTransfer(initAcceleration * worldArray[step-1, varHeadingX], initAcceleration * worldArray[step-1, varHeadingY], initYawRate) + slipAngle = calcSlipAngle(worldArray, step) slipRatio = 0.15 - corneringStiffness = calcCorneringStiffness(tireLoad, slipAngle, slipRatio, prevWorld.speed, 80, 40, Parameters, Magic) # Works but unused - res = calcYawRate(initYawRate, prevWorld.speed, steerAngle, timeSinceLastSteer, corneringStiffness[0], corneringStiffness[1], Parameters) - return res \ No newline at end of file + corneringStiffness = calcCorneringStiffness(tireLoad, slipAngle, slipRatio, worldArray[step-1, varSpeed], 80, 40, Parameters, Magic) # Works but unused + res = calcYawRate(initYawRate, worldArray[step-1, varSpeed], worldArray[step, varSteerAngle], timeSinceLastSteer, corneringStiffness[0], corneringStiffness[1]) + return res diff --git a/FullVehicleSim/Mech/steering.py b/FullVehicleSim/Mech/steering.py index 44d4486..4c06198 100644 --- a/FullVehicleSim/Mech/steering.py +++ b/FullVehicleSim/Mech/steering.py @@ -1,9 +1,8 @@ # Steering model import numpy as np -from state import VehicleState -from paramLoader import Parameters, Magic +from paramLoader import * -def calcSlipAngle(prevWorld:VehicleState, inputs) -> tuple[float,float]: +def calcSlipAngle(worldArray:np.ndarray, step:int) -> tuple[float,float]: """ Calculate Slip Angle Based on yawRate, Velocity, and Steering Angle. @@ -15,15 +14,16 @@ def calcSlipAngle(prevWorld:VehicleState, inputs) -> tuple[float,float]: :param steerAngle: Description :return: (frontSlipAngle, rearSlipAngle) """ - steerAngle = inputs[3] - speed = np.sqrt(prevWorld.velocity[0] ** 2 + prevWorld.velocity[1] ** 2 + prevWorld.velocity[2]**2) - if prevWorld.yawRate == 0 or speed == 0: # WRONG. RELAXATION LENGTH. PROJECT + steerAngle = worldArray[step, varSteerAngle] + speed = worldArray[step-1, varSpeed] + yawRate = worldArray[step-1, varYawRate] + if yawRate == 0 or speed == 0: # WRONG. RELAXATION LENGTH. PROJECT return (0, 0) else: - bodySlip = np.arctan(prevWorld.velocity[1]/prevWorld.velocity[0]) + bodySlip = np.arctan(worldArray[step-1, varVelY]/worldArray[step-1, varVelX]) - frontSlipAngle = calcVirtualSlipAngle() + bodySlip + (Parameters["wheelBase"]*Parameters["frontWeightDist"]/100 * prevWorld.yawRate)/speed - steerAngle - rearSlipAngle = bodySlip - (Parameters["wheelBase"]*(100-Parameters["frontWeightDist"])/100 * prevWorld.yawRate)/speed + frontSlipAngle = calcVirtualSlipAngle() + bodySlip + (Parameters["wheelBase"]*Parameters["frontWeightDist"]/100 * yawRate)/speed - steerAngle + rearSlipAngle = bodySlip - (Parameters["wheelBase"]*(100-Parameters["frontWeightDist"])/100 * yawRate)/speed return (frontSlipAngle, rearSlipAngle) diff --git a/FullVehicleSim/Mech/tireLoad.py b/FullVehicleSim/Mech/tireLoad.py index 13fbe81..5eeb645 100644 --- a/FullVehicleSim/Mech/tireLoad.py +++ b/FullVehicleSim/Mech/tireLoad.py @@ -1,4 +1,4 @@ -from paramLoader import Parameters +from paramLoader import * def calcLoadTransfer(accelerationX, accelerationY, yawVelocity:float) -> tuple[float, float, float, float]: # TODO: add weight transfer for torsional compliancy diff --git a/FullVehicleSim/Mech/tireState.py b/FullVehicleSim/Mech/tireState.py index a3ecc5d..9ec394b 100644 --- a/FullVehicleSim/Mech/tireState.py +++ b/FullVehicleSim/Mech/tireState.py @@ -4,7 +4,7 @@ from paramLoader import Parameters, Magic class Tire: - def __init__(self, normalForce, slipRatio, slipAngle, velocityX, pressure, temperature): + def __init__(self, normalForce, slipRatio, slipAngle, velocityX, pressure, temperature, massTire, accelX, accelY, carHeight, frontBack, leftRight, frontAxleFraction, remainingFraction, springConstant, dampingCoeff): self.normalForce = normalForce * -1 self.velocityX = velocityX self.slipRatioInit = slipRatio @@ -14,6 +14,17 @@ def __init__(self, normalForce, slipRatio, slipAngle, velocityX, pressure, tempe self.tireTemperature = temperature self.actPressure = 12 # Actual PSI self.camber = 0 # Radians + self.mass = massTire + self.gravity = 9.81 + self.accelX = accelX + self.accelY = accelY + self.carHeight = carHeight + self.frontBackDist = frontBack + self.leftRightDist = leftRight + self.frontAxleFraction = frontAxleFraction + self.remainingFraction = remainingFraction + self.springConstant = springConstant + self.dampingCoeff = dampingCoeff #if(lat): self.normDeltaLoadLat = self.normalizeLoadLat() @@ -212,3 +223,75 @@ def updateParams(self, normalForce=-1, slipRatio=-1, velocityX=-1): self.slipRatio = slipRatio if velocityX != -1: self.velocityX = velocityX + + + w = (self.mass*self.gravity)/4 ## If tires are the same + + @property + def F_long(self): + f_long = (self.mass * self.accelX * self.carHeight)/self.frontBackDist + return f_long + @property + def F_lat(self): + f_lat = (self.mass * self.accelY * self.carHeight)/self.leftRightDist + return f_lat + @property + def F_front_left(self): + f_front_left = self.remainingFraction(self.frontAxleFraction * self.mass * self.gravity) + return f_front_left + @property + def F_front_right(self): + f_front_right = (1-self.remainingFraction) * (self.frontAxleFraction * self.mass * self.gravity) + return f_front_right + @property + def F_back_left(self): + f_back_left = self.remainingFraction * ((1-self.frontAxleFraction) * self.mass * self.gravity) + return f_back_left + @property + def F_back_right(self): + f_back_right = (1-self.remainingFraction) * ((1-self.frontAxleFraction) * self.mass * self.gravity) + return f_back_right + + ## vertical load on each tire : F_z + @property + def F_zfl(self): + f_z = (F_front_left + (F_long)/2 + (F_lat)/2) + return f_z + + @property + def F_zfr(self): + f_z = (F_front_right + (F_long)/2 - (F_lat)/2) + return f_z + + @property + def F_zbl(self): + f_z = (F_back_left - (F_long)/2 + (F_lat)/2) + return f_z + + @property + def F_zbr(self): + f_z = (F_back_right - (F_long)/2 - (F_lat)/2) + return f_z + + ## suspension travel: + @property + def x_fl(self): + x_fl = (F_zfl - self.dampingCoeff)/self.springConstant + return x_fl + + @property + def x_fr(self): + x_fr = (F_zfr - self.dampingCoeff)/self.springConstant + return x_fr + + @property + def x_bl(self): + x_bl = (F_zbl - self.dampingCoeff)/self.springConstant + return x_bl + + @property + def x_br(self): + x_br = (F_zbr - self.dampingCoeff)/self.SpringConstant + return x_br + + diff --git a/FullVehicleSim/SimultionControlInputs/controls.json b/FullVehicleSim/SimulationControlInputs/controls.json similarity index 100% rename from FullVehicleSim/SimultionControlInputs/controls.json rename to FullVehicleSim/SimulationControlInputs/controls.json diff --git a/FullVehicleSim/SimulationControlInputs/simulationControls.csv b/FullVehicleSim/SimulationControlInputs/simulationControls.csv new file mode 100644 index 0000000..c19041a --- /dev/null +++ b/FullVehicleSim/SimulationControlInputs/simulationControls.csv @@ -0,0 +1,6 @@ +time,throttle,brakePressureFront,brakePressureRear,steerAngle +0.0,0.0,0.0,0.0,0.0 +10.0,1.0,0.0,0.0,0.0 +20.0,0.0,0.0,0.0,0.0 +30.0,0.0,300.0,300.0,0.0 +40.0,0.0,0.0,0.0,0.0 \ No newline at end of file diff --git a/FullVehicleSim/SimultionControlInputs/simulationControls.csv b/FullVehicleSim/SimultionControlInputs/simulationControls.csv deleted file mode 100644 index dd8b409..0000000 --- a/FullVehicleSim/SimultionControlInputs/simulationControls.csv +++ /dev/null @@ -1,6 +0,0 @@ -time,throttle,brakesFront,brakesRear,steerAngle -0.0,0.0,0.0,0.0,0.0 -10.0,1.0,0.0,0.0,0.0 -20.0,0.0,0.0,0.0,0.0 -30.0,0.0,500.0,1.0,0.0 -40.0,0.0,0.0,0.0,0.0 \ No newline at end of file diff --git a/FullVehicleSim/basicViewer.py b/FullVehicleSim/basicViewer.py index 07f28e7..1687a8e 100644 --- a/FullVehicleSim/basicViewer.py +++ b/FullVehicleSim/basicViewer.py @@ -2,22 +2,17 @@ import matplotlib.pyplot as plt df = pl.read_parquet("FullVehicleSim/simulation_output.parquet") +t = df["time"] -cols = ["x", "y", "z", "vX", "vY", "vZ", "speed", - "headingX", "headingY", "headingZ", - "yawRate", "frontBrakeTemperature", "rearBrakeTemperature", - "charge", "drag", "resistiveForces", - "motorTorque", "motorForce", "netForce", - "maxTraction", "wheelRotationsHZ", "motorRPM", - "motorRotationsHZ", "current", - "maxWheelTorque", "maxPower", "power", - "voltage", - "frontBrakeForce", "rearBrakeForce", - "frontBrakeHeating", "rearBrakeHeating", - "frontBrakeCooling", "rearBrakeCooling", - "frontSlipAngle", "rearSlipAngle"] -plt.plot(df["time"], df["motorForce"], label="motorForce") -plt.plot(df["time"], df["frontBrakeForce"] + df["rearBrakeForce"], label="Brake Force") +plt.plot(t, df["throttle"]*300, label="throttle") +plt.plot(t, df["brakePressureFront"], label="brakesF") +plt.plot(t, df["netForce"], label="netForce") +plt.plot(t, df["motorForce"], label="motorForce") +plt.plot(t, df["motorTorque"], label="motorTorque") +# plt.plot(t, df["motorRPM"], label="motorRPM") +plt.plot(t, df["speed"], label="speed") plt.legend() plt.show() + +df["speed"].max() \ No newline at end of file diff --git a/FullVehicleSim/engine.py b/FullVehicleSim/engine.py index ec1eabe..45abde5 100644 --- a/FullVehicleSim/engine.py +++ b/FullVehicleSim/engine.py @@ -1,6 +1,5 @@ -from paramLoader import Parameters, Magic +from paramLoader import * import numpy as np -from state import VehicleState from Mech.braking import calcBrakeCooling, calcBrakeHeating, calcBrakeForce from Mech.aero import calcDrag, calcDownForce from Mech.steering import calcSlipAngle @@ -10,9 +9,10 @@ # Vibe coded but it looks about right so idk. # TODO: Verify that this is correct -def calculateHeading(heading, yaw_rate, time_increment): - initial_heading = heading[:2] - rotation_angle = yaw_rate * time_increment +def calculateHeading(worldArray:np.ndarray, step:int) -> np.ndarray: + time_increment = 1/Parameters["stepsPerSecond"] + initial_heading = worldArray[step-1, varHeadingX:varHeadingZ] # Yes this removes Z, we just want X and Y for this simplification + rotation_angle = worldArray[step-1, varYawRate] * time_increment cos_theta = np.cos(rotation_angle) sin_theta = np.sin(rotation_angle) @@ -27,86 +27,82 @@ def calculateHeading(heading, yaw_rate, time_increment): return np.append(new_heading, 0) -def stepState(worldPrev:VehicleState, inputs): +def stepState(worldArray:np.ndarray, step:int) -> np.ndarray: + """ + The order by which things get updated in this function is incredibly important. + If you calculate velocity before you calculate acceleration, + you would just wind up using the 0 that is present in the array at that index. + Update acceleration before you update velocity. This will also fail somewhat + silently so be cautious. - # Empirically we see that throttle can only go from about 0-.75. - # TODO: Update later - # Made it so you can just comment this out when it's fixed. - # Throttle, brakesFront, brakesRear, steering angle - # 0-1, PSI, PSI, Radians + The worldArray will also fail silently if it doen't contain a row before step. + The 0-1 evaluates to -1 so it just grabs the last row in the array instead of the + previous one. + + :param worldArray: The main world array. To work properly, the worldArray needs to contain a row (step) and a previous row (step-1) with the same format as the output of this function. The function will read from the previous row to calculate the new values for the current step. + :type worldArray: np.ndarray + :param step: The current step in the simulation + :type step: int + :return: The updated state array for the current step + :rtype: ndarray[Any, Any] + """ + + # Empirically we see that throttle can only go from about 0-.75. Currently not accounted for. + arr = np.copy(worldArray[step, :]) # This is the array that is updated and then returned. delta = 1/Parameters["stepsPerSecond"] - maxTraction = 180.0 # Needs a more complex implementation before being used. Potentially something akin to the gaussian kernel of the voltage histeresis model but for acceleration? Or literally based on the suspension travel. - voltage = calcVoltage() # Not yet implemented. Returns 120 for now. - maxPower = calcMaxPower(voltage) # Watts + arr[varMaxTraction] = 180.0 # Needs a more complex implementation before being used. Potentially something akin to the gaussian kernel of the voltage histeresis model but for acceleration? Or literally based on the suspension travel. + arr[varVoltage] = calcVoltage() # Not yet implemented. Returns 120 for now. + arr[varMaxPower] = calcMaxPower(arr[varVoltage]) # Watts - resistiveForces = calcResistiveForces(worldPrev, inputs) - frontBrakeHeating, rearBrakeHeating = calcBrakeHeating(worldPrev, inputs) - frontBrakeCooling, rearBrakeCooling = calcBrakeCooling(worldPrev) - frontBrakeTemperature = worldPrev.frontBrakeTemperature + frontBrakeHeating - frontBrakeCooling - rearBrakeTemperature = worldPrev.rearBrakeTemperature + rearBrakeHeating - rearBrakeCooling + arr[varResistiveForces] = calcResistiveForces(worldArray, step) + arr[varFrontBrakeHeating], arr[varRearBrakeHeating] = calcBrakeHeating(worldArray, step) + arr[varFrontBrakeCooling], arr[varRearBrakeCooling] = calcBrakeCooling(worldArray, step) + arr[varFrontBrakeForce], arr[varRearBrakeForce] = calcBrakeForce(worldArray, step) + arr[varFrontBrakeTemperature] = worldArray[step-1, varFrontBrakeTemperature] + arr[varFrontBrakeHeating] - arr[varFrontBrakeCooling] + arr[varRearBrakeTemperature] = worldArray[step-1, varRearBrakeTemperature] + arr[varRearBrakeHeating] - arr[varRearBrakeCooling] - maxMotorTorque = calcMaxMotorTorque(worldPrev, resistiveForces, maxPower, maxTraction) - motorTorque = min(Parameters["maxTorque"]*inputs[0], maxMotorTorque) # Nm + arr[varMaxMotorTorque] = calcMaxMotorTorque(worldArray, step, arr[varResistiveForces], arr[varMaxPower], arr[varMaxTraction]) + arr[varMotorTorque] = min(Parameters["maxTorque"]*arr[varThrottle], arr[varMaxMotorTorque]) # Nm - power = motorTorque * worldPrev.motorRotationsHZ # Watts - motorForce = calcMotorForce(motorTorque) # Newtons - netForce = motorForce + resistiveForces # Newtons + arr[varPower] = arr[varMotorTorque] * worldArray[step-1, varMotorRotationsHZ] # Watts + arr[varMotorForce] = calcMotorForce(arr[varMotorTorque]) # Newtons + arr[varNetForce] = arr[varMotorForce] + arr[varResistiveForces] # Newtons - acceleration = netForce / Parameters["Mass"] # m/s^2 + arr[varAcceleration] = arr[varNetForce] / Parameters["Mass"] # m/s^2 - current = power/voltage # Amps + arr[varCurrent] = arr[varPower] / arr[varVoltage] # Amps - charge = worldPrev.charge - current * delta / 3600.0 - position = worldPrev.position + worldPrev.velocity * delta - speed = max(0, worldPrev.speed + acceleration * delta) # Sometimes braking falls a tad below 0 so we just correct that because otherwise everything breaks - yawRate = worldPrev.yawRate - if inputs[2] == 0: - yawRate = 0 - heading = calculateHeading(worldPrev.heading, yawRate, delta) + arr[varCharge] = worldArray[step-1, varCharge] - worldArray[step, varCurrent] * delta / 3600.0 + arr[varPosX:varPosZ+1] = worldArray[step-1, varPosX:varPosZ+1] + worldArray[step-1, varVelX:varVelZ+1] * delta + arr[varSpeed] = max(0, worldArray[step-1, varSpeed] + arr[varAcceleration] * delta) # Sometimes braking falls a tad below 0 so we just correct that because otherwise everything breaks + arr[varYawRate] = worldArray[step-1, varYawRate] + if worldArray[step, varSteerAngle] == 0: + arr[varYawRate] = 0 + arr[varVelX:varVelZ+1] = arr[varSpeed] * worldArray[step-1, varHeadingX:varHeadingZ+1] + arr[varHeadingX:varHeadingZ+1] = calculateHeading(worldArray, step) - drag = calcDrag(worldPrev) - frontBrakeForce, rearBrakeForce = calcBrakeForce(worldPrev, inputs) - frontSlipAngle, rearSlipAngle = calcSlipAngle(worldPrev, inputs) - maxWheelTorque = calcMaxWheelTorque(maxMotorTorque) - - # cols = ["x", "y", "z", "vX", "vY", "vZ", "speed", - # "headingX", "headingY", "headingZ", - # "yawRate", "frontBrakeTemperature", "rearBrakeTemperature", - # "charge", "drag", "resistiveForces", - # "motorTorque", "motorForce", "netForce", - # "maxTraction", "wheelRotationsHZ", "motorRPM", - # "motorRotationsHZ", "current", - # "maxWheelTorque", "maxPower", "power", - # "voltage", "downForce", - # "frontBrakeForce", "rearBrakeForce", - # "frontBrakeHeating", "rearBrakeHeating", - # "frontBrakeCooling", "rearBrakeCooling", - # "frontSlipAngle", "rearSlipAngle"] + arr[varDrag] = calcDrag(worldArray, step) - log:list[float] = [position[0], position[1], position[2], - worldPrev.velocity[0], worldPrev.velocity[1], worldPrev.velocity[2], - worldPrev.speed, - worldPrev.heading[0], worldPrev.heading[1], worldPrev.heading[2], - worldPrev.yawRate, frontBrakeTemperature, rearBrakeTemperature, - charge, drag, resistiveForces, - motorTorque, motorForce, netForce, - maxTraction, worldPrev.wheelRotationsHZ, worldPrev.motorRPM, - worldPrev.motorRotationsHZ, current, - maxWheelTorque, maxPower, power, - voltage, - frontBrakeForce, rearBrakeForce, - frontBrakeHeating, rearBrakeHeating, - frontBrakeCooling, rearBrakeCooling, - frontSlipAngle, rearSlipAngle] + arr[varFrontSlipAngle], arr[varRearSlipAngle] = calcSlipAngle(worldArray, step) + arr[varMaxWheelTorque] = calcMaxWheelTorque(arr[varMaxMotorTorque]) + arr[varWheelRotationsHZ] = arr[varSpeed] / Parameters["wheelCircumferance"] * 2.0 * np.pi + arr[varMotorRotationsHZ] = arr[varWheelRotationsHZ] / Parameters["gearRatio"] + arr[varWheelRPM] = arr[varWheelRotationsHZ] * 60.0 + arr[varMotorRPM] = arr[varWheelRPM] / Parameters["gearRatio"] + return arr - worldNext = VehicleState( - position=position, - speed=speed, - heading = heading, - charge=charge, - frontBrakeTemperature = frontBrakeTemperature, - rearBrakeTemperature = rearBrakeTemperature, - yawRate = worldPrev.yawRate - ) - return worldNext, log +def dynamicStepState(step:np.ndarray) -> np.ndarray: + """ + Interface for simulation step state that takes a single row with inputs and a previous step. + Separates this into 2 rows with the first row being the previous step and the second row being the current inputs. + Then calls stepState to get the new state for the current step. + + :param step: Step you wish to input (Array of all features) + :type step: np.ndarray + :return: Next step in the simulation given your contorl input and vehicle state. + :rtype: ndarray[Any, Any] + """ + arr = np.array([step, step]) + arr[1, 5:] = np.zeros((step.shape[0]-5)) + return stepState(arr, 1) \ No newline at end of file diff --git a/FullVehicleSim/main.py b/FullVehicleSim/main.py index c869c1c..62d0f5f 100644 --- a/FullVehicleSim/main.py +++ b/FullVehicleSim/main.py @@ -4,8 +4,7 @@ import argparse import time -from paramLoader import Magic, Parameters -from state import * +from paramLoader import * from engine import * if __name__ == "__main__": @@ -29,31 +28,20 @@ raise Exception("Please provide a valid simulation controls file path using --simulation_controls or -c") ## Double check it has the correct columns - if df_controls.columns != ['time', 'throttle', 'brakesFront','brakesRear', 'steerAngle']: - raise Exception("Simulation controls file must contain the following columns: 'time', 'throttle', 'brakesFront', 'brakesRear', 'steerAngle'") + if df_controls.columns != ['time', 'throttle', 'brakePressureFront','brakePressureRear', 'steerAngle']: + raise Exception("Simulation controls file must contain the following columns: 'time', 'throttle', 'brakePressureFront', 'brakePressureRear', 'steerAngle'") totalSteps = int(Parameters["stepsPerSecond"] * Parameters["simulationDuration"]) steps = np.arange(0, Parameters["simulationDuration"], 1/Parameters["stepsPerSecond"]) - cols = ["x", "y", "z", "vX", "vY", "vZ", "speed", - "headingX", "headingY", "headingZ", - "yawRate", "frontBrakeTemperature", "rearBrakeTemperature", - "charge", "drag", "resistiveForces", - "motorTorque", "motorForce", "netForce", - "maxTraction", "wheelRotationsHZ", "motorRPM", - "motorRotationsHZ", "current", - "maxWheelTorque", "maxPower", "power", - "voltage", - "frontBrakeForce", "rearBrakeForce", - "frontBrakeHeating", "rearBrakeHeating", - "frontBrakeCooling", "rearBrakeCooling", - "frontSlipAngle", "rearSlipAngle"] - - log = np.zeros((totalSteps + 1, len(cols))) - worldArray = np.zeros(totalSteps + 1, dtype=VehicleState) - # Set the inital time to 0 if not already 0 - timeSeries = df_controls['time'] - df_controls['time'][0] + ## This is structured so the first row is the initial conditions (inputs don't matter and will just be left to 0), and the + ## rest are generated as the simulation progresses. This means that a simulation array will always be 1 longer than just the time steps + ## and duration would indicate. + worldArray = np.zeros((totalSteps + 1, len(VARIABLE_NAMES)), dtype=np.float32) + + # Set the inital time to 0 if not already 0. Eg. [1.79, 2.36, 3.13] becomes [0.0, 0.57, 1.34] + timeSeries = df_controls['time'] - df_controls['time'][0] # Normalize to start at 0 # This takes the last time step and copies it out to the end of the simulation duration. # This has the effect of holding the last command constant until the end of the simulation duration. @@ -61,8 +49,8 @@ df_controls = df_controls.vstack(pl.DataFrame({ 'time': [Parameters["simulationDuration"]], 'throttle': df_controls["throttle"][-1], - 'brakesFront': df_controls["brakesFront"][-1], - 'brakesRear': df_controls["brakesRear"][-1], + 'brakePressureFront': df_controls["brakePressureFront"][-1], + 'brakePressureRear': df_controls["brakePressureRear"][-1], 'steerAngle': df_controls["steerAngle"][-1]})) timeSeries = df_controls['time'] @@ -76,27 +64,29 @@ elif Parameters["interpolationMethod"] == "linear": controlInputs = np.zeros((len(steps), 4)) controlInputs[:,0] = np.interp(steps, timeSeries, df_controls['throttle']) - controlInputs[:,1] = np.interp(steps, timeSeries, df_controls['brakesFront']) - controlInputs[:,2] = np.interp(steps, timeSeries, df_controls['brakesRear']) + controlInputs[:,1] = np.interp(steps, timeSeries, df_controls['brakePressureFront']) + controlInputs[:,2] = np.interp(steps, timeSeries, df_controls['brakePressureRear']) controlInputs[:,3] = np.interp(steps, timeSeries, df_controls['steerAngle']) else: raise Exception("Unsupported interpolation method. Please use 'cubic' or 'linear'.") - worldArray[0] = VehicleState( - position=np.asarray([0,0,0], dtype=np.float32), - speed=0, - heading = np.asarray([1,0,0], dtype=np.float32), - charge=Parameters["vehicleSOC"], - yawRate = 0, - frontBrakeTemperature = Parameters["initialBrakeTemperature"], - rearBrakeTemperature= Parameters["initialBrakeTemperature"] - ) - - timeCol = np.arange(0, Parameters["simulationDuration"] + 1/Parameters["stepsPerSecond"], 1/Parameters["stepsPerSecond"]) + ## Setup initial conditions. Leaves row 0 with no inputs (don't matter anyway since sim runs from 1 -> end) + ## Some other initial conditions based on input parameters. + worldArray[1:, varThrottle] = controlInputs[:,0] + worldArray[1:, varBrakePressureFront] = controlInputs[:,1] + worldArray[1:, varBrakePressureRear] = controlInputs[:,2] + worldArray[1:, varSteerAngle] = controlInputs[:,3] + worldArray[0,varCharge] = Parameters["vehicleSOC"] + worldArray[0,varFrontBrakeTemperature] = Parameters["initialBrakeTemperature"] + worldArray[0,varRearBrakeTemperature] = Parameters["initialBrakeTemperature"] + worldArray[0, varHeadingX:varHeadingZ+1] = Parameters["initHeading"] + worldArray[0, varPosX:varPosZ+1] = Parameters["initPosition"] + worldArray[0, varVelX:varVelZ+1] = Parameters["initVelocity"] + worldArray[:, varTime] = np.arange(0, Parameters["simulationDuration"] + 1/Parameters["stepsPerSecond"], 1/Parameters["stepsPerSecond"]) start = time.time() - for i in range(totalSteps): - worldArray[i+1], log[i+1] = stepState(worldArray[i], controlInputs[i]) # Step forward!! + for i in range(1, totalSteps): + worldArray[i, :] = stepState(worldArray, i) # Step forward!! ## This was above the stepState but I moved it down to make it clearer to read. # timeRunning += 1/stepsPerSecond # timeSinceLastSteer += 1/stepsPerSecond @@ -116,18 +106,12 @@ # 'cooledBrakeTemperature', 'wheelRPM', 'wheelRotationsHZ', # 'rpm', 'motorRotationsHZ', 'charge', 'voltage', 'current', # 'power', 'maxPower', 'stepSize', 'timeSinceLastSteer'] + # print(VARIABLE_NAMES) - df = pl.DataFrame(log, schema=cols, orient="row") + df = pl.DataFrame(worldArray, schema=VARIABLE_NAMES, orient="row") # print(f"df shape: {df.shape}") # print(f"control inputs shape: {controlInputs.shape}") # print(f"timeCol shape: {timeCol.shape}") - df = df[1:].with_columns( - pl.Series(timeCol[1:]).alias("time"), - pl.Series(controlInputs[:,0]).alias("throttle"), - pl.Series(controlInputs[:,1]).alias("brakesFront"), - pl.Series(controlInputs[:,2]).alias("brakesRear"), - pl.Series(controlInputs[:,3]).alias("steerAngle") - ) df.write_parquet("simulation_output.parquet") @@ -138,30 +122,32 @@ torque = df['motorTorque'] yawRate = df['yawRate'] frontBrakeTemperature = df['frontBrakeTemperature'] - ax1 = plt.subplot(1,4,1) - ax2 = plt.subplot(1,4,2) - ax3 = plt.subplot(1,4,3) - ax4 = plt.subplot(1,4,4) - - ax1.set_title("Current vs Time") + ax1 = plt.subplot(411) + ax11 = ax1.twinx() + ax2 = plt.subplot(412) + ax22 = ax2.twinx() + ax3 = plt.subplot(413) + ax33 = ax3.twinx() + ax4 = plt.subplot(414) + ax44 = ax4.twinx() + + ax1.set_title("I (Blue)/V (Orange) vs Time") ax1.set_xlabel("Time (s)") - ax1.set_ylabel("Current (A)") - ax1.plot(t, current) + ax1.set_ylabel("Current (A) / Voltage (V)") + ax1.plot(t, current, label="Current") + ax11.plot(t, voltage, label="Voltage", color='orange') ax2.set_title("Speed vs Time") ax2.set_xlabel("Time (s)") ax2.set_ylabel("Speed (m/s)") ax2.plot(t, speed) - ax3.set_title("Voltage vs Time") - ax3.set_xlabel("Time (s)") - ax3.set_ylabel("Voltage (V)") - ax3.plot(t, voltage) - - ax3.set_title("Voltage vs Time") + ax3.set_title("Throttle (Blue)/Brakes (Orange) vs Time") ax3.set_xlabel("Time (s)") - ax3.set_ylabel("Voltage (V)") - ax3.plot(t, voltage) + ax3.set_ylabel("Throttle (0-1)") + ax33.set_ylabel("Brake Pressure (PSI)") + ax3.plot(t, df["throttle"], label="Throttle") + ax33.plot(t, df["brakePressureFront"], color='orange') ax4.set_title("rvt") ax4.plot(t, yawRate) diff --git a/FullVehicleSim/paramLoader.py b/FullVehicleSim/paramLoader.py index 8e06320..5249d9d 100644 --- a/FullVehicleSim/paramLoader.py +++ b/FullVehicleSim/paramLoader.py @@ -1,9 +1,101 @@ import json5 -Magic:dict -Parameters:dict +from typing import Dict, List, Tuple + +Magic: dict +Parameters: dict with open('params.json5', 'r') as file: params = json5.load(file) Magic = params["Magic"] Parameters = params["Parameters"] del params + +# Variable definitions - maintain original order for compatibility + +varTime = 0 +varThrottle = 1 +varBrakePressureFront = 2 +varBrakePressureRear = 3 +varSteerAngle = 4 +varPosX = 5 +varPosY = 6 +varPosZ = 7 +varVelX = 8 +varVelY = 9 +varVelZ = 10 +varSpeed = 11 +varHeadingX = 12 +varHeadingY = 13 +varHeadingZ = 14 +varYawRate = 15 +varFrontBrakeTemperature = 16 +varRearBrakeTemperature = 17 +varCharge = 18 +varDrag = 19 +varResistiveForces = 20 +varMotorTorque = 21 +varMotorForce = 22 +varNetForce = 23 +varMaxTraction = 24 +varWheelRotationsHZ = 25 +varMotorRPM = 26 +varMotorRotationsHZ = 27 +varCurrent = 28 +varMaxWheelTorque = 29 +varMaxPower = 30 +varPower = 31 +varVoltage = 32 +varFrontBrakeForce = 33 +varRearBrakeForce = 34 +varFrontBrakeHeating = 35 +varRearBrakeHeating = 36 +varFrontBrakeCooling = 37 +varRearBrakeCooling = 38 +varFrontSlipAngle = 39 +varRearSlipAngle = 40 +varMaxMotorTorque = 41 +varAcceleration = 42 +varWheelRPM = 43 + +# Automatically generate schema from defined variables +def generate_variable_schema() -> Dict[int, str]: + """ + Generate a schema mapping variable indices to their names. + Preserves the order of definition in the file. + """ + schema = {} + + # Get all variables that start with 'var' from the current module + current_module = globals() + var_items = [(name, value) for name, value in current_module.items() + if name.startswith('var') and isinstance(value, int)] + + # Sort by value to maintain order + var_items.sort(key=lambda x: x[1]) + + # Create the schema + for name, index in var_items: + # Convert variable name to readable format + readable_name = name[3].lower() + name[4:] # Remove 'var' prefix and lowercase first letter + schema[index] = readable_name + + return schema + +def get_variable_names() -> List[str]: + """ + Get ordered list of variable names (without 'var' prefix). + """ + schema = generate_variable_schema() + return [schema[i] for i in range(len(schema))] + +def get_variable_mapping() -> Dict[str, int]: + """ + Get mapping from variable names to indices. + """ + schema = generate_variable_schema() + return {name: index for index, name in schema.items()} + +# Generate the schema on module load +VARIABLE_SCHEMA = generate_variable_schema() +VARIABLE_NAMES = get_variable_names() +VARIABLE_MAPPING = get_variable_mapping() print("Parameters loaded...") diff --git a/FullVehicleSim/params.json5 b/FullVehicleSim/params.json5 index 32daac1..99123d2 100644 --- a/FullVehicleSim/params.json5 +++ b/FullVehicleSim/params.json5 @@ -11,6 +11,9 @@ "initialBatteryTemperature": 20, // °C "vehicleSOC": 1.0, // Out of 1 "initSpeed": 0.0, // m/s + "initHeading": [1.0, 0.0, 0.0], // Vector pointing in the direction of the front of the car + "initPosition": [0.0, 0.0, 0.0], // Starting position of the car in the world frame + "initVelocity": [0.0, 0.0, 0.0], // Initial velocity vector in the world frame "airDensity": 1.230, "dragCoeffAreaCombo": 1.0858790012112278, // Combines area and drag coeff into one constant @@ -46,7 +49,7 @@ "brakeSurfaceArea": 0.001180643, "brakepadThickness": 0.007874, "brakeMass": 0.408, - "maxBrakeForce": 1500 + "maxBrakeForce": 1500, }, "Magic": { "shape-factor": 0.696268618106842, @@ -149,6 +152,6 @@ "pressureYA": -3.086921788053587e-05, "pressureYB": -9.812999633140862e-05, "pressureYC": 0.9998338222503662, - "gysign": -0.10000000149011612 + "gysign": -0.10000000149011612, } } diff --git a/FullVehicleSim/state.py b/FullVehicleSim/state.py deleted file mode 100644 index 7976733..0000000 --- a/FullVehicleSim/state.py +++ /dev/null @@ -1,41 +0,0 @@ -import numpy as np -from paramLoader import Parameters, Magic -from dataclasses import dataclass - -@dataclass -class VehicleState: - def __init__(self, position, speed, heading, charge, yawRate, frontBrakeTemperature, rearBrakeTemperature): - self.position:np.ndarray = position - self.speed:float = speed - self.heading:np.ndarray = heading - self.charge:float = charge - self.frontBrakeTemperature:float = frontBrakeTemperature - self.rearBrakeTemperature:float = rearBrakeTemperature - self.yawRate:float = yawRate - - #self.wheelRPM: np.array = np.asarray([0,0,0,0], dtype=np.float32) - #self.wheelRotationsHz: float = self.speed / self.WheelCircumference * 2.0 * np.pi - self.tires:np.ndarray = np.asarray([None, None, None, None])#, dtype=tire.Tire) # [FL, FR, BL, BR] - - @property - def velocity(self): - return self.heading * self.speed - - @property - def wheelRPM(self): - return self.speed / Parameters["wheelCircumferance"] * 60.0 - - @property - def wheelRotationsHZ(self): - return self.speed / Parameters["wheelCircumferance"] * 2.0 * np.pi - @property - def motorRPM(self): - return self.wheelRPM * Parameters["gearRatio"] - - @property - def motorRotationsHZ(self): - return self.wheelRotationsHZ * Parameters["gearRatio"] - - @property - def maxTractionTorqueAtWheel(self): - return Parameters["maxTractionTorque"] * Parameters["wheelRadius"] diff --git a/FullVehicleSim/visualizeManual.py b/FullVehicleSim/visualizeManual.py index 107e30e..5318590 100644 --- a/FullVehicleSim/visualizeManual.py +++ b/FullVehicleSim/visualizeManual.py @@ -3,7 +3,6 @@ import numpy as np import asyncio import platform -from state import VehicleState from engine import stepState #Simulated control inputs (since no file I/O in Pyodide) diff --git a/endur1Curr.csv b/endur1Curr.csv new file mode 100644 index 0000000..322bda2 --- /dev/null +++ b/endur1Curr.csv @@ -0,0 +1,72 @@ +Current,Time +24.611364,0.0 +32.075455,1.0 +83.82,2.0 +103.79028,3.0 +1.6792728,4.0 +47.380276,5.0 +156.73936,6.0 +181.45502,7.0 +178.01755,8.0 +266.4711,9.0 +314.56467,10.0 +337.80002,11.0 +123.42737,12.0 +18.743092,13.0 +41.31073,14.0 +23.57373,15.0 +71.3661,16.0 +147.76573,17.0 +38.92582,18.0 +21.131275,19.0 +15.494909,20.0 +40.06673,21.0 +118.78701,22.0 +115.37664,23.0 +147.45909,24.0 +223.76947,25.0 +148.38336,26.0 +78.637184,27.0 +23.648548,28.0 +43.008003,29.0 +58.465275,30.0 +91.7551,31.0 +77.75927,32.0 +24.253273,33.0 +37.92873,34.0 +42.02046,35.0 +48.948368,36.0 +88.985916,37.0 +89.52327,38.0 +99.00618,39.0 +67.61691,40.0 +23.409546,41.0 +59.80364,42.0 +79.542,43.0 +83.21628,44.0 +128.63428,45.0 +151.5899,46.0 +113.44146,47.0 +91.6961,48.0 +24.420546,49.0 +25.824457,50.0 +60.09946,51.0 +69.13337,52.0 +98.356,53.0 +57.09346,54.0 +55.792725,55.0 +43.36391,56.0 +39.913273,57.0 +53.080276,58.0 +73.36591,59.0 +83.82373,60.0 +54.693638,61.0 +52.09064,62.0 +48.891186,63.0 +48.31355,64.0 +57.849186,65.0 +29.795094,66.0 +37.14237,67.0 +22.748,68.0 +34.09791,69.0 +30.930456,70.0