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 System, Subsystem, Command, Container, AndExpression, EqExpression
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
- export_file_path = '.',
20
- snapshot_file_path = './.rccn_snapshots',
21
- diff_file_path = './.rccn_diffs',
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.export_file_path = export_file_path
32
- self.snapshot_file_path = snapshot_file_path
33
- self.snapshot_generated_file_path = os.path.join(snapshot_file_path, 'auto_generated')
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.export_file_path, 'rccn_usr_'+snakecase(self.name), 'src', 'main.rs'),
50
- 'main_generated_snapshot': os.path.join(self.snapshot_file_path, 'generated', 'rccn_usr_'+snakecase(self.name), 'src', 'main.rs'),
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.diff_file_path, 'rccn_usr_'+snakecase(self.name), 'src', 'main.diff'),
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.export_file_path, 'rccn_usr_'+snakecase(self.name), 'Cargo.toml'),
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
- return os.path.join(self.snapshot_file_path, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
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
- app_src_dir = os.path.join(self.export_file_path, 'rccn_usr_'+snakecase(self.name), 'src')
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, export_file_path='.'):
115
- self.export_file_path = export_file_path
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.export_file_path = self.export_file_path
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.export_file_path, 'rccn_usr_'+snakecase(self.name), 'src'), os.path.join(self.text_modules_path, 'command'))
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
- if os.path.exists(os.path.join(self.snapshot_file_path, 'user')):
150
- user_snapshots_path = os.path.join(self.snapshot_file_path, 'user')
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
- # Check if all services have unique names
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('All services must have unique names.')
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('All services must have unique service_ids.')
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
- if len(command_names) != len(set(command_names)):
172
- raise ValueError('All commands must have unique names.')
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
- if len(command_subtypes) != len(set(command_subtypes)):
179
- raise ValueError('All commands must have unique subtypes.')
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.export_file_path, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
239
- 'service_generated_snapshot': os.path.join(self.snapshot_file_path, 'generated', 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
240
- 'service_user_snapshot': os.path.join(self.snapshot_file_path, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
241
- 'service_diff': os.path.join(self.diff_file_path, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.diff'),
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.export_file_path, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
244
- 'command_generated_snapshot': os.path.join(self.snapshot_file_path, 'generated', 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
245
- 'command_user_snapshot': os.path.join(self.snapshot_file_path, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
246
- 'command_diff': os.path.join(self.diff_file_path, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.diff'),
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.export_file_path, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'mod.rs'),
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.export_file_path, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.rs'),
251
- 'telemetry_generated_snapshot': os.path.join(self.snapshot_file_path, 'generated', 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.rs'),
252
- 'telemetry_user_snapshot': os.path.join(self.snapshot_file_path, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
253
- 'telemetry_diff': os.path.join(self.diff_file_path, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.diff'),
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
- command_export_file_path = os.path.join(export_file_dir, snakecase(self.name), 'command.rs')
331
- with open(command_export_file_path, 'w') as file:
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
- def __init__(self, *args, **kwargs):
391
- self.init_args = args
392
- self.init_kwargs = kwargs
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 'base' in kwargs and (isinstance(kwargs['base'], Command) or isinstance(kwargs['base'], RCCNCommand)):
396
- self.add_to_service(kwargs['base'].system)
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 used with system = \'"+service.name+"\' and type = "+str(service.service_id)+".")
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
- return os.path.join(self.snapshot_file_path, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
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
- def __init__(self, base="/PUS/pus-tm", subtype = None, *args, **kwargs):
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.init_args = args
478
- self.init_kwargs = kwargs
479
- self.init_kwargs['base'] = base
480
- self.name = kwargs['name']
481
- if 'system' in kwargs and isinstance(kwargs['system'], Service):
482
- self.add_to_service(kwargs['system'])
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' in self.init_kwargs:
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 not 'condition' in self.init_kwargs:
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
- raise ValueError('RCCN-Error: Container '+self.name+' has no \'condition\' argument and condition could not be created due to a missing \'subtype\' argument.')
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__(*self.init_args, **self.init_kwargs)
1250
+ super().__init__(**self.init_kwargs)
511
1251
  else:
512
- super().__init__(system=service, *self.init_args, **self.init_kwargs)
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))