fprime-gds 3.6.2a1__py3-none-any.whl → 4.0.0a2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. fprime_gds/common/communication/adapters/uart.py +34 -25
  2. fprime_gds/common/decoders/ch_decoder.py +1 -1
  3. fprime_gds/common/decoders/event_decoder.py +2 -1
  4. fprime_gds/common/decoders/pkt_decoder.py +1 -1
  5. fprime_gds/common/distributor/distributor.py +2 -2
  6. fprime_gds/common/encoders/ch_encoder.py +2 -2
  7. fprime_gds/common/encoders/cmd_encoder.py +2 -2
  8. fprime_gds/common/encoders/event_encoder.py +2 -2
  9. fprime_gds/common/encoders/pkt_encoder.py +2 -2
  10. fprime_gds/common/encoders/seq_writer.py +2 -2
  11. fprime_gds/common/fpy/__init__.py +0 -0
  12. fprime_gds/common/fpy/serialize_bytecode.py +229 -0
  13. fprime_gds/common/fpy/types.py +203 -0
  14. fprime_gds/common/gds_cli/base_commands.py +1 -1
  15. fprime_gds/common/handlers.py +39 -0
  16. fprime_gds/common/loaders/fw_type_json_loader.py +54 -0
  17. fprime_gds/common/loaders/pkt_json_loader.py +121 -0
  18. fprime_gds/common/loaders/prm_json_loader.py +85 -0
  19. fprime_gds/common/pipeline/dictionaries.py +21 -4
  20. fprime_gds/common/pipeline/encoding.py +19 -0
  21. fprime_gds/common/pipeline/histories.py +4 -0
  22. fprime_gds/common/pipeline/standard.py +16 -2
  23. fprime_gds/common/templates/prm_template.py +81 -0
  24. fprime_gds/common/testing_fw/api.py +42 -0
  25. fprime_gds/common/testing_fw/pytest_integration.py +25 -2
  26. fprime_gds/common/tools/README.md +34 -0
  27. fprime_gds/common/tools/params.py +246 -0
  28. fprime_gds/common/utils/config_manager.py +6 -6
  29. fprime_gds/executables/apps.py +184 -11
  30. fprime_gds/executables/cli.py +443 -125
  31. fprime_gds/executables/comm.py +5 -2
  32. fprime_gds/executables/fprime_cli.py +3 -3
  33. fprime_gds/executables/run_deployment.py +12 -4
  34. fprime_gds/flask/static/js/vue-support/channel.js +1 -1
  35. fprime_gds/flask/static/js/vue-support/event.js +1 -1
  36. fprime_gds/plugin/definitions.py +86 -8
  37. fprime_gds/plugin/system.py +171 -58
  38. {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a2.dist-info}/METADATA +18 -19
  39. {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a2.dist-info}/RECORD +44 -35
  40. {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a2.dist-info}/WHEEL +1 -1
  41. {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a2.dist-info}/entry_points.txt +2 -0
  42. {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a2.dist-info/licenses}/LICENSE.txt +0 -0
  43. {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a2.dist-info/licenses}/NOTICE.txt +0 -0
  44. {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a2.dist-info}/top_level.txt +0 -0
@@ -59,7 +59,7 @@ class SerialAdapter(fprime_gds.common.communication.adapters.base.BaseAdapter):
59
59
  4000000,
60
60
  ]
61
61
 
62
- def __init__(self, device, baud):
62
+ def __init__(self, device, baud, skip_check):
63
63
  """
64
64
  Initialize the serial adapter using the default settings. This does not open the serial port, but sets up all
65
65
  the internal variables used when opening the device.
@@ -77,9 +77,14 @@ class SerialAdapter(fprime_gds.common.communication.adapters.base.BaseAdapter):
77
77
  Opens the serial port based on previously supplied settings. If the port is already open, then close it first.
78
78
  Then open the port up again.
79
79
  """
