PRC163Radio class

L3Harris PRC-163 radio adapter.

This device reads and write asynchronously from a USB TTY device connected to the radio. It has a thread managing the low-level IO, and a timer to periodically send the commands that turn into the events required by the BDE (GPS, time, etc.).

Both reading and writing happen in two stages: a queue and a buffer.

write_queue -> write_buffer -> <TTY> -> read_buffer -> read_queue

Commands for the radio are appended to the write_queue. The IO thread pulls each command off of the queue and appends it to the write_buffer, then writes as much as it can to the radio. It then reads any pending output from the radio into the read_buffer and then breaks as many whole lines off of that as it can, appending each one to the read_queue. The read_queue is then scanned for events, and those are emitted up the tree.

== Emits:

  • sys.device.info

    • sys.gps.position

    • sys.gps.time

    • sys.radio.battery

    • sys.radio.channel

    • sys.radio.volume

    • sys.radio.versions

== Consumes:

  • sys.radio.command

This class includes code from the ruby-termios examples, used under the terms of the Ruby License. The original software does not include a copyright statement, so none is duplicated here.

Refs:

  • L3Harris Ascii Interface Design Document (RF-335M/1.3.1/February 19, 2021) obsidian://open?vault=Software%20Notes&file=Radios%2FL3Harris%20PRC-163%2Fattachments%2FL3Harris%20Ascii%20PLATFORM%20163%201.3.1.pdf

Constants

AUTOVIVIFY

Auto-vivifying Hash Proc; used to create Hashes that auto-expand their contents

COMMAND_PROMPT

Prompt text that should be stripped from radio output

ECHOED_COMMANDS

An output line containing an echoed command

ECHOED_COMMAND_LINE
EOL

End-of-line character

IO_SELECT_TIMEOUT

Maximum number of floating-point seconds to wait in the IO loop. This is effectively the maximum amount of time a command will spend in the write_queue before being sent.

IO_THREAD_WAIT_TIME

How long to wait for the IO thread to die

L3H_DMS_FORMAT

Pattern for matching L3H’s weird degrees/minutes/seconds format.

LINE_SEPARATOR

Character/s used to split radio output into lines

LIST_ENDING_MARKER

Marker of the end of multi-line output.

MAX_READ_SIZE

Maximum of bytes to read from the radio at one time

OOB_MESSAGE_LINE

Pattern to match ongoing (out-of-band) status message lines in radio output

PRESET_CHANGE_COMPLETE

Pattern that matches the OOB message when a preset is changed

Attributes

command_timer R

Concurrent::TimerTask that causes periodic information events to be emitted from the device.

io_thread R

The Thread object that is reading and writing to/from the radio serial device

radio R

The UART object connected via serial USB to the radio

read_buffer R

A String buffer that contains output that has been read from the radio but not yet split into lines into the read_queue

read_queue R

Line-oriented output from the radio that is waiting to be parsed

write_buffer R

A String with unwritten commands for the radio. Commands are appended to this as they’re removed from the write_queue by the IO thread.

write_queue R

Commands for the radio that haven’t been written yet.

Public Class Methods

new( * )

Create a new device adapter for a L3H PRC-163 radio.

# File lib/ravn/hal/device/prc163_radio.rb, line 171
def initialize( * )
        @radio        = nil
        @io_thread    = nil

        @read_queue   = []
        @read_buffer  = String.new

        @write_queue  = []
        @write_buffer = String.new

        @command_timer = self.make_command_timer or
                raise "couldn't create the periodic command timer"

        if self.class.simulation_mode?
                self.log.warn "Adding simulation mode to %p" % [ self ]
                self.extend( SimulationMode )
        end

        super
end

Public Instance Methods

buffer_pending_writes()

Append any data in the write_queue to the write_buffer.

# File lib/ravn/hal/device/prc163_radio.rb, line 406
def buffer_pending_writes
        if (data = self.write_queue.shift)
                self.write_buffer << data << LINE_SEPARATOR
        end
end
build_gps_message( gps_info )

Construct and return a ‘sys.gps.position’ message using the given gps_info.

# File lib/ravn/hal/device/prc163_radio.rb, line 630
def build_gps_message( gps_info )
        return unless gps_info[ :state ] == :tracking

        gps = {
                pos: [ gps_info[ :latitude ], gps_info[ :longitude ] ],
                hae: gps_info[ :altitude ],
                ce:  gps_info[ :epe ],
                heading: gps_info[ :heading ],
                velocity: gps_info[ :velocity ]
        }

        return Ravn::HAL::Message.new( 'sys.gps.position', data: gps )
