driving_physics 0.0.1.2 → 0.0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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)
|