80
- self.close()
81
- self.serial = serial.Serial(self.device, self.baud)
82
- return self.serial is not None
80
+ try:
81
+ self.close()
82
+ self.serial = serial.Serial(self.device, self.baud)
83
+ return self.serial is not None
84
+ except serial.serialutil.SerialException as exc:
85
+ LOGGER.warning("Serial exception caught: %s. Reconnecting.", (str(exc)))
86
+ self.close()
87
+ return False
83
88
 
84
89
  def close(self):
85
90
  """
@@ -100,12 +105,11 @@ class SerialAdapter(fprime_gds.common.communication.adapters.base.BaseAdapter):
100
105
  :return: True, when data was sent through the UART. False otherwise.
101
106
  """
102
107
  try:
103
- if self.serial is None:
104
- self.open()
105
- written = self.serial.write(frame)
106
- # Not believed to be possible to not send everything without getting a timeout exception
107
- assert written == len(frame)
108
- return True
108
+ if self.serial is not None or self.open():
109
+ written = self.serial.write(frame)
110
+ # Not believed to be possible to not send everything without getting a timeout exception
111
+ assert written == len(frame)
112
+ return True
109
113
  except serial.serialutil.SerialException as exc:
110
114
  LOGGER.warning("Serial exception caught: %s. Reconnecting.", (str(exc)))
111
115
  self.close()
@@ -121,17 +125,16 @@ class SerialAdapter(fprime_gds.common.communication.adapters.base.BaseAdapter):
121
125
  """
122
126
  data = b""
123
127
  try:
124
- if self.serial is None:
125
- self.open()
126
- # Read as much data as possible, while ensuring to block if no data is available at this time. Note: as much
127
- # data is read as possible to avoid a long-return time to this call. Minimum data to read is one byte in
128
- # order to block this function while data is incoming.
129
- self.serial.timeout = timeout
130
- data = self.serial.read(1) # Force a block for at least 1 character
131
- while self.serial.in_waiting:
132
- data += self.serial.read(
133
- self.serial.in_waiting
134
- ) # Drain the incoming data queue
128
+ if self.serial is not None or self.open():
129
+ # Read as much data as possible, while ensuring to block if no data is available at this time. Note: as much
130
+ # data is read as possible to avoid a long-return time to this call. Minimum data to read is one byte in
131
+ # order to block this function while data is incoming.
132
+ self.serial.timeout = timeout
133
+ data = self.serial.read(1) # Force a block for at least 1 character
134
+ while self.serial.in_waiting:
135
+ data += self.serial.read(
136
+ self.serial.in_waiting
137
+ ) # Drain the incoming data queue
135
138
  except serial.serialutil.SerialException as exc:
136
139
  LOGGER.warning("Serial exception caught: %s. Reconnecting.", (str(exc)))
137
140
  self.close()
@@ -161,6 +164,12 @@ class SerialAdapter(fprime_gds.common.communication.adapters.base.BaseAdapter):
161
164
  "default": 9600,
162
165
  "help": "Baud rate of the serial device.",
163
166
  },
167
+ ("--uart-skip-port-check",): {
168
+ "dest": "skip_check",
169
+ "default": False,
170
+ "action": "store_true",
171
+ "help": "Skip checking that the port exists"
172
+ }
164
173
  }
165
174
 
166
175
  @classmethod
@@ -175,20 +184,20 @@ class SerialAdapter(fprime_gds.common.communication.adapters.base.BaseAdapter):
175
184
  return cls
176
185
 
177
186
  @classmethod
178
- def check_arguments(cls, device, baud):
187
+ def check_arguments(cls, device, baud, skip_check):
179
188
  """
180
189
  Code that should check arguments of this adapter. If there is a problem with this code, then a "ValueError"
181
190
  should be raised describing the problem with these arguments.
182
191
 
183
192
  :param args: arguments as dictionary