end
build_gps_time_message( gps_info )

Construct and return a ‘sys.gps.time’ message using the given gps_info.

# File lib/ravn/hal/device/prc163_radio.rb, line 646
def build_gps_time_message( gps_info )
        return nil unless gps_info.key?( :timestamp )

        raw_timestamp = gps_info[ :timestamp ]
        return Ravn::HAL::Message.new( 'sys.gps.time', data: { time: raw_timestamp.to_i } )
end
build_radio_connection()

Connect to the radio via serial UART.

# File lib/ravn/hal/device/prc163_radio.rb, line 329
def build_radio_connection
        device = self.class.serial_device
        speed = self.class.serial_speed
        mode = self.class.serial_mode

        self.log.info "Opening radio device: %p at %p(%p)" % [ device, speed, mode ]
        uart = UART.open( device.to_s, speed, mode )

        desc = self.dump_termios( uart )
        self.log.info "Radio device terminal capabilities: %s" % [ desc ]

        return uart
rescue SystemCallError => err
        self.log.error "%p while opening the serial device: %s" % [ err.class, err.message ]
        raise
end
discover()

Connect to the radio in the discovery phase

# File lib/ravn/hal/device/prc163_radio.rb, line 234
def discover
        @radio = self.build_radio_connection
        super
rescue => err
        self.reset( "couldn't open the serial connection (%p)" % [ err.class ] )
end
emit_preset_change_complete_event( match_data )

Emit a sys.radio.channel event given match_data for a OOB SYS_PRESETSTATUS line.

# File lib/ravn/hal/device/prc163_radio.rb, line 667
def emit_preset_change_complete_event( match_data )
        transceiver = match_data[:transceiver][ /(\d+)/, 1 ] or
                raise "no transceiver in match data: %p" % [ match_data ]
        data = {
                radio: self.class.object_id,
                transceiver: transceiver.to_i,
                name: match_data[:preset_name],
                number: match_data[:preset_number].to_i,
                waveform: match_data[:waveform],
        }
        message = Ravn::HAL::Message.new( 'sys.radio.channel', data: )
        self.filter_up( message )
end
gather_device_info()

Return a Hash that contains information describing this device for intra-device communication; overridden to set the correct vendor.

# File lib/ravn/hal/device/prc163_radio.rb, line 319
def gather_device_info
        return super.merge( vendor: 'l3harris' )
end
handle_oob_status_line( line )

Handle any status lines emitted by the radio by emitting events for the stuff we care about.

# File lib/ravn/hal/device/prc163_radio.rb, line 656
def handle_oob_status_line( line )
        case line
        when PRESET_CHANGE_COMPLETE
                self.emit_preset_change_complete_event( $~ )
        else
                self.log.debug "Unhandled OOB status line: %p" % [ line ]
        end
end
handle_paused_event( * )

Stop reading/writing timers while paused

# File lib/ravn/hal/device/prc163_radio.rb, line 278
def handle_paused_event( * )
        self.log.info "Paused; resetting the command timer."
        self.reset_command_timer
end
handle_resetting_event( * )

Stop cleanly when the device is going to be reset.

# File lib/ravn/hal/device/prc163_radio.rb, line 292
def handle_resetting_event( * )
        self.log.warn "Resetting."
        self.stop
end
handle_resumed_event( * )

Recreate and restart the reading/writing timers when resumed

# File lib/ravn/hal/device/prc163_radio.rb, line 285
def handle_resumed_event( * )
        self.log.info "Paused; restarting the command timer."
        self.command_timer.execute
end
handle_terminated_event( * )

Stop the device cleanly when it’s terminated.

# File lib/ravn/hal/device/prc163_radio.rb, line 299
def handle_terminated_event( * )
        self.log.warn "Terminated."
        self.stop
end
io_loop()

Connect to the radio and loop over the IO routine for it while it’s open.

# File lib/ravn/hal/device/prc163_radio.rb, line 357
def io_loop
        Thread.current.report_on_exception = true
        Thread.current.name = "PRC163 Device I/O"

        self.log.info "Starting IO loop."
        while self.radio && ! self.radio.closed?
                self.read_and_write
        end
        self.log.info "Stopped IO loop."
