trixter 1.0.0
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.
- data/bin/trixter +11 -0
- data/lib/trixter.rb +1 -0
- data/lib/trixter/difficulties.rb +20 -0
- data/lib/trixter/event.rb +83 -0
- data/lib/trixter/handler.rb +177 -0
- data/lib/trixter/trixter.rb +144 -0
- metadata +52 -0
data/bin/trixter
ADDED
data/lib/trixter.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'trixter/trixter'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# These rates could be dynamically calculated
|
2
|
+
Difficulties = [
|
3
|
+
"6a013d5b7974",
|
4
|
+
"6a114d6b89d4",
|
5
|
+
"6a215d7b99f4",
|
6
|
+
"6a316d8ba914",
|
7
|
+
"6a417d9bb974",
|
8
|
+
"6a518dabc9d4",
|
9
|
+
"6a619dbbd9f4",
|
10
|
+
"6a71adcbe994",
|
11
|
+
"6a81bddbf974",
|
12
|
+
"6a91cdeb0ad7",
|
13
|
+
"6aa1ddfb1af7",
|
14
|
+
"6ab1ed0c2a10",
|
15
|
+
"6ac1fd1c3a70",
|
16
|
+
"6ad10e2c4ad3",
|
17
|
+
"6ae11e3c5af3",
|
18
|
+
"6af12e4c6a93",
|
19
|
+
"6aff3c5a788b"
|
20
|
+
]
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
class Event
|
4
|
+
attr_reader :time
|
5
|
+
|
6
|
+
def initialize(bytes)
|
7
|
+
@bytes = bytes
|
8
|
+
@time = Time.now.to_f
|
9
|
+
end
|
10
|
+
|
11
|
+
def crankPosition
|
12
|
+
return @bytes[6..7]
|
13
|
+
end
|
14
|
+
|
15
|
+
def rightBrakePressure
|
16
|
+
return @bytes[8..9]
|
17
|
+
end
|
18
|
+
|
19
|
+
def leftBrakePressure
|
20
|
+
return @bytes[10..11]
|
21
|
+
end
|
22
|
+
|
23
|
+
def leftGearUp
|
24
|
+
return @bytes[16] == '7'
|
25
|
+
end
|
26
|
+
|
27
|
+
def leftGearDown
|
28
|
+
return @bytes[18] == '7'
|
29
|
+
end
|
30
|
+
|
31
|
+
def leftControlLeft
|
32
|
+
return @bytes[16] == 'e'
|
33
|
+
end
|
34
|
+
|
35
|
+
def leftControlRight
|
36
|
+
return @bytes[16] == 'b'
|
37
|
+
end
|
38
|
+
|
39
|
+
def leftControlDown
|
40
|
+
return @bytes[17] == 'b'
|
41
|
+
end
|
42
|
+
|
43
|
+
def leftControlUp
|
44
|
+
return @bytes[17] == 'e'
|
45
|
+
end
|
46
|
+
|
47
|
+
def rightControlLeft
|
48
|
+
return @bytes[17] == 'd'
|
49
|
+
end
|
50
|
+
|
51
|
+
def rightControlRight
|
52
|
+
return @bytes[17] == '7'
|
53
|
+
end
|
54
|
+
|
55
|
+
def rightControlUp
|
56
|
+
return @bytes[16] == 'd'
|
57
|
+
end
|
58
|
+
|
59
|
+
def rightGearUp
|
60
|
+
return @bytes[18] == 'd'
|
61
|
+
end
|
62
|
+
|
63
|
+
def rightGearDown
|
64
|
+
return @bytes[18] == 'b'
|
65
|
+
end
|
66
|
+
|
67
|
+
def seated
|
68
|
+
return @bytes[19] == '7'
|
69
|
+
end
|
70
|
+
|
71
|
+
def flywheelRotationTime
|
72
|
+
return @bytes[24..27]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class CrankChange
|
77
|
+
attr_accessor :ticks, :time, :elapsed
|
78
|
+
def initialize(ticks, time, elapsed)
|
79
|
+
@ticks = ticks
|
80
|
+
@time = time
|
81
|
+
@elapsed = elapsed
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# This handler, by default, does no more than log
|
2
|
+
class EventHandler
|
3
|
+
def initialize
|
4
|
+
@current_event = nil
|
5
|
+
@crank_changes = []
|
6
|
+
end
|
7
|
+
|
8
|
+
def handleEvent(event)
|
9
|
+
# We throw out initial state
|
10
|
+
if @current_event == nil
|
11
|
+
@current_event = event
|
12
|
+
return
|
13
|
+
end
|
14
|
+
|
15
|
+
# Process boolean-on events (we only care when they are 'on')
|
16
|
+
if event.leftGearUp and not @current_event.leftGearUp
|
17
|
+
leftGearUp
|
18
|
+
end
|
19
|
+
if event.leftGearDown and not @current_event.leftGearDown
|
20
|
+
leftGearDown
|
21
|
+
end
|
22
|
+
if event.rightGearUp and not @current_event.rightGearUp
|
23
|
+
rightGearUp
|
24
|
+
end
|
25
|
+
if event.rightGearDown and not @current_event.rightGearDown
|
26
|
+
rightGearDown
|
27
|
+
end
|
28
|
+
if event.leftControlUp and not @current_event.leftControlUp
|
29
|
+
leftControlUp
|
30
|
+
end
|
31
|
+
if event.leftControlDown and not @current_event.leftControlDown
|
32
|
+
leftControlDown
|
33
|
+
end
|
34
|
+
if event.leftControlLeft and not @current_event.leftControlLeft
|
35
|
+
leftControlLeft
|
36
|
+
end
|
37
|
+
if event.leftControlRight and not @current_event.leftControlRight
|
38
|
+
leftControlRight
|
39
|
+
end
|
40
|
+
if event.rightControlLeft and not @current_event.rightControlLeft
|
41
|
+
rightControlLeft
|
42
|
+
end
|
43
|
+
if event.rightControlRight and not @current_event.rightControlRight
|
44
|
+
rightControlRight
|
45
|
+
end
|
46
|
+
if event.rightControlUp and not @current_event.rightControlUp
|
47
|
+
rightControlUp
|
48
|
+
end
|
49
|
+
|
50
|
+
# Process scaled events
|
51
|
+
if event.leftBrakePressure != @current_event.leftBrakePressure
|
52
|
+
leftBrakeChanged(event.leftBrakePressure)
|
53
|
+
end
|
54
|
+
if event.rightBrakePressure != @current_event.rightBrakePressure
|
55
|
+
rightBrakeChanged(event.rightBrakePressure)
|
56
|
+
end
|
57
|
+
if event.crankPosition != @current_event.crankPosition
|
58
|
+
crankPositionChanged(event)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Seated or not
|
62
|
+
if event.seated != @current_event.seated
|
63
|
+
seatingChanged(event.seated)
|
64
|
+
end
|
65
|
+
|
66
|
+
@current_event = event
|
67
|
+
end
|
68
|
+
|
69
|
+
def seatingChanged(seated)
|
70
|
+
if seated
|
71
|
+
puts "Rider is seated"
|
72
|
+
else
|
73
|
+
puts "Rider is standing"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def crankPositionChanged(event)
|
78
|
+
if @last_crank_change == nil
|
79
|
+
@last_crank_change = event.time
|
80
|
+
return
|
81
|
+
end
|
82
|
+
|
83
|
+
forward = false
|
84
|
+
ticks = 0
|
85
|
+
past_position = @current_event.crankPosition.hex
|
86
|
+
current_position = event.crankPosition.hex
|
87
|
+
if past_position < current_position
|
88
|
+
ticks = current_position - past_position
|
89
|
+
if ticks < 30
|
90
|
+
forward = true
|
91
|
+
else
|
92
|
+
ticks = 60 - ticks
|
93
|
+
end
|
94
|
+
else
|
95
|
+
ticks = past_position - current_position
|
96
|
+
if ticks > 30
|
97
|
+
ticks = 60 - ticks
|
98
|
+
forward = true
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
change = CrankChange.new(ticks, event.time, event.time - @last_crank_change)
|
103
|
+
@crank_changes << change
|
104
|
+
to_delete = []
|
105
|
+
while (event.time - @crank_changes.first.time) > 0.4
|
106
|
+
@crank_changes.delete_at(0)
|
107
|
+
end
|
108
|
+
|
109
|
+
smoothed_ticks = 0
|
110
|
+
for change in @crank_changes
|
111
|
+
smoothed_ticks += change.ticks
|
112
|
+
end
|
113
|
+
|
114
|
+
first_change = @crank_changes.first
|
115
|
+
|
116
|
+
if event.time - first_change.time == 0
|
117
|
+
puts "Crank RPM: 0"
|
118
|
+
else
|
119
|
+
rpms = (smoothed_ticks/60.0) * (60.0/(event.time - first_change.time))
|
120
|
+
puts "Crank RPM: #{rpms}"
|
121
|
+
end
|
122
|
+
|
123
|
+
@last_crank_change = event.time
|
124
|
+
end
|
125
|
+
|
126
|
+
def leftBrakeChanged(strength)
|
127
|
+
puts "Left brake engaged at #{strength}"
|
128
|
+
end
|
129
|
+
|
130
|
+
def rightBrakeChanged(strength)
|
131
|
+
puts "Right brake engaged at #{strength}"
|
132
|
+
end
|
133
|
+
|
134
|
+
def leftGearUp
|
135
|
+
puts "Left gear up!"
|
136
|
+
end
|
137
|
+
|
138
|
+
def leftGearDown
|
139
|
+
puts "Left gear down!"
|
140
|
+
end
|
141
|
+
|
142
|
+
def rightGearUp
|
143
|
+
puts "Right gear up!"
|
144
|
+
end
|
145
|
+
|
146
|
+
def rightGearDown
|
147
|
+
puts "Right gear down!"
|
148
|
+
end
|
149
|
+
|
150
|
+
def leftControlLeft
|
151
|
+
puts "Left control left pushed"
|
152
|
+
end
|
153
|
+
|
154
|
+
def leftControlRight
|
155
|
+
puts "Left control right pushed"
|
156
|
+
end
|
157
|
+
|
158
|
+
def leftControlUp
|
159
|
+
puts "Left control up pushed"
|
160
|
+
end
|
161
|
+
|
162
|
+
def leftControlDown
|
163
|
+
puts "Left control down pushed"
|
164
|
+
end
|
165
|
+
|
166
|
+
def rightControlLeft
|
167
|
+
puts "Right control left pushed"
|
168
|
+
end
|
169
|
+
|
170
|
+
def rightControlRight
|
171
|
+
puts "Right control right pushed"
|
172
|
+
end
|
173
|
+
|
174
|
+
def rightControlUp
|
175
|
+
puts "Right control up pushed"
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'serialport'
|
4
|
+
require 'trixter/handler'
|
5
|
+
require 'trixter/difficulties'
|
6
|
+
require 'trixter/event'
|
7
|
+
|
8
|
+
class Trixter < EventHandler
|
9
|
+
def initialize(port)
|
10
|
+
super()
|
11
|
+
|
12
|
+
@port = SerialPort.new(port, 115200, 8, 1)
|
13
|
+
@status_thread = nil
|
14
|
+
@control_thread = nil
|
15
|
+
@current_difficulty = 0
|
16
|
+
@saved_difficulty = 0
|
17
|
+
@right_brake = false
|
18
|
+
@left_brake = false
|
19
|
+
|
20
|
+
@should_run = false
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
return if @should_run or @status_thread != nil
|
25
|
+
return if @should_run or @control_thread != nil
|
26
|
+
|
27
|
+
@should_run = true
|
28
|
+
@status_thread = Thread.new do
|
29
|
+
status
|
30
|
+
end
|
31
|
+
@control_thread = Thread.new do
|
32
|
+
control
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def stop
|
37
|
+
@should_run = false
|
38
|
+
end
|
39
|
+
|
40
|
+
def rightGearUp
|
41
|
+
@current_difficulty += 1
|
42
|
+
if @current_difficulty >= Difficulties.size
|
43
|
+
@current_difficulty = Difficulties.size - 1
|
44
|
+
end
|
45
|
+
puts "Increased difficulty to level #{@current_difficulty}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def rightGearDown
|
49
|
+
@current_difficulty -= 1
|
50
|
+
if @current_difficulty < 0
|
51
|
+
@current_difficulty = 0
|
52
|
+
end
|
53
|
+
puts "Decreased difficulty to level #{@current_difficulty}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def leftBrakeChanged(strength)
|
57
|
+
pressure = 240 - strength.hex
|
58
|
+
if pressure > 10
|
59
|
+
if !@left_brake and !@right_brake
|
60
|
+
puts "Left brake engaged"
|
61
|
+
@saved_difficulty = @current_difficulty
|
62
|
+
end
|
63
|
+
|
64
|
+
@left_brake = true
|
65
|
+
applyBrake(pressure)
|
66
|
+
else
|
67
|
+
if @left_brake
|
68
|
+
puts "Left brake disengaged"
|
69
|
+
# restore selected difficulty
|
70
|
+
@left_brake = false
|
71
|
+
if !@right_brake
|
72
|
+
@current_difficulty = @saved_difficulty
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def rightBrakeChanged(strength)
|
79
|
+
pressure = 240 - strength.hex
|
80
|
+
if pressure > 10
|
81
|
+
if !@left_brake and !@right_brake
|
82
|
+
puts "Right brake engaged"
|
83
|
+
@saved_difficulty = @current_difficulty
|
84
|
+
end
|
85
|
+
|
86
|
+
@right_brake = true
|
87
|
+
applyBrake(pressure)
|
88
|
+
else
|
89
|
+
if @right_brake
|
90
|
+
puts "Right brake disengaged"
|
91
|
+
# restore selected difficulty
|
92
|
+
@right_brake = false
|
93
|
+
if !@left_brake
|
94
|
+
@current_difficulty = @saved_difficulty
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def applyBrake(pressure)
|
101
|
+
span = Difficulties.size - @saved_difficulty - 1
|
102
|
+
units = 100.0/span
|
103
|
+
new_difficulty = (pressure/units).to_i + @saved_difficulty
|
104
|
+
@current_difficulty = [new_difficulty, Difficulties.size - 1].min
|
105
|
+
end
|
106
|
+
|
107
|
+
def crankPositionChanged(event)
|
108
|
+
# pass
|
109
|
+
end
|
110
|
+
|
111
|
+
def status
|
112
|
+
while @should_run
|
113
|
+
raw_event = ""
|
114
|
+
while true
|
115
|
+
raw_event << @port.read(1).strip
|
116
|
+
if raw_event.size == 1
|
117
|
+
raw_event = "" unless raw_event[0] == '6'
|
118
|
+
elsif raw_event.size == 2
|
119
|
+
raw_event == "" unless raw_event[1] == 'a'
|
120
|
+
elsif raw_event.size > 2
|
121
|
+
raw_event == "" unless raw_event[0..1] == '6a'
|
122
|
+
end
|
123
|
+
break if raw_event.size == 32
|
124
|
+
end
|
125
|
+
handleEvent(Event.new(raw_event))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def control
|
130
|
+
while @should_run
|
131
|
+
sleep 0.01
|
132
|
+
@port.write([Difficulties[@current_difficulty]].pack("H*"))
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def join
|
137
|
+
if @status_thread != nil
|
138
|
+
@status_thread.join
|
139
|
+
end
|
140
|
+
if @control_thread != nil
|
141
|
+
@control_thread.join
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
metadata
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: trixter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tom Metge
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-10-13 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Turns the Trixter Dream Bike into a real training platform
|
15
|
+
email: tom@accident-prone.com
|
16
|
+
executables:
|
17
|
+
- trixter
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/trixter.rb
|
22
|
+
- lib/trixter/difficulties.rb
|
23
|
+
- lib/trixter/event.rb
|
24
|
+
- lib/trixter/handler.rb
|
25
|
+
- lib/trixter/trixter.rb
|
26
|
+
- bin/trixter
|
27
|
+
homepage: http://www.accident-prone.com
|
28
|
+
licenses:
|
29
|
+
- MIT
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
requirements: []
|
47
|
+
rubyforge_project:
|
48
|
+
rubygems_version: 1.8.23
|
49
|
+
signing_key:
|
50
|
+
specification_version: 3
|
51
|
+
summary: Trixter Dream Bike Controller
|
52
|
+
test_files: []
|