184
193
  """
185
- ports = map(lambda info: info.device, list_ports.comports(include_links=True))
186
- if device not in ports:
194
+ ports = list(map(lambda info: info.device, list_ports.comports(include_links=True)))
195
+ if not skip_check and device not in ports:
187
196
  msg = f"Serial port '{device}' not valid. Available ports: {ports}"
188
197
  raise ValueError(
189
198
  msg
190
199
  )
191
- # Note: baud rate may not *always* work. These are a superset
200
+ # Note: baud rate may not *always* work. These are a superset.
192
201
  try:
193
202
  baud = int(baud)
194
203
  except ValueError:
@@ -44,7 +44,7 @@ class ChDecoder(Decoder):
44
44
  config = config_manager.ConfigManager().get_instance()
45
45
 
46
46
  self.__dict = ch_dict
47
- self.id_obj = config.get_type("ch_id")
47
+ self.id_obj = config.get_type("FwChanIdType")
48
48
 
49
49
  def decode_api(self, data):
50
50
  """
@@ -19,6 +19,7 @@ from fprime.common.models.serialize.type_exceptions import TypeException
19
19
 
20
20
  from fprime_gds.common.data_types import event_data
21
21
  from fprime_gds.common.decoders import decoder
22
+ from fprime_gds.common.decoders.decoder import DecodingException
22
23
  from fprime_gds.common.utils import config_manager
23
24
 
24
25
 
@@ -43,7 +44,7 @@ class EventDecoder(decoder.Decoder):
43
44
  config = config_manager.ConfigManager().get_instance()
44
45
 
45
46
  self.__dict = event_dict
46
- self.id_obj = config.get_type("event_id")
47
+ self.id_obj = config.get_type("FwEventIdType")
47
48
 
48
49
  def decode_api(self, data):
49
50
  """
@@ -45,7 +45,7 @@ class PktDecoder(ChDecoder):
45
45
  super().__init__(ch_dict, config)
46
46
 
47
47
  self.__dict = pkt_name_dict
48
- self.id_obj = config.get_type("pkt_id")
48
+ self.id_obj = config.get_type("FwTlmPacketizeIdType")
49
49
 
50
50
  def decode_api(self, data):
51
51
  """
@@ -57,7 +57,7 @@ class Distributor(DataHandler):
57
57
  self.key_frame = int(config.get("framing", "key_val"), 16)
58
58
  self.key_obj = config.get_type("key_val")
59
59
  self.len_obj = config.get_type("msg_len")
60
- self.desc_obj = config.get_type("msg_desc")
60
+ self.desc_obj = config.get_type("FwPacketDescriptorType")
61
61
 
62
62
  # NOTE we could use either the type of the object or an enum as the type argument.
63
63
  # It should indicate what the decoder decodes.
@@ -204,4 +204,4 @@ class Distributor(DataHandler):
204
204
  try:
205
205
  d.data_callback(msg)
206
206
  except DecodingException as dexc:
207
- LOGGER.warning("Decoding error occurred: %s. Skipping.", dexc)
207
+ LOGGER.warning("Decoding error occurred: %s. Skipping.", dexc)
@@ -57,8 +57,8 @@ class ChEncoder(Encoder):
57
57
  super().__init__(config)
58
58
 
59
59
  self.len_obj = self.config.get_type("msg_len")
60
- self.desc_obj = self.config.get_type("msg_desc")
61
- self.id_obj = self.config.get_type("ch_id")
60
+ self.desc_obj = self.config.get_type("FwPacketDescriptorType")
61
+ self.id_obj = self.config.get_type("FwChanIdType")
62
62
 
63
63
  def encode_api(self, data):
64
64
  """
@@ -72,8 +72,8 @@ class CmdEncoder(encoder.Encoder):
72
72
  super().__init__(config)
73
73
 
74
74
  self.len_obj = self.config.get_type("msg_len")
75
- self.desc_obj = self.config.get_type("msg_desc")
76
- self.opcode_obj = self.config.get_type("op_code")
75
+ self.desc_obj = self.config.get_type("FwPacketDescriptorType")
76
+ self.opcode_obj = self.config.get_type("FwOpcodeType")
77
77
 
78
78
  def encode_api(self, data):
79
79
  """
