driving_physics 0.0.1.2 → 0.0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +23 -1
- data/Rakefile +84 -0
- data/VERSION +1 -1
- data/demo/car.rb +85 -72
- data/demo/gearbox.rb +4 -3
- data/demo/motor.rb +12 -10
- data/demo/mruby/car.rb +184 -0
- data/demo/mruby/motor.rb +140 -0
- data/demo/pid_controller.rb +143 -0
- data/demo/powertrain.rb +1 -1
- data/driving_physics.gemspec +2 -1
- data/lib/driving_physics/car.rb +14 -5
- data/lib/driving_physics/cli.rb +7 -35
- data/lib/driving_physics/cockpit.rb +167 -0
- data/lib/driving_physics/disk.rb +10 -30
- data/lib/driving_physics/environment.rb +2 -0
- data/lib/driving_physics/gearbox.rb +62 -32
- data/lib/driving_physics/motor.rb +218 -63
- data/lib/driving_physics/mruby.rb +48 -0
- data/lib/driving_physics/pid_controller.rb +111 -0
- data/lib/driving_physics/powertrain.rb +8 -13
- data/lib/driving_physics/scalar_force.rb +2 -1
- data/lib/driving_physics/timer.rb +34 -0
- data/lib/driving_physics/tire.rb +6 -6
- data/lib/driving_physics/vector_force.rb +23 -0
- data/lib/driving_physics.rb +28 -22
- data/test/disk.rb +23 -59
- data/test/gearbox.rb +36 -0
- data/test/motor.rb +236 -0
- data/test/powertrain.rb +21 -0
- data/test/scalar_force.rb +3 -6
- data/test/tire.rb +25 -53
- data/test/vector_force.rb +76 -37
- metadata +28 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b9159b499a0082d5af3ef926fceab925572552978b50d020b71bc51df7506fb
|
4
|
+
data.tar.gz: 183eaae3e07e24f38bb1417d96a6310eebfd288a1300d4e8bab96f008abbf3e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9a19ba4646abe21f30ca12c88f43e9ddc7083028e96504260f0277b167af5374364967b1617ab83744f824d6d2aa513ef38627d23ad5e9ccc8ff7e017b8a2ac8
|
7
|
+
data.tar.gz: 5357970cd547b12727bfc20c408df27a07d3aaac5d5b9042cbb10fc98c85484eb7bf054e140158f7e8695ef6c30eb18961fdd6979b4d0e3ab6c343f7767fc296
|
data/README.md
CHANGED
@@ -10,14 +10,36 @@ using `Vector` class from `matrix.rb` in stdlib.
|
|
10
10
|
|
11
11
|
This is very much a **Work In Progress**. Implemented so far:
|
12
12
|
|
13
|
+
### Library Features
|
14
|
+
|
13
15
|
* Spinning: rotational inertia and friction / hysteresis
|
14
|
-
* Tires: traction force via friction and the normal force
|
16
|
+
* Tires: traction force via friction (static and kinetic) and the normal force
|
15
17
|
* Vectors: 2D Vector forces and 3D Vector torques
|
16
18
|
* Motor: torque curves, rotating mass with friction
|
17
19
|
* Gearbox: gear ratios, final drive, rotating mass with friction
|
18
20
|
* Powertrain: combines motor and gearbox
|
19
21
|
* Car: 4 tires, powertrain, extra mass presents a load to the powertrain
|
20
22
|
* Acceleration: via drive traction from "first principles"
|
23
|
+
* Variable clutch and throttle
|
24
|
+
* Engine braking
|
25
|
+
* Power and Energy
|
26
|
+
|
27
|
+
### mruby Support
|
28
|
+
|
29
|
+
This library is primarily a Ruby library, intended for use with CRuby (MRI),
|
30
|
+
JRuby, etc. **mruby** is the redheaded stepchild, with significant
|
31
|
+
implications for a codebase that wants to support both sides. Supporting
|
32
|
+
mruby here meant:
|
33
|
+
|
34
|
+
1. Editing the codebase (minimally) to avoid and remove *mruby-deadends*.
|
35
|
+
2. Creating `rake` tasks to process normal .rb files into *mruby-compatible*
|
36
|
+
.rb files
|
37
|
+
3. Assembling all needed library .rb files into a master .rb file
|
38
|
+
4. Compiling the library to bytecode
|
39
|
+
5. Executing a toplevel/main script against the library
|
40
|
+
6. Optionally executing toplevel/main bytecode against the library
|
41
|
+
|
42
|
+
See the main Rakefile near the top for mruby stuff.
|
21
43
|
|
22
44
|
## Rationale
|
23
45
|
|
data/Rakefile
CHANGED
@@ -16,6 +16,90 @@ end
|
|
16
16
|
|
17
17
|
task default: :test
|
18
18
|
|
19
|
+
#
|
20
|
+
# mruby
|
21
|
+
#
|
22
|
+
|
23
|
+
MRBLIB_DIR = File.join %w[mruby mrblib]
|
24
|
+
MRBLIB_FILE = File.join(MRBLIB_DIR, 'driving_physics.rb')
|
25
|
+
MRBLIB_MRB = File.join(MRBLIB_DIR, 'driving_physics.mrb')
|
26
|
+
MRUBY_DEMO_DIR = File.join %w[demo mruby]
|
27
|
+
|
28
|
+
def write_mruby(input_file, output_file = MRBLIB_FILE, append: false)
|
29
|
+
file_obj = File.open(output_file, append ? 'a' : 'w')
|
30
|
+
line_count = 0
|
31
|
+
|
32
|
+
File.readlines(input_file).each { |line|
|
33
|
+
next if line.match /\A *(?:require|autoload)/
|
34
|
+
file_obj.write(line)
|
35
|
+
line_count += 1
|
36
|
+
}
|
37
|
+
file_obj.close
|
38
|
+
line_count
|
39
|
+
end
|
40
|
+
|
41
|
+
file MRBLIB_FILE do
|
42
|
+
line_count = write_mruby(File.join(%w[lib driving_physics.rb]), MRBLIB_FILE)
|
43
|
+
%w[cli environment imperial power timer
|
44
|
+
disk tire motor gearbox powertrain car pid_controller].each { |name|
|
45
|
+
file = File.join('lib', 'driving_physics', "#{name}.rb")
|
46
|
+
line_count += write_mruby(file, MRBLIB_FILE, append: true)
|
47
|
+
puts "wrote #{file} to #{MRBLIB_FILE}"
|
48
|
+
}
|
49
|
+
puts "wrote #{line_count} lines to #{MRBLIB_FILE}"
|
50
|
+
end
|
51
|
+
|
52
|
+
file MRBLIB_MRB => MRBLIB_FILE do
|
53
|
+
rb_file = File.join(%w[mruby mrblib driving_physics.rb])
|
54
|
+
mrb_file = File.join(%w[mruby mrblib driving_physics.mrb])
|
55
|
+
sh 'mrbc', rb_file
|
56
|
+
puts format("%s: %d bytes (created %s)",
|
57
|
+
mrb_file, File.size(mrb_file), File.birthtime(mrb_file))
|
58
|
+
end
|
59
|
+
|
60
|
+
file MRUBY_DEMO_DIR do
|
61
|
+
mkdir_p MRUBY_DEMO_DIR
|
62
|
+
end
|
63
|
+
|
64
|
+
desc "Map/Filter/Reduce lib/**/*.rb to mruby/mrblib/driving_physics.rb"
|
65
|
+
task mrblib: MRBLIB_FILE
|
66
|
+
|
67
|
+
desc "Compile mruby/mrblib/driving_physics.rb to .mrb bytecode"
|
68
|
+
task mrbc: MRBLIB_MRB
|
69
|
+
|
70
|
+
%w[disk tire motor gearbox powertrain car pid_controller].each { |name|
|
71
|
+
demo_file = File.join(MRUBY_DEMO_DIR, "#{name}.rb")
|
72
|
+
demo_mrb = File.join(MRUBY_DEMO_DIR, "#{name}.mrb")
|
73
|
+
|
74
|
+
file demo_file => MRUBY_DEMO_DIR do
|
75
|
+
write_mruby(File.join('demo', "#{name}.rb"), demo_file)
|
76
|
+
end
|
77
|
+
|
78
|
+
file demo_mrb => demo_file do
|
79
|
+
sh 'mrbc', demo_file
|
80
|
+
end
|
81
|
+
|
82
|
+
desc "run demo/#{name}.rb via mruby"
|
83
|
+
task "demo_#{name}" => [demo_file, MRBLIB_MRB] do
|
84
|
+
sh 'mruby', '-r', MRBLIB_MRB, demo_file
|
85
|
+
end
|
86
|
+
|
87
|
+
desc "run demo/#{name}.rb via mruby bytecode"
|
88
|
+
task "mrb_#{name}" => [demo_mrb, MRBLIB_MRB] do
|
89
|
+
sh 'mruby', '-r', MRBLIB_MRB, '-b', demo_mrb
|
90
|
+
end
|
91
|
+
}
|
92
|
+
|
93
|
+
desc "Remove generated .rb and .mrb files"
|
94
|
+
task :clean do
|
95
|
+
[MRBLIB_DIR, MRUBY_DEMO_DIR].each { |dir|
|
96
|
+
Dir[File.join(dir, '*rb')].each { |file|
|
97
|
+
rm file unless File.directory?(file)
|
98
|
+
}
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
|
19
103
|
#
|
20
104
|
# METRICS
|
21
105
|
#
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.1
|
1
|
+
0.0.3.1
|
data/demo/car.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'driving_physics/car'
|
2
|
+
require 'driving_physics/cockpit'
|
2
3
|
require 'driving_physics/imperial'
|
3
4
|
require 'driving_physics/cli'
|
4
5
|
|
@@ -10,13 +11,16 @@ puts env
|
|
10
11
|
tire = Tire.new(env)
|
11
12
|
motor = Motor.new(env)
|
12
13
|
gearbox = Gearbox.new(env)
|
13
|
-
powertrain = Powertrain.new(motor, gearbox)
|
14
|
+
powertrain = Powertrain.new(motor: motor, gearbox: gearbox)
|
14
15
|
car = Car.new(tire: tire, powertrain: powertrain) { |c|
|
15
16
|
c.body_mass = 850.0
|
16
17
|
c.frontal_area = 2.5
|
17
18
|
c.cd = 0.5
|
18
19
|
}
|
19
20
|
puts car
|
21
|
+
|
22
|
+
cockpit = Cockpit.new(car)
|
23
|
+
puts cockpit
|
20
24
|
CLI.pause
|
21
25
|
|
22
26
|
duration = 120
|
@@ -31,16 +35,20 @@ tire_theta = 0.0
|
|
31
35
|
|
32
36
|
crank_alpha = 0.0
|
33
37
|
crank_omega = 0.0
|
34
|
-
crank_theta = 0.0
|
35
38
|
|
36
39
|
start = Timer.now
|
37
40
|
paused = 0.0
|
38
41
|
num_ticks = duration * env.hz + 1
|
39
42
|
|
40
|
-
|
43
|
+
rev_match = :ok
|
41
44
|
phase = :ignition
|
45
|
+
gearbox.clutch = 0.0 # clutch in to fire up motor
|
42
46
|
flag = false
|
43
|
-
|
47
|
+
|
48
|
+
rpm, crank_torque = 0, 0
|
49
|
+
axle_torque, drive_force, net_force = 0, 0, 0
|
50
|
+
ar, rr, rf, ir = 0, 0, 0, 0
|
51
|
+
|
44
52
|
puts <<EOF
|
45
53
|
|
46
54
|
#
|
@@ -51,24 +59,15 @@ EOF
|
|
51
59
|
|
52
60
|
num_ticks.times { |i|
|
53
61
|
if phase == :ignition
|
54
|
-
# ignition phase
|
55
62
|
crank_alpha = motor.alpha(motor.starter_torque, omega: crank_omega)
|
56
63
|
crank_omega += crank_alpha * env.tick
|
57
|
-
crank_theta += crank_omega * env.tick
|
58
64
|
|
59
65
|
rpm = DrivingPhysics.rpm(crank_omega)
|
60
66
|
|
61
|
-
if
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
puts format("%d RPM %d Nm starter torque", rpm, motor.starter_torque)
|
66
|
-
puts
|
67
|
-
end
|
68
|
-
|
69
|
-
if rpm > motor.idle_rpm
|
70
|
-
car.gear = 1
|
71
|
-
car.throttle = 1.0
|
67
|
+
if rpm > motor.idle
|
68
|
+
flag = true
|
69
|
+
cockpit.gear = 1
|
70
|
+
cockpit.throttle_pedal = 1.0
|
72
71
|
phase = :running
|
73
72
|
|
74
73
|
puts <<EOF
|
@@ -79,23 +78,19 @@ num_ticks.times { |i|
|
|
79
78
|
|
80
79
|
EOF
|
81
80
|
end
|
82
|
-
elsif phase == :running
|
83
|
-
# track crank_alpha/omega/theta
|
84
|
-
|
85
|
-
# cut throttle after 60 s
|
86
|
-
car.throttle = 0 if i > 60 * env.hz and car.throttle == 1.0
|
87
|
-
|
81
|
+
elsif phase == :running or phase == :off_throttle
|
88
82
|
ar = car.air_force(speed)
|
89
83
|
rr = car.tire_rolling_force(tire_omega)
|
90
84
|
rf = car.tire_rotational_force(tire_omega)
|
91
85
|
|
92
86
|
# note, this fails if we're in neutral
|
93
|
-
|
87
|
+
drive_force = car.drive_force(rpm, axle_omega: tire_omega)
|
88
|
+
net_force = drive_force + ar + rr + rf
|
94
89
|
|
95
|
-
ir = car.tire_inertial_force(
|
96
|
-
|
90
|
+
ir = car.tire_inertial_force(net_force)
|
91
|
+
net_force += ir
|
97
92
|
|
98
|
-
acc = DrivingPhysics.acc(
|
93
|
+
acc = DrivingPhysics.acc(net_force, car.total_mass)
|
99
94
|
speed += acc * env.tick
|
100
95
|
dist += speed * env.tick
|
101
96
|
|
@@ -105,71 +100,89 @@ EOF
|
|
105
100
|
|
106
101
|
crank_alpha = tire_alpha / car.powertrain.gearbox.ratio
|
107
102
|
crank_omega += crank_alpha * env.tick
|
108
|
-
crank_theta += crank_omega * env.tick
|
109
|
-
|
110
|
-
axle_torque = car.powertrain.axle_torque(rpm)
|
111
|
-
crank_torque = car.powertrain.motor.torque(rpm)
|
112
|
-
|
113
|
-
if flag or (i < 5000 and i % 100 == 0) or (i % 1000 == 0)
|
114
|
-
puts Timer.display(i)
|
115
|
-
puts format(" Tire: %.1f r/s/s %.2f r/s %.3f r",
|
116
|
-
tire_alpha, tire_omega, tire_theta)
|
117
|
-
puts format(" Car: %.1f m/s/s %.2f m/s %.3f m (%.1f MPH)",
|
118
|
-
acc, speed, dist, Imperial.mph(speed))
|
119
|
-
puts format(" Motor: %d RPM %.1f Nm", rpm, crank_torque)
|
120
|
-
puts format(" Axle: %.1f Nm (%d N) Net Force: %.1f N",
|
121
|
-
axle_torque, car.drive_force(rpm), force)
|
122
|
-
puts "Resist: " + format(%w[Air Roll Spin Inertial].map { |s|
|
123
|
-
"#{s}: %.1f N"
|
124
|
-
}.join(' '), ar, rr, rf, ir)
|
125
|
-
puts
|
126
|
-
flag = false
|
127
|
-
end
|
128
103
|
|
129
104
|
# tire_omega determines new rpm
|
130
|
-
new_rpm =
|
131
|
-
|
105
|
+
new_rpm = gearbox.crank_rpm(tire_omega, crank_rpm: rpm)
|
106
|
+
new_rev_match, clutch, proportion = Cockpit.rev_match(rpm, new_rpm)
|
132
107
|
|
133
|
-
if
|
108
|
+
if new_rev_match != rev_match
|
134
109
|
flag = true
|
135
|
-
puts format("
|
136
|
-
|
137
|
-
clutch
|
138
|
-
|
110
|
+
puts format("Rev Match: [%s] %d RPM is %.1f%% from %d RPM",
|
111
|
+
new_rev_match, new_rpm, proportion * 100, rpm)
|
112
|
+
# puts format("Recommend clutch to %.1f%%", clutch * 100)
|
113
|
+
rev_match = new_rev_match
|
139
114
|
end
|
140
115
|
|
141
|
-
|
142
|
-
|
143
|
-
rpm = new_rpm
|
144
|
-
when :mismatch
|
116
|
+
clutch_diff = clutch - gearbox.clutch
|
117
|
+
if clutch_diff.abs > 0.1
|
145
118
|
flag = true
|
146
|
-
puts
|
147
|
-
|
148
|
-
puts '#'
|
149
|
-
puts
|
150
|
-
rpm = new_rpm
|
119
|
+
puts format("Clutch: %.1f%% Recommended Clutch: %.1f%%",
|
120
|
+
gearbox.clutch * 100, clutch * 100)
|
151
121
|
end
|
152
|
-
|
153
|
-
|
122
|
+
# the clutch pedal will reflect this change
|
123
|
+
gearbox.clutch += clutch_diff * 0.5
|
124
|
+
|
125
|
+
# update the motor RPM based on new clutch
|
126
|
+
new_rpm = gearbox.crank_rpm(tire_omega, crank_rpm: rpm)
|
127
|
+
rpm = new_rpm if new_rpm > motor.idle
|
128
|
+
|
129
|
+
new_gear = cockpit.choose_gear(rpm)
|
130
|
+
if new_gear != cockpit.gear
|
131
|
+
flag = true
|
132
|
+
puts "Gear Change: #{new_gear}"
|
133
|
+
cockpit.gear = new_gear
|
134
|
+
end
|
135
|
+
|
136
|
+
# cut throttle after 60 s
|
137
|
+
if i > 60 * env.hz and car.throttle == 1.0
|
154
138
|
flag = true
|
155
|
-
|
156
|
-
|
157
|
-
paused += CLI.pause
|
139
|
+
phase = :off_throttle
|
140
|
+
cockpit.throttle_pedal = 0
|
158
141
|
end
|
159
142
|
|
160
143
|
# maintain idle when revs drop
|
161
|
-
if
|
144
|
+
if cockpit.throttle_pedal == 0 and rpm < motor.idle
|
162
145
|
phase = :idling
|
163
146
|
car.gear = 0
|
164
|
-
paused += CLI.pause
|
165
147
|
end
|
166
148
|
|
149
|
+
print "===\n\n" if flag
|
167
150
|
|
168
151
|
elsif phase == :idling
|
169
|
-
# fake
|
170
|
-
rpm = motor.
|
152
|
+
# fake; exit
|
153
|
+
rpm = motor.idle
|
171
154
|
break
|
172
155
|
end
|
156
|
+
|
157
|
+
if flag or (i < 5000 and i % 100 == 0) or (i % 1000 == 0)
|
158
|
+
puts Timer.display(ms: i)
|
159
|
+
puts format(" Phase: %s", phase)
|
160
|
+
puts format(" Tire: %.3f r/s/s %.2f r/s %.1f r",
|
161
|
+
tire_alpha, tire_omega, tire_theta)
|
162
|
+
puts format(" Car: %.3f m/s/s %.2f m/s %.1f m (%.1f MPH)",
|
163
|
+
acc, speed, dist, Imperial.mph(speed))
|
164
|
+
if phase == :ignition
|
165
|
+
puts format(" Motor: %d RPM Starter: %d Nm Friction: %.1f Nm",
|
166
|
+
rpm, motor.starter_torque, motor.friction(crank_omega))
|
167
|
+
else
|
168
|
+
crank_torque = car.powertrain.motor.torque(rpm)
|
169
|
+
puts format(" Motor: %d RPM %.1f Nm Friction: %.1f Nm",
|
170
|
+
rpm, crank_torque, motor.friction(crank_omega))
|
171
|
+
end
|
172
|
+
puts format("Gearbox: %s", gearbox.inputs)
|
173
|
+
if phase != :ignition
|
174
|
+
axle_torque = car.powertrain.axle_torque(rpm, axle_omega: tire_omega)
|
175
|
+
puts format(" Axle: %.1f Nm (%d N) Net Force: %.1f N",
|
176
|
+
axle_torque, drive_force, net_force)
|
177
|
+
end
|
178
|
+
puts " Resist: " + format(%w[Air Roll Spin Inertial].map { |s|
|
179
|
+
"#{s}: %.1f N"
|
180
|
+
}.join(' '), ar, rr, rf, ir)
|
181
|
+
puts cockpit
|
182
|
+
puts
|
183
|
+
paused += CLI.pause if flag
|
184
|
+
flag = false
|
185
|
+
end
|
173
186
|
}
|
174
187
|
|
175
188
|
puts Timer.summary(start, num_ticks, paused)
|
data/demo/gearbox.rb
CHANGED
@@ -21,10 +21,11 @@ alpha = 0.0
|
|
21
21
|
omega = 0.0
|
22
22
|
theta = 0.0
|
23
23
|
|
24
|
+
num_ticks = duration * env.hz + 1
|
24
25
|
start = Timer.now
|
25
|
-
paused = 0
|
26
|
+
paused = 0.0
|
26
27
|
|
27
|
-
|
28
|
+
num_ticks.times { |i|
|
28
29
|
# just for info, not used in the simulation
|
29
30
|
friction = gearbox.spinner.rotating_friction(omega)
|
30
31
|
|
@@ -41,7 +42,7 @@ paused = 0
|
|
41
42
|
(i < 100 and i % 10 == 0) or
|
42
43
|
(i < 1000 and i % 100 == 0) or
|
43
44
|
i % 1000 == 0
|
44
|
-
puts Timer.display(i)
|
45
|
+
puts Timer.display(ms: i)
|
45
46
|
puts format("RPM %d Torque: %.3f Nm (%d Nm) Friction: %.3f Nm",
|
46
47
|
DrivingPhysics.rpm(omega), net_torque, torque, friction)
|
47
48
|
puts format("%.1f rad %.1f rad/s %.1f rad/s/s", theta, omega, alpha)
|
data/demo/motor.rb
CHANGED
@@ -48,7 +48,7 @@ motor.throttle = 1.0
|
|
48
48
|
puts
|
49
49
|
puts "Now, the simulation begins..."
|
50
50
|
puts "---"
|
51
|
-
puts "* Spin the motor up to #{motor.
|
51
|
+
puts "* Spin the motor up to #{motor.idle} RPM with the starter motor."
|
52
52
|
puts "* Rev it up with the throttle."
|
53
53
|
puts "* Let it die."
|
54
54
|
CLI.pause
|
@@ -82,15 +82,14 @@ num_ticks.times { |i|
|
|
82
82
|
omega += alpha * env.tick
|
83
83
|
theta += omega * env.tick
|
84
84
|
|
85
|
-
|
85
|
+
# prevent silly oscillations due to ticks or tiny floating point errors
|
86
|
+
omega = 0 if omega < 0.0001
|
86
87
|
|
87
|
-
|
88
|
-
omega = 0 if omega < 0.00001
|
88
|
+
net_torque = motor.implied_torque(alpha)
|
89
89
|
rpm = DrivingPhysics.rpm(omega)
|
90
|
-
|
91
90
|
power = DrivingPhysics.power(net_torque, omega)
|
92
91
|
|
93
|
-
if rpm > motor.
|
92
|
+
if rpm > motor.idle and status == :ignition
|
94
93
|
status = :running
|
95
94
|
flag = true
|
96
95
|
end
|
@@ -107,10 +106,13 @@ num_ticks.times { |i|
|
|
107
106
|
flag = true
|
108
107
|
end
|
109
108
|
|
109
|
+
# we don't need no PID control
|
110
110
|
if status == :idling
|
111
|
-
if rpm
|
111
|
+
if rpm.round <= 999
|
112
112
|
motor.throttle += (1.0 - motor.throttle) * 0.005
|
113
|
-
elsif rpm
|
113
|
+
elsif rpm.round == 1000
|
114
|
+
motor.throttle += (rand - 0.5) * 0.0005
|
115
|
+
else
|
114
116
|
motor.throttle -= motor.throttle * 0.005
|
115
117
|
end
|
116
118
|
end
|
@@ -122,7 +124,7 @@ num_ticks.times { |i|
|
|
122
124
|
(i < 10_000 and i % 500 == 0) or
|
123
125
|
(i % 5000 == 0) or
|
124
126
|
(status == :idling and i % 100 == 0)
|
125
|
-
puts Timer.display(i)
|
127
|
+
puts Timer.display(ms: i)
|
126
128
|
puts format("Throttle: %.1f%%", motor.throttle * 100)
|
127
129
|
puts format("%d RPM %.1f Nm (%d Nm) %.1f kW Friction: %.1f Nm",
|
128
130
|
DrivingPhysics.rpm(omega),
|
@@ -130,7 +132,7 @@ num_ticks.times { |i|
|
|
130
132
|
torque,
|
131
133
|
power / 1000,
|
132
134
|
motor.spinner.rotating_friction(omega))
|
133
|
-
puts format("
|
135
|
+
puts format("%.3f rad/s/s %.2f rad/s %.1f rad", alpha, omega, theta)
|
134
136
|
puts
|
135
137
|
|
136
138
|
paused += CLI.pause if flag
|
data/demo/mruby/car.rb
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
|
2
|
+
include DrivingPhysics
|
3
|
+
|
4
|
+
env = Environment.new
|
5
|
+
puts env
|
6
|
+
|
7
|
+
tire = Tire.new(env)
|
8
|
+
motor = Motor.new(env)
|
9
|
+
gearbox = Gearbox.new(env)
|
10
|
+
powertrain = Powertrain.new(motor: motor, gearbox: gearbox)
|
11
|
+
car = Car.new(tire: tire, powertrain: powertrain) { |c|
|
12
|
+
c.body_mass = 850.0
|
13
|
+
c.frontal_area = 2.5
|
14
|
+
c.cd = 0.5
|
15
|
+
}
|
16
|
+
puts car
|
17
|
+
|
18
|
+
cockpit = Cockpit.new(car)
|
19
|
+
puts cockpit
|
20
|
+
CLI.pause
|
21
|
+
|
22
|
+
duration = 120
|
23
|
+
|
24
|
+
acc = 0.0
|
25
|
+
speed = 0.0
|
26
|
+
dist = 0.0
|
27
|
+
|
28
|
+
tire_alpha = 0.0
|
29
|
+
tire_omega = 0.0
|
30
|
+
tire_theta = 0.0
|
31
|
+
|
32
|
+
crank_alpha = 0.0
|
33
|
+
crank_omega = 0.0
|
34
|
+
|
35
|
+
start = Timer.now
|
36
|
+
paused = 0.0
|
37
|
+
num_ticks = duration * env.hz + 1
|
38
|
+
|
39
|
+
rev_match = :ok
|
40
|
+
phase = :ignition
|
41
|
+
gearbox.clutch = 0.0 # clutch in to fire up motor
|
42
|
+
flag = false
|
43
|
+
|
44
|
+
rpm, crank_torque = 0, 0
|
45
|
+
axle_torque, drive_force, net_force = 0, 0, 0
|
46
|
+
ar, rr, rf, ir = 0, 0, 0, 0
|
47
|
+
|
48
|
+
puts <<EOF
|
49
|
+
|
50
|
+
#
|
51
|
+
# IGNITION
|
52
|
+
#
|
53
|
+
|
54
|
+
EOF
|
55
|
+
|
56
|
+
num_ticks.times { |i|
|
57
|
+
if phase == :ignition
|
58
|
+
crank_alpha = motor.alpha(motor.starter_torque, omega: crank_omega)
|
59
|
+
crank_omega += crank_alpha * env.tick
|
60
|
+
|
61
|
+
rpm = DrivingPhysics.rpm(crank_omega)
|
62
|
+
|
63
|
+
if rpm > motor.idle
|
64
|
+
flag = true
|
65
|
+
cockpit.gear = 1
|
66
|
+
cockpit.throttle_pedal = 1.0
|
67
|
+
phase = :running
|
68
|
+
|
69
|
+
puts <<EOF
|
70
|
+
|
71
|
+
#
|
72
|
+
# RUNNING
|
73
|
+
#
|
74
|
+
|
75
|
+
EOF
|
76
|
+
end
|
77
|
+
elsif phase == :running or phase == :off_throttle
|
78
|
+
ar = car.air_force(speed)
|
79
|
+
rr = car.tire_rolling_force(tire_omega)
|
80
|
+
rf = car.tire_rotational_force(tire_omega)
|
81
|
+
|
82
|
+
# note, this fails if we're in neutral
|
83
|
+
drive_force = car.drive_force(rpm, axle_omega: tire_omega)
|
84
|
+
net_force = drive_force + ar + rr + rf
|
85
|
+
|
86
|
+
ir = car.tire_inertial_force(net_force)
|
87
|
+
net_force += ir
|
88
|
+
|
89
|
+
acc = DrivingPhysics.acc(net_force, car.total_mass)
|
90
|
+
speed += acc * env.tick
|
91
|
+
dist += speed * env.tick
|
92
|
+
|
93
|
+
tire_alpha = acc / car.tire.radius
|
94
|
+
tire_omega += tire_alpha * env.tick
|
95
|
+
tire_theta += tire_omega * env.tick
|
96
|
+
|
97
|
+
crank_alpha = tire_alpha / car.powertrain.gearbox.ratio
|
98
|
+
crank_omega += crank_alpha * env.tick
|
99
|
+
|
100
|
+
# tire_omega determines new rpm
|
101
|
+
new_rpm = gearbox.crank_rpm(tire_omega, crank_rpm: rpm)
|
102
|
+
new_rev_match, clutch, proportion = Cockpit.rev_match(rpm, new_rpm)
|
103
|
+
|
104
|
+
if new_rev_match != rev_match
|
105
|
+
flag = true
|
106
|
+
puts format("Rev Match: [%s] %d RPM is %.1f%% from %d RPM",
|
107
|
+
new_rev_match, new_rpm, proportion * 100, rpm)
|
108
|
+
# puts format("Recommend clutch to %.1f%%", clutch * 100)
|
109
|
+
rev_match = new_rev_match
|
110
|
+
end
|
111
|
+
|
112
|
+
clutch_diff = clutch - gearbox.clutch
|
113
|
+
if clutch_diff.abs > 0.1
|
114
|
+
flag = true
|
115
|
+
puts format("Clutch: %.1f%% Recommended Clutch: %.1f%%",
|
116
|
+
gearbox.clutch * 100, clutch * 100)
|
117
|
+
end
|
118
|
+
# the clutch pedal will reflect this change
|
119
|
+
gearbox.clutch += clutch_diff * 0.5
|
120
|
+
|
121
|
+
# update the motor RPM based on new clutch
|
122
|
+
new_rpm = gearbox.crank_rpm(tire_omega, crank_rpm: rpm)
|
123
|
+
rpm = new_rpm if new_rpm > motor.idle
|
124
|
+
|
125
|
+
new_gear = cockpit.choose_gear(rpm)
|
126
|
+
if new_gear != cockpit.gear
|
127
|
+
flag = true
|
128
|
+
puts "Gear Change: #{new_gear}"
|
129
|
+
cockpit.gear = new_gear
|
130
|
+
end
|
131
|
+
|
132
|
+
# cut throttle after 60 s
|
133
|
+
if i > 60 * env.hz and car.throttle == 1.0
|
134
|
+
flag = true
|
135
|
+
phase = :off_throttle
|
136
|
+
cockpit.throttle_pedal = 0
|
137
|
+
end
|
138
|
+
|
139
|
+
# maintain idle when revs drop
|
140
|
+
if cockpit.throttle_pedal == 0 and rpm < motor.idle
|
141
|
+
phase = :idling
|
142
|
+
car.gear = 0
|
143
|
+
end
|
144
|
+
|
145
|
+
print "===\n\n" if flag
|
146
|
+
|
147
|
+
elsif phase == :idling
|
148
|
+
# fake; exit
|
149
|
+
rpm = motor.idle
|
150
|
+
break
|
151
|
+
end
|
152
|
+
|
153
|
+
if flag or (i < 5000 and i % 100 == 0) or (i % 1000 == 0)
|
154
|
+
puts Timer.display(ms: i)
|
155
|
+
puts format(" Phase: %s", phase)
|
156
|
+
puts format(" Tire: %.3f r/s/s %.2f r/s %.1f r",
|
157
|
+
tire_alpha, tire_omega, tire_theta)
|
158
|
+
puts format(" Car: %.3f m/s/s %.2f m/s %.1f m (%.1f MPH)",
|
159
|
+
acc, speed, dist, Imperial.mph(speed))
|
160
|
+
if phase == :ignition
|
161
|
+
puts format(" Motor: %d RPM Starter: %d Nm Friction: %.1f Nm",
|
162
|
+
rpm, motor.starter_torque, motor.friction(crank_omega))
|
163
|
+
else
|
164
|
+
crank_torque = car.powertrain.motor.torque(rpm)
|
165
|
+
puts format(" Motor: %d RPM %.1f Nm Friction: %.1f Nm",
|
166
|
+
rpm, crank_torque, motor.friction(crank_omega))
|
167
|
+
end
|
168
|
+
puts format("Gearbox: %s", gearbox.inputs)
|
169
|
+
if phase != :ignition
|
170
|
+
axle_torque = car.powertrain.axle_torque(rpm, axle_omega: tire_omega)
|
171
|
+
puts format(" Axle: %.1f Nm (%d N) Net Force: %.1f N",
|
172
|
+
axle_torque, drive_force, net_force)
|
173
|
+
end
|
174
|
+
puts " Resist: " + format(%w[Air Roll Spin Inertial].map { |s|
|
175
|
+
"#{s}: %.1f N"
|
176
|
+
}.join(' '), ar, rr, rf, ir)
|
177
|
+
puts cockpit
|
178
|
+
puts
|
179
|
+
paused += CLI.pause if flag
|
180
|
+
flag = false
|
181
|
+
end
|
182
|
+
}
|
183
|
+
|
184
|
+
puts Timer.summary(start, num_ticks, paused)
|