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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5dffcc970035c48e2b6026c72884b6ab97cc14ac43ba255cb4386244b46287c
4
- data.tar.gz: 347c958575c0e9510047c75b997426083d4e3abf8ee479ff9b407c3395bbe118
3
+ metadata.gz: 3e111aaff75b5c948f935ac6f76747487c2a6acf2c4e661e07b5e670aa48024b
4
+ data.tar.gz: 8b0dae14c6a69c2222e38522d3f7e562b45c33e754cf5019a9f0b081d6191fc6
5
5
  SHA512:
6
- metadata.gz: bdee04756fe59509497bc455f7874e9c05ecca6c9031f55133460a28ea987d9a0dd788588e2b449b9fe0630f734f23b6c968722361153b9130fa0362c354b67c
7
- data.tar.gz: de267f01e59622938cfb1ad3ebbcb7c08c72bb02a625f1de72c93255b5e3be868cae29a1625383890c4c4aa57292b27942d735adb0abb4f36763dd481ca62893
6
+ metadata.gz: 101b2c3581effba8bc0fae32d4c68030649104862101336409fdeb4679ff25236adf0b67dfeac588eceb0196a7616175cc1112d1d926ee6d528c95c60cbeed60
7
+ data.tar.gz: 981bf2364b073ae0d25e0fea03341b289db890836af399c4cee4079d0dcde1ceef20f9669d3fdeb36d2ea143b6566830bcc676ea705bf9bb6439dc8162234e52
data/lib/sgs/alarm.rb CHANGED
@@ -109,7 +109,7 @@ module SGS
109
109
  # Main daemon function (called from executable)
110
110
  def self.daemon
111
111
  puts "Alarm daemon starting up..."
112
- otto = SGS::RPCClient.new(:otto)
112
+ otto = RPCClient.new(:otto)
113
113
  loop do
114
114
  #puts "Check for any alarms..."
115
115
  #resp = otto.command "A?"
@@ -126,7 +126,7 @@ module SGS
126
126
  f.puts "/*\n * Autogenerated by #{__FILE__}.\n * DO NOT HAND-EDIT!\n */"
127
127
  constants.sort.each do |c|
128
128
  unless c == :ALARM_NAMES
129
- cval = SGS::Alarm.const_get(c)
129
+ cval = Alarm.const_get(c)
130
130
  str = "#define SGS_ALARM_#{c.to_s}"
131
131
  str += "\t" if str.length < 32
132
132
  str += "\t#{cval}\t/* #{alarm.name(cval)} */"
data/lib/sgs/bearing.rb CHANGED
@@ -49,7 +49,8 @@ module SGS
49
49
  attr_accessor :distance
50
50
 
51
51
  #
52
- # Create the Bearing instance.
52
+ # Create the Bearing instance. Angle is in radians, distance in nautical
53
+ # miles.
53
54
  def initialize(angle = 0.0, distance = 0.0)
54
55
  self.angle = angle.to_f
55
56
  self.distance = distance.to_f
@@ -73,6 +74,23 @@ module SGS
73
74
  rad.to_f * 180.0 / Math::PI
74
75
  end
75
76
 
77
+ #
78
+ # Convert boat angle (0->255) to radians. The boat uses an 8bit quantity
79
+ # to represent an angle, where 256 maps to 360 degrees. This makes angle
80
+ # arithmetic quite simple on an 8 bit processor, but useless for
81
+ # something which has a proper FPU. These two helper functions convert
82
+ # to and from radians.
83
+ def self.xtor(val)
84
+ val &= 0xff
85
+ val.to_f * Math::PI / 128.0
86
+ end
87
+
88
+ #
89
+ # Convert radians to hex-degrees.
90
+ def self.rtox(rad)
91
+ (rad.to_f * 128.0 / Math::PI).round.to_i & 0xff
92
+ end
93
+
76
94
  #
77
95
  # Handy function to re-adjust an angle away from negative
78
96
  def self.absolute(angle)
@@ -111,7 +129,7 @@ module SGS
111
129
  sin_dlon = Math.sin(loc2.longitude - loc1.longitude)
