Ravn::BDE::

Console class

Constants

COMMANDS

Dispatch table/help text for commands

DEFAULT_PROMPT

What to display as a prompt

ICONS

Icons to display for particular message types

JADC2_MESSAGE_PATTERN

Pattern that matches messages types shown in JADC2 mode

PLACEHOLDER_PATTERN

Pattern for matching placeholders in bolt messages

STARTUP_FORMAT

Attributes

beacon R

The beacon object that receives beacon broadcasts

bolts RW

The table of bolts loaded from the startup message.

bolts_lookup RW

The abbreviation table of bolt ID prefixes to bolt IDs.

client R

The client object for communications with the ZMQ device

command_lookup RW

The abbreviation table of command prefixes to commands.

current_pos R

The current position of this station (if known)

format R

A formatter object that allows for colorization and styling (a Pastel object).

logdir R

Path for optional event logging

prompt RW

The current prompt text

received_bolts R

The hash that stores received bolt information, keyed by UUID

received_bolts_lookup RW

The hash that stores abbreviations for lookup

Public Class Methods

new( endpoint, logdir=nil )

Create a CLI

# File lib/ravn/bde/console.rb, line 106
def initialize( endpoint, logdir=nil )
        @prompt = DEFAULT_PROMPT
        @logdir = logdir

        @client         = Ravn::HAL::ZmqSocketClient.new( endpoint )
        @beacon         = Ravn::BDE::BeaconListener.new
        @running        = false
        @bolts          = nil
        @bolts_lookup   = {}
        @command_lookup = Abbrev.abbrev( COMMANDS.keys )
        @current_pos    = [0.0, 0.0]

        @format     = Pastel.new
        @jadc2_mode = false

        @received_bolts = {}
        @received_bolts_lookup = {}
end
run( endpoint, logdir=nil )

Run the console, connecting via ZMQ socket device at the given endpoint.

# File lib/ravn/bde/console.rb, line 96
def self::run( endpoint, logdir=nil )
        RbReadline.rl_instream = $stdin
        RbReadline.rl_outstream = $stdout

        Loggability[ Ravn::BDE ].level = :debug
        self.new( endpoint, logdir ).run
end

Public Instance Methods

body_for_bolt( message )

Return a String representation of the given Bolt message.

# File lib/ravn/bde/console.rb, line 508
def body_for_bolt( message )
        position = if ( coords = message.pos )
                        "  %0.5f, %0.5f  " % coords
                else
                        "  ---  "
                end

        main_title = "  %s  " % [ message.callsign ]


        titles = { top_left: main_title, bottom_right: position }
        box = TTY::Box.frame(
                width: TTY::Screen.width,
                height: 3,
                style: { border: {fg: :bright_yellow} },
                title: titles
        ) do
                "  " + message.data[:name]
        end
end
body_for_message( message )

Return a display for the body of the given message.

# File lib/ravn/bde/console.rb, line 457
def body_for_message( message )
        return case message.type
                when 'sys.startup'
                        start_time = Time.at( message.data[:system_time] )
                        versions   = message.data[ :versions ]
                        mission    = message.data[ :mission ]
                        STARTUP_FORMAT % [
                                mission[:name],
                                mission[:id],
                                mission[:version],
                                start_time,
                                message.data[ :callsign ],
                                *versions.values_at( :ravn, :'ravn-bde', :'ravn-hal', :'ravn-net' )
                        ]
                when 'sys.gps.position'
                        "%0.5f, %0.5f @ %0.2f (± %dm)" % [
                                message.data[:pos][ 0 ],
                                message.data[:pos][ 1 ],
                                message.data[:hae],
                                message.data[:ce],
                        ]
                when 'net.gps.position'
                        "%s: %0.5f, %0.5f @ %0.2f (± %dm)" % [
                                message.label,
                                message.pos[ 0 ],
                                message.pos[ 1 ],
                                message.hae,
                                message.ce,
                        ]
                when /\Abolt\.received\./
                        self.handle_received_bolt( message )
                else
                        explode( message.data )
                end
end
describe_jadc2_mode()

Output a message describing the current state of the JADC2 mode flag.

# File lib/ravn/bde/console.rb, line 209
def describe_jadc2_mode
        self.display "JADC2 mode: %s" % [ self.jadc2_mode? ? "enabled" : "disabled" ]
end
dispatch_command( input_string )

Dispatch the given input_string read from the prompt. Dispatches input strings that start with a /command as a control command, and everything else as a character command.

# File lib/ravn/bde/console.rb, line 245
def dispatch_command( input_string )
        if input_string =~ /\A(?<command>[a-z][\w-]*)(\s+(?<args>\S.*))?\z/
                command_name = self.command_lookup[ $~[:command] ]
                args = $~[:args]

                self.display_log "Command = %p, args = %p" % [ command_name, args ]
                if (command = COMMANDS[ command_name.to_sym ])
                        meth = self.method( command[:method] )
                        meth.call( args )
                else
                        self.display_help
                end
        else
                self.display_error( "Invalid command." )
        end
