sgslib 1.5.1 → 1.7.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.
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,37 +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
- puts "Mission management system starting up..."
55
- loop do
56
- sleep 300
57
- end
58
- end
59
-
60
- #
61
- # Load a new mission from the missions directory.
62
- def self.file_load(filename)
63
- parse YAML.load(File.open(filename))
64
- end
65
-
66
- #
67
- # Load a new mission from the missions directory.
68
- def self.parse(data)
69
- mission = new
70
- mission.parse(data)
71
- mission
72
- end
52
+ attr_accessor :attractors, :repellors, :status
73
53
 
74
54
  #
75
55
  # Create the attractors and repellors as well as the track array
76
- # and other items. @where is our current TrackPoint, @current_wpt is
77
- # the waypoint we're working (-1 if none), @course is the heading/speed
78
- # the boat is on.
56
+ # and other items.
79
57
  def initialize
80
58
  @title = nil
81
59
  @url = nil
@@ -84,166 +62,60 @@ module SGS
84
62
  @launch_location = nil
85
63
  @attractors = []
86
64
  @repellors = []
87
- @track = nil
88
- @start_time = @time = nil
89
- @where = nil
90
- @course = Course.new
91
- @distance = 0
92
- @swing = 60
65
+ @status = MissionStatus.load
66
+ super
93
67
  end
94
68
 
95
69
  #
96
- # Compute the best heading based on our current position and the position
97
- # of the current attractor. This is where the heavy-lifting happens
98
- def navigate
99
- return unless active?
100
- puts "Attempting to navigate to #{waypoint}"
101
- #
102
- # First off, compute distance and bearing from our current location
103
- # to every attractor and repellor.
104
- @attractors[@current_wpt..-1].each do |waypt|
105
- waypt.compute_bearing(@where)
106
- puts "Angle: #{waypt.bearing.angle_d}, Distance: #{waypt.bearing.distance} (adj:#{waypt.distance})"
107
- end
108
- @repellors.each do |waypt|
109
- waypt.compute_bearing(@where)
110
- puts "Angle: #{waypt.bearing.angle_d}, Distance: #{waypt.bearing.distance} (adj:#{waypt.distance})"
111
- end
70
+ # Main daemon function (called from executable)
71
+ def self.daemon
72
+ puts "Mission management system starting up..."
112
73
  #
113
- # Right. Now look to see if we've achieved the current waypoint and
114
- # adjust, accordingly
115
- while active? and reached? do
116
- next_waypoint!
117
- end
118
- return unless active?
119
- puts "Angle to next waypoint: #{waypoint.bearing.angle_d}d"
120
- 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
121
80
  #
122
- # Now, start the vector field analysis by examining headings either side
123
- # of the bearing to the waypoint.
124
- best_course = @course
125
- best_relvmg = 0.0
126
- puts "Currently on a #{@course.tack_name} tack (heading is #{@course.heading_d} degrees)"
127
- (-@swing..@swing).each do |alpha_d|
128
- puts ">> Computing swing of #{alpha_d} degrees"
129
- new_course = Course.new(@course.wind)
130
- new_course.heading = waypoint.bearing.angle + Bearing.dtor(alpha_d)
131
- #
132
- # Ignore head-to-wind cases, as they're pointless.
133
- next if new_course.speed < 0.001
134
- puts "AWA:#{new_course.awa_d}, heading:#{new_course.heading_d}, speed:#{new_course.speed}"
135
- relvmg = 0.0
136
- relvmg = new_course.relative_vmg(@attractors[@current_wpt])
137
- @attractors[@current_wpt..-1].each do |waypt|
138
- relvmg += new_course.relative_vmg(waypt)
139
- end
140
- @repellors.each do |waypt|
141
- relvmg -= new_course.relative_vmg(waypt)
142
- end
143
- relvmg *= 0.1 if new_course.tack != @course.tack
144
- puts "Relative VMG: #{relvmg}"
145
- if relvmg > best_relvmg
146
- puts "Best heading (so far)"
147
- best_relvmg = relvmg
148
- 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
149
103
  end
150
104
  end
151
- puts "Best RELVMG: #{best_relvmg}"
152
- puts "TACKING!" if best_course.tack != @course.tack
153
- puts "New HDG: #{best_course.heading_d} (AWA:#{best_course.awa_d}), WPT:#{waypoint.name}"
154
- @course = best_course
155
105
  end
