Class: SGS::Otto
Constant Summary collapse
- 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.
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/sgs/otto.rb', line 116 def initialize serial_port = nil # # 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
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/sgs/otto.rb', line 170 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.
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/sgs/otto.rb', line 150 def self.daemon puts "Low-level (Otto) communication subsystem starting up..." 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
328 329 330 |
# File 'lib/sgs/otto.rb', line 328 def alarm_clear(alarm) set_register(ALARM_CLEAR_REGISTER, alarm) end |
#awa ⇒ Object
Return the apparent wind angle (in radians)
384 385 386 387 |
# File 'lib/sgs/otto.rb', line 384 def awa @actual_awa -= 256 if @actual_awa > 128 Bearing.xtor(@actual_awa) end |
#compass ⇒ Object
Return the compass angle (in radians)
378 379 380 |
# File 'lib/sgs/otto.rb', line 378 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.
322 323 324 |
# File 'lib/sgs/otto.rb', line 322 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.
306 307 308 |
# File 'lib/sgs/otto.rb', line 306 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
278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/sgs/otto.rb', line 278 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.
313 314 315 316 317 |
# File 'lib/sgs/otto.rb', line 313 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.
295 296 297 298 299 300 301 |
# File 'lib/sgs/otto.rb', line 295 def parse_tstamp(tstamp) newval = tstamp.to_i(16) if newval < @otto_timestamp puts "ALARM! Otto rebooted (or something)..." end @otto_timestamp = newval end |
#read_data ⇒ Object
Read data from the serial port
265 266 267 268 269 270 271 272 273 |
# File 'lib/sgs/otto.rb', line 265 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
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/sgs/otto.rb', line 211 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
353 354 355 |
# File 'lib/sgs/otto.rb', line 353 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
340 341 342 343 344 345 346 347 348 349 |
# File 'lib/sgs/otto.rb', line 340 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)
372 373 374 |
# File 'lib/sgs/otto.rb', line 372 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.
359 360 361 362 363 364 365 366 367 368 |
# File 'lib/sgs/otto.rb', line 359 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
431 432 433 434 |
# File 'lib/sgs/otto.rb', line 431 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.
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/sgs/otto.rb', line 193 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).
425 426 427 |
# File 'lib/sgs/otto.rb', line 425 def track_awa Bearing.xtor(@track_awa) end |
#track_awa=(val) ⇒ Object
Set the required AWA for tracking (in radians).
414 415 416 417 418 419 420 421 |
# File 'lib/sgs/otto.rb', line 414 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.
408 409 410 |
# File 'lib/sgs/otto.rb', line 408 def track_compass Bearing.xtor(@track_compass) end |
#track_compass=(val) ⇒ Object
Set the required compass reading (in radians)
397 398 399 400 401 402 403 404 |
# File 'lib/sgs/otto.rb', line 397 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)
391 392 393 |
# File 'lib/sgs/otto.rb', line 391 def wind Bearing.xtor(@actual_compass + @actual_awa) end |
#writer_thread ⇒ Object
Thread to write commands direct to Otto.
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/sgs/otto.rb', line 243 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'] next if request['method'] != "set_local_register" puts "PARAMS: #{params}" cmd = "R%d=%X\r\n" % params puts "Command: #{cmd}" @serial_port.write cmd puts "> Sending command: #{str}" @serial_port.puts "#{str}" end end |