fprime-gds 3.4.3__py3-none-any.whl → 3.4.4a2__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 (37) hide show
  1. fprime_gds/common/communication/adapters/base.py +30 -58
  2. fprime_gds/common/communication/adapters/ip.py +23 -5
  3. fprime_gds/common/communication/adapters/uart.py +20 -7
  4. fprime_gds/common/communication/checksum.py +1 -3
  5. fprime_gds/common/communication/framing.py +53 -4
  6. fprime_gds/common/data_types/event_data.py +6 -1
  7. fprime_gds/common/data_types/exceptions.py +16 -11
  8. fprime_gds/common/loaders/ch_json_loader.py +107 -0
  9. fprime_gds/common/loaders/ch_xml_loader.py +5 -5
  10. fprime_gds/common/loaders/cmd_json_loader.py +85 -0
  11. fprime_gds/common/loaders/dict_loader.py +1 -1
  12. fprime_gds/common/loaders/event_json_loader.py +108 -0
  13. fprime_gds/common/loaders/event_xml_loader.py +10 -6
  14. fprime_gds/common/loaders/json_loader.py +222 -0
  15. fprime_gds/common/loaders/xml_loader.py +31 -9
  16. fprime_gds/common/pipeline/dictionaries.py +38 -3
  17. fprime_gds/common/tools/seqgen.py +4 -4
  18. fprime_gds/common/utils/string_util.py +57 -65
  19. fprime_gds/common/zmq_transport.py +37 -20
  20. fprime_gds/executables/apps.py +150 -0
  21. fprime_gds/executables/cli.py +239 -103
  22. fprime_gds/executables/comm.py +17 -27
  23. fprime_gds/executables/data_product_writer.py +935 -0
  24. fprime_gds/executables/run_deployment.py +55 -14
  25. fprime_gds/executables/utils.py +24 -12
  26. fprime_gds/flask/sequence.py +1 -1
  27. fprime_gds/flask/static/addons/commanding/command-input.js +3 -2
  28. fprime_gds/plugin/__init__.py +0 -0
  29. fprime_gds/plugin/definitions.py +71 -0
  30. fprime_gds/plugin/system.py +225 -0
  31. {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/METADATA +3 -2
  32. {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/RECORD +37 -28
  33. {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/WHEEL +1 -1
  34. {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/entry_points.txt +2 -3
  35. {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/LICENSE.txt +0 -0
  36. {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/NOTICE.txt +0 -0
  37. {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/top_level.txt +0 -0
@@ -9,6 +9,8 @@ adapter for use with the comm-layer.
9
9
  @author lestarch
10
10
  """
11
11
  import abc
12
+ from typing import Type
13
+ from fprime_gds.plugin.definitions import gds_plugin_implementation, gds_plugin_specification
12
14
 
13
15
 
14
16
  class BaseAdapter(abc.ABC):
@@ -47,71 +49,41 @@ class BaseAdapter(abc.ABC):
47
49
  """
48
50
 
49
51
  @classmethod
50
- @abc.abstractmethod
51
- def get_arguments(cls):
52
- """
53
- Returns a set of arguments consumed by this adapter. This will be consumed by the CLI layer in order to provide
54
- command line arguments to the user. Note: these should be globally unique args, e.g. --ip-address
52
+ @gds_plugin_specification
53
+ def register_communication_plugin(cls) -> Type["BaseAdapter"]:
54
+ """Register a communications adapter
55
55
 
56
- :return: dictionary, keys of tuple of arg flags and value of list of other arguments to argparse's add_argument
57
- """
56
+ Plugin hook for registering a plugin that supplies an adapter to the communications interface (radio, uart, i2c,
57
+ etc). This interface is expected to read and write bytes from a wire and will be provided to the framing system.
58
58
 
59
- @classmethod
60
- @abc.abstractmethod
61
- def check_arguments(cls, args):
62
- """
63
- Code that should check arguments of this adapter. If there is a problem with this code, then a "ValueError"
64
- should be raised describing the problem with these arguments.
59
+ Note: users should return the class, not an instance of the class. Needed arguments for instantiation are
60
+ determined from class methods, solicited via the command line, and provided at construction time to the chosen
61
+ instantiation.
65
62
 
66
- :param args: arguments as dictionary
63
+ Returns:
64
+ BaseAdapter subclass
67
65
  """
66
+ raise NotImplementedError()
67
+
68
+
69
+ class NoneAdapter(BaseAdapter):
70
+ """ None adapter used to turn off the comm script """
68
71
 
69
72
  @classmethod
70
- def get_adapters(cls):
71
- """
72
- Get all known adapters of this base class. These must be imported into the comm-layer to be available to the
73
- system, however; they only need to be imported. Once imported this function will find them and make them
74
- available to the comm-layer in the standard way.
73
+ def get_name(cls):
74
+ """ Get name of the non-adapter """
75
+ return "none"
75
76
 
76
- :return: list of all imported comm adapters.
77
- """
78
- adapter_map = {}
79
- for adapter in cls.__subclasses__():
80
- # Get two versions of names
81
- adapter_name = adapter.__module__
82
- adapter_short = adapter_name.split(".")[-1]
83
- # Check to use long or short name
84
- if adapter_short not in adapter_map:
85
- adapter_name = adapter_short
86
- adapter_map[adapter_name] = adapter
87
- return adapter_map
88
-
89
- @staticmethod
90
- def process_arguments(clazz, args):
91
- """
92
- Process arguments incoming from the command line and construct a dictionary of kwargs to supply to the chosen
93
- adapter at creation time. This will allow the user to choose any imported adapters at runtime.
77
+ def read(self, timeout=0.500):
78
+ """ Raise exception if this is called"""
79
+ raise NotImplementedError()
94
80
 
95
- :param args: arguments to process
96
- :return: dictionary of constructor keyword arguments
97
- """
98
- return {
99
- value["dest"]: getattr(args, value["dest"])
100
- for value in clazz.get_arguments().values()
101
- }
81
+ def write(self, frame):
82
+ """ Raise exception if this is called"""
83
+ raise NotImplementedError()
102
84
 
103
85
  @classmethod
104
- def construct_adapter(cls, adapter_name, args):
105
- """
106
- Constructs a new adapter, from the given adapter name and the given namespace of argument inputs. This is a
107
- wrapper of "get_adapters" and "process_arguments" to help build a new, fully configured, adapter. This is a
108
- factory method.
109
-
110
- :param adapter_name: name of the adapter to build
111
- :param args: namespace of arg value to help build an adapter
112
- :return: newly constructed adapter
113
- """
114
- if adapter_name == "none":
115
- return None
116
- adapter = cls.get_adapters()[adapter_name]
117
- return adapter(**cls.process_arguments(adapter, args))
86
+ @gds_plugin_implementation
87
+ def register_communication_plugin(cls):
88
+ """ Register this as a plugin """
89
+ return cls
@@ -18,6 +18,8 @@ import time
18
18
  import fprime_gds.common.communication.adapters.base
19
19
  import fprime_gds.common.logger
20
20
 
21
+ from fprime_gds.plugin.definitions import gds_plugin_implementation
22
+
21
23
  LOGGER = logging.getLogger("ip_adapter")
22
24
 
23
25
 
@@ -114,7 +116,7 @@ class IpAdapter(fprime_gds.common.communication.adapters.base.BaseAdapter):
114
116
 
115
117
  def write(self, frame):
116
118
  """
117
- Send a given framed bit of data by sending it out the serial interface. It will attempt to reconnect if there is
119
+ Send a given framed bit of data by sending it out the serial interface. It will attempt to reconnect if there
118
120
  was a problem previously. This function will return true on success, or false on error.
119
121
 
120
122
  :param frame: framed data packet to send out
@@ -151,6 +153,11 @@ class IpAdapter(fprime_gds.common.communication.adapters.base.BaseAdapter):
151
153
  self.write(IpAdapter.KEEPALIVE_DATA)
152
154
  time.sleep(interval)
153
155
 
156
+ @classmethod
157
+ def get_name(cls):
158
+ """ Get the name of this adapter """
159
+ return "ip"
160
+
154
161
  @classmethod
155
162
  def get_arguments(cls):
156
163
  """
@@ -163,13 +170,13 @@ class IpAdapter(fprime_gds.common.communication.adapters.base.BaseAdapter):
163
170
  "dest": "address",
164
171
  "type": str,
165
172
  "default": "0.0.0.0",
166
- "help": "Address of the IP adapter server. Default: %(default)s",
173
+ "help": "Address of the IP adapter server.",
167
174
  },
168
175
  ("--ip-port",): {
169
176
  "dest": "port",
170
177
  "type": int,
171
178
  "default": 50000,
172
- "help": "Port of the IP adapter server. Default: %(default)s",
179
+ "help": "Port of the IP adapter server.",
173
180
  },
174
181
  ("--ip-client",): {
175
182
  # dest is "server" since it is handled in BaseAdapter.construct_adapter and passed with the same
@@ -182,14 +189,25 @@ class IpAdapter(fprime_gds.common.communication.adapters.base.BaseAdapter):
182
189
  }
183
190
 
184
191
  @classmethod
185
- def check_arguments(cls, args):
192
+ @gds_plugin_implementation
193
+ def register_communication_plugin(cls):
194
+ """ Register this as a communication plugin """
195
+ return cls
196
+
197
+ @classmethod
198
+ def check_arguments(cls, address, port, server=True):
186
199
  """
187
200
  Code that should check arguments of this adapter. If there is a problem with this code, then a "ValueError"
188
201
  should be raised describing the problem with these arguments.
189
202
 
190
203
  :param args: arguments as dictionary
191
204
  """
192
- check_port(args["address"], args["port"])
205
+ try:
206
+ if server:
207
+ check_port(address, port)
208
+ except OSError as os_error:
209
+ raise ValueError(f"{os_error}")
210
+
193
211
 
194
212
 
195
213
  class IpHandler(abc.ABC):
@@ -15,6 +15,8 @@ from serial.tools import list_ports
15
15
 
16
16
  import fprime_gds.common.communication.adapters.base
17
17
 
18
+ from fprime_gds.plugin.definitions import gds_plugin_implementation
19
+
18
20
  LOGGER = logging.getLogger("serial_adapter")
19
21
 
20
22
 
@@ -151,18 +153,29 @@ class SerialAdapter(fprime_gds.common.communication.adapters.base.BaseAdapter):
151
153
  "dest": "device",
152
154
  "type": str,
153
155
  "default": default,
154
- "help": "UART device representing the FSW. Default: %(default)s",
156
+ "help": "UART device representing the FSW.",
155
157
  },
156
158
  ("--uart-baud",): {
157
159
  "dest": "baud",
158
160
  "type": int,
159
161
  "default": 9600,
160
- "help": "Baud rate of the serial device. Default: %(default)s",
162
+ "help": "Baud rate of the serial device.",
161
163
  },
162
164
  }
163
165
 
164
166
  @classmethod
165
- def check_arguments(cls, args):
167
+ def get_name(cls):
168
+ """ Get name of the adapter """
169
+ return "uart"
170
+
171
+ @classmethod
172
+ @gds_plugin_implementation
173
+ def register_communication_plugin(cls):
174
+ """ Register this as a communication plugin """
175
+ return cls
176
+
177
+ @classmethod
178
+ def check_arguments(cls, device, baud):
166
179
  """
167
180
  Code that should check arguments of this adapter. If there is a problem with this code, then a "ValueError"
168
181
  should be raised describing the problem with these arguments.
@@ -170,16 +183,16 @@ class SerialAdapter(fprime_gds.common.communication.adapters.base.BaseAdapter):
170
183
  :param args: arguments as dictionary
171
184
  """
172
185
  ports = map(lambda info: info.device, list_ports.comports(include_links=True))
173
- if args["device"] not in ports:
174
- msg = f"Serial port '{args['device']}' not valid. Available ports: {ports}"
186
+ if device not in ports:
187
+ msg = f"Serial port '{device}' not valid. Available ports: {ports}"
175
188
  raise ValueError(
176
189
  msg
177
190
  )
178
191
  # Note: baud rate may not *always* work. These are a superset
179
192
  try:
180
- baud = int(args["baud"])
193
+ baud = int(baud)
181
194
  except ValueError:
182
- msg = f"Serial baud rate '{args['baud']}' not integer. Use one of: {SerialAdapter.BAUDS}"
195
+ msg = f"Serial baud rate '{baud}' not integer. Use one of: {SerialAdapter.BAUDS}"
183
196
  raise ValueError(
184
197
  msg
185
198
  )
@@ -11,7 +11,6 @@ def crc_calculation(data: bytes):
11
11
  return zlib.crc32(data) & 0xFFFFFFFF
12
12
 
13
13
 
14
- CHECKSUM_SELECTION = "crc32"
15
14
  CHECKSUM_MAPPING = {
16
15
  "fixed": lambda data: 0xCAFECAFE,
17
16
  "crc32": crc_calculation,
@@ -19,8 +18,7 @@ CHECKSUM_MAPPING = {
19
18
  }
20
19
 
21
20
 
22
- def calculate_checksum(data: bytes):
21
+ def calculate_checksum(data: bytes, selected_checksum: str):
23
22
  """Calculates the checksum of bytes"""
24
- selected_checksum = CHECKSUM_SELECTION
25
23
  hash_fn = CHECKSUM_MAPPING.get(selected_checksum, CHECKSUM_MAPPING.get("default"))
26
24
  return hash_fn(data)
@@ -15,8 +15,10 @@ import abc
15
15
  import copy
16
16
  import struct
17
17
  import sys
18
+ from typing import Type
18
19
 
19
- from .checksum import calculate_checksum
20
+ from .checksum import calculate_checksum, CHECKSUM_MAPPING
21
+ from fprime_gds.plugin.definitions import gds_plugin_implementation, gds_plugin_specification
20
22
 
21
23
 
22
24
  class FramerDeframer(abc.ABC):
@@ -70,6 +72,24 @@ class FramerDeframer(abc.ABC):
70
72
  return packets, data, discarded_aggregate
71
73
  packets.append(packet)
72
74
 
75
+ @classmethod
76
+ @gds_plugin_specification
77
+ def register_framing_plugin(cls) -> Type["FramerDeframer"]:
78
+ """Register a plugin to provide framing capabilities
79
+
80
+ Plugin hook for registering a plugin that supplies a FramerDeframer implementation. Implementors of this hook must
81
+ return a non-abstract subclass of FramerDeframer. This class will be provided as a framing implementation option
82
+ that users may select via command line arguments.
83
+
84
+ Note: users should return the class, not an instance of the class. Needed arguments for instantiation are
85
+ determined from class methods, solicited via the command line, and provided at construction time to the chosen
86
+ instantiation.
87
+
88
+ Returns:
89
+ FramerDeframer subclass
90
+ """
91
+ raise NotImplementedError()
92
+
73
93
 
74
94
  class FpFramerDeframer(FramerDeframer):
75
95
  """
@@ -97,10 +117,11 @@ class FpFramerDeframer(FramerDeframer):
97
117
  HEADER_FORMAT = None
98
118
  START_TOKEN = None
99
119
 
100
- def __init__(self):
120
+ def __init__(self, checksum_type):
101
121
  """Sets constants on construction."""
102
122
  # Setup the constants as soon as possible.
103
123
  FpFramerDeframer.set_constants()
124
+ self.checksum = checksum_type
104
125
 
105
126
  @classmethod
106
127
  def set_constants(cls):
@@ -134,7 +155,7 @@ class FpFramerDeframer(FramerDeframer):
134
155
  FpFramerDeframer.HEADER_FORMAT, FpFramerDeframer.START_TOKEN, len(data)
135
156
  )
136
157
  framed += data
137
- framed += struct.pack(">I", calculate_checksum(framed))
158
+ framed += struct.pack(">I", calculate_checksum(framed, self.checksum))
138
159
  return framed
139
160
 
140
161
  def deframe(self, data, no_copy=False):
@@ -176,7 +197,8 @@ class FpFramerDeframer(FramerDeframer):
176
197
  )
177
198
  # If the checksum is valid, return the packet. Otherwise continue to rotate
178
199
  if check == calculate_checksum(
179
- data[: data_size + FpFramerDeframer.HEADER_SIZE]
200
+ data[: data_size + FpFramerDeframer.HEADER_SIZE],
201
+ self.checksum
180
202
  ):
181
203
  data = data[total_size:]
182
204
  return deframed, data, discarded
@@ -192,6 +214,33 @@ class FpFramerDeframer(FramerDeframer):
192
214
  return None, data, discarded
193
215
  return None, data, discarded
194
216
 
217
+ @classmethod
218
+ def get_name(cls):
219
+ """ Get the name of this plugin """
220
+ return "fprime"
221
+
222
+ @classmethod
223
+ def get_arguments(cls):
224
+ """ Get arguments for the framer/deframer """
225
+ return {("--comm-checksum-type",): {
226
+ "dest": "checksum_type",
227
+ "action": "store",
228
+ "type": str,
229
+ "help": "Setup the checksum algorithm. [default: %(default)s]",
230
+ "choices": [
231
+ item
232
+ for item in CHECKSUM_MAPPING.keys()
233
+ if item != "default"
234
+ ],
235
+ "default": "crc32",
236
+ }}
237
+
238
+ @classmethod
239
+ @gds_plugin_implementation
240
+ def register_framing_plugin(cls):
241
+ """ Register a bad plugin """
242
+ return cls
243
+
195
244
 
196
245
  class TcpServerFramerDeframer(FramerDeframer):
197
246
  """
@@ -44,7 +44,12 @@ class EventData(sys_data.SysData):
44
44
  self.display_text = event_temp.description
45
45
  elif event_temp.format_str == "":
46
46
  args_template = self.template.get_args()
47
- self.display_text = str([{args_template[index][0]:arg.val} for index, arg in enumerate(event_args)])
47
+ self.display_text = str(
48
+ [
49
+ {args_template[index][0]: arg.val}
50
+ for index, arg in enumerate(event_args)
51
+ ]
52
+ )
48
53
  else:
49
54
  self.display_text = format_string_template(
50
55
  event_temp.format_str, tuple([arg.val for arg in event_args])
@@ -2,10 +2,25 @@
2
2
  Created on Jan 9, 2015
3
3
  @author: reder
4
4
  """
5
+
5
6
  # Exception classes for all controllers modules
6
7
 
7
8
 
8
- class GseControllerException(Exception):
9
+ class FprimeGdsException(Exception):
10
+ """
11
+ Base Exception for exceptions that need to be handled at the system-level
12
+ by the GDS
13
+ """
14
+
15
+ pass
16
+
17
+
18
+ class GdsDictionaryParsingException(FprimeGdsException):
19
+ pass
20
+
21
+
22
+ # Note: Gse is the historical name for what is now called the GDS
23
+ class GseControllerException(FprimeGdsException):
9
24
  def __init__(self, val):
10
25
  self.except_msg = val
11
26
  super().__init__(val)
@@ -27,13 +42,3 @@ class GseControllerUndefinedFileException(GseControllerException):
27
42
  class GseControllerParsingException(GseControllerException):
28
43
  def __init__(self, val):
29
44
  super().__init__(f"Parsing error: {str(val)}")
30
-
31
-
32
- class GseControllerMnemonicMismatchException(GseControllerException):
33
- def __init__(self, val1, val2):
34
- super().__init__(f"ID mismatch ({str(val1)}, {str(val2)})!")
35
-
36
-
37
- class GseControllerStatusUpdateException(GseControllerException):
38
- def __init__(self, val):
39
- super().__init__(f"Bad status update mode: {str(val)}!")
@@ -0,0 +1,107 @@
1
+ """
2
+ ch_json_loader.py:
3
+
4
+ Loads flight dictionary (JSON) and returns id and mnemonic based Python dictionaries of channels
5
+
6
+ @author thomas-bc
7
+ """
8
+
9
+ from fprime_gds.common.templates.ch_template import ChTemplate
10
+ from fprime_gds.common.loaders.json_loader import JsonLoader
11
+ from fprime_gds.common.data_types.exceptions import GdsDictionaryParsingException
12
+
13
+
14
+ class ChJsonLoader(JsonLoader):
15
+ """Class to load python based telemetry channel dictionaries"""
16
+
17
+ CHANNELS_FIELD = "telemetryChannels"
18
+
19
+ ID = "id"
20
+ NAME = "name"
21
+ DESC = "annotation"
22
+ TYPE = "type"
23
+ FMT_STR = "format"
24
+ LIMIT_FIELD = "limit"
25
+ LIMIT_LOW = "low"
26
+ LIMIT_HIGH = "high"
27
+ LIMIT_RED = "red"
28
+ LIMIT_ORANGE = "orange"
29
+ LIMIT_YELLOW = "yellow"
30
+
31
+ def construct_dicts(self, _):
32
+ """
33
+ Constructs and returns python dictionaries keyed on id and name
34
+
35
+ Args:
36
+ _: Unused argument (inherited)
37
+ Returns:
38
+ A tuple with two channel dictionaries (python type dict):
39
+ (id_dict, name_dict). The keys should be the channels' id and
40
+ name fields respectively and the values should be ChTemplate
41
+ objects.
42
+ """
43
+ id_dict = {}
44
+ name_dict = {}
45
+
46
+ if self.CHANNELS_FIELD not in self.json_dict:
47
+ raise GdsDictionaryParsingException(
48
+ f"Ground Dictionary missing '{self.CHANNELS_FIELD}' field: {str(self.json_file)}"
49
+ )
50
+
51
+ for ch_dict in self.json_dict[self.CHANNELS_FIELD]:
52
+ # Create a channel template object
53
+ ch_temp = self.construct_template_from_dict(ch_dict)
54
+
55
+ id_dict[ch_temp.get_id()] = ch_temp
56
+ name_dict[ch_temp.get_full_name()] = ch_temp
57
+
58
+ return (
59
+ dict(sorted(id_dict.items())),
60
+ dict(sorted(name_dict.items())),
61
+ self.get_versions(),
62
+ )
63
+
64
+ def construct_template_from_dict(self, channel_dict: dict) -> ChTemplate:
65
+ try:
66
+ ch_id = channel_dict[self.ID]
67
+ # The below assignment also raises a ValueError if the name does not contain a '.'
68
+ component_name, channel_name = channel_dict[self.NAME].split(".")
69
+ if not component_name or not channel_name:
70
+ raise ValueError()
71
+
72
+ type_obj = self.parse_type(channel_dict[self.TYPE])
73
+ except ValueError as e:
74
+ raise GdsDictionaryParsingException(
75
+ f"Channel dictionary entry malformed, expected name of the form '<COMP_NAME>.<CH_NAME>' in : {str(channel_dict)}"
76
+ )
77
+ except KeyError as e:
78
+ raise GdsDictionaryParsingException(
79
+ f"{str(e)} key missing from Channel dictionary entry or its associated type in the dictionary: {str(channel_dict)}"
80
+ )
81
+
82
+ format_str = JsonLoader.preprocess_format_str(channel_dict.get(self.FMT_STR))
83
+
84
+ limit_field = channel_dict.get(self.LIMIT_FIELD)
85
+ limit_low = limit_field.get(self.LIMIT_LOW) if limit_field else None
86
+ limit_high = limit_field.get(self.LIMIT_HIGH) if limit_field else None
87
+ limit_low_yellow = limit_low.get(self.LIMIT_YELLOW) if limit_low else None
88
+ limit_low_red = limit_low.get(self.LIMIT_RED) if limit_low else None
89
+ limit_low_orange = limit_low.get(self.LIMIT_ORANGE) if limit_low else None
90
+ limit_high_yellow = limit_high.get(self.LIMIT_YELLOW) if limit_high else None
91
+ limit_high_red = limit_high.get(self.LIMIT_RED) if limit_high else None
92
+ limit_high_orange = limit_high.get(self.LIMIT_ORANGE) if limit_high else None
93
+
94
+ return ChTemplate(
95
+ ch_id,
96
+ channel_name,
97
+ component_name,
98
+ type_obj,
99
+ ch_fmt_str=format_str,
100
+ ch_desc=channel_dict.get(self.DESC),
101
+ low_red=limit_low_red,
102
+ low_orange=limit_low_orange,
103
+ low_yellow=limit_low_yellow,
104
+ high_yellow=limit_high_yellow,
105
+ high_orange=limit_high_orange,
106
+ high_red=limit_high_red,
107
+ )
@@ -49,15 +49,15 @@ class ChXmlLoader(XmlLoader):
49
49
  respectively and the values are ChTemplate objects
50
50
  """
51
51
  xml_tree = self.get_xml_tree(path)
52
- versions = xml_tree.attrib.get("framework_version", "unknown"), xml_tree.attrib.get("project_version", "unknown")
52
+ versions = xml_tree.attrib.get(
53
+ "framework_version", "unknown"
54
+ ), xml_tree.attrib.get("project_version", "unknown")
53
55
 
54
56
  # Check if xml dict has channels section
55
57
  ch_section = self.get_xml_section(self.CH_SECT, xml_tree)
56
58
  if ch_section is None:
57
59
  msg = f"Xml dict did not have a {self.CH_SECT} section"
58
- raise exceptions.GseControllerParsingException(
59
- msg
60
- )
60
+ raise exceptions.GseControllerParsingException(msg)
61
61
 
62
62
  id_dict = {}
63
63
  name_dict = {}
@@ -84,7 +84,7 @@ class ChXmlLoader(XmlLoader):
84
84
  ch_desc = ch_dict[self.DESC_TAG]
85
85
 
86
86
  if self.FMT_STR_TAG in ch_dict:
87
- ch_fmt_str = ch_dict[self.FMT_STR_TAG]
87
+ ch_fmt_str = XmlLoader.preprocess_format_str(ch_dict[self.FMT_STR_TAG])
88
88
 
89
89
  # TODO we need to convert these into numbers, is this the best
90
90
  # way to do it?
@@ -0,0 +1,85 @@
1
+ """
2
+ cmd_json_loader.py:
3
+
4
+ Loads flight dictionary (JSON) and returns id and mnemonic based Python dictionaries of commands
5
+
6
+ @author thomas-bc
7
+ """
8
+
9
+ from fprime_gds.common.templates.cmd_template import CmdTemplate
10
+ from fprime_gds.common.loaders.json_loader import JsonLoader
11
+ from fprime_gds.common.data_types.exceptions import GdsDictionaryParsingException
12
+
13
+
14
+ class CmdJsonLoader(JsonLoader):
15
+ """Class to load json based command dictionaries"""
16
+
17
+ COMMANDS_FIELD = "commands"
18
+
19
+ NAME = "name"
20
+ OPCODE = "opcode"
21
+ DESC = "annotation"
22
+ PARAMETERS = "formalParams"
23
+
24
+ def construct_dicts(self, _):
25
+ """
26
+ Constructs and returns python dictionaries keyed on id and name
27
+
28
+ Args:
29
+ _: Unused argument (inherited)
30
+ Returns:
31
+ A tuple with two command dictionaries (python type dict):
32
+ (id_dict, name_dict). The keys are the events' id and name fields
33
+ respectively and the values are CmdTemplate objects
34
+ """
35
+ id_dict = {}
36
+ name_dict = {}
37
+
38
+ if self.COMMANDS_FIELD not in self.json_dict:
39
+ raise GdsDictionaryParsingException(
40
+ f"Ground Dictionary missing '{self.COMMANDS_FIELD}' field: {str(self.json_file)}"
41
+ )
42
+
43
+ for cmd_dict in self.json_dict[self.COMMANDS_FIELD]:
44
+ cmd_temp = self.construct_template_from_dict(cmd_dict)
45
+
46
+ id_dict[cmd_temp.get_id()] = cmd_temp
47
+ name_dict[cmd_temp.get_full_name()] = cmd_temp
48
+
49
+ return (
50
+ dict(sorted(id_dict.items())),
51
+ dict(sorted(name_dict.items())),
52
+ self.get_versions(),
53
+ )
54
+
55
+ def construct_template_from_dict(self, cmd_dict: dict) -> CmdTemplate:
56
+ try:
57
+ cmd_comp, cmd_mnemonic = cmd_dict[self.NAME].split(".")
58
+ cmd_opcode = cmd_dict[self.OPCODE]
59
+ cmd_desc = cmd_dict.get(self.DESC)
60
+ except ValueError as e:
61
+ raise GdsDictionaryParsingException(
62
+ f"Command dictionary entry malformed, expected name of the form '<COMP_NAME>.<CMD_NAME>' in : {str(cmd_dict)}"
63
+ )
64
+ except KeyError as e:
65
+ raise GdsDictionaryParsingException(
66
+ f"{str(e)} key missing from Command dictionary entry: {str(cmd_dict)}"
67
+ )
68
+ # Parse Arguments
69
+ cmd_args = []
70
+ for param in cmd_dict.get(self.PARAMETERS, []):
71
+ try:
72
+ param_name = param["name"]
73
+ param_type = self.parse_type(param["type"])
74
+ except KeyError as e:
75
+ raise GdsDictionaryParsingException(
76
+ f"{str(e)} key missing from Command parameter or its associated type in the dictionary: {str(param)}"
77
+ )
78
+ cmd_args.append(
79
+ (
80
+ param_name,
81
+ param.get("annotation"),
82
+ param_type,
83
+ )
84
+ )
85
+ return CmdTemplate(cmd_opcode, cmd_mnemonic, cmd_comp, cmd_args, cmd_desc)
@@ -93,7 +93,7 @@ class DictLoader:
93
93
  return name_dict
94
94
 
95
95
  def get_versions(self):
96
- """ Get version tuple """
96
+ """Get version tuple"""
97
97
  return self.versions
98
98
 
99
99
  def construct_dicts(self, path):