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.
- fprime_gds/common/communication/adapters/base.py +30 -58
- fprime_gds/common/communication/adapters/ip.py +23 -5
- fprime_gds/common/communication/adapters/uart.py +20 -7
- fprime_gds/common/communication/checksum.py +1 -3
- fprime_gds/common/communication/framing.py +53 -4
- fprime_gds/common/data_types/event_data.py +6 -1
- fprime_gds/common/data_types/exceptions.py +16 -11
- fprime_gds/common/loaders/ch_json_loader.py +107 -0
- fprime_gds/common/loaders/ch_xml_loader.py +5 -5
- fprime_gds/common/loaders/cmd_json_loader.py +85 -0
- fprime_gds/common/loaders/dict_loader.py +1 -1
- fprime_gds/common/loaders/event_json_loader.py +108 -0
- fprime_gds/common/loaders/event_xml_loader.py +10 -6
- fprime_gds/common/loaders/json_loader.py +222 -0
- fprime_gds/common/loaders/xml_loader.py +31 -9
- fprime_gds/common/pipeline/dictionaries.py +38 -3
- fprime_gds/common/tools/seqgen.py +4 -4
- fprime_gds/common/utils/string_util.py +57 -65
- fprime_gds/common/zmq_transport.py +37 -20
- fprime_gds/executables/apps.py +150 -0
- fprime_gds/executables/cli.py +239 -103
- fprime_gds/executables/comm.py +17 -27
- fprime_gds/executables/data_product_writer.py +935 -0
- fprime_gds/executables/run_deployment.py +55 -14
- fprime_gds/executables/utils.py +24 -12
- fprime_gds/flask/sequence.py +1 -1
- fprime_gds/flask/static/addons/commanding/command-input.js +3 -2
- fprime_gds/plugin/__init__.py +0 -0
- fprime_gds/plugin/definitions.py +71 -0
- fprime_gds/plugin/system.py +225 -0
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/METADATA +3 -2
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/RECORD +37 -28
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/WHEEL +1 -1
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/entry_points.txt +2 -3
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/LICENSE.txt +0 -0
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/NOTICE.txt +0 -0
- {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
|
-
@
|
51
|
-
def
|
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
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
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
|
-
:
|
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
|
71
|
-
"""
|
72
|
-
|
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
|
-
|
77
|
-
"""
|
78
|
-
|
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
|
-
|
96
|
-
|
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
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
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.
|
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.
|
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
|
-
|
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
|
-
|
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.
|
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.
|
162
|
+
"help": "Baud rate of the serial device.",
|
161
163
|
},
|
162
164
|
}
|
163
165
|
|
164
166
|
@classmethod
|
165
|
-
def
|
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
|
174
|
-
msg = f"Serial port '{
|
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(
|
193
|
+
baud = int(baud)
|
181
194
|
except ValueError:
|
182
|
-
msg = f"Serial baud rate '{
|
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(
|
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
|
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(
|
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)
|