Add elevator control loop and test.
This commit includes a big chunk of elevator code that Comran worked on,
and all the zeroing code that Adam got working for the bot3.
Unfortunately, we did not split these two projects apart earlier, so
this is a big commit containing all of the code combined. Right now, all
of the elevator tests that aren't commented out pass with the hall
effect zeroing method that Adam made, but all the tests in
elevator_lib_test will need to be checked by other programmers on the
team & updated as needed in a future commit.
Change-Id: I4ad6247303c3a939a28eff7d2043de74a2d3865d
diff --git a/bot3/control_loops/python/control_loop.py b/bot3/control_loops/python/control_loop.py
deleted file mode 100644
index 881880f..0000000
--- a/bot3/control_loops/python/control_loop.py
+++ /dev/null
@@ -1,338 +0,0 @@
-import controls
-import numpy
-
-class Constant(object):
- def __init__ (self, name, formatt, value):
- self.name = name
- self.formatt = formatt
- self.value = value
- self.formatToType = {}
- self.formatToType['%f'] = "double";
- self.formatToType['%d'] = "int";
- def __str__ (self):
- return str("\nstatic constexpr %s %s = "+ self.formatt +";\n") % \
- (self.formatToType[self.formatt], self.name, self.value)
-
-
-class ControlLoopWriter(object):
- def __init__(self, gain_schedule_name, loops, namespaces=None, write_constants=False):
- """Constructs a control loop writer.
-
- Args:
- gain_schedule_name: string, Name of the overall controller.
- loops: array[ControlLoop], a list of control loops to gain schedule
- in order.
- namespaces: array[string], a list of names of namespaces to nest in
- order. If None, the default will be used.
- """
- self._gain_schedule_name = gain_schedule_name
- self._loops = loops
- if namespaces:
- self._namespaces = namespaces
- else:
- self._namespaces = ['frc971', 'control_loops']
-
- self._namespace_start = '\n'.join(
- ['namespace %s {' % name for name in self._namespaces])
-
- self._namespace_end = '\n'.join(
- ['} // namespace %s' % name for name in reversed(self._namespaces)])
-
- self._constant_list = []
-
- def AddConstant(self, constant):
- """Adds a constant to write.
-
- Args:
- constant: Constant, the constant to add to the header.
- """
- self._constant_list.append(constant)
-
- def _TopDirectory(self):
- return self._namespaces[0]
-
- def _HeaderGuard(self, header_file):
- return (self._TopDirectory().upper() + '_CONTROL_LOOPS_' +
- header_file.upper().replace('.', '_').replace('/', '_') +
- '_')
-
- def Write(self, header_file, cc_file):
- """Writes the loops to the specified files."""
- self.WriteHeader(header_file)
- self.WriteCC(header_file, cc_file)
-
- def _GenericType(self, typename):
- """Returns a loop template using typename for the type."""
- num_states = self._loops[0].A.shape[0]
- num_inputs = self._loops[0].B.shape[1]
- num_outputs = self._loops[0].C.shape[0]
- return '%s<%d, %d, %d>' % (
- typename, num_states, num_inputs, num_outputs)
-
- def _ControllerType(self):
- """Returns a template name for StateFeedbackController."""
- return self._GenericType('StateFeedbackController')
-
- def _LoopType(self):
- """Returns a template name for StateFeedbackLoop."""
- return self._GenericType('StateFeedbackLoop')
-
- def _PlantType(self):
- """Returns a template name for StateFeedbackPlant."""
- return self._GenericType('StateFeedbackPlant')
-
- def _CoeffType(self):
- """Returns a template name for StateFeedbackPlantCoefficients."""
- return self._GenericType('StateFeedbackPlantCoefficients')
-
- def WriteHeader(self, header_file, double_appendage=False, MoI_ratio=0.0):
- """Writes the header file to the file named header_file.
- Set double_appendage to true in order to include a ratio of
- moments of inertia constant. Currently, only used for 2014 claw."""
- with open(header_file, 'w') as fd:
- header_guard = self._HeaderGuard(header_file)
- fd.write('#ifndef %s\n'
- '#define %s\n\n' % (header_guard, header_guard))
- fd.write('#include \"frc971/control_loops/state_feedback_loop.h\"\n')
- fd.write('\n')
-
- fd.write(self._namespace_start)
-
- for const in self._constant_list:
- fd.write(str(const))
-
- fd.write('\n\n')
- for loop in self._loops:
- fd.write(loop.DumpPlantHeader())
- fd.write('\n')
- fd.write(loop.DumpControllerHeader())
- fd.write('\n')
-
- fd.write('%s Make%sPlant();\n\n' %
- (self._PlantType(), self._gain_schedule_name))
-
- fd.write('%s Make%sLoop();\n\n' %
- (self._LoopType(), self._gain_schedule_name))
-
- fd.write(self._namespace_end)
- fd.write('\n\n')
- fd.write("#endif // %s\n" % header_guard)
-
- def WriteCC(self, header_file_name, cc_file):
- """Writes the cc file to the file named cc_file."""
- with open(cc_file, 'w') as fd:
- fd.write('#include \"%s/control_loops/%s\"\n' %
- (self._TopDirectory(), header_file_name))
- fd.write('\n')
- fd.write('#include <vector>\n')
- fd.write('\n')
- fd.write('#include \"frc971/control_loops/state_feedback_loop.h\"\n')
- fd.write('\n')
- fd.write(self._namespace_start)
- fd.write('\n\n')
- for loop in self._loops:
- fd.write(loop.DumpPlant())
- fd.write('\n')
-
- for loop in self._loops:
- fd.write(loop.DumpController())
- fd.write('\n')
-
- fd.write('%s Make%sPlant() {\n' %
- (self._PlantType(), self._gain_schedule_name))
- fd.write(' ::std::vector< ::std::unique_ptr<%s>> plants(%d);\n' % (
- self._CoeffType(), len(self._loops)))
- for index, loop in enumerate(self._loops):
- fd.write(' plants[%d] = ::std::unique_ptr<%s>(new %s(%s));\n' %
- (index, self._CoeffType(), self._CoeffType(),
- loop.PlantFunction()))
- fd.write(' return %s(&plants);\n' % self._PlantType())
- fd.write('}\n\n')
-
- fd.write('%s Make%sLoop() {\n' %
- (self._LoopType(), self._gain_schedule_name))
- fd.write(' ::std::vector< ::std::unique_ptr<%s>> controllers(%d);\n' % (
- self._ControllerType(), len(self._loops)))
- for index, loop in enumerate(self._loops):
- fd.write(' controllers[%d] = ::std::unique_ptr<%s>(new %s(%s));\n' %
- (index, self._ControllerType(), self._ControllerType(),
- loop.ControllerFunction()))
- fd.write(' return %s(&controllers);\n' % self._LoopType())
- fd.write('}\n\n')
-
- fd.write(self._namespace_end)
- fd.write('\n')
-
-
-class ControlLoop(object):
- def __init__(self, name):
- """Constructs a control loop object.
-
- Args:
- name: string, The name of the loop to use when writing the C++ files.
- """
- self._name = name
-
- def ContinuousToDiscrete(self, A_continuous, B_continuous, dt):
- """Calculates the discrete time values for A and B.
-
- Args:
- A_continuous: numpy.matrix, The continuous time A matrix
- B_continuous: numpy.matrix, The continuous time B matrix
- dt: float, The time step of the control loop
-
- Returns:
- (A, B), numpy.matrix, the control matricies.
- """
- return controls.c2d(A_continuous, B_continuous, dt)
-
- def InitializeState(self):
- """Sets X, Y, and X_hat to zero defaults."""
- self.X = numpy.zeros((self.A.shape[0], 1))
- self.Y = self.C * self.X
- self.X_hat = numpy.zeros((self.A.shape[0], 1))
-
- def PlaceControllerPoles(self, poles):
- """Places the controller poles.
-
- Args:
- poles: array, An array of poles. Must be complex conjegates if they have
- any imaginary portions.
- """
- self.K = controls.dplace(self.A, self.B, poles)
-
- def PlaceObserverPoles(self, poles):
- """Places the observer poles.
-
- Args:
- poles: array, An array of poles. Must be complex conjegates if they have
- any imaginary portions.
- """
- self.L = controls.dplace(self.A.T, self.C.T, poles).T
-
- def Update(self, U):
- """Simulates one time step with the provided U."""
- #U = numpy.clip(U, self.U_min, self.U_max)
- self.X = self.A * self.X + self.B * U
- self.Y = self.C * self.X + self.D * U
-
- def PredictObserver(self, U):
- """Runs the predict step of the observer update."""
- self.X_hat = (self.A * self.X_hat + self.B * U)
-
- def CorrectObserver(self, U):
- """Runs the correct step of the observer update."""
- self.X_hat += numpy.linalg.inv(self.A) * self.L * (
- self.Y - self.C * self.X_hat - self.D * U)
-
- def UpdateObserver(self, U):
- """Updates the observer given the provided U."""
- self.X_hat = (self.A * self.X_hat + self.B * U +
- self.L * (self.Y - self.C * self.X_hat - self.D * U))
-
- def _DumpMatrix(self, matrix_name, matrix):
- """Dumps the provided matrix into a variable called matrix_name.
-
- Args:
- matrix_name: string, The variable name to save the matrix to.
- matrix: The matrix to dump.
-
- Returns:
- string, The C++ commands required to populate a variable named matrix_name
- with the contents of matrix.
- """
- ans = [' Eigen::Matrix<double, %d, %d> %s;\n' % (
- matrix.shape[0], matrix.shape[1], matrix_name)]
- first = True
- for x in xrange(matrix.shape[0]):
- for y in xrange(matrix.shape[1]):
- element = matrix[x, y]
- if first:
- ans.append(' %s << ' % matrix_name)
- first = False
- else:
- ans.append(', ')
- ans.append(str(element))
-
- ans.append(';\n')
- return ''.join(ans)
-
- def DumpPlantHeader(self):
- """Writes out a c++ header declaration which will create a Plant object.
-
- Returns:
- string, The header declaration for the function.
- """
- num_states = self.A.shape[0]
- num_inputs = self.B.shape[1]
- num_outputs = self.C.shape[0]
- return 'StateFeedbackPlantCoefficients<%d, %d, %d> Make%sPlantCoefficients();\n' % (
- num_states, num_inputs, num_outputs, self._name)
-
- def DumpPlant(self):
- """Writes out a c++ function which will create a PlantCoefficients object.
-
- Returns:
- string, The function which will create the object.
- """
- num_states = self.A.shape[0]
- num_inputs = self.B.shape[1]
- num_outputs = self.C.shape[0]
- ans = ['StateFeedbackPlantCoefficients<%d, %d, %d>'
- ' Make%sPlantCoefficients() {\n' % (
- num_states, num_inputs, num_outputs, self._name)]
-
- ans.append(self._DumpMatrix('A', self.A))
- ans.append(self._DumpMatrix('B', self.B))
- ans.append(self._DumpMatrix('C', self.C))
- ans.append(self._DumpMatrix('D', self.D))
- ans.append(self._DumpMatrix('U_max', self.U_max))
- ans.append(self._DumpMatrix('U_min', self.U_min))
-
- ans.append(' return StateFeedbackPlantCoefficients<%d, %d, %d>'
- '(A, B, C, D, U_max, U_min);\n' % (num_states, num_inputs,
- num_outputs))
- ans.append('}\n')
- return ''.join(ans)
-
- def PlantFunction(self):
- """Returns the name of the plant coefficient function."""
- return 'Make%sPlantCoefficients()' % self._name
-
- def ControllerFunction(self):
- """Returns the name of the controller function."""
- return 'Make%sController()' % self._name
-
- def DumpControllerHeader(self):
- """Writes out a c++ header declaration which will create a Controller object.
-
- Returns:
- string, The header declaration for the function.
- """
- num_states = self.A.shape[0]
- num_inputs = self.B.shape[1]
- num_outputs = self.C.shape[0]
- return 'StateFeedbackController<%d, %d, %d> %s;\n' % (
- num_states, num_inputs, num_outputs, self.ControllerFunction())
-
- def DumpController(self):
- """Returns a c++ function which will create a Controller object.
-
- Returns:
- string, The function which will create the object.
- """
- num_states = self.A.shape[0]
- num_inputs = self.B.shape[1]
- num_outputs = self.C.shape[0]
- ans = ['StateFeedbackController<%d, %d, %d> %s {\n' % (
- num_states, num_inputs, num_outputs, self.ControllerFunction())]
-
- ans.append(self._DumpMatrix('L', self.L))
- ans.append(self._DumpMatrix('K', self.K))
- ans.append(self._DumpMatrix('A_inv', numpy.linalg.inv(self.A)))
-
- ans.append(' return StateFeedbackController<%d, %d, %d>'
- '(L, K, A_inv, Make%sPlantCoefficients());\n' % (
- num_states, num_inputs, num_outputs, self._name))
- ans.append('}\n')
- return ''.join(ans)
diff --git a/bot3/control_loops/python/drivetrain.py b/bot3/control_loops/python/drivetrain.py
index 8ed62d6..43b597f 100644
--- a/bot3/control_loops/python/drivetrain.py
+++ b/bot3/control_loops/python/drivetrain.py
@@ -232,7 +232,8 @@
else:
dog_loop_writer = control_loop.ControlLoopWriter(
"Drivetrain", [drivetrain_low_low, drivetrain_low_high,
- drivetrain_high_low, drivetrain_high_high])
+ drivetrain_high_low, drivetrain_high_high],
+ namespaces=['bot3', 'control_loops'])
if argv[1][-3:] == '.cc':
dog_loop_writer.Write(argv[2], argv[1])
else:
diff --git a/bot3/control_loops/python/elevator.py b/bot3/control_loops/python/elevator.py
deleted file mode 100644
index 2d72ff2..0000000
--- a/bot3/control_loops/python/elevator.py
+++ /dev/null
@@ -1,247 +0,0 @@
-#!/usr/bin/python
-
-import control_loop
-import controls
-import polytope
-import polydrivetrain
-import numpy
-import sys
-import matplotlib
-from matplotlib import pylab
-
-class Elevator(control_loop.ControlLoop):
- def __init__(self, name="Elevator", mass=None):
- super(Elevator, self).__init__(name)
- # Stall Torque in N m
- self.stall_torque = 2.42
- # Stall Current in Amps
- self.stall_current = 133
- # Free Speed in RPM
- self.free_speed = 4650.0
- # Free Current in Amps
- self.free_current = 2.7
- # Mass of the elevator
- # TODO(comran): Get actual value.
- self.mass = 13.0
- # Resistance of the motor
- self.R = 12.0 / self.stall_current
- # Motor velocity constant
- self.Kv = ((self.free_speed / 60.0 * 2.0 * numpy.pi) /
- (12.0 - self.R * self.free_current))
- # Torque constant
- self.Kt = self.stall_torque / self.stall_current
- # Gear ratio
- # TODO(comran): Get actual value.
- self.G = (56.0 / 12.0) * (84.0 / 14.0)
- # Pulley diameter
- # TODO(comran): Get actual value.
- self.r = 32 * 0.005 / numpy.pi / 2.0
- # Control loop time step
- self.dt = 0.005
-
- # Elevator spring constant (N/m)
- # TODO(comran): Get actual value.
- self.spring = 800.0
-
- # State is [average position, average velocity]
- # Input is [V]
-
- # TODO(comran): Change everything below.
-
- C1 = self.spring / (self.mass * 0.5)
- C2 = self.Kt * self.G / (self.mass * 0.5 * self.r * self.R)
- C3 = self.G * self.G * self.Kt / (
- self.R * self.r * self.r * self.mass * 0.5 * self.Kv)
-
- self.A_continuous = numpy.matrix(
- [[0, 1, 0, 0],
- [0, -C3, 0, 0],
- [0, 0, 0, 1],
- [0, 0, -C1 * 2.0, -C3]])
-
- print "Full speed is", C2 / C3 * 12.0
-
- # Start with the unmodified input
- self.B_continuous = numpy.matrix(
- [[0, 0],
- [C2 / 2.0, C2 / 2.0],
- [0, 0],
- [C2 / 2.0, -C2 / 2.0]])
-
- self.C = numpy.matrix([[1, 0, 1, 0],
- [1, 0, -1, 0]])
- self.D = numpy.matrix([[0, 0],
- [0, 0]])
-
- self.A, self.B = self.ContinuousToDiscrete(
- self.A_continuous, self.B_continuous, self.dt)
-
- print self.A
-
- controlability = controls.ctrb(self.A, self.B);
- print "Rank of augmented controlability matrix.", numpy.linalg.matrix_rank(
- controlability)
-
- q_pos = 0.02
- q_vel = 0.400
- q_pos_diff = 0.01
- q_vel_diff = 0.45
- self.Q = numpy.matrix([[(1.0 / (q_pos ** 2.0)), 0.0, 0.0, 0.0],
- [0.0, (1.0 / (q_vel ** 2.0)), 0.0, 0.0],
- [0.0, 0.0, (1.0 / (q_pos_diff ** 2.0)), 0.0],
- [0.0, 0.0, 0.0, (1.0 / (q_vel_diff ** 2.0))]])
-
- self.R = numpy.matrix([[(1.0 / (12.0 ** 2.0)), 0.0],
- [0.0, 1.0 / (12.0 ** 2.0)]])
- self.K = controls.dlqr(self.A, self.B, self.Q, self.R)
- print self.K
-
- print numpy.linalg.eig(self.A - self.B * self.K)[0]
-
- self.rpl = 0.20
- self.ipl = 0.05
- self.PlaceObserverPoles([self.rpl + 1j * self.ipl,
- self.rpl + 1j * self.ipl,
- self.rpl - 1j * self.ipl,
- self.rpl - 1j * self.ipl])
-
- # The box formed by U_min and U_max must encompass all possible values,
- # or else Austin's code gets angry.
- self.U_max = numpy.matrix([[12.0], [12.0]])
- self.U_min = numpy.matrix([[-12.0], [-12.0]])
-
- self.InitializeState()
-
-
-def CapU(U):
- if U[0, 0] - U[1, 0] > 24:
- return numpy.matrix([[12], [-12]])
- elif U[0, 0] - U[1, 0] < -24:
- return numpy.matrix([[-12], [12]])
- else:
- max_u = max(U[0, 0], U[1, 0])
- min_u = min(U[0, 0], U[1, 0])
- if max_u > 12:
- return U - (max_u - 12)
- if min_u < -12:
- return U - (min_u + 12)
- return U
-
-
-def run_test(elevator, initial_X, goal, max_separation_error=0.01,
- show_graph=True, iterations=200, controller_elevator=None,
- observer_elevator=None):
- """Runs the elevator plant with an initial condition and goal.
-
- The tests themselves are not terribly sophisticated; I just test for
- whether the goal has been reached and whether the separation goes
- outside of the initial and goal values by more than max_separation_error.
- Prints out something for a failure of either condition and returns
- False if tests fail.
- Args:
- elevator: elevator object to use.
- initial_X: starting state.
- goal: goal state.
- show_graph: Whether or not to display a graph showing the changing
- states and voltages.
- iterations: Number of timesteps to run the model for.
- controller_elevator: elevator object to get K from, or None if we should
- use elevator.
- observer_elevator: elevator object to use for the observer, or None if we
- should use the actual state.
- """
-
- elevator.X = initial_X
-
- if controller_elevator is None:
- controller_elevator = elevator
-
- if observer_elevator is not None:
- observer_elevator.X_hat = initial_X + 0.01
- observer_elevator.X_hat = initial_X
-
- # Various lists for graphing things.
- t = []
- x_avg = []
- x_sep = []
- x_hat_avg = []
- x_hat_sep = []
- v_avg = []
- v_sep = []
- u_left = []
- u_right = []
-
- sep_plot_gain = 100.0
-
- for i in xrange(iterations):
- X_hat = elevator.X
- if observer_elevator is not None:
- X_hat = observer_elevator.X_hat
- x_hat_avg.append(observer_elevator.X_hat[0, 0])
- x_hat_sep.append(observer_elevator.X_hat[2, 0] * sep_plot_gain)
- U = controller_elevator.K * (goal - X_hat)
- U = CapU(U)
- x_avg.append(elevator.X[0, 0])
- v_avg.append(elevator.X[1, 0])
- x_sep.append(elevator.X[2, 0] * sep_plot_gain)
- v_sep.append(elevator.X[3, 0])
- if observer_elevator is not None:
- observer_elevator.PredictObserver(U)
- elevator.Update(U)
- if observer_elevator is not None:
- observer_elevator.Y = elevator.Y
- observer_elevator.CorrectObserver(U)
-
- t.append(i * elevator.dt)
- u_left.append(U[0, 0])
- u_right.append(U[1, 0])
-
- print numpy.linalg.inv(elevator.A)
- print "delta time is ", elevator.dt
- print "Velocity at t=0 is ", x_avg[0], v_avg[0], x_sep[0], v_sep[0]
- print "Velocity at t=1+dt is ", x_avg[1], v_avg[1], x_sep[1], v_sep[1]
-
- if show_graph:
- pylab.subplot(2, 1, 1)
- pylab.plot(t, x_avg, label='x avg')
- pylab.plot(t, x_sep, label='x sep')
- if observer_elevator is not None:
- pylab.plot(t, x_hat_avg, label='x_hat avg')
- pylab.plot(t, x_hat_sep, label='x_hat sep')
- pylab.legend()
-
- pylab.subplot(2, 1, 2)
- pylab.plot(t, u_left, label='u left')
- pylab.plot(t, u_right, label='u right')
- pylab.legend()
- pylab.show()
-
-
-def main(argv):
- loaded_mass = 25
- #loaded_mass = 0
- elevator = Elevator(mass=13 + loaded_mass)
- elevator_controller = Elevator(mass=13 + 15)
- observer_elevator = Elevator(mass=13 + 15)
- #observer_elevator = None
-
- # Test moving the elevator with constant separation.
- initial_X = numpy.matrix([[0.0], [0.0], [0.01], [0.0]])
- #initial_X = numpy.matrix([[0.0], [0.0], [0.00], [0.0]])
- R = numpy.matrix([[1.0], [0.0], [0.0], [0.0]])
- run_test(elevator, initial_X, R, controller_elevator=elevator_controller,
- observer_elevator=observer_elevator)
-
- # Write the generated constants out to a file.
- if len(argv) != 3:
- print "Expected .h file name and .cc file name for the elevator."
- else:
- elevator = Elevator("Elevator")
- loop_writer = control_loop.ControlLoopWriter("Elevator", [elevator])
- if argv[1][-3:] == '.cc':
- loop_writer.Write(argv[2], argv[1])
- else:
- loop_writer.Write(argv[1], argv[2])
-
-if __name__ == '__main__':
- sys.exit(main(sys.argv))
diff --git a/bot3/control_loops/python/elevator3.py b/bot3/control_loops/python/elevator3.py
index 120e8cc..04edd53 100755
--- a/bot3/control_loops/python/elevator3.py
+++ b/bot3/control_loops/python/elevator3.py
@@ -259,15 +259,24 @@
scenario_plotter.Plot()
# Write the generated constants out to a file.
- if len(argv) != 3:
- print "Expected .h file name and .cc file name for the Elevator."
+ if len(argv) != 5:
+ print "Expected .h file name and .cc file name for the Elevator and integral elevator."
else:
elevator = Elevator("Elevator")
- loop_writer = control_loop.ControlLoopWriter("Elevator", [elevator])
+ loop_writer = control_loop.ControlLoopWriter("Elevator", [elevator],
+ namespaces=['bot3', 'control_loops'])
if argv[1][-3:] == '.cc':
loop_writer.Write(argv[2], argv[1])
else:
loop_writer.Write(argv[1], argv[2])
+ integral_elevator = IntegralElevator("IntegralElevator")
+ integral_loop_writer = control_loop.ControlLoopWriter("IntegralElevator", [integral_elevator],
+ namespaces=['bot3', 'control_loops'])
+ if argv[3][-3:] == '.cc':
+ integral_loop_writer.Write(argv[4], argv[3])
+ else:
+ integral_loop_writer.Write(argv[3], argv[4])
+
if __name__ == '__main__':
sys.exit(main(sys.argv))
diff --git a/bot3/control_loops/python/polydrivetrain.py b/bot3/control_loops/python/polydrivetrain.py
index a62c8c8..1090c8f 100644
--- a/bot3/control_loops/python/polydrivetrain.py
+++ b/bot3/control_loops/python/polydrivetrain.py
@@ -405,7 +405,8 @@
"VelocityDrivetrain", [vdrivetrain.drivetrain_low_low,
vdrivetrain.drivetrain_low_high,
vdrivetrain.drivetrain_high_low,
- vdrivetrain.drivetrain_high_high])
+ vdrivetrain.drivetrain_high_high],
+ namespaces=['bot3', 'control_loops'])
if argv[1][-3:] == '.cc':
dog_loop_writer.Write(argv[2], argv[1])
@@ -413,7 +414,8 @@
dog_loop_writer.Write(argv[1], argv[2])
cim_writer = control_loop.ControlLoopWriter(
- "CIM", [drivetrain.CIM()])
+ "CIM", [drivetrain.CIM()],
+ namespaces=['bot3', 'control_loops'])
if argv[5][-3:] == '.cc':
cim_writer.Write(argv[6], argv[5])