trixter 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'trixter'
4
+
5
+ port = ARGV[0]
6
+ if port == nil
7
+ port = '/dev/tty.usbserial'
8
+ end
9
+ trix = Trixter.new(port)
10
+ trix.run
11
+ trix.join
@@ -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: []