rescue SystemCallError => err
        self.log.error( err )
        reason = "%p in the IO loop: %s" % [ err.class, err.message ]
        self.reset( reason )
end
parse_and_emit_events()

Turn data in the read_queue into events if possible.

# File lib/ravn/hal/device/prc163_radio.rb, line 458
def parse_and_emit_events
        line = self.read_queue.first or return

        self.log.debug "Parsing and emitting events from the read queue: %p..." % [ self.read_queue ]

        while line
                self.log.debug "Looking at line: %p" % [ line ]
                result = case line
                        when ECHOED_COMMAND_LINE, ''
                                self.log.debug "Skipping empty or echoed command line: %p" % [ line ]
                                self.read_queue.shift
                        when /^BATTERY\b/i
                                self.log.info "Parsing battery event."
                                self.parse_battery_event
                        when /^VERSION\b/i
                                self.log.info "Parsing versions event."
                                self.parse_versions_event
                        when /^GPS\b/i
                                self.log.info "Parsing GPS event."
                                self.parse_gps_event
                        when /^RT[12] STATE\b/i
                                self.log.info "Parsing channel event."
                                self.parse_channel_event
                        when /^RT[12] VOLUME\b/i
                                self.log.info "Parsing volume event."
                                self.parse_volume_event
                        else
                                self.log.debug "Unhandled line: %p" % [ line ]
                                self.read_queue.shift
                        end

                return unless result

                line = self.read_queue.first
        end

end
parse_battery_event()

Parse the given BATTERY output lines and emit an event describing the battery status.

# File lib/ravn/hal/device/prc163_radio.rb, line 800
def parse_battery_event
        data = self.parse_event_from_queue( radio: self.class.object_id ) do |line, data|
                case line
                when /^BATTERY HUB BOD (.*)$/
                        # Sun Dec  8 15:51:08 2019
                        timestamp = Time.strptime( $1.strip, '%a %b %e %H:%M:%S %Y' )
                        data[:hub][:bod] = timestamp
                when /^BATTERY HUB CAPACITY DAYS REMAINING (\d+)/
                        data[:hub][:capacity] = $1.to_i
                when /^BATTERY TYPE (\w+)/
                        data[:type] = $1
                when /^BATTERY MODEL (\w+)/
                        data[:model] = $1
                when /^BATTERY CAPACITY (\w+)/
                        data[:capacity] = $1
                when /^BATTERY STATUS (\w+)/
                        data[:status] = $1
                when /^BATTERY CHARGE (\d+)/
                        data[:charge] = $1.to_i
                when /^BATTERY VOLTAGE (\d+)/
                        data[:voltage] = $1.to_i
                when /^BATTERY CURRENT (\d+)/
                        data[:current] = $1.to_i
                when /^BATTERY TEMP (\d+)/
                        data[:temp] = $1.to_i

                else
                        self.log.debug "Unhandled battery info line: %p" % [ line ]
                end
        end

        return if data.nil? || data.empty?

        self.log.info "Emitting a sys.radio.battery event: %p" % [ data ]
        message = Ravn::HAL::Message.new( 'sys.radio.battery', data: )
        self.filter_up( message )
end
parse_channel_event()

Parse the given RT STATE output lines and emit an event describing the transceiver’s channel state.

# File lib/ravn/hal/device/prc163_radio.rb, line 716
def parse_channel_event
        data = self.parse_event_from_queue( radio: self.class.object_id ) do |line, hash|
                transceiver = line[ /^RT([12])/i, 1 ] or
                        raise "failed to match transceiver number from %p" % [ line ]
                hash[ :transceiver ] = transceiver.to_i

                case line
                when /^RT[12] STATE OPMODE (\S+)$/
                        hash[:opmode] = $1.downcase.to_sym
                when /^RT[12] STATE SYS_PRESET_NAME (.+)$/
                        hash[:name] = $1.strip
                when /^RT[12] STATE SYS_PRESET_NUMBER\s+(\d+)/
                        hash[:number] = $1.to_i
                when /^RT[12] STATE CURRENT_WAVEFORM (.+)$/
                        hash[:waveform] = $1.strip
                when /^RT[12] STATE OPERATIONAL_STATE (\w+)/
                        hash[:state] = $1.downcase.to_sym
                when /^RT[12] STATE MISSION_PLAN (\S+)/
                        hash[:plan] = $1
                when /^RT[12] STATE TYPE1-I (\S+)/
                        hash[:encrypted] = ($1 == 'TRUE')
                when /^RT[12] STATE CIPHER_SWITCH (\w+)/
                        hash[:cipher_switch] = $1
                when /^RT[12] STATE MODE_SWITCH (\w+)/
                        hash[:mode_switch] = $1
                when /^RT[12] STATE EXTERNAL_PA (\w+)/
                        hash[:external_pa] = $1
                when /^RT[12] STATE KEYLINE (\w+)/
                        hash[:keyline] = $1
                when /^RT[12] STATE PASSWORD_STATUS (\w+)/
                        hash[:password_status] = $1

                else
                        self.log.debug "Unhandled transceiver state line: %p" % [ line ]
                end
        end

        return if data.nil? || data.empty?

        self.log.info "Emitting a sys.radio.channel event: %p" % [ data ]
        message = Ravn::HAL::Message.new( 'sys.radio.channel', data: )
        self.filter_up( message )
