sgslib 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/sgs/mission.rb CHANGED
@@ -32,6 +32,10 @@
32
32
  # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
33
  #
34
34
  # ABSTRACT
35
+ # Mostly this class is for the overall daemon running on Mother. It takes
36
+ # care of listening for GPS updates and adjusting the sailing route,
37
+ # accordingly. It is responsible for keeping MissionStatus up to
38
+ # date. The actual mission information is stored in a YAML file.
35
39
  #
36
40
 
37
41
  ##
@@ -45,36 +49,11 @@ module SGS
45
49
  class Mission
46
50
  attr_accessor :title, :url, :description
47
51
  attr_accessor :launch_site, :launch_location
48
- attr_accessor :attractors, :repellors, :track
49
- attr_accessor :where, :time, :course, :distance
50
-
51
- #
52
- # Main daemon function (called from executable)
53
- def self.daemon
54
- loop do
55
- sleep 300
56
- end
57
- end
58
-
59
- #
60
- # Load a new mission from the missions directory.
61
- def self.file_load(filename)
62
- parse YAML.load(File.open(filename))
63
- end
64
-
65
- #
66
- # Load a new mission from the missions directory.
67
- def self.parse(data)
68
- mission = new
69
- mission.parse(data)
70
- mission
71
- end
52
+ attr_accessor :attractors, :repellors, :status
72
53
 
73
54
  #
74
55
  # Create the attractors and repellors as well as the track array
75
- # and other items. @where is our current TrackPoint, @current_wpt is
76
- # the waypoint we're working (-1 if none), @course is the heading/speed
77
- # the boat is on.
56
+ # and other items.
78
57
  def initialize
79
58
  @title = nil
80
59
  @url = nil
@@ -83,166 +62,60 @@ module SGS
83
62
  @launch_location = nil
84
63
  @attractors = []
85
64
  @repellors = []
86
- @track = nil
87
- @start_time = @time = nil
88
- @where = nil
89
- @course = Course.new
90
- @distance = 0
91
- @swing = 60
65
+ @status = MissionStatus.load
66
+ super
92
67
  end
93
68
 
94
69
  #
95
- # Compute the best heading based on our current position and the position
96
- # of the current attractor. This is where the heavy-lifting happens
97
- def navigate
98
- return unless active?
99
- puts "Attempting to navigate to #{waypoint}"
100
- #
101
- # First off, compute distance and bearing from our current location
102
- # to every attractor and repellor.
103
- @attractors[@current_wpt..-1].each do |waypt|
104
- waypt.compute_bearing(@where)
105
- puts "Angle: #{waypt.bearing.angle_d}, Distance: #{waypt.bearing.distance} (adj:#{waypt.distance})"
106
- end
107
- @repellors.each do |waypt|
108
- waypt.compute_bearing(@where)
109
- puts "Angle: #{waypt.bearing.angle_d}, Distance: #{waypt.bearing.distance} (adj:#{waypt.distance})"
110
- end
70
+ # Main daemon function (called from executable)
71
+ def self.daemon
72
+ puts "Mission management system starting up..."
111
73
  #
112
- # Right. Now look to see if we've achieved the current waypoint and
113
- # adjust, accordingly
114
- while active? and reached? do
115
- next_waypoint!
116
- end
117
- return unless active?
118
- puts "Angle to next waypoint: #{waypoint.bearing.angle_d}d"
119
- puts "Adjusted distance to waypoint is #{@distance}"
74
+ # Load the mission data from Redis and augment it with the
75
+ # contents of the mission file.
76
+ config = Config.load
77
+ mission = Mission.file_load config.mission_file
78
+ nav = Navigate.new(mission)
79
+ otto = Otto.load
120
80
  #