@@ -61,8 +61,8 @@ class EventEncoder(Encoder):
61
61
  super().__init__(config)
62
62
 
63
63
  self.len_obj = self.config.get_type("msg_len")
64
- self.desc_obj = self.config.get_type("msg_desc")
65
- self.id_obj = self.config.get_type("event_id")
64
+ self.desc_obj = self.config.get_type("FwPacketDescriptorType")
65
+ self.id_obj = self.config.get_type("FwEventIdType")
66
66
 
67
67
  def encode_api(self, data):
68
68
  """
@@ -61,8 +61,8 @@ class PktEncoder(Encoder):
61
61
  super().__init__(config)
62
62
 
63
63
  self.len_obj = self.config.get_type("msg_len")
64
- self.desc_obj = self.config.get_type("msg_desc")
65
- self.id_obj = self.config.get_type("pkt_id")
64
+ self.desc_obj = self.config.get_type("FwPacketDescriptorType")
65
+ self.id_obj = self.config.get_type("FwTlmPacketizeIdType")
66
66
 
67
67
  def encode_api(self, data):
68
68
  """
@@ -28,8 +28,8 @@ class SeqBinaryWriter:
28
28
 
29
29
  self.__fd = None
30
30
  self.__timebase = timebase
31
- self.desc_obj = config.get_type("msg_desc")
32
- self.opcode_obj = config.get_type("op_code")
31
+ self.desc_obj = config.get_type("FwPacketDescriptorType")
32
+ self.opcode_obj = config.get_type("FwOpcodeType")
33
33
  self.len_obj = config.get_type("msg_len")
34
34
 
35
35
  def open(self, filename):
