rccn-gen 1.0.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/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ from .systems import *
2
+ from .utils import *
3
+ from .telemetry import *
4
+
5
+ __version__ = "0.1.0"
6
+
7
+ __all__ = [
8
+ "Application",
9
+ "Service",
10
+ "RCCNCommand",
11
+ "System",
12
+ "RCCNContainer"
13
+ ]
File without changes
rccn_gen/service.py ADDED
File without changes
rccn_gen/systems.py ADDED
@@ -0,0 +1,481 @@
1
+ from yamcs.pymdb import System, Subsystem, Command
2
+ import shutil
3
+ import difflib
4
+ import datetime
5
+ from importlib.resources import files
6
+ from .utils import *
7
+ from .telemetry import *
8
+
9
+
10
+ class Application(Subsystem):
11
+ """
12
+ A PUS application.
13
+ """
14
+ def __init__(
15
+ self,
16
+ system: System,
17
+ name: str,
18
+ apid: int,
19
+ export_file_path = '.',
20
+ snapshot_file_path = './.rccn_snapshots',
21
+ diff_file_path = './.rccn_diffs',
22
+ snapshots = True,
23
+ *args,
24
+ **kwargs
25
+ ):
26
+ super().__init__(system=system, name=name, *args, **kwargs)
27
+
28
+ self.apid = apid
29
+ system._subsystems_by_name[name] = self
30
+ self.export_file_path = export_file_path
31
+ self.snapshot_file_path = snapshot_file_path
32
+ self.snapshot_generated_file_path = os.path.join(snapshot_file_path, 'auto_generated')
33
+ self.diff_file_path = diff_file_path
34
+ self.text_modules_path = files('pymdb_rust_generation').joinpath('text_modules')
35
+ print(self.text_modules_path)
36
+ self.text_modules_main_path = os.path.join(self.text_modules_path, 'main')
37
+ print(self.text_modules_main_path)
38
+ self.snapshots = snapshots
39
+ self.keep_snapshots = 10
40
+
41
+ def add_service(self, service):
42
+ if not isinstance(service, Service):
43
+ raise TypeError('Service '+service.name+' is not a RCCNCommand.')
44
+ service.add_to_application(self)
45
+
46
+ def file_paths(self):
47
+ paths = {
48
+ 'main': os.path.join(self.export_file_path, 'rccn_usr_'+to_snake_case(self.name), 'src', 'main.rs'),
49
+ 'main_generated_snapshot': os.path.join(self.snapshot_file_path, 'generated', 'rccn_usr_'+to_snake_case(self.name), 'src', 'main.rs'),
50
+ 'main_user_snapshot': os.path.join(self.user_snapshot_path(), 'rccn_usr_'+to_snake_case(self.name), 'src', 'main.rs'),
51
+ 'main_diff': os.path.join(self.diff_file_path, 'rccn_usr_'+to_snake_case(self.name), 'src', 'main.diff'),
52
+ 'main_template': os.path.join(self.text_modules_main_path, 'main.txt'),
53
+ 'cargo_toml': os.path.join(self.export_file_path, 'rccn_usr_'+to_snake_case(self.name), 'Cargo.toml'),
54
+ 'cargo_toml_template': os.path.join(self.text_modules_path, 'cargo_toml', 'cargo.txt'),
55
+ }
56
+ return paths
57
+
58
+ def user_snapshot_path(self):
59
+ return os.path.join(self.snapshot_file_path, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
60
+
61
+ def services(self):
62
+ return [subsystem for subsystem in self.subsystems if isinstance(subsystem, Service)]
63
+
64
+ def create_rccn_directories(self):
65
+ app_src_dir = os.path.join(self.export_file_path, 'rccn_usr_'+to_snake_case(self.name), 'src')
66
+ if not os.path.exists(app_src_dir):
67
+ os.makedirs(app_src_dir)
68
+ for service in self.services():
69
+ service_dir = os.path.join(app_src_dir, to_snake_case(service.name))
70
+ if not os.path.exists(service_dir):
71
+ os.makedirs(service_dir)
72
+
73
+ def generate_rccn_main_file(self):
74
+ # Create main.diff file
75
+ if os.path.exists(self.file_paths()['main']) and os.path.exists(self.file_paths()['main_generated_snapshot']):
76
+ os.makedirs(os.path.dirname(self.file_paths()['main_diff']), exist_ok=True)
77
+ self.generate_diff_file('main', 'main_generated_snapshot', 'main_diff')
78
+ # Create snapshot of main.rs with user changes if instructed
79
+ if self.snapshots and os.path.exists(self.file_paths()['main']):
80
+ self.generate_snapshot('main', 'main_user_snapshot')
81
+ # Generate main.rs file
82
+ with open(self.file_paths()['main_template'], 'r') as file:
83
+ main_template_text = file.read()
84
+ with open(self.file_paths()['main'], 'w') as file:
85
+ new_main_text = self.find_and_replace_keywords(main_template_text)
86
+ file.write("".join(new_main_text))
87
+ # Create snapshot of newly generated main.rs if instructed
88
+ if self.snapshots:
89
+ self.generate_snapshot('main', 'main_generated_snapshot')
90
+ # Rebase main.diff on main.rs if instructed
91
+ if self.rebase_changes and os.path.exists(self.file_paths()['main_diff']):
92
+ os.system('patch '+self.file_paths()['main']+' < '+self.file_paths()['main_diff'])
93
+
94
+ def find_and_replace_keywords(self, text):
95
+ # Call keyword replacement for all associated services (Later, there needs to be checking to account for user changes to the generated files)
96
+ for service in self.services():
97
+ text = service.find_and_replace_keywords(text, self.text_modules_main_path)
98
+ # Find and replace service variable keywords
99
+ var_translation = {
100
+ '<<VAR_APID>>':str(self.apid),
101
+ '<<VAR_APP_NAME_SCASE>>':to_snake_case(self.name),
102
+ }
103
+ var_keywords = get_var_keywords(text)
104
+ for var_keyword in var_keywords:
105
+ if var_keyword in var_translation.keys():
106
+ text = text.replace(var_keyword, var_translation[var_keyword])
107
+ else:
108
+ raise KeyError('Keyword '+var_keyword+' is not in translation dictionary.')
109
+ text = delete_all_keywords(text)
110
+ return text
111
+
112
+ def generate_rccn_code(self, rebase_changes=True, check=True, export_file_path='.'):
113
+ self.export_file_path = export_file_path
114
+ if check:
115
+ self.check_user_input()
116
+ self.rebase_changes = rebase_changes
117
+ self.create_rccn_directories()
118
+ self.generate_rccn_main_file()
119
+ self.generate_rccn_main_file()
120
+ if not os.path.exists(self.file_paths()['cargo_toml']):
121
+ self.generate_cargo_toml_file()
122
+ for service in self.services():
123
+ service.export_file_path = self.export_file_path
124
+ service.generate_rccn_service_file()
125
+ if not os.path.exists(service.file_paths()['mod']):
126
+ service.generate_mod_file()
127
+ service.generate_telemetry_file()
128
+ service.generate_rccn_command_file(os.path.join(self.export_file_path, 'rccn_usr_'+to_snake_case(self.name), 'src'), os.path.join(self.text_modules_path, 'command'))
129
+ self.delete_old_snapshots()
130
+
131
+ def generate_snapshot(self, current_file_reference, snapshot_file_reference):
132
+ os.makedirs(os.path.dirname(self.file_paths()[snapshot_file_reference]), exist_ok=True)
133
+ shutil.copyfile(self.file_paths()[current_file_reference], self.file_paths()[snapshot_file_reference])
134
+
135
+ def generate_diff_file(self, current_file_reference, snapshot_file_reference, diff_file_reference):
136
+ with open(self.file_paths()[current_file_reference], 'r') as current_file:
137
+ current_text = current_file.readlines()
138
+ with open(self.file_paths()[snapshot_file_reference], 'r') as snapshot_file:
139
+ snapshot_text = snapshot_file.readlines()
140
+ diff = difflib.unified_diff(snapshot_text, current_text, fromfile='snapshot', tofile='current')
141
+ diff_text = ''.join(diff)
142
+ current_date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
143
+ with open(self.file_paths()[diff_file_reference], 'w') as diff_file:
144
+ diff_file.write(diff_text)
145
+
146
+ def delete_old_snapshots(self):
147
+ if os.path.exists(os.path.join(self.snapshot_file_path, 'user')):
148
+ user_snapshots_path = os.path.join(self.snapshot_file_path, 'user')
149
+ 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))]
150
+ snapshots.sort(key=os.path.getctime)
151
+ while len(snapshots) > self.keep_snapshots:
152
+ shutil.rmtree(snapshots.pop(0))
153
+
154
+ def check_user_input(self):
155
+ # Check if all services have unique names
156
+ service_names = [service.name for service in self.services()]
157
+ if len(service_names) != len(set(service_names)):
158
+ raise ValueError('All services must have unique names.')
159
+
160
+ # Check if all services have unique service_ids
161
+ service_ids = [service.service_id for service in self.services()]
162
+ if len(service_ids) != len(set(service_ids)):
163
+ raise ValueError('All services must have unique service_ids.')
164
+
165
+ # Check if all commands have unique names
166
+ command_names = []
167
+ for service in self.services():
168
+ command_names += [command.name for command in service.rccn_commands()]
169
+ if len(command_names) != len(set(command_names)):
170
+ raise ValueError('All commands must have unique names.')
171
+
172
+ # Check if all commands have unique subtypes
173
+ command_subtypes = []
174
+ for service in self.services():
175
+ command_subtypes += [command.assignments['subtype'] for command in service.rccn_commands()]
176
+ if len(command_subtypes) != len(set(command_subtypes)):
177
+ raise ValueError('All commands must have unique subtypes.')
178
+
179
+ def generate_cargo_toml_file(self):
180
+ with open(self.file_paths()['cargo_toml_template'], 'r') as file:
181
+ cargo_toml_template_text = file.read()
182
+ with open(self.file_paths()['cargo_toml'], 'w') as file:
183
+ file.write(cargo_toml_template_text)
184
+
185
+
186
+ class Service(Subsystem):
187
+ def __init__(
188
+ self,
189
+ name: str,
190
+ service_id: int = None,
191
+ *args,
192
+ **kwargs
193
+ ):
194
+ self.init_args = args
195
+ self.init_kwargs = kwargs
196
+ self.init_args = args
197
+ self.init_kwargs = kwargs
198
+ self.name = name
199
+ self.service_id = service_id
200
+ self.text_modules_path = files('pymdb_rust_generation').joinpath('text_modules')
201
+ self.text_modules_service_path = os.path.join(self.text_modules_path, 'service')
202
+ self.text_modules_command_path = os.path.join(self.text_modules_path, 'command')
203
+ self.text_modules_telemetry_path = os.path.join(self.text_modules_path, 'telemetry')
204
+ if 'system' in kwargs and isinstance(kwargs['system'], Application):
205
+ self.add_to_application(kwargs['system'])
206
+
207
+ def add_to_application(self, application):
208
+ if 'system' in self.init_kwargs and isinstance(self.init_kwargs['system'], Application):
209
+ super().__init__(
210
+ name=self.name,
211
+ *self.init_args, **self.init_kwargs
212
+ )
213
+ else:
214
+ super().__init__(
215
+ system=application,
216
+ name=self.name,
217
+ *self.init_args, **self.init_kwargs
218
+ )
219
+ self.export_file_path = self.system.export_file_path
220
+ self.snapshot_file_path = self.system.snapshot_file_path
221
+ self.snapshots = self.system.snapshots
222
+ self.diff_file_path = self.system.diff_file_path
223
+
224
+ def add_container(self, container):
225
+ if not isinstance(container, RCCNContainer):
226
+ raise TypeError('Container '+container.name+' is not a RCCNContainer.')
227
+ container.add_to_service(self)
228
+
229
+ def add_command(self, command):
230
+ if not isinstance(command, RCCNCommand):
231
+ raise TypeError('Command '+command.name+' is not a RCCNCommand.')
232
+ command.add_to_service(self)
233
+
234
+ def file_paths(self):
235
+ paths = {
236
+ 'service': os.path.join(self.export_file_path, 'rccn_usr_'+to_snake_case(self.system.name), 'src', to_snake_case(self.name), 'service.rs'),
237
+ 'service_generated_snapshot': os.path.join(self.snapshot_file_path, 'generated', 'rccn_usr_'+to_snake_case(self.system.name), 'src', to_snake_case(self.name), 'service.rs'),
238
+ 'service_user_snapshot': os.path.join(self.snapshot_file_path, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 'rccn_usr_'+to_snake_case(self.system.name), 'src', to_snake_case(self.name), 'service.rs'),
239
+ 'service_diff': os.path.join(self.diff_file_path, 'rccn_usr_'+to_snake_case(self.system.name), 'src', to_snake_case(self.name), 'service.diff'),
240
+ 'service_template': os.path.join(self.text_modules_service_path, 'service.txt'),
241
+ 'command': os.path.join(self.export_file_path, 'rccn_usr_'+to_snake_case(self.system.name), 'src', to_snake_case(self.name), 'command.rs'),
242
+ 'command_generated_snapshot': os.path.join(self.snapshot_file_path, 'generated', 'rccn_usr_'+to_snake_case(self.system.name), 'src', to_snake_case(self.name), 'command.rs'),
243
+ 'command_user_snapshot': os.path.join(self.snapshot_file_path, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 'rccn_usr_'+to_snake_case(self.system.name), 'src', to_snake_case(self.name), 'command.rs'),
244
+ 'command_diff': os.path.join(self.diff_file_path, 'rccn_usr_'+to_snake_case(self.system.name), 'src', to_snake_case(self.name), 'command.diff'),
245
+ 'command_template': os.path.join(self.text_modules_command_path, 'command.txt'),
246
+ 'mod': os.path.join(self.export_file_path, 'rccn_usr_'+to_snake_case(self.system.name), 'src', to_snake_case(self.name), 'mod.rs'),
247
+ 'mod_template': os.path.join(self.text_modules_path, 'mod', 'mod.txt'),
248
+ 'telemetry': os.path.join(self.export_file_path, 'rccn_usr_'+to_snake_case(self.system.name), 'src', to_snake_case(self.name), 'telemetry.rs'),
249
+ 'telemetry_generated_snapshot': os.path.join(self.snapshot_file_path, 'generated', 'rccn_usr_'+to_snake_case(self.system.name), 'src', to_snake_case(self.name), 'telemetry.rs'),
250
+ 'telemetry_user_snapshot': os.path.join(self.snapshot_file_path, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 'rccn_usr_'+to_snake_case(self.system.name), 'src', to_snake_case(self.name), 'command.rs'),
251
+ 'telemetry_diff': os.path.join(self.diff_file_path, 'rccn_usr_'+to_snake_case(self.system.name), 'src', to_snake_case(self.name), 'telemetry.diff'),
252
+ 'telemetry_template': os.path.join(self.text_modules_telemetry_path, 'telemetry.txt'),
253
+ }
254
+ return paths
255
+
256
+ def rccn_commands(self):
257
+ return [command for command in self.commands if isinstance(command, RCCNCommand)]
258
+
259
+ def find_and_replace_keywords(self, text, text_modules_path):
260
+ # Find and replace service module keywords
261
+ service_module_keywords = get_service_module_keywords(text)
262
+ for service_module_keyword in service_module_keywords:
263
+ service_module_file_name = service_module_keyword.replace('>','').replace('<', '').lower() + '.txt'
264
+ service_module_path = os.path.join(text_modules_path, service_module_file_name)
265
+ if not os.path.exists(service_module_path):
266
+ raise FileExistsError('Specified keyword '+service_module_keyword+' does not correspond to a text file.')
267
+
268
+ with open(service_module_path, 'r') as file:
269
+ module_text = file.read()
270
+ replacement_text = (self.find_and_replace_keywords(module_text, text_modules_path) + '\n')
271
+ text = insert_before_with_indentation(text, service_module_keyword, replacement_text)
272
+
273
+ # Call keyword replacement for all associated commands (Later, there needs to be checking to account for user changes to the generated files)
274
+ if len(self.rccn_commands()) == 0:
275
+ print('Service '+self.name+' does not have any commands associated with it.')
276
+ for command in self.rccn_commands():
277
+ text = command.find_and_replace_keywords(text, text_modules_path)
278
+
279
+ # Find and replace service variable keywords
280
+ var_keywords = get_var_keywords(text)
281
+ service_var_translation = {
282
+ '<<VAR_SERVICE_NAME>>': self.name,
283
+ '<<VAR_SERVICE_ID>>': str(self.service_id),
284
+ '<<VAR_SERVICE_NAME_UCASE>>': to_upper_camel_case(self.name),
285
+ '<<VAR_SERVICE_TELEMETRY>>': self.generate_rust_telemetry_definition(),
286
+ }
287
+ for var_keyword in var_keywords:
288
+ if var_keyword in service_var_translation.keys():
289
+ text = replace_with_indentation(text, var_keyword, service_var_translation[var_keyword])
290
+
291
+ # Delete all command module keywords
292
+ text = delete_all_command_module_keywords(text)
293
+
294
+ return text
295
+
296
+ def generate_rccn_service_file(self):
297
+ # Create service.diff file
298
+ if os.path.exists(self.file_paths()['service']) and os.path.exists(self.file_paths()['service_generated_snapshot']):
299
+ os.makedirs(os.path.dirname(self.file_paths()['service_diff']), exist_ok=True)
300
+ self.generate_diff_file('service', 'service_generated_snapshot', 'service_diff')
301
+ # Create snapshot of service.rs with user changes if instructed
302
+ if self.snapshots and os.path.exists(self.file_paths()['service']):
303
+ self.generate_snapshot('service', 'service_user_snapshot')
304
+ # Generate service.rs file
305
+ with open(self.file_paths()['service_template'], 'r') as file:
306
+ service_template_file_text = file.read()
307
+ with open(self.file_paths()['service'], 'w') as file:
308
+ file.write(self.find_and_replace_keywords(service_template_file_text, self.text_modules_service_path))
309
+ # Create snapshot of service.rs if instructed
310
+ if self.snapshots:
311
+ self.generate_snapshot('service', 'service_generated_snapshot')
312
+ # Rebase main.diff on main.rs if instructed
313
+ if self.system.rebase_changes and os.path.exists(self.file_paths()['service_diff']):
314
+ os.system('patch '+self.file_paths()['service']+' < '+self.file_paths()['service_diff'])
315
+
316
+ def generate_rccn_command_file(self, export_file_dir='.', text_modules_path='./text_modules/command'):
317
+ # Create command.diff file
318
+ if os.path.exists(self.file_paths()['command']) and os.path.exists(self.file_paths()['command_generated_snapshot']):
319
+ os.makedirs(os.path.dirname(self.file_paths()['command_diff']), exist_ok=True)
320
+ self.generate_diff_file('command', 'command_generated_snapshot', 'command_diff')
321
+ # Create snapshot of command.rs with user changes if instructed
322
+ if self.snapshots and os.path.exists(self.file_paths()['command']):
323
+ self.generate_snapshot('command', 'command_user_snapshot')
324
+ # Generate command.rs file
325
+ if len(self.rccn_commands()) == 0:
326
+ print('Service '+self.name+' has no implemented commands. Generation of command.rs file will be skipped.')
327
+ return
328
+ command_file_path = self.file_paths()['command_template']
329
+ with open(command_file_path, 'r') as file:
330
+ command_file_text = file.read()
331
+ command_export_file_path = os.path.join(export_file_dir, to_snake_case(self.name), 'command.rs')
332
+ with open(command_export_file_path, 'w') as file:
333
+ file.write(self.find_and_replace_keywords(command_file_text, text_modules_path))
334
+ # Create snapshot of command.rs if instructed
335
+ if self.snapshots:
336
+ self.generate_snapshot('command', 'command_generated_snapshot')
337
+ # Rebase command.diff on command.rs if instructed
338
+ if self.system.rebase_changes and os.path.exists(self.file_paths()['command_diff']):
339
+ os.system('patch '+self.file_paths()['command']+' < '+self.file_paths()['command_diff'])
340
+
341
+ def generate_snapshot(self, current_file_reference, snapshot_file_reference):
342
+ os.makedirs(os.path.dirname(self.file_paths()[snapshot_file_reference]), exist_ok=True)
343
+ shutil.copyfile(self.file_paths()[current_file_reference], self.file_paths()[snapshot_file_reference])
344
+
345
+ def generate_diff_file(self, current_file_reference, snapshot_file_reference, diff_file_reference):
346
+ with open(self.file_paths()[current_file_reference], 'r') as current_file:
347
+ current_text = current_file.readlines()
348
+ with open(self.file_paths()[snapshot_file_reference], 'r') as snapshot_file:
349
+ snapshot_text = snapshot_file.readlines()
350
+ diff = difflib.unified_diff(snapshot_text, current_text, fromfile='snapshot', tofile='current')
351
+ diff_text = ''.join(diff)
352
+ with open(self.file_paths()[diff_file_reference], 'w') as diff_file:
353
+ diff_file.write(diff_text)
354
+
355
+ def generate_mod_file(self):
356
+ with open(self.file_paths()['mod_template'], 'r') as file:
357
+ mod_template_text = file.read()
358
+ with open(self.file_paths()['mod'], 'w') as file:
359
+ file.write(mod_template_text)
360
+
361
+ def generate_telemetry_file(self):
362
+ # Create telemetry.diff file
363
+ if os.path.exists(self.file_paths()['telemetry']) and os.path.exists(self.file_paths()['telemetry_generated_snapshot']):
364
+ os.makedirs(os.path.dirname(self.file_paths()['telemetry_diff']), exist_ok=True)
365
+ self.generate_diff_file('telemetry', 'telemetry_generated_snapshot', 'telemetry_diff')
366
+ # Create snapshot of telemetry.rs with user changes if instructed
367
+ if self.snapshots and os.path.exists(self.file_paths()['telemetry']):
368
+ self.generate_snapshot('telemetry', 'telemetry_user_snapshot')
369
+ # Generate telemetry.rs file
370
+ with open(self.file_paths()['telemetry_template'], 'r') as file:
371
+ telemetry_template_file_text = file.read()
372
+ with open(self.file_paths()['telemetry'], 'w') as file:
373
+ file.write(self.find_and_replace_keywords(telemetry_template_file_text, self.text_modules_telemetry_path))
374
+ # Create snapshot of telemetry.rs if instructed
375
+ if self.snapshots:
376
+ self.generate_snapshot('telemetry', 'telemetry_generated_snapshot')
377
+ # Rebase main.diff on main.rs if instructed
378
+ if self.system.rebase_changes and os.path.exists(self.file_paths()['telemetry_diff']):
379
+ os.system('patch '+self.file_paths()['telemetry']+' < '+self.file_paths()['telemetry_diff'])
380
+
381
+ def generate_rust_telemetry_definition(self):
382
+ telemetry_definition_text = ''
383
+ for container in self.containers:
384
+ container.__class__ = RCCNContainer
385
+ telemetry_definition_text += container.generate_rccn_telemetry()
386
+ return telemetry_definition_text
387
+
388
+
389
+ class RCCNCommand(Command):
390
+ def __init__(self, *args, **kwargs):
391
+ self.init_args = args
392
+ self.init_kwargs = kwargs
393
+ if 'system' in kwargs and isinstance(kwargs['system'], Service):
394
+ self.add_to_service(kwargs['system'])
395
+
396
+ def add_to_service(self, service):
397
+ if not any(command.name == 'base' for command in service.commands):
398
+ base_command = Command(
399
+ system=service,
400
+ name='base',
401
+ abstract=True,
402
+ base='/PUS/pus-tc',
403
+ assignments={'type': service.service_id}
404
+ )
405
+ self.init_kwargs['base'] = base_command
406
+ if 'system' in self.init_kwargs and isinstance(self.init_kwargs['system'], Service):
407
+ super().__init__(*self.init_args, **self.init_kwargs)
408
+ else:
409
+ super().__init__(system=service, *self.init_args, **self.init_kwargs)
410
+ self.assignments['apid'] = self.system.system.apid
411
+
412
+
413
+ def find_and_replace_keywords(self, text, text_modules_path):
414
+ # Find and replace command module keywords
415
+ command_module_keywords = get_command_module_keywords(text)
416
+ for command_module_keyword in command_module_keywords:
417
+ command_module_file_name = command_module_keyword.replace('>','').replace('<', '').lower() + '.txt'
418
+ command_module_path = os.path.join(text_modules_path, command_module_file_name)
419
+ if not os.path.exists(command_module_path):
420
+ raise FileExistsError('Specified keyword '+command_module_keyword+' does not correspond to a text file.')
421
+
422
+ with open(command_module_path, 'r') as file:
423
+ module_text = file.read()
424
+ replacement_text = (self.find_and_replace_keywords(module_text, text_modules_path) + '\n')
425
+ text = insert_before_with_indentation(text, command_module_keyword, replacement_text)
426
+
427
+ # Find and replace command variable keywords
428
+ command_var_keywords = get_var_keywords(text)
429
+ command_var_translation = {
430
+ '<<VAR_COMMAND_NAME_UCASE>>': to_upper_camel_case(self.name),
431
+ '<<VAR_COMMAND_NAME>>': self.name,
432
+ '<<VAR_COMMAND_SUBTYPE>>': str(self.assignments['subtype']),
433
+ '<<VAR_COMMAND_STRUCT>>': self.struct_definition()
434
+ }
435
+ for command_var_keyword in command_var_keywords:
436
+ if command_var_keyword in command_var_translation.keys():
437
+ text = replace_with_indentation(text, command_var_keyword, command_var_translation[command_var_keyword])
438
+ return text
439
+
440
+ def check_user_input(self):
441
+ if self.assignments['subtype'] == None and self.name != 'base':
442
+ raise ValueError('Command '+self.name+' does not have a subtype assigned to it.')
443
+
444
+ def user_snapshot_path(self):
445
+ return os.path.join(self.snapshot_file_path, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
446
+
447
+ def struct_definition(self):
448
+ if len(self.arguments) == 0:
449
+ return ''
450
+ struct_definition_text = "#[derive(BitStruct, Debug, PartialEq)]\npub struct "+to_upper_camel_case(self.name)+" {\n"
451
+ for argument in self.arguments:
452
+ struct_definition_text += rust_type_definition(argument)[0]
453
+ struct_definition_text += "}\n"
454
+ for argument in self.arguments:
455
+ struct_definition_text += rust_type_definition(argument)[1]
456
+ return struct_definition_text
457
+
458
+ class RCCNContainer(Container):
459
+ def __init__(self, base="/PUS/pus-tm", *args, **kwargs):
460
+ self.base = base
461
+ self.init_args = args
462
+ self.init_kwargs = kwargs
463
+ if 'system' in kwargs and isinstance(kwargs['system'], Service):
464
+ self.add_to_service(kwargs['system'])
465
+
466
+ def add_to_service(self, service):
467
+ if 'system' in self.init_kwargs and isinstance(self.init_kwargs['system'], Service):
468
+ super().__init__(*self.init_args, **self.init_kwargs)
469
+ else:
470
+ super().__init__(system=service, *self.init_args, **self.init_kwargs)
471
+
472
+ def generate_rccn_telemetry(self):
473
+ rccn_telemetry_text = "pub struct " + self.name + " {\n"
474
+ for parameter_entry in self.entries:
475
+ rccn_telemetry_text += rust_type_definition(parameter_entry.parameter)[0]
476
+ #rccn_telemetry_text += parameter_entry.generate_rccn_telemetry()[0]
477
+ rccn_telemetry_text += "}\n"
478
+ for parameter_entry in self.entries:
479
+ rccn_telemetry_text += rust_type_definition(parameter_entry.parameter)[1]
480
+
481
+ return rccn_telemetry_text
rccn_gen/telemetry.py ADDED
@@ -0,0 +1,83 @@
1
+ from yamcs.pymdb import Container, ParameterEntry, AggregateParameter, ArrayParameter, BinaryParameter, StringParameter, BooleanParameter, FloatParameter, IntegerParameter, StringMember, EnumeratedMember
2
+ from .utils import *
3
+
4
+
5
+ def generate_rccn_parameter_telemetry(parameter):
6
+ sc_parameter_name = to_snake_case(parameter.name)
7
+
8
+ if isinstance(parameter, ArrayParameter):
9
+ struct_name = to_upper_camel_case(parameter.name)
10
+ telemetry = ["\tpub "+sc_parameter_name+": Vec<"+struct_name+">,\n"]
11
+ telemetry.append("pub struct "+struct_name+" {\n")
12
+ for member in parameter.data_type.members:
13
+ telemetry[1] += generate_rccn_member_telementry(member)[0]
14
+ telemetry[1] += "}\n"
15
+ for member in parameter.data_type.members:
16
+ telemetry[1] += generate_rccn_member_telementry(member)[1]
17
+
18
+ elif isinstance(parameter, BooleanParameter):
19
+ telemetry = ["\t#[bits(1)]\n\tpub "+sc_parameter_name+": bool,\n"]
20
+ telemetry.append("")
21
+
22
+ elif isinstance(parameter, IntegerParameter):
23
+ if parameter.encoding is None or parameter.encoding.bits is None:
24
+ raw_bit_number_str = '8'
25
+ print("Warning: No encoding for parameter "+parameter.name+" found. Using 8 as default for raw bit number.")
26
+ else:
27
+ raw_bit_number = parameter.encoding.bits
28
+ raw_bit_number_str = str(raw_bit_number)
29
+ eng_bit_number = engineering_bit_number(raw_bit_number)
30
+ eng_bit_number_str = str(eng_bit_number)
31
+ telemetry = ["\t#[bits("+raw_bit_number_str+")]\n"]
32
+ if parameter.signed:
33
+ telemetry[0] += ("\tpub "+sc_parameter_name+": i"+eng_bit_number_str+",\n")
34
+ else:
35
+ telemetry += ("\tpub "+sc_parameter_name+": u"+eng_bit_number_str+",\n")
36
+ telemetry.append("")
37
+
38
+ elif isinstance(parameter, StringParameter):
39
+ telemetry = ["#[null_terminated]\npub "+sc_parameter_name+": String,\n"]
40
+ telemetry.append("")
41
+
42
+ else:
43
+ telemetry = ["\t// Please implement datatype "+type(parameter).__name__+" here.\n", ""]
44
+ return telemetry
45
+
46
+ def generate_rccn_member_telementry(member):
47
+ sc_member_name = to_snake_case(member.name)
48
+
49
+ if isinstance(member, StringMember):
50
+ member_telemetry = ["#[null_terminated]\n\tpub "+member.name+": String,\n"]
51
+ member_telemetry.append("")
52
+
53
+ elif isinstance(member, EnumeratedMember):
54
+ member_telemetry = ["\tpub "+member.name+": "+to_upper_camel_case(member.name)+",\n"]
55
+ member_telemetry.append("pub enum "+to_upper_camel_case(member.name)+" {\n")
56
+ for choice in member.choices:
57
+ member_telemetry[1] += "\t"+str(choice[1])+" = "+str(choice[0])+",\n"
58
+ member_telemetry[1] += "}\n"
59
+
60
+ elif isinstance(member, BooleanParameter):
61
+ telemetry = ["pub "+sc_member_name+": bool,\n"]
62
+ telemetry.append("")
63
+
64
+ elif isinstance(member, IntegerParameter):
65
+ if member.encoding is None or member.encoding.bits is None:
66
+ raw_bit_number = 8
67
+ print("Warning: No encoding for member "+member.name+" found. Using 8 as default for raw bit number.")
68
+ else:
69
+ raw_bit_number = member.encoding.bits
70
+ raw_bit_number_str = str(raw_bit_number)
71
+ eng_bit_number = engineering_bit_number(raw_bit_number)
72
+ eng_bit_number_str = str(eng_bit_number)
73
+ telemetry = ["\t#[bits("+raw_bit_number_str+")]\n"]
74
+ if member.signed:
75
+ telemetry[0] += ("\tpub "+sc_member_name+": i"+eng_bit_number_str+",\n")
76
+ else:
77
+ telemetry += ("\tpub "+sc_member_name+": u"+eng_bit_number_str+",\n")
78
+ telemetry.append("")
79
+
80
+ else:
81
+ member_telemetry[0] += "\t// Please implement datatype "+type(member).__name__+" here.\n"
82
+ member_telemetry.append("")
83
+ return member_telemetry
@@ -0,0 +1,14 @@
1
+ [package]
2
+ name = "rccn_usr_example_app"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+
6
+ [dependencies]
7
+ anyhow = "1.0.91"
8
+ binary_serde = "1.0.24"
9
+ crossbeam-channel = "0.5.13"
10
+ futures = "0.3.31"
11
+ rccn_usr = { version = "0.1.0", path = "../rccn_usr" }
12
+ satrs = "0.2.1"
13
+ spacepackets = "0.12.0"
14
+ tokio = "1.41.1"
@@ -0,0 +1,11 @@
1
+ use rccn_usr::{bitbuffer::{BitReader, BitStruct}, service::{CommandParseResult, ServiceCommand}};
2
+ use satrs::spacepackets::ecss::{tc::PusTcReader, PusPacket};
3
+ use rccn_usr_bitstruct_derive::BitStruct;
4
+ use rccn_usr_pus_macros::ServiceCommand;
5
+
6
+ #[derive(ServiceCommand)]
7
+ pub enum Command {
8
+ <<COMMAND_MODULE_ENUM>>
9
+ }
10
+
11
+ <<COMMAND_MODULE_STRUCT>>
@@ -0,0 +1,2 @@
1
+ #[subservice(<<VAR_COMMAND_SUBTYPE>>)]
2
+ <<VAR_COMMAND_NAME_UCASE>>(<<VAR_COMMAND_NAME>>::Args),
@@ -0,0 +1 @@
1
+ <<VAR_COMMAND_STRUCT>>
@@ -0,0 +1,24 @@
1
+ <<SERVICE_MODULE_IMPORT_SERVICE>>
2
+ use rccn_usr::pus::app::PusApp;
3
+ use rccn_usr::zenoh::key_expr::OwnedKeyExpr;
4
+
5
+ <<SERVICE_MODULE_MOD_SERVICE>>
6
+
7
+ const APID: u16 = <<VAR_APID>>;
8
+
9
+ fn main() -> Result<()> {
10
+ env_logger::init();
11
+ let mut app = PusApp::new(APID);
12
+
13
+ app
14
+ .add_tc_tm_channel(
15
+ OwnedKeyExpr::new("vc/bus_realtime/rx").unwrap(),
16
+ OwnedKeyExpr::new("vc/bus_realtime/tx").unwrap(),
17
+ )
18
+ .unwrap();
19
+
20
+ <<SERVICE_MODULE_REGISTER_SERVICE>>
21
+
22
+ app.run();
23
+ Ok(())
24
+ }
@@ -0,0 +1 @@
1
+ use picture_service::service::<<VAR_SERVICE_NAME_UCASE>>;
@@ -0,0 +1 @@
1
+ mod <<VAR_SERVICE_NAME>>;
@@ -0,0 +1,2 @@
1
+ let service<<VAR_SERVICE_ID>> = <<VAR_SERVICE_NAME_UCASE>>::new();
2
+ app.register_service(service<<VAR_SERVICE_ID>>);
@@ -0,0 +1,3 @@
1
+ pub mod command;
2
+ pub mod service;
3
+ pub mod telemetry;