121
- # Now, start the vector field analysis by examining headings either side
122
- # of the bearing to the waypoint.
123
- best_course = @course
124
- best_relvmg = 0.0
125
- puts "Currently on a #{@course.tack_name} tack (heading is #{@course.heading_d} degrees)"
126
- (-@swing..@swing).each do |alpha_d|
127
- puts ">> Computing swing of #{alpha_d} degrees"
128
- new_course = Course.new(@course.wind)
129
- new_course.heading = waypoint.bearing.angle + Bearing.dtor(alpha_d)
130
- #
131
- # Ignore head-to-wind cases, as they're pointless.
132
- next if new_course.speed < 0.001
133
- puts "AWA:#{new_course.awa_d}, heading:#{new_course.heading_d}, speed:#{new_course.speed}"
134
- relvmg = 0.0
135
- relvmg = new_course.relative_vmg(@attractors[@current_wpt])
136
- @attractors[@current_wpt..-1].each do |waypt|
137
- relvmg += new_course.relative_vmg(waypt)
138
- end
139
- @repellors.each do |waypt|
140
- relvmg -= new_course.relative_vmg(waypt)
141
- end
142
- relvmg *= 0.1 if new_course.tack != @course.tack
143
- puts "Relative VMG: #{relvmg}"
144
- if relvmg > best_relvmg
145
- puts "Best heading (so far)"
146
- best_relvmg = relvmg
147
- best_course = new_course
81
+ # Keep running in our mission state, forever.
82
+ while true do
83
+ if mission.status.active?
84
+ #
85
+ # Listen for GPS data. When we have a new position, call the
86
+ # navigation code to determine a new course to sail (currently
87
+ # a compass course), and set the Otto register accordingly.
88
+ # Repeat until we run out of waypoints.
89
+ GPS.subscribe do |count|
90
+ puts "Mission received new GPS count: #{count}"
91
+ new_course = nav.navigate
92
+ if new_course.nil?
93
+ mission.status.completed!
94
+ break
95
+ end
96
+ mission.status.save
97
+ compass = Bearing.rtox(new_course.heading)
98
+ otto.set_register(Otto::COMPASS_HEADING_REGISTER, compass)
99
+ end
100
+ else
101
+ sleep 60
102
+ mission.status.load
148
103
  end
149
104
  end
150
- puts "Best RELVMG: #{best_relvmg}"
151
- puts "TACKING!" if best_course.tack != @course.tack
152
- puts "New HDG: #{best_course.heading_d} (AWA:#{best_course.awa_d}), WPT:#{waypoint.name}"
153
- @course = best_course
154
105
  end
155
106
 
156
107
  #
157
- # Set new position
158
- def set_position(time, loc)
159
- @where = loc
160
- @time = time
161
- @track << TrackPoint.new(@time, @where)
162
- end
163
-
164
- #
165
- # Advance the mission by a number of seconds (computing the new location
166
- # in the process). Fake out the speed and thus the location.
167
- def simulated_movement(how_long = 60)
168
- puts "Advancing mission by #{how_long}s"
169
- distance = @course.speed * how_long.to_f / 3600.0
170
- puts "Travelled #{distance * 1852.0} metres in that time."
171
- set_position(@time + how_long, @where + Bearing.new(@course.heading, distance))
172
- end
173
-
174
- #
175
- # How long has the mission been active?
176
- def elapsed
177
- @time - @start_time
178
- end
179
-
180
- #
181
- # Return the current waypoint.
182
- def waypoint
183
- #@attractors[@current_wpt] : nil
184
- end
185
-
186
- #
187
- # Have we reached the waypoint? Note that even though the waypoints have
188
- # a "reached" circle, we discard the last 10m on the basis that it is
189
- # within the GPS error.
190
- def reached?
191
- @distance = @attractors[@current_wpt].distance
192
- puts "ARE WE THERE YET? (dist=#{@distance})"
193
- return true if @distance <= 0.0027
194
- #
195
- # Check to see if the next WPT is nearer than the current one
196
- #if @current_wpt < (@attractors.count - 1)
197
- # next_wpt = @attractors[@current_wpt + 1]
198
- # brng = @attractors[@current_wpt].location - next_wpt.location
199
- # angle = Bearing.absolute(waypoint.bearing.angle - next_wpt.bearing.angle)
200
- # return true if brng.distance > next_wpt.distance and
201
- # angle > (0.25 * Math::PI) and
202
- # angle < (0.75 * Math::PI)
203
- #end
204
- puts "... Sadly, no."
205
- return false
206
- end
207
-
208
- #
209
- # Advance to the next waypoint. Return TRUE if
210
- # there actually is one...
211
- def next_waypoint!
212
- raise "No mission currently active" unless active?
213
- @current_wpt += 1
214
- puts "Attempting to navigate to #{waypoint.name}" if active?
215
- end
216
-
217
- #
218
- # Return the mission status as a string
219
- def status_str
220
- mins = elapsed / 60
221
- hours = mins / 60
222
- mins %= 60
223
- days = hours / 24
224
- hours %= 24
225
- str = ">>> #{@time}, "
226
- if days < 1
227
- str += "%dh%02dm" % [hours, mins]
228
- else
229
- str += "+%dd%%02dh%02dm" % [days, hours, mins]
230
- end
231
- str + ": My position is #{@where}"
108
+ # Load a new mission from the missions directory.
109
+ def self.file_load(filename)
110
+ parse YAML.load(File.open(filename))
232
111
  end