rescue => err
        self.display_error( err.message )
end
display( *lines )

Display a message and redisplay the prompt on the next line.

# File lib/ravn/bde/console.rb, line 379
def display( *lines )
        lines = lines.flatten

        RbReadline._rl_erase_entire_line
        lines.each do |line|
                line = line.to_s.chomp

                RbReadline.insert_some_chars( line, line.bytesize, 0 )
                RbReadline.rl_crlf
                RbReadline.rl_on_new_line
        end
        RbReadline.rl_redisplay
end
display_beacon( message )

Display a representation of the given BDE beacon message.

# File lib/ravn/bde/console.rb, line 433
def display_beacon( message )
        icon = ICONS[  :beacon ]
        body = "%s %s" % [ icon, self.explode(message) ]

        self.display( body ) unless self.jadc2_mode?
end
display_block( block )

Display a block of text with appropriate spacing.

# File lib/ravn/bde/console.rb, line 395
def display_block( block )
        lines = [ '', *block.to_s.each_line.to_a, '' ]
        self.display( *lines )
end
display_error( message )

Display the given message as an error.

# File lib/ravn/bde/console.rb, line 545
def display_error( message )
        message = "%s %s" % [ ICONS[:error], self.format.bold.red(message) ]
        self.display( message )
end
display_help( command=nil )

Display help text for the given command, or a list of commands if no command is specified.

# File lib/ravn/bde/console.rb, line 267
def display_help( command=nil )
        if command
                raise "not implemented"
        else
                table = TTY::Table.new
                COMMANDS.keys.sort.each do |command|
                        table << [ command, COMMANDS[command][:help] ]
                end

                output = table.render( :basic, indent: 2, padding: [0, 2] )
                self.display_block( output )
        end
end
display_log( message )

Display the given message as logging output.

# File lib/ravn/bde/console.rb, line 538
def display_log( message )
        message = "%s %s" % [ ICONS[:log], self.format.dim(message) ]
        self.display( message )
end
display_message( message )

Display a representation of the given message.

# File lib/ravn/bde/console.rb, line 402
def display_message( message )
        if self.logdir
                encoded_data = JSON.dump( message.fields )
                self.dump_to_file( encoded_data )
        end

        self.record_position( message ) if message.type == 'sys.gps.position'
        self.populate_bolts_table( message ) if message.type == 'sys.startup'

        return if self.jadc2_mode? && message.type !~ JADC2_MESSAGE_PATTERN

        icon = self.icon_for_message( message )
        body = self.body_for_message( message )

        formatted = "%s  {%s} %s" % [
                icon,
                self.format.cyan( message.type ),
                body
        ]

        self.display "→ %s" % [ formatted ]
end
display_result( result )

Display the result of running a command.

# File lib/ravn/bde/console.rb, line 531
def display_result( result )
        message = "%s %s" % [ ICONS[:result], result ]
        self.display( message )
end
dump_to_file( message )

Write the message to a file, for manual inspection.

# File lib/ravn/bde/console.rb, line 566
def dump_to_file( message )
        file = self.logdir + Time.now.to_f.to_s
        file.open( 'w' ) do |fh|
                fh.puts( message.inspect )
        end
end
explode( hash )

Explode the given hash into a more human-readable set of values.

# File lib/ravn/bde/console.rb, line 552
def explode( hash )
        pairs = hash.map do |key, val|
                if val.is_a?( Hash )
                        "%s: { %s }" % [ key, self.explode(val) ]
                else
                        "%s: %s" % [ key, self.format.bold(val.inspect) ]
                end
        end

        return pairs.join( ' ' )
end
handle_received_bolt( message )
# File lib/ravn/bde/console.rb, line 494
def handle_received_bolt( message )
        # save bolt message in a hash for "later"
        instance_id = message.data[:instance_id]

        self.received_bolts_lookup = Abbrev.abbrev(
                self.received_bolts.keys + [instance_id]
        )

        self.received_bolts[instance_id] = message
        return "\n" + self.body_for_bolt( message )
end
icon_for_message( message )

Return an icon for displaying the given message.

# File lib/ravn/bde/console.rb, line 442
def icon_for_message( message )
        return ICONS[ message.type ]
end
initiate_bolt( prefixes )

Initiate the Bolt with the given bolt_id. The bolt_id can be abbreviated.

# File lib/ravn/bde/console.rb, line 354
def initiate_bolt( prefixes )
        send_bolt_messages( prefixes, "bolt.initiate.%<bolt_id>s" )
end
jadc2_mode()

Whether to show only JADC2 messages or show everything

# File lib/ravn/bde/console.rb, line 172
attr_predicate_accessor :jadc2_mode
populate_bolts_table( startup_message )

Populate the bolts table from the given startup_message.

# File lib/ravn/bde/console.rb, line 215
def populate_bolts_table( startup_message )
        self.bolts = startup_message.data[:mission][:bolts].transform_keys( &:to_s )
        self.display_log "Loaded bolts from startup message: %p." % [ self.bolts ]
        self.bolts_lookup = Abbrev.abbrev( self.bolts.keys )
