fprime-gds 3.6.1__py3-none-any.whl → 4.0.0a1__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 (52) hide show
  1. fprime_gds/common/decoders/ch_decoder.py +1 -1
  2. fprime_gds/common/decoders/event_decoder.py +2 -1
  3. fprime_gds/common/decoders/pkt_decoder.py +1 -1
  4. fprime_gds/common/distributor/distributor.py +2 -2
  5. fprime_gds/common/encoders/ch_encoder.py +2 -2
  6. fprime_gds/common/encoders/cmd_encoder.py +2 -2
  7. fprime_gds/common/encoders/event_encoder.py +2 -2
  8. fprime_gds/common/encoders/pkt_encoder.py +2 -2
  9. fprime_gds/common/encoders/seq_writer.py +2 -2
  10. fprime_gds/common/fpy/__init__.py +0 -0
  11. fprime_gds/common/fpy/serialize_bytecode.py +229 -0
  12. fprime_gds/common/fpy/types.py +203 -0
  13. fprime_gds/common/gds_cli/base_commands.py +1 -1
  14. fprime_gds/common/handlers.py +39 -0
  15. fprime_gds/common/loaders/fw_type_json_loader.py +54 -0
  16. fprime_gds/common/loaders/json_loader.py +15 -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 +53 -43
  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 +280 -122
  31. fprime_gds/executables/comm.py +5 -2
  32. fprime_gds/executables/fprime_cli.py +3 -3
  33. fprime_gds/executables/run_deployment.py +10 -3
  34. fprime_gds/flask/app.py +3 -0
  35. fprime_gds/flask/resource.py +5 -2
  36. fprime_gds/flask/static/addons/chart-display/addon.js +8 -3
  37. fprime_gds/flask/static/js/datastore.js +1 -0
  38. fprime_gds/flask/static/js/vue-support/channel.js +1 -1
  39. fprime_gds/flask/static/js/vue-support/event.js +1 -1
  40. fprime_gds/plugin/definitions.py +86 -8
  41. fprime_gds/plugin/system.py +171 -58
  42. {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0a1.dist-info}/METADATA +17 -19
  43. {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0a1.dist-info}/RECORD +48 -43
  44. {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0a1.dist-info}/WHEEL +1 -1
  45. {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0a1.dist-info}/entry_points.txt +2 -0
  46. fprime_gds/common/loaders/ch_py_loader.py +0 -79
  47. fprime_gds/common/loaders/cmd_py_loader.py +0 -66
  48. fprime_gds/common/loaders/event_py_loader.py +0 -75
  49. fprime_gds/common/loaders/python_loader.py +0 -132
  50. {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0a1.dist-info/licenses}/LICENSE.txt +0 -0
  51. {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0a1.dist-info/licenses}/NOTICE.txt +0 -0
  52. {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0a1.dist-info}/top_level.txt +0 -0
@@ -71,6 +71,18 @@ class JsonLoader(dict_loader.DictLoader):
71
71
  self.json_dict["metadata"].get("projectVersion", "unknown"),
72
72
  )
73
73
 
74
+ def get_metadata(self):
75
+ """Get the metadata field of the JSON dictionary
76
+
77
+ Raises:
78
+ GdsDictionaryParsingException: if the dictionary has no metadata field
79
+ """
80
+ if "metadata" not in self.json_dict:
81
+ raise GdsDictionaryParsingException(
82
+ f"Dictionary has no metadata field: {self.json_file}"
83
+ )
84
+ return self.json_dict["metadata"]
85
+
74
86
  def parse_type(self, type_dict: dict) -> BaseType:
75
87
  type_name: str = type_dict.get("name", None)
76
88
 
@@ -102,6 +114,9 @@ class JsonLoader(dict_loader.DictLoader):
102
114
  f"Dictionary type name has no corresponding type definition: {type_name}"
103
115
  )
104
116
 
117
+ if qualified_type.get("kind") == "alias":
118
+ return self.parse_type(qualified_type.get("underlyingType"))
119
+
105
120
  if qualified_type.get("kind") == "array":
106
121
  return self.construct_array_type(type_name, qualified_type)
107
122
 
