rccn-gen 1.1.0__py3-none-any.whl → 1.3.0__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.
rccn_gen/systems.py
CHANGED
@@ -1,14 +1,37 @@
|
|
1
|
-
from yamcs.pymdb import
|
1
|
+
from yamcs.pymdb import *
|
2
2
|
import shutil
|
3
3
|
import difflib
|
4
4
|
import datetime
|
5
5
|
from caseconverter import *
|
6
6
|
from importlib.resources import files
|
7
7
|
from .utils import *
|
8
|
+
from collections.abc import Mapping, Sequence
|
9
|
+
from typing import Union, Any, Literal
|
10
|
+
from yamcs.pymdb.alarms import EnumerationAlarm, ThresholdAlarm
|
11
|
+
from yamcs.pymdb.alarms import EnumerationContextAlarm, ThresholdContextAlarm
|
12
|
+
from yamcs.pymdb.datatypes import Epoch, Member, DataType, DynamicInteger
|
13
|
+
from yamcs.pymdb.encodings import Encoding, TimeEncoding
|
14
|
+
from yamcs.pymdb.calibrators import Calibrator
|
15
|
+
from yamcs.pymdb.parameters import (
|
16
|
+
AbsoluteTimeParameter, AggregateParameter, ArrayParameter,
|
17
|
+
BinaryParameter, BooleanParameter, DataSource,
|
18
|
+
EnumeratedParameter, FloatParameter, IntegerParameter, Parameter,
|
19
|
+
StringParameter
|
20
|
+
)
|
21
|
+
from yamcs.pymdb.commands import Command, CommandLevel, CommandEntry, Argument, TransmissionConstraint
|
8
22
|
|
9
23
|
class Application(Subsystem):
|
10
24
|
"""
|
11
25
|
A PUS application.
|
26
|
+
|
27
|
+
This class represents a Protocol Utilization Standard (PUS) application in the context
|
28
|
+
of space systems. It extends the Subsystem class of pymdb and provides functionality for managing
|
29
|
+
services, generating Rust code for the application, and handling snapshots and diffs of
|
30
|
+
the generated code.
|
31
|
+
|
32
|
+
The Application class is responsible for creating and organizing the directory structure
|
33
|
+
for Rust code generation, managing services and their associated commands and telemetry,
|
34
|
+
and facilitating the generation of Rust code files for the entire application.
|
12
35
|
"""
|
13
36
|
def __init__(
|
14
37
|
self,
|
@@ -16,54 +39,169 @@ class Application(Subsystem):
|
|
16
39
|
name: str,
|
17
40
|
apid: int,
|
18
41
|
vcid: int = 0,
|
19
|
-
|
20
|
-
|
21
|
-
|
42
|
+
export_directory = '.',
|
43
|
+
snapshot_directory = './.rccn_snapshots',
|
44
|
+
diff_directory = './.rccn_diffs',
|
22
45
|
snapshots = True,
|
23
46
|
*args,
|
24
47
|
**kwargs
|
25
48
|
):
|
49
|
+
"""
|
50
|
+
Initialize a new PUS application.
|
51
|
+
|
52
|
+
Parameters:
|
53
|
+
-----------
|
54
|
+
system : System
|
55
|
+
The parent system that this application belongs to.
|
56
|
+
name : str
|
57
|
+
The name of the application.
|
58
|
+
apid : int
|
59
|
+
The Application Process ID (APID) for this application.
|
60
|
+
vcid : int, optional
|
61
|
+
The Virtual Channel ID (VCID) for this application. Default is 0.
|
62
|
+
export_directory : str, optional
|
63
|
+
The directory where generated Rust code will be exported. Default is current directory.
|
64
|
+
snapshot_directory : str, optional
|
65
|
+
The directory where snapshots of generated code will be stored. Default is './.rccn_snapshots'.
|
66
|
+
diff_directory : str, optional
|
67
|
+
The directory where diffs between generated code versions will be stored. Default is './.rccn_diffs'.
|
68
|
+
snapshots : bool, optional
|
69
|
+
Whether to create snapshots of generated code. Default is True.
|
70
|
+
*args, **kwargs
|
71
|
+
Additional arguments and keyword arguments passed to the parent class constructor.
|
72
|
+
"""
|
26
73
|
super().__init__(system=system, name=name, *args, **kwargs)
|
27
74
|
|
28
75
|
self.apid = apid
|
29
76
|
self.vcid = vcid
|
77
|
+
self.export_directory = export_directory
|
30
78
|
system._subsystems_by_name[name] = self
|
31
|
-
self.
|
32
|
-
self.
|
33
|
-
self.
|
34
|
-
self.diff_file_path = diff_file_path
|
79
|
+
self.snapshot_directory = snapshot_directory
|
80
|
+
self.snapshot_generated_file_path = os.path.join(snapshot_directory, 'auto_generated')
|
81
|
+
self.diff_directory = diff_directory
|
35
82
|
self.text_modules_path = files('rccn_gen').joinpath('text_modules')
|
36
|
-
print(self.text_modules_path)
|
37
83
|
self.text_modules_main_path = os.path.join(self.text_modules_path, 'main')
|
38
|
-
print(self.text_modules_main_path)
|
39
84
|
self.snapshots = snapshots
|
40
85
|
self.keep_snapshots = 10
|
41
86
|
|
42
87
|
def add_service(self, service):
|
88
|
+
"""
|
89
|
+
Add a service to this application.
|
90
|
+
|
91
|
+
Parameters:
|
92
|
+
-----------
|
93
|
+
service : Service
|
94
|
+
The service to add to this application.
|
95
|
+
|
96
|
+
Raises:
|
97
|
+
-------
|
98
|
+
TypeError
|
99
|
+
If the provided service is not an instance of Service.
|
100
|
+
"""
|
43
101
|
if not isinstance(service, Service):
|
44
102
|
raise TypeError('Service '+service.name+' is not a RCCNCommand.')
|
45
103
|
service.add_to_application(self)
|
46
|
-
|
104
|
+
|
105
|
+
def create_and_add_service(
|
106
|
+
self,
|
107
|
+
name: str,
|
108
|
+
service_id: int,
|
109
|
+
aliases: Mapping[str, str] | None = None,
|
110
|
+
short_description: str | None = None,
|
111
|
+
long_description: str | None = None,
|
112
|
+
extra: Mapping[str, str] | None = None,
|
113
|
+
*args,
|
114
|
+
**kwargs
|
115
|
+
):
|
116
|
+
"""
|
117
|
+
Create a new service and add it to this application.
|
118
|
+
|
119
|
+
Parameters:
|
120
|
+
-----------
|
121
|
+
name : str
|
122
|
+
The name of the service.
|
123
|
+
service_id : int
|
124
|
+
The service ID.
|
125
|
+
aliases : Mapping[str, str], optional
|
126
|
+
Alternative names for the service, keyed by namespace.
|
127
|
+
short_description : str, optional
|
128
|
+
A short description of the service.
|
129
|
+
long_description : str, optional
|
130
|
+
A longer description of the service.
|
131
|
+
extra : Mapping[str, str], optional
|
132
|
+
Arbitrary information about the service, keyed by name.
|
133
|
+
*args, **kwargs
|
134
|
+
Additional arguments and keyword arguments passed to the Service constructor.
|
135
|
+
|
136
|
+
Raises:
|
137
|
+
-------
|
138
|
+
ValueError
|
139
|
+
If 'system' is provided in kwargs.
|
140
|
+
"""
|
141
|
+
if 'system' not in kwargs:
|
142
|
+
kwargs['system'] = self
|
143
|
+
else:
|
144
|
+
raise ValueError('RCCN-Error: \'create_and_add_service\' function can not be called with a \'system\' argument.')
|
145
|
+
Service(
|
146
|
+
name=name,
|
147
|
+
service_id=service_id,
|
148
|
+
aliases=aliases,
|
149
|
+
short_description=short_description,
|
150
|
+
long_description=long_description,
|
151
|
+
*args,
|
152
|
+
**kwargs
|
153
|
+
)
|
154
|
+
|
47
155
|
def file_paths(self):
|
156
|
+
"""
|
157
|
+
Get the file paths for various files used by this application.
|
158
|
+
|
159
|
+
Returns:
|
160
|
+
--------
|
161
|
+
dict
|
162
|
+
A dictionary mapping file types to their absolute paths.
|
163
|
+
"""
|
48
164
|
paths = {
|
49
|
-
'main': os.path.join(self.
|
50
|
-
'main_generated_snapshot': os.path.join(self.
|
165
|
+
'main': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.name), 'src', 'main.rs'),
|
166
|
+
'main_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', 'rccn_usr_'+snakecase(self.name), 'src', 'main.rs'),
|
51
167
|
'main_user_snapshot': os.path.join(self.user_snapshot_path(), 'rccn_usr_'+snakecase(self.name), 'src', 'main.rs'),
|
52
|
-
'main_diff': os.path.join(self.
|
168
|
+
'main_diff': os.path.join(self.diff_directory, 'rccn_usr_'+snakecase(self.name), 'src', 'main.diff'),
|
53
169
|
'main_template': os.path.join(self.text_modules_main_path, 'main.txt'),
|
54
|
-
'cargo_toml': os.path.join(self.
|
170
|
+
'cargo_toml': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.name), 'Cargo.toml'),
|
55
171
|
'cargo_toml_template': os.path.join(self.text_modules_path, 'cargo_toml', 'cargo.txt'),
|
56
172
|
}
|
57
173
|
return paths
|
58
174
|
|
59
175
|
def user_snapshot_path(self):
|
60
|
-
|
176
|
+
"""
|
177
|
+
Get the path for user snapshots with current timestamp.
|
178
|
+
|
179
|
+
Returns:
|
180
|
+
--------
|
181
|
+
str
|
182
|
+
The path where user snapshots are stored.
|
183
|
+
"""
|
184
|
+
return os.path.join(self.snapshot_directory, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
|
61
185
|
|
62
186
|
def services(self):
|
187
|
+
"""
|
188
|
+
Get all services belonging to this application.
|
189
|
+
|
190
|
+
Returns:
|
191
|
+
--------
|
192
|
+
list[Service]
|
193
|
+
A list of Service objects that are subsystems of this application.
|
194
|
+
"""
|
63
195
|
return [subsystem for subsystem in self.subsystems if isinstance(subsystem, Service)]
|
64
196
|
|
65
197
|
def create_rccn_directories(self):
|
66
|
-
|
198
|
+
"""
|
199
|
+
Create directory structure for Rust code generation.
|
200
|
+
|
201
|
+
Creates the necessary directory structure for the application and its services
|
202
|
+
in the export directory.
|
203
|
+
"""
|
204
|
+
app_src_dir = os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.name), 'src')
|
67
205
|
if not os.path.exists(app_src_dir):
|
68
206
|
os.makedirs(app_src_dir)
|
69
207
|
for service in self.services():
|
@@ -72,6 +210,16 @@ class Application(Subsystem):
|
|
72
210
|
os.makedirs(service_dir)
|
73
211
|
|
74
212
|
def generate_rccn_main_file(self):
|
213
|
+
"""
|
214
|
+
Generate the main.rs file for the application.
|
215
|
+
|
216
|
+
Performs several tasks:
|
217
|
+
1. Creates diff file if both current and snapshot files exist
|
218
|
+
2. Creates snapshot of current main.rs with user changes if snapshots are enabled
|
219
|
+
3. Generates new main.rs file from template
|
220
|
+
4. Creates snapshot of newly generated main.rs if snapshots are enabled
|
221
|
+
5. Applies user changes from diff file if rebase_changes is enabled
|
222
|
+
"""
|
75
223
|
# Create main.diff file
|
76
224
|
if os.path.exists(self.file_paths()['main']) and os.path.exists(self.file_paths()['main_generated_snapshot']):
|
77
225
|
os.makedirs(os.path.dirname(self.file_paths()['main_diff']), exist_ok=True)
|
@@ -93,6 +241,24 @@ class Application(Subsystem):
|
|
93
241
|
os.system('patch '+self.file_paths()['main']+' < '+self.file_paths()['main_diff'])
|
94
242
|
|
95
243
|
def find_and_replace_keywords(self, text):
|
244
|
+
"""
|
245
|
+
Replace template keywords with actual values.
|
246
|
+
|
247
|
+
Parameters:
|
248
|
+
-----------
|
249
|
+
text : str
|
250
|
+
Template text containing keywords to be replaced.
|
251
|
+
|
252
|
+
Returns:
|
253
|
+
--------
|
254
|
+
str
|
255
|
+
Text with all keywords replaced with actual values.
|
256
|
+
|
257
|
+
Raises:
|
258
|
+
-------
|
259
|
+
KeyError
|
260
|
+
If a keyword in the template text is not found in the translation dictionary.
|
261
|
+
"""
|
96
262
|
# Call keyword replacement for all associated services (Later, there needs to be checking to account for user changes to the generated files)
|
97
263
|
for service in self.services():
|
98
264
|
text = service.find_and_replace_keywords(text, self.text_modules_main_path)
|
@@ -111,8 +277,36 @@ class Application(Subsystem):
|
|
111
277
|
text = delete_all_keywords(text)
|
112
278
|
return text
|
113
279
|
|
114
|
-
def generate_rccn_code(self, rebase_changes=True, check=True
|
115
|
-
|
280
|
+
def generate_rccn_code(self, export_directory:str, snapshot_directory='', diff_directory='', rebase_changes=True, check=True):
|
281
|
+
"""
|
282
|
+
Generate Rust code for the application and its services.
|
283
|
+
|
284
|
+
Parameters:
|
285
|
+
-----------
|
286
|
+
export_directory : str
|
287
|
+
Directory where the generated code will be exported.
|
288
|
+
snapshot_directory : str, optional
|
289
|
+
Directory where snapshots of generated code will be stored. Default is a subdirectory of export_directory.
|
290
|
+
diff_directory : str, optional
|
291
|
+
Directory where diffs between generated code versions will be stored. Default is a subdirectory of export_directory.
|
292
|
+
rebase_changes : bool, optional
|
293
|
+
Whether to apply user changes from diff files. Default is True.
|
294
|
+
check : bool, optional
|
295
|
+
Whether to perform checks on user input. Default is True.
|
296
|
+
"""
|
297
|
+
# Update export, snapshot and diff directory for the Application and all Services
|
298
|
+
self.export_directory = export_directory
|
299
|
+
if snapshot_directory == '':
|
300
|
+
snapshot_directory = os.path.join(self.export_directory, '.rccn-snapshots')
|
301
|
+
if diff_directory == '':
|
302
|
+
diff_directory = os.path.join(self.export_directory, '.rccn-diffs')
|
303
|
+
self.snapshot_directory = snapshot_directory
|
304
|
+
self.diff_directory = diff_directory
|
305
|
+
for service in self.services():
|
306
|
+
service.export_directory = self.export_directory
|
307
|
+
service.diff_directory = self.diff_directory
|
308
|
+
service.snapshot_directory = self.snapshot_directory
|
309
|
+
|
116
310
|
if check:
|
117
311
|
self.check_user_input()
|
118
312
|
self.rebase_changes = rebase_changes
|
@@ -122,19 +316,41 @@ class Application(Subsystem):
|
|
122
316
|
if not os.path.exists(self.file_paths()['cargo_toml']):
|
123
317
|
self.generate_cargo_toml_file()
|
124
318
|
for service in self.services():
|
125
|
-
service.
|
319
|
+
service.export_directory = self.export_directory
|
126
320
|
service.generate_rccn_service_file()
|
127
321
|
if not os.path.exists(service.file_paths()['mod']):
|
128
322
|
service.generate_mod_file()
|
129
323
|
service.generate_telemetry_file()
|
130
|
-
service.generate_rccn_command_file(os.path.join(self.
|
324
|
+
service.generate_rccn_command_file(os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.name), 'src'), os.path.join(self.text_modules_path, 'command'))
|
131
325
|
self.delete_old_snapshots()
|
132
326
|
|
133
327
|
def generate_snapshot(self, current_file_reference, snapshot_file_reference):
|
328
|
+
"""
|
329
|
+
Create a snapshot of a file.
|
330
|
+
|
331
|
+
Parameters:
|
332
|
+
-----------
|
333
|
+
current_file_reference : str
|
334
|
+
Reference to the current file in the file_paths dictionary.
|
335
|
+
snapshot_file_reference : str
|
336
|
+
Reference to the snapshot file in the file_paths dictionary.
|
337
|
+
"""
|
134
338
|
os.makedirs(os.path.dirname(self.file_paths()[snapshot_file_reference]), exist_ok=True)
|
135
339
|
shutil.copyfile(self.file_paths()[current_file_reference], self.file_paths()[snapshot_file_reference])
|
136
340
|
|
137
341
|
def generate_diff_file(self, current_file_reference, snapshot_file_reference, diff_file_reference):
|
342
|
+
"""
|
343
|
+
Generate a diff file between current and snapshot files.
|
344
|
+
|
345
|
+
Parameters:
|
346
|
+
-----------
|
347
|
+
current_file_reference : str
|
348
|
+
Reference to the current file in the file_paths dictionary.
|
349
|
+
snapshot_file_reference : str
|
350
|
+
Reference to the snapshot file in the file_paths dictionary.
|
351
|
+
diff_file_reference : str
|
352
|
+
Reference to the diff file in the file_paths dictionary.
|
353
|
+
"""
|
138
354
|
with open(self.file_paths()[current_file_reference], 'r') as current_file:
|
139
355
|
current_text = current_file.readlines()
|
140
356
|
with open(self.file_paths()[snapshot_file_reference], 'r') as snapshot_file:
|
@@ -146,39 +362,53 @@ class Application(Subsystem):
|
|
146
362
|
diff_file.write(diff_text)
|
147
363
|
|
148
364
|
def delete_old_snapshots(self):
|
149
|
-
|
150
|
-
|
365
|
+
"""
|
366
|
+
Delete old user snapshots exceeding the keep_snapshots limit.
|
367
|
+
"""
|
368
|
+
if os.path.exists(os.path.join(self.snapshot_directory, 'user')):
|
369
|
+
user_snapshots_path = os.path.join(self.snapshot_directory, 'user')
|
151
370
|
snapshots = [os.path.join(user_snapshots_path, d) for d in os.listdir(user_snapshots_path) if os.path.isdir(os.path.join(user_snapshots_path, d))]
|
152
371
|
snapshots.sort(key=os.path.getctime)
|
153
372
|
while len(snapshots) > self.keep_snapshots:
|
154
373
|
shutil.rmtree(snapshots.pop(0))
|
155
374
|
|
156
375
|
def check_user_input(self):
|
157
|
-
|
376
|
+
"""
|
377
|
+
Perform checks on user input to ensure consistency and uniqueness.
|
378
|
+
|
379
|
+
Raises:
|
380
|
+
-------
|
381
|
+
ValueError
|
382
|
+
If there are duplicate service names, service IDs, command names, or command subtypes.
|
383
|
+
"""
|
384
|
+
# Check if all services in the application have unique names
|
158
385
|
service_names = [service.name for service in self.services()]
|
159
386
|
if len(service_names) != len(set(service_names)):
|
160
|
-
raise ValueError('
|
387
|
+
raise ValueError('RCCN-Error: App \''+self.name+'\' has multiple services with the same name.')
|
161
388
|
|
162
|
-
# Check if all services have unique service_ids
|
389
|
+
# Check if all services in the application have unique service_ids
|
163
390
|
service_ids = [service.service_id for service in self.services()]
|
164
391
|
if len(service_ids) != len(set(service_ids)):
|
165
|
-
raise ValueError('
|
392
|
+
raise ValueError('RCCN-Error: App \''+self.name+'\' has multiple services with the same ID.')
|
166
393
|
|
167
|
-
# Check if all commands have unique names
|
168
|
-
command_names = []
|
394
|
+
# Check if all commands in each service have unique names
|
169
395
|
for service in self.services():
|
396
|
+
command_names = []
|
170
397
|
command_names += [command.name for command in service.rccn_commands()]
|
171
|
-
|
172
|
-
|
398
|
+
if len(command_names) != len(set(command_names)):
|
399
|
+
raise ValueError('RCCN-Error: Service \''+service.name+'\' has multiple commands with the same name.')
|
173
400
|
|
174
|
-
# Check if all commands have unique subtypes
|
175
|
-
command_subtypes = []
|
401
|
+
# Check if all commands in each service have unique subtypes
|
176
402
|
for service in self.services():
|
403
|
+
command_subtypes = []
|
177
404
|
command_subtypes += [command.assignments['subtype'] for command in service.rccn_commands()]
|
178
|
-
|
179
|
-
|
405
|
+
if len(command_subtypes) != len(set(command_subtypes)):
|
406
|
+
raise ValueError('RCCN-Error: Service \''+service.name+'\' has multiple commands with the same subtype.')
|
180
407
|
|
181
408
|
def generate_cargo_toml_file(self):
|
409
|
+
"""
|
410
|
+
Generate the Cargo.toml file for the application.
|
411
|
+
"""
|
182
412
|
with open(self.file_paths()['cargo_toml_template'], 'r') as file:
|
183
413
|
cargo_toml_template_text = file.read()
|
184
414
|
with open(self.file_paths()['cargo_toml'], 'w') as file:
|
@@ -186,6 +416,17 @@ class Application(Subsystem):
|
|
186
416
|
|
187
417
|
|
188
418
|
class Service(Subsystem):
|
419
|
+
"""
|
420
|
+
A PUS service that belongs to an Application.
|
421
|
+
|
422
|
+
This class represents a Protocol Utilization Standard (PUS) service, which is a collection of
|
423
|
+
related functionality within a space application. It extends the Subsystem class of pymdb and provides
|
424
|
+
functionality for managing commands, telemetry containers, and generating Rust code.
|
425
|
+
|
426
|
+
Each service has a unique service ID within its parent application and is responsible for
|
427
|
+
organizing related commands and telemetry into logical groups. The class facilitates the
|
428
|
+
generation of Rust code files for the service: service.rs, command.rs, and telemetry.rs.
|
429
|
+
"""
|
189
430
|
def __init__(
|
190
431
|
self,
|
191
432
|
name: str,
|
@@ -193,6 +434,19 @@ class Service(Subsystem):
|
|
193
434
|
*args,
|
194
435
|
**kwargs
|
195
436
|
):
|
437
|
+
"""
|
438
|
+
Initialize a new PUS service.
|
439
|
+
|
440
|
+
Parameters:
|
441
|
+
-----------
|
442
|
+
name : str
|
443
|
+
The name of the service.
|
444
|
+
service_id : int
|
445
|
+
The unique service ID for this service within its parent application.
|
446
|
+
*args, **kwargs
|
447
|
+
Additional arguments and keyword arguments passed to the parent class constructor.
|
448
|
+
A 'system' parameter can be provided if the service is being created directly.
|
449
|
+
"""
|
196
450
|
self.init_args = args
|
197
451
|
self.init_kwargs = kwargs
|
198
452
|
self.init_args = args
|
@@ -207,6 +461,17 @@ class Service(Subsystem):
|
|
207
461
|
self.add_to_application(kwargs['system'])
|
208
462
|
|
209
463
|
def add_to_application(self, application):
|
464
|
+
"""
|
465
|
+
Add this service to an Application.
|
466
|
+
|
467
|
+
This method initializes the Service as a Subsystem and sets it as a child
|
468
|
+
of the provided application.
|
469
|
+
|
470
|
+
Parameters:
|
471
|
+
-----------
|
472
|
+
application : Application
|
473
|
+
The parent application that this service will belong to.
|
474
|
+
"""
|
210
475
|
if 'system' in self.init_kwargs and isinstance(self.init_kwargs['system'], Application):
|
211
476
|
super().__init__(
|
212
477
|
name=self.name,
|
@@ -218,47 +483,112 @@ class Service(Subsystem):
|
|
218
483
|
name=self.name,
|
219
484
|
*self.init_args, **self.init_kwargs
|
220
485
|
)
|
221
|
-
self.export_file_path = self.system.export_file_path
|
222
|
-
self.snapshot_file_path = self.system.snapshot_file_path
|
223
486
|
self.snapshots = self.system.snapshots
|
224
|
-
self.diff_file_path = self.system.diff_file_path
|
225
487
|
|
226
488
|
def add_container(self, container):
|
489
|
+
"""
|
490
|
+
Add a container to this service.
|
491
|
+
|
492
|
+
Parameters:
|
493
|
+
-----------
|
494
|
+
container : RCCNContainer
|
495
|
+
The container to add to this service.
|
496
|
+
|
497
|
+
Raises:
|
498
|
+
-------
|
499
|
+
TypeError
|
500
|
+
If the provided container is not an instance of RCCNContainer.
|
501
|
+
"""
|
227
502
|
if not isinstance(container, RCCNContainer):
|
228
503
|
raise TypeError('Container '+container.name+' is not a RCCNContainer.')
|
229
504
|
container.add_to_service(self)
|
230
505
|
|
231
506
|
def add_command(self, command):
|
507
|
+
"""
|
508
|
+
Add a command to this service.
|
509
|
+
|
510
|
+
Parameters:
|
511
|
+
-----------
|
512
|
+
command : RCCNCommand
|
513
|
+
The command to add to this service.
|
514
|
+
|
515
|
+
Raises:
|
516
|
+
-------
|
517
|
+
TypeError
|
518
|
+
If the provided command is not an instance of RCCNCommand.
|
519
|
+
"""
|
232
520
|
if not isinstance(command, RCCNCommand):
|
233
521
|
raise TypeError('Command '+command.name+' is not a RCCNCommand.')
|
234
522
|
command.add_to_service(self)
|
235
523
|
|
236
524
|
def file_paths(self):
|
525
|
+
"""
|
526
|
+
Get the file paths for various files used by this service.
|
527
|
+
|
528
|
+
Returns:
|
529
|
+
--------
|
530
|
+
dict
|
531
|
+
A dictionary mapping file types to their absolute paths, including
|
532
|
+
source files, generated snapshots, user snapshots, diff files, and templates.
|
533
|
+
"""
|
237
534
|
paths = {
|
238
|
-
'service': os.path.join(self.
|
239
|
-
'service_generated_snapshot': os.path.join(self.
|
240
|
-
'service_user_snapshot': os.path.join(self.
|
241
|
-
'service_diff': os.path.join(self.
|
535
|
+
'service': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
|
536
|
+
'service_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
|
537
|
+
'service_user_snapshot': os.path.join(self.snapshot_directory, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
|
538
|
+
'service_diff': os.path.join(self.diff_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.diff'),
|
242
539
|
'service_template': os.path.join(self.text_modules_service_path, 'service.txt'),
|
243
|
-
'command': os.path.join(self.
|
244
|
-
'command_generated_snapshot': os.path.join(self.
|
245
|
-
'command_user_snapshot': os.path.join(self.
|
246
|
-
'command_diff': os.path.join(self.
|
540
|
+
'command': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
|
541
|
+
'command_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
|
542
|
+
'command_user_snapshot': os.path.join(self.snapshot_directory, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
|
543
|
+
'command_diff': os.path.join(self.diff_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.diff'),
|
247
544
|
'command_template': os.path.join(self.text_modules_command_path, 'command.txt'),
|
248
|
-
'mod': os.path.join(self.
|
545
|
+
'mod': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'mod.rs'),
|
249
546
|
'mod_template': os.path.join(self.text_modules_path, 'mod', 'mod.txt'),
|
250
|
-
'telemetry': os.path.join(self.
|
251
|
-
'telemetry_generated_snapshot': os.path.join(self.
|
252
|
-
'telemetry_user_snapshot': os.path.join(self.
|
253
|
-
'telemetry_diff': os.path.join(self.
|
547
|
+
'telemetry': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.rs'),
|
548
|
+
'telemetry_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.rs'),
|
549
|
+
'telemetry_user_snapshot': os.path.join(self.snapshot_directory, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
|
550
|
+
'telemetry_diff': os.path.join(self.diff_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.diff'),
|
254
551
|
'telemetry_template': os.path.join(self.text_modules_telemetry_path, 'telemetry.txt'),
|
255
552
|
}
|
256
553
|
return paths
|
257
554
|
|
258
555
|
def rccn_commands(self):
|
556
|
+
"""
|
557
|
+
Get all RCCNCommand objects belonging to this service, excluding base commands.
|
558
|
+
|
559
|
+
Returns:
|
560
|
+
--------
|
561
|
+
list
|
562
|
+
A list of RCCNCommand objects that belong to this service but are not
|
563
|
+
named 'base'.
|
564
|
+
"""
|
259
565
|
return [command for command in self.commands if isinstance(command, RCCNCommand) and command.name != 'base']
|
260
566
|
|
261
567
|
def find_and_replace_keywords(self, text, text_modules_path):
|
568
|
+
"""
|
569
|
+
Replace template keywords with actual values specific to this service.
|
570
|
+
|
571
|
+
This method processes the template text, replacing service-specific keywords,
|
572
|
+
including module keywords, variable keywords, and command keywords. It delegates
|
573
|
+
command-specific keyword replacement to each command's find_and_replace_keywords method.
|
574
|
+
|
575
|
+
Parameters:
|
576
|
+
-----------
|
577
|
+
text : str
|
578
|
+
The template text containing keywords to be replaced.
|
579
|
+
text_modules_path : str
|
580
|
+
The path to the directory containing text module templates.
|
581
|
+
|
582
|
+
Returns:
|
583
|
+
--------
|
584
|
+
str
|
585
|
+
The processed text with all keywords replaced with their actual values.
|
586
|
+
|
587
|
+
Raises:
|
588
|
+
-------
|
589
|
+
FileExistsError
|
590
|
+
If a referenced text module file does not exist.
|
591
|
+
"""
|
262
592
|
# Find and replace service module keywords
|
263
593
|
service_module_keywords = get_service_module_keywords(text)
|
264
594
|
for service_module_keyword in service_module_keywords:
|
@@ -293,6 +623,15 @@ class Service(Subsystem):
|
|
293
623
|
return text
|
294
624
|
|
295
625
|
def generate_rccn_service_file(self):
|
626
|
+
"""
|
627
|
+
Generate the service.rs file for this service.
|
628
|
+
|
629
|
+
This method creates a diff file (if both current and snapshot files exist),
|
630
|
+
takes a snapshot of user-modified service.rs if snapshots are enabled,
|
631
|
+
generates the new service.rs from the template, takes a snapshot of the
|
632
|
+
newly generated file, and applies user changes from the diff if rebase_changes
|
633
|
+
is enabled.
|
634
|
+
"""
|
296
635
|
# Create service.diff file
|
297
636
|
if os.path.exists(self.file_paths()['service']) and os.path.exists(self.file_paths()['service_generated_snapshot']):
|
298
637
|
os.makedirs(os.path.dirname(self.file_paths()['service_diff']), exist_ok=True)
|
@@ -313,6 +652,23 @@ class Service(Subsystem):
|
|
313
652
|
os.system('patch '+self.file_paths()['service']+' < '+self.file_paths()['service_diff'])
|
314
653
|
|
315
654
|
def generate_rccn_command_file(self, export_file_dir='.', text_modules_path='./text_modules/command'):
|
655
|
+
"""
|
656
|
+
Generate the command.rs file for this service.
|
657
|
+
|
658
|
+
This method creates a diff file (if both current and snapshot files exist),
|
659
|
+
takes a snapshot of user-modified command.rs if snapshots are enabled,
|
660
|
+
generates the new command.rs from the template only if there are commands
|
661
|
+
in the service, takes a snapshot of the newly generated file, and applies
|
662
|
+
user changes from the diff if rebase_changes is enabled.
|
663
|
+
|
664
|
+
Parameters:
|
665
|
+
-----------
|
666
|
+
export_file_dir : str, optional
|
667
|
+
The directory where the command.rs file will be exported. Default is current directory.
|
668
|
+
text_modules_path : str, optional
|
669
|
+
The path to the directory containing command text module templates.
|
670
|
+
Default is './text_modules/command'.
|
671
|
+
"""
|
316
672
|
# Create command.diff file
|
317
673
|
if os.path.exists(self.file_paths()['command']) and os.path.exists(self.file_paths()['command_generated_snapshot']):
|
318
674
|
os.makedirs(os.path.dirname(self.file_paths()['command_diff']), exist_ok=True)
|
@@ -327,8 +683,8 @@ class Service(Subsystem):
|
|
327
683
|
command_file_path = self.file_paths()['command_template']
|
328
684
|
with open(command_file_path, 'r') as file:
|
329
685
|
command_file_text = file.read()
|
330
|
-
|
331
|
-
with open(
|
686
|
+
command_export_directory = os.path.join(export_file_dir, snakecase(self.name), 'command.rs')
|
687
|
+
with open(command_export_directory, 'w') as file:
|
332
688
|
file.write(self.find_and_replace_keywords(command_file_text, text_modules_path))
|
333
689
|
# Create snapshot of command.rs if instructed
|
334
690
|
if self.snapshots:
|
@@ -338,10 +694,32 @@ class Service(Subsystem):
|
|
338
694
|
os.system('patch '+self.file_paths()['command']+' < '+self.file_paths()['command_diff'])
|
339
695
|
|
340
696
|
def generate_snapshot(self, current_file_reference, snapshot_file_reference):
|
697
|
+
"""
|
698
|
+
Create a snapshot of a file.
|
699
|
+
|
700
|
+
Parameters:
|
701
|
+
-----------
|
702
|
+
current_file_reference : str
|
703
|
+
Reference to the current file in the file_paths dictionary.
|
704
|
+
snapshot_file_reference : str
|
705
|
+
Reference to the snapshot file in the file_paths dictionary.
|
706
|
+
"""
|
341
707
|
os.makedirs(os.path.dirname(self.file_paths()[snapshot_file_reference]), exist_ok=True)
|
342
708
|
shutil.copyfile(self.file_paths()[current_file_reference], self.file_paths()[snapshot_file_reference])
|
343
709
|
|
344
710
|
def generate_diff_file(self, current_file_reference, snapshot_file_reference, diff_file_reference):
|
711
|
+
"""
|
712
|
+
Generate a diff file between current and snapshot files.
|
713
|
+
|
714
|
+
Parameters:
|
715
|
+
-----------
|
716
|
+
current_file_reference : str
|
717
|
+
Reference to the current file in the file_paths dictionary.
|
718
|
+
snapshot_file_reference : str
|
719
|
+
Reference to the snapshot file in the file_paths dictionary.
|
720
|
+
diff_file_reference : str
|
721
|
+
Reference to the diff file in the file_paths dictionary.
|
722
|
+
"""
|
345
723
|
with open(self.file_paths()[current_file_reference], 'r') as current_file:
|
346
724
|
current_text = current_file.readlines()
|
347
725
|
with open(self.file_paths()[snapshot_file_reference], 'r') as snapshot_file:
|
@@ -352,12 +730,26 @@ class Service(Subsystem):
|
|
352
730
|
diff_file.write(diff_text)
|
353
731
|
|
354
732
|
def generate_mod_file(self):
|
733
|
+
"""
|
734
|
+
Generate the mod.rs file for this service.
|
735
|
+
|
736
|
+
This file serves as the module definition file for the service in Rust,
|
737
|
+
defining what's exported from the service module.
|
738
|
+
"""
|
355
739
|
with open(self.file_paths()['mod_template'], 'r') as file:
|
356
740
|
mod_template_text = file.read()
|
357
741
|
with open(self.file_paths()['mod'], 'w') as file:
|
358
742
|
file.write(mod_template_text)
|
359
743
|
|
360
744
|
def generate_telemetry_file(self):
|
745
|
+
"""
|
746
|
+
Generate the telemetry.rs file for this service.
|
747
|
+
|
748
|
+
This method creates a diff file (if both current and snapshot files exist),
|
749
|
+
takes a snapshot of user-modified telemetry.rs if snapshots are enabled,
|
750
|
+
generates the new telemetry.rs from the template, takes a snapshot of the newly
|
751
|
+
generated file, and applies user changes from the diff if rebase_changes is enabled.
|
752
|
+
"""
|
361
753
|
# Create telemetry.diff file
|
362
754
|
if os.path.exists(self.file_paths()['telemetry']) and os.path.exists(self.file_paths()['telemetry_generated_snapshot']):
|
363
755
|
os.makedirs(os.path.dirname(self.file_paths()['telemetry_diff']), exist_ok=True)
|
@@ -378,26 +770,212 @@ class Service(Subsystem):
|
|
378
770
|
os.system('patch '+self.file_paths()['telemetry']+' < '+self.file_paths()['telemetry_diff'])
|
379
771
|
|
380
772
|
def generate_rust_telemetry_definition(self):
|
773
|
+
"""
|
774
|
+
Generate Rust code definitions for all telemetry containers in this service.
|
775
|
+
|
776
|
+
Returns:
|
777
|
+
--------
|
778
|
+
str
|
779
|
+
A string containing Rust struct definitions for all telemetry containers.
|
780
|
+
"""
|
381
781
|
telemetry_definition_text = ''
|
382
782
|
for container in self.containers:
|
383
783
|
if not isinstance(container, RCCNContainer):
|
384
784
|
container.__class__ = RCCNContainer
|
385
785
|
telemetry_definition_text += container.generate_rccn_telemetry()
|
386
786
|
return telemetry_definition_text
|
787
|
+
|
788
|
+
def create_and_add_command(
|
789
|
+
self,
|
790
|
+
name: str,
|
791
|
+
*,
|
792
|
+
aliases: Mapping[str, str] | None = None,
|
793
|
+
short_description: str | None = None,
|
794
|
+
long_description: str | None = None,
|
795
|
+
extra: Mapping[str, str] | None = None,
|
796
|
+
abstract: bool = False,
|
797
|
+
base: Command | str | None = None,
|
798
|
+
assignments: Mapping[str, Any] | None = None,
|
799
|
+
arguments: Sequence[Argument] | None = None,
|
800
|
+
entries: Sequence[CommandEntry] | None = None,
|
801
|
+
level: CommandLevel = CommandLevel.NORMAL,
|
802
|
+
warning_message: str | None = None,
|
803
|
+
constraint: (
|
804
|
+
Union[TransmissionConstraint, Sequence[TransmissionConstraint]] | None
|
805
|
+
) = None,
|
806
|
+
):
|
807
|
+
"""
|
808
|
+
Create a new command and add it to this service.
|
809
|
+
|
810
|
+
Parameters:
|
811
|
+
-----------
|
812
|
+
name : str
|
813
|
+
The name of the command.
|
814
|
+
aliases : Mapping[str, str], optional
|
815
|
+
Alternative names for the command, keyed by namespace.
|
816
|
+
short_description : str, optional
|
817
|
+
A short description of the command.
|
818
|
+
long_description : str, optional
|
819
|
+
A longer description of the command.
|
820
|
+
extra : Mapping[str, str], optional
|
821
|
+
Arbitrary information about the command, keyed by name.
|
822
|
+
abstract : bool, optional
|
823
|
+
Whether this command is abstract. Default is False.
|
824
|
+
base : Command | str, optional
|
825
|
+
The base command or reference to a base command.
|
826
|
+
assignments : Mapping[str, Any], optional
|
827
|
+
Command assignments.
|
828
|
+
arguments : Sequence[Argument], optional
|
829
|
+
The arguments for this command.
|
830
|
+
entries : Sequence[CommandEntry], optional
|
831
|
+
Command entries.
|
832
|
+
level : CommandLevel, optional
|
833
|
+
The command level. Default is CommandLevel.NORMAL.
|
834
|
+
warning_message : str, optional
|
835
|
+
Warning message to display when executing this command.
|
836
|
+
constraint : Union[TransmissionConstraint, Sequence[TransmissionConstraint]], optional
|
837
|
+
Transmission constraints for this command.
|
838
|
+
"""
|
839
|
+
Command(
|
840
|
+
name=name,
|
841
|
+
system=self,
|
842
|
+
aliases=aliases,
|
843
|
+
short_description=short_description,
|
844
|
+
long_description=long_description,
|
845
|
+
extra=extra,
|
846
|
+
abstract=abstract,
|
847
|
+
base=base,
|
848
|
+
assignments=assignments,
|
849
|
+
arguments=arguments,
|
850
|
+
entries=entries,
|
851
|
+
level=level,
|
852
|
+
warning_message=warning_message,
|
853
|
+
constraint=constraint
|
854
|
+
)
|
855
|
+
|
856
|
+
def rccn_container(self):
|
857
|
+
"""
|
858
|
+
Get all RCCNContainer objects belonging to this service.
|
859
|
+
|
860
|
+
Returns:
|
861
|
+
--------
|
862
|
+
list
|
863
|
+
A list of RCCNContainer objects that belong to this service.
|
864
|
+
"""
|
865
|
+
return [container for container in self.containers if isinstance(container, RCCNContainer)]
|
387
866
|
|
388
867
|
|
389
868
|
class RCCNCommand(Command):
|
390
|
-
|
391
|
-
|
392
|
-
|
869
|
+
"""
|
870
|
+
A Protocol Utilization Standard (PUS) command that belongs to a Service.
|
871
|
+
|
872
|
+
This class extends the Command class and provides specialized functionality for
|
873
|
+
generating Rust code for commands within a PUS service. RCCNCommand manages command
|
874
|
+
subtype assignment, struct name generation for arguments, and handling of base commands.
|
875
|
+
|
876
|
+
Each RCCNCommand automatically gets assigned to its parent Service's APID (Application
|
877
|
+
Process ID) and a unique subtype within that service. It can generate Rust code for
|
878
|
+
command structs and implements keyword replacement for templates.
|
879
|
+
"""
|
880
|
+
def __init__(
|
881
|
+
self,
|
882
|
+
name: str,
|
883
|
+
*,
|
884
|
+
aliases: Mapping[str, str] | None = None,
|
885
|
+
short_description: str | None = None,
|
886
|
+
long_description: str | None = None,
|
887
|
+
extra: Mapping[str, str] | None = None,
|
888
|
+
abstract: bool = False,
|
889
|
+
base: Command | str | None = None,
|
890
|
+
assignments: Mapping[str, Any] | None = None,
|
891
|
+
arguments: Sequence[Argument] | None = None,
|
892
|
+
entries: Sequence[CommandEntry] | None = None,
|
893
|
+
level: CommandLevel = CommandLevel.NORMAL,
|
894
|
+
warning_message: str | None = None,
|
895
|
+
constraint: (
|
896
|
+
Union[TransmissionConstraint, Sequence[TransmissionConstraint]] | None
|
897
|
+
) = None,
|
898
|
+
**kwargs
|
899
|
+
):
|
900
|
+
"""
|
901
|
+
Initialize a new PUS command.
|
902
|
+
|
903
|
+
Parameters:
|
904
|
+
-----------
|
905
|
+
name : str
|
906
|
+
The name of the command.
|
907
|
+
aliases : Mapping[str, str], optional
|
908
|
+
Alternative names for the command, keyed by namespace.
|
909
|
+
short_description : str, optional
|
910
|
+
A short description of the command.
|
911
|
+
long_description : str, optional
|
912
|
+
A longer description of the command.
|
913
|
+
extra : Mapping[str, str], optional
|
914
|
+
Arbitrary information about the command, keyed by name.
|
915
|
+
abstract : bool, optional
|
916
|
+
Whether this command is abstract. Default is False.
|
917
|
+
base : Command | str, optional
|
918
|
+
The base command or reference to a base command.
|
919
|
+
assignments : Mapping[str, Any], optional
|
920
|
+
Command assignments, including the subtype.
|
921
|
+
arguments : Sequence[Argument], optional
|
922
|
+
The arguments for this command.
|
923
|
+
entries : Sequence[CommandEntry], optional
|
924
|
+
Command entries.
|
925
|
+
level : CommandLevel, optional
|
926
|
+
The command level. Default is CommandLevel.NORMAL.
|
927
|
+
warning_message : str, optional
|
928
|
+
Warning message to display when executing this command.
|
929
|
+
constraint : Union[TransmissionConstraint, Sequence[TransmissionConstraint]], optional
|
930
|
+
Transmission constraints for this command.
|
931
|
+
**kwargs
|
932
|
+
Additional keyword arguments that may include 'system' to specify
|
933
|
+
which service this command belongs to.
|
934
|
+
"""
|
935
|
+
self.init_args = ()
|
936
|
+
self.init_kwargs = {
|
937
|
+
'name': name,
|
938
|
+
'aliases': aliases,
|
939
|
+
'short_description': short_description,
|
940
|
+
'long_description': long_description,
|
941
|
+
'extra': extra,
|
942
|
+
'abstract': abstract,
|
943
|
+
'base': base,
|
944
|
+
'assignments': assignments,
|
945
|
+
'arguments': arguments,
|
946
|
+
'entries': entries,
|
947
|
+
'level': level,
|
948
|
+
'warning_message': warning_message,
|
949
|
+
'constraint': constraint,
|
950
|
+
**kwargs
|
951
|
+
}
|
393
952
|
if 'system' in kwargs and isinstance(kwargs['system'], Service):
|
394
953
|
self.add_to_service(kwargs['system'])
|
395
|
-
elif
|
396
|
-
self.add_to_service(
|
954
|
+
elif base and (isinstance(base, Command) or isinstance(base, RCCNCommand)):
|
955
|
+
self.add_to_service(base.system)
|
397
956
|
|
398
957
|
def add_to_service(self, service):
|
958
|
+
"""
|
959
|
+
Add this command to a Service.
|
960
|
+
|
961
|
+
This method handles the initialization of the command as part of a service,
|
962
|
+
setting up base commands if needed, and assigning a subtype if one isn't
|
963
|
+
explicitly provided.
|
964
|
+
|
965
|
+
Parameters:
|
966
|
+
-----------
|
967
|
+
service : Service
|
968
|
+
The service this command will belong to.
|
969
|
+
|
970
|
+
Notes:
|
971
|
+
------
|
972
|
+
- If no base command is specified and the service has no base command,
|
973
|
+
a standard base command will be created automatically
|
974
|
+
- If no subtype is provided, a unique one will be assigned automatically
|
975
|
+
- The command's APID is automatically set to match the service's application APID
|
976
|
+
"""
|
399
977
|
if not 'base' in self.init_kwargs and not any(command.name == 'base' for command in service.commands):
|
400
|
-
print("RCCN-Information: Command \'"+self.init_kwargs['name']+"\' doesn\'t have a base argument and no base command was found in service \'"+service.name+"\'.\nStandard base command will be
|
978
|
+
print("RCCN-Information: Command \'"+self.init_kwargs['name']+"\' doesn\'t have a base argument and no base command was found in service \'"+service.name+"\'.\nStandard base command will be created with system = \'"+service.name+"\' and type = "+str(service.service_id)+".")
|
401
979
|
self.init_kwargs['base'] = Command(
|
402
980
|
system=service,
|
403
981
|
name='base',
|
@@ -413,10 +991,42 @@ class RCCNCommand(Command):
|
|
413
991
|
else:
|
414
992
|
super().__init__(system=service, *self.init_args, **self.init_kwargs)
|
415
993
|
self.assignments['apid'] = self.system.system.apid
|
994
|
+
if not 'subtype' in self.assignments and self.name is not 'base':
|
995
|
+
used_subtypes = [command.assignments['subtype'] if 'subtype' in command.assignments else None for command in self.system.rccn_commands()]
|
996
|
+
new_subtype = 1
|
997
|
+
while new_subtype in used_subtypes:
|
998
|
+
new_subtype = new_subtype + 1
|
999
|
+
print('RCCN-Information: Command \''+self.name+'\' has no subtype specified. Subtype will be set to '+str(new_subtype)+'.')
|
1000
|
+
self.assignments['subtype'] = new_subtype
|
416
1001
|
self.struct_name = self.name + 'Args'
|
417
1002
|
|
418
1003
|
|
419
1004
|
def find_and_replace_keywords(self, text, text_modules_path):
|
1005
|
+
"""
|
1006
|
+
Replace template keywords with actual command values.
|
1007
|
+
|
1008
|
+
This method processes template text, replacing command-specific keywords with
|
1009
|
+
the actual values from this command. It handles:
|
1010
|
+
1. Command module keywords - references to external template files
|
1011
|
+
2. Command variable keywords - specific properties of this command
|
1012
|
+
|
1013
|
+
Parameters:
|
1014
|
+
-----------
|
1015
|
+
text : str
|
1016
|
+
Template text containing keywords to be replaced.
|
1017
|
+
text_modules_path : str
|
1018
|
+
Path to the directory containing text module templates.
|
1019
|
+
|
1020
|
+
Returns:
|
1021
|
+
--------
|
1022
|
+
str
|
1023
|
+
The processed text with all command keywords replaced with their actual values.
|
1024
|
+
|
1025
|
+
Raises:
|
1026
|
+
-------
|
1027
|
+
FileExistsError
|
1028
|
+
If a referenced command module file does not exist.
|
1029
|
+
"""
|
420
1030
|
# Find and replace command module keywords
|
421
1031
|
command_module_keywords = get_command_module_keywords(text)
|
422
1032
|
for command_module_keyword in command_module_keywords:
|
@@ -445,14 +1055,31 @@ class RCCNCommand(Command):
|
|
445
1055
|
text = replace_with_indentation(text, command_var_keyword, command_var_translation[command_var_keyword]())
|
446
1056
|
return text
|
447
1057
|
|
448
|
-
def check_user_input(self):
|
449
|
-
if self.assignments['subtype'] == None and self.name != 'base':
|
450
|
-
raise ValueError('Command '+self.name+' does not have a subtype assigned to it.')
|
451
|
-
|
452
1058
|
def user_snapshot_path(self):
|
453
|
-
|
1059
|
+
"""
|
1060
|
+
Get the path for user snapshots with current timestamp.
|
1061
|
+
|
1062
|
+
Returns:
|
1063
|
+
--------
|
1064
|
+
str
|
1065
|
+
The path where user snapshots are stored.
|
1066
|
+
"""
|
1067
|
+
return os.path.join(self.snapshot_directory, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
|
454
1068
|
|
455
1069
|
def struct_definition(self):
|
1070
|
+
"""
|
1071
|
+
Generate a Rust struct definition for this command's arguments.
|
1072
|
+
|
1073
|
+
This method creates a BitStruct definition in Rust for the command's arguments,
|
1074
|
+
with appropriate documentation comments. If the command has no arguments,
|
1075
|
+
an empty string is returned.
|
1076
|
+
|
1077
|
+
Returns:
|
1078
|
+
--------
|
1079
|
+
str
|
1080
|
+
A string containing the Rust code for the struct definition, or an empty
|
1081
|
+
string if the command has no arguments.
|
1082
|
+
"""
|
456
1083
|
struct_definition_text = ""
|
457
1084
|
if len(self.arguments) == 0:
|
458
1085
|
return ''
|
@@ -470,22 +1097,126 @@ class RCCNCommand(Command):
|
|
470
1097
|
struct_definition_text += append
|
471
1098
|
return struct_definition_text
|
472
1099
|
|
1100
|
+
|
1101
|
+
|
473
1102
|
class RCCNContainer(Container):
|
474
|
-
|
1103
|
+
"""
|
1104
|
+
A Protocol Utilization Standard (PUS) telemetry container that belongs to a Service.
|
1105
|
+
|
1106
|
+
This class extends the Container class and provides specialized functionality for
|
1107
|
+
generating Rust code for telemetry containers within a PUS service. RCCNContainer
|
1108
|
+
manages container type and subtype assignment, condition expressions, and handles
|
1109
|
+
the generation of Rust struct definitions for telemetry data.
|
1110
|
+
|
1111
|
+
Each RCCNContainer is automatically assigned to its parent Service's service ID (type)
|
1112
|
+
and receives a unique subtype within that service. It supports various parameter types
|
1113
|
+
for telemetry data collection and provides methods to add these parameters to the container.
|
1114
|
+
"""
|
1115
|
+
def __init__(
|
1116
|
+
self,
|
1117
|
+
base="/PUS/pus-tm",
|
1118
|
+
subtype = None,
|
1119
|
+
*,
|
1120
|
+
system: System = None,
|
1121
|
+
name: str = None,
|
1122
|
+
entries: Sequence[ParameterEntry | ContainerEntry] | None = None,
|
1123
|
+
abstract: bool = False,
|
1124
|
+
condition: Expression | None = None,
|
1125
|
+
aliases: Mapping[str, str] | None = None,
|
1126
|
+
short_description: str | None = None,
|
1127
|
+
long_description: str | None = None,
|
1128
|
+
extra: Mapping[str, str] | None = None,
|
1129
|
+
bits: int | None = None,
|
1130
|
+
rate: float | None = None,
|
1131
|
+
hint_partition: bool = False,
|
1132
|
+
):
|
1133
|
+
"""
|
1134
|
+
Initialize a new PUS telemetry container.
|
1135
|
+
|
1136
|
+
Parameters:
|
1137
|
+
-----------
|
1138
|
+
base : str, optional
|
1139
|
+
Base path for the telemetry packet. Default is "/PUS/pus-tm".
|
1140
|
+
subtype : int, optional
|
1141
|
+
Subtype for the container. If not provided, a unique value will be assigned.
|
1142
|
+
system : System, optional
|
1143
|
+
The system (usually a Service) this container belongs to.
|
1144
|
+
name : str
|
1145
|
+
The name of the container. Required.
|
1146
|
+
entries : Sequence[ParameterEntry | ContainerEntry], optional
|
1147
|
+
Parameter or container entries for this container.
|
1148
|
+
abstract : bool, optional
|
1149
|
+
Whether this container is abstract. Default is False.
|
1150
|
+
condition : Expression, optional
|
1151
|
+
Condition expression for when this container should be used.
|
1152
|
+
aliases : Mapping[str, str], optional
|
1153
|
+
Alternative names for the container, keyed by namespace.
|
1154
|
+
short_description : str, optional
|
1155
|
+
A short description of the container.
|
1156
|
+
long_description : str, optional
|
1157
|
+
A longer description of the container.
|
1158
|
+
extra : Mapping[str, str], optional
|
1159
|
+
Arbitrary information about the container, keyed by name.
|
1160
|
+
bits : int, optional
|
1161
|
+
Size in bits.
|
1162
|
+
rate : float, optional
|
1163
|
+
Expected rate of reception in Hz.
|
1164
|
+
hint_partition : bool, optional
|
1165
|
+
Hint to partition this container. Default is False.
|
1166
|
+
|
1167
|
+
Raises:
|
1168
|
+
-------
|
1169
|
+
ValueError
|
1170
|
+
If name is not provided.
|
1171
|
+
"""
|
475
1172
|
self.base = base
|
476
1173
|
self.subtype = subtype
|
477
|
-
self.
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
1174
|
+
self.init_kwargs = {
|
1175
|
+
'system': system,
|
1176
|
+
'name': name,
|
1177
|
+
'entries': entries,
|
1178
|
+
'base': base,
|
1179
|
+
'abstract': abstract,
|
1180
|
+
'condition': condition,
|
1181
|
+
'aliases': aliases,
|
1182
|
+
'short_description': short_description,
|
1183
|
+
'long_description': long_description,
|
1184
|
+
'extra': extra,
|
1185
|
+
'bits': bits,
|
1186
|
+
'rate': rate,
|
1187
|
+
'hint_partition': hint_partition,
|
1188
|
+
}
|
1189
|
+
|
1190
|
+
if name is None:
|
1191
|
+
raise ValueError('RCCN-Error: Container must have a name.')
|
1192
|
+
|
1193
|
+
self.name = name
|
1194
|
+
if system is not None and isinstance(system, Service):
|
1195
|
+
self.add_to_service(system)
|
483
1196
|
|
484
1197
|
def add_to_service(self, service):
|
1198
|
+
"""
|
1199
|
+
Add this container to a Service.
|
1200
|
+
|
1201
|
+
This method handles the initialization of the container as part of a service,
|
1202
|
+
setting up condition expressions for type and subtype, and managing the container's
|
1203
|
+
integration with the service.
|
1204
|
+
|
1205
|
+
The method performs several key operations:
|
1206
|
+
1. Sets the container's type to match the service ID
|
1207
|
+
2. Extracts type/subtype from existing condition expressions if present
|
1208
|
+
3. Creates appropriate condition expressions for the container
|
1209
|
+
4. Assigns a unique subtype if one isn't explicitly provided
|
1210
|
+
|
1211
|
+
Parameters:
|
1212
|
+
-----------
|
1213
|
+
service : Service
|
1214
|
+
The service this container will belong to.
|
1215
|
+
"""
|
485
1216
|
self.type = service.service_id
|
486
1217
|
condition_type = None
|
487
1218
|
condition_subtype = None
|
488
|
-
if 'condition'
|
1219
|
+
if self.init_kwargs['condition'] is not None:
|
489
1220
|
for eq_expression in self.init_kwargs['condition'].expressions:
|
490
1221
|
if eq_expression.ref == self.base+'/type':
|
491
1222
|
condition_type = eq_expression.value
|
@@ -498,20 +1229,43 @@ class RCCNContainer(Container):
|
|
498
1229
|
print('RCCN-Warning: Container '+self.name+' has an ambiguous user-defined subtype. \'subtype\' argument should match the \'condition\' argument.')
|
499
1230
|
elif condition_subtype is not None:
|
500
1231
|
self.subtype = condition_subtype
|
501
|
-
elif self.subtype is not None and
|
1232
|
+
elif self.subtype is not None and self.init_kwargs['condition'] is not None:
|
502
1233
|
self.init_kwargs['condition'] = AndExpression(
|
503
1234
|
EqExpression(self.base+'/type', self.type),
|
504
1235
|
EqExpression(self.base+'/subtype', self.subtype)
|
505
1236
|
)
|
506
1237
|
else:
|
507
|
-
|
1238
|
+
used_subtypes = [container.subtype for container in service.rccn_container()]
|
1239
|
+
new_subtype = 1
|
1240
|
+
while new_subtype in used_subtypes:
|
1241
|
+
new_subtype = new_subtype + 1
|
1242
|
+
self.subtype = new_subtype
|
1243
|
+
self.init_kwargs['condition'] = AndExpression(
|
1244
|
+
EqExpression(self.base+'/type', self.type),
|
1245
|
+
EqExpression(self.base+'/subtype', self.subtype)
|
1246
|
+
)
|
1247
|
+
print('RCCN-Information: Subtype for Container '+self.name+' is not specified through \'subtype\' or \'condition\' arguments. Subtype will be set to '+str(self.subtype)+'.')
|
508
1248
|
|
509
1249
|
if 'system' in self.init_kwargs and isinstance(self.init_kwargs['system'], Service):
|
510
|
-
super().__init__(
|
1250
|
+
super().__init__(**self.init_kwargs)
|
511
1251
|
else:
|
512
|
-
super().__init__(system=service,
|
1252
|
+
super().__init__(system=service, **self.init_kwargs)
|
513
1253
|
|
514
1254
|
def generate_rccn_telemetry(self):
|
1255
|
+
"""
|
1256
|
+
Generate Rust code for this telemetry container.
|
1257
|
+
|
1258
|
+
This method creates a Rust struct definition for the container, including:
|
1259
|
+
1. Documentation comments from the container's short_description
|
1260
|
+
2. ServiceTelemetry and BitStruct derive attributes
|
1261
|
+
3. Subtype attribute if a subtype is defined
|
1262
|
+
4. All parameter entries from the container
|
1263
|
+
|
1264
|
+
Returns:
|
1265
|
+
--------
|
1266
|
+
str
|
1267
|
+
A string containing the complete Rust struct definition for this container.
|
1268
|
+
"""
|
515
1269
|
rccn_telemetry_text = ""
|
516
1270
|
if hasattr(self, 'short_description') and self.short_description is not None:
|
517
1271
|
rccn_telemetry_text += "/// "+str(self.short_description)+"\n"
|
@@ -527,4 +1281,615 @@ class RCCNContainer(Container):
|
|
527
1281
|
rccn_telemetry_text += insert
|
528
1282
|
rccn_telemetry_text += "}\n\n"
|
529
1283
|
rccn_telemetry_text += append
|
530
|
-
return rccn_telemetry_text
|
1284
|
+
return rccn_telemetry_text
|
1285
|
+
|
1286
|
+
def add_integer_parameter_entry(
|
1287
|
+
self,
|
1288
|
+
name: str,
|
1289
|
+
signed: bool = True,
|
1290
|
+
bits: int = 32,
|
1291
|
+
minimum: int | None = None,
|
1292
|
+
maximum: int | None = None,
|
1293
|
+
aliases: Mapping[str, str] | None = None,
|
1294
|
+
data_source: DataSource = DataSource.TELEMETERED,
|
1295
|
+
initial_value: Any = None,
|
1296
|
+
persistent: bool = True,
|
1297
|
+
short_description: str | None = None,
|
1298
|
+
long_description: str | None = None,
|
1299
|
+
extra: Mapping[str, str] | None = None,
|
1300
|
+
units: str | None = None,
|
1301
|
+
encoding: Encoding | None = None,
|
1302
|
+
calibrator: Calibrator | None = None,
|
1303
|
+
alarm: ThresholdAlarm | None = None,
|
1304
|
+
context_alarms: Sequence[ThresholdContextAlarm] | None = None,
|
1305
|
+
):
|
1306
|
+
"""
|
1307
|
+
Add an integer parameter to this container.
|
1308
|
+
|
1309
|
+
This method creates an integer parameter and adds it as an entry to this container.
|
1310
|
+
|
1311
|
+
Parameters:
|
1312
|
+
-----------
|
1313
|
+
name : str
|
1314
|
+
The name of the parameter.
|
1315
|
+
signed : bool, optional
|
1316
|
+
Whether the integer is signed. Default is True.
|
1317
|
+
bits : int, optional
|
1318
|
+
Number of bits. Default is 32.
|
1319
|
+
minimum : int, optional
|
1320
|
+
Minimum valid value.
|
1321
|
+
maximum : int, optional
|
1322
|
+
Maximum valid value.
|
1323
|
+
aliases : Mapping[str, str], optional
|
1324
|
+
Alternative names for the parameter, keyed by namespace.
|
1325
|
+
data_source : DataSource, optional
|
1326
|
+
Source of the parameter value. Default is DataSource.TELEMETERED.
|
1327
|
+
initial_value : Any, optional
|
1328
|
+
Initial value for this parameter.
|
1329
|
+
persistent : bool, optional
|
1330
|
+
Whether this parameter's value should be persisted. Default is True.
|
1331
|
+
short_description : str, optional
|
1332
|
+
A short description of the parameter.
|
1333
|
+
long_description : str, optional
|
1334
|
+
A longer description of the parameter.
|
1335
|
+
extra : Mapping[str, str], optional
|
1336
|
+
Arbitrary information about the parameter, keyed by name.
|
1337
|
+
units : str, optional
|
1338
|
+
Units of this parameter.
|
1339
|
+
encoding : Encoding, optional
|
1340
|
+
Encoding information for this parameter.
|
1341
|
+
calibrator : Calibrator, optional
|
1342
|
+
Calibration information for this parameter.
|
1343
|
+
alarm : ThresholdAlarm, optional
|
1344
|
+
Alarm conditions for this parameter.
|
1345
|
+
context_alarms : Sequence[ThresholdContextAlarm], optional
|
1346
|
+
Context-dependent alarm conditions for this parameter.
|
1347
|
+
"""
|
1348
|
+
parameter = IntegerParameter(
|
1349
|
+
system=self.system,
|
1350
|
+
name=name,
|
1351
|
+
signed=signed,
|
1352
|
+
bits=bits,
|
1353
|
+
minimum=minimum,
|
1354
|
+
maximum=maximum,
|
1355
|
+
aliases=aliases,
|
1356
|
+
data_source=data_source,
|
1357
|
+
initial_value=initial_value,
|
1358
|
+
persistent=persistent,
|
1359
|
+
short_description=short_description,
|
1360
|
+
long_description=long_description,
|
1361
|
+
extra=extra,
|
1362
|
+
units=units,
|
1363
|
+
encoding=encoding,
|
1364
|
+
calibrator=calibrator,
|
1365
|
+
alarm=alarm,
|
1366
|
+
context_alarms=context_alarms
|
1367
|
+
)
|
1368
|
+
self.entries.append(ParameterEntry(parameter=parameter))
|
1369
|
+
|
1370
|
+
def add_enumerated_parameter_entry(
|
1371
|
+
self,
|
1372
|
+
name: str,
|
1373
|
+
choices: Choices,
|
1374
|
+
alarm: EnumerationAlarm | None = None,
|
1375
|
+
context_alarms: Sequence[EnumerationContextAlarm] | None = None,
|
1376
|
+
aliases: Mapping[str, str] | None = None,
|
1377
|
+
data_source: DataSource = DataSource.TELEMETERED,
|
1378
|
+
initial_value: Any = None,
|
1379
|
+
persistent: bool = True,
|
1380
|
+
short_description: str | None = None,
|
1381
|
+
long_description: str | None = None,
|
1382
|
+
extra: Mapping[str, str] | None = None,
|
1383
|
+
units: str | None = None,
|
1384
|
+
encoding: Encoding | None = None,
|
1385
|
+
):
|
1386
|
+
"""
|
1387
|
+
Add an enumerated parameter to this container.
|
1388
|
+
|
1389
|
+
This method creates an enumerated parameter with predefined choices and adds it
|
1390
|
+
as an entry to this container.
|
1391
|
+
|
1392
|
+
Parameters:
|
1393
|
+
-----------
|
1394
|
+
name : str
|
1395
|
+
The name of the parameter.
|
1396
|
+
choices : Choices
|
1397
|
+
The enumeration choices for this parameter.
|
1398
|
+
alarm : EnumerationAlarm, optional
|
1399
|
+
Alarm conditions for this parameter.
|
1400
|
+
context_alarms : Sequence[EnumerationContextAlarm], optional
|
1401
|
+
Context-dependent alarm conditions for this parameter.
|
1402
|
+
aliases : Mapping[str, str], optional
|
1403
|
+
Alternative names for the parameter, keyed by namespace.
|
1404
|
+
data_source : DataSource, optional
|
1405
|
+
Source of the parameter value. Default is DataSource.TELEMETERED.
|
1406
|
+
initial_value : Any, optional
|
1407
|
+
Initial value for this parameter.
|
1408
|
+
persistent : bool, optional
|
1409
|
+
Whether this parameter's value should be persisted. Default is True.
|
1410
|
+
short_description : str, optional
|
1411
|
+
A short description of the parameter.
|
1412
|
+
long_description : str, optional
|
1413
|
+
A longer description of the parameter.
|
1414
|
+
extra : Mapping[str, str], optional
|
1415
|
+
Arbitrary information about the parameter, keyed by name.
|
1416
|
+
units : str, optional
|
1417
|
+
Units of this parameter.
|
1418
|
+
encoding : Encoding, optional
|
1419
|
+
Encoding information for this parameter.
|
1420
|
+
"""
|
1421
|
+
parameter = EnumeratedParameter(
|
1422
|
+
system = self.system,
|
1423
|
+
name=name,
|
1424
|
+
choices=choices,
|
1425
|
+
alarm=alarm,
|
1426
|
+
context_alarms=context_alarms,
|
1427
|
+
aliases=aliases,
|
1428
|
+
data_source=data_source,
|
1429
|
+
initial_value=initial_value,
|
1430
|
+
persistent=persistent,
|
1431
|
+
short_description=short_description,
|
1432
|
+
long_description=long_description,
|
1433
|
+
extra=extra,
|
1434
|
+
units=units,
|
1435
|
+
encoding=encoding
|
1436
|
+
)
|
1437
|
+
self.entries.append(ParameterEntry(parameter=parameter))
|
1438
|
+
|
1439
|
+
def add_boolean_parameter_entry(
|
1440
|
+
self,
|
1441
|
+
name: str,
|
1442
|
+
zero_string_value: str = "False",
|
1443
|
+
one_string_value: str = "True",
|
1444
|
+
aliases: Mapping[str, str] | None = None,
|
1445
|
+
data_source: DataSource = DataSource.TELEMETERED,
|
1446
|
+
initial_value: Any = None,
|
1447
|
+
persistent: bool = True,
|
1448
|
+
short_description: str | None = None,
|
1449
|
+
long_description: str | None = None,
|
1450
|
+
extra: Mapping[str, str] | None = None,
|
1451
|
+
units: str | None = None,
|
1452
|
+
encoding: Encoding | None = None,
|
1453
|
+
):
|
1454
|
+
"""
|
1455
|
+
Add a boolean parameter to this container.
|
1456
|
+
|
1457
|
+
This method creates a boolean parameter and adds it as an entry to this container.
|
1458
|
+
|
1459
|
+
Parameters:
|
1460
|
+
-----------
|
1461
|
+
name : str
|
1462
|
+
The name of the parameter.
|
1463
|
+
zero_string_value : str, optional
|
1464
|
+
String representation of the boolean value 'false'. Default is "False".
|
1465
|
+
one_string_value : str, optional
|
1466
|
+
String representation of the boolean value 'true'. Default is "True".
|
1467
|
+
aliases : Mapping[str, str], optional
|
1468
|
+
Alternative names for the parameter, keyed by namespace.
|
1469
|
+
data_source : DataSource, optional
|
1470
|
+
Source of the parameter value. Default is DataSource.TELEMETERED.
|
1471
|
+
initial_value : Any, optional
|
1472
|
+
Initial value for this parameter.
|
1473
|
+
persistent : bool, optional
|
1474
|
+
Whether this parameter's value should be persisted. Default is True.
|
1475
|
+
short_description : str, optional
|
1476
|
+
A short description of the parameter.
|
1477
|
+
long_description : str, optional
|
1478
|
+
A longer description of the parameter.
|
1479
|
+
extra : Mapping[str, str], optional
|
1480
|
+
Arbitrary information about the parameter, keyed by name.
|
1481
|
+
units : str, optional
|
1482
|
+
Units of this parameter.
|
1483
|
+
encoding : Encoding, optional
|
1484
|
+
Encoding information for this parameter.
|
1485
|
+
"""
|
1486
|
+
parameter = BooleanParameter(
|
1487
|
+
system=self.system,
|
1488
|
+
name=name,
|
1489
|
+
zero_string_value=zero_string_value,
|
1490
|
+
one_string_value=one_string_value,
|
1491
|
+
aliases=aliases,
|
1492
|
+
data_source=data_source,
|
1493
|
+
initial_value=initial_value,
|
1494
|
+
persistent=persistent,
|
1495
|
+
short_description=short_description,
|
1496
|
+
long_description=long_description,
|
1497
|
+
extra=extra,
|
1498
|
+
units=units,
|
1499
|
+
encoding=encoding
|
1500
|
+
)
|
1501
|
+
self.entries.append(ParameterEntry(parameter=parameter))
|
1502
|
+
|
1503
|
+
def add_float_parameter_entry(
|
1504
|
+
self,
|
1505
|
+
name: str,
|
1506
|
+
bits: Literal[32, 64] = 32,
|
1507
|
+
minimum: float | None = None,
|
1508
|
+
minimum_inclusive: bool = True,
|
1509
|
+
maximum: float | None = None,
|
1510
|
+
maximum_inclusive: bool = True,
|
1511
|
+
aliases: Mapping[str, str] | None = None,
|
1512
|
+
data_source: DataSource = DataSource.TELEMETERED,
|
1513
|
+
initial_value: Any = None,
|
1514
|
+
persistent: bool = True,
|
1515
|
+
short_description: str | None = None,
|
1516
|
+
long_description: str | None = None,
|
1517
|
+
extra: Mapping[str, str] | None = None,
|
1518
|
+
units: str | None = None,
|
1519
|
+
encoding: Encoding | None = None,
|
1520
|
+
calibrator: Calibrator | None = None,
|
1521
|
+
alarm: ThresholdAlarm | None = None,
|
1522
|
+
context_alarms: Sequence[ThresholdContextAlarm] | None = None,
|
1523
|
+
):
|
1524
|
+
"""
|
1525
|
+
Add a floating-point parameter to this container.
|
1526
|
+
|
1527
|
+
This method creates a float parameter and adds it as an entry to this container.
|
1528
|
+
|
1529
|
+
Parameters:
|
1530
|
+
-----------
|
1531
|
+
name : str
|
1532
|
+
The name of the parameter.
|
1533
|
+
bits : Literal[32, 64], optional
|
1534
|
+
Number of bits, either 32 (float) or 64 (double). Default is 32.
|
1535
|
+
minimum : float, optional
|
1536
|
+
Minimum valid value.
|
1537
|
+
minimum_inclusive : bool, optional
|
1538
|
+
Whether the minimum value is inclusive. Default is True.
|
1539
|
+
maximum : float, optional
|
1540
|
+
Maximum valid value.
|
1541
|
+
maximum_inclusive : bool, optional
|
1542
|
+
Whether the maximum value is inclusive. Default is True.
|
1543
|
+
aliases : Mapping[str, str], optional
|
1544
|
+
Alternative names for the parameter, keyed by namespace.
|
1545
|
+
data_source : DataSource, optional
|
1546
|
+
Source of the parameter value. Default is DataSource.TELEMETERED.
|
1547
|
+
initial_value : Any, optional
|
1548
|
+
Initial value for this parameter.
|
1549
|
+
persistent : bool, optional
|
1550
|
+
Whether this parameter's value should be persisted. Default is True.
|
1551
|
+
short_description : str, optional
|
1552
|
+
A short description of the parameter.
|
1553
|
+
long_description : str, optional
|
1554
|
+
A longer description of the parameter.
|
1555
|
+
extra : Mapping[str, str], optional
|
1556
|
+
Arbitrary information about the parameter, keyed by name.
|
1557
|
+
units : str, optional
|
1558
|
+
Units of this parameter.
|
1559
|
+
encoding : Encoding, optional
|
1560
|
+
Encoding information for this parameter.
|
1561
|
+
calibrator : Calibrator, optional
|
1562
|
+
Calibration information for this parameter.
|
1563
|
+
alarm : ThresholdAlarm, optional
|
1564
|
+
Alarm conditions for this parameter.
|
1565
|
+
context_alarms : Sequence[ThresholdContextAlarm], optional
|
1566
|
+
Context-dependent alarm conditions for this parameter.
|
1567
|
+
"""
|
1568
|
+
parameter = FloatParameter(
|
1569
|
+
system=self.system,
|
1570
|
+
name=name,
|
1571
|
+
bits=bits,
|
1572
|
+
minimum=minimum,
|
1573
|
+
minimum_inclusive=minimum_inclusive,
|
1574
|
+
maximum=maximum,
|
1575
|
+
maximum_inclusive=maximum_inclusive,
|
1576
|
+
aliases=aliases,
|
1577
|
+
data_source=data_source,
|
1578
|
+
initial_value=initial_value,
|
1579
|
+
persistent=persistent,
|
1580
|
+
short_description=short_description,
|
1581
|
+
long_description=long_description,
|
1582
|
+
extra=extra,
|
1583
|
+
units=units,
|
1584
|
+
encoding=encoding,
|
1585
|
+
calibrator=calibrator,
|
1586
|
+
alarm=alarm,
|
1587
|
+
context_alarms=context_alarms
|
1588
|
+
)
|
1589
|
+
self.entries.append(ParameterEntry(parameter=parameter))
|
1590
|
+
|
1591
|
+
def add_string_parameter_entry(
|
1592
|
+
self,
|
1593
|
+
name: str,
|
1594
|
+
min_length: int | None = None,
|
1595
|
+
max_length: int | None = None,
|
1596
|
+
aliases: Mapping[str, str] | None = None,
|
1597
|
+
data_source: DataSource = DataSource.TELEMETERED,
|
1598
|
+
initial_value: Any = None,
|
1599
|
+
persistent: bool = True,
|
1600
|
+
short_description: str | None = None,
|
1601
|
+
long_description: str | None = None,
|
1602
|
+
extra: Mapping[str, str] | None = None,
|
1603
|
+
units: str | None = None,
|
1604
|
+
encoding: Encoding | None = None,
|
1605
|
+
):
|
1606
|
+
"""
|
1607
|
+
Add a string parameter to this container.
|
1608
|
+
|
1609
|
+
This method creates a string parameter and adds it as an entry to this container.
|
1610
|
+
|
1611
|
+
Parameters:
|
1612
|
+
-----------
|
1613
|
+
name : str
|
1614
|
+
The name of the parameter.
|
1615
|
+
min_length : int, optional
|
1616
|
+
Minimum valid length of the string.
|
1617
|
+
max_length : int, optional
|
1618
|
+
Maximum valid length of the string.
|
1619
|
+
aliases : Mapping[str, str], optional
|
1620
|
+
Alternative names for the parameter, keyed by namespace.
|
1621
|
+
data_source : DataSource, optional
|
1622
|
+
Source of the parameter value. Default is DataSource.TELEMETERED.
|
1623
|
+
initial_value : Any, optional
|
1624
|
+
Initial value for this parameter.
|
1625
|
+
persistent : bool, optional
|
1626
|
+
Whether this parameter's value should be persisted. Default is True.
|
1627
|
+
short_description : str, optional
|
1628
|
+
A short description of the parameter.
|
1629
|
+
long_description : str, optional
|
1630
|
+
A longer description of the parameter.
|
1631
|
+
extra : Mapping[str, str], optional
|
1632
|
+
Arbitrary information about the parameter, keyed by name.
|
1633
|
+
units : str, optional
|
1634
|
+
Units of this parameter.
|
1635
|
+
encoding : Encoding, optional
|
1636
|
+
Encoding information for this parameter.
|
1637
|
+
"""
|
1638
|
+
parameter = StringParameter(
|
1639
|
+
system=self.system,
|
1640
|
+
name=name,
|
1641
|
+
min_length=min_length,
|
1642
|
+
max_length=max_length,
|
1643
|
+
aliases=aliases,
|
1644
|
+
data_source=data_source,
|
1645
|
+
initial_value=initial_value,
|
1646
|
+
persistent=persistent,
|
1647
|
+
short_description=short_description,
|
1648
|
+
long_description=long_description,
|
1649
|
+
extra=extra,
|
1650
|
+
units=units,
|
1651
|
+
encoding=encoding
|
1652
|
+
)
|
1653
|
+
self.entries.append(ParameterEntry(parameter=parameter))
|
1654
|
+
|
1655
|
+
def add_binary_parameter_entry(
|
1656
|
+
self,
|
1657
|
+
name: str,
|
1658
|
+
min_length: int | None = None,
|
1659
|
+
max_length: int | None = None,
|
1660
|
+
aliases: Mapping[str, str] | None = None,
|
1661
|
+
data_source: DataSource = DataSource.TELEMETERED,
|
1662
|
+
initial_value: Any = None,
|
1663
|
+
persistent: bool = True,
|
1664
|
+
short_description: str | None = None,
|
1665
|
+
long_description: str | None = None,
|
1666
|
+
extra: Mapping[str, str] | None = None,
|
1667
|
+
units: str | None = None,
|
1668
|
+
encoding: Encoding | None = None,
|
1669
|
+
):
|
1670
|
+
"""
|
1671
|
+
Add a binary parameter to this container.
|
1672
|
+
|
1673
|
+
This method creates a binary parameter and adds it as an entry to this container.
|
1674
|
+
|
1675
|
+
Parameters:
|
1676
|
+
-----------
|
1677
|
+
name : str
|
1678
|
+
The name of the parameter.
|
1679
|
+
min_length : int, optional
|
1680
|
+
Minimum valid length of the binary data in bytes.
|
1681
|
+
max_length : int, optional
|
1682
|
+
Maximum valid length of the binary data in bytes.
|
1683
|
+
aliases : Mapping[str, str], optional
|
1684
|
+
Alternative names for the parameter, keyed by namespace.
|
1685
|
+
data_source : DataSource, optional
|
1686
|
+
Source of the parameter value. Default is DataSource.TELEMETERED.
|
1687
|
+
initial_value : Any, optional
|
1688
|
+
Initial value for this parameter.
|
1689
|
+
persistent : bool, optional
|
1690
|
+
Whether this parameter's value should be persisted. Default is True.
|
1691
|
+
short_description : str, optional
|
1692
|
+
A short description of the parameter.
|
1693
|
+
long_description : str, optional
|
1694
|
+
A longer description of the parameter.
|
1695
|
+
extra : Mapping[str, str], optional
|
1696
|
+
Arbitrary information about the parameter, keyed by name.
|
1697
|
+
units : str, optional
|
1698
|
+
Units of this parameter.
|
1699
|
+
encoding : Encoding, optional
|
1700
|
+
Encoding information for this parameter.
|
1701
|
+
"""
|
1702
|
+
parameter = BinaryParameter(
|
1703
|
+
system=self.system,
|
1704
|
+
name=name,
|
1705
|
+
min_length=min_length,
|
1706
|
+
max_length=max_length,
|
1707
|
+
aliases=aliases,
|
1708
|
+
data_source=data_source,
|
1709
|
+
initial_value=initial_value,
|
1710
|
+
persistent=persistent,
|
1711
|
+
short_description=short_description,
|
1712
|
+
long_description=long_description,
|
1713
|
+
extra=extra,
|
1714
|
+
units=units,
|
1715
|
+
encoding=encoding
|
1716
|
+
)
|
1717
|
+
self.entries.append(ParameterEntry(parameter=parameter))
|
1718
|
+
|
1719
|
+
def add_absolute_time_parameter_entry(
|
1720
|
+
self,
|
1721
|
+
name: str,
|
1722
|
+
reference: Union[Epoch, datetime.datetime, AbsoluteTimeParameter],
|
1723
|
+
aliases: Mapping[str, str] | None = None,
|
1724
|
+
data_source: DataSource = DataSource.TELEMETERED,
|
1725
|
+
initial_value: Any = None,
|
1726
|
+
persistent: bool = True,
|
1727
|
+
short_description: str | None = None,
|
1728
|
+
long_description: str | None = None,
|
1729
|
+
extra: Mapping[str, str] | None = None,
|
1730
|
+
units: str | None = None,
|
1731
|
+
encoding: TimeEncoding | None = None,
|
1732
|
+
):
|
1733
|
+
"""
|
1734
|
+
Add an absolute time parameter to this container.
|
1735
|
+
|
1736
|
+
This method creates an absolute time parameter and adds it as an entry to this container.
|
1737
|
+
|
1738
|
+
Parameters:
|
1739
|
+
-----------
|
1740
|
+
name : str
|
1741
|
+
The name of the parameter.
|
1742
|
+
reference : Union[Epoch, datetime.datetime, AbsoluteTimeParameter]
|
1743
|
+
Reference time (epoch) for this parameter.
|
1744
|
+
aliases : Mapping[str, str], optional
|
1745
|
+
Alternative names for the parameter, keyed by namespace.
|
1746
|
+
data_source : DataSource, optional
|
1747
|
+
Source of the parameter value. Default is DataSource.TELEMETERED.
|
1748
|
+
initial_value : Any, optional
|
1749
|
+
Initial value for this parameter.
|
1750
|
+
persistent : bool, optional
|
1751
|
+
Whether this parameter's value should be persisted. Default is True.
|
1752
|
+
short_description : str, optional
|
1753
|
+
A short description of the parameter.
|
1754
|
+
long_description : str, optional
|
1755
|
+
A longer description of the parameter.
|
1756
|
+
extra : Mapping[str, str], optional
|
1757
|
+
Arbitrary information about the parameter, keyed by name.
|
1758
|
+
units : str, optional
|
1759
|
+
Units of this parameter.
|
1760
|
+
encoding : TimeEncoding, optional
|
1761
|
+
Encoding information for this time parameter.
|
1762
|
+
"""
|
1763
|
+
parameter = AbsoluteTimeParameter(
|
1764
|
+
system=self,
|
1765
|
+
name=name,
|
1766
|
+
reference=reference,
|
1767
|
+
aliases=aliases,
|
1768
|
+
data_source=data_source,
|
1769
|
+
initial_value=initial_value,
|
1770
|
+
persistent=persistent,
|
1771
|
+
short_description=short_description,
|
1772
|
+
long_description=long_description,
|
1773
|
+
extra=extra,
|
1774
|
+
units=units,
|
1775
|
+
encoding=encoding
|
1776
|
+
)
|
1777
|
+
self.entries.append(ParameterEntry(parameter=parameter))
|
1778
|
+
|
1779
|
+
def add_aggregate_parameter_entry(
|
1780
|
+
self,
|
1781
|
+
name: str,
|
1782
|
+
members: Sequence[Member],
|
1783
|
+
aliases: Mapping[str, str] | None = None,
|
1784
|
+
data_source: DataSource = DataSource.TELEMETERED,
|
1785
|
+
initial_value: Any = None,
|
1786
|
+
persistent: bool = True,
|
1787
|
+
short_description: str | None = None,
|
1788
|
+
long_description: str | None = None,
|
1789
|
+
extra: Mapping[str, str] | None = None,
|
1790
|
+
encoding: Encoding | None = None,
|
1791
|
+
):
|
1792
|
+
"""
|
1793
|
+
Add an aggregate parameter to this container.
|
1794
|
+
|
1795
|
+
This method creates an aggregate parameter (a parameter composed of multiple members)
|
1796
|
+
and adds it as an entry to this container.
|
1797
|
+
|
1798
|
+
Parameters:
|
1799
|
+
-----------
|
1800
|
+
name : str
|
1801
|
+
The name of the parameter.
|
1802
|
+
members : Sequence[Member]
|
1803
|
+
The members that make up this aggregate parameter.
|
1804
|
+
aliases : Mapping[str, str], optional
|
1805
|
+
Alternative names for the parameter, keyed by namespace.
|
1806
|
+
data_source : DataSource, optional
|
1807
|
+
Source of the parameter value. Default is DataSource.TELEMETERED.
|
1808
|
+
initial_value : Any, optional
|
1809
|
+
Initial value for this parameter.
|
1810
|
+
persistent : bool, optional
|
1811
|
+
Whether this parameter's value should be persisted. Default is True.
|
1812
|
+
short_description : str, optional
|
1813
|
+
A short description of the parameter.
|
1814
|
+
long_description : str, optional
|
1815
|
+
A longer description of the parameter.
|
1816
|
+
extra : Mapping[str, str], optional
|
1817
|
+
Arbitrary information about the parameter, keyed by name.
|
1818
|
+
encoding : Encoding, optional
|
1819
|
+
Encoding information for this parameter.
|
1820
|
+
"""
|
1821
|
+
parameter = AggregateParameter(
|
1822
|
+
system=self.system,
|
1823
|
+
name=name,
|
1824
|
+
members=members,
|
1825
|
+
aliases=aliases,
|
1826
|
+
data_source=data_source,
|
1827
|
+
initial_value=initial_value,
|
1828
|
+
persistent=persistent,
|
1829
|
+
short_description=short_description,
|
1830
|
+
long_description=long_description,
|
1831
|
+
extra=extra,
|
1832
|
+
encoding=encoding
|
1833
|
+
)
|
1834
|
+
self.entries.append(ParameterEntry(parameter=parameter))
|
1835
|
+
|
1836
|
+
def add_array_parameter_entry(
|
1837
|
+
self,
|
1838
|
+
name: str,
|
1839
|
+
data_type: DataType,
|
1840
|
+
length: int | DynamicInteger,
|
1841
|
+
aliases: Mapping[str, str] | None = None,
|
1842
|
+
data_source: DataSource = DataSource.TELEMETERED,
|
1843
|
+
initial_value: Any = None,
|
1844
|
+
persistent: bool = True,
|
1845
|
+
short_description: str | None = None,
|
1846
|
+
long_description: str | None = None,
|
1847
|
+
extra: Mapping[str, str] | None = None,
|
1848
|
+
encoding: Encoding | None = None,
|
1849
|
+
):
|
1850
|
+
"""
|
1851
|
+
Add an array parameter to this container.
|
1852
|
+
|
1853
|
+
This method creates an array parameter (a parameter containing multiple elements
|
1854
|
+
of the same type) and adds it as an entry to this container.
|
1855
|
+
|
1856
|
+
Parameters:
|
1857
|
+
-----------
|
1858
|
+
name : str
|
1859
|
+
The name of the parameter.
|
1860
|
+
data_type : DataType
|
1861
|
+
The data type of the array elements.
|
1862
|
+
length : int | DynamicInteger
|
1863
|
+
The length of the array, either as a fixed integer or a dynamic reference.
|
1864
|
+
aliases : Mapping[str, str], optional
|
1865
|
+
Alternative names for the parameter, keyed by namespace.
|
1866
|
+
data_source : DataSource, optional
|
1867
|
+
Source of the parameter value. Default is DataSource.TELEMETERED.
|
1868
|
+
initial_value : Any, optional
|
1869
|
+
Initial value for this parameter.
|
1870
|
+
persistent : bool, optional
|
1871
|
+
Whether this parameter's value should be persisted. Default is True.
|
1872
|
+
short_description : str, optional
|
1873
|
+
A short description of the parameter.
|
1874
|
+
long_description : str, optional
|
1875
|
+
A longer description of the parameter.
|
1876
|
+
extra : Mapping[str, str], optional
|
1877
|
+
Arbitrary information about the parameter, keyed by name.
|
1878
|
+
encoding : Encoding, optional
|
1879
|
+
Encoding information for this parameter.
|
1880
|
+
"""
|
1881
|
+
parameter = ArrayParameter(
|
1882
|
+
system=self.system,
|
1883
|
+
name=name,
|
1884
|
+
data_type=data_type,
|
1885
|
+
length=length,
|
1886
|
+
aliases=aliases,
|
1887
|
+
data_source=data_source,
|
1888
|
+
initial_value=initial_value,
|
1889
|
+
persistent=persistent,
|
1890
|
+
short_description=short_description,
|
1891
|
+
long_description=long_description,
|
1892
|
+
extra=extra,
|
1893
|
+
encoding=encoding
|
1894
|
+
)
|
1895
|
+
self.entries.append(ParameterEntry(parameter=parameter))
|