end
read_command()

Read a command from the prompt and return it.

# File lib/ravn/bde/console.rb, line 223
def read_command
        buff = RbReadline.readline( self.prompt )

        self.display_log "Read: %p" % [ buff ]

        if buff
                # self.log.debug "Read %d bytes of input." % [ buff.bytesize ]
                if buff.length.nonzero?
                        RbReadline.add_history( buff )
                        return buff
                end
        else
                self.display_log "Stopping."
                @running = false
                return nil
        end
end
receive_bolt( prefixes )

Simulate receiving the Bolt with the given bolt_id. The bolt_id can be abbreviated.

# File lib/ravn/bde/console.rb, line 366
def receive_bolt( prefixes )
        send_bolt_messages( prefixes, "bolt.sent.%<bolt_id>s" )
end
record_position( message )

Set the current position from the message.

# File lib/ravn/bde/console.rb, line 427
def record_position( message )
        @current_pos = [ message.data[:pos][0], message.data[:pos][1] ]
end
respond_to_bolt( prefix )

Respond to the bolt with the given UUID with the default response

# File lib/ravn/bde/console.rb, line 316
def respond_to_bolt( prefix )
        # send default bolt response
        # lookup the bolt from the prefix
        instance_id = self.received_bolts_lookup[ prefix ] or raise "No such bolt %p" % [ prefix ]
        # get the message from the history using the UUID
        message = self.received_bolts[ instance_id ] or raise "No such bolt %p" % [ prefix ]
        msg_type = message.type.sub( /received/, 'respond' )

        msg = Ravn::BDE::Message.new( msg_type )
        data = msg.fields.merge( type: msg_type )
        self.client.send_message( data )

        self.display_result "Queued a %s message." % [ msg_type ]
end
run()

Run the CLI

# File lib/ravn/bde/console.rb, line 186
def run
        @running = true
        RbReadline.rl_initialize

        self.client.on_message( &self.method(:display_message) )
        self.client.start

        self.beacon.on_message( &self.method(:display_beacon) )
        self.beacon.start

        self.describe_jadc2_mode

        while @running
                buff = self.read_command
                self.dispatch_command( buff ) if buff
        end
ensure
        self.client.stop
        RbReadline.rl_deprep_terminal
end
running()

Does the CLI want to stay running?

# File lib/ravn/bde/console.rb, line 144
attr_predicate :running
send_bolt( prefixes )

Send the Bolt with the given bolt_id. The bolt_id can be abbreviated.

# File lib/ravn/bde/console.rb, line 360
def send_bolt( prefixes )
        send_bolt_messages( prefixes, "bolt.send.%<bolt_id>s" )
end
send_bolt_messages( prefixes, type_pattern )

Create and send messages for the bolts with the given prefixes (in a space-separated String), with the type created via the given type_pattern.

# File lib/ravn/bde/console.rb, line 335
def send_bolt_messages( prefixes, type_pattern )
        raise "No bolts loaded!" unless self.bolts
        raise "Bolt prefix(es) required." unless prefixes

        prefixes.split( /\s+/ ).each do |prefix|
                bolt_id = self.bolts_lookup[ prefix ] or raise "No such bolt %p" % [ prefix ]
                msg_type = type_pattern % { bolt_id: bolt_id }

                msg = Ravn::BDE::Message.new( msg_type )
                data = msg.fields.merge( type: msg_type )
                msg.pos = self.current_pos
                self.client.send_message( data )

                self.display_result "Queued a %s message." % [ msg_type ]
        end
end
show_bolt_history( * )

Show the received bolt messages

# File lib/ravn/bde/console.rb, line 299
def show_bolt_history( * )
        raise "No received bolts loaded!" unless self.received_bolts

        table = TTY::Table.new
        table << ["Bolt ID", "Name", "Ontology Suffix"]
        self.received_bolts.each do |bolt_id, config|
                table << [bolt_id, config[:name], config[:ontological_suffix]]
        end

        output = table.render( :unicode, indent: 2, padding: [0, 2] )
        output = '  ' + output unless output&.start_with?( '  ' )
        self.display_block( output )
end
show_mission_bolts( * )

Show the bolts loaded from the current mission.

# File lib/ravn/bde/console.rb, line 283
def show_mission_bolts( * )
        raise "No bolts loaded!" unless self.bolts

        table = TTY::Table.new
        self.bolts.each do |bolt_id, config|
                table << [bolt_id, config[:name], config[:ontological_suffix]]
        end

        output = table.render( :unicode, indent: 2, padding: [0, 2] )
        output = '  ' + output unless output.start_with?( '  ' ) # tty-table bug?
        self.display_block( output )
end
toggle_jadc2_mode( * )

Toggle JADC2 mode on or off.

# File lib/ravn/bde/console.rb, line 372
def toggle_jadc2_mode( * )
        self.jadc2_mode = !self.jadc2_mode?
        self.describe_jadc2_mode
end