This appendix contains the source code for the programs in the book. You can also find the source code online at the author’s website, as well as built into techBASIC and techBASIC Sampler. Look in the Maker Books folder in the techBASIC Programs directory.
This text-based BASIC program appears in Chapter 14. It is a one-dimensional simulation of the flight of Juno. The program will run in almost any implementation of BASIC with little or no change.
You can easily change the program to simulate other rocket flights. The lines to edit appear near the top of the listing.
rocketMass
is the mass of the rocket in kilograms. The best way to get this
value is to weigh the rocket on a kitchen scale. The weight includes the
weight of the parachute and recovery wadding, but not the weight of the motor:
REM Rocket mass in kg, w/o the motor. rocketMass = 0.036
You can calculate the coefficient of drag using the techniques in Chapter 13. You can also estimate the Cd. Use a value of 0.4 to 0.6 for most rockets, with 0.4 reserved for polished rockets with airfoil fins. Go higher if the rocket has lots of decorations hanging off it:
Cd = 0.524: REM Coefficient of drag.
The frontal area of the rocket is the area at the base of the nose cone. Given the diameter of the base of the nose cone, the area is:
The area is in square meters:
REM Frontal area of the rocket. Ar = 0.000483
ρ (rho
) is the density of the atmosphere in kilograms per cubic meter. The default value is the air density at sea level:
rho = 1.225: REM Density of the air.
Subtract about 4% per 1,000 feet if your launch site is significantly above sea level.
dt
is the simulation time step. Lower numbers are generally more accurate,
but make the program take longer to run:
dt = 0.01: REM Simulation time step.
motorName$
is the filename of a motor file in RASP format. You can find these
files at http://www.thrustcurve.org. There are three preloaded
in techBASIC (the other two are Estes_A8.eng and Estes_B6.eng):
motorName$ = "Estes_C6.eng"
You can install other motor files using iTunes; see the techBASIC Quick Start Guide for details. You can find the techBASIC Quick Start Guide at the techBASIC documentation page.
Example A-1 lists the complete source for the Juno Simulation program.
REM Simple Text-Based Rocket Simulator.
REM
REM This simulation is set up to simulate a sea-level flight of Juno,
REM a rocket in the book "Make: Rockets, Down to Earth Rocket Science."
REM Change the variables in the next section to simulate a different rocket
REM rocket flight. See the book for details on how this simulator works.
DIM
SHARED
thrust
(
50
),
time
(
50
),
mass
(
50
)
DIM
SHARED
propellantWeight
,
motorWeight
DIM
SHARED
bounds
AS
INTEGER
DIM
SHARED
tokens
(
20
)
AS
STRING
,
tokenCount
AS
INTEGER
debug
=
0
REM Initialize the rocket data. Change these values for different rockets,
REM rockets, motors, or flight conditions.
DIM
rocketMass
,
d
,
d0
,
v0
,
a
,
fg
,
fd
,
ft
,
t
,
f
,
dt
,
Ar
,
rho
,
Cd
REM Rocket mass in kg, w/o the motor.
rocketMass
=
0.036
Cd
=
0.524
:
REM Coefficient of drag.
REM The Frontal area of the rocket.
Ar
=
0.000483
rho
=
1.225
:
REM Density of the air.
dt
=
0.01
:
REM Simulation time step.
motorName$
=
"Estes_C6.eng"
REM Load the motor data.
CALL
LoadMotor
(
motorName$
)
IF
debug
THEN
propellantWeight
,
motorWeight
FOR
i
=
1
TO
bounds
time
(
i
),
thrust
(
i
),
mass
(
i
)
NEXT
END
IF
REM Run the simulation.
MaxA
=
0.0
MaxV
=
0.0
fg
=
(
rocketMass
+
motorWeight
)
*
9.81
DO
t
=
t
+
dt
fd
=
Cd
*
rho
*
v0
*
v0
*
Ar
ft
=
Interpolate
(
t
,
time
(),
thrust
())
f
=
ft
-
fd
-
fg
a
=
f
/
(
rocketMass
+
Interpolate
(
t
,
time
(),
mass
()))
IF
a
>
MaxA
THEN
MaxA
=
a
d
=
d0
+
v0
*
dt
+
a
*
t0
*
t0
/
2
d0
=
d
v0
=
v0
+
a
*
dt
fg
=
(
rocketMass
+
Interpolate
(
t
,
time
(),
mass
()))
*
9.81
IF
v0
>
MaxV
THEN
MaxV
=
v0
IF
debug
THEN
t
,
d
,
v0
,
a
LOOP
UNTIL
(
t
>
1
)
AND
(
v0
<
0
)
REM Print the results.
"Maximum altitude = "
;
d
;
" meters."
"Maximum velocity = "
;
MaxV
;
" m/s."
"Maximum acceleration = "
;
MaxA
;
" m/s^2."
"Ideal ejection delay = "
;
t
-
time
(
bounds
)
;
" seconds."
END
REM Use a specific time to interpolate in a table of time-based values.
REM
REM Inputs:
REM t - The desired time.
REM time - An array of times, from lowest to highest.
REM value - A table of values, one for each elemnt element in the time array.
REM
REM Returns: A linearly interpolated value from the value array. If t is
REM less than the first time value, the first value in the value array
REM is returned. If t is greater that than the last time, the last value in
REM the value array is returned.
FUNCTION
Interpolate
(
t
,
time
(),
value
())
IF
t
<
time
(
1
)
THEN
Interpolate
=
value
(
1
)
*
t
/
time
(
1
)
ELSE
IF
t
<
time
(
bounds
)
THEN
DIM
index
AS
INTEGER
index
=
1
WHILE
time
(
index
)
<
t
index
=
index
+
1
WEND
Interpolate
=
value
(
index
-
1
)
+
_
(
t
-
time
(
index
-
1
))
*
(
value
(
index
)
-
value
(
index
-
1
))
_
/
(
time
(
index
)
-
time
(
index
-
1
))
ELSE
Interpolate
=
value
(
bounds
)
END
IF
END
IF
END
FUNCTION
REM Load a motor file.
REM
REM Parameters:
REM fileName - The name of the motor file.
REM
REM Global values set:
REM propellantWeight - The total weight of the propellant in kg.
REM motorWeight - The total weight of the motor, with propellant
REM in kg.
REM time - The times for which thrust data is available.
REM thrust - The thrust at each time, in newtons.
REM mass - The motor mass values at each time step.
SUB
LoadMotor
(
fileName
AS
STRING
)
DIM
aLine
AS
STRING
,
count
AS
INTEGER
REM Scan the file, reading the data.
OPEN
fileName
FOR
INPUT
AS
#
1
bounds
=
-
1
WHILE
NOT
EOF
(
1
)
LINE
INPUT
#
1
,
aLine
IF
LEN
(
aLine
)
>
0
AND
LEFT$
(
aLine
,
1
)
<>
";"
THEN
CALL
Parse
(
aLine
)
bounds
=
bounds
+
1
IF
bounds
=
0
THEN
propellantWeight
=
VAL
(
tokens
(
5
))
motorWeight
=
VAL
(
tokens
(
6
))
ELSE
time
(
bounds
)
=
VAL
(
tokens
(
1
))
thrust
(
bounds
)
=
VAL
(
tokens
(
2
))
END
IF
END
IF
WEND
CLOSE
#
1
REM Find the total impulse.
DIM
impulse
impulse
=
TotalImpulse
(
time
(),
thrust
(),
bounds
)
IF
debug
THEN
"Total Impulse = "
;
impulse
REM Assume the mass loss is proportional to the impulse. Find
REM the motor mass as a function of time.
DIM
currentMass
,
t0
,
f0
,
di
,
i
AS
INTEGER
currentMass
=
motorWeight
FOR
i
=
1
TO
bounds
di
=
(
f0
+
thrust
(
i
))
*
(
time
(
i
)
-
t0
)
/
2
currentMass
=
currentMass
-
propellantWeight
*
di
/
impulse
mass
(
i
)
=
currentMass
f0
=
thrust
(
i
)
t0
=
time
(
i
)
NEXT
END
SUB
REM A utility subroutine for LoadMotor that breaks a line into space-
REM delimited strings. The tokens are placed in the global array tokens.
REM
REM Parameters:
REM aLine - The line to parse.
SUB
Parse
(
aLine
AS
STRING
)
REM Declare and initialize our local variables.
DIM
index
AS
INTEGER
tokenCount
=
0
REM Strip any leading space from the line.
WHILE
LEFT$
(
aLine
,
1
)
=
" "
aLine
=
RIGHT$
(
aLine
,
LEN
(
aLine
)
-
1
)
WEND
REM Collect the tokens.
WHILE
LEN
(
aLine
)
>
0
AND
tokenCount
<
10
tokenCount
=
tokenCount
+
1
index
=
SpaceIndex
(
aLine
)
IF
index
=
0
THEN
tokens
(
tokenCount
)
=
aLine
aLine
=
""
ELSE
tokens
(
tokenCount
)
=
LEFT$
(
aLine
,
index
-
1
)
aLine
=
RIGHT$
(
aLine
,
LEN
(
aLine
)
-
index
)
WHILE
LEFT$
(
aLine
,
1
)
=
" "
aLine
=
RIGHT$
(
aLine
,
LEN
(
aLine
)
-
1
)
WEND
END
IF
WEND
END
SUB
REM A utility function for Parse that finds the index of the next space
REM character.
REM
REM Parameters:
REM sval - The string.
REM
REM Returns: The index of the next space, or 0 if there are none.
FUNCTION
SpaceIndex
(
sval
AS
STRING
)
DIM
index
AS
INTEGER
,
fund
AS
INTEGER
found
=
0
index
=
1
WHILE
(
index
<
LEN
(
sval
))
AND
(
found
=
0
)
IF
MID$
(
sval
,
index
,
1
)
=
" "
THEN
found
=
1
ELSE
index
=
index
+
1
END
IF
WEND
IF
found
=
0
THEN
index
=
0
END
IF
SpaceIndex
=
index
END
FUNCTION
REM Find the total impulse of the motor.
REM
REM Parameters:
REM time - The time steps where motor thrust is available.
REM thrust - The thrust at each time step.
REM bounds - The number of data points in time() and thrust().
REM
REM Returns: The total impuls impulse in newton-seconds.
FUNCTION
TotalImpulse
(
time
(),
thrust
(),
bounds
AS
INTEGER
)
DIM
t0
,
f0
,
impulse
,
i
AS
INTEGER
FOR
i
=
1
TO
bounds
impulse
=
impulse
+
(
f0
+
thrust
(
i
))
*
(
time
(
i
)
-
t0
)
/
2
t0
=
time
(
i
)
f0
=
thrust
(
i
)
NEXT
TotalImpulse
=
impulse
END
FUNCTION
The Simple Rocket Simulator (SRockS) is essentially the Juno Simulation from the previous section with a user interface for the iPhone and iPod touch. It will also run on the iPad, of course. It is preinstalled in techBASIC and techBASIC Sampler in the Maker Books folder.
The rocket mass is given in kilograms. The best way to get this value is to weigh the rocket on a kitchen scale. As before, the weight includes the weight of the parachute and recovery wadding, but not the weight of the motor.
You can calculate the coefficient of drag using the techniques in Chapter 13. You can also estimate the Cd. Use a value of 0.4 to 0.6 for most rockets, with 0.4 reserved for polished rockets with airfoil fins. Go higher if the rocket has lots of decorations hanging off it.
The area of the rocket is the frontal area, at the base of the nose cone. Tap on the value to select a new value. A list of common body tube sizes appears. Select the appropriate body tube size and the frontal area will be filled in automatically. The area is in square meters.
Atm. density is the density of the atmosphere in kilograms per cubic meter. Subtract about 4% per 1,000 feet if your launch site is significantly above sea level.
The time step is the simulation time step. Lower numbers are generally more accurate, but make the program take longer to run.
Tap the motor value to select a different motor. A picker will appear that will show all of the installed motor files. Select the new motor and tap the Done button. You can find additional motor files at http://www.thrustcurve.org. Follow the instructions in the techBASIC Quick Start Guide to install new motor files using iTunes. You can find the techBASIC Quick Start Guide at the techBASIC documentation page.
Example A-2 shows the complete source for the SRockS program.
REM Simple Rocket Simulator.
REM
REM One-dimensional rocket flight simulator.
REM Motor variables.
DIM
propellantWeight
,
motorWeight
,
motorName
AS
STRING
DIM
thrust
(
1
),
time
(
1
),
mass
(
1
)
DIM
motorNames
(
1
)
AS
STRING
,
motorFileNames
(
1
)
AS
STRING
REM GUI elements.
DIM
titleLabel
AS
Label
DIM
quitButton
AS
Button
,
simulateButton
AS
Button
DIM
massTextField
as
TextField
,
massLabel
as
Label
DIM
cdTextField
as
TextField
,
cdLabel
as
Label
DIM
arValueLabel
as
Label
,
arPicker
AS
Picker
,
arLabel
as
Label
DIM
rhoTextField
as
TextField
,
rhoLabel
as
Label
DIM
dtTextField
as
TextField
,
dtLabel
as
Label
DIM
motorValueLabel
as
Label
,
motorPicker
AS
Picker
,
motorLabel
as
Label
DIM
altitudeValueLabel
as
Label
,
altitudeLabel
as
Label
DIM
speedValueLabel
as
Label
,
speedLabel
as
Label
DIM
accelerationValueLabel
as
Label
,
accelerationLabel
as
Label
DIM
delayValueLabel
as
Label
,
delayLabel
as
Label
DIM
pickerBackground
AS
Label
,
pickerDoneButton
AS
Button
REM Initialize the rocket data. Change these values for different rockets,
REM motors, or flight conditions.
DIM
rocketMass
,
d
,
d0
,
v0
,
a
,
fg
,
fd
,
ft
,
t
,
f
,
dt
,
Ar
,
rho
,
Cd
rocketMass
=
0.036
:
REM Rocket mass in kilograms, without the motor.
Cd
=
0.524
:
REM Coefficient of drag.
Ar
=
0.000483
:
REM Frontal area of the rocket.
rho
=
1.225
:
REM Density of the air.
dt
=
0.01
:
REM Simulation time step.
REM Set up the program.
loadMotors
motorName$
=
motorNames
(
1
)
motorFileName$
=
motorFileNames
(
1
)
initGUI
simulate
showAbout
END
REM Create a label and an initialized TextField.
REM
REM Parameters:
REM x, y - The top-left location for the label.
REM title - The title for the label.
REM labelCtrl - The label control.
REM textFieldCtrl - The text field control.
REM value - The initial value.
REM scientific - Format the value using scientific notation?
SUB
createEditable
(
x
,
_
y
,
_
title
AS
STRING
,
_
BYREF
labelCtrl
AS
Label
,
_
BYREF
textFieldCtrl
AS
TextField
,
_
value
,
_
scientific
AS
INTEGER
)
labelCtrl
=
Graphics
.
newLabel
(
x
,
y
,
140
)
labelCtrl
.
setText
(
title
)
labelCtrl
.
setAlignment
(
3
)
textFieldCtrl
=
Graphics
.
newTextField
(
x
+
150
,
y
,
140
)
textFieldCtrl
.
setText
(
format
(
value
,
scientific
))
END
SUB
REM Create a label, button, and picker. The button displays a value.
REM That value can be changed using a picker, which is displayed when
REM the user taps on the button and hidden when the user picks a value
REM or taps on a Done button, which is shared among all picker controls.
REM
REM Parameters:
REM x, y - The top-left location for the label.
REM title - The title for the label.
REM labelCtrl - The label control.
REM valueCtrl - The label for displaying a value.
REM pickerCtrl - The picker control.
REM values - The values to display in the picker control.
REM value - The initial value.
REM scientific - Format the value using scientific notation?
SUB
createPicker
(
x
,
_
y
,
_
title
AS
STRING
,
_
BYREF
labelCtrl
AS
Label
,
_
BYREF
valueCtrl
AS
Label
,
_
value
,
_
scientific
AS
INTEGER
)
labelCtrl
=
Graphics
.
newLabel
(
x
,
y
,
140
)
labelCtrl
.
setText
(
title
)
labelCtrl
.
setAlignment
(
3
)
valueCtrl
=
Graphics
.
newLabel
(
x
+
150
,
y
,
140
)
valueCtrl
.
setText
(
format
(
value
,
scientific
))
END
SUB
REM Create a label and an initialized TextField.
REM
REM Parameters:
REM x, y - The top-left location for the label.
REM title - The title for the label.
REM labelCtrl - The label control.
REM valueCtrl - The label for displaying a value.
REM value - The initial value.
REM scientific - Format the value using scientific notation?
SUB
createUneditable
(
x
,
_
y
,
_
title
AS
STRING
,
_
BYREF
labelCtrl
AS
Label
,
_
BYREF
valueCtrl
AS
Label
,
_
value
,
_
scientific
AS
INTEGER
)
labelCtrl
=
Graphics
.
newLabel
(
x
,
y
,
140
)
labelCtrl
.
setText
(
title
)
labelCtrl
.
setAlignment
(
3
)
valueCtrl
=
Graphics
.
newLabel
(
x
+
150
,
y
,
140
)
valueCtrl
.
setText
(
format
(
value
,
scientific
))
END
SUB
REM Format a floating-point value for display. This is used for all
REM fields to give a consistent, readable view of values.
REM
REM Parameters:
REM value - The value to format.
REM scientific - Format the value using scientific notation?
REM
REM Returns: The formatted value.
FUNCTION
format
(
value
,
scientific
AS
INTEGER
)
AS
STRING
DIM
s$
IF
scientific
THEN
$
s$
USING
"#######.###^^^^^^"
;
value
;
ELSE
$
s$
USING
"#######.###"
;
value
;
END
IF
s$
=
LTRIM
(
s$
)
format
=
s$
END
FUNCTION
REM Set up the user interface.
SUB
initGUI
System
.
showGraphics
(
1
)
Graphics
.
setToolsHidden
(
1
)
width
=
Graphics
.
width
height
=
Graphics
.
height
REM Add a title.
titleLabel
=
Graphics
.
newLabel
(
20
,
20
,
width
-
40
,
40
)
titleLabel
.
setText
(
"Simple Rocket Simulator"
)
titleLabel
.
setAlignment
(
2
)
titleLabel
.
setFont
(
"Sans-Serif"
,
22
,
1
)
REM Create labels and initialized text boxes for the editable rocket
REM components.
DIM
y
,
dy
y
=
70
dy
=
28
createEditable
(
20
,
y
,
"Rocket Mass:"
,
massLabel
,
massTextField
,
_
rocketMass
,
0
)
y
=
y
+
dy
createEditable
(
20
,
y
,
"Cd:"
,
cdLabel
,
cdTextField
,
Cd
,
0
)
y
=
y
+
dy
createPicker
(
20
,
y
,
"Area:"
,
arLabel
,
arValueLabel
,
Ar
,
1
)
y
=
y
+
dy
createEditable
(
20
,
y
,
"Atm. Density:"
,
rhoLabel
,
rhoTextField
,
rho
,
0
)
y
=
y
+
dy
createEditable
(
20
,
y
,
"Time Step:"
,
dtLabel
,
dtTextField
,
dt
,
0
)
y
=
y
+
dy
createPicker
(
20
,
y
,
"Motor:"
,
motorLabel
,
motorValueLabel
,
0
,
0
)
motorValueLabel
.
setText
(
motorName$
)
y
=
y
+
dy
REM Create the buttons.
quitButton
=
newButton
(
width
-
92
,
height
-
57
,
-
1
,
"Quit"
)
simulateButton
=
newButton
(
20
,
y
,
280
,
"Run Simulation"
)
REM Create the labels and uneditable text fields for the results.
y
=
y
+
47
createUneditable
(
20
,
y
,
"Altitude:"
,
altitudeLabel
,
_
altitudeValueLabel
,
0
,
0
)
y
=
y
+
dy
createUneditable
(
20
,
y
,
"Max Speed:"
,
speedLabel
,
speedValueLabel
,
0
,
0
)
y
=
y
+
dy
createUneditable
(
20
,
y
,
"Max Accel.:"
,
accelerationLabel
,
_
accelerationValueLabel
,
0
,
0
)
y
=
y
+
dy
createUneditable
(
20
,
y
,
"Best Delay:"
,
delayLabel
,
delayValueLabel
,
0
,
_
0
)
REM Create the pickers and related objects. These are created last so they
REM will show up on top of other controls.
pickerBackground
=
Graphics
.
newLabel
(
0
,
70
,
width
,
height
-
70
)
pickerBackground
.
setHidden
(
1
)
pickerDoneButton
=
newButton
((
width
-
72
)
/
2
,
320
,
-
1
,
"Done"
)
pickerDoneButton
.
setHidden
(
1
)
DIM
values
(
7
)
AS
STRING
values
(
1
)
=
"BT-5"
values
(
2
)
=
"BT-20"
values
(
3
)
=
"BT-50"
values
(
4
)
=
"BT-55"
values
(
5
)
=
"BT-60"
values
(
6
)
=
"BT-70"
values
(
7
)
=
"BT-80"
arPicker
=
Graphics
.
newPicker
(
0
,
80
)
arPicker
.
setHidden
(
1
)
arPicker
.
insertRows
(
values
,
1
)
arPicker
.
selectRow
(
3
)
motorPicker
=
Graphics
.
newPicker
(
0
,
80
)
motorPicker
.
setHIdden
(
1
)
motorPicker
.
insertRows
(
motorNames
,
1
)
motorPicker
.
selectRow
(
1
)
END
SUB
REM Use a specific time to interpolate in a table of time-based values.
REM
REM Inputs:
REM t - The desired time.
REM time - An array of times, from lowest to highest.
REM value - A table of values, one for each elemnt in the time array.
REM
REM Returns: A linearly interpolated value from the value array. If t is
REM less than the first time value, the first value in the value array
REM is returned. If t is greater than the last time, the last value in
REM the value array is returned.
FUNCTION
interpolate
(
t
,
time
(),
value
())
IF
t
<
time
(
1
)
THEN
interpolate
=
value
(
1
)
*
t
/
time
(
1
)
ELSE
IF
t
<
time
(
UBOUND
(
time
,
1
))
THEN
DIM
index
AS
INTEGER
index
=
1
WHILE
time
(
index
)
<
t
index
=
index
+
1
WEND
interpolate
=
value
(
index
-
1
)
+
_
(
t
-
time
(
index
-
1
))
*
(
value
(
index
)
-
value
(
index
-
1
))
_
/
(
time
(
index
)
-
time
(
index
-
1
))
ELSE
interpolate
=
value
(
UBOUND
(
value
,
1
))
END
IF
END
FUNCTION
REM Load a motor file.
REM
REM The values for the motor file are placed in global variables. The
REM variables set are:
REM
REM propellantWeight - The total weight of the propellant in kg.
REM motorWeight - The total weight of the motor, with propellant, in kg.
REM motorName - The name for this motor.
REM time - The times for which thrust data is available.
REM thrust - The thrust at each time, in newtons.
REM mass - The motor mass values at each time step.
REM
REM Parameters:
REM fileName - The name of the motor file.
SUB
loadMotor
(
fileName
AS
String
)
DIM
aLine
AS
STRING
,
tokens
(
1
)
AS
STRING
,
count
AS
INTEGER
REM Scan the file, counting the non-comment lines. This gives
REM the number of data points.
OPEN
fileName
FOR
INPUT
AS
#
1
WHILE
NOT
EOF
(
1
)
LINE
INPUT
#
1
,
aLine
IF
LEN
(
aLine
)
>
0
AND
LEFT
(
aLine
,
1
)
<>
";"
THEN
count
=
count
+
1
END
IF
WEND
CLOSE
#
1
REM Dimension arrays to hold the time, thrust, and weight.
DIM
tm
(
count
-
1
),
tr
(
count
-
1
),
ma
(
count
-
1
)
REM Rescan the file, reading the data.
OPEN
fileName
FOR
INPUT
AS
#
1
count
=
0
WHILE
NOT
EOF
(
1
)
LINE
INPUT
#
1
,
aLine
IF
LEN
(
aLine
)
>
0
AND
LEFT
(
aLine
,
1
)
<>
";"
THEN
tokens
=
parse
(
aLine
)
IF
count
=
0
THEN
motorName
=
tokens
(
1
)
propellantWeight
=
VAL
(
tokens
(
5
))
motorWeight
=
VAL
(
tokens
(
6
))
ELSE
tm
(
count
)
=
VAL
(
tokens
(
1
))
tr
(
count
)
=
VAL
(
tokens
(
2
))
END
IF
count
=
count
+
1
END
IF
WEND
CLOSE
#
1
REM Find the total impulse.
DIM
impulse
impulse
=
totalImpulse
(
tm
,
tr
)
IF
debug
THEN
"Total Impulse = "
;
impulse
REM Assume the mass loss is proportional to the impulse. Find
REM the motor mass as a function of time.
DIM
currentMass
,
t0
,
f0
,
di
,
i
AS
INTEGER
currentMass
=
motorWeight
FOR
i
=
1
TO
UBOUND
(
tm
,
1
)
di
=
(
f0
+
tr
(
i
))
*
(
tm
(
i
)
-
t0
)
/
2
currentMass
=
currentMass
-
propellantWeight
*
di
/
impulse
ma
(
i
)
=
currentMass
f0
=
tr
(
i
)
t0
=
tm
(
i
)
NEXT
REM Copy the values to the global arrays.
time
=
tm
thrust
=
tr
mass
=
ma
END
SUB
REM Scans the document directory for motor files. Any that are found are
REM placed in the two arrays of motors. The arrays are:
REM
REM motorNames - The display names for the motors.
REM motorFileNames - The filenames for each motor file.
SUB
loadMotors
DIM
fileName
AS
STRING
,
count
AS
INTEGER
,
i
AS
INTEGER
count
=
0
fileName
=
DIR
(
"*"
)
WHILE
fileName
<>
""
IF
LCASE
(
RIGHT
(
fileName
,
4
))
=
".eng"
THEN
count
=
count
+
1
loadMotor
(
fileName
)
DIM
names
(
count
)
AS
STRING
,
fileNames
(
COUNT
)
AS
STRING
FOR
i
=
1
TO
count
-
1
names
(
i
)
=
motorNames
(
i
)
fileNames
(
i
)
=
motorFileNames
(
i
)
NEXT
names
(
count
)
=
motorName
fileNames
(
count
)
=
fileName
motorNames
=
names
motorFileNames
=
fileNames
END
IF
fileName
=
DIR
WEND
motorNames
motorFileNames
END
SUB
REM Creates a new button with a gradient fill.
REM
REM Parameters:
REM x - Horizontal location.
REM y - Vertical location.
REM width - Width of the button, or -1 for the default width.
REM title - Name of the button.
REM
REM Returns: The new button.
FUNCTION
newButton
(
x
,
y
,
width
,
title
AS
STRING
)
AS
Button
DIM
b
AS
Button
IF
width
=
-
1
THEN
width
=
72
b
=
Graphics
.
newButton
(
x
,
y
,
width
)
b
.
setTitle
(
title
)
b
.
setBackgroundColor
(
1
,
1
,
1
)
b
.
setGradientColor
(
0.6
,
0.6
,
0.6
)
newButton
=
b
END
FUNCTION
REM A utility subroutine for loadMotor that breaks a line into space-
REM delimited strings.
REM
REM Parameters:
REM aLine - The line to parse.
REM
REM Returns: An array of strings.
FUNCTION
parse
(
aLine
AS
STRING
)
(
1
)
AS
STRING
REM Declare and initialize our local variables.
DIM
tokens
(
10
)
AS
STRING
,
count
AS
INTEGER
,
index
AS
INTEGER
count
=
0
REM Strip any leading space from the line.
WHILE
LEFT
(
aLine
,
1
)
=
" "
aLine
=
RIGHT
(
aLine
,
LEN
(
aLine
)
-
1
)
WEND
REM Collect the tokens.
WHILE
LEN
(
aLine
)
>
0
AND
count
<
10
count
=
count
+
1
index
=
POS
(
aLine
,
" "
)
IF
index
=
0
THEN
tokens
(
count
)
=
aLine
aLine
=
""
ELSE
tokens
(
count
)
=
LEFT
(
aLine
,
index
)
aLine
=
RIGHT
(
aLine
,
LEN
(
aLine
)
-
index
)
WHILE
LEFT
(
aLine
,
1
)
=
" "
aLine
=
RIGHT
(
aLine
,
LEN
(
aLine
)
-
1
)
WEND
END
IF
WEND
REM Create an array for the final tokens and return them.
DIM
finalTokens
(
count
)
AS
STRING
WHILE
count
>
0
finalTokens
(
count
)
=
tokens
(
count
)
count
=
count
-
1
WEND
parse
=
finalTokens
END
FUNCTION
REM Shows the About alert when the program starts.
SUB
showAbout
about$
=
"One-dimensional simulation of a model rocket flight."
about$
=
about$
&
CHR
(
10
)
&
CHR
(
10
)
&
"See the book, Make: Rockets, "
_
&
"for a complete description of this app."
about$
=
about$
&
CHR
(
10
)
&
CHR
(
10
)
&
"This app takes basic "
_
&
"information about a model rocket and simulates its flight to "
_
&
"find the maximum altitude, acceleration, and speed and the "
_
&
"ideal ejection charge."
about$
=
about$
&
CHR
(
10
)
&
CHR
(
10
)
&
"All units are in the meters-"
_
&
"kilograms-seconds (MKS) system. Cd is the coefficient of drag, "
_
&
"generally 0.4 to 0.7 for most model rockets. Area is the "
_
&
"frontal area of the body tube. Atm. density is the atmospheric "
_
&
"density, preset for sea level. Time step is the simulation "
_
&
"time step."
about$
=
about$
&
CHR
(
10
)
&
CHR
(
10
)
&
"Tap a value to change it. Tap "
_
&
"Run Simulation to run the simulation with the new values."
i
=
Graphics
.
showAlert
(
"About This Sample"
,
about$
)
END
SUB
REM Simulate a flight with the current flight characteristics.
SUB
simulate
REM Load the motor data.
loadMotor
(
motorFileName$
)
motorFileName$
IF
debug
THEN
propellantWeight
,
motorWeight
FOR
i
=
1
TO
UBOUND
(
time
,
1
)
time
(
i
),
thrust
(
i
),
mass
(
i
)
NEXT
END
IF
REM Get the latest values for the simulation.
rocketMass
=
VAL
(
massTextField
.
getText
)
Cd
=
VAL
(
cdTextField
.
getText
)
rho
=
VAL
(
rhoTextField
.
getText
)
dt
=
VAL
(
dtTextField
.
getText
)
REM Run the simulation.
DIM
MaxA
,
MaxV
,
fg
,
t
,
fd
,
ft
,
f
,
a
,
d
,
d0
,
v0
MaxA
=
0.0
MaxV
=
0.0
fg
=
(
rocketMass
+
motorWeight
)
*
9.81
DO
t
=
t
+
dt
fd
=
Cd
*
rho
*
v0
*
v0
*
Ar
ft
=
interpolate
(
t
,
time
,
thrust
)
f
=
ft
-
fd
-
fg
a
=
f
/
(
rocketMass
+
interpolate
(
t
,
time
,
mass
))
IF
a
>
MaxA
THEN
MaxA
=
a
d
=
d0
+
v0
*
dt
+
a
*
t0
*
t0
/
2
d0
=
d
v0
=
v0
+
a
*
dt
fg
=
(
rocketMass
+
interpolate
(
t
,
time
,
mass
))
*
9.81
IF
v0
>
MaxV
THEN
MaxV
=
v0
IF
DEBUG
THEN
t
,
d
,
v0
,
a
LOOP
UNTIL
t
>
1
AND
v0
<
0
REM Show the results.
altitudeValueLabel
.
setText
(
format
(
d
,
0
))
speedValueLabel
.
setText
(
format
(
MaxV
,
0
))
accelerationValueLabel
.
setText
(
format
(
MaxA
,
0
))
delayValueLabel
.
setText
(
format
(
t
-
time
(
UBOUND
(
time
,
1
)),
0
))
END
SUB
REM Find the total impulse of the motor.
REM
REM Parameters:
REM time - The time steps where motor thrust is available.
REM thrust - The thrust at each time step.
REM
REM Returns: The total impulse in newton-seconds.
FUNCTION
totalImpulse
(
time
(),
thrust
())
DIM
t0
,
f0
,
impulse
,
i
AS
INTEGER
FOR
i
=
1
TO
UBOUND
(
time
,
1
)
impulse
=
impulse
+
(
f0
+
thrust
(
i
))
*
(
time
(
i
)
-
t0
)
/
2
t0
=
time
(
i
)
f0
=
thrust
(
i
)
NEXT
totalImpulse
=
impulse
END
FUNCTION
REM Called when a touch ends, this subroutine checks to see if the
REM touch was inside one of the labels used to trigger the appearance
REM of a picker and, if so, displays the picker.
REM
REM Parameters:
REM e - The touch event that triggered this call.
SUB
touchesEnded
(
e
AS
EVENT
)
DIM
where
(
1
,
2
),
x
,
y
where
=
e
.
where
x
=
where
(
1
,
1
)
y
=
where
(
1
,
2
)
IF
x
>=
arValueLabel
.
x
_
AND
x
<=
arValueLabel
.
x
+
arValueLabel
.
width
_
AND
y
>=
arValueLabel
.
y
_
AND
y
<=
arValueLabel
.
y
+
arValueLabel
.
height
THEN
pickerBackground
.
setHidden
(
0
)
arPicker
.
setHidden
(
0
)
pickerDoneButton
.
setHidden
(
0
)
ELSE
IF
x
>=
motorValueLabel
.
x
_
AND
x
<=
motorValueLabel
.
x
+
motorValueLabel
.
width
_
AND
y
>=
motorValueLabel
.
y
_
AND
y
<=
motorValueLabel
.
y
+
motorValueLabel
.
height
THEN
pickerBackground
.
setHidden
(
0
)
motorPicker
.
setHidden
(
0
)
pickerDoneButton
.
setHidden
(
0
)
END
IF
END
SUB
REM Handle a tap on one of the buttons.
REM
REM Parameters:
REM ctrl - The button that was tapped.
REM time - The time when the event occurred.
SUB
touchUpInside
(
ctrl
AS
Button
,
time
AS
DOUBLE
)
IF
ctrl
=
quitButton
THEN
STOP
ELSE
IF
ctrl
=
pickerDoneButton
THEN
arPicker
.
setHidden
(
1
)
motorPicker
.
setHidden
(
1
)
pickerDoneButton
.
setHidden
(
1
)
pickerBackground
.
setHidden
(
1
)
SELECT
CASE
arPicker
.
selection
CASE
1
:
Ar
=
0.000150
CASE
2
:
Ar
=
0.000275
CASE
3
:
Ar
=
0.000483
CASE
4
:
Ar
=
0.000892
CASE
5
:
Ar
=
0.001359
CASE
6
:
Ar
=
0.002463
CASE
7
:
Ar
=
0.003390
END
SELECT
arValueLabel
.
setText
(
format
(
Ar
,
1
))
motorName$
=
motorNames
(
motorPicker
.
selection
)
motorValueLabel
.
setText
(
motorName$
)
motorFileName$
=
motorFileNames
(
motorPicker
.
selection
)
loadMotor
(
motorFileName$
)
ELSE
IF
ctrl
=
simulateButton
THEN
simulate
END
IF
END
SUB
Theodolite is a simple BASIC program that should run in just about any modern BASIC compiler. It takes the baseline and angles from two theodolites and calculates the altitude of a rocket. See Chapter 8 for a detailed description of how to build and use theodolites to track rockets.
REM Calculate the altitude of a model rocket using angles from
REM two theodolites.
REM Get the baseline and angles.
INPUT
"Baseline length: "
;
B
REM Find the conversion from degrees to radians.
DTR
=
3.1415926535
/
180
REM Get the angles for the first tracker.
INPUT
"Tracker 1 azimuth: "
;
alpha1
INPUT
"Tracker 1 elevation: "
;
epsilon1
alpha1
=
alpha1
*
DTR
epsilon1
=
epsilon1
*
DTR
REM Get the angles for the second tracker.
INPUT
"Tracker 2 azimuth: "
;
alpha2
INPUT
"Tracker 2 elevation: "
;
epsilon2
alpha2
=
alpha2
*
DTR
epsilon2
=
epsilon2
*
DTR
REM Calculate the altitude using the vertical midpoint method.
A1
=
B
*
SIN
(
alpha2
)
*
TAN
(
epsilon1
)
/
SIN
(
alpha1
+
alpha2
)
A2
=
B
*
SIN
(
alpha1
)
*
TAN
(
epsilon2
)
/
SIN
(
alpha1
+
alpha2
)
A
=
(
A1
+
A2
)
/
2
C
=
ABS
((
A1
-
A2
)
/
(
2
*
A
))
"Vertical Midpoint Method:"
CALL
Report
(
A
,
C
)
REM Calculate the altitude using the geodesic method.
se1
=
SIN
(
epsilon1
)
se2
=
SIN
(
epsilon2
)
ce1
=
COS
(
epsilon1
)
ce2
=
COS
(
epsilon2
)
sa1
=
SIN
(
alpha1
)
sa2
=
SIN
(
alpha2
)
ca1
=
COS
(
alpha1
)
ca2
=
COS
(
alpha2
)
f
=
se1
*
se2
-
ce1
*
ce2
*
(
ca1
*
ca2
-
sa1
*
sa2
)
d1
=
B
*
(
ce1
*
ca1
+
f
*
ce2
*
ca2
)
/
(
1
-
f
*
f
)
d2
=
B
*
(
ce2
*
ca2
+
f
*
ce1
*
ca1
)
/
(
1
-
f
*
f
)
A
=
d1
*
d2
*
(
se1
+
se2
)
/
(
d1
+
d2
)
C
=
B
*
ABS
((
ce2
*
se1
*
sa2
-
ce1
*
se2
*
sa1
)
/
(
A
*
SQR
(
1
-
f
*
f
)))
"Geodesic Method:"
CALL
Report
(
A
,
C
)
END
REM Print the results.
REM
REM Inputs:
REM A - The altitude.
REM C - Track closed value.
SUB
Report
(
A
,
C
)
" Altitude: "
;
A
IF
C
<=
0.1
THEN
" Closed track; C = "
;
C
ELSE
" Track not closed; C = "
;
C
END
IF
END
SUB