156
106
 
157
107
  #
158
- # Set new position
159
- def set_position(time, loc)
160
- @where = loc
161
- @time = time
162
- @track << TrackPoint.new(@time, @where)
163
- end
164
-
165
- #
166
- # Advance the mission by a number of seconds (computing the new location
167
- # in the process). Fake out the speed and thus the location.
168
- def simulated_movement(how_long = 60)
169
- puts "Advancing mission by #{how_long}s"
170
- distance = @course.speed * how_long.to_f / 3600.0
171
- puts "Travelled #{distance * 1852.0} metres in that time."
172
- set_position(@time + how_long, @where + Bearing.new(@course.heading, distance))
173
- end
174
-
175
- #
176
- # How long has the mission been active?
177
- def elapsed
178
- @time - @start_time
179
- end
180
-
181
- #
182
- # Return the current waypoint.
183
- def waypoint
184
- #@attractors[@current_wpt] : nil
185
- end
186
-
187
- #
188
- # Have we reached the waypoint? Note that even though the waypoints have
189
- # a "reached" circle, we discard the last 10m on the basis that it is
190
- # within the GPS error.
191
- def reached?
192
- @distance = @attractors[@current_wpt].distance
193
- puts "ARE WE THERE YET? (dist=#{@distance})"
194
- return true if @distance <= 0.0027
195
- #
196
- # Check to see if the next WPT is nearer than the current one
197
- #if @current_wpt < (@attractors.count - 1)
198
- # next_wpt = @attractors[@current_wpt + 1]
199
- # brng = @attractors[@current_wpt].location - next_wpt.location
200
- # angle = Bearing.absolute(waypoint.bearing.angle - next_wpt.bearing.angle)
201
- # return true if brng.distance > next_wpt.distance and
202
- # angle > (0.25 * Math::PI) and
203
- # angle < (0.75 * Math::PI)
204
- #end
205
- puts "... Sadly, no."
206
- return false
207
- end
208
-
209
- #
210
- # Advance to the next waypoint. Return TRUE if
211
- # there actually is one...
212
- def next_waypoint!
213
- raise "No mission currently active" unless active?
214
- @current_wpt += 1
215
- puts "Attempting to navigate to #{waypoint.name}" if active?
216
- end
217
-
218
- #
219
- # Return the mission status as a string
220
- def status_str
221
- mins = elapsed / 60
222
- hours = mins / 60
223
- mins %= 60
224
- days = hours / 24
225
- hours %= 24
226
- str = ">>> #{@time}, "
227
- if days < 1
228
- str += "%dh%02dm" % [hours, mins]
229
- else
230
- str += "+%dd%%02dh%02dm" % [days, hours, mins]
231
- end
232
- 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))
233
111
  end
234
112
 
235
113
  #
236
- # Compute the remaining distance from the current location
237
- def overall_distance
238
- start_wpt = active? ? @current_wpt : 0
239
- dist = 0.0
240
- loc = @where
241
- @attractors[start_wpt..-1].each do |wpt|
242
- wpt.compute_bearing(loc)
243
- dist += wpt.bearing.distance
244
- loc = wpt.location
245
- end
246
- dist
114
+ # Load a new mission from the missions directory.
115
+ def self.parse(data)
116
+ mission = new
117
+ mission.parse(data)
118
+ mission
247
119
  end
248
120
 
249
121
  #
@@ -253,18 +125,23 @@ module SGS
253
125
  @url = data["url"]
254
126
  @description = data["description"]
255
127
  if data["launch"]
256
- @launch_site = data["launch"]["name"] || "Launch Site"
257
- @launch_location = SGS::Location.parse data["launch"]
128
+ @launch_site = data["launch"]["site"] || "Launch Site"
129
+ @launch_location = Location.new
130
+ @launch_location.parse_hash data["launch"]
258
131
  end
259
- data["attractors"].each do |waypt_data|
260
- waypt = Waypoint.parse(waypt_data)
261
- waypt.attractor = true
262
- @attractors << waypt
132
+ if data["attractors"]
133
+ data["attractors"].each do |waypt_data|
134
+ waypt = Waypoint.parse(waypt_data)
135
+ waypt.attractor = true
136
+ @attractors << waypt
137
+ end
263
138
  end