112
130
  cos_dlon = Math.cos(loc2.longitude - loc1.longitude)
113
131
  bearing.distance = Math.acos(sin_lat1*sin_lat2 + cos_lat1*cos_lat2*cos_dlon) *
114
- SGS::EARTH_RADIUS
132
+ EARTH_RADIUS
115
133
  y = sin_dlon * cos_lat2
116
134
  x = cos_lat1 * sin_lat2 - sin_lat1 * cos_lat2 * cos_dlon
117
135
  bearing.angle = Math.atan2(y, x)
@@ -142,10 +160,20 @@ module SGS
142
160
  Bearing.absolute(@angle - Math::PI)
143
161
  end
144
162
 
163
+ #
164
+ # Return the distance in metres
165
+ def distance_m
166
+ @distance * 1852.0
167
+ end
168
+
145
169
  #
146
170
  # Convert to a string
147
171
  def to_s
148
- "BRNG %03dd,%.3fnm" % [angle_d, @distance]
172
+ if @distance > 0.9
173
+ "BRNG %03dd,%.3fNM" % [angle_d, @distance]
174
+ else
175
+ "BRNG %03dd,%.1fm" % [angle_d, distance_m]
176
+ end
149
177
  end
150
178
  end
151
179
  end
data/lib/sgs/config.rb CHANGED
@@ -56,7 +56,6 @@ module SGS
56
56
  GPS.setup
57
57
  Otto.setup
58
58
  Timing.setup
59
- Waypoint.setup
60
59
  end
61
60
  end
62
61
  end
data/lib/sgs/course.rb CHANGED
@@ -149,11 +149,21 @@ module SGS
149
149
  end
150
150
 
151
151
  #
152
- # Compute a relative VMG based on the waypoint
152
+ # Compute a relative VMG based on the waypoint. If you're computing
153
+ # angles to one waypoint, the relative VMG will allow you to compare
154
+ # against each possible angle. If you're computing to different
155
+ # waypoints, then the distance from your position to that waypoint is
156
+ # taken into consideration so that waypoints further away have less
157
+ # impact on your VMG choice.
153
158
  def relative_vmg(waypt)
154
- relvmg = @speed * Math::cos(waypt.bearing.angle - @heading) / waypt.distance
155
- puts "Relative VMG to WPT: #{waypt.name} is #{relvmg}"
156
- relvmg
159
+ @speed * Math::cos(waypt.bearing.angle - @heading) / waypt.distance
160
+ end
161
+
162
+ #
163
+ # Compute the wind (as a Bearing) based on the compass heading and the
164
+ # apparent wind as reported.
165
+ def compute_wind
166
+ @wind.angle = @heading + @awa
157
167
  end
158
168
 
159
169
  #
@@ -161,7 +171,10 @@ module SGS
161
171
  # fast the boat will travel at the particular apparent wind angle.
162
172
  def compute_speed
163
173
  awa = @awa.abs
164
- return 0.0 if awa < 0.75
174
+ if awa < 0.75
175
+ @speed = 0.0
176
+ return
177
+ end
165
178
  ap = 1.0
166
179
  @speed = 0.0
167
180
  @polar_curve.each do |poly_val|
@@ -37,7 +37,7 @@
37
37
  # Routines for handling sailboat navigation and route planning.
38
38
  #
39
39
  module SGS
40
- class Diagnostics < RedisBase
40
+ class Diagnostics
41
41
  #
42
42
  # Main daemon function (called from executable)
43
43
  def self.daemon
data/lib/sgs/gps.rb CHANGED
@@ -36,8 +36,10 @@ require 'serialport'
36
36
 
37
37
  module SGS
38
38
  class GPS < RedisBase
39
- attr_accessor :time, :location, :sog, :cmg, :magvar
39
+ attr_accessor :time, :location, :sog, :cmg, :magvar, :val
40
40
 
41
+ #
42
+ # Create a new GPS record. Lat/Long are in radians.
41
43
  def initialize(lat = nil, long = nil)
42
44
  @time = Time.new(2000, 1, 1)
43
45
  @location = Location.new(lat, long)
