001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package edu.wpi.first.wpilibj.simulation; 006 007import edu.wpi.first.math.Matrix; 008import edu.wpi.first.math.Num; 009import edu.wpi.first.math.StateSpaceUtil; 010import edu.wpi.first.math.numbers.N1; 011import edu.wpi.first.math.system.LinearSystem; 012import edu.wpi.first.wpilibj.RobotController; 013import org.ejml.MatrixDimensionException; 014import org.ejml.simple.SimpleMatrix; 015 016/** 017 * This class helps simulate linear systems. To use this class, do the following in the {@link 018 * edu.wpi.first.wpilibj.IterativeRobotBase#simulationPeriodic} method. 019 * 020 * <p>Call {@link #setInput(double...)} with the inputs to the system (usually voltage). 021 * 022 * <p>Call {@link #update} to update the simulation. 023 * 024 * <p>Set simulated sensor readings with the simulated positions in {@link #getOutput()} 025 * 026 * @param <States> The number of states of the system. 027 * @param <Inputs> The number of inputs to the system. 028 * @param <Outputs> The number of outputs of the system. 029 */ 030@SuppressWarnings("ClassTypeParameterName") 031public class LinearSystemSim<States extends Num, Inputs extends Num, Outputs extends Num> { 032 // The plant that represents the linear system. 033 protected final LinearSystem<States, Inputs, Outputs> m_plant; 034 035 // Variables for state, output, and input. 036 @SuppressWarnings("MemberName") 037 protected Matrix<States, N1> m_x; 038 039 @SuppressWarnings("MemberName") 040 protected Matrix<Outputs, N1> m_y; 041 042 @SuppressWarnings("MemberName") 043 protected Matrix<Inputs, N1> m_u; 044 045 // The standard deviations of measurements, used for adding noise 046 // to the measurements. 047 protected final Matrix<Outputs, N1> m_measurementStdDevs; 048 049 /** 050 * Creates a simulated generic linear system. 051 * 052 * @param system The system to simulate. 053 */ 054 public LinearSystemSim(LinearSystem<States, Inputs, Outputs> system) { 055 this(system, null); 056 } 057 058 /** 059 * Creates a simulated generic linear system with measurement noise. 060 * 061 * @param system The system being controlled. 062 * @param measurementStdDevs Standard deviations of measurements. Can be null if no noise is 063 * desired. 064 */ 065 public LinearSystemSim( 066 LinearSystem<States, Inputs, Outputs> system, Matrix<Outputs, N1> measurementStdDevs) { 067 this.m_plant = system; 068 this.m_measurementStdDevs = measurementStdDevs; 069 070 m_x = new Matrix<>(new SimpleMatrix(system.getA().getNumRows(), 1)); 071 m_u = new Matrix<>(new SimpleMatrix(system.getB().getNumCols(), 1)); 072 m_y = new Matrix<>(new SimpleMatrix(system.getC().getNumRows(), 1)); 073 } 074 075 /** 076 * Updates the simulation. 077 * 078 * @param dtSeconds The time between updates. 079 */ 080 @SuppressWarnings("LocalVariableName") 081 public void update(double dtSeconds) { 082 // Update X. By default, this is the linear system dynamics X = Ax + Bu 083 m_x = updateX(m_x, m_u, dtSeconds); 084 085 // y = cx + du 086 m_y = m_plant.calculateY(m_x, m_u); 087 088 // Add measurement noise. 089 if (m_measurementStdDevs != null) { 090 m_y = m_y.plus(StateSpaceUtil.makeWhiteNoiseVector(m_measurementStdDevs)); 091 } 092 } 093 094 /** 095 * Returns the current output of the plant. 096 * 097 * @return The current output of the plant. 098 */ 099 public Matrix<Outputs, N1> getOutput() { 100 return m_y; 101 } 102 103 /** 104 * Returns an element of the current output of the plant. 105 * 106 * @param row The row to return. 107 * @return An element of the current output of the plant. 108 */ 109 public double getOutput(int row) { 110 return m_y.get(row, 0); 111 } 112 113 /** 114 * Sets the system inputs (usually voltages). 115 * 116 * @param u The system inputs. 117 */ 118 @SuppressWarnings("ParameterName") 119 public void setInput(Matrix<Inputs, N1> u) { 120 this.m_u = clampInput(u); 121 } 122 123 /** 124 * Sets the system inputs. 125 * 126 * @param row The row in the input matrix to set. 127 * @param value The value to set the row to. 128 */ 129 public void setInput(int row, double value) { 130 m_u.set(row, 0, value); 131 m_u = clampInput(m_u); 132 } 133 134 /** 135 * Sets the system inputs. 136 * 137 * @param u An array of doubles that represent the inputs of the system. 138 */ 139 @SuppressWarnings("ParameterName") 140 public void setInput(double... u) { 141 if (u.length != m_u.getNumRows()) { 142 throw new MatrixDimensionException( 143 "Malformed input! Got " + u.length + " elements instead of " + m_u.getNumRows()); 144 } 145 m_u = new Matrix<>(new SimpleMatrix(m_u.getNumRows(), 1, true, u)); 146 } 147 148 /** 149 * Sets the system state. 150 * 151 * @param state The new state. 152 */ 153 public void setState(Matrix<States, N1> state) { 154 m_x = state; 155 } 156 157 /** 158 * Returns the current drawn by this simulated system. Override this method to add a custom 159 * current calculation. 160 * 161 * @return The current drawn by this simulated mechanism. 162 */ 163 public double getCurrentDrawAmps() { 164 return 0.0; 165 } 166 167 /** 168 * Updates the state estimate of the system. 169 * 170 * @param currentXhat The current state estimate. 171 * @param u The system inputs (usually voltage). 172 * @param dtSeconds The time difference between controller updates. 173 * @return The new state. 174 */ 175 @SuppressWarnings("ParameterName") 176 protected Matrix<States, N1> updateX( 177 Matrix<States, N1> currentXhat, Matrix<Inputs, N1> u, double dtSeconds) { 178 return m_plant.calculateX(currentXhat, u, dtSeconds); 179 } 180 181 /** 182 * Clamp the input vector such that no element exceeds the given voltage. If any does, the 183 * relative magnitudes of the input will be maintained. 184 * 185 * @param u The input vector. 186 * @return The normalized input. 187 */ 188 protected Matrix<Inputs, N1> clampInput(Matrix<Inputs, N1> u) { 189 return StateSpaceUtil.desaturateInputVector(u, RobotController.getBatteryVoltage()); 190 } 191}