sgslib 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +40 -0
- data/Rakefile +7 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/sgs/alarm.rb +103 -0
- data/lib/sgs/command.rb +167 -0
- data/lib/sgs/gps.rb +53 -0
- data/lib/sgs/location.rb +328 -0
- data/lib/sgs/logger.rb +96 -0
- data/lib/sgs/mission.rb +362 -0
- data/lib/sgs/navigate.rb +141 -0
- data/lib/sgs/nmea.rb +138 -0
- data/lib/sgs/otto.rb +163 -0
- data/lib/sgs/polar.rb +86 -0
- data/lib/sgs/redis_base.rb +251 -0
- data/lib/sgs/setup.rb +40 -0
- data/lib/sgs/timing.rb +45 -0
- data/lib/sgs/version.rb +3 -0
- data/lib/sgs/waypoint.rb +115 -0
- data/lib/sgslib.rb +46 -0
- data/sgslib.gemspec +35 -0
- metadata +129 -0
data/lib/sgs/otto.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2013, Kalopa Research. All rights reserved. This is free
|
3
|
+
# software; you can redistribute it and/or modify it under the terms of the
|
4
|
+
# GNU General Public License as published by the Free Software Foundation;
|
5
|
+
# either version 2, or (at your option) any later version.
|
6
|
+
#
|
7
|
+
# It is distributed in the hope that it will be useful, but WITHOUT
|
8
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
9
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
10
|
+
# for more details.
|
11
|
+
#
|
12
|
+
# You should have received a copy of the GNU General Public License along
|
13
|
+
# with this product; see the file COPYING. If not, write to the Free
|
14
|
+
# Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
15
|
+
#
|
16
|
+
# THIS SOFTWARE IS PROVIDED BY KALOPA RESEARCH "AS IS" AND ANY EXPRESS OR
|
17
|
+
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
18
|
+
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
19
|
+
# IN NO EVENT SHALL KALOPA RESEARCH BE LIABLE FOR ANY DIRECT, INDIRECT,
|
20
|
+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
21
|
+
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
22
|
+
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
23
|
+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
24
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
25
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
26
|
+
#
|
27
|
+
|
28
|
+
##
|
29
|
+
# Routines for interfacing with the low-level microcontroller.
|
30
|
+
#
|
31
|
+
module SGS
|
32
|
+
class Otto < RedisBase
|
33
|
+
attr_reader :rudder, :sail, :compass, :twa
|
34
|
+
attr_accessor :bvcal, :bical, :btcal, :svcal
|
35
|
+
|
36
|
+
def initialize
|
37
|
+
@bvcal = 0.0
|
38
|
+
@bical = 0.0
|
39
|
+
@btcal = 0.0
|
40
|
+
@svcal = 0.0
|
41
|
+
@rudder = read_rudder
|
42
|
+
@sail = read_sail
|
43
|
+
@compass = read_compass
|
44
|
+
@twa = read_twa
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Set the required rudder angle. Input values range from +/- 40 degrees
|
50
|
+
def rudder=(val)
|
51
|
+
val = -39.9 if val < -39.9
|
52
|
+
val = 39.9 if val > 39.9
|
53
|
+
return if @rudder == val
|
54
|
+
@rudder = val
|
55
|
+
intval = (@rudder * 120.0 / 40.0).to_int + 128
|
56
|
+
puts "New rudder value: #{intval} (#{@rudder} degrees)"
|
57
|
+
send_command(SET_RUDDER, intval)
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Set the required sail angle. Input values range from 0 -> 90 degrees.
|
62
|
+
def sail=(val)
|
63
|
+
val = 0.0 if val < 0.0
|
64
|
+
val = 90.0 if val > 90.0
|
65
|
+
return if @sail == val
|
66
|
+
@sail = val
|
67
|
+
intval = (@sail * 256.0 / 90.0).to_int
|
68
|
+
puts "New sail angle: #{intval} (#{@sail} degrees)"
|
69
|
+
send_command(SET_SAIL, intval)
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# Set the required compass reading. Input values range from 0 -> 359 degrees
|
74
|
+
def compass=(val)
|
75
|
+
while val < 0.0
|
76
|
+
val += 360.0
|
77
|
+
end
|
78
|
+
val %= 360.0
|
79
|
+
return if @compass == val
|
80
|
+
@compass = val
|
81
|
+
intval = (@compass * 256.0 / 360.0).to_int
|
82
|
+
puts "New compass heading: #{intval} (#{@compass} degrees)"
|
83
|
+
send_command(SET_COMPASS, intval)
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# Set the required true wind angle. Input values range from +/- 180 degrees
|
88
|
+
def twa=(val)
|
89
|
+
val = -179.9 if val < -179.9
|
90
|
+
val = 179.9 if val > 179.9
|
91
|
+
return if @twa == val
|
92
|
+
@twa = val
|
93
|
+
val = 360.0 + val if val < 0.0
|
94
|
+
intval = (val * 256.0 / 360.0).to_int
|
95
|
+
puts "New TWA: #{intval} (#{@twa} degrees)"
|
96
|
+
send_command(SET_TWA, intval)
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# Read the uptime clock
|
101
|
+
def read_uptime
|
102
|
+
intval = send_command(READ_UPTIME)
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Read the battery voltage
|
107
|
+
def read_battery_volts
|
108
|
+
intval = send_command(READ_BATTERY_VOLTAGE)
|
109
|
+
intval.to_f * @bvcal / 1024.0
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# Read the battery current
|
114
|
+
def read_battery_current
|
115
|
+
intval = send_command(READ_BATTERY_CURRENT)
|
116
|
+
intval.to_f * @bical / 1024.0
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Read the boat temperature
|
121
|
+
def read_boat_temperature
|
122
|
+
intval = send_command(READ_BOAT_TEMPERATURE)
|
123
|
+
intval.to_f * @btcal / 1024.0
|
124
|
+
end
|
125
|
+
|
126
|
+
#
|
127
|
+
# Read the solar voltage
|
128
|
+
def read_solar_volts
|
129
|
+
intval = send_command(READ_SOLAR_VOLTAGE)
|
130
|
+
intval.to_f * @svcal / 1024.0
|
131
|
+
end
|
132
|
+
|
133
|
+
#
|
134
|
+
# Read the actual compass value
|
135
|
+
def read_compass
|
136
|
+
intval = send_command(GET_COMPASS)
|
137
|
+
intval.to_f * 360.0 / 256.0
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
# Read the actual TWA value
|
142
|
+
def read_twa
|
143
|
+
intval = send_command(GET_TWA)
|
144
|
+
val = intval.to_f * 180.0 / 128.0
|
145
|
+
val = val - 360.0 if val > 180.0
|
146
|
+
val
|
147
|
+
end
|
148
|
+
|
149
|
+
#
|
150
|
+
# Read the actual boat pitch
|
151
|
+
def read_pitch
|
152
|
+
intval = send_command(GET_PITCH)
|
153
|
+
intval.to_f
|
154
|
+
end
|
155
|
+
|
156
|
+
#
|
157
|
+
# Read the actual boat heel
|
158
|
+
def read_heel
|
159
|
+
intval = send_command(GET_HEEL)
|
160
|
+
intval.to_f
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
data/lib/sgs/polar.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2013, Kalopa Research. All rights reserved. This is free
|
3
|
+
# software; you can redistribute it and/or modify it under the terms of the
|
4
|
+
# GNU General Public License as published by the Free Software Foundation;
|
5
|
+
# either version 2, or (at your option) any later version.
|
6
|
+
#
|
7
|
+
# It is distributed in the hope that it will be useful, but WITHOUT
|
8
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
9
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
10
|
+
# for more details.
|
11
|
+
#
|
12
|
+
# You should have received a copy of the GNU General Public License along
|
13
|
+
# with this product; see the file COPYING. If not, write to the Free
|
14
|
+
# Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
15
|
+
#
|
16
|
+
# THIS SOFTWARE IS PROVIDED BY KALOPA RESEARCH "AS IS" AND ANY EXPRESS OR
|
17
|
+
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
18
|
+
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
19
|
+
# IN NO EVENT SHALL KALOPA RESEARCH BE LIABLE FOR ANY DIRECT, INDIRECT,
|
20
|
+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
21
|
+
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
22
|
+
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
23
|
+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
24
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
25
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
26
|
+
#
|
27
|
+
|
28
|
+
##
|
29
|
+
# Routines for handling sailboat navigation and route planning.
|
30
|
+
#
|
31
|
+
# The code on this page was derived from formulae on the Movable Type site:
|
32
|
+
# http://www.movable-type.co.uk/scripts/latlong.html
|
33
|
+
#
|
34
|
+
|
35
|
+
require 'date'
|
36
|
+
|
37
|
+
module SGS
|
38
|
+
##
|
39
|
+
#
|
40
|
+
# A class to handle boat polar calculations. It takes a range of polars
|
41
|
+
# as polynomials and then applies them.
|
42
|
+
class Polar
|
43
|
+
# Right now, we have one polar - from a Catalina 22.
|
44
|
+
# Note that the speed is the same, regardless of the tack.
|
45
|
+
STANDARD = [
|
46
|
+
-3.15994,
|
47
|
+
23.8741,
|
48
|
+
-27.4595,
|
49
|
+
16.4868,
|
50
|
+
-5.15663,
|
51
|
+
0.743936,
|
52
|
+
-0.0344716
|
53
|
+
].freeze
|
54
|
+
|
55
|
+
#
|
56
|
+
# set up the default values
|
57
|
+
def initialize
|
58
|
+
@curve = STANDARD
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Compute the hull speed from the polar
|
63
|
+
# :awa: is the apparent wind angle (in radians)
|
64
|
+
# :wspeed: is the wind speed (in knots)
|
65
|
+
def speed(awa, wspeed = 0.0)
|
66
|
+
awa = awa.to_f.abs
|
67
|
+
ap = 1.0
|
68
|
+
@speed = 0.0
|
69
|
+
@curve.each do |poly_val|
|
70
|
+
@speed += poly_val * ap
|
71
|
+
ap *= awa
|
72
|
+
end
|
73
|
+
@speed /= 1.529955
|
74
|
+
if @speed < 0.0
|
75
|
+
@speed = 0.0
|
76
|
+
end
|
77
|
+
@speed
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Calculate the VMG from the angle to the mark.
|
82
|
+
def vmg(alpha)
|
83
|
+
Math::cos(alpha) * @speed
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2013, Kalopa Research. All rights reserved. This is free
|
3
|
+
# software; you can redistribute it and/or modify it under the terms of the
|
4
|
+
# GNU General Public License as published by the Free Software Foundation;
|
5
|
+
# either version 2, or (at your option) any later version.
|
6
|
+
#
|
7
|
+
# It is distributed in the hope that it will be useful, but WITHOUT
|
8
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
9
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
10
|
+
# for more details.
|
11
|
+
#
|
12
|
+
# You should have received a copy of the GNU General Public License along
|
13
|
+
# with this product; see the file COPYING. If not, write to the Free
|
14
|
+
# Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
15
|
+
#
|
16
|
+
# THIS SOFTWARE IS PROVIDED BY KALOPA RESEARCH "AS IS" AND ANY EXPRESS OR
|
17
|
+
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
18
|
+
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
19
|
+
# IN NO EVENT SHALL KALOPA RESEARCH BE LIABLE FOR ANY DIRECT, INDIRECT,
|
20
|
+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
21
|
+
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
22
|
+
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
23
|
+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
24
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
25
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
26
|
+
#
|
27
|
+
|
28
|
+
##
|
29
|
+
# Routines for manipulating data in Redis.
|
30
|
+
#
|
31
|
+
require 'redis'
|
32
|
+
|
33
|
+
module SGS
|
34
|
+
class RedisBase
|
35
|
+
##
|
36
|
+
# The base (inherited) class for dealing with Redis data for
|
37
|
+
# the navigation system. Each model class inherits this parent,
|
38
|
+
# and gets an update count for free.
|
39
|
+
|
40
|
+
#
|
41
|
+
# Initialize the base class.
|
42
|
+
def initialize
|
43
|
+
$redis = Redis.new unless $redis
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Initialize the (sub-)class variables in Redis.
|
48
|
+
def self.setup
|
49
|
+
cls = new
|
50
|
+
cls.instance_variables.each do |var|
|
51
|
+
val = cls.instance_variable_get var
|
52
|
+
if val.kind_of? Array
|
53
|
+
#
|
54
|
+
# Arrays are handled separately. We instead
|
55
|
+
# use the index to create a series of 'fooN'
|
56
|
+
# variables.
|
57
|
+
val.size.times do |idx|
|
58
|
+
var_init var, val, idx
|
59
|
+
end
|
60
|
+
else
|
61
|
+
var_init var, val
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Initialize a Redis variable.
|
68
|
+
def self.var_init(var, val, idx = nil)
|
69
|
+
cls = new
|
70
|
+
$redis.setnx cls.make_redis_name(var, :idx => idx), self.to_redis(var, val, idx)
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Load the instance variables for the class.
|
75
|
+
def self.load()
|
76
|
+
cls = new
|
77
|
+
cls.load
|
78
|
+
cls
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# Load the instance variables for the class.
|
83
|
+
def load
|
84
|
+
instance_variables.each do |var|
|
85
|
+
lval = instance_variable_get var
|
86
|
+
if lval.kind_of? Array
|
87
|
+
#
|
88
|
+
# It's an array - iterate and read the values.
|
89
|
+
lval.size.times do |idx|
|
90
|
+
idx_val = lval[idx]
|
91
|
+
lval[idx] = redis_read_var var, idx_val.class, :idx => idx
|
92
|
+
end
|
93
|
+
elsif lval.kind_of? Location
|
94
|
+
#
|
95
|
+
# ::FIXME:: Yes. this is a hack.
|
96
|
+
# This belongs in the Location class itself. It's arguable that a lot
|
97
|
+
# of the stuff belongs in the parent class. Perhaps the thing to do
|
98
|
+
# is ask the class to return a hash of names and values, and then
|
99
|
+
# set them accordingly.
|
100
|
+
lval.latitude = redis_read_var var, Float, :name => 'latitude'
|
101
|
+
lval.longitude = redis_read_var var, Float, :name => 'longitude'
|
102
|
+
else
|
103
|
+
lval = redis_read_var var, lval.class
|
104
|
+
end
|
105
|
+
instance_variable_set var, lval
|
106
|
+
end
|
107
|
+
true
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Write the instance to Redis. IWe produce a Hash of keys and values. From
|
112
|
+
# this and inside a Redis "multi" block, we set all the values and finally
|
113
|
+
# increment the count. @count is actually an instance variable of redis_base
|
114
|
+
def save
|
115
|
+
#
|
116
|
+
# Get the Hash of settable values (including count).
|
117
|
+
var_list = {}
|
118
|
+
self.instance_variables.each do |var|
|
119
|
+
lval = self.instance_variable_get var
|
120
|
+
if lval.kind_of? Array
|
121
|
+
lval.size.times do |idx|
|
122
|
+
var_list[make_redis_name(var, :idx => idx)] = self.class.to_redis var, lval, idx
|
123
|
+
end
|
124
|
+
elsif lval.kind_of? Location
|
125
|
+
#
|
126
|
+
# ::FIXME:: Yes. this is a hack. see 'load' above.
|
127
|
+
var_list[make_redis_name(var, :name => 'latitude')] = lval.latitude
|
128
|
+
var_list[make_redis_name(var, :name => 'longitude')] = lval.longitude
|
129
|
+
else
|
130
|
+
var_list[make_redis_name(var)] = self.class.to_redis var, lval
|
131
|
+
end
|
132
|
+
end
|
133
|
+
#
|
134
|
+
# Inside a multi-block, set all the variables and increment
|
135
|
+
# the count.
|
136
|
+
$redis.multi do
|
137
|
+
var_list.each do |key, value|
|
138
|
+
$redis.set key, value
|
139
|
+
end
|
140
|
+
$redis.incr count_name
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
#
|
145
|
+
# Publish the count onto a Redis pub/sub channel. The trick to subscribing
|
146
|
+
# to a channel is that whenever there's a publish, the new count is
|
147
|
+
# published as a string. If you subscribe to the channel (usually the
|
148
|
+
# class name), you can remember the last received count and decide if
|
149
|
+
# there is fresh data. Or, you can just act anyway.
|
150
|
+
def publish
|
151
|
+
$redis.publish self.class.redis_handle, count.to_s
|
152
|
+
end
|
153
|
+
|
154
|
+
#
|
155
|
+
# Subscribe to messages from this particular channel. Each count is sent
|
156
|
+
# to the code block. It's up to the called code block to decide if the
|
157
|
+
# count has changed and if so, to read the data from Redis.
|
158
|
+
def self.subscribe
|
159
|
+
redis = Redis.new
|
160
|
+
redis.subscribe(redis_handle) do |on|
|
161
|
+
on.message do |channel, count|
|
162
|
+
yield count.to_i
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
#
|
168
|
+
# Combined save and publish
|
169
|
+
def save_and_publish
|
170
|
+
save && publish
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
# Retrieve the count
|
175
|
+
def count
|
176
|
+
$redis.get count_name
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
# What is the official name of the count instance variable
|
181
|
+
def count_name
|
182
|
+
make_redis_name "@count"
|
183
|
+
end
|
184
|
+
|
185
|
+
#
|
186
|
+
# Get an instance variable value from a Redis value.
|
187
|
+
def redis_read_var(var, klass, opts = {})
|
188
|
+
redis_name = make_redis_name var, opts
|
189
|
+
redis_val = $redis.get redis_name
|
190
|
+
redis_val = nil if redis_val == ""
|
191
|
+
if redis_val
|
192
|
+
if not klass or klass == NilClass
|
193
|
+
redis_val = true if redis_val == "true"
|
194
|
+
redis_val = false if redis_val == "false"
|
195
|
+
klass = Float if redis_val =~ /[0-9+-\.]+/
|
196
|
+
end
|
197
|
+
case
|
198
|
+
when klass == Time
|
199
|
+
redis_val = Time.at(redis_val.to_f).gmtime
|
200
|
+
when klass == Fixnum
|
201
|
+
redis_val = redis_val.to_i
|
202
|
+
when klass == Float
|
203
|
+
redis_val = redis_val.to_f
|
204
|
+
when klass == FalseClass
|
205
|
+
redis_val = false
|
206
|
+
when klass == TrueClass
|
207
|
+
redis_val = true
|
208
|
+
end
|
209
|
+
end
|
210
|
+
redis_val
|
211
|
+
end
|
212
|
+
|
213
|
+
#
|
214
|
+
# Set a variable - convert from Ruby format to Redis format.
|
215
|
+
# As of now, we only convert times. Floats and integers are
|
216
|
+
# dealt with by Redis (converted to strings, unfortunately).
|
217
|
+
def self.to_redis(var, local_val, idx = nil)
|
218
|
+
if local_val
|
219
|
+
local_val = local_val[idx] if idx
|
220
|
+
if local_val.class == Time
|
221
|
+
local_val = local_val.to_f
|
222
|
+
end
|
223
|
+
end
|
224
|
+
local_val
|
225
|
+
end
|
226
|
+
|
227
|
+
#
|
228
|
+
# Translate an instance variable into a Redis key name.
|
229
|
+
# This is simply the class name, a dot and the instance
|
230
|
+
# variable. A bit of jiggery-pokery to convert the
|
231
|
+
# instance variable into a proper name. Probably an easier
|
232
|
+
# way to do this, but...
|
233
|
+
#
|
234
|
+
# Instance method for above
|
235
|
+
def make_redis_name(var, opts = {})
|
236
|
+
var_name = opts[:name] || var.to_s.gsub(/^@/, '')
|
237
|
+
prefix = opts[:prefix] || self.class.redis_handle
|
238
|
+
if opts[:idx]
|
239
|
+
"#{prefix}.#{var_name}#{opts[:idx] + 1}"
|
240
|
+
else
|
241
|
+
"#{prefix}.#{var_name}"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
#
|
246
|
+
# Convert the class name into something suitable for Redis
|
247
|
+
def self.redis_handle
|
248
|
+
self.name.downcase.gsub(/^sgs::/, 'sgs_')
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
data/lib/sgs/setup.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Copyright (c) 2013, Kalopa Research. All rights reserved. This is free
|
4
|
+
# software; you can redistribute it and/or modify it under the terms of the
|
5
|
+
# GNU General Public License as published by the Free Software Foundation;
|
6
|
+
# either version 2, or (at your option) any later version.
|
7
|
+
#
|
8
|
+
# It is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
11
|
+
# for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License along
|
14
|
+
# with this product; see the file COPYING. If not, write to the Free
|
15
|
+
# Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY KALOPA RESEARCH "AS IS" AND ANY EXPRESS OR
|
18
|
+
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
19
|
+
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
20
|
+
# IN NO EVENT SHALL KALOPA RESEARCH BE LIABLE FOR ANY DIRECT, INDIRECT,
|
21
|
+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
22
|
+
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
23
|
+
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
24
|
+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
25
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
26
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
#
|
28
|
+
$: << '../sgslib'
|
29
|
+
|
30
|
+
require 'sgslib'
|
31
|
+
|
32
|
+
##
|
33
|
+
# Initialise the REDIS system.
|
34
|
+
#
|
35
|
+
SGS::Alarm.setup
|
36
|
+
SGS::GPS.setup
|
37
|
+
SGS::Otto.setup
|
38
|
+
SGS::Timing.setup
|
39
|
+
SGS::Waypoint.setup
|
40
|
+
#SGS::Mission.setup
|
data/lib/sgs/timing.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2013, Kalopa Research. All rights reserved. This is free
|
3
|
+
# software; you can redistribute it and/or modify it under the terms of the
|
4
|
+
# GNU General Public License as published by the Free Software Foundation;
|
5
|
+
# either version 2, or (at your option) any later version.
|
6
|
+
#
|
7
|
+
# It is distributed in the hope that it will be useful, but WITHOUT
|
8
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
9
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
10
|
+
# for more details.
|
11
|
+
#
|
12
|
+
# You should have received a copy of the GNU General Public License along
|
13
|
+
# with this product; see the file COPYING. If not, write to the Free
|
14
|
+
# Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
15
|
+
#
|
16
|
+
# THIS SOFTWARE IS PROVIDED BY KALOPA RESEARCH "AS IS" AND ANY EXPRESS OR
|
17
|
+
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
18
|
+
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
19
|
+
# IN NO EVENT SHALL KALOPA RESEARCH BE LIABLE FOR ANY DIRECT, INDIRECT,
|
20
|
+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
21
|
+
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
22
|
+
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
23
|
+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
24
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
25
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
26
|
+
#
|
27
|
+
|
28
|
+
##
|
29
|
+
# Routines for handling sailboat navigation and route planning.
|
30
|
+
#
|
31
|
+
module SGS
|
32
|
+
class Timing < RedisBase
|
33
|
+
attr_accessor :status, :boot, :briefing, :m_start, :m_abort, :m_complete
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@status = nil
|
37
|
+
@boot = Time.at(0)
|
38
|
+
@briefing = nil
|
39
|
+
@m_start = nil
|
40
|
+
@m_abort = nil
|
41
|
+
@m_complete = nil
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/sgs/version.rb
ADDED
data/lib/sgs/waypoint.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2013, Kalopa Research. All rights reserved. This is free
|
3
|
+
# software; you can redistribute it and/or modify it under the terms of the
|
4
|
+
# GNU General Public License as published by the Free Software Foundation;
|
5
|
+
# either version 2, or (at your option) any later version.
|
6
|
+
#
|
7
|
+
# It is distributed in the hope that it will be useful, but WITHOUT
|
8
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
9
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
10
|
+
# for more details.
|
11
|
+
#
|
12
|
+
# You should have received a copy of the GNU General Public License along
|
13
|
+
# with this product; see the file COPYING. If not, write to the Free
|
14
|
+
# Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
15
|
+
#
|
16
|
+
# THIS SOFTWARE IS PROVIDED BY KALOPA RESEARCH "AS IS" AND ANY EXPRESS OR
|
17
|
+
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
18
|
+
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
19
|
+
# IN NO EVENT SHALL KALOPA RESEARCH BE LIABLE FOR ANY DIRECT, INDIRECT,
|
20
|
+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
21
|
+
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
22
|
+
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
23
|
+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
24
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
25
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
26
|
+
#
|
27
|
+
|
28
|
+
##
|
29
|
+
# Routines for handling sailboat navigation and route planning.
|
30
|
+
#
|
31
|
+
require 'date'
|
32
|
+
|
33
|
+
module SGS
|
34
|
+
#
|
35
|
+
# Waypoint, Attractor, and Repellor definitions
|
36
|
+
class Waypoint < RedisBase
|
37
|
+
attr_accessor :location, :chord, :type, :name, :bearing, :distance
|
38
|
+
|
39
|
+
#
|
40
|
+
# Types of waypoints
|
41
|
+
START_LINE = 0
|
42
|
+
FINISH_LINE = 1
|
43
|
+
WAYPOINT = 2
|
44
|
+
REPELLOR = 3
|
45
|
+
|
46
|
+
def initialize(location = nil, chord = nil, name = "", type = WAYPOINT)
|
47
|
+
@location = location || Location.new
|
48
|
+
@chord = chord
|
49
|
+
@type = type
|
50
|
+
@name = name
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Calculate the back-vector from the waypoint to the specified position
|
55
|
+
# Calculate the adjusted distance between the mark and the set location.
|
56
|
+
# Check to see if our back-bearing from the waypoint to our location is
|
57
|
+
# inside the chord of the waypoint. If so, reduce the distance to the
|
58
|
+
# waypoint by the length of the chord.
|
59
|
+
def compute_bearing(loc)
|
60
|
+
@bearing = Bearing.calculate(loc, @location)
|
61
|
+
@distance = @bearing.distance
|
62
|
+
d = Bearing.new(@bearing.back_angle - @chord.angle, @distance)
|
63
|
+
# A chord angle of 0 gives a semicircle from 180 to 360 degrees
|
64
|
+
# (inclusive). If our approach angle is within range, then reduce our
|
65
|
+
# distance to the mark by the chord distance (radius).
|
66
|
+
unless d.angle > 0.0 and d.angle < Math::PI
|
67
|
+
@distance -= @chord.distance
|
68
|
+
end
|
69
|
+
@distance
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# Is the waypoint in scope? In other words, is our angle inside the chord.
|
74
|
+
def in_scope?
|
75
|
+
puts "In-scope distance is %f..." % @distance
|
76
|
+
@distance <= 0.0
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# Pretty version of the waypoint.
|
81
|
+
def to_s
|
82
|
+
"'#{@name}' at #{@location} => #{chord}"
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# Display a string for a KML file
|
87
|
+
def to_kml
|
88
|
+
c2 = @chord.clone
|
89
|
+
c2.angle += Math::PI
|
90
|
+
pos1 = @location.calculate(@chord)
|
91
|
+
pos2 = @location.calculate(c2)
|
92
|
+
"#{pos1.to_kml(',')} #{pos2.to_kml(',')}"
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
# Show the axis line for the waypoint (as a KML)
|
97
|
+
def to_axis_kml
|
98
|
+
c2 = @chord.clone
|
99
|
+
c2.angle += 1.5 * Math::PI
|
100
|
+
pos1 = @location.calculate(c2)
|
101
|
+
"#{@location.to_kml(',')} #{pos1.to_kml(',')}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Store the track over the ground.
|
107
|
+
class Track
|
108
|
+
attr_accessor :time, :location
|
109
|
+
|
110
|
+
def initialize(time, location)
|
111
|
+
@time = time
|
112
|
+
@location = location
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|