sgslib 1.5.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/sgs/otto.rb CHANGED
@@ -31,6 +31,13 @@
31
31
  # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
32
  #
33
33
  # ABSTRACT
34
+ # This daemon handles all serial I/O with the low-level board (Otto). Otto
35
+ # publishes various status messages at regular intervals, and has a series
36
+ # of registers which can be used to alter the low-level operational state.
37
+ # This daemon code has two threads. One thread listens for RPCs to update
38
+ # Otto register state, and the other listens for status messages from Otto.
39
+ # The class also has helper functions for converting between Otto data
40
+ # formats (usually 8bit) and internal formats (usually floating point).
34
41
  #
35
42
  require 'serialport'
36
43
  require 'msgpack'
@@ -40,49 +47,87 @@ require 'msgpack'
40
47
  #
41
48
  module SGS
42
49
  class Otto < RedisBase
43
- attr_accessor :raw_rudder, :raw_sail, :raw_compass, :raw_awa, :raw_tc, :raw_ta
44
- attr_accessor :mode, :rudder_m, :rudder_c, :sail_m, :sail_c
50
+ attr_accessor :mode, :serial_port
45
51
  attr_accessor :bv_m, :bv_c, :bi_m, :bi_c, :bt_m, :bt_c, :sv_m, :sv_c
46
- attr_accessor :serial_port
47
- attr_reader :alarm_status, :wind, :compass, :actual_rudder, :actual_sail
52
+ attr_reader :alarm_status
53
+ attr_reader :actual_rudder, :actual_sail
48
54
  attr_reader :otto_mode, :otto_timestamp, :telemetry
49
55
 
56
+ #
57
+ # Updates to Otto are done by setting an 8bit register value, as below.
58
+ ALARM_CLEAR_REGISTER = 0
59
+ MISSION_CONTROL_REGISTER = 1
60
+ MODE_REGISTER = 2
61
+ BUZZER_REGISTER = 3
62
+ RUDDER_ANGLE_REGISTER = 4
63
+ SAIL_ANGLE_REGISTER = 5
64
+ COMPASS_HEADING_REGISTER = 6
65
+ MIN_COMPASS_REGISTER = 7
66
+ MAX_COMPASS_REGISTER =8
67
+ AWA_HEADING_REGISTER = 9
68
+ MIN_AWA_REGISTER = 10
69
+ MAX_AWA_REGISTER = 11
70
+ WAKE_DURATION_REGISTER = 12
71
+ NEXT_WAKEUP_REGISTER = 13
72
+ MAX_REGISTER = 14
73
+
74
+ #
75
+ # This is different from mission mode. This mode defines how Otto should
76
+ # operate. Inert means "do nothing". Diagnostic mode is for the low-level
77
+ # code to run self-checks and calibrations. Manual means that the upper
78
+ # level system controls the rudder and sail angle without any higher-level
79
+ # PID controller. Track compass means that the boat will try to keep the
80
+ # actual compass reading within certain parameters, and track AWA will
81
+ # try to maintain a specific "apparent wind angle".
50
82
  MODE_INERT = 0
51
- MODE_DIAGNOSTICS = 1
83
+ MODE_DIAG = 1
52
84
  MODE_MANUAL = 2
53
- MODE_TRACK_COMPASS = 3
54
- MODE_TRACK_AWA = 4
85
+ MODE_REMOTE = 3
86
+ MODE_TRACK_COMPASS = 4
87
+ MODE_TRACK_AWA = 5
55
88
 
56
- MODE_NAMES = [
57
- "Inert Mode", "Diagnostics Mode", "Manual Control Mode",
58
- "Compass-Tracking Mode", "AWA-Tracking Mode"
59
- ].freeze
89
+ #
90
+ # Define some tweaks for rudder and sail setting. Rudder goes from
91
+ # +/-40 degrees, with zero indicating a straight rudder. On Otto, this
92
+ # translates to 0 (for -40.0), 128 (for the zero position) and 255 (for
93
+ # +40 degrees of rudder). A fully trimmed-in sail is zero and a fully
94
+ # extended sail is 255 (0->100 from a function perspective).
95
+ RUDDER_MAX = 40.0
96
+ RUDDER_MIN = -40.0
97
+ RUDDER_M = 3.175
98
+ RUDDER_C = 128.0
99
+ SAIL_MAX = 100.0
100
+ SAIL_MIN = 0.0
101
+ SAIL_M = 2.55
102
+ SAIL_C = 0.0
60
103
 