@@ -0,0 +1,121 @@
1
+ """
2
+ pkt_json_loader.py:
3
+
4
+ Loads flight dictionary (JSON) and returns Python dictionaries of telemetry packets
5
+
6
+ @author jawest
7
+ """
8
+
9
+ from fprime_gds.common.templates.pkt_template import PktTemplate
10
+ from fprime_gds.common.loaders.json_loader import JsonLoader
11
+ from fprime_gds.common.data_types.exceptions import GdsDictionaryParsingException
12
+
13
+
14
+ class PktJsonLoader(JsonLoader):
15
+ """Class to load python based telemetry packet dictionaries"""
16
+
17
+ PACKETS_FIELD = "telemetryPacketSets"
18
+
19
+ SET_NAME = "name"
20
+ MEMBERS = "members"
21
+
22
+ def get_id_dict(self, path, packet_set_name: str, ch_name_dict: dict):
23
+ if path in self.saved_dicts and packet_set_name in self.saved_dicts[path]:
24
+ (id_dict, name_dict) = self.saved_dicts[path][packet_set_name]
25
+ else:
26
+ (id_dict, name_dict, self.versions) = self.construct_dicts(packet_set_name, ch_name_dict)
27
+ if path not in self.saved_dicts:
28
+ self.saved_dicts[path] = dict()
29
+ self.saved_dicts[path].update({packet_set_name: (id_dict, name_dict)})
30
+
31
+ return id_dict
32
+
33
+ def get_name_dict(self, path, packet_set_name: str, ch_name_dict: dict):
34
+ if path in self.saved_dicts and packet_set_name in self.saved_dicts[path]:
35
+ (id_dict, name_dict) = self.saved_dicts[path][packet_set_name]
36
+ else:
37
+ (id_dict, name_dict, self.versions) = self.construct_dicts(packet_set_name, ch_name_dict)
38
+ if path not in self.saved_dicts:
39
+ self.saved_dicts[path] = dict()
40
+ self.saved_dicts[path].update({packet_set_name: (id_dict, name_dict)})
41
+
42
+ return name_dict
43
+
44
+
45
+ def construct_dicts(self, packet_set_name: str, ch_name_dict: dict):
46
+ """
47
+ Constructs and returns python dictionaries keyed on id and name
48
+
49
+ This function should not be called directly, instead, use
50
+ get_id_dict(path) and get_name_dict(path)
51
+
52
+ Args:
53
+ ch_name_dict (dict()): Channel dictionary with names as keys and
54
+ ChTemplate objects as values.
55
+
56
+ Returns:
57
+ A tuple with two packet dictionaries (type==dict()):
58
+ (id_dict, name_dict) and the dictionary version. The keys of the packet dictionaries should
59
+ be the packets' id and name fields respectively and the values should be PktTemplate objects.
60
+ """
61
+ id_dict = {}
62
+ name_dict = {}
63
+
64
+ if self.PACKETS_FIELD not in self.json_dict:
65
+ raise GdsDictionaryParsingException(
66
+ f"Ground Dictionary missing '{self.PACKETS_FIELD}' field: {str(self.json_file)}"
67
+ )
68
+
69
+ for packet_dict in self.json_dict[self.PACKETS_FIELD]:
70
+ try:
71
+ if packet_set_name == packet_dict[self.SET_NAME]:
72
+ for packet_group_dict in packet_dict.get(self.MEMBERS, []):
73
+ packet_temp = self.construct_template_from_dict(packet_group_dict, ch_name_dict)
74
+ id_dict[packet_temp.get_id()] = packet_temp
75
+ name_dict[packet_temp.get_name()] = packet_temp
76
+
77
+ return (
78
+ dict(sorted(id_dict.items())),
79
+ dict(sorted(name_dict.items())),
80
+ self.get_versions(),
81
+ )
82
+
83
+ except KeyError as e:
84
+ raise GdsDictionaryParsingException(
85
+ f"{str(e)} key missing from telemetry packet dictionary entry: {str(packet_dict)}"
86
+ )
87
+
88
+ raise GdsDictionaryParsingException(
89
+ f"Ground Dictionary does not contain packet set '{packet_set_name}'"
90
+ )
91
+
92
+ def construct_template_from_dict(self, packet_group_dict: dict, ch_name_dict: dict):
93
+ """
94
+ Args:
95
+ packet_group_dict (dict()): Packet group dictionary with group id, name, and members
96
+ ch_name_dict (dict()): Channel dictionary with names as keys and ChTemplate objects as values.
97
+ Returns:
98
+ A a PktTemplate object containing the packet group id, group name, and list of ChTemplate
99
+ objects that represent each member in the packet.
100
+ """
101
+ try:
102
+ ch_list = []
103
+ group_name = packet_group_dict["name"]
104
+ group_id = packet_group_dict["id"]
105
+ group_members = packet_group_dict["members"]
106
+
107
+ for ch_name in group_members:
108
+ ch_template = ch_name_dict[ch_name]
109
+ ch_list.append(ch_template)
110
+
111
+ except KeyError as e:
112
+ raise GdsDictionaryParsingException(
113
+ f"{str(e)} key missing from telemetry packet member or member is not a channel in the dictionary: {str(group_name)}"
114
+ )
115
+
116
+ return PktTemplate(
117
+ group_id,
118
+ group_name,
119
+ ch_list
120
+ )
121
+
@@ -0,0 +1,85 @@
1
+ """
2
+ prm_json_loader.py:
3
+
4
+ Loads flight dictionary (JSON) and returns id and mnemonic based Python dictionaries of params
5
+
6
+ @author zimri.leisher
7
+ """
8
+
9
+ from fprime_gds.common.templates.prm_template import PrmTemplate
10
+ from fprime_gds.common.loaders.json_loader import JsonLoader
11
+ from fprime_gds.common.data_types.exceptions import GdsDictionaryParsingException
12
+
13
+
14
+ class PrmJsonLoader(JsonLoader):
15
+ """Class to load parameters from json dictionaries"""
16
+
17
+ PARAMS_FIELD = "parameters"
18
+
19
+ ID = "id"
20
+ NAME = "name"
21
+ TYPE = "type"
22
+ DESC = "annotation"
23
+ DEFAULT = "default"
24
+
25
+
26
+ def construct_dicts(self, _):
27
+ """
28
+ Constructs and returns python dictionaries keyed on id and name
29
+
30
+ Args:
31
+ _: Unused argument (inherited)
32
+ Returns:
33
+ A tuple with two channel dictionaries (python type dict):
34
+ (id_dict, fqn_name_dict). The keys should be the channels' id and
35
+ fully qualified name fields respectively and the values should be PrmTemplate
36
+ objects.
37
+ """
38
+ id_dict = {}
39
+ fqn_name_dict = {}
40
+
41
+ if self.PARAMS_FIELD not in self.json_dict:
42
+ raise GdsDictionaryParsingException(
43
+ f"Ground Dictionary missing '{self.PARAMS_FIELD}' field: {str(self.json_file)}"
44
+ )
45
+
46
+ for prm_dict in self.json_dict[self.PARAMS_FIELD]:
47
+ # Create a channel template object
48
+ prm_temp = self.construct_template_from_dict(prm_dict)
49
+
50
+ id_dict[prm_temp.get_id()] = prm_temp
51
+ fqn_name_dict[prm_temp.get_full_name()] = prm_temp
52
+
53
+ return (
54
+ dict(sorted(id_dict.items())),
55
+ dict(sorted(fqn_name_dict.items())),
56
+ self.get_versions(),
57
+ )
58
+
59
+ def construct_template_from_dict(self, prm_dict: dict) -> PrmTemplate:
60
+ try:
61
+ prm_id = prm_dict[self.ID]
62
+ # The below assignment also raises a ValueError if the name does not contain a '.'
63
+ qualified_component_name, prm_name = prm_dict[self.NAME].rsplit('.', 1)
64
+ if not qualified_component_name or not prm_name:
65
+ raise ValueError()
66
+
67
+ type_obj = self.parse_type(prm_dict[self.TYPE])
68
+ except ValueError as e:
69
+ raise GdsDictionaryParsingException(
70
+ f"Parameter dictionary entry malformed, expected name of the form '<QUAL_COMP_NAME>.<PRM_NAME>' in : {str(prm_dict)}"
71
+ )
72
+ except KeyError as e:
73
+ raise GdsDictionaryParsingException(
74
+ f"{str(e)} key missing from parameter dictionary entry or its associated type in the dictionary: {str(prm_dict)}"
75
+ )
76
+
77
+ prm_default_val = prm_dict.get(self.DEFAULT, None)
78
+
79
+ return PrmTemplate(
80
+ prm_id,
81
+ prm_name,
82
+ qualified_component_name,
83
+ type_obj,
84
+ prm_default_val
85
+ )
@@ -7,25 +7,22 @@ class called "Dictionaries".
7
7
  @author mstarch