File without changes
@@ -0,0 +1,229 @@
1
+ from __future__ import annotations
2
+ from dataclasses import astuple
3
+ import inspect
4
+ import json
5
+ from pathlib import Path
6
+ from argparse import ArgumentParser
7
+ import struct
8
+ import zlib
9
+ from fprime_gds.common.fpy.types import (
10
+ StatementTemplate,
11
+ StatementData,
12
+ Header,
13
+ Footer,
14
+ HEADER_FORMAT,
15
+ FOOTER_FORMAT,
16
+ StatementType,
17
+ FPY_DIRECTIVES,
18
+ BytecodeParseContext,
19
+ get_type_obj_for,
20
+ )
21
+ from fprime_gds.common.loaders.ch_json_loader import ChJsonLoader
22
+ from fprime_gds.common.loaders.cmd_json_loader import CmdJsonLoader
23
+ from fprime.common.models.serialize.numerical_types import (
24
+ U8Type,
25
+ )
26
+
27
+ from fprime_gds.common.loaders.prm_json_loader import PrmJsonLoader
28
+
29
+
30
+ def serialize_statement(stmt: StatementData) -> bytes:
31
+ """converts a StatementData object into bytes that the FpySequencer can read"""
32
+ # see https://github.com/nasa/fprime/issues/3023#issuecomment-2693051677
33
+ # TODO replace this with actual documentation
34
+
35
+ # type: U8 (0 if directive, 1 if cmd)
36
+ # opcode: FwOpcodeType (default U32)
37
+ # argBufSize: FwSizeStoreType (default U16)
38
+ # argBuf: X bytes
39
+
40
+ output = bytes()
41
+ output += U8Type(stmt.template.statement_type.value).serialize()
42
+ output += get_type_obj_for("FwOpcodeType")(stmt.template.opcode).serialize()
43
+
44
+ arg_bytes = bytes()
45
+ for arg in stmt.arg_values:
46
+ arg_bytes += arg.serialize()
47
+
48
+ output += get_type_obj_for("FwSizeStoreType")(len(arg_bytes)).serialize()
49
+ output += arg_bytes
50
+
51
+ return output
52
+
53
+
54
+ def parse_str_as_statement(
55
+ stmt: str, templates: list[StatementTemplate], context: BytecodeParseContext
56
+ ) -> StatementData:
57
+ """Converts a human-readable line of bytecode into a StatementData instance, given a list of
58
+ possible statement templates"""
59
+ name = stmt.split()[0]
60
+ args = stmt[len(name) :]
61
+
62
+ args = json.loads("[" + args + "]")
63
+
64
+ matching_template = [t for t in templates if t.name == name]
65
+ if len(matching_template) != 1:
66
+ # no unique match
67
+ if len(matching_template) == 0:
68
+ raise RuntimeError("Could not find command or directive " + str(name))
69
+ raise RuntimeError(
70
+ "Found multiple commands or directives with name " + str(name)
71
+ )
72
+ matching_template = matching_template[0]
73
+
74
+ arg_values = []
75
+ if len(args) < len(matching_template.args):
76
+ raise RuntimeError(
77
+ "Missing arguments for statement "
78
+ + str(matching_template.name)
79
+ + ": "
80
+ + str(matching_template.args[len(args) :])
81
+ )
82
+ if len(args) > len(matching_template.args):
83
+ raise RuntimeError(
84
+ "Extra arguments for"
85
+ + str(matching_template.name)
86
+ + ": "
87
+ + str(args[len(matching_template.args) :])
88
+ )
89
+ for index, arg_json in enumerate(args):
90
+ arg_type = matching_template.args[index]
91
+ if inspect.isclass(arg_type):
92
+ # it's a type. instantiate it with the json
93
+ arg_value = arg_type(arg_json)
94
+ else:
95
+ # it's a function. give it the json and the ctx
96
+ arg_value = arg_type(arg_json, context)
97
+ arg_values.append(arg_value)
98
+
99
+ return StatementData(matching_template, arg_values)
100
+
101
+
102
+ def main():
103
+ arg_parser = ArgumentParser()
104
+ arg_parser.add_argument(
105
+ "input", type=Path, help="The path to the input .fpybc file"
106
+ )
107
+
108
+ arg_parser.add_argument(
109
+ "-d",
110
+ "--dictionary",
111
+ type=Path,
112
+ help="The JSON topology dictionary to compile against",
113
+ required=True,
114
+ )
115
+
116
+ arg_parser.add_argument(
117
+ "-o",
118
+ "--output",
119
+ type=Path,
120
+ help="The output .bin file path. Defaults to the input file path with a .bin extension",
121
+ default=None,
122
+ )
123
+
124
+ args = arg_parser.parse_args()
125
+
126
+ if not args.input.exists():
127
+ print("Input file", args.input, "does not exist")
128
+ exit(1)
129
+
130
+ if not args.dictionary.exists():
131
+ print("Dictionary file", args.dictionary, "does not exist")
132
+ exit(1)
133
+
134
+ serialize_bytecode(args.input, args.dictionary, args.output)
135
+
136
+
137
+ def serialize_bytecode(input: Path, dictionary: Path, output: Path = None):
138
+ """Given an input .fpybc file, and a dictionary .json file, converts the
139
+ bytecode file into binary and writes it to the output file. If the output file
140
+ is None, writes it to the input file with a .bin extension"""
141
+ cmd_json_dict_loader = CmdJsonLoader(str(dictionary))
142
+ (_, cmd_name_dict, _) = cmd_json_dict_loader.construct_dicts(
143
+ str(dictionary)
144
+ )
145
+
146
+ stmt_templates = []
147
+ stmt_templates.extend(FPY_DIRECTIVES)
148
+ for cmd_template in cmd_name_dict.values():
149
+ stmt_template = StatementTemplate(
150
+ StatementType.CMD,
151
+ cmd_template.opcode,
152
+ cmd_template.get_full_name(),
153
+ [arg[2] for arg in cmd_template.arguments],
154
+ )
155
+ stmt_templates.append(stmt_template)
156
+
157
+ tlm_json_loader = ChJsonLoader(str(dictionary))
158
+ (_, tlm_name_dict, _) = tlm_json_loader.construct_dicts(
159
+ str(dictionary)
160
+ )
161
+
162
+ prm_json_loader = PrmJsonLoader(str(dictionary))
163
+ (_, prm_name_dict, _) = prm_json_loader.construct_dicts(
164
+ str(dictionary)
165
+ )
166
+
167
+ context = BytecodeParseContext()
168
+ context.types = cmd_json_dict_loader.parsed_types
169
+ context.channels = tlm_name_dict
170
+ context.params = prm_name_dict
171
+
172
+ input_lines = input.read_text().splitlines()
173
+ input_lines = [line.strip() for line in input_lines]
174
+ # remove comments and empty lines
175
+ input_lines = [
176
+ line for line in input_lines if not line.startswith(";") and len(line) > 0
177
+ ]
178
+
179
+ goto_tags = {}
180
+ stmt_idx = 0
181
+ statement_strs: list[str] = []
182
+ for stmt in input_lines:
183
+ if stmt.endswith(":"):
184
+ # it's a goto tag
185
+ goto_tags[stmt[:-1]] = stmt_idx
186
+ else:
187
+ statement_strs.append(stmt)
188
+ stmt_idx += 1
189
+
190
+ context.goto_tags = goto_tags
191
+
192
+ statements: list[StatementData] = []
193
+ for stmt_idx, stmt in enumerate(statement_strs):
194
+ try:
195
+ stmt_data = parse_str_as_statement(stmt, stmt_templates, context)
196
+ statements.append(stmt_data)
197
+ except BaseException as e:
198
+ raise RuntimeError(
199
+ "Exception while parsing statement index " + str(stmt_idx) + ": " + stmt
200
+ ) from e
201
+
202
+ # perform some checks for things we know will fail
203
+ for stmt in statements:
204
+ if stmt.template.name == "GOTO":
205
+ if stmt.arg_values[0].val > len(statements):
206
+ raise RuntimeError(
207
+ f"GOTO index is outside the valid range for this sequence (was {stmt.arg_values[0].val}, should be <{len(statements)})"
208
+ )
209
+
210
+ output_bytes = bytes()
211
+
212
+ for stmt in statements:
213
+ output_bytes += serialize_statement(stmt)
214
+
215
+ header = Header(0, 0, 0, 1, 0, len(statements), len(output_bytes))
216
+ output_bytes = struct.pack(HEADER_FORMAT, *astuple(header)) + output_bytes
217
+
218
+ crc = zlib.crc32(output_bytes) % (1 << 32)
219
+ footer = Footer(crc)
220
+ output_bytes += struct.pack(FOOTER_FORMAT, *astuple(footer))
221
+
222
+ if output is None:
223
+ output = input.with_suffix(".bin")
224
+
225
+ output.write_bytes(output_bytes)
226
+
227
+
228
+ if __name__ == "__main__":
229
+ main()
@@ -0,0 +1,203 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass, field
3
+ from enum import Enum
4
+ import struct
5
+ from typing import Any, Callable
6
+ from fprime.common.models.serialize.type_base import BaseType, ValueType
7
+ from fprime.common.models.serialize.time_type import TimeType
8
+ from fprime.common.models.serialize.numerical_types import U32Type, U16Type, U8Type
9
+ from fprime.common.models.serialize.string_type import StringType
10
+
11
+ from fprime_gds.common.loaders.json_loader import PRIMITIVE_TYPE_MAP
12
+ from fprime_gds.common.templates.ch_template import ChTemplate
13
+ from fprime_gds.common.templates.prm_template import PrmTemplate
14
+
15
+
16
+ def get_type_obj_for(type: str) -> type[ValueType]:
17
+ if type == "FwOpcodeType":
18
+ return U32Type
19
+ elif type == "FwSizeStoreType":
20
+ return U16Type
21
+ elif type == "FwChanIdType":
22
+ return U32Type
23
+ elif type == "FwPrmIdType":
24
+ return U32Type
25
+
26
+ raise RuntimeError("Unknown FPrime type alias " + str(type))
27
+
28
+
29
+ class StatementType(Enum):
30
+ DIRECTIVE = 0
31
+ CMD = 1
32
+
33
+
34
+ @dataclass
35
+ class StatementTemplate:
36
+ """a statement with unspecified argument values"""
37
+
38
+ statement_type: StatementType
39
+ opcode: int
40
+ name: str
41
+ """fully qualified statement name"""
42
+ args: list[type[BaseType] | Callable[[Any, BytecodeParseContext], BaseType]]
43
+ """list of argument types of this statement, or functions that return an arg type"""
44
+
45
+
46
+ @dataclass
47
+ class StatementData:
48
+ template: StatementTemplate
49
+ arg_values: list[BaseType]
50
+
51
+
52
+ HEADER_FORMAT = "!BBBBBHI"
53
+ HEADER_SIZE = struct.calcsize(HEADER_FORMAT)
54
+
55
+
56
+ @dataclass
57
+ class Header:
58
+ majorVersion: int
59
+ minorVersion: int
60
+ patchVersion: int
61
+ schemaVersion: int
62
+ argumentCount: int
63
+ statementCount: int
64
+ bodySize: int
65
+
66
+
67
+ FOOTER_FORMAT = "!I"
68
+ FOOTER_SIZE = struct.calcsize(FOOTER_FORMAT)
69
+
70
+
71
+ @dataclass
72
+ class Footer:
73
+ crc: int
74
+
75
+
76
+ class DirectiveOpcode(Enum):
77
+ INVALID = 0x00000000
78
+ WAIT_REL = 0x00000001
79
+ WAIT_ABS = 0x00000002
80
+ SET_LVAR = 0x00000003
81
+ GOTO = 0x00000004
82
+ IF = 0x00000005
83
+ NO_OP = 0x00000006
84
+ GET_TLM = 0x00000007
85
+ GET_PRM = 0x00000008
86
+
87
+
88
+ @dataclass
89
+ class BytecodeParseContext:
90
+ goto_tags: map[str, int] = field(default_factory=dict)
91
+ """a map of tag name with tag statement index"""
92
+ types: map[str, type[BaseType]] = field(default_factory=dict)
93
+ """a map of name to all parsed types available in the dictionary"""
94
+ channels: map[str, ChTemplate] = field(default_factory=dict)
95
+ """a map of name to ChTemplate object for all tlm channels"""
96
+ params: map[str, PrmTemplate] = field(default_factory=dict)
97
+ """a map of name to PrmTemplate object for all prms"""
98
+
99
+
100
+ def time_type_from_json(js, ctx: BytecodeParseContext):
101
+ return TimeType(js["time_base"], js["time_context"], js["seconds"], js["useconds"])
102
+
103
+
104
+ def arbitrary_type_from_json(js, ctx: BytecodeParseContext):
105
+ type_name = js["type"]
106
+
107
+ if type_name == "string":
108
+ # by default no max size restrictions in the bytecode
109
+ return StringType.construct_type(f"String", None)(js["value"])
110
+
111
+ # try first checking parsed_types, then check primitive types
112
+ type_class = ctx.types.get(type_name, PRIMITIVE_TYPE_MAP.get(type_name, None))
113
+ if type_class is None:
114
+ raise RuntimeError("Unknown type " + str(type_name))
115
+
116
+ return type_class(js["value"])
117
+
118
+
119
+ def goto_tag_or_idx_from_json(js, ctx: BytecodeParseContext):
120
+ if isinstance(js, str):
121
+ # it's a tag
122
+ if js not in ctx.goto_tags:
123
+ raise RuntimeError("Unknown goto tag " + str(js))
124
+ return U32Type(ctx.goto_tags[js])
125
+
126
+ # otherwise it is a statement index
127
+ return U32Type(js)
128
+
129
+
130
+ def tlm_chan_id_from_json(js, ctx: BytecodeParseContext):
131
+ if isinstance(js, str):
132
+ if js not in ctx.channels:
133
+ raise RuntimeError("Unknown telemetry channel " + str(js))
134
+ return get_type_obj_for("FwChanIdType")(ctx.channels[js].id)
135
+ elif isinstance(js, int):
136
+ matching = [tmp for tmp in ctx.channels.keys() if tmp.id == js]
137
+ if len(matching) != 1:
138
+ if len(matching) == 0:
139
+ raise RuntimeError("Unknown telemetry channel id " + str(js))
140
+ raise RuntimeError("Multiple matches for telemetry channel id " + str(js))
141
+ matching = matching[0]
142
+ return get_type_obj_for("FwChanIdType")(matching.id)
143
+
144
+
145
+ def prm_id_from_json(js, ctx: BytecodeParseContext):
146
+ if isinstance(js, str):
147
+ if js not in ctx.params:
148
+ raise RuntimeError("Unknown parameter " + str(js))
149
+ return get_type_obj_for("FwPrmIdType")(ctx.params[js].prm_id)
150
+ elif isinstance(js, int):
151
+ matching = [tmp for tmp in ctx.params.keys() if tmp.prm_id == js]
152
+ if len(matching) != 1:
153
+ if len(matching) == 0:
154
+ raise RuntimeError("Unknown param id " + str(js))
155
+ raise RuntimeError("Multiple matches for param id " + str(js))
156
+ matching = matching[0]
157
+ return get_type_obj_for("FwPrmIdType")(matching.prm_id)
158
+
159
+
160
+ FPY_DIRECTIVES: list[StatementTemplate] = [
161
+ StatementTemplate(
162
+ StatementType.DIRECTIVE,
163
+ DirectiveOpcode.WAIT_REL.value,
164
+ "WAIT_REL",
165
+ [U32Type, U32Type],
166
+ ),
167
+ StatementTemplate(
168
+ StatementType.DIRECTIVE,
169
+ DirectiveOpcode.WAIT_ABS.value,
170
+ "WAIT_ABS",
171
+ [time_type_from_json],
172
+ ),
173
+ StatementTemplate(
174
+ StatementType.DIRECTIVE,
175
+ DirectiveOpcode.SET_LVAR.value,
176
+ "SET_LVAR",
177
+ [U8Type, arbitrary_type_from_json],
178
+ ),
179
+ StatementTemplate(
180
+ StatementType.DIRECTIVE,
181
+ DirectiveOpcode.GOTO.value,
182
+ "GOTO",
183
+ [goto_tag_or_idx_from_json],
184
+ ),
185
+ StatementTemplate(
186
+ StatementType.DIRECTIVE,
187
+ DirectiveOpcode.IF.value,
188
+ "IF",
189
+ [U8Type, goto_tag_or_idx_from_json],
190
+ ),
191
+ StatementTemplate(
192
+ StatementType.DIRECTIVE,
193
+ DirectiveOpcode.GET_TLM.value,
194
+ "GET_TLM",
195
+ [U8Type, U8Type, tlm_chan_id_from_json],
196
+ ),
197
+ StatementTemplate(
198
+ StatementType.DIRECTIVE,
199
+ DirectiveOpcode.GET_PRM.value,
200
+ "GET_PRM",
201
+ [U8Type, prm_id_from_json],
202
+ ),
203
+ ]
@@ -130,7 +130,7 @@ class BaseCommand(abc.ABC):
130
130
  passes the given filter
131
131
  """
132
132
  project_dictionary = Dictionaries()
133
- project_dictionary.load_dictionaries(dictionary_path, packet_spec=None)
133
+ project_dictionary.load_dictionaries(dictionary_path, packet_spec=None, packet_set_name=None)
134
134
  items = cls._get_item_list(project_dictionary, search_filter)
135
135
  return cls._get_item_list_string(items, json)
136
136