rccn-gen 1.0.3__py3-none-any.whl → 1.2.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,10 +1,11 @@
1
- from yamcs.pymdb import System, Subsystem, Command, Container
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
8
9
 
9
10
  class Application(Subsystem):
10
11
  """
@@ -16,9 +17,9 @@ class Application(Subsystem):
16
17
  name: str,
17
18
  apid: int,
18
19
  vcid: int = 0,
19
- export_file_path = '.',
20
- snapshot_file_path = './.rccn_snapshots',
21
- diff_file_path = './.rccn_diffs',
20
+ export_directory = '.',
21
+ snapshot_directory = './.rccn_snapshots',
22
+ diff_directory = './.rccn_diffs',
22
23
  snapshots = True,
23
24
  *args,
24
25
  **kwargs
@@ -27,15 +28,13 @@ class Application(Subsystem):
27
28
 
28
29
  self.apid = apid
29
30
  self.vcid = vcid
31
+ self.export_directory = export_directory
30
32
  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
33
+ self.snapshot_directory = snapshot_directory
34
+ self.snapshot_generated_file_path = os.path.join(snapshot_directory, 'auto_generated')
35
+ self.diff_directory = diff_directory
35
36
  self.text_modules_path = files('rccn_gen').joinpath('text_modules')
36
- print(self.text_modules_path)
37
37
  self.text_modules_main_path = os.path.join(self.text_modules_path, 'main')
38
- print(self.text_modules_main_path)
39
38
  self.snapshots = snapshots
40
39
  self.keep_snapshots = 10
41
40
 
@@ -43,27 +42,52 @@ class Application(Subsystem):
43
42
  if not isinstance(service, Service):
44
43
  raise TypeError('Service '+service.name+' is not a RCCNCommand.')
45
44
  service.add_to_application(self)
46
-
45
+
46
+ def create_and_add_service(
47
+ self,
48
+ name: str,
49
+ service_id: int,
50
+ aliases: Mapping[str, str] | None = None,
51
+ short_description: str | None = None,
52
+ long_description: str | None = None,
53
+ extra: Mapping[str, str] | None = None,
54
+ *args,
55
+ **kwargs
56
+ ):
57
+ if 'system' not in kwargs:
58
+ kwargs['system'] = self
59
+ else:
60
+ raise ValueError('RCCN-Error: \'create_and_add_service\' function can not be called with a \'system\' argument.')
61
+ Service(
62
+ name=name,
63
+ service_id=service_id,
64
+ aliases=aliases,
65
+ short_description=short_description,
66
+ long_description=long_description,
67
+ *args,
68
+ **kwargs
69
+ )
70
+
47
71
  def file_paths(self):
48
72
  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'),
73
+ 'main': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.name), 'src', 'main.rs'),
74
+ 'main_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', 'rccn_usr_'+snakecase(self.name), 'src', 'main.rs'),
51
75
  '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'),
76
+ 'main_diff': os.path.join(self.diff_directory, 'rccn_usr_'+snakecase(self.name), 'src', 'main.diff'),
53
77
  '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'),
78
+ 'cargo_toml': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.name), 'Cargo.toml'),
55
79
  'cargo_toml_template': os.path.join(self.text_modules_path, 'cargo_toml', 'cargo.txt'),
56
80
  }
57
81
  return paths
58
82
 
59
83
  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"))
84
+ return os.path.join(self.snapshot_directory, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
61
85
 
62
86
  def services(self):
63
87
  return [subsystem for subsystem in self.subsystems if isinstance(subsystem, Service)]
64
88
 
65
89
  def create_rccn_directories(self):
66
- app_src_dir = os.path.join(self.export_file_path, 'rccn_usr_'+snakecase(self.name), 'src')
90
+ app_src_dir = os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.name), 'src')
67
91
  if not os.path.exists(app_src_dir):
68
92
  os.makedirs(app_src_dir)
69
93
  for service in self.services():
@@ -111,8 +135,20 @@ class Application(Subsystem):
111
135
  text = delete_all_keywords(text)
112
136
  return text
113
137
 
114
- def generate_rccn_code(self, rebase_changes=True, check=True, export_file_path='.'):
115
- self.export_file_path = export_file_path
138
+ def generate_rccn_code(self, export_directory:str, snapshot_directory='', diff_directory='', rebase_changes=True, check=True):
139
+ # Update export, snapshot and diff directory for the Application and all Services
140
+ self.export_directory = export_directory
141
+ if snapshot_directory == '':
142
+ snapshot_directory = os.path.join(self.export_directory, '.rccn-snapshots')
143
+ if diff_directory == '':
144
+ diff_directory = os.path.join(self.export_directory, '.rccn-diffs')
145
+ self.snapshot_directory = snapshot_directory
146
+ self.diff_directory = diff_directory
147
+ for service in self.services():
148
+ service.export_directory = self.export_directory
149
+ service.diff_directory = self.diff_directory
150
+ service.snapshot_directory = self.snapshot_directory
151
+
116
152
  if check:
117
153
  self.check_user_input()
118
154
  self.rebase_changes = rebase_changes
@@ -122,12 +158,12 @@ class Application(Subsystem):
122
158
  if not os.path.exists(self.file_paths()['cargo_toml']):
123
159
  self.generate_cargo_toml_file()
124
160
  for service in self.services():
125
- service.export_file_path = self.export_file_path
161
+ service.export_directory = self.export_directory
126
162
  service.generate_rccn_service_file()
127
163
  if not os.path.exists(service.file_paths()['mod']):
128
164
  service.generate_mod_file()
129
165
  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'))
166
+ 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
167
  self.delete_old_snapshots()
132
168
 
133
169
  def generate_snapshot(self, current_file_reference, snapshot_file_reference):
@@ -146,37 +182,37 @@ class Application(Subsystem):
146
182
  diff_file.write(diff_text)
147
183
 
148
184
  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')
185
+ if os.path.exists(os.path.join(self.snapshot_directory, 'user')):
186
+ user_snapshots_path = os.path.join(self.snapshot_directory, 'user')
151
187
  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
188
  snapshots.sort(key=os.path.getctime)
153
189
  while len(snapshots) > self.keep_snapshots:
154
190
  shutil.rmtree(snapshots.pop(0))
155
191
 
156
192
  def check_user_input(self):
157
- # Check if all services have unique names
193
+ # Check if all services in the application have unique names
158
194
  service_names = [service.name for service in self.services()]
159
195
  if len(service_names) != len(set(service_names)):
160
- raise ValueError('All services must have unique names.')
196
+ raise ValueError('RCCN-Error: App \''+self.name+'\' has multiple services with the same name.')
161
197
 
162
- # Check if all services have unique service_ids
198
+ # Check if all services in the application have unique service_ids
163
199
  service_ids = [service.service_id for service in self.services()]
164
200
  if len(service_ids) != len(set(service_ids)):
165
- raise ValueError('All services must have unique service_ids.')
201
+ raise ValueError('RCCN-Error: App \''+self.name+'\' has multiple services with the same ID.')
166
202
 
167
- # Check if all commands have unique names
168
- command_names = []
203
+ # Check if all commands in each service have unique names
169
204
  for service in self.services():
205
+ command_names = []
170
206
  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.')
207
+ if len(command_names) != len(set(command_names)):
208
+ raise ValueError('RCCN-Error: Service \''+service.name+'\' has multiple commands with the same name.')
173
209
 
174
- # Check if all commands have unique subtypes
175
- command_subtypes = []
210
+ # Check if all commands in each service have unique subtypes
176
211
  for service in self.services():
212
+ command_subtypes = []
177
213
  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.')
214
+ if len(command_subtypes) != len(set(command_subtypes)):
215
+ raise ValueError('RCCN-Error: Service \''+service.name+'\' has multiple commands with the same subtype.')
180
216
 
181
217
  def generate_cargo_toml_file(self):
182
218
  with open(self.file_paths()['cargo_toml_template'], 'r') as file:
@@ -189,7 +225,7 @@ class Service(Subsystem):
189
225
  def __init__(
190
226
  self,
191
227
  name: str,
192
- service_id: int = None,
228
+ service_id: int,
193
229
  *args,
194
230
  **kwargs
195
231
  ):
@@ -218,10 +254,7 @@ class Service(Subsystem):
218
254
  name=self.name,
219
255
  *self.init_args, **self.init_kwargs
220
256
  )
221
- self.export_file_path = self.system.export_file_path
222
- self.snapshot_file_path = self.system.snapshot_file_path
223
257
  self.snapshots = self.system.snapshots
224
- self.diff_file_path = self.system.diff_file_path
225
258
 
226
259
  def add_container(self, container):
227
260
  if not isinstance(container, RCCNContainer):
@@ -235,28 +268,28 @@ class Service(Subsystem):
235
268
 
236
269
  def file_paths(self):
237
270
  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'),
271
+ 'service': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
272
+ 'service_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
273
+ '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'),
274
+ 'service_diff': os.path.join(self.diff_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.diff'),
242
275
  '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'),
276
+ 'command': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
277
+ 'command_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
278
+ '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'),
279
+ 'command_diff': os.path.join(self.diff_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.diff'),
247
280
  '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'),
281
+ 'mod': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'mod.rs'),
249
282
  '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'),
283
+ 'telemetry': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.rs'),
284
+ 'telemetry_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.rs'),
285
+ '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'),
286
+ 'telemetry_diff': os.path.join(self.diff_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.diff'),
254
287
  'telemetry_template': os.path.join(self.text_modules_telemetry_path, 'telemetry.txt'),
255
288
  }
256
289
  return paths
257
290
 
258
291
  def rccn_commands(self):
259
- return [command for command in self.commands if isinstance(command, RCCNCommand)]
292
+ return [command for command in self.commands if isinstance(command, RCCNCommand) and command.name != 'base']
260
293
 
261
294
  def find_and_replace_keywords(self, text, text_modules_path):
262
295
  # Find and replace service module keywords
@@ -272,16 +305,13 @@ class Service(Subsystem):
272
305
  replacement_text = (self.find_and_replace_keywords(module_text, text_modules_path) + '\n')
273
306
  text = insert_before_with_indentation(text, service_module_keyword, replacement_text)
274
307
 
275
- # Call keyword replacement for all associated commands (Later, there needs to be checking to account for user changes to the generated files)
276
- if len(self.rccn_commands()) == 0:
277
- print('RCCN-Gen: Service '+self.name+' does not have any commands associated with it.')
278
308
  for command in self.rccn_commands():
279
309
  text = command.find_and_replace_keywords(text, text_modules_path)
280
310
 
281
311
  # Find and replace service variable keywords
282
312
  var_keywords = get_var_keywords(text)
283
313
  service_var_translation = {
284
- '<<VAR_SERVICE_NAME>>': lambda: self.name,
314
+ '<<VAR_SERVICE_NAME>>': lambda: snakecase(self.name),
285
315
  '<<VAR_SERVICE_ID>>': lambda: str(self.service_id),
286
316
  '<<VAR_SERVICE_NAME_UCASE>>': lambda: pascalcase(self.name),
287
317
  '<<VAR_SERVICE_TELEMETRY>>': lambda: self.generate_rust_telemetry_definition(),
@@ -325,13 +355,13 @@ class Service(Subsystem):
325
355
  self.generate_snapshot('command', 'command_user_snapshot')
326
356
  # Generate command.rs file
327
357
  if len(self.rccn_commands()) == 0:
328
- print('RCCN-Gen: Service '+self.name+' has no implemented commands. Generation of command.rs file will be skipped.')
358
+ print('RCCN-Information: Service \''+self.name+'\' has no commands other than base command. Generation of command.rs file will be skipped.')
329
359
  return
330
360
  command_file_path = self.file_paths()['command_template']
331
361
  with open(command_file_path, 'r') as file:
332
362
  command_file_text = file.read()
333
- command_export_file_path = os.path.join(export_file_dir, snakecase(self.name), 'command.rs')
334
- with open(command_export_file_path, 'w') as file:
363
+ command_export_directory = os.path.join(export_file_dir, snakecase(self.name), 'command.rs')
364
+ with open(command_export_directory, 'w') as file:
335
365
  file.write(self.find_and_replace_keywords(command_file_text, text_modules_path))
336
366
  # Create snapshot of command.rs if instructed
337
367
  if self.snapshots:
@@ -387,30 +417,116 @@ class Service(Subsystem):
387
417
  container.__class__ = RCCNContainer
388
418
  telemetry_definition_text += container.generate_rccn_telemetry()
389
419
  return telemetry_definition_text
420
+
421
+ def create_and_add_command(
422
+ self,
423
+ name: str,
424
+ *,
425
+ aliases: Mapping[str, str] | None = None,
426
+ short_description: str | None = None,
427
+ long_description: str | None = None,
428
+ extra: Mapping[str, str] | None = None,
429
+ abstract: bool = False,
430
+ base: Command | str | None = None,
431
+ assignments: Mapping[str, Any] | None = None,
432
+ arguments: Sequence[Argument] | None = None,
433
+ entries: Sequence[CommandEntry] | None = None,
434
+ level: CommandLevel = CommandLevel.NORMAL,
435
+ warning_message: str | None = None,
436
+ constraint: (
437
+ Union[TransmissionConstraint, Sequence[TransmissionConstraint]] | None
438
+ ) = None,
439
+ ):
440
+ Command(
441
+ name=name,
442
+ system=self,
443
+ aliases=aliases,
444
+ short_description=short_description,
445
+ long_description=long_description,
446
+ extra=extra,
447
+ abstract=abstract,
448
+ base=base,
449
+ assignments=assignments,
450
+ arguments=arguments,
451
+ entries=entries,
452
+ level=level,
453
+ warning_message=warning_message,
454
+ constraint=constraint
455
+ )
456
+
457
+ def rccn_container(self):
458
+ return [container for container in self.containers if isinstance(container, RCCNContainer)]
390
459
 
391
460
 
392
461
  class RCCNCommand(Command):
393
- def __init__(self, *args, **kwargs):
394
- self.init_args = args
395
- self.init_kwargs = kwargs
462
+ def __init__(
463
+ self,
464
+ name: str,
465
+ *,
466
+ aliases: Mapping[str, str] | None = None,
467
+ short_description: str | None = None,
468
+ long_description: str | None = None,
469
+ extra: Mapping[str, str] | None = None,
470
+ abstract: bool = False,
471
+ base: Command | str | None = None,
472
+ assignments: Mapping[str, Any] | None = None,
473
+ arguments: Sequence[Argument] | None = None,
474
+ entries: Sequence[CommandEntry] | None = None,
475
+ level: CommandLevel = CommandLevel.NORMAL,
476
+ warning_message: str | None = None,
477
+ constraint: (
478
+ Union[TransmissionConstraint, Sequence[TransmissionConstraint]] | None
479
+ ) = None,
480
+ **kwargs
481
+ ):
482
+ self.init_args = ()
483
+ self.init_kwargs = {
484
+ 'name': name,
485
+ 'aliases': aliases,
486
+ 'short_description': short_description,
487
+ 'long_description': long_description,
488
+ 'extra': extra,
489
+ 'abstract': abstract,
490
+ 'base': base,
491
+ 'assignments': assignments,
492
+ 'arguments': arguments,
493
+ 'entries': entries,
494
+ 'level': level,
495
+ 'warning_message': warning_message,
496
+ 'constraint': constraint,
497
+ **kwargs
498
+ }
396
499
  if 'system' in kwargs and isinstance(kwargs['system'], Service):
397
500
  self.add_to_service(kwargs['system'])
501
+ elif base and (isinstance(base, Command) or isinstance(base, RCCNCommand)):
502
+ self.add_to_service(base.system)
398
503
 
399
504
  def add_to_service(self, service):
400
- if not any(command.name == 'base' for command in service.commands):
401
- base_command = Command(
505
+ if not 'base' in self.init_kwargs and not any(command.name == 'base' for command in service.commands):
506
+ 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)+".")
507
+ self.init_kwargs['base'] = Command(
402
508
  system=service,
403
509
  name='base',
404
510
  abstract=True,
405
511
  base='/PUS/pus-tc',
406
512
  assignments={'type': service.service_id}
407
513
  )
408
- self.init_kwargs['base'] = base_command
514
+ elif not 'base' in self.init_kwargs and any(command.name == 'base' for command in service.commands):
515
+ print("RCCN-Information: Command \'"+self.init_kwargs['name']+"\' doesn\'t have a \'base\' argument. Existing base command for service \'"+service.name+"\' will be used.")
516
+ self.init_kwargs['base'] = next(command for command in service.commands if command.name == 'base')
409
517
  if 'system' in self.init_kwargs and isinstance(self.init_kwargs['system'], Service):
410
518
  super().__init__(*self.init_args, **self.init_kwargs)
411
519
  else:
412
520
  super().__init__(system=service, *self.init_args, **self.init_kwargs)
413
521
  self.assignments['apid'] = self.system.system.apid
522
+ if not 'subtype' in self.assignments and self.name is not 'base':
523
+ used_subtypes = [command.assignments['subtype'] if 'subtype' in command.assignments else None for command in self.system.rccn_commands()]
524
+ new_subtype = 1
525
+ while new_subtype in used_subtypes:
526
+ new_subtype = new_subtype + 1
527
+ print('RCCN-Information: Command \''+self.name+'\' has no subtype specified. Subtype will be set to '+str(new_subtype)+'.')
528
+ self.assignments['subtype'] = new_subtype
529
+ self.struct_name = self.name + 'Args'
414
530
 
415
531
 
416
532
  def find_and_replace_keywords(self, text, text_modules_path):
@@ -432,25 +548,26 @@ class RCCNCommand(Command):
432
548
  command_var_translation = {
433
549
  '<<VAR_COMMAND_NAME_UCASE>>': lambda: pascalcase(self.name),
434
550
  '<<VAR_COMMAND_NAME>>': lambda: self.name,
551
+ '<<VAR_COMMAND_STRUCT_NAME>>': lambda: self.struct_name,
435
552
  '<<VAR_COMMAND_SUBTYPE>>': lambda: str(self.assignments['subtype']),
436
- '<<VAR_COMMAND_STRUCT>>': lambda: self.struct_definition()
553
+ '<<VAR_COMMAND_STRUCT>>': lambda: self.struct_definition(),
554
+ '<<VAR_SHORT_DESCRIPTION>>': lambda: "\n/// " + self.short_description if self.short_description is not None else "",
437
555
  }
438
556
  for command_var_keyword in command_var_keywords:
439
557
  if command_var_keyword in command_var_translation.keys():
440
558
  text = replace_with_indentation(text, command_var_keyword, command_var_translation[command_var_keyword]())
441
559
  return text
442
560
 
443
- def check_user_input(self):
444
- if self.assignments['subtype'] == None and self.name != 'base':
445
- raise ValueError('Command '+self.name+' does not have a subtype assigned to it.')
446
-
447
561
  def user_snapshot_path(self):
448
- return os.path.join(self.snapshot_file_path, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
562
+ return os.path.join(self.snapshot_directory, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
449
563
 
450
564
  def struct_definition(self):
565
+ struct_definition_text = ""
451
566
  if len(self.arguments) == 0:
452
567
  return ''
453
- struct_definition_text = "#[derive(BitStruct, Debug, PartialEq)]\npub struct "+pascalcase(self.name)+" {\n"
568
+ if hasattr(self, 'long_description') and self.long_description is not None:
569
+ struct_definition_text += "/// "+str(self.long_description)+"\n"
570
+ struct_definition_text += "#[derive(BitStruct, Debug, PartialEq)]\npub struct "+pascalcase(self.struct_name)+" {\n"
454
571
  ins = ""
455
572
  append = ""
456
573
  for argument in self.arguments:
@@ -462,28 +579,147 @@ class RCCNCommand(Command):
462
579
  struct_definition_text += append
463
580
  return struct_definition_text
464
581
 
582
+
583
+
465
584
  class RCCNContainer(Container):
466
- def __init__(self, base="/PUS/pus-tm", *args, **kwargs):
585
+ def __init__(
586
+ self,
587
+ base="/PUS/pus-tm",
588
+ subtype = None,
589
+ *,
590
+ system: System = None,
591
+ name: str = None,
592
+ entries: Sequence[ParameterEntry | ContainerEntry] | None = None,
593
+ abstract: bool = False,
594
+ condition: Expression | None = None,
595
+ aliases: Mapping[str, str] | None = None,
596
+ short_description: str | None = None,
597
+ long_description: str | None = None,
598
+ extra: Mapping[str, str] | None = None,
599
+ bits: int | None = None,
600
+ rate: float | None = None,
601
+ hint_partition: bool = False,
602
+ ):
467
603
  self.base = base
468
- self.init_args = args
469
- self.init_kwargs = kwargs
470
- if 'system' in kwargs and isinstance(kwargs['system'], Service):
471
- self.add_to_service(kwargs['system'])
604
+ self.subtype = subtype
605
+ self.init_kwargs = {
606
+ 'system': system,
607
+ 'name': name,
608
+ 'entries': entries,
609
+ 'base': base,
610
+ 'abstract': abstract,
611
+ 'condition': condition,
612
+ 'aliases': aliases,
613
+ 'short_description': short_description,
614
+ 'long_description': long_description,
615
+ 'extra': extra,
616
+ 'bits': bits,
617
+ 'rate': rate,
618
+ 'hint_partition': hint_partition,
619
+ }
620
+
621
+ if name is None:
622
+ raise ValueError('RCCN-Error: Container must have a name.')
623
+
624
+ self.name = name
625
+ if system is not None and isinstance(system, Service):
626
+ self.add_to_service(system)
472
627
 
473
628
  def add_to_service(self, service):
629
+ self.type = service.service_id
630
+ condition_type = None
631
+ condition_subtype = None
632
+ if self.init_kwargs['condition'] is not None:
633
+ for eq_expression in self.init_kwargs['condition'].expressions:
634
+ if eq_expression.ref == self.base+'/type':
635
+ condition_type = eq_expression.value
636
+ if eq_expression.ref == self.base+'/subtype':
637
+ condition_subtype = eq_expression.value
638
+ if condition_type is not None and condition_type != self.type:
639
+ print('RCCN-Warning: Container '+self.name+' has a user-defined type of '+str(eq_expression.value)+', which does\'nt match the service ID. User-defined type will be used.')
640
+ self.type = condition_type
641
+ if condition_subtype is not None and self.subtype is not None and condition_subtype != self.subtype:
642
+ print('RCCN-Warning: Container '+self.name+' has an ambiguous user-defined subtype. \'subtype\' argument should match the \'condition\' argument.')
643
+ elif condition_subtype is not None:
644
+ self.subtype = condition_subtype
645
+ elif self.subtype is not None and self.init_kwargs['condition'] is not None:
646
+ self.init_kwargs['condition'] = AndExpression(
647
+ EqExpression(self.base+'/type', self.type),
648
+ EqExpression(self.base+'/subtype', self.subtype)
649
+ )
650
+ else:
651
+ used_subtypes = [container.subtype for container in service.rccn_container()]
652
+ new_subtype = 1
653
+ while new_subtype in used_subtypes:
654
+ new_subtype = new_subtype + 1
655
+ self.subtype = new_subtype
656
+ self.init_kwargs['condition'] = AndExpression(
657
+ EqExpression(self.base+'/type', self.type),
658
+ EqExpression(self.base+'/subtype', self.subtype)
659
+ )
660
+ print('RCCN-Information: Subtype for Container '+self.name+' is not specified through \'subtype\' or \'condition\' arguments. Subtype will be set to '+str(self.subtype)+'.')
661
+
474
662
  if 'system' in self.init_kwargs and isinstance(self.init_kwargs['system'], Service):
475
- super().__init__(*self.init_args, **self.init_kwargs)
663
+ super().__init__(**self.init_kwargs)
476
664
  else:
477
- super().__init__(system=service, *self.init_args, **self.init_kwargs)
665
+ super().__init__(system=service, **self.init_kwargs)
478
666
 
479
667
  def generate_rccn_telemetry(self):
480
- rccn_telemetry_text = "pub struct " + self.name + " {\n"
668
+ rccn_telemetry_text = ""
669
+ if hasattr(self, 'short_description') and self.short_description is not None:
670
+ rccn_telemetry_text += "/// "+str(self.short_description)+"\n"
671
+ rccn_telemetry_text += "#[derive(ServiceTelemetry, BitStruct, Debug)]\n"
672
+ if hasattr(self, 'subtype') and self.subtype is not None:
673
+ rccn_telemetry_text += "#[subtype("+str(self.subtype)+")]\n"
674
+ rccn_telemetry_text += "pub struct " + self.name + " {\n"
481
675
  insert, append = ["",""]
482
676
  for parameter_entry in self.entries:
483
677
  par_def = rust_type_definition(parameter_entry.parameter)
484
678
  insert += par_def[0]
485
679
  append += par_def[1]
486
680
  rccn_telemetry_text += insert
487
- rccn_telemetry_text += "}\n"
681
+ rccn_telemetry_text += "}\n\n"
488
682
  rccn_telemetry_text += append
489
- return rccn_telemetry_text
683
+ return rccn_telemetry_text
684
+
685
+ def add_integer_parameter(
686
+ self,
687
+ name: str,
688
+ signed: bool = True,
689
+ bits: int = 32,
690
+ minimum: int | None = None,
691
+ maximum: int | None = None,
692
+ aliases: Mapping[str, str] | None = None,
693
+ data_source: DataSource = DataSource.TELEMETERED,
694
+ initial_value: Any = None,
695
+ persistent: bool = True,
696
+ short_description: str | None = None,
697
+ long_description: str | None = None,
698
+ extra: Mapping[str, str] | None = None,
699
+ units: str | None = None,
700
+ encoding: Encoding | None = None,
701
+ calibrator: Calibrator | None = None,
702
+ alarm: ThresholdAlarm | None = None,
703
+ context_alarms: Sequence[ThresholdContextAlarm] | None = None,
704
+ ):
705
+ int_parameter = IntegerParameter(
706
+ system=self,
707
+ name=name,
708
+ signed=signed,
709
+ bits=bits,
710
+ minimum=minimum,
711
+ maximum=maximum,
712
+ aliases=aliases,
713
+ data_source=data_source,
714
+ initial_value=initial_value,
715
+ persistent=persistent,
716
+ short_description=short_description,
717
+ long_description=long_description,
718
+ extra=extra,
719
+ units=units,
720
+ encoding=encoding,
721
+ calibrator=calibrator,
722
+ alarm=alarm,
723
+ context_alarms=context_alarms
724
+ )
725
+ self.entries.append(int_parameter)
@@ -11,4 +11,7 @@ futures = "0.3.31"
11
11
  rccn_usr = { version = "0.1.0", path = "../rccn_usr" }
12
12
  satrs = "0.2.1"
13
13
  spacepackets = "0.12.0"
14
- tokio = "1.41.1"
14
+ tokio = "1.41.1"
15
+ env_logger = { version = "0.11.7", default-features = false, features = ["color", "humantime"] }
16
+ num-derive = "0.4"
17
+ num-traits = "0.2"
@@ -1,2 +1,3 @@
1
+ <<VAR_SHORT_DESCRIPTION>>
1
2
  #[subservice(<<VAR_COMMAND_SUBTYPE>>)]
2
- <<VAR_COMMAND_NAME_UCASE>>(<<VAR_COMMAND_NAME>>::Args),
3
+ <<VAR_COMMAND_NAME_UCASE>>(<<VAR_COMMAND_STRUCT_NAME>>),
@@ -1,3 +1,4 @@
1
+ use anyhow::Result;
1
2
  <<SERVICE_MODULE_IMPORT_SERVICE>>
2
3
  use rccn_usr::pus::app::PusApp;
3
4
  use rccn_usr::zenoh::key_expr::OwnedKeyExpr;
@@ -1 +1 @@
1
- use picture_service::service::<<VAR_SERVICE_NAME_UCASE>>;
1
+ use <<VAR_SERVICE_NAME>>::service::<<VAR_SERVICE_NAME_UCASE>>;
@@ -21,7 +21,7 @@ impl <<VAR_SERVICE_NAME_UCASE>> {
21
21
  impl PusService for <<VAR_SERVICE_NAME_UCASE>> {
22
22
  type CommandT = command::Command;
23
23
 
24
- fn handle_tc(&mut self, tc: AcceptedTc, cmd: Self::CommandT) -> AcceptanceResult {
24
+ fn handle_tc(&mut self, mut tc: AcceptedTc, cmd: Self::CommandT) -> AcceptanceResult {
25
25
  println!("PUS-Service: Command received.");
26
26
  match cmd {
27
27
  <<COMMAND_MODULE_MATCH_CMD>>
rccn_gen/utils.py CHANGED
@@ -133,7 +133,9 @@ def rust_type_definition(pymdb_data_instance, parent_name="MyStruct"):
133
133
  pymdb_data_instance.name = singular_name
134
134
  print("RCCN-Gen: Information: An unnamed "+base_type+" has been named \""+pascalcase(pymdb_data_instance.name)+"\" in the generated RCCN code.")
135
135
  sc_instance_name = snakecase(pymdb_data_instance.name)
136
-
136
+ definition_text = ["",""]
137
+ if pymdb_data_instance.short_description is not None:
138
+ definition_text[0] += ("\n\t/// "+str(pymdb_data_instance.short_description)+"\n")
137
139
  if data_type == 'IntegerDataType':
138
140
  if pymdb_data_instance.encoding is None or pymdb_data_instance.encoding.bits is None:
139
141
  raw_bit_number = 8
@@ -143,42 +145,50 @@ def rust_type_definition(pymdb_data_instance, parent_name="MyStruct"):
143
145
  raw_bit_number_str = str(raw_bit_number)
144
146
  eng_bit_number = engineering_bit_number(raw_bit_number)
145
147
  eng_bit_number_str = str(eng_bit_number)
146
- definition_text = ["\t#[bits("+raw_bit_number_str+")]\n"]
148
+ definition_text[0] += "\t#[bits("+raw_bit_number_str+")]\n"
147
149
  if pymdb_data_instance.signed:
148
150
  definition_text[0] += ("\tpub "+sc_instance_name+": i"+eng_bit_number_str+",\n")
149
151
  else:
150
152
  definition_text[0] += ("\tpub "+sc_instance_name+": u"+eng_bit_number_str+",\n")
151
- definition_text.append("")
152
153
 
153
154
  elif data_type == 'BooleanDataType':
154
- definition_text = ["\t#[bits(1)]\n\tpub "+sc_instance_name+": bool,\n"]
155
- definition_text.append("")
155
+ definition_text[0] += ("\t#[bits(1)]\n\tpub "+sc_instance_name+": bool,\n")
156
156
 
157
157
  elif data_type == 'StringDataType':
158
- definition_text = ["\t#[null_terminated]\n\tpub "+sc_instance_name+": String,\n", ""]
158
+ definition_text[0] += "\t#[null_terminated]\n\tpub "+sc_instance_name+": String,\n"
159
159
 
160
160
  elif data_type == 'ArrayDataType':
161
161
  definition_text = rust_type_definition(pymdb_data_instance.data_type, parent_name=pymdb_data_instance.name)
162
162
  definition_text[0] = definition_text[0].replace(': ', ': Vec<').replace(',\n', '>,\n')
163
+ if pymdb_data_instance.short_description is not None:
164
+ definition_text[0] = "\n\t/// "+pymdb_data_instance.short_description+"\n"+definition_text[0]
165
+ if pymdb_data_instance.long_description is not None:
166
+ definition_text[1] = "/// "+pymdb_data_instance.long_description+"\n" + definition_text[1]
163
167
 
164
168
  elif data_type == 'EnumeratedDataType':
165
- definition_text = ["\tpub "+pymdb_data_instance.name+": "+pascalcase(pymdb_data_instance.name)+",\n"]
166
- definition_text.append("pub enum "+pascalcase(pymdb_data_instance.name)+" {\n")
169
+ definition_text[0] += "\t#[bits("+str(pymdb_data_instance.encoding.bits)+")]\n"
170
+ definition_text[0] += "\tpub "+pymdb_data_instance.name+": "+pascalcase(pymdb_data_instance.name)+",\n"
171
+ definition_text[1] += ("#[derive(FromPrimative, ToPrimative, Debug)]\npub enum "+pascalcase(pymdb_data_instance.name)+" {\n")
167
172
  for choice in pymdb_data_instance.choices:
168
173
  definition_text[1] += "\t"+str(choice[1])+" = "+str(choice[0])+",\n"
169
- definition_text[1] += "}\n"
174
+ definition_text[1] += "}\n\n"
175
+ if pymdb_data_instance.long_description is not None:
176
+ definition_text[1] = "/// "+pymdb_data_instance.long_description+"\n" + definition_text[1]
170
177
 
171
178
  elif data_type == 'AggregateDataType':
172
179
  struct_name = pascalcase(pymdb_data_instance.name)
173
- definition_text = ["\tpub "+sc_instance_name+": "+struct_name+",\n"]
174
- definition_text.append("pub struct "+struct_name+" {\n")
180
+ definition_text[0] += "\tpub "+sc_instance_name+": "+struct_name+",\n"
181
+ if pymdb_data_instance.long_description is not None:
182
+ definition_text[1] += "\t/// "+pymdb_data_instance.long_description+"\n"
183
+ definition_text[1] += ("#[derive(FromPrimative, ToPrimative, Debug)]\n")
184
+ definition_text[1] += ("pub struct "+struct_name+" {\n")
175
185
  insert, append = ["",""]
176
186
  for member in pymdb_data_instance.members:
177
187
  mem_def = rust_type_definition(member, parent_name=pymdb_data_instance.name)
178
188
  insert += mem_def[0]
179
189
  append += mem_def[1]
180
190
  definition_text[1] += insert
181
- definition_text[1] += "}\n"
191
+ definition_text[1] += "}\n\n"
182
192
  definition_text[1] += append
183
193
  else:
184
194
  definition_text = ["\t// Please implement datatype "+data_type+" here.\n", ""]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rccn_gen
3
- Version: 1.0.3
3
+ Version: 1.2.0
4
4
  Summary: A python based generator for RACCOON OS source files in Rust from yamcs-pymdb config files.
5
5
  Project-URL: Homepage, https://gitlab.com/rccn/pymdb_code_generation
6
6
  Project-URL: Issues, https://gitlab.com/rccn/pymdb_code_generation/issues
@@ -16,19 +16,18 @@ Description-Content-Type: text/markdown
16
16
 
17
17
  # RCCN Code Generator
18
18
  This generator is used to generate Rust files to deploy on the RACCOON OS satellite based on the pymdb config files used to generate the ground station XTCE files.
19
+
20
+ To see whats new, see the [CHANGELOG](CHANGELOG.md).
19
21
  ## Setup
20
- - Pull the repo
21
22
  - Set up a virtual python environment with `python3` and activate:
22
23
  ```zsh
23
24
  python -m venv .venv
24
25
  source .venv/bin/activate
25
26
  ```
26
- - Install pymdb with `pip install yamcs-pymdb`
27
+ - Install with `pip install rccn-gen`
27
28
 
28
29
  ## Using the Generator
29
30
  The generator is based on [`pymdb`](https://github.com/yamcs/pymdb) and uses the same structure. Where `pymdb` gives the user freedom in defining systems, subsystems and their respective relations, the definitions for rust code generation is more restricted. For the rust generation, the user is bound to the following classes:
30
- - Application,
31
- - Service, and
32
31
  - `Application`,
33
32
  - `Service`, and
34
33
  - `RCCNCommand`.
@@ -45,7 +44,7 @@ An application can be defined with the following statement.
45
44
  ```python
46
45
  app = Application(system=root_system, name="ExampleApp", apid=42)
47
46
  ```
48
- It has the obligatory arguments **system**, **name** and **apid**. After all Applications, Services and RCCNCommands are defined, the rust code generator can be called on the application with `app.generate_rccn_code()`.
47
+ It has the obligatory arguments **system**, **name**, **apid** and **export_directory**. After all Applications, Services and RCCNCommands are defined, the rust code generator can be called on the application with `app.generate_rccn_code(export_directory='.')`.
49
48
 
50
49
  ### Service
51
50
 
@@ -53,7 +52,13 @@ A service may be defined with the following command.
53
52
  ```python
54
53
  service = Service(name="ExampleService", system=app, service_id = 131)
55
54
  ```
56
- It has the obligatory arguments **name** and **system**. The system corresponds to the application where the service is associated to. Note that each service has one associated app. A service cannot be associated to more than one apps. You may think of the Application-Service-Command structure as a mathematical tree, where merging branches are not allowed. However, you can create multiple instances of the same service and associate them to different applications, or create multiple identical command instances and associate them to different services.
55
+ Or like this:
56
+ ```python
57
+ service = Service(name="ExampleService", service_id = 131)
58
+ app.add_service(service)
59
+ ```
60
+
61
+ It has the obligatory arguments **name** and **system**. The system corresponds to the application where the service is associated to. Note that each service has one associated app. A service cannot be associated to more than one app. You may think of the Application-Service-Command structure as a mathematical tree, where merging branches are not allowed. However, you can create multiple instances of the same service and associate them to different applications, or create multiple identical command instances and associate them to different services.
57
62
 
58
63
  ### RCCNCommand
59
64
  An RCCNCommand can be defined with the following statement.
@@ -65,7 +70,33 @@ RCCNCommand(
65
70
  arguments=[StringArgument("name", encoding=StringEncoding())],
66
71
  )
67
72
  ```
68
- Obligatory arguments are **system**, **base**, **name** and **arguments**. The latter can be an empty list. The definition must contain a subtype definition in the assignment property, like shown in the example.
73
+
74
+ Or like this:
75
+ ```python
76
+ my_command = RCCNCommand(
77
+ assignments={"subtype": 2},
78
+ name="StopProcess",
79
+ arguments=[StringArgument("name", encoding=StringEncoding())],
80
+ )
81
+ service.add_command(my_command)
82
+ ```
83
+
84
+ The only obligatory arguments are **name** and a **subtype assignment**, like shown in the example. The connection to a service can also be achieved with base commands, where every base command must be unique to a service. For example:
85
+
86
+ ```python
87
+ base_cmd = RCCNCommand(
88
+ system=service,
89
+ assignments={"type": service.service_id},
90
+ name="base",
91
+ base="/PUS/pus-tc"
92
+ )
93
+ my_command = RCCNCommand(
94
+ base=base_cmd,
95
+ assignments={"subtype": 2},
96
+ name="StopProcess",
97
+ arguments=[StringArgument("name", encoding=StringEncoding())],
98
+ )
99
+ ```
69
100
 
70
101
  ## Output
71
102
  From the python configuration, the `main.rs`, `service.rs`, `command.rs`, `mod.rs`, `Cargo.toml` and `telemetry.rs` files are generated and are structured accordingly:
@@ -124,4 +155,4 @@ Snapshots of the .rs files in the export directory are stored in `/user/`. These
124
155
  app.keep_snapshots = 15 # Whatever value you want
125
156
  ```
126
157
 
127
- With the sequence from above, it becomes apperant that changes to the .rs file in the export directory always trump changes to the pymdb config. If for example, a main.rs file is generated for an application with and APID of 42, and this apid is changed in the main.rs file to 45, this change will persist after regenerating from the python config. Even if changes to the pymdb config where made after the changes to the main.rs file.
158
+ With the sequence from above, it becomes apperant that changes to the .rs file in the export directory always trump changes to the pymdb config. If for example, a main.rs file is generated for an application with and APID of 42, and this apid is changed in the main.rs file to 45, this change will persist after regenerating from the python config. Even if changes to the pymdb config where made after the changes to the main.rs file.
@@ -1,19 +1,19 @@
1
1
  rccn_gen/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
2
2
  rccn_gen/__init__.py,sha256=rBnqIw3uQk-uBbRh9VnungoTRSr2V0Bqos32xFZ44Eo,168
3
- rccn_gen/systems.py,sha256=D_AqspMI3VTyHbV3cmi6uizlZSKgmOHq21ASXoG-9vk,26863
4
- rccn_gen/utils.py,sha256=FHNatrkeyjF1qxyw3_o5cvNjIX9SXrEWRD9ugj-qtSo,7292
5
- rccn_gen/text_modules/cargo_toml/cargo.txt,sha256=2e6xKomkml6-onfQ0QHNqvKnhGLYMUl8zYgbykmm83Y,292
3
+ rccn_gen/systems.py,sha256=vGsCyVkwrsFgYqHDL_bPPv8FuX3pIHYUwfIRB5YUWrY,37414
4
+ rccn_gen/utils.py,sha256=VKnTC2hrgMyLdneksAifnqEgXO27zLQJ9x5igaF35rE,8269
5
+ rccn_gen/text_modules/cargo_toml/cargo.txt,sha256=AYjSo3WJE7lhOcJaiNgXP9Y-DXHDIFIt6p42rDTVNVE,427
6
6
  rccn_gen/text_modules/command/command.txt,sha256=8Y-uJilhFLoinftIbn7uKfia9LLMZno2LkoDJ-4Y-9M,345
7
- rccn_gen/text_modules/command/command_module_enum.txt,sha256=ApzRDQIs-BHJHtFA4ysfpYuWElGntXakkzZkgy57b74,94
7
+ rccn_gen/text_modules/command/command_module_enum.txt,sha256=35sBlAV_CzQw95Uf2dNynrYOxVD2tT2XWfEvS4Zx_KY,121
8
8
  rccn_gen/text_modules/command/command_module_struct.txt,sha256=FT7Ke0uOVxWYpGC_oj9zafr1ahrH-nf0nSxQje6kToY,22
9
- rccn_gen/text_modules/main/main.txt,sha256=UQ0oHbzH7H3G4mxfeiTh5UeQ-X4C0Fw7IrCyZNH2clQ,426
10
- rccn_gen/text_modules/main/service_module_import_service.txt,sha256=qPrU4fLs7Y65X5KJ868ut-izV5pHq7hU2nVRLxPnFCE,57
9
+ rccn_gen/text_modules/main/main.txt,sha256=t8lAYNCgxY8XHHGt8lVeZC103O1YLEY82UoTZ2U7S2I,446
10
+ rccn_gen/text_modules/main/service_module_import_service.txt,sha256=BYPvt2VrTV4yjt7SSAPYKzvQOBIqeDQsMzgzTQ8XmZY,62
11
11
  rccn_gen/text_modules/main/service_module_mod_service.txt,sha256=guvXFdV_-YezhTD_PWA-Z0tL8ReSZc0rh3RuWraJnQE,25
12
12
  rccn_gen/text_modules/main/service_module_register_service.txt,sha256=4EIsgaxDLh51u0WjXfy7Xgo-6UFTdb4Nh2O4J7uFjS0,115
13
13
  rccn_gen/text_modules/mod/mod.txt,sha256=BF8LablBE4ddutdl5m0prvpvLdBRejueVOujkyrLe7I,52
14
14
  rccn_gen/text_modules/service/command_module_match_cmd.txt,sha256=eVGo6ltuerG37rVxpXtL-JYuLyLW4c0i6NXb5g1_U-A,89
15
- rccn_gen/text_modules/service/service.txt,sha256=0KwwrfrjXuY4e3VNpCdTTzgW8bmYRQ21i-Lvhkz3hVA,691
15
+ rccn_gen/text_modules/service/service.txt,sha256=qTxoOD5i7wH4yFiDn13rOJW9hIZyACA8W3m6UABe22U,695
16
16
  rccn_gen/text_modules/telemetry/telemetry.txt,sha256=Re1d3BfpyXT_CEe7jJzLF3MARik0-J-K98K85iPOE40,193
17
- rccn_gen-1.0.3.dist-info/METADATA,sha256=Z4LaK07eqhpSDcv-4rRO2hIerglzl6rUubE2CvtnVOs,8260
18
- rccn_gen-1.0.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
- rccn_gen-1.0.3.dist-info/RECORD,,
17
+ rccn_gen-1.2.0.dist-info/METADATA,sha256=h-4mJGYkmnAxOFD5Pn4ART7ml2VjQPrVdENO2eIVj7s,8967
18
+ rccn_gen-1.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
+ rccn_gen-1.2.0.dist-info/RECORD,,