264
- data["repellors"].each do |waypt_data|
265
- waypt = Waypoint.parse(waypt_data)
266
- waypt.attractor = false
267
- @repellors << waypt
139
+ if data["repellors"]
140
+ data["repellors"].each do |waypt_data|
141
+ waypt = Waypoint.parse(waypt_data)
142
+ waypt.attractor = false
143
+ @repellors << waypt
144
+ end
268
145
  end
269
146
  end
270
147
 
@@ -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,8 +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
84
+ @current_waypoint = -1
85
+ @where = nil
86
+ @distance = 0
87
+ @track = nil
76
88
  end
77
89
 
78
90
  #
data/lib/sgs/navigate.rb CHANGED
@@ -31,102 +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
- puts "Navigation system starting up..."
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)
75
104
  #
76
- # Load the mission data from Redis and augment it with the
77
- # contents of the mission file.
78
- config = SGS::Config.load
79
- mission = SGS::Mission.file_load config.mission_file
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!
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}"
80
113
  #
81
- # Now listen for GPS data...
82
- SGS::GPS.subscribe do |count|
83
- puts "Received new GPS count: #{count}"
84
- case SGS::MissionStatus.state
85
- when STATE_COMPASS_FOLLOW
86
- when STATE_WIND_FOLLOW
87
- mission.navigate
88
- when STATE_COMPLETE
89
- when STATE_TERMINATED
90
- when STATE_FAILURE
91
- mission.hold_station
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)
92
138
  end
93
- gps = SGS::GPS.load
94
- p gps
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)
95
156
  end
96
157
  end
97
158
 
98
159
  #
99
- # What is the mode name?
100
- def mode_name
101
- MODENAMES[@mode]
160
+ # Set new position
161
+ def set_position(time, loc)
162
+ @where = loc
163
+ @time = time
164
+ @track << TrackPoint.new(@time, @where)
165
+ end
166
+
167
+ #
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))
175
+ end
176
+
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
102
218
  end
103
219
 
104
- def mode=(val)
105
- puts "SETTING NEW MODE TO #{MODENAMES[val]}"
106
- @mode = val
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}"
107
241
  end
108
242
 
109
243
  #
110
- # This is the main navigator function. It does several things;
111
- # 1. Look for the next waypoint and compute bearing and distance to it
112
- # 2. Decide if we have reached the waypoint (and adjust accordingly)
113
- # 3. Compute the boat heading (and adjust accordingly)
114
- def run
115
- puts "Navigator mode is #{mode_name}: Current Position:"
116
- p curpos
117
- p waypoint
118
- case @mode
119
- when MODE_UPDOWN
120
- upwind_downwind_course
121
- when MODE_OLYMPIC
122
- olympic_course
123
- when MODE_MISSION
124
- mission
125
- when MODE_MISSION_END
126
- mission_end
127
- when MODE_MISSION_ABORT
128
- 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
129
252
  end
253
+ dist
130
254
  end
131
255
 
132
256
  #
@@ -164,13 +288,13 @@ module SGS
164
288
  #
165
289
  # What is our current position?
166
290
  def curpos
167
- @curpos ||= SGS::GPS.load
291
+ @curpos ||= GPS.load
168
292
  end
169
293
 
170
294
  #
171
295
  # What is the next waypoint?
172
296
  def waypoint
173
- @waypoint ||= SGS::Waypoint.load
297
+ @waypoint ||= Waypoint.load
174
298
  end
175
299
  end
176
300
  end
data/lib/sgs/nmea.rb CHANGED
@@ -87,7 +87,7 @@ module SGS
87
87
  if @args.count < 12 or @args.count > 13
88
88
  return nil
89
89
  end
90
- gps = SGS::GPS.new
90
+ gps = GPS.new
91
91
  gps.is_valid if @args[2] == "A"
92
92
  hh = @args[1][0..1].to_i
93
93
  mm = @args[1][2..3].to_i
@@ -100,7 +100,8 @@ module SGS
100
100
  gps.time = Time.gm(yy, mn, dd, hh, mm, ss, us)
101
101
  pos = {"latitude" => ll_nmea(@args[3,4]),
102
102
  "longitude" => ll_nmea(@args[5,6])}
103
- gps.location = Location.parse pos
103
+ gps.location = Location.new
104
+ gps.location.parse_hash(pos)
104
105
  gps.sog = @args[7].to_f
105
106
  gps.cmg = Bearing.dtor @args[8].to_f
106
107
  gps