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.
@@ -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: []