233
112
 
234
113
  #
235
- # Compute the remaining distance from the current location
236
- def overall_distance
237
- start_wpt = active? ? @current_wpt : 0
238
- dist = 0.0
239
- loc = @where
240
- @attractors[start_wpt..-1].each do |wpt|
241
- wpt.compute_bearing(loc)
242
- dist += wpt.bearing.distance
243
- loc = wpt.location
244
- end
245
- dist
114
+ # Load a new mission from the missions directory.
115
+ def self.parse(data)
116
+ mission = new
117
+ mission.parse(data)
118
+ mission
246
119
  end
247
120
 
248
121
  #
@@ -252,18 +125,22 @@ module SGS
252
125
  @url = data["url"]
253
126
  @description = data["description"]
254
127
  if data["launch"]
255
- @launch_site = data["launch"]["name"] || "Launch Site"
256
- @launch_location = SGS::Location.parse data["launch"]
128
+ @launch_site = data["launch"]["site"] || "Launch Site"
129
+ @launch_location = Location.parse data["launch"]
257
130
  end
258
- data["attractors"].each do |waypt_data|
259
- waypt = Waypoint.parse(waypt_data)
260
- waypt.attractor = true
261
- @attractors << waypt
131
+ if data["attractors"]
132
+ data["attractors"].each do |waypt_data|
133
+ waypt = Waypoint.parse(waypt_data)
134
+ waypt.attractor = true
135
+ @attractors << waypt
136
+ end
262
137
  end
263
- data["repellors"].each do |waypt_data|
264
- waypt = Waypoint.parse(waypt_data)
265
- waypt.attractor = false
266
- @repellors << waypt
138
+ if data["repellors"]
139
+ data["repellors"].each do |waypt_data|
140
+ waypt = Waypoint.parse(waypt_data)
141
+ waypt.attractor = false
142
+ @repellors << waypt
143
+ end
267
144
  end
268
145
  end
269
146
 
@@ -31,16 +31,26 @@
31
31
  # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
32
  #
33
33
  # ABSTRACT
34
+ # This class is used to store the actual mission status. As the SGS::Mission
35
+ # class doesn't actually store anything in Redis. Nor does the SGS::Navigate
36
+ # class. In order to save the mission and navigation details, this class
37
+ # manages that information. Note that only the SGS::Mission daemon should
38
+ # save this object, to avoid race conditions.
39
+ #
40
+ # The state here refers to the mission states, as opposed to Otto modes.
41
+ # Initially the boat will be in AWAITING mode until something wakes it up. At
42
+ # that point it may go to READY_TO_START or START_TEST followed by something
43
+ # like pre-mission trying to sail to a start line or awaiting mission control.
34
44
  #
35
45
 
36
46
  ##
37
- # Mission state
47
+ # Mission status
38
48
  #
39
49
  module SGS
40
50
  #
41
51
  # Handle a specific mission.
42
52
  class MissionStatus < RedisBase
43
- attr_accessor :state, :current_waypoint, :start_time, :end_time
53
+ attr_accessor :state, :current_waypoint, :course, :distance, :start_time, :end_time
44
54
 
45
55
  STATE_AWAITING = 0
46
56
  STATE_READY_TO_START = 1
@@ -71,9 +81,10 @@ module SGS
71
81
  # the boat is on.
72
82
  def initialize
73
83
  @state = STATE_AWAITING
74
- @current_waypoint = 0
75
- @start_time = @end_time = nil
76
- @logger = Logger.new(STDOUT)
84
+ @current_waypoint = -1
85
+ @where = nil
86
+ @distance = 0
87
+ @track = nil
77
88
  end
78
89
 
79
90
  #
