# encoding: ascii-8bit # Copyright 2022 Ball Aerospace & Technologies Corp. # All Rights Reserved. # # This program is free software; you can modify and/or redistribute it # under the terms of the GNU Affero General Public License # as published by the Free Software Foundation; version 3 with # attribution addendums as found in the LICENSE.txt # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # This program may also be used under the terms of a commercial or # enterprise edition license of COSMOS if purchased from the # copyright holder require 'cosmos/config/config_parser' require 'cosmos/packets/packet_config' require 'cosmos/tools/table_manager/table' require 'cosmos/tools/table_manager/table_parser' require 'cosmos/tools/table_manager/table_item_parser' module Cosmos # Processes the Table Manager configuration files which define tables. Since # this class inherits from {PacketConfig} it only needs to implement Table # Manager specific keywords. All tables are accessed through the table # and tables methods. class TableConfig < PacketConfig # @return [String] Table configuration filename attr_reader :filename def self.process_file(filename) instance = self.new() instance.process_file(filename) instance end # Create the table configuration def initialize super # Override commands with the Table::TARGET name to store tables @commands[Table::TARGET] = {} @definitions = {} @last_config = [] # Stores array of [filename, contents] end # @return [Array] All tables defined in the configuration file def tables @commands[Table::TARGET] end # @return [String] Table definition for the specific table def definition(table_name) @definitions[table_name.upcase] end # @return [Array] All the table names def table_names tables.keys end # @param table_name [String] Table name to return # @return [Table] def table(table_name) tables[table_name.upcase] end # Processes a COSMOS table configuration file and uses the keywords to build up # knowledge of the tables. # # @param filename [String] The name of the configuration file def process_file(filename) # Partial files are included into another file and thus aren't directly processed return if File.basename(filename)[0] == '_' # Partials start with underscore @filename = filename @last_config = [File.basename(filename), File.read(filename)] @converted_type = nil @converted_bit_size = nil @proc_text = '' @building_generic_conversion = false @defaults = [] parser = ConfigParser.new( 'http://cosmosrb.com/docs/tools/#table-manager-configuration-cosmos--39', ) parser.parse_file(filename) do |keyword, params| if @building_generic_conversion case keyword # Complete a generic conversion when 'GENERIC_READ_CONVERSION_END', 'GENERIC_WRITE_CONVERSION_END' parser.verify_num_parameters(0, 0, keyword) @current_item.read_conversion = GenericConversion.new( @proc_text, @converted_type, @converted_bit_size, ) if keyword.include? 'READ' @current_item.write_conversion = GenericConversion.new( @proc_text, @converted_type, @converted_bit_size, ) if keyword.include? 'WRITE' @building_generic_conversion = false # Add the current config.line to the conversion being built else @proc_text << parser.line << "\n" end # case keyword else # not building generic conversion case keyword when 'TABLEFILE' usage = "#{keyword} " parser.verify_num_parameters(1, 1, usage) table_filename = File.join(File.dirname(filename), params[0]) unless File.exist?(table_filename) raise parser.error( "Table file #{table_filename} not found", usage, ) end process_file(table_filename) when 'TABLE' finish_packet @current_packet = TableParser.parse_table(parser, @commands, @warnings) @definitions[@current_packet.packet_name] = @last_config @current_cmd_or_tlm = COMMAND @default_index = 0 # Select an existing table for editing when 'SELECT_TABLE' usage = "#{keyword}
" finish_packet parser.verify_num_parameters(1, 1, usage) table_name = params[0].upcase @current_packet = table(table_name) unless @current_packet raise parser.error("Table #{table_name} not found", usage) end ####################################################################### # All the following keywords must have a current packet defined ####################################################################### when 'SELECT_PARAMETER', 'PARAMETER', 'ID_PARAMETER', 'ARRAY_PARAMETER', 'APPEND_PARAMETER', 'APPEND_ID_PARAMETER', 'APPEND_ARRAY_PARAMETER', 'ALLOW_SHORT', 'HAZARDOUS', 'PROCESSOR', 'META', 'DISABLE_MESSAGES', 'DISABLED' unless @current_packet raise parser.error("No current packet for #{keyword}") end process_current_packet(parser, keyword, params) ####################################################################### # All the following keywords must have a current item defined ####################################################################### when 'STATE', 'READ_CONVERSION', 'WRITE_CONVERSION', 'POLY_READ_CONVERSION', 'POLY_WRITE_CONVERSION', 'SEG_POLY_READ_CONVERSION', 'SEG_POLY_WRITE_CONVERSION', 'GENERIC_READ_CONVERSION_START', 'GENERIC_WRITE_CONVERSION_START', 'REQUIRED', 'LIMITS', 'LIMITS_RESPONSE', 'UNITS', 'FORMAT_STRING', 'DESCRIPTION', 'HIDDEN', 'MINIMUM_VALUE', 'MAXIMUM_VALUE', 'DEFAULT_VALUE', 'OVERFLOW', 'UNEDITABLE' unless @current_item raise parser.error("No current item for #{keyword}") end process_current_item(parser, keyword, params) when 'DEFAULT' if params.length != @current_packet.sorted_items.length raise parser.error("DEFAULT #{params.join(' ')} length of #{params.length} doesn't match item length of #{@current_packet.sorted_items.length}") end @defaults.concat(params) else # blank config.lines will have a nil keyword and should not raise an exception raise parser.error("Unknown keyword '#{keyword}'") if keyword end # case keyword end end # Complete the last defined packet finish_packet end # (see PacketConfig#process_current_packet) def process_current_packet(parser, keyword, params) super(parser, keyword, params) rescue => err if err.message.include?('not found') raise parser.error( "#{params[0]} not found in table #{@current_packet.table_name}", 'SELECT_PARAMETER ', ) else raise err end end # Overridden method to handle the unique table item parameters: UNEDITABLE # and HIDDEN. # (see PacketConfig#process_current_item) def process_current_item(parser, keyword, params) super(parser, keyword, params) case keyword when 'UNEDITABLE' usage = "#{keyword}" parser.verify_num_parameters(0, 0, usage) @current_item.editable = false when 'HIDDEN' usage = "#{keyword}" parser.verify_num_parameters(0, 0, usage) @current_item.hidden = true end end # (see PacketConfig#start_item) def start_item(parser) finish_item @current_item = TableItemParser.parse(parser, @current_packet, @warnings) end # If the table is ROW_COLUMN all currently defined items are # duplicated until the specified number of rows are created. def finish_packet if @current_packet warnings = @current_packet.check_bit_offsets if warnings.length > 0 raise "Overlapping items not allowed in tables.\n#{warnings}" end if @current_packet.type == :ROW_COLUMN items = @current_packet.sorted_items.clone (@current_packet.num_rows - 1).times do |row| items.each do |item| new_item = item.clone new_item.name = "#{new_item.name[0...-1]}#{row + 1}" @current_packet.append(new_item) end end end unless @defaults.empty? @current_packet.sorted_items.each_with_index do |item, index| case item.data_type when :INT, :UINT begin # Integer handles hex strings, e.g. 0xDEADBEEF item.default = Integer(@defaults[index]) rescue ArgumentError value = item.states[@defaults[index]] if value item.default = value else raise "Unknown DEFAULT #{@defaults[index]} for item #{item.name}. Valid states are #{item.states.keys.join(', ')}." end end when :FLOAT item.default = @defaults[index].to_f when :STRING, :BLOCK item.default = @defaults[index] end end end end super() end end end