8
8
  """
9
9
 
10
- import os
11
10
  from pathlib import Path
12
11
 
13
- import fprime_gds.common.loaders.ch_py_loader
14
- import fprime_gds.common.loaders.ch_xml_loader
15
-
16
- # Py Loaders
17
- import fprime_gds.common.loaders.cmd_py_loader
18
12
 
19
13
  # XML Loaders
14
+ import fprime_gds.common.loaders.ch_xml_loader
20
15
  import fprime_gds.common.loaders.cmd_xml_loader
21
- import fprime_gds.common.loaders.event_py_loader
22
16
  import fprime_gds.common.loaders.event_xml_loader
17
+ import fprime_gds.common.loaders.fw_type_json_loader
18
+ import fprime_gds.common.loaders.pkt_json_loader
23
19
  import fprime_gds.common.loaders.pkt_xml_loader
20
+
21
+ # JSON Loaders
24
22
  import fprime_gds.common.loaders.ch_json_loader
25
23
  import fprime_gds.common.loaders.cmd_json_loader
26
24
  import fprime_gds.common.loaders.event_json_loader
27
25
 
28
-
29
26
  class Dictionaries:
30
27
  """
31
28
  Dictionaries class to encapsulate the many different dictionaries used in the system. This includes the following
@@ -49,9 +46,11 @@ class Dictionaries:
49
46
  self._event_name_dict = None