@@ -91,7 +102,7 @@ module SGS
91
102
  #
92
103
  # Commence a mission...
93
104
  def start_test!(time = nil)
94
- @logger.warn "***** Starting test phase *****"
105
+ puts "***** Starting test phase *****"
95
106
  @start_time = time || Time.now
96
107
  @state = STATE_START_TEST
97
108
  @current_waypoint = 0
@@ -104,7 +115,7 @@ module SGS
104
115
  @end_time = time || Time.now
105
116
  @state = STATE_COMPLETE
106
117
  save_and_publish
107
- @logger.warn "***** Mission completed! *****"
118
+ puts "***** Mission completed! *****"
108
119
  end
109
120
 
110
121
  #
@@ -113,7 +124,7 @@ module SGS
113
124
  @end_time = time || Time.now
114
125
  @state = STATE_TERMINATED
115
126
  save_and_publish
116
- @logger.warn "***** Mission terminated! *****"
127
+ puts "***** Mission terminated! *****"
117
128
  end
118
129
 
119
130
  #
@@ -122,7 +133,7 @@ module SGS
122
133
  @end_time = time || Time.now
123
134
  @state = STATE_FAILURE
124
135
  save_and_publish
125
- @logger.warn "***** Mission failure! *****"
136
+ puts "***** Mission failure! *****"
126
137
  end
127
138
  end
128
139
  end
data/lib/sgs/navigate.rb CHANGED
@@ -31,83 +31,226 @@
31
31
  # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
32
  #
33
33
  # ABSTRACT
34
+ # All of the code to navigate a sailboat to a series of waypoints is defined
35
+ # herein. The main Navigate class does not save anything to Redis, it
36
+ # is purely a utility class for navigation. The navigation is based on my
37
+ # paper "An Attractor/Repellor Approach to Autonomous Sailboat Navigation".
38
+ # https://link.springer.com/chapter/10.1007/978-3-319-72739-4_6
39
+ #
40
+ # We save a copy of the actual mission so we can find the attractors and
41
+ # repellors. We also assume that doing a GPS.load will pull the latest
42
+ # GPS co-ordinates and an Otto.load will pull the latest telemetry from
43
+ # the boat. Specifically, the GPS will give us our lat/long and the Otto
44
+ # data will allow us to compute the actual wind direction (as well as the
45
+ # boat heading and apparent wind angle).
34
46
  #
35
47
 
36
48
  ##
37
49
  #
38
50
  module SGS
39
51
  class Navigate
40
- attr_reader :mode
41
-
42
- MODE_SLEEP = 0
43
- MODE_TEST = 1
44
- MODE_MANUAL = 2
45
- MODE_UPDOWN = 3
46
- MODE_OLYMPIC = 4
47
- MODE_PRE_MISSION = 5
48
- MODE_MISSION = 6
49
- MODE_MISSION_END = 7
50
- MODE_MISSION_ABORT = 8
51
-
52
- MODENAMES = [
53
- "Sleeping...",
54
- "Test Mode",
55
- "Manual Steering",
56
- "Sail Up and Down",
57
- "Sail a Triangle",
58
- "Pre-Mission Wait",
59
- "On Mission",
60
- "Mission Ended",
61
- "** Mission Abort **"
62
- ].freeze
63
-
64
- def initialize
65
- @mode = MODE_SLEEP
66
- @waypoint = nil
67
- @curpos = nil
68
- super
69
- end
70
-
71
- #
72
- # Main daemon function (called from executable)
73
- def self.daemon
74
- loop do
75
- sleep 300
52
+ #
53
+ # Initialize the navigational parameters
54
+ def initialize(mission)
55
+ @mission = mission
56
+ @swing = 45
57
+ end
58
+
59
+ #
60
+ # Compute the best heading based on our current position and the position
61
+ # of the current attractor. This is where the heavy-lifting happens
62
+ def navigate
63
+ if @mission.status.current_waypoint == -1
64
+ @mission.status.current_waypoint = 0
65
+ @mission.status.distance = 0
66
+ end
67
+ set_waypoint
68
+ puts "Attempting to navigate to #{@waypoint}..."
69
+ #
70
+ # Pull the latest GPS data...
71
+ @gps = GPS.load
72
+ puts "GPS: #{@gps}"
73
+ return unless @gps.valid?
74
+ #
75
+ # Pull the latest Otto data...
76
+ @otto = Otto.load
77
+ puts "OTTO:"
78
+ p @otto
79
+ puts "Compass: #{@otto.compass}"
80
+ puts "AWA: #{@otto.awa}"
81
+ puts "Wind: #{@otto.wind}"
82
+ #
83
+ # Update our local copy of the course based on what Otto says.
84
+ puts "Course:"
85
+ @course = Course.new
86
+ @course.heading = @otto.compass
87
+ @course.awa = @otto.awa
88
+ @course.compute_wind
89
+ #
90
+ # Compute a new course from the parameter set
91
+ compute_new_course
92
+ end
93
+
94
+ #
95
+ # Compute a new course based on our position and other information.
96
+ def compute_new_course
97
+ puts "Compute new course..."
98
+ #
99
+ # First off, compute distance and bearing from our current location
100
+ # to every attractor and repellor. We only look at forward attractors,
101
+ # not ones behind us.
102
+ compute_bearings(@mission.attractors[@mission.status.current_waypoint..-1])
103
+ compute_bearings(@mission.repellors)
104
+ #
105
+ # Right. Now look to see if we've achieved the current waypoint and
106
+ # adjust, accordingly
107
+ while active? and reached?
108
+ next_waypoint!
76
109
  end