61
104
  #
62
- # Set up some useful defaults. We assume rudder goes from 0 to 200 as does
63
- # the sail angle.
105
+ # Set up some useful defaults. We assume rudder goes from 0 to 255 as does
106
+ # the sail angle.
64
107
  def initialize
65
108
  serial_port = nil
66
109
  #
67
- # Configure the Mx + C values for sail and rudder
68
- @rudder_m = 2.5
69
- @rudder_c = 100.0
70
- @sail_m = 2.0
71
- @sail_c = 0.0
72
- #
73
- # Now set the rudder and sail to default positions (rudder is centered)
74
- rudder = 0.0
75
- sail = 0.0
76
- #
77
110
  # Set some defaults for the read-back parameters
78
- @alarm_status = @wind = @compass = @actual_rudder = @actual_sail = 0
79
- @otto_mode = 0
80
- @otto_timestamp = 1000
111
+ # The following five parameters are reported back by Otto with a status
112
+ # message, and are read-only. @alarm_status is 16 bits while the other
113
+ # four are 8-bit values. The helper methods convert these 8-bit values
114
+ # into radians, etc. The telemetry parameters are used to capture
115
+ # telemetry data from Otto.
116
+ @alarm_status = 0
117
+ @actual_rudder = @actual_sail = @actual_awa = @actual_compass = 0
81
118
  @telemetry = Array.new(16)
82
119
  #
120
+ # Mode is used by Otto to decide how to steer the boat and trim the
121
+ # sails.
122
+ @otto_mode = MODE_INERT
123
+ @otto_timestamp = 1000
124
+ #
83
125
  # Set up some basic parameters for battery/solar readings
84
126
  @bv_m = @bi_m = @bt_m = @sv_m = 1.0
85
127
  @bv_c = @bi_c = @bt_c = @sv_c = 0.0
128
+ #
129
+ # RPC client / server
130
+ @rpc_client = @rpc_server = nil
86
131
  super
87
132
  end
88
133
 
@@ -96,7 +141,7 @@ module SGS
96
141
  def self.daemon
97
142
  puts "Low-level (Otto) communication subsystem starting up..."
98
143
  otto = new
99
- config = SGS::Config.load
144
+ config = Config.load
100
145
  otto.serial_port = SerialPort.new config.otto_device, config.otto_speed
101
146
  otto.serial_port.read_timeout = 10000
102
147
  #
@@ -111,9 +156,31 @@ module SGS
111
156
  t2.join
112
157
  end
113
158
 
159
+ #
160
+ # Build a C include file based on the current register definitions
161
+ def self.build_include(fname)
162
+ otto = new
163
+ File.open(fname, "w") do |f|
164
+ f.puts "/*\n * Autogenerated by #{__FILE__}.\n * DO NOT HAND-EDIT!\n */"
165
+ constants.sort.each do |c|
166
+ if c.to_s =~ /REGISTER$/
167
+ cval = Otto.const_get(c)
168
+ str = "#define SGS_#{c.to_s}"
169
+ str += "\t" if str.length < 32
170
+ str += "\t#{cval}"
171
+ f.puts str
172
+ end
173
+ end
174
+ end
175
+ end
176
+
114
177
  #
115
178
  # Synchronize with the low-level board by sending CQ messages until
116
- # they respond.
179
+ # they respond. When Mother boots up, the serial console is shared with
180
+ # Otto so a lot of rubbish is sent to the low-level board. To notify
181
+ # Otto that we are now talking sense, we send @@CQ! and Otto responds
182
+ # with +CQOK. Note that this function, which is always called before any
183
+ # of the threads, is bidirectional in terms of serial I/O.
117
184
  def synchronize
118
185
  index = 0
119
186
  backoffs = [1, 1, 1, 1, 2, 2, 3, 5, 10, 10, 20, 30, 60]
@@ -154,6 +221,10 @@ module SGS
154
221
  #
155
222
  # Telemetry data (every two seconds)
156
223
  parse_telemetry(data[1..])
224
+ when '*'
225
+ #
226
+ # Message for the debug log
227
+ parse_debug(data[1..])
157
228
  end
158
229
  end
159
230
  end
@@ -164,31 +235,19 @@ module SGS
164
235
  puts "Starting OTTO writer thread..."
165
236
  #
166
237
  # Now listen for Redis PUB/SUB requests and act on each one.
238
+ myredis = Redis.new
167
239
  while true