50
47
  self._channel_name_dict = None
51
48
  self._packet_dict = None
49
+ self._fw_type_name_dict = None
52
50
  self._versions = None
51
+ self._metadata = None
53
52
 
54
- def load_dictionaries(self, dictionary, packet_spec):
53
+ def load_dictionaries(self, dictionary, packet_spec, packet_set_name):
55
54
  """
56
55
  Loads the dictionaries based on the dictionary path supplied. Optional packet_spec is allowed to specify the
57
56
  definitions of packets.
@@ -59,57 +58,44 @@ class Dictionaries:
59
58
  :param dictionary: dictionary path used for loading dictionaries
60
59
  :param packet_spec: specification for packets, or None, for packetized telemetry
61
60
  """
62
- # Loading the dictionaries from a directory. A directory indicates heritage python dicts.
63
- if os.path.isdir(dictionary):
64
- # Events
65
- event_loader = fprime_gds.common.loaders.event_py_loader.EventPyLoader()
66
- self._event_id_dict = event_loader.get_id_dict(
67
- os.path.join(dictionary, "events")
68
- )
69
- self._event_name_dict = event_loader.get_name_dict(
70
- os.path.join(dictionary, "events")
71
- )
72
- # Commands
73
- command_loader = fprime_gds.common.loaders.cmd_py_loader.CmdPyLoader()
74
- self._command_id_dict = command_loader.get_id_dict(
75
- os.path.join(dictionary, "commands")
76
- )
77
- self._command_name_dict = command_loader.get_name_dict(
78
- os.path.join(dictionary, "commands")
79
- )
80
- # Channels
81
- channel_loader = fprime_gds.common.loaders.ch_py_loader.ChPyLoader()
82
- self._channel_id_dict = channel_loader.get_id_dict(
83
- os.path.join(dictionary, "channels")
84
- )
85
- self._channel_name_dict = channel_loader.get_name_dict(
86
- os.path.join(dictionary, "channels")
87
- )
88
- elif Path(dictionary).is_file() and ".json" in Path(dictionary).suffixes:
61
+ if Path(dictionary).is_file() and ".json" in Path(dictionary).suffixes:
89
62
  # Events
90
63
  json_event_loader = (
91
64
  fprime_gds.common.loaders.event_json_loader.EventJsonLoader(dictionary)
92
65
  )
93
66
  self._event_name_dict = json_event_loader.get_name_dict(None)
94
67
  self._event_id_dict = json_event_loader.get_id_dict(None)
95
- self._versions = json_event_loader.get_versions()
96
68
  # Commands
97
69
  json_command_loader = (
98
70
  fprime_gds.common.loaders.cmd_json_loader.CmdJsonLoader(dictionary)
99
71
  )
100
72
  self._command_name_dict = json_command_loader.get_name_dict(None)
101
73
  self._command_id_dict = json_command_loader.get_id_dict(None)
102
- assert (
103
- self._versions == json_command_loader.get_versions()
104
- ), "Version mismatch while loading"
105
74
  # Channels