110
+ return nil unless active?
111
+ puts "Angle to next waypoint: #{@waypoint.bearing.angle_d}d"
112
+ puts "Adjusted distance to waypoint is #{@waypoint.distance}"
113
+ #
114
+ # Now, start the vector field analysis by examining headings either side
115
+ # of the bearing to the waypoint.
116
+ best_course = @course
117
+ best_relvmg = 0.0
118
+ puts "Currently on a #{@course.tack_name} tack (heading is #{@course.heading_d} degrees)"
119
+ (-@swing..@swing).each do |alpha_d|
120
+ new_course = Course.new(@course.wind)
121
+ new_course.heading = waypoint.bearing.angle + Bearing.dtor(alpha_d)
122
+ #
123
+ # Ignore head-to-wind cases, as they're pointless. When looking at
124
+ # the list of waypoints to compute relative VMG, only look to the next
125
+ # three or so waypoints.
126
+ next if new_course.speed < 0.001
127
+ relvmg = 0.0
128
+ relvmg = new_course.relative_vmg(@mission.attractors[@mission.status.current_waypoint])
129
+ end_wpt = @mission.status.current_waypoint + 3
130
+ if end_wpt >= @mission.attractors.count
131
+ end_wpt = @mission.attractors.count - 1
132
+ end
133
+ @mission.attractors[@mission.status.current_waypoint..end_wpt].each do |waypt|
134
+ relvmg += new_course.relative_vmg(waypt)
135
+ end
136
+ @mission.repellors.each do |waypt|
137
+ relvmg -= new_course.relative_vmg(waypt)
138
+ end
139
+ relvmg *= 0.1 if new_course.tack != @course.tack
140
+ if relvmg > best_relvmg
141
+ best_relvmg = relvmg
142
+ best_course = new_course
143
+ end
144
+ end
145
+ if best_course.tack != @course.tack
146
+ puts "TACKING!!!!"
147
+ end
148
+ best_course
149
+ end
150
+
151
+ #
152
+ # Compute the bearing for every attractor or repellor
153
+ def compute_bearings(waypoints)
154
+ waypoints.each do |waypt|
155
+ waypt.compute_bearing(@gps.location)
156
+ end
157
+ end
158
+
159
+ #
160
+ # Set new position
161
+ def set_position(time, loc)
162
+ @where = loc
163
+ @time = time
164
+ @track << TrackPoint.new(@time, @where)
77
165
  end
78
166
 
79
167
  #
80
- # What is the mode name?
81
- def mode_name
82
- MODENAMES[@mode]
168
+ # Advance the mission by a number of seconds (computing the new location
169
+ # in the process). Fake out the speed and thus the location.
170
+ def simulated_movement(how_long = 60)
171
+ puts "Advancing mission by #{how_long}s"
172
+ distance = @course.speed * how_long.to_f / 3600.0
173
+ puts "Travelled #{distance * 1852.0} metres in that time."
174
+ set_position(@time + how_long, @where + Bearing.new(@course.heading, distance))
83
175
  end