168
- channel, request = SGS::RedisBase.redis.brpop("otto")
240
+ channel, request = myredis.brpop("otto")
169
241
  request = MessagePack.unpack(request)
170
242
  puts "Req:[#{request.inspect}]"
171
- cmd = {
172
- id: request['id'],
173
- args: request['params'].unshift(request['method'])
174
- }
175
- puts "CMD:#{cmd.inspect}"
176
- #
177
- # Don't let the command stack get too big.
178
- while @command_stack.length > 5
179
- sleep 5
180
- end
181
-
243
+ params = request['params']
244
+ next if request['method'] != "set_local_register"
245
+ puts "PARAMS: #{params}"
246
+ cmd = "R%d=%X\r\n" % params
247
+ puts "Command: #{cmd}"
248
+ @serial_port.write cmd
182
249
  puts "> Sending command: #{str}"
183
250
  @serial_port.puts "#{str}"
184
-
185
- reply = {
186
- 'id' => id,
187
- 'jsonrpc' => '2.0',
188
- 'result' => result
189
- }
190
- SGS::RedisBase.redis.rpush(id, MessagePack.pack(reply))
191
- SGS::RedisBase.redis.expire(id, 30)
192
251
  end
193
252
  end
194
253
 
@@ -208,13 +267,13 @@ module SGS
208
267
  # Parse a status message from Otto. In the form:
209
268
  # 0001:C000:0000
210
269
  def parse_status(status)
211
- puts "Parse status: #{status}"
270
+ puts "OTTO PARSE: #{status}"
212
271
  args = status.split /:/
213
272
  @alarm_status = args[0].to_i(16)
214
273
  wc = args[1].to_i(16)
215
274
  rs = args[2].to_i(16)
216
- @wind = (wc >> 8) & 0xff
217
- @compass = (wc & 0xff)
275
+ @actual_awa = (wc >> 8) & 0xff
276
+ @actual_compass = (wc & 0xff)
218
277
  @actual_rudder = (rs >> 8) & 0xff
219
278
  @actual_sail = (rs & 0xff)
220
279
  p self
@@ -225,7 +284,6 @@ module SGS
225
284
  # Parse a timestamp message from Otto. In the form: "000FE2" 24 bits
226
285
  # representing the elapsed seconds since Otto restarted.
227
286
  def parse_tstamp(tstamp)
228
- puts "Parse timestamp: #{tstamp}"
229
287
  newval = tstamp.to_i(16)
230
288
  if newval < @otto_timestamp
231
289
  puts "ALARM! Otto rebooted (or something)..."
@@ -237,7 +295,6 @@ module SGS
237
295
  # Parse a mode state message from Otto. In the form: "00". An eight bit
238
296
  # quantity.
239
297
  def parse_mode(mode)
240
- puts "Parse Otto Mode State: #{mode}"
241
298
  @otto_mode = mode.to_i(16)
242
299
  end
243
300
 
@@ -245,80 +302,126 @@ module SGS
245
302
  # Parse a telemetry message from Otto. In the form: "7327" where the first
246
303
  # character is the channel (0->9) and the remaining 12 bits are the value.
247
304
  def parse_telemetry(telemetry)
248
- puts "Parse Otto Telemetry Data: #{telemetry}"
249
305
  data = telemetry.to_i(16)
250
306
  chan = (data >> 12) & 0xf
251
- @telemetry[chan] = data & 0xff
307
+ @telemetry[chan] = data & 0xfff
308
+ end
309
+
310
+ #
311
+ # Parse a debug message from the low-level code. Basically just append it
312
+ # to a log file.
313
+ def parse_debug(debug_data)
314
+ puts "DEBUG: [#{debug_data}].\n"
315
+ end
316
+
317
+ #
318
+ # Clear an alarm setting
319
+ def alarm_clear(alarm)
320
+ set_register(ALARM_CLEAR_REGISTER, alarm)
321
+ end
322
+
323
+ #
324
+ # Set the Otto mode
325
+ def mode=(val)
326
+ set_register(MODE_REGISTER, val) if @otto_mode != val
252
327
  end
253
328
 
254
329
  #
255
330
  # Set the required rudder angle. Input values range from +/- 40.0 degrees
256
331
  def rudder=(val)
257
- val = -40.0 if val < -40.0
258
- val = 40.0 if val > 40.0
259
- @raw_rudder = (@rudder_m * val.to_f + @rudder_c).to_i
332
+ val = RUDDER_MIN if val < RUDDER_MIN
333
+ val = RUDDER_MAX if val > RUDDER_MAX
334
+ val = (RUDDER_M * val.to_f + RUDDER_C).to_i
335
+ if val != @actual_rudder
336
+ @actual_rudder = val
337
+ set_register(RUDDER_ANGLE_REGISTER, val)
338
+ end
339
+ mode = MODE_MANUAL
260
340
  end