106
75
  json_channel_loader = fprime_gds.common.loaders.ch_json_loader.ChJsonLoader(
107
76
  dictionary
108
77
  )
109
78
  self._channel_name_dict = json_channel_loader.get_name_dict(None)
110
79
  self._channel_id_dict = json_channel_loader.get_id_dict(None)
80
+ # Fw Types
81
+ fw_types_loader = fprime_gds.common.loaders.fw_type_json_loader.FwTypeJsonLoader(
82
+ dictionary
83
+ )
84
+ self._fw_type_name_dict = fw_types_loader.get_name_dict(None)
85
+ # Metadata
86
+ self._versions = json_event_loader.get_versions()
87
+ self._metadata = json_event_loader.get_metadata().copy()
88
+ self._metadata["dictionary_type"] = "json"
89
+ # Each loaders should agree on metadata and versions
90
+ assert (
91
+ json_command_loader.get_metadata()
92
+ == json_channel_loader.get_metadata()
93
+ == json_event_loader.get_metadata()
94
+ ), "Metadata mismatch while loading"
111
95
  assert (
112
- self._versions == json_channel_loader.get_versions()
96
+ json_command_loader.get_versions()
97
+ == json_channel_loader.get_versions()
98
+ == json_event_loader.get_versions()
113
99
  ), "Version mismatch while loading"
114
100
  # XML dictionaries
115
101
  elif Path(dictionary).is_file():
@@ -132,12 +118,23 @@ class Dictionaries:
132
118
  assert (
133
119
  self._versions == channel_loader.get_versions()
134
120
  ), "Version mismatch while loading"
121
+ # versions are camelCase to match the metadata field of the JSON dictionaries
122
+ self._metadata = {
123
+ "frameworkVersion": self._versions[0],
124
+ "projectVersion": self._versions[1],
125
+ "dictionary_type": "xml",
126
+ }
135
127
  else:
136
128
  msg = f"[ERROR] Dictionary '{dictionary}' does not exist."
137
129
  raise Exception(msg)
138
130
  # Check for packet specification
139
- if packet_spec is not None:
140
- packet_loader = fprime_gds.common.loaders.pkt_xml_loader.PktXmlLoader()
131
+ if self._metadata["dictionary_type"] == "json" and packet_set_name is not None:
132
+ packet_loader = fprime_gds.common.loaders.pkt_json_loader.PktJsonLoader(dictionary)
133
+ self._packet_dict = packet_loader.get_id_dict(
134
+ None, packet_set_name, self._channel_name_dict
135
+ )
136
+ elif packet_spec is not None:
137
+ packet_loader = fprime_gds.common.loaders.pkt_xml_loader.PktXmlLoader(dictionary)
141
138
  self._packet_dict = packet_loader.get_id_dict(
142
139
  packet_spec, self._channel_name_dict
143
140
  )
@@ -173,6 +170,11 @@ class Dictionaries:
173
170
  def channel_name(self):
174
171
  """Channel dictionary by name"""
175
172
  return self._channel_name_dict
173
+
174
+ @property
175
+ def fw_type_name(self):
176
+ """Fw type name dictionary by name"""
177
+ return self._fw_type_name_dict
176
178
 
177
179
  @property
178
180
  def project_version(self):
@@ -184,6 +186,14 @@ class Dictionaries:
184
186
  """Framework version in dictionary"""
185
187
  return self._versions[0]
186
188
 
189
+ @property
190
+ def metadata(self):
191
+ """Dictionary metadata.
192
+
193
+ Note: framework_version and project_version are also available as separate properties
194
+ for legacy reasons. New code should use the metadata property."""
195
+ return self._metadata
196
+
187
197
  @property
188
198
  def packet(self):
189
199
  """Packet dictionary"""
@@ -6,6 +6,7 @@ and decoding into a single component that the be composed into the standard pipe
6
6
 
7
7
  @mstarch
8
8
  """
9
+
9
10
  import fprime_gds.common.decoders.ch_decoder
10
11
  import fprime_gds.common.decoders.event_decoder
11
12
  import fprime_gds.common.decoders.file_decoder
@@ -130,6 +131,24 @@ class EncodingDecoding:
130
131
  """
131
132
  return self.channel_decoder.deregister(consumer)
132
133
 