end
parse_gps_event()

Read GPS info from the given GPS output lines emit an event describing the current GPS position.

# File lib/ravn/hal/device/prc163_radio.rb, line 841
def parse_gps_event
        info = self.parse_event_from_queue do |line, hash|
                case line
                when /^GPS INFO\s*$/
                        # ignore
                when /^GPS INFO POS1 (.*)/
                        hash[ :pos1 ] = $1
                when /^GPS INFO POS2 (.*)/
                        hash[ :pos2 ] = $1
                when /^GPS INFO STATE (\w+)/
                        hash[ :state ] = $1.downcase.to_sym
                when /^GPS INFO LATITUDE\s+(.+)/
                        hash[ :latitude ] = parse_dms_position( $1 )
                when /^GPS INFO LONGITUDE\s+(.+)/
                        hash[ :longitude ] = parse_dms_position( $1 )
                when /^GPS INFO ALTITUDE\s+(.+)/
                        hash[ :altitude ] = $1.to_f
                when /^GPS INFO SEPARATION\s+(.+)/
                        hash[ :separation ] = $1.to_f
                when /^GPS INFO HEADING\s+(.+)/
                        hash[ :heading ] = $1.to_f
                when /^GPS INFO VELOCITY\s+(.+)/
                        hash[ :velocity ] = $1.to_f
                when /^GPS INFO FOM\s+(.+)/
                        hash[ :fom ] = $1.to_i
                when /^GPS INFO TFOM\s+(.+)/
                        hash[ :tfom ] = $1.to_i
                when /^GPS INFO EPE\s+\+\/-\s+(.+?) m/
                        hash[ :epe ] = $1.to_f
                when /^GPS INFO DATUM\s+(.+)/
                        hash[ :datum ] = $1.downcase.to_sym
                when /^GPS INFO KEYSTATUS\s+(.+)/
                        hash[ :keystatus ] = $1.downcase.to_sym
                when /^GPS INFO VALIDITY\s+(.+)/
                        hash[ :validity ] = $1.downcase.to_sym
                when /^GPS INFO TIMESTAMP\s+\d{2}-(.+)/
                        self.log.warn "Ignoring bootup timestamp"
                when /^GPS INFO TIMESTAMP\s+(.+)/
                        begin
                                munged_time = $1 + ' UTC'
                                time = Time.strptime( munged_time, '%Y-%m-%d %H:%M:%S %Z' )
                                hash[ :timestamp ] = time
                        rescue ArgumentError => err
                                self.log.warn "%p while parsing GPS timestamp: %s" % [ err.class, err.message ]
                        end
                else
                        self.log.debug "Unknown GPS INFO line: %p" % [ line ]
                end
        end

        return if info.nil? || info.empty?

        self.log.debug "Parsed event from GPS INFO lines: %p" % [ info ]

        if (message = self.build_gps_message( info ))
                self.log.info "Emitting a sys.gps.position event."
                self.filter_up( message )
        else
                self.log.warn "couldn't create GPS position message (GPS state = %p)" % [ info[:state] ]
        end

        if (message = self.build_gps_time_message( info ))
                self.log.info "Emitting a sys.gps.time event."
                self.filter_up( message )
        else
                self.log.warn "couldn't create GPS time message (GPS state = %p)" % [ info[:state] ]
        end
end
parse_versions_event()

