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 +4 -4
- data/lib/sgs/alarm.rb +2 -2
- data/lib/sgs/bearing.rb +31 -3
- data/lib/sgs/config.rb +0 -1
- data/lib/sgs/course.rb +18 -5
- data/lib/sgs/diagnostics.rb +1 -1
- data/lib/sgs/gps.rb +23 -3
- data/lib/sgs/location.rb +116 -59
- data/lib/sgs/logger.rb +1 -1
- data/lib/sgs/mission.rb +62 -185
- data/lib/sgs/mission_status.rb +16 -4
- data/lib/sgs/navigate.rb +203 -79
- data/lib/sgs/nmea.rb +3 -2
- data/lib/sgs/otto.rb +190 -78
- data/lib/sgs/redis_base.rb +9 -9
- data/lib/sgs/report.rb +2 -2
- data/lib/sgs/rpc.rb +5 -6
- data/lib/sgs/version.rb +1 -1
- data/lib/sgs/waypoint.rb +3 -2
- data/sgslib.gemspec +3 -3
- metadata +7 -9
- data/exe/sgs_nav +0 -43
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e111aaff75b5c948f935ac6f76747487c2a6acf2c4e661e07b5e670aa48024b
|
4
|
+
data.tar.gz: 8b0dae14c6a69c2222e38522d3f7e562b45c33e754cf5019a9f0b081d6191fc6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 =
|
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 =
|
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
|
-
|
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
|
-
|
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
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
|
-
|
155
|
-
|
156
|
-
|
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
|
-
|
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|
|
data/lib/sgs/diagnostics.rb
CHANGED
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 =
|
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 =
|
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 /
|
88
|
-
cos_dstr = Math.cos(bearing.distance /
|
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
|
-
#
|
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
|
-
|
119
|
-
|
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" =>
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
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
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
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
|