@@ -52,13 +54,13 @@ module SGS
52
54
  # Main daemon function (called from executable)
53
55
  def self.daemon
54
56
  puts "GPS reader starting up..."
55
- config = SGS::Config.load
57
+ config = Config.load
56
58
 
57
59
  sp = SerialPort.new config.gps_device, config.gps_speed
58
60
  sp.read_timeout = 10000
59
61
 
60
62
  loop do
61
- nmea = SGS::NMEA.parse sp.readline
63
+ nmea = NMEA.parse sp.readline
62
64
  if nmea.is_gprmc?
63
65
  gps = nmea.parse_gprmc
64
66
  p gps
@@ -67,6 +69,14 @@ module SGS
67
69
  end
68
70
  end
69
71
 
72
+ #
73
+ # Hard-code a GPS value (usually for debugging purposes)
74
+ def force(lat, long, time = nil)
75
+ @time = time || Time.now
76
+ @location = Location.new(lat, long)
77
+ @valid = true
78
+ end
79
+
70
80
  #
71
81
  # Set the validity
72
82
  def is_valid
@@ -78,5 +88,15 @@ module SGS
78
88
  def valid?
79
89
  @valid == true
80
90
  end
91
+
92
+ #
93
+ # Display the GPS data as a useful string (in degrees)
94
+ def to_s
95
+ if valid?
96
+ "@#{@time.strftime('%Y%m%d-%T')}, #{@location}, SOG:#{@sog}, CMG:#{@cmg}"
97
+ else
98
+ "GPS error"
99
+ end
100
+ end
81
101
  end
82
102
  end
data/lib/sgs/location.rb CHANGED
@@ -56,7 +56,7 @@ module SGS
56
56
  attr_accessor :latitude, :longitude
57
57
 
58
58
  #
59
- # Create the Location instance.
59
+ # Create the Location instance. Latitude and longitude passed in radians.
60
60
  def initialize(lat = nil, long = nil)
61
61
  @latitude = lat.to_f if lat
62
62
  @longitude = long.to_f if long
@@ -65,7 +65,6 @@ module SGS
65
65
  #
66
66
  # The difference between two locations is a Bearing
67
67
  def -(loc)
68
- puts "Distance from #{self} to #{loc}"
69
68
  Bearing.compute(self, loc)
70
69
  end
71
70
 
@@ -84,8 +83,8 @@ module SGS
84
83
  loc = Location.new
85
84
  sin_angle = Math.sin(bearing.angle)
86
85
  cos_angle = Math.cos(bearing.angle)
87
- sin_dstr = Math.sin(bearing.distance / SGS::EARTH_RADIUS)
88
- cos_dstr = Math.cos(bearing.distance / SGS::EARTH_RADIUS)
86
+ sin_dstr = Math.sin(bearing.distance / EARTH_RADIUS)
87
+ cos_dstr = Math.cos(bearing.distance / EARTH_RADIUS)
89
88
  sin_lat1 = Math.sin(@latitude)
90
89
  cos_lat1 = Math.cos(@latitude)
91
90
  loc.latitude = Math.asin(sin_lat1*cos_dstr + cos_lat1*sin_dstr*cos_angle)
@@ -113,10 +112,33 @@ module SGS
113
112
  end
114
113
 
115
114
  #
116
- # Set the lat/long from a hash.
115
+ # Parse the lat/long values passed as a string. This function
116
+ # should be able to handle any type of lat/long string, in most
117
+ # general formats. See :to_s for examples.
117
118
  def parse(data)
118
- @latitude = ll_parse(data["latitude"], "NS")
119
- @longitude = ll_parse(data["longitude"], "EW")
119
+ llvals = data.split /,/
120
+ if llvals.count == 1
121
+ #
122
+ # Must be space-separated. Try that...
123
+ llvals = data.split(/ /)
124
+ if llvals.count != 2
125
+ raise ArgumentError.new "Cannot split lat/long values"
126
+ end
127
+ elsif llvals.count != 2
128
+ #
129
+ # Too many comma separators.
130
+ raise ArgumentError.new "Invalid lat/long values"
131
+ end
132
+ self.latitude_d = _ll_parse(llvals[0], "NS")
133
+ self.longitude_d = _ll_parse(llvals[1], "EW")
134
+ true
135
+ end
136
+
137
+ #
138
+ # Parse the lat/long from a hash object.
139
+ def parse_hash(data = {})
140
+ self.latitude_d = _ll_parse(data["latitude"], "NS")
141
+ self.longitude_d = _ll_parse(data["longitude"], "EW")
120
142
  end