261
341
 
262
342
  #
263
343
  # Return the rudder angle in degrees
264
344
  def rudder
265
- (@raw_rudder.to_f - @rudder_c) / @rudder_m
345
+ (@actual_rudder.to_f - RUDDER_C) / RUDDER_M
266
346
  end
267
347
 
268
348
  #
269
- # Set the required sail angle. Input values range from 0 -> 90 degrees.
349
+ # Set the required sail angle. Input values range from 0 -> 100.
270
350
  def sail=(val)
271
- val = 0.0 if val < 0.0
272
- val = 100.0 if val > 100.0
273
- @raw_sail = (@sail_m * val.to_f + @sail_c).to_i
351
+ val = SAIL_MIN if val < SAIL_MIN
352
+ val = SAIL_MAX if val > SAIL_MAX
353
+ val = (SAIL_M * val.to_f + SAIL_C).to_i
354
+ if val != @actual_sail
355
+ @actual_sail = val
356
+ set_register(SAIL_ANGLE_REGISTER, val)
357
+ end
358
+ mode = MODE_MANUAL
274
359
  end
275
360
 
276
361
  #
277
362
  # Return the sail setting (0.0 -> 100.0)
278
363
  def sail
279
- (@raw_sail.to_f - @sail_c) / @sail_m
364
+ (@actual_sail.to_f - SAIL_C) / SAIL_M
280
365
  end
281
366
 
282
367
  #
283
368
  # Return the compass angle (in radians)
284
369
  def compass
285
- @raw_compass.to_f * Math::PI / 128.0
370
+ Bearing.xtor(@actual_compass)
286
371
  end
287
372
 
288
373
  #
289
374
  # Return the apparent wind angle (in radians)
290
375
  def awa
291
- @raw_awa.to_f * Math::PI / 128.0
376
+ @actual_awa -= 256 if @actual_awa > 128
377
+ Bearing.xtor(@actual_awa)
378
+ end
379
+
380
+ #
381
+ # Return the actual wind direction (in radians)
382
+ def wind
383
+ Bearing.xtor(@actual_compass + @actual_awa)
292
384
  end
293
385
 
294
386
  #
295
- # Set the required compass reading. Input values range from 0 -> 359 degrees
387
+ # Set the required compass reading (in radians)
296
388
  def track_compass=(val)
297
- while val < 0.0
298
- val += 360.0
389
+ val = Bearing.rtox(val)
390
+ if @track_compass.nil? or @track_compass != val
391
+ @track_compass = val
392
+ set_register(COMPASS_HEADING_REGISTER, val)
299
393
  end
300
- val %= 360.0
301
- @raw_tc = (val.to_f * 128.0 / Math::PI).to_i
394
+ mode = MODE_TRACK_COMPASS
302
395
  end
303
396
 
304
397
  #
305
398
  # Return the compass value for tracking.
306
399
  def track_compass
307
- @raw_tc.to_f * Math::PI / 128.0
400
+ Bearing.xtor(@track_compass)
308
401
  end
309
402
 
310
403
  #
311
- # Set the required AWA for tracking.
404
+ # Set the required AWA for tracking (in radians).
312
405
  def track_awa=(val)
313
- val = -180.0 if val < -180.0
314
- val = 180.0 if val > 180.0
315
- @raw_ta = (val.to_f * 128.0 / Math::PI).to_i
406
+ val = Bearing.rtox(val)
407
+ if @track_awa.nil? or @track_awa != val
408
+ @track_awa = val
409
+ set_register(AWA_HEADING_REGISTER, val)
410
+ end
411
+ mode = MODE_TRACK_AWA
316
412
  end
317
413
 
318
414
  #
319
- # Return the current tracking AWA.
415
+ # Return the current tracking AWA (in radians).
320
416
  def track_awa
321
- @raw_ta.to_f * Math::PI / 128.0
417
+ Bearing.xtor(@track_awa)
418
+ end
419
+
420
+ #
421
+ # RPC client call to set register - sent to writer function above
422
+ def set_register(regno, value)
423
+ @rpc_client = RPCClient.new("otto") unless @rpc_client
424
+ @rpc_client.set_local_register(regno, value)
322
425
  end
323
426
  end