134
+ def register_file_consumer(self, consumer):
135
+ """
136
+ Registers a consumer with the file decoder.
137
+
138
+ :param consumer: consumer of file packets
139
+ """
140
+ self.file_decoder.register(consumer)
141
+
142
+ def remove_file_consumer(self, consumer):
143
+ """
144
+ Removes a consumer from the file decoder. Will raise an error if the history was not
145
+ previously registered.
146
+
147
+ :param consumer: consumer of channels
148
+ :return: a boolean indicating if the consumer was removed.
149
+ """
150
+ return self.file_decoder.deregister(consumer)
151
+
133
152
  def register_command_consumer(self, consumer):
134
153
  """
135
154
  Registers a history with the standard pipeline.
@@ -6,6 +6,7 @@ to compose in this code.
6
6
 
7
7
  @author mstarch
8
8
  """
9
+
9
10
  from typing import Type
10
11
 
11
12
  from fprime_gds.common.history.history import History
@@ -37,6 +38,9 @@ class Histories:
37
38
  :param coders: coders object to register histories with
38
39
  """
39
40
  self.coders = coders
41
+ # Allow implementation type to disable histories
42
+ if self._implementation_type is None:
43
+ return
40
44
  # Create histories, RAM histories for now
41
45
  self.commands = self._implementation_type()
42
46
  self.events = self._implementation_type()
@@ -8,6 +8,7 @@ below.
8
8
 
9
9
  :author: lestarch
10
10
  """
11
+
11
12
  import datetime
12
13
  import os.path
13
14
  from pathlib import Path
@@ -19,6 +20,7 @@ import fprime_gds.common.data_types.cmd_data
19
20
  import fprime_gds.common.distributor.distributor
20
21
  import fprime_gds.common.logger.data_logger
21
22
  from fprime_gds.common.transport import RoutingTag, ThreadedTCPSocketClient
23
+ from fprime_gds.common.utils.config_manager import ConfigManager
22
24
 
23
25
  # Local imports for the sake of composition
24
26
  from . import dictionaries, encoding, files, histories
@@ -54,7 +56,13 @@ class StandardPipeline:
54
56
  self.__transport_type = ThreadedTCPSocketClient
55
57
 
56
58
  def setup(
57
- self, config, dictionary, file_store, logging_prefix=None, packet_spec=None
59
+ self,
60
+ config: ConfigManager,
61
+ dictionary,
62
+ file_store,
63
+ logging_prefix=None,
64
+ packet_spec=None,
65
+ packet_set_name=None,
58
66
  ):
59
67
  """
60
68
  Setup the standard pipeline for moving data from the middleware layer through the GDS layers using the standard
@@ -84,7 +92,13 @@ class StandardPipeline:
84
92
  self.distributor = fprime_gds.common.distributor.distributor.Distributor(config)
85
93
  self.client_socket = self.__transport_type()
86
94
  # Setup dictionaries encoders and decoders
87
- self.dictionaries.load_dictionaries(self.dictionary_path, packet_spec)
95
+ self.dictionaries.load_dictionaries(
96
+ self.dictionary_path, packet_spec, packet_set_name
97
+ )
98
+ # Update config to use Fw types defined in the JSON dictionary
99
+ if self.dictionaries.fw_type_name:
100
+ for fw_type_name, fw_type in self.dictionaries.fw_type_name.items():
101
+ config.set("types", fw_type_name, fw_type)
88
102
  self.coders.setup_coders(
89
103
  self.dictionaries, self.distributor, self.client_socket, config
90
104
  )
@@ -0,0 +1,81 @@
1
+ """
2
+ @brief Params Template class
3
+
4
+ Instances of this class describe a parameter of a component instance (not
5
+ including a specific value)
6
+
7
+ @date Created January 27, 2025
8
+ @author Zimri Leisher
9
+
10
+ @bug Hopefully none
11
+ """
12
+
13
+ from fprime.common.models.serialize.type_base import BaseType
14
+ from fprime.common.models.serialize.type_exceptions import TypeMismatchException
15
+
16
+ from . import data_template
17
+
18
+
19
+ class PrmTemplate(data_template.DataTemplate):
20
+ """Class for param templates that describe parameters of component instances"""
21
+
22
+ def __init__(
23
+ self,
24
+ prm_id: int,
25
+ prm_name: str,
26
+ comp_name: str,
27
+ prm_type_obj: BaseType,
28
+ prm_default_val,
29
+ ):
30
+ """
31
+ Constructor
32
+
33
+ Args:
34
+ prm_id: the id of the parameter
35
+ prm_name: the name of the parameter
36
+ comp_name: the name of the component instance owning this parameter
37
+ prm_type_obj: the instance of BaseType corresponding to the type of this parameter
38
+ prm_default_val: the default value of this parameter, in raw JSON form
39
+ """
40
+ super().__init__()
41
+ # Make sure correct types are passed
42
+ if not isinstance(prm_id, int):
43
+ raise TypeMismatchException(int, type(prm_id))
44
+
45
+ if not isinstance(prm_name, str):
46
+ raise TypeMismatchException(str, type(prm_name))
47
+
48
+ if not isinstance(comp_name, str):
49
+ raise TypeMismatchException(str, type(comp_name))
50
+
51
+ if not issubclass(prm_type_obj, BaseType):
52
+ raise TypeMismatchException(BaseType, prm_type_obj)
53
+
54
+ # prm_default_val is an arbitrary type, likely a primitive or dict
55
+
56
+ self.prm_id = prm_id
57
+ self.prm_name = prm_name
58
+ self.comp_name = comp_name
59
+ self.prm_type_obj = prm_type_obj
60
+ self.prm_default_val = prm_default_val
61
+
62
+ def get_full_name(self):
63
+ """
64
+ Get the full name of this param
65
+
66
+ Returns:
67
+ The full name (component.param) for this param
68
+ """
69
+ return f"{self.comp_name}.{self.prm_name}"
70
+
71
+ def get_id(self):
72
+ return self.prm_id
73
+
74
+ def get_name(self):
75
+ return self.prm_name
76
+
77
+ def get_comp_name(self):
78
+ return self.comp_name
79
+
80
+ def get_type_obj(self):
81
+ return self.prm_type_obj
@@ -9,6 +9,8 @@ telemetry and dictionaries.
9
9
  """
