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/LICENSE +674 -0
- rccn_gen/__init__.py +13 -0
- rccn_gen/application.py +0 -0
- rccn_gen/service.py +0 -0
- rccn_gen/systems.py +481 -0
- rccn_gen/telemetry.py +83 -0
- rccn_gen/text_modules/cargo_toml/cargo.txt +14 -0
- rccn_gen/text_modules/command/command.txt +11 -0
- rccn_gen/text_modules/command/command_module_enum.txt +2 -0
- rccn_gen/text_modules/command/command_module_struct.txt +1 -0
- rccn_gen/text_modules/main/main.txt +24 -0
- rccn_gen/text_modules/main/service_module_import_service.txt +1 -0
- rccn_gen/text_modules/main/service_module_mod_service.txt +1 -0
- rccn_gen/text_modules/main/service_module_register_service.txt +2 -0
- rccn_gen/text_modules/mod/mod.txt +3 -0
- rccn_gen/text_modules/service/command_module_match_cmd.txt +3 -0
- rccn_gen/text_modules/service/service.txt +34 -0
- rccn_gen/text_modules/telemetry/telemetry.txt +6 -0
- rccn_gen/utils.py +159 -0
- rccn_gen-1.0.0.dist-info/METADATA +123 -0
- rccn_gen-1.0.0.dist-info/RECORD +22 -0
- rccn_gen-1.0.0.dist-info/WHEEL +4 -0
rccn_gen/__init__.py
ADDED
rccn_gen/application.py
ADDED
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 @@
|
|
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>>;
|