324
427
  end
@@ -75,7 +75,7 @@ module SGS
75
75
  # Initialize a Redis variable.
76
76
  def self.var_init(var, val, idx = nil)
77
77
  cls = new
78
- SGS::RedisBase.redis.setnx cls.make_redis_name(var, :idx => idx), self.to_redis(var, val, idx)
78
+ RedisBase.redis.setnx cls.make_redis_name(var, :idx => idx), self.to_redis(var, val, idx)
79
79
  end
80
80
 
81
81
  #
@@ -96,8 +96,8 @@ module SGS
96
96
  # It's an array - iterate and read the values.
97
97
  lval.size.times do |idx|
98
98
  idx_val = lval[idx]
99
- lval[idx] = redis_read_var var, idx_val.class, :idx => idx
100
- end
99
+ lval[idx] = redis_read_var var, idx_val.class, :idx => idx
100
+ end
101
101
  elsif lval.kind_of? Location
102
102
  #
103
103
  # ::FIXME:: Yes. this is a hack.
@@ -141,7 +141,7 @@ module SGS
141
141
  #
142
142
  # Inside a multi-block, set all the variables and increment
143
143
  # the count.
144
- SGS::RedisBase.redis.multi do |pipeline|
144
+ RedisBase.redis.multi do |pipeline|
145
145
  var_list.each do |key, value|
146
146
  pipeline.set key, value
147
147
  end
@@ -157,7 +157,7 @@ module SGS
157
157
  # class name), you can remember the last received count and decide if
158
158
  # there is fresh data. Or, you can just act anyway.
159
159
  def publish
160
- SGS::RedisBase.redis.publish self.class.redis_handle, count.to_s
160
+ RedisBase.redis.publish self.class.redis_handle, count.to_s
161
161
  end
162
162
 
163
163
  #
@@ -182,7 +182,7 @@ module SGS
182
182
  #
183
183
  # Retrieve the count
184
184
  def count
185
- SGS::RedisBase.redis.get count_name
185
+ RedisBase.redis.get count_name
186
186
  end
187
187
 
188
188
  #
@@ -195,7 +195,7 @@ module SGS
195
195
  # Get an instance variable value from a Redis value.
196
196
  def redis_read_var(var, klass, opts = {})
197
197
  redis_name = make_redis_name var, opts
198
- redis_val = SGS::RedisBase.redis.get redis_name
198
+ redis_val = RedisBase.redis.get redis_name
199
199
  redis_val = nil if redis_val == ""
200
200
  if redis_val
201
201
  if not klass or klass == NilClass
@@ -211,9 +211,9 @@ module SGS
211
211
  when klass == Float
212
212
  redis_val = redis_val.to_f
213
213
  when klass == FalseClass
214
- redis_val = false
214
+ redis_val = (redis_val == "true" or redis_val == "TRUE")
215
215
  when klass == TrueClass
216
- redis_val = true
216
+ redis_val = (redis_val == "true" or redis_val == "TRUE")
217
217
  end
218
218
  end
219
219
  redis_val
data/lib/sgs/report.rb CHANGED
@@ -38,12 +38,12 @@ require 'serialport'
38
38
  # Routines for handling sailboat navigation and route planning.
39
39
  #
40
40
  module SGS
41
- class Report < RedisBase
41
+ class Report
42
42
  #
43
43
  # Main daemon function (called from executable)
44
44
  def self.daemon
45
45
  puts "Reporting subsystem starting up..."
46
- config = SGS::Config.load
46
+ config = Config.load
47
47
  sp = SerialPort.new config.comm_device, config.comm_speed
48
48
  sp.read_timeout = 10000
49
49
  loop do
data/lib/sgs/rpc.rb CHANGED
@@ -55,9 +55,8 @@ module SGS
55
55
  'method' => name,
56
56
  'params' => args
57
57
  }
58
- SGS::RedisBase.redis.lpush(@channel, request.to_msgpack)
59
- channel, response = SGS::RedisBase.redis.brpop(uuid, timeout=60)
60
- MessagePack.unpack(response)['result']
58
+ puts "RPC CLIENT SENDING MESSAGE! request: #{request.inspect}, channel: #{@channel}"
59
+ RedisBase.redis.lpush(@channel, request.to_msgpack)
61
60
  end
62
61
  end
63
62
 
@@ -70,7 +69,7 @@ module SGS
70
69
  def start
71
70
  puts "Starting RPC server for #{@channel}"
72
71
  loop do