10
10
  import signal
11
11
  import time
12
+ from pathlib import Path
13
+ import shutil
12
14
 
13
15
  from fprime.common.models.serialize.time_type import TimeType
14
16
 
@@ -63,6 +65,15 @@ class IntegrationTestAPI(DataHandler):
63
65
  # Initialize the logger
64
66
  self.logger = TestLogger(logpath) if logpath is not None else None
65
67
 
68
+ # Copy dictionaries and binary file to output directory
69
+ if logpath is not None:
70
+ base_dir = Path(self.pipeline.dictionary_path).parents[1]
71
+ for subdir in ['bin', 'dict']:
72
+ dir_path = base_dir / subdir
73
+ if dir_path.is_dir():
74
+ shutil.copytree(dir_path, Path(logpath) / subdir,
75
+ dirs_exist_ok=True)
76
+
66
77
  # A predicate used as a filter to choose which events to log automatically
67
78
  self.event_log_filter = self.get_event_pred()
68
79
 
@@ -215,6 +226,37 @@ class IntegrationTestAPI(DataHandler):
215
226
  """
216
227
  self.event_log_filter = self.get_event_pred(event, args, severity, time_pred)
217
228
 
229
+ def get_deployment(self):
230
+ """
231
+ Get the deployment of the target using the loaded FSW dictionary path
232
+ Returns:
233
+ The name of the deployment (str)
234
+ """
235
+ return Path(self.pipeline.dictionary_path).parent.parent.name
236
+
237
+ def wait_for_dataflow(self, count=1, channels=None, start=None, timeout=120):
238
+ """
239
+ Wait for data flow by checking for any telemetry updates within a specified timeout.
240
+
241
+ Args:
242
+ count: either an exact amount (int) or a predicate to specify how many objects to find
243
+ channels: a channel specifier or list of channel specifiers (mnemonic, ID, or predicate). All will count if None
244
+ start: an optional index or predicate to specify the earliest item to search
245
+ timeout: the number of seconds to wait before terminating the search (int)
246
+ """
247
+ if start is None:
248
+ start = self.get_latest_time()
249
+
250
+ history = self.get_telemetry_subhistory()
251
+ result = self.await_telemetry_count(
252
+ count, channels=channels, history=history, start=start, timeout=timeout
253
+ )
254
+ if not result:
255
+ msg = f'Failed to detect any data flow for {timeout} s.'
256
+ self.__log(msg, TestLogger.RED)
257
+ assert False, msg
258
+ self.remove_telemetry_subhistory(history)
259
+
218
260
  ######################################################################################
219
261
  # History Functions
220
262
  ######################################################################################