Parse VERSION output lines and emit an event describing various versions of hardware and software running on the radio. Returns the number of lines that were read.

# File lib/ravn/hal/device/prc163_radio.rb, line 764
def parse_versions_event
        data = self.parse_event_from_queue( radio: self.class.object_id ) do |line, data|
                case line
                when /^VERSION ALL/
                        # Skip
                when %r{^VERSION OPTION NAME (?<name>\w+)\s+P/N (?<partno>\S+)\s+(?<desc>.+)$}
                        hashify_captures_into( $~, data[:options] )

                when /^VERSION HW (?<name>\w+)\s+(?<sku>\w+)\s+PN (?<partno>\S+)\s+PL_REV (?<plrev>\w+)\s+PWB_REV (?<pwbrev>\w+)/
                        hashify_captures_into( $~, data[:hw] )

                when %r{^VERSION INFOSEC (?<name>\p{Print}+)\s+P/N (?<partno>\S+)\s+REVISION (?<revision>\S+)}
                        hashify_captures_into( $~, data[:infosec] )

                # VERSION SW GPS_GRAM_SAASM P/N N/A REVISION 811-7994-005 SW_REV N/A
                when %r{^VERSION SW (?<name>\S+)\s+P/N (?<partno>\S+)\s+REVISION (?<revision>.+?)\s+SW_REV (\S+)}
                        hashify_captures_into( $~, data[:sw] )

                when /^VERSION SYSTEM SW\s+REVISION (\S+ \S+)/
                        data[:system] = $1

                else
                        self.log.debug "Unhandled software version line: %p" % [ line ]
                end
        end

        return if data.nil? || data.empty?

        self.log.info "Emitting a sys.radio.versions event: %p" % [ data ]
        message = Ravn::HAL::Message.new( 'sys.radio.versions', data: )
        self.filter_up( message )
end
parse_volume_event()

Parse the given RT VOLUME output lines and emit an event describing the transceiver’s volume level.

# File lib/ravn/hal/device/prc163_radio.rb, line 684
def parse_volume_event
        radio = self.class.object_id

        line = self.read_queue.shift or
                raise "can't read volume event: empty read queue"
        line = self.read_queue.shift or return if line =~ /^RT[12] VOLUME\s*$/

        self.log.warn "Parsing volume event from line: \n  %p" % [ line ]

        data = { radio: }
        case line
        when /^RT([12]) VOLUME (\d+|MIN|MAX)/
                data[:transceiver] = $1.to_i
                level = case $2
                        when 'MIN' then 0
                        when 'MAX' then 10
                        else
                                $2.to_i
                        end
                data[:level] = level
        else
                self.log.debug "Unhandled transceiver volume line: %p" % [ line ]
        end

        self.log.info "Emitting a sys.radio.volume event: %p" % [ data ]
        message = Ravn::HAL::Message.new( 'sys.radio.volume', data: )
        self.filter_up( message )
end
queue_command( *commands )
Alias for: queue_commands
queue_commands( *commands )

Add new commands to the write queue.

# File lib/ravn/hal/device/prc163_radio.rb, line 518
def queue_commands( *commands )
        self.write_queue.push( *commands )
end
Also aliased as: queue_command
queue_periodic_commands( * )

Timer callback: queue commands for events that should be emitted periodically.

# File lib/ravn/hal/device/prc163_radio.rb, line 526
def queue_periodic_commands( * )
        self.request_gps_info
        self.request_rt_info if self.class.radio_control_enabled?
end
queue_read_lines()

Split off complete lines from the read_buffer and append then to the read queue.

# File lib/ravn/hal/device/prc163_radio.rb, line 427
def queue_read_lines
        self.log.debug "Queueing read lines..."

        if (index = self.read_buffer.rindex( EOL ))
                self.log.debug "  reading lines up to index %d" % [ index ]

                if (line_data = self.read_buffer.slice!( 0 .. index ))
                        self.log.debug "  read: %p" % [ line_data ]

                        line_data.each_line do |line|
                                line.strip!
                                line.slice!( COMMAND_PROMPT )
                                next if line.empty?

                                if OOB_MESSAGE_LINE.match?( line )
                                        self.handle_oob_status_line( line )
                                else
                                        self.read_queue.push( line )
                                end
                        end
                        self.log.debug "  there are now %d queued lines" % [ self.read_queue.length ]
                else
                        self.log.warn "no line data! (0..%d of %p)" % [ index, self.read_buffer ]
                end
        else
                self.log.debug "No complete lines in: %p." % [ self.read_buffer ]
        end