73
- channel, request = SGS::RedisBase.redis.brpop(@channel)
72
+ channel, request = RedisBase.redis.brpop(@channel)
74
73
  request = MessagePack.unpack(request)
75
74
 
76
75
  puts "Working on request: #{request['id']}"
@@ -84,8 +83,8 @@ module SGS
84
83
  'id' => request['id']
85
84
  }
86
85
 
87
- SGS::RedisBase.redis.rpush(request['id'], MessagePack.pack(reply))
88
- SGS::RedisBase.redis.expire(request['id'], 30)
86
+ RedisBase.redis.rpush(request['id'], MessagePack.pack(reply))
87
+ RedisBase.redis.expire(request['id'], 30)
89
88
  end
90
89
  end
91
90
  end
data/lib/sgs/version.rb CHANGED
@@ -33,5 +33,5 @@
33
33
  # ABSTRACT
34
34
  #
35
35
  module SGS
36
- VERSION = "1.5.1"
36
+ VERSION = "1.6.0"
37
37
  end
data/lib/sgs/waypoint.rb CHANGED
@@ -39,7 +39,7 @@
39
39
  module SGS
40
40
  #
41
41
  # Waypoint, Attractor, and Repellor definitions
42
- class Waypoint < RedisBase
42
+ class Waypoint
43
43
  attr_accessor :location, :normal, :range, :name, :attractor
44
44
  attr_reader :bearing, :distance
45
45
 
@@ -58,7 +58,7 @@ module SGS
58
58
  def parse(data)
59
59
  @@count += 1
60
60
  @name = data["name"] || "Waypoint ##{@@count}"
61
- @location = SGS::Location.parse(data)
61
+ @location = Location.parse(data)
62
62
  @normal = data["normal"] || 0.0
63
63
  @range = data["range"] || 0.1
64
64
  end
data/sgslib.gemspec CHANGED
@@ -34,15 +34,15 @@ Gem::Specification.new do |spec|
34
34
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
35
35
  spec.require_paths = ["lib"]
36
36
 
37
- spec.required_ruby_version = '>= 1.9.2'
37
+ spec.required_ruby_version = '>= 2.7.6'
38
38
 
39
39
  spec.add_development_dependency "bundler", "~> 2.2"
40
40
  spec.add_development_dependency "rake", ">= 12.3.3"
41
41
  spec.add_development_dependency "rspec", "~> 3.0"
42
42
 
43
- spec.add_runtime_dependency "redis", "~> 4.7"
43
+ spec.add_runtime_dependency "redis", "~> 5.0"
44
44
  spec.add_runtime_dependency "serialport", "~> 1.3"
45
- spec.add_runtime_dependency "msgpack", "~> 1.3"
45
+ spec.add_runtime_dependency "msgpack", "~> 1.6"
46
46
  spec.add_runtime_dependency "json"
47
47
  spec.add_runtime_dependency "securerandom"
48
48
  spec.add_runtime_dependency "yaml"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sgslib
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.1
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dermot Tynan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-01-16 00:00:00.000000000 Z
11
+ date: 2023-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '4.7'
61
+ version: '5.0'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '4.7'
68
+ version: '5.0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: serialport
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '1.3'
89
+ version: '1.6'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '1.3'
96
+ version: '1.6'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: json
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -146,7 +146,6 @@ executables:
146
146
  - sgs_gpsread
147
147
  - sgs_logger
148
148
  - sgs_mission
149
- - sgs_nav
150
149
  - sgs_otto
151
150
  - sgs_report
152
151
  extensions: []
@@ -162,7 +161,6 @@ files:
162
161
  - exe/sgs_gpsread
163
162
  - exe/sgs_logger
164
163
  - exe/sgs_mission
165
- - exe/sgs_nav
166
164
  - exe/sgs_otto
167
165
  - exe/sgs_report
168
166
  - lib/sgs/alarm.rb
@@ -199,14 +197,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
199
197
  requirements:
200
198
  - - ">="
201
199
  - !ruby/object:Gem::Version
202
- version: 1.9.2
200
+ version: 2.7.6
203
201
  required_rubygems_version: !ruby/object:Gem::Requirement
204
202
  requirements:
205
203
  - - ">="
206
204
  - !ruby/object:Gem::Version
207
205
  version: '0'
208
206
  requirements: []
209
- rubygems_version: 3.1.6
207
+ rubygems_version: 3.0.3.1
210
208
  signing_key:
211
209
  specification_version: 4
212
210
  summary: Sailboat Guidance System