121
143
 
122
144
  #
@@ -128,15 +150,21 @@ module SGS
128
150
  #
129
151
  # Convert the lat/long to a hash.
130
152
  def to_hash
131
- {"latitude" => ll_to_s(latitude, "NS"),
132
- "longitude" => ll_to_s(longitude, "EW")}
153
+ {"latitude" => latitude_d.round(6), "longitude" => longitude_d.round(6)}
133
154
  end
134
155
 
135
156
  #
136
- # Display the lat/long as a useful string (in degrees).
137
- def to_s
157
+ # Display the lat/long as a useful string (in degrees). Output
158
+ # formats are as follows (default is :d):
159
+ # :d "48.104051, -7.282614"
160
+ # :dd "48.104051N, 7.282614W"
161
+ # :dmm "48 6.243060N, 7 16.956840W"
162
+ # :dms "48 6 14.583600N, 7 16 57.410400W"
163
+ def to_s(opts = {})
138
164
  if valid?
139
- "%s, %s" % [ll_to_s(@latitude, "NS"), ll_to_s(@longitude, "EW")]
165
+ lat_str = _ll_conv(latitude_d.round(6), "NS", opts)
166
+ lon_str = _ll_conv(longitude_d.round(6), "EW", opts)
167
+ "#{lat_str}, #{lon_str}"
140
168
  else
141
169
  "unknown"
142
170
  end
@@ -151,29 +179,39 @@ module SGS
151
179
  end
152
180
 
153
181
  #
154
- # Helper functions for working in degrees.
182
+ # Return the latitude in degrees.
155
183
  def latitude_d
156
184
  Bearing.rtod @latitude
157
185
  end
158
186
 
187
+ #
188
+ # Set the latitude using a value in degrees.
159
189
  def latitude_d=(val)
160
190
  @latitude = Bearing.dtor val
161
191
  end
162
192
 
193
+ #
194
+ # Produce a latitude array for NMEA output.
163
195
  def latitude_array(fmt = nil)
164
- make_ll_array latitude_d, "NS", fmt
196
+ _make_ll_array latitude_d, "NS", fmt
165
197
  end
166
198
 
199
+ #
200
+ # Return the longitude in degrees.
167
201
  def longitude_d
168
202
  Bearing.rtod @longitude
169
203
  end
170
204
 
205
+ #
206
+ # Set the longitude using a value in degrees.
171
207
  def longitude_d=(val)
172
208
  @longitude = Bearing.dtor val
173
209
  end
174
210
 
211
+ #
212
+ # Produce a longitude array for NMEA output.
175
213
  def longitude_array(fmt = nil)
176
- make_ll_array longitude_d, "EW", fmt
214
+ _make_ll_array longitude_d, "EW", fmt
177
215
  end
178
216
 
179
217
  #
@@ -182,53 +220,72 @@ module SGS
182
220
  Bearing.compute(self, loc)
183
221
  end
184
222
 