84
176
 
85
- def mode=(val)
86
- puts "SETTING NEW MODE TO #{MODENAMES[val]}"
87
- @mode = val
177
+ #
178
+ # How long has the mission been active?
179
+ def elapsed
180
+ @time - @start_time
181
+ end
182
+
183
+ #
184
+ # Check we're active - basically, are there any more waypoints left?
185
+ def active?
186
+ @mission.status.current_waypoint < @mission.attractors.count
187
+ end
188
+
189
+ #
190
+ # Have we reached the waypoint? Note that even though the waypoints have
191
+ # a "reached" circle, we discard the last 10m on the basis that it is
192
+ # within the GPS error.
193
+ def reached?
194
+ puts "ARE WE THERE YET? (dist=#{@waypoint.distance})"
195
+ p @waypoint
196
+ return true if @waypoint.distance <= 0.0054
197
+ #
198
+ # Check to see if the next WPT is nearer than the current one
199
+ #if current_wpt < (@mission.attractors.count - 1)
200
+ # next_wpt = @mission.attractors[@current_wpt + 1]
201
+ # brng = @mission.attractors[@current_wpt].location - next_wpt.location
202
+ # angle = Bearing.absolute(waypoint.bearing.angle - next_wpt.bearing.angle)
203
+ # return true if brng.distance > next_wpt.distance and
204
+ # angle > (0.25 * Math::PI) and
205
+ # angle < (0.75 * Math::PI)
206
+ #end
207
+ puts "... Sadly, no."
208
+ return false
209
+ end
210
+
211
+ #
212
+ # Advance to the next waypoint. Return TRUE if
213
+ # there actually is one...
214
+ def next_waypoint!
215
+ @mission.status.current_waypoint += 1
216
+ puts "Attempting to navigate to new waypoint: #{waypoint}"
217
+ set_waypoint
218
+ end
219
+
220
+ #
221
+ # Set the waypoint instance variable based on where we are
222
+ def set_waypoint
223
+ @waypoint = @mission.attractors[@mission.status.current_waypoint]
224
+ end
225
+
226
+ #
227
+ # Return the mission status as a string
228
+ def status_str
229
+ mins = elapsed / 60
230
+ hours = mins / 60
231
+ mins %= 60
232
+ days = hours / 24
233
+ hours %= 24
234
+ str = ">>> #{@time}, "
235
+ if days < 1
236
+ str += "%dh%02dm" % [hours, mins]
237
+ else
238
+ str += "+%dd%%02dh%02dm" % [days, hours, mins]
239
+ end
240
+ str + ": My position is #{@where}"
88
241
  end
89
242
 
90
243
  #
91
- # This is the main navigator function. It does several things;
92
- # 1. Look for the next waypoint and compute bearing and distance to it
93
- # 2. Decide if we have reached the waypoint (and adjust accordingly)
94
- # 3. Compute the boat heading (and adjust accordingly)
95
- def run
96
- puts "Navigator mode is #{mode_name}: Current Position:"
97
- p curpos
98
- p waypoint
99
- case @mode
100
- when MODE_UPDOWN
101
- upwind_downwind_course
102
- when MODE_OLYMPIC
103
- olympic_course
104
- when MODE_MISSION
105
- mission
106
- when MODE_MISSION_END
107
- mission_end
108
- when MODE_MISSION_ABORT
109
- mission_abort
244
+ # Compute the remaining distance from the current location
245
+ def overall_distance
246
+ dist = 0.0
247
+ loc = @where
248
+ @mission.attractors[@mission.status.current_waypoint..-1].each do |wpt|
249
+ wpt.compute_bearing(loc)
250
+ dist += wpt.bearing.distance
251
+ loc = wpt.location
110
252
  end
253
+ dist
111
254
  end
112
255
 
113
256
  #
@@ -145,13 +288,13 @@ module SGS
145
288
  #
146
289
  # What is our current position?
147
290
  def curpos
148
- @curpos ||= SGS::GPS.load
291
+ @curpos ||= GPS.load
149
292
  end
150
293
 
151
294
  #
152
295
  # What is the next waypoint?
153
296
  def waypoint
154
- @waypoint ||= SGS::Waypoint.load
297
+ @waypoint ||= Waypoint.load
155
298
  end
156
299
  end
157
300
  end