diff --git a/docs.openc3.com/docs/guides/scripting-api.md b/docs.openc3.com/docs/guides/scripting-api.md index 8909847424..0f5a20952c 100644 --- a/docs.openc3.com/docs/guides/scripting-api.md +++ b/docs.openc3.com/docs/guides/scripting-api.md @@ -2936,6 +2936,74 @@ Python Example: router_protocol_cmd("INST", "DISABLE_CRC", read_write='READ_WRITE', index=-1) ``` +## Tables + +These methods allow the user to script Table Manager. + +### table_create_binary + +Creates a table binary based on a table definition file. You can achieve the same result in the Table Manager GUI with File->New File. Returns the path to the binary file created. + +Ruby / Python Syntax: + +```ruby +table_create_binary() +``` + +| Parameter | Description | +| --------------------- | ------------------------------------------------------------------------------- | +| Table Definition File | Path to the table definition file, e.g. INST/tables/config/ConfigTables_def.txt | + +Ruby Example: + +```ruby +table = table_create_binary("INST/tables/config/ConfigTables_def.txt") #=> +# {"filename"=>"INST/tables/bin/ConfigTables.bin"} +``` + +Python Example: + +```python +table = table_create_binary("INST/tables/config/ConfigTables_def.txt") #=> +# {'filename': 'INST/tables/bin/ConfigTables.bin'} +``` + +### table_create_report + +Creates a table binary based on a table definition file. You can achieve the same result in the Table Manager GUI with File->New File. Returns the path to the binary file created. + +Ruby / Python Syntax: + +```ruby +table_create_report(
,
,
) +``` + +filename, definition, table_name + +| Parameter | Description | +| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Table Binary File | Path to the table binary file, e.g. INST/tables/bin/ConfigTables.bin | +| Table Definition File | Path to the table definition file, e.g. INST/tables/config/ConfigTables_def.txt | +| Table Name | Name of the table to create the report. This only applies if the Table Binary and Table Definition consist of multiple tables. By default the report consists of all tables and is named after the binary file. If the table name is given, the report is just the specified table and is named after the table. | + +Ruby Example: + +```ruby +table = table_create_report("INST/tables/bin/ConfigTables.bin", "INST/tables/config/ConfigTables_def.txt") #=> +# {"filename"=>"INST/tables/bin/ConfigTables.csv", "contents"=>"MC_CONFIGURATION\nLabel, ... +table = table_create_report("INST/tables/bin/ConfigTables.bin", "INST/tables/config/ConfigTables_def.txt", table_name: "MC_CONFIGURATION") #=> +# {"filename"=>"INST/tables/bin/McConfiguration.csv", "contents"=>"MC_CONFIGURATION\nLabel, ... +``` + +Python Example: + +```python +table = table_create_report("INST/tables/bin/ConfigTables.bin", "INST/tables/config/ConfigTables_def.txt") #=> +# {'filename': 'INST/tables/bin/ConfigTables.csv', 'contents': 'MC_CONFIGURATION\nLabel, ... +table = table_create_report("INST/tables/bin/ConfigTables.bin", "INST/tables/config/ConfigTables_def.txt", table_name="MC_CONFIGURATION") #=> +# {'filename': 'INST/tables/bin/ConfigTables.csv', 'contents': 'MC_CONFIGURATION\nLabel, ... +``` + ## Stashing Data These methods allow the user to store temporary data into COSMOS and retrieve it. The storage is implemented as a key / value storage (Ruby hash or Python dict). This can be used in scripts to store information that applies across multiple scripts or multiple runs of a single script. diff --git a/openc3-cosmos-cmd-tlm-api/app/controllers/tables_controller.rb b/openc3-cosmos-cmd-tlm-api/app/controllers/tables_controller.rb index c1283560bc..66eb1c2bba 100644 --- a/openc3-cosmos-cmd-tlm-api/app/controllers/tables_controller.rb +++ b/openc3-cosmos-cmd-tlm-api/app/controllers/tables_controller.rb @@ -33,10 +33,10 @@ def index def binary return unless authorization('system') - scope, binary, definition, table = sanitize_params([:scope, :binary, :definition, :table], require_params: false, allow_forward_slash: true) + scope, binary, definition, table_name = sanitize_params([:scope, :binary, :definition, :table_name], require_params: false, allow_forward_slash: true) return unless scope begin - file = Table.binary(scope, binary, definition, table) + file = Table.binary(scope, binary, definition, table_name) results = { filename: file.filename, contents: Base64.encode64(file.contents) } render json: results rescue Table::NotFound => e @@ -47,10 +47,10 @@ def binary def definition return unless authorization('system') - scope, definition, table = sanitize_params([:scope, :definition, :table], require_params: false, allow_forward_slash: true) + scope, definition, table_name = sanitize_params([:scope, :definition, :table_name], require_params: false, allow_forward_slash: true) return unless scope begin - file = Table.definition(scope, definition, table) + file = Table.definition(scope, definition, table_name) render json: { filename: file.filename, contents: file.contents } rescue Table::NotFound => e log_error(e) @@ -60,10 +60,10 @@ def definition def report return unless authorization('system') - scope, binary, definition, table = sanitize_params([:scope, :binary, :definition, :table], require_params: false, allow_forward_slash: true) + scope, binary, definition, table_name = sanitize_params([:scope, :binary, :definition, :table_name], require_params: false, allow_forward_slash: true) return unless scope begin - file = Table.report(scope, binary, definition, table) + file = Table.report(scope, binary, definition, table_name) render json: { filename: file.filename, contents: file.contents } rescue Table::NotFound => e log_error(e) diff --git a/openc3-cosmos-cmd-tlm-api/app/models/table.rb b/openc3-cosmos-cmd-tlm-api/app/models/table.rb index efab7c5bc1..a8fcbaf79f 100644 --- a/openc3-cosmos-cmd-tlm-api/app/models/table.rb +++ b/openc3-cosmos-cmd-tlm-api/app/models/table.rb @@ -79,12 +79,12 @@ def self.report(scope, binary_filename, definition_filename, table_name = nil) # Convert the typical table naming convention of all caps with underscores # to the typical binary convention of camelcase, e.g. MC_CONFIG => McConfig.bin filename = table_name.split('_').map { |part| part.capitalize }.join() - report.filename = "#{filename}.csv" + report.filename = "#{File.dirname(binary_filename)}/#{filename}.csv" else - report.filename = File.basename(binary_filename).sub('.bin', '.csv') + report.filename = binary_filename.sub('.bin', '.csv') end report.contents = OpenC3::TableManagerCore.report(binary, root_definition, table_name) - create(scope, binary_filename.sub('.bin', '.csv'), report.contents) + create(scope, report.filename, report.contents) return report end diff --git a/openc3-cosmos-cmd-tlm-api/spec/models/table_spec.rb b/openc3-cosmos-cmd-tlm-api/spec/models/table_spec.rb index 6fddcbd179..433cda0265 100644 --- a/openc3-cosmos-cmd-tlm-api/spec/models/table_spec.rb +++ b/openc3-cosmos-cmd-tlm-api/spec/models/table_spec.rb @@ -187,7 +187,7 @@ def add_table(table_name) @get_object.body.read = 'definition' allow(OpenC3::TableManagerCore).to receive(:report).and_return('report') file = Table.report('DEFAULT', 'INST/tables/bin/table.bin', 'INST/tables/config/table_def.txt') - expect(file.filename).to eql 'table.csv' + expect(file.filename).to eql 'INST/tables/bin/table.csv' expect(file.contents).to eql 'report' expect(@put_object['DEFAULT/targets_modified/INST/tables/bin/table.csv']).to eql "report" end @@ -202,9 +202,9 @@ def add_table(table_name) Dir.mkdir(tmp_dir) unless File.exist?(tmp_dir) allow(Dir).to receive(:mktmpdir).and_return(tmp_dir) file = Table.report('DEFAULT', 'INST/tables/bin/table.bin', 'INST/tables/config/table_def.txt', 'MY_TABLE') - expect(file.filename).to eql 'MyTable.csv' + expect(file.filename).to eql 'INST/tables/bin/MyTable.csv' expect(file.contents).to eql 'report' # Check the simple stub - expect(@put_object['DEFAULT/targets_modified/INST/tables/bin/table.csv']).to eql "report" + expect(@put_object['DEFAULT/targets_modified/INST/tables/bin/MyTable.csv']).to eql "report" # Real test is did we create the definition files files = Dir.glob("#{tmp_dir}/**/*").map {|file| File.basename(file) } expect(files).to include('table_def.txt') diff --git a/openc3-cosmos-init/plugins/packages/openc3-cosmos-tool-tablemanager/src/tools/TableManager/TableManager.vue b/openc3-cosmos-init/plugins/packages/openc3-cosmos-tool-tablemanager/src/tools/TableManager/TableManager.vue index 09b6d51327..7c0dde99b4 100644 --- a/openc3-cosmos-init/plugins/packages/openc3-cosmos-tool-tablemanager/src/tools/TableManager/TableManager.vue +++ b/openc3-cosmos-init/plugins/packages/openc3-cosmos-tool-tablemanager/src/tools/TableManager/TableManager.vue @@ -524,7 +524,7 @@ export default { formData.append('binary', this.filename) formData.append('definition', this.definitionFilename) if (tableName !== null) { - formData.append('table', tableName) + formData.append('table_name', tableName) } Api.post(`/openc3-api/tables/binary`, { data: formData, @@ -553,7 +553,7 @@ export default { const formData = new FormData() formData.append('definition', this.definitionFilename) if (tableName !== null) { - formData.append('table', tableName) + formData.append('table_name', tableName) } Api.post(`/openc3-api/tables/definition`, { data: formData, @@ -574,7 +574,7 @@ export default { formData.append('binary', this.filename) formData.append('definition', this.definitionFilename) if (tableName !== null) { - formData.append('table', tableName) + formData.append('table_name', tableName) } Api.post(`/openc3-api/tables/report`, { data: formData, diff --git a/openc3/lib/openc3/script/script.rb b/openc3/lib/openc3/script/script.rb index 5d05f29aab..26dd537035 100644 --- a/openc3/lib/openc3/script/script.rb +++ b/openc3/lib/openc3/script/script.rb @@ -25,18 +25,23 @@ require 'openc3/io/json_drb_object' require 'openc3/script/api_shared' require 'openc3/script/calendar' -require 'openc3/script/metadata' require 'openc3/script/commands' -require 'openc3/script/telemetry' -require 'openc3/script/limits' +require 'openc3/script/critical_cmd' require 'openc3/script/exceptions' +# openc3/script/extract is just helper methods +require 'openc3/script/limits' +require 'openc3/script/metadata' +require 'openc3/script/packages' +require 'openc3/script/plugins' require 'openc3/script/screen' require 'openc3/script/script_runner' require 'openc3/script/storage' +# openc3/script/suite_results and suite_runner are used by +# running_script.rb and the script_runner_api +# openc3/script/suite is used by end user SR Suites +require 'openc3/script/tables' +require 'openc3/script/telemetry' require 'openc3/script/web_socket_api' -require 'openc3/script/packages' -require 'openc3/script/plugins' -require 'openc3/script/critical_cmd' require 'openc3/utilities/authentication' $api_server = nil diff --git a/openc3/lib/openc3/script/tables.rb b/openc3/lib/openc3/script/tables.rb new file mode 100644 index 0000000000..f53a729a3b --- /dev/null +++ b/openc3/lib/openc3/script/tables.rb @@ -0,0 +1,49 @@ +# encoding: ascii-8bit + +# Copyright 2025 OpenC3, Inc. +# 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 file may also be used under the terms of a commercial license +# if purchased from OpenC3, Inc. + +module OpenC3 + module Script + private + + def table_create_binary(definition, scope: $openc3_scope) + post_data = {} + post_data['definition'] = definition + response = $api_server.request('post', '/openc3-api/tables/generate', json: true, data: post_data, scope: scope) + return _handle_response(response, 'Failed to create binary') + end + + def table_create_report(filename, definition, table_name: nil, scope: $openc3_scope) + post_data = {} + post_data['binary'] = filename + post_data['definition'] = definition + post_data['table_name'] = table_name if table_name + response = $api_server.request('post', '/openc3-api/tables/report', json: true, data: post_data, scope: scope) + return _handle_response(response, 'Failed to create report') + end + + # Helper method to handle the response + def _handle_response(response, error_message) + return nil if response.nil? + if response.status >= 400 + result = JSON.parse(response.body, :allow_nan => true, :create_additions => true) + raise "#{error_message} due to #{result['message']}" + end + return JSON.parse(response.body, :allow_nan => true, :create_additions => true) + end + end +end diff --git a/openc3/python/openc3/script/__init__.py b/openc3/python/openc3/script/__init__.py index f0a58b013a..c14e84894b 100644 --- a/openc3/python/openc3/script/__init__.py +++ b/openc3/python/openc3/script/__init__.py @@ -132,16 +132,17 @@ def prompt( ########################################################################### from .api_shared import * -from .cosmos_calendar import * from .commands import * +from .cosmos_calendar import * +from .critical_cmd import * from .exceptions import * from .limits import * -from .telemetry import * from .metadata import * from .screen import * from .script_runner import * from .storage import * -from .critical_cmd import * +from .tables import * +from .telemetry import * from openc3.api import WHITELIST diff --git a/openc3/python/openc3/script/tables.py b/openc3/python/openc3/script/tables.py new file mode 100644 index 0000000000..88a4c0e9c0 --- /dev/null +++ b/openc3/python/openc3/script/tables.py @@ -0,0 +1,47 @@ +# Copyright 2025 OpenC3, Inc. +# 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 file may also be used under the terms of a commercial license +# if purchased from OpenC3, Inc. + +import json +import openc3.script +from openc3.environment import OPENC3_SCOPE + +def table_create_binary(definition: str, scope: str = OPENC3_SCOPE): + data = {} + data['definition'] = definition + response = openc3.script.API_SERVER.request("post", "/openc3-api/tables/generate", data=data, json=True, scope=scope) + return _handle_response(response, 'Failed to create binary') + +def table_create_report(filename: str, definition: str, table_name: str = None, scope: str = OPENC3_SCOPE): + data = {} + data['binary'] = filename + data['definition'] = definition + if table_name: + data['table_name'] = table_name + response = openc3.script.API_SERVER.request("post", "/openc3-api/tables/report", data=data, json=True, scope=scope) + return _handle_response(response, 'Failed to create binary') + +# Helper method to handle the response +def _handle_response(response, error_message): + if response is None: + return None + if response.status_code >= 400: + result = json.loads(response.text) + raise RuntimeError(f"{error_message} due to {result['message']}") + # Not sure why the response body is empty (on delete) but check for that + if response.text is None or len(response.text) == 0: + return None + else: + return json.loads(response.text)