end
read_and_write()

Wait on the radio to be readable and/or writable and handle reading and writing when it’s ready.

# File lib/ravn/hal/device/prc163_radio.rb, line 375
def read_and_write
        radio_dev = self.radio or raise "Couldn't connect to the radio"

        ios = [ radio_dev ]
        readable = ios

        self.buffer_pending_writes
        writable = self.write_buffer.empty? ? nil : ios

        if (ready = IO.select( readable, writable, nil, IO_SELECT_TIMEOUT ))
                self.log.debug "IO ready"

                # Readable
                if ready[ 0 ]
                        self.read_from_radio
                        self.queue_read_lines
                        self.parse_and_emit_events
                end

                # Writable
                if ready[ 1 ]
                        self.write_to_radio
                end
        end
rescue => err
        self.log.debug "%p while reading/writing to the radio: %s" %
                [ err.class, err.full_message(order: :top) ]
end
read_from_radio()

Read output from the radio and generate events from it. It propagates errors from the read to its caller, notably IO::WaitReadable exceptions and EOFError.

# File lib/ravn/hal/device/prc163_radio.rb, line 415
def read_from_radio
        if (data = self.radio&.read_nonblock( MAX_READ_SIZE ))
                self.log.debug "Read %d bytes from the radio" % [ data.bytesize ]
                self.read_buffer << data
        end
rescue IO::WaitReadable, EOFError
        # No-op, just go back into the select loop
end
request_battery_info()

Queue up the command to emit battery info events.

# File lib/ravn/hal/device/prc163_radio.rb, line 545
def request_battery_info
        self.queue_command( 'BATTERY' )
end
request_gps_info()

Queue up the command to emit GPS info events.

# File lib/ravn/hal/device/prc163_radio.rb, line 539
def request_gps_info
        self.queue_command( 'GPS INFO' )
end
request_next_preset( transceiver )

Queue up the command to change the given transceiver‘s preset to the next one.

# File lib/ravn/hal/device/prc163_radio.rb, line 598
def request_next_preset( transceiver )
        command = "RT%d SYS_PRESET SELECT NEXT" % [ transceiver ]
        self.queue_command( command )
end
request_previous_preset( transceiver )

Queue up the command to change the given transceiver‘s preset to the previous one.

# File lib/ravn/hal/device/prc163_radio.rb, line 591
def request_previous_preset( transceiver )
        command = "RT%d SYS_PRESET SELECT PREVIOUS" % [ transceiver ]
        self.queue_command( command )
end
request_rt_change( message )
# File lib/ravn/hal/device/prc163_radio.rb, line 562
def request_rt_change( message )
        target_id = message.data[ :radio ]
        unless target_id == self.identifier #BR/not equal/nil
                self.log.debug "Ignoring `command` event for radio %p" % [ target_id ]
                return
        end

        transceiver = message.data[ :transceiver ]
        unless transceiver&.between?( 1, 2 ) #BR/less than/greater than
                self.log.error "Invalid/unset transceiver (%p) for message %p" % [ transceiver, message ]
                return
        end

        case message.data[ :command ] #BR/else/nil
        when 'channel up'
                self.request_next_preset( transceiver )
        when 'channel down'
                self.request_previous_preset( transceiver )
        when 'volume set'
                self.request_volume_set( transceiver, message )
        when nil
                self.log.warn "No `command` specified for %p" % [ message ]
        else
                self.log.warn "Command %p not implemented." % [ message.data[:command] ]
        end
end
request_rt_info()

Queue commands that will emit events describing the status of the radio transmitters (preset, volume, etc.).

# File lib/ravn/hal/device/prc163_radio.rb, line 552
def request_rt_info
        self.queue_command( "RT1 STATE ALL" )
        self.queue_command( "RT1 VOLUME" )
        self.queue_command( "RT2 STATE ALL" )
        self.queue_command( "RT2 VOLUME" )
end
request_version_info()

Queue up the command to emit version info events.

# File lib/ravn/hal/device/prc163_radio.rb, line 533
def request_version_info
        self.queue_command( 'VERSION ALL' )
end
request_volume_set( transceiver, message )

Queue up the command to change the given transceiver‘s volume to the level specified by the message.

