Class: SGS::Otto
Constant Summary collapse
- MISSION_SWITCH =
1
- RUDDER_FAULT =
2
- SAIL_FAULT =
3
- BATTERY_FAULT =
4
- SOLAR_FAULT =
5
- COMPASS_FAULT =
6
- ACCEL_FAULT =
7
- WDI_FAULT =
8
- ALARM_CLEAR_REGISTER =
Updates to Otto are done by setting an 8bit register value, as below.
0
- MISSION_CONTROL_REGISTER =
1
- MODE_REGISTER =
2
- BUZZER_REGISTER =
3
- RUDDER_ANGLE_REGISTER =
4
- SAIL_ANGLE_REGISTER =
5
- COMPASS_HEADING_REGISTER =
6
- MIN_COMPASS_REGISTER =
7
- MAX_COMPASS_REGISTER =
8
- AWA_HEADING_REGISTER =
9
- MIN_AWA_REGISTER =
10
- MAX_AWA_REGISTER =
11
- WAKE_DURATION_REGISTER =
12
- NEXT_WAKEUP_REGISTER =
13
- RUDDER_PID_P =
14
- RUDDER_PID_I =
15
- RUDDER_PID_D =
16
- RUDDER_PID_E_NUM =
17
- RUDDER_PID_E_DEN =
18
- RUDDER_PID_U_DIV =
19
- SAIL_MXC_M_VALUE =
20
- SAIL_MXC_C_VALUE =
21
- SAIL_MXC_U_DIV =
22
- MAX_REGISTER =
23
- MODE_INERT =
This is different from mission mode. This mode defines how Otto should operate. Inert means “do nothing”. Diagnostic mode is for the low-level code to run self-checks and calibrations. Manual means that the upper level system controls the rudder and sail angle without any higher-level PID controller. Track compass means that the boat will try to keep the actual compass reading within certain parameters, and track AWA will try to maintain a specific “apparent wind angle”.
0
- MODE_DIAG =
1
- MODE_MANUAL =
2
- MODE_REMOTE =
3
- MODE_TRACK_COMPASS =
4
- MODE_TRACK_AWA =
5
- RUDDER_MAX =
Define some tweaks for rudder and sail setting. Rudder goes from /-40 degrees, with zero indicating a straight rudder. On Otto, this translates to 0 (for -40.0), 128 (for the zero position) and 255 (for 40 degrees of rudder). A fully trimmed-in sail is zero and a fully extended sail is 255 (0->100 from a function perspective).
40.0
- RUDDER_MIN =
-40.0
- RUDDER_M =
3.175
- RUDDER_C =
128.0
- SAIL_MAX =
100.0
- SAIL_MIN =
0.0
- SAIL_M =
2.55
- SAIL_C =
0.0
Instance Attribute Summary collapse
-
#actual_rudder ⇒ Object
readonly
Returns the value of attribute actual_rudder.
-
#actual_sail ⇒ Object
readonly
Returns the value of attribute actual_sail.
-
#alarm_status ⇒ Object
readonly
Returns the value of attribute alarm_status.
-
#bi_c ⇒ Object
Returns the value of attribute bi_c.
-
#bi_m ⇒ Object
Returns the value of attribute bi_m.
-
#bt_c ⇒ Object
Returns the value of attribute bt_c.
-
#bt_m ⇒ Object
Returns the value of attribute bt_m.
-
#bv_c ⇒ Object
Returns the value of attribute bv_c.
-
#bv_m ⇒ Object
Returns the value of attribute bv_m.
-
#mode ⇒ Object
Returns the value of attribute mode.
-
#otto_mode ⇒ Object
readonly
Returns the value of attribute otto_mode.
-
#otto_timestamp ⇒ Object
readonly
Returns the value of attribute otto_timestamp.
-
#serial_port ⇒ Object
Returns the value of attribute serial_port.
-
#sv_c ⇒ Object
Returns the value of attribute sv_c.
-
#sv_m ⇒ Object
Returns the value of attribute sv_m.
-
#telemetry ⇒ Object
readonly
Returns the value of attribute telemetry.
Class Method Summary collapse
-
.build_include(fname) ⇒ Object
Build a C include file based on the current register definitions.
-
.daemon ⇒ Object
Main daemon function (called from executable).
Instance Method Summary collapse
-
#alarm_clear(alarm) ⇒ Object
Clear an alarm setting.
-
#awa ⇒ Object
Return the apparent wind angle (in radians).
-
#compass ⇒ Object
Return the compass angle (in radians).
-
#initialize ⇒ Otto
constructor
Set up some useful defaults.
-
#parse_debug(debug_data) ⇒ Object
Parse a debug message from the low-level code.
-
#parse_mode(mode) ⇒ Object
Parse a mode state message from Otto.
-
#parse_status(status) ⇒ Object
Parse a status message from Otto.
-
#parse_telemetry(telemetry) ⇒ Object
Parse a telemetry message from Otto.
-
#parse_tstamp(tstamp) ⇒ Object
Parse a timestamp message from Otto.
-
#read_data ⇒ Object
Read data from the serial port.
-
#reader_thread ⇒ Object
Thread to read status messages from Otto and handle them.
-
#rudder ⇒ Object
Return the rudder angle in degrees.
-
#rudder=(val) ⇒ Object
Set the required rudder angle.
-
#sail ⇒ Object
Return the sail setting (0.0 -> 100.0).
-
#sail=(val) ⇒ Object
Set the required sail angle.
-
#set_register(regno, value) ⇒ Object
RPC client call to set register - sent to writer function above.
-
#synchronize ⇒ Object
Synchronize with the low-level board by sending CQ messages until they respond.
-
#track_awa ⇒ Object
Return the current tracking AWA (in radians).
-
#track_awa=(val) ⇒ Object
Set the required AWA for tracking (in radians).
-
#track_compass ⇒ Object
Return the compass value for tracking.
-
#track_compass=(val) ⇒ Object
Set the required compass reading (in radians).
-
#wind ⇒ Object
Return the actual wind direction (in radians).
-
#writer_thread ⇒ Object
Thread to write commands direct to Otto.
Methods inherited from RedisBase
#count, #count_name, #load, load, #make_redis_name, #publish, redis, redis_handle, #redis_read_var, #save, #save_and_publish, setup, subscribe, to_redis, var_init
Constructor Details
#initialize ⇒ Otto
Set up some useful defaults. We assume rudder goes from 0 to 255 as does the sail angle.
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/sgs/otto.rb', line 124 def initialize serial_port = nil # # Keep a local copy of the register set to avoid duplication. @registers = Array.new(MAX_REGISTER) # # Set some defaults for the read-back parameters # The following five parameters are reported back by Otto with a status # message, and are read-only. @alarm_status is 16 bits while the other # four are 8-bit values. The helper methods convert these 8-bit values # into radians, etc. The telemetry parameters are used to capture # telemetry data from Otto. @alarm_status = 0 @actual_rudder = @actual_sail = @actual_awa = @actual_compass = 0 @telemetry = Array.new(16) # # Mode is used by Otto to decide how to steer the boat and trim the # sails. @otto_mode = MODE_INERT @otto_timestamp = 1000 # # Set up some basic parameters for battery/solar readings @bv_m = @bi_m = @bt_m = @sv_m = 1.0 @bv_c = @bi_c = @bt_c = @sv_c = 0.0 # # RPC client / server @rpc_client = @rpc_server = nil super end |
Instance Attribute Details
#actual_rudder ⇒ Object (readonly)
Returns the value of attribute actual_rudder.
53 54 55 |
# File 'lib/sgs/otto.rb', line 53 def actual_rudder @actual_rudder end |
#actual_sail ⇒ Object (readonly)
Returns the value of attribute actual_sail.
53 54 55 |
# File 'lib/sgs/otto.rb', line 53 def actual_sail @actual_sail end |
#alarm_status ⇒ Object (readonly)
Returns the value of attribute alarm_status.
52 53 54 |
# File 'lib/sgs/otto.rb', line 52 def alarm_status @alarm_status end |
#bi_c ⇒ Object
Returns the value of attribute bi_c.
51 52 53 |
# File 'lib/sgs/otto.rb', line 51 def bi_c @bi_c end |
#bi_m ⇒ Object
Returns the value of attribute bi_m.
51 52 53 |
# File 'lib/sgs/otto.rb', line 51 def bi_m @bi_m end |
#bt_c ⇒ Object
Returns the value of attribute bt_c.
51 52 53 |
# File 'lib/sgs/otto.rb', line 51 def bt_c @bt_c end |
#bt_m ⇒ Object
Returns the value of attribute bt_m.
51 52 53 |
# File 'lib/sgs/otto.rb', line 51 def bt_m @bt_m end |
#bv_c ⇒ Object
Returns the value of attribute bv_c.
51 52 53 |
# File 'lib/sgs/otto.rb', line 51 def bv_c @bv_c end |
#bv_m ⇒ Object
Returns the value of attribute bv_m.
51 52 53 |
# File 'lib/sgs/otto.rb', line 51 def bv_m @bv_m end |
#mode ⇒ Object
Returns the value of attribute mode.
50 51 52 |
# File 'lib/sgs/otto.rb', line 50 def mode @mode end |
#otto_mode ⇒ Object (readonly)
Returns the value of attribute otto_mode.
54 55 56 |
# File 'lib/sgs/otto.rb', line 54 def otto_mode @otto_mode end |
#otto_timestamp ⇒ Object (readonly)
Returns the value of attribute otto_timestamp.
54 55 56 |
# File 'lib/sgs/otto.rb', line 54 def @otto_timestamp end |
#serial_port ⇒ Object
Returns the value of attribute serial_port.
50 51 52 |
# File 'lib/sgs/otto.rb', line 50 def serial_port @serial_port end |
#sv_c ⇒ Object
Returns the value of attribute sv_c.
51 52 53 |
# File 'lib/sgs/otto.rb', line 51 def sv_c @sv_c end |
#sv_m ⇒ Object
Returns the value of attribute sv_m.
51 52 53 |
# File 'lib/sgs/otto.rb', line 51 def sv_m @sv_m end |
#telemetry ⇒ Object (readonly)
Returns the value of attribute telemetry.
54 55 56 |
# File 'lib/sgs/otto.rb', line 54 def telemetry @telemetry end |
Class Method Details
.build_include(fname) ⇒ Object
Build a C include file based on the current register definitions
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/sgs/otto.rb', line 181 def self.build_include(fname) otto = new File.open(fname, "w") do |f| f.puts "/*\n * Autogenerated by #{__FILE__}.\n * DO NOT HAND-EDIT!\n */" constants.sort.each do |c| if c.to_s =~ /REGISTER$/ cval = Otto.const_get(c) str = "#define SGS_#{c.to_s}" str += "\t" if str.length < 32 str += "\t#{cval}" f.puts str end end end end |
.daemon ⇒ Object
Main daemon function (called from executable). The job of this daemon is to accept commands from the Redis pub/sub stream and send them to the low-level device, recording the response and sending it back to the caller. Note that we need to do an initial sync with the device as it will ignore the usual serial console boot-up gumph awaiting our sync message.
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/sgs/otto.rb', line 161 def self.daemon puts "Low-level (Otto) communication subsystem starting up. Version #{SGS::VERSION}" otto = new config = Config.load otto.serial_port = SerialPort.new config.otto_device, config.otto_speed otto.serial_port.read_timeout = 10000 # # Start by getting a sync message from Otto. otto.synchronize() # # Run the communications service with Otto. Two threads are used, one for # reading and one for writing. Don't let the command stack get too big. t1 = Thread.new { otto.reader_thread } t2 = Thread.new { otto.writer_thread } t1.join t2.join end |
Instance Method Details
#alarm_clear(alarm) ⇒ Object
Clear an alarm setting
343 344 345 |
# File 'lib/sgs/otto.rb', line 343 def alarm_clear(alarm) set_register(ALARM_CLEAR_REGISTER, alarm) end |
#awa ⇒ Object
Return the apparent wind angle (in radians)
399 400 401 402 |
# File 'lib/sgs/otto.rb', line 399 def awa @actual_awa -= 256 if @actual_awa > 128 Bearing.xtor(@actual_awa) end |
#compass ⇒ Object
Return the compass angle (in radians)
393 394 395 |
# File 'lib/sgs/otto.rb', line 393 def compass Bearing.xtor(@actual_compass) end |
#parse_debug(debug_data) ⇒ Object
Parse a debug message from the low-level code. Basically just append it to a log file.
337 338 339 |
# File 'lib/sgs/otto.rb', line 337 def parse_debug(debug_data) puts "DEBUG: [#{debug_data}].\n" end |
#parse_mode(mode) ⇒ Object
Parse a mode state message from Otto. In the form: “00”. An eight bit quantity.
321 322 323 |
# File 'lib/sgs/otto.rb', line 321 def parse_mode(mode) @otto_mode = mode.to_i(16) end |
#parse_status(status) ⇒ Object
Parse a status message from Otto. In the form: 0001:C000:0000
293 294 295 296 297 298 299 300 301 302 303 304 305 |
# File 'lib/sgs/otto.rb', line 293 def parse_status(status) puts "OTTO PARSE: #{status}" args = status.split /:/ @alarm_status = args[0].to_i(16) wc = args[1].to_i(16) rs = args[2].to_i(16) @actual_awa = (wc >> 8) & 0xff @actual_compass = (wc & 0xff) @actual_rudder = (rs >> 8) & 0xff @actual_sail = (rs & 0xff) p self self.save_and_publish end |
#parse_telemetry(telemetry) ⇒ Object
Parse a telemetry message from Otto. In the form: “7327” where the first character is the channel (0->9) and the remaining 12 bits are the value.
328 329 330 331 332 |
# File 'lib/sgs/otto.rb', line 328 def parse_telemetry(telemetry) data = telemetry.to_i(16) chan = (data >> 12) & 0xf @telemetry[chan] = data & 0xfff end |
#parse_tstamp(tstamp) ⇒ Object
Parse a timestamp message from Otto. In the form: “000FE2” 24 bits representing the elapsed seconds since Otto restarted.
310 311 312 313 314 315 316 |
# File 'lib/sgs/otto.rb', line 310 def parse_tstamp(tstamp) newval = tstamp.to_i(16) if @otto_timestamp.nil? or newval < @otto_timestamp puts "ALARM! Otto rebooted (or something)..." end @otto_timestamp = newval end |
#read_data ⇒ Object
Read data from the serial port
280 281 282 283 284 285 286 287 288 |
# File 'lib/sgs/otto.rb', line 280 def read_data begin data = @serial_port.readline.chomp rescue EOFError => error puts "Otto Read Timeout!" data = nil end data end |
#reader_thread ⇒ Object
Thread to read status messages from Otto and handle them
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/sgs/otto.rb', line 222 def reader_thread puts "Starting OTTO reader thread..." while true data = read_data next if data.nil? or data.length == 0 case data[0] when '$' # # Status message (every second) parse_status(data[1..]) when '@' # # Otto elapsed time (every four seconds) parse_tstamp(data[1..]) when '!' # # Otto mode state (every four seconds) parse_mode(data[1..]) when '>' # # Telemetry data (every two seconds) parse_telemetry(data[1..]) when '*' # # Message for the debug log parse_debug(data[1..]) end end end |
#rudder ⇒ Object
Return the rudder angle in degrees
368 369 370 |
# File 'lib/sgs/otto.rb', line 368 def rudder (@actual_rudder.to_f - RUDDER_C) / RUDDER_M end |
#rudder=(val) ⇒ Object
Set the required rudder angle. Input values range from +/- 40.0 degrees
355 356 357 358 359 360 361 362 363 364 |
# File 'lib/sgs/otto.rb', line 355 def rudder=(val) val = RUDDER_MIN if val < RUDDER_MIN val = RUDDER_MAX if val > RUDDER_MAX val = (RUDDER_M * val.to_f + RUDDER_C).to_i if val != @actual_rudder @actual_rudder = val set_register(RUDDER_ANGLE_REGISTER, val) end mode = MODE_MANUAL end |
#sail ⇒ Object
Return the sail setting (0.0 -> 100.0)
387 388 389 |
# File 'lib/sgs/otto.rb', line 387 def sail (@actual_sail.to_f - SAIL_C) / SAIL_M end |
#sail=(val) ⇒ Object
Set the required sail angle. Input values range from 0 -> 100.
374 375 376 377 378 379 380 381 382 383 |
# File 'lib/sgs/otto.rb', line 374 def sail=(val) val = SAIL_MIN if val < SAIL_MIN val = SAIL_MAX if val > SAIL_MAX val = (SAIL_M * val.to_f + SAIL_C).to_i if val != @actual_sail @actual_sail = val set_register(SAIL_ANGLE_REGISTER, val) end mode = MODE_MANUAL end |
#set_register(regno, value) ⇒ Object
RPC client call to set register - sent to writer function above
446 447 448 449 |
# File 'lib/sgs/otto.rb', line 446 def set_register(regno, value) @rpc_client = RPCClient.new("otto") unless @rpc_client @rpc_client.set_local_register(regno, value) end |
#synchronize ⇒ Object
Synchronize with the low-level board by sending CQ messages until they respond. When Mother boots up, the serial console is shared with Otto so a lot of rubbish is sent to the low-level board. To notify Otto that we are now talking sense, we send @@CQ! and Otto responds with +CQOK. Note that this function, which is always called before any of the threads, is bidirectional in terms of serial I/O.
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/sgs/otto.rb', line 204 def synchronize index = 0 backoffs = [1, 1, 1, 1, 2, 2, 3, 5, 10, 10, 20, 30, 60] puts "Attempting to synchronize with Otto..." while true do begin @serial_port.puts "@@CQ!" resp = read_data break if resp =~ /^\+CQOK/ or resp =~ /^\+OK/ sleep backoffs[index] index += 1 if index < (backoffs.count - 1) end end puts "Synchronization complete!" end |
#track_awa ⇒ Object
Return the current tracking AWA (in radians).
440 441 442 |
# File 'lib/sgs/otto.rb', line 440 def track_awa Bearing.xtor(@track_awa) end |
#track_awa=(val) ⇒ Object
Set the required AWA for tracking (in radians).
429 430 431 432 433 434 435 436 |
# File 'lib/sgs/otto.rb', line 429 def track_awa=(val) val = Bearing.rtox(val) if @track_awa.nil? or @track_awa != val @track_awa = val set_register(AWA_HEADING_REGISTER, val) end mode = MODE_TRACK_AWA end |
#track_compass ⇒ Object
Return the compass value for tracking.
423 424 425 |
# File 'lib/sgs/otto.rb', line 423 def track_compass Bearing.xtor(@track_compass) end |
#track_compass=(val) ⇒ Object
Set the required compass reading (in radians)
412 413 414 415 416 417 418 419 |
# File 'lib/sgs/otto.rb', line 412 def track_compass=(val) val = Bearing.rtox(val) if @track_compass.nil? or @track_compass != val @track_compass = val set_register(COMPASS_HEADING_REGISTER, val) end mode = MODE_TRACK_COMPASS end |
#wind ⇒ Object
Return the actual wind direction (in radians)
406 407 408 |
# File 'lib/sgs/otto.rb', line 406 def wind Bearing.xtor(@actual_compass + @actual_awa) end |
#writer_thread ⇒ Object
Thread to write commands direct to Otto.
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
# File 'lib/sgs/otto.rb', line 254 def writer_thread puts "Starting OTTO writer thread..." # # Now listen for Redis PUB/SUB requests and act on each one. myredis = Redis.new while true channel, request = myredis.brpop("otto") request = MessagePack.unpack(request) puts "Req:[#{request.inspect}]" params = request['params'] puts "PARAMS: #{params}" case request['method'] when "set_local_register" reg, value = params if @registers[reg] != value cmd = "R%d=%X\r\n" % [reg, value] puts "Command: #{cmd}" @serial_port.write cmd @registers[reg] = value end end end end |