185
- private
186
- #
187
- # Parse a string into a lat or long. The latitude or longitude string
188
- # should be of the format dd mm ss.sssssC (where C is NS or EW). Note
189
- # that latitude and longitude are saved internally in radians.
190
- def ll_parse(value, nsew)
191
- args = value.split
192
- dir = args[-1].gsub(/[\d\. ]+/, '').upcase
193
- args.map! {|val| val.to_f}
194
- val = args.shift
195
- val = val + args.shift / 60.0 if args.length > 0
196
- val = val + args.shift / 3600.0 if args.length > 0
197
- Bearing.dtor val * ((nsew.index(dir) == 1) ? -1 : 1)
198
- end
223
+ private
224
+ #
225
+ # Parse a latitude or longitude value, from a wide range of
226
+ # formats. Should handle D.ddd, D M.mmm and D M S.sss values.
227
+ # Can also strip out the special degrees unicode, as well as
228
+ # single and double quotes.
229
+ def _ll_parse(arg, nsew)
230
+ return arg.to_f if arg.kind_of? Float or arg.kind_of? Integer
231
+ str = arg.chomp.gsub /[\u00B0'"]/, ' '
232
+ if str[-1].upcase =~ /[#{nsew}]/
233
+ sign = (str[-1].upcase == nsew[1]) ? -1 : 1
234
+ str[-1] = ' '
235
+ else
236
+ sign = 1
237
+ end
238
+ args = str.split
239
+ raise ArgumentError.new "Cannot parse lat/long value" if args.count > 3
240
+ value = 0.0
241
+ (args.count - 1).downto(0).each do |idx|
242
+ value = args[idx].to_f + value / 60.0
243
+ end
244
+ sign * value
245
+ end
199
246
 
200
- #
201
- # Convert a latitude or longitude into an ASCII string of the form:
202
- # dd mm ss.ssssssC (where C is NS or EW). The value should be in
203
- # radians.
204
- def ll_to_s(val, str)
205
- val = Bearing.rtod val
206
- if val < 0.0
207
- chr = str[1]
208
- val = -val
209
- else
210
- chr = str[0]
247
+ #
248
+ # Convert a latitude/longitude to a string. Can specify
249
+ # multiple output formats such as :d, :dd, :dmm, :dms to
250
+ # format according to various different styles.
251
+ def _ll_conv(value, nsew, opts)
252
+ format = opts[:format] || :d
253
+ return "%.6f" % value if format == :d
254
+ if value < 0.0
255
+ suffix = nsew[1]
256
+ value = -value
257
+ else
258
+ suffix = nsew[0]
259
+ end
260
+ case opts[:format] || :d
261
+ when :dd
262
+ "%.6f%s" % [value, suffix]
263
+ when :dmm
264
+ dd = value.to_i
265
+ mm = (value - dd.to_f) * 60.0
266
+ "%d %.6f%s" % [dd, mm, suffix]
267
+ when :dms
268
+ dd = value.to_i
269
+ value = (value - dd.to_f) * 60.0
270
+ mm = value.to_i
271
+ ss = (value - mm.to_f) * 60.0
272
+ "%d %d %.6f%s" % [dd, mm, ss, suffix]
273
+ end
211
274
  end
212
- deg = val.to_i
213
- val = (val - deg.to_f) * 60.0
214
- min = val.to_i
215
- sec = (val - min.to_f) * 60.0
216
- "%d %d %8.6f%c" % [deg, min, sec, chr]
217
- end
218
275
 
219
- #
220
- # Create a Lat/Long array suitable for an NMEA output
221
- def make_ll_array(val, nsew, fmt = nil)
222
- fmt ||= "%02d%07.4f"
223
- if (val < 0)
224
- val = -val
225
- ne = nsew[1]
226
- else
227
- ne = nsew[0]
276
+ #
277
+ # Create a Lat/Long array suitable for an NMEA output.
278
+ def _make_ll_array(val, nsew, fmt = nil)
279
+ fmt ||= "%02d%07.4f"
280
+ if (val < 0)
281
+ val = -val
282
+ suffix = nsew[1]
283
+ else
284
+ suffix = nsew[0]
285
+ end
286
+ deg = val.to_i
287
+ val = (val - deg) * 60
288
+ [fmt % [deg, val], suffix.chr]
228
289
  end
229
- deg = val.to_i
230
- val = (val - deg) * 60
231
- [fmt % [deg, val], ne.chr]
232
- end
233
290
  end
234
291
  end
data/lib/sgs/logger.rb CHANGED
@@ -37,6 +37,6 @@
37
37
  # Routines for handling sailboat navigation and route planning.
38
38
  #
39
39
  module SGS
40
- class Logger < RedisBase
40
+ class Logger
41
41
  end
42
42
  end