# File lib/ravn/hal/device/prc163_radio.rb, line 606
def request_volume_set( transceiver, message )
        if (level = message.data[ :level ])
                unless level.between?( 0, 12 )
                        self.log.warn "Ignoring request to set volume to %p (0 <= level <=12)" %
                                [ level ]
                        return
                end
        else
                self.log.error "No `level` given for `volume set` command: %p" % [ message ]
                return
        end

        command = "RT%d VOLUME %d" % [ transceiver, level ]
        self.queue_command( command )
end
reset( reason )

Reset the device.

# File lib/ravn/hal/device/prc163_radio.rb, line 306
def reset( reason )
        self.log.error "Reset: %p" % [ reason ]
        sleep( RESET_THROTTLE_TIME )
        self.core&.parent&.tell( reset_device: self )
end
reset_command_timer()

Stop sending commands by canceling the timer that triggers them.

# File lib/ravn/hal/device/prc163_radio.rb, line 271
def reset_command_timer
        @command_timer&.shutdown
        @command_timer = self.make_command_timer
end
start()

Start talking to the radio.

# File lib/ravn/hal/device/prc163_radio.rb, line 243
def start
        self.log.info "Starting."

        super

        self.command_timer.execute
        self.request_version_info

        @io_thread = self.start_io_loop unless self.class.simulation_mode?
end
start_io_loop()

Start reading and writing from/to the radio. Emits events for information read, and writes commands from the write_queue if there are any. Returns a Thread that is running the loop.

# File lib/ravn/hal/device/prc163_radio.rb, line 350
def start_io_loop
        self.log.info "Starting the IO loop thread."
        return Thread.new( &self.method(:io_loop) )
end
stop()

Stop talking to the radio.

# File lib/ravn/hal/device/prc163_radio.rb, line 256
def stop
        self.log.info "Stopping."

        self.reset_command_timer

        self.log.warn "Closing the radio device and shutting down the IO thread."
        @radio&.close unless @radio&.closed?
        @radio = nil

        @io_thread&.join( IO_THREAD_WAIT_TIME )
        @io_thread = nil
end
write_to_radio()

Write any pending commands to the radio.

# File lib/ravn/hal/device/prc163_radio.rb, line 498
def write_to_radio
        return if self.write_buffer.empty?

        self.log.debug "Trying to write %d bytes to the radio" % [ self.write_buffer.bytesize ]
        bytes = self.radio&.write_nonblock( self.write_buffer )

        if bytes&.nonzero?
                self.log.debug "wrote %d bytes to the radio" % [ bytes ]
                self.write_buffer.slice!( 0, bytes )
        end
rescue IO::WaitWritable
        # No-op, just go back into the select loop
end

Protected Instance Methods

make_command_timer()

Build a Concurrent::TimerTask that will read output from the radio and generate events from it.

# File lib/ravn/hal/device/prc163_radio.rb, line 956
def make_command_timer
        timer = Concurrent::TimerTask.new { self.queue_periodic_commands }
        timer.execution_interval = self.class.status_frequency
        timer.add_observer( Ravn::LoggingTaskObserver.new(:prc163_command_timer) )

        return timer
end
parse_event_from_queue( **initial_data ) { |line, data| ... }

Iterate line-by-line through the current read buffer, yielding each line and a Hash of accumulated data to the block, prepopulated with the given initial_data. Returns the accumulated data Hash back to the caller if successful, or nil if not successful at parsing.

# File lib/ravn/hal/device/prc163_radio.rb, line 926
def parse_event_from_queue( **initial_data )
        raise LocalJumpError, "no block given" unless block_given?

        unless self.read_queue_contains_list_ending?
                self.log.debug "Partial output in read buffer, skipping."
                sleep 2.0
                return nil
        end

        data = Hash.new( &AUTOVIVIFY )
        data.merge!( initial_data )

        line = self.read_queue.shift
        while line
                if line.match?( LIST_ENDING_MARKER )
                        self.log.debug "Found the list ending marker."
                        break
                end

                yield( line, data )

                line = self.read_queue.shift
        end

        return data
end
read_queue_contains_list_ending?()

Returns true if the current read queue contains one or more list-ending markers.

# File lib/ravn/hal/device/prc163_radio.rb, line 917
def read_queue_contains_list_ending?
        return self.read_queue.any? {|line| line.match?(LIST_ENDING_MARKER) }
end