rccn-gen 1.3.2__tar.gz → 1.3.4__tar.gz

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.
Files changed (25) hide show
  1. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/.gitignore +2 -1
  2. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/CHANGELOG.md +20 -1
  3. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/PKG-INFO +3 -3
  4. rccn_gen-1.3.4/examples/generate-user-example.py +263 -0
  5. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/pyproject.toml +3 -2
  6. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/src/rccn_gen/systems.py +89 -36
  7. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/src/rccn_gen/text_modules/cargo_toml/cargo.txt +6 -3
  8. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/src/rccn_gen/text_modules/command/command.txt +1 -0
  9. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/src/rccn_gen/text_modules/command/command_module_enum.txt +1 -1
  10. rccn_gen-1.3.4/src/rccn_gen/text_modules/service/command_module_match_cmd.txt +5 -0
  11. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/src/rccn_gen/text_modules/service/service.txt +2 -1
  12. rccn_gen-1.3.2/rccn_usr_bi_x1_cntrl_app/Cargo.toml +0 -17
  13. rccn_gen-1.3.2/src/rccn_gen/text_modules/service/command_module_match_cmd.txt +0 -3
  14. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/.gitlab-ci.yml +0 -0
  15. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/README.md +0 -0
  16. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/src/rccn_gen/LICENSE +0 -0
  17. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/src/rccn_gen/__init__.py +0 -0
  18. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/src/rccn_gen/text_modules/command/command_module_struct.txt +0 -0
  19. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/src/rccn_gen/text_modules/main/main.txt +0 -0
  20. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/src/rccn_gen/text_modules/main/service_module_import_service.txt +0 -0
  21. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/src/rccn_gen/text_modules/main/service_module_mod_service.txt +0 -0
  22. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/src/rccn_gen/text_modules/main/service_module_register_service.txt +0 -0
  23. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/src/rccn_gen/text_modules/mod/mod.txt +0 -0
  24. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/src/rccn_gen/text_modules/telemetry/telemetry.txt +0 -0
  25. {rccn_gen-1.3.2 → rccn_gen-1.3.4}/src/rccn_gen/utils.py +0 -0
@@ -3,7 +3,8 @@
3
3
  *.ipynb
4
4
  *.pyc
5
5
  .venv/
6
- examples/
6
+ examples/*
7
+ !examples/generate-user-example.py
7
8
  tests/
8
9
  *.diff
9
10
  *.rs
@@ -1,7 +1,26 @@
1
1
  # CHANGE LOG
2
2
 
3
+ ### [1.3.4] - 2025-07-16
4
+ - Fixed Rust edition in autogenerated Cargo.toml file
5
+ - Removed 'rccn_usr'-prefix from generated user workspace
6
+ - Removed '(args)' from autogenerated commands if they don't take any arguments
7
+ - Fixed handler function call for commands that request telemetry
8
+ - RCCNCommand now has a 'specify_target_telemetry' parameter
9
+ - used if a RCCNCommand should request a RCCNContainer (Telemetry)
10
+ - specifies name of the target RCCNContainer to request
11
+ - generated files and directories no longer hold the 'rccn_usr_' prefix
12
+ - fixed major bug of receiving multiple telemetries of different apps when apid's are the same
13
+ - Added autogenerated output for missing implementation of commands
14
+ - Added dedicated example script for users to experiment with
15
+ - Added generation info texts as logging.info (user has to set 'include-system-site-packages = true' in pyvenv.py)
16
+
17
+ ### [1.3.3] - 2025-05-26
18
+ - Fixed toml file error. Added new create_toml_file method for the Application class.
19
+ - Updated minimum required python version
20
+ - Updated toml file contents
21
+
3
22
  ### [1.3.2] - 2025-05-26
4
- - Fixed base command hinheritance bug
23
+ - Fixed base command inheritance bug
5
24
 
6
25
  ### [1.3.1] - 2025-05-15
7
26
  - Added documentation for utils.py
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rccn_gen
3
- Version: 1.3.2
3
+ Version: 1.3.4
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
7
- Author-email: Fabian Krech <f.krech@tu-berlin.de>
7
+ Author-email: Fabian Krech <f.krech@tu-berlin.de>, Alexander Balke <a.balke@tu-berlin.de>
8
8
  License-Expression: GPL-3.0
9
9
  Classifier: Operating System :: OS Independent
10
10
  Classifier: Programming Language :: Python :: 3
11
- Requires-Python: >=3.8
11
+ Requires-Python: >=3.12
12
12
  Requires-Dist: case-converter>=1.2.0
13
13
  Requires-Dist: inflect>=7.5.0
14
14
  Requires-Dist: yamcs-pymdb>=0.1.0
@@ -0,0 +1,263 @@
1
+ #!/usr/bin/env python3.13
2
+ import sys
3
+ import os
4
+
5
+ from yamcs.pymdb import *
6
+ from rccn_gen import *
7
+
8
+ root_system = System("RACCOON_GS")
9
+ app = Application(system = root_system, name = "AutogeneratedUserApp", apid = 67)
10
+
11
+ service = Service(name="CommConfigService", system=app, service_id=133)
12
+
13
+ virtual_channel_id = IntegerArgument(
14
+ name="virtual channel id",
15
+ signed=False,
16
+ encoding=uint8_t,
17
+ )
18
+
19
+ fop1_set_v_s_command = RCCNCommand(
20
+ system=service,
21
+ name="SetVsDirective",
22
+ short_description="Set Next Expected Frame Sequence Number [V(s)]",
23
+ arguments=[
24
+ virtual_channel_id,
25
+ IntegerArgument(
26
+ name="NextExpectedSequenceNumber",
27
+ encoding=uint8_t,
28
+ signed=False,
29
+
30
+ ),
31
+ ],
32
+ )
33
+
34
+ fop1_set_sliding_window_command = RCCNCommand(
35
+ system=service,
36
+ name="SetSlidingWindowDirective",
37
+ short_description="Set Sliding Window Width [k]",
38
+ arguments=[
39
+ virtual_channel_id,
40
+ IntegerArgument(
41
+ name="SlidingWindowWidth",
42
+ encoding=uint8_t,
43
+ signed=False,
44
+ ),
45
+ ],
46
+ )
47
+
48
+ fop1_set_t1_initial_command = RCCNCommand(
49
+ system=service,
50
+ name="SetT1initialDirective",
51
+ short_description="Set the initial value [t1] in ms to which the countdown Timer is set",
52
+ arguments=[
53
+ virtual_channel_id,
54
+ IntegerArgument(
55
+ name="T1Initial",
56
+ encoding=uint32_t,
57
+ signed=False,
58
+ ),
59
+ ],
60
+ )
61
+
62
+ fop1_set_transmission_limit_command = RCCNCommand(
63
+ system=service,
64
+ name="SetTransmissionLimitDirective",
65
+ short_description="Set the Transmission Limit",
66
+ arguments=[
67
+ virtual_channel_id,
68
+ IntegerArgument(
69
+ name="TransmissionLimit",
70
+ encoding=uint8_t,
71
+ signed=False,
72
+ ),
73
+ ],
74
+ )
75
+
76
+ fop1_set_timeout_type_command = RCCNCommand(
77
+ system=service,
78
+ name="SetTimeoutTypeDirective",
79
+ short_description="Set the Timeout Type",
80
+ arguments=[
81
+ virtual_channel_id,
82
+ IntegerArgument(
83
+ name="TimeoutType",
84
+ encoding=uint8_t,
85
+ minimum=0,
86
+ maximum=1,
87
+ signed=False,
88
+ ),
89
+ ],
90
+ )
91
+
92
+ fop1_initiate_ad_service_command = RCCNCommand(
93
+ system=service,
94
+ name="InitADServiceDirective",
95
+ short_description="Set AD Service",
96
+ arguments=[
97
+ virtual_channel_id,
98
+ BooleanArgument(
99
+ name="CLCW Check",
100
+ short_description="Set if CLCW check shall be performed",
101
+ ),
102
+ ],
103
+ )
104
+
105
+ fop1_initiate_ad_service_with_unlock_command = RCCNCommand(
106
+ system=service,
107
+ name="InitADServiceWithUnlockDirective",
108
+ short_description="Set AD Service With Unlock",
109
+ arguments=[
110
+ virtual_channel_id
111
+ ]
112
+ )
113
+
114
+ fop1_initiate_ad_service_with_set_v_r_command = RCCNCommand(
115
+ system=service,
116
+ name="InitADServiceWithSetVrDirective",
117
+ short_description="Set AD Service With Set Vr",
118
+ arguments=[
119
+ virtual_channel_id,
120
+ IntegerArgument(
121
+ name="ReceiverFrameSequenceNumber",
122
+ short_description="Receiver Frame Sequence Number [V(R)]",
123
+ encoding=uint8_t,
124
+ signed=False,
125
+ ),
126
+ ],
127
+ )
128
+
129
+ fop1_terminate_ad_service_command = RCCNCommand(
130
+ system=service,
131
+ name="TerminateADServiceDirective",
132
+ short_description="Terminate AD Service",
133
+ arguments=[
134
+ virtual_channel_id
135
+ ]
136
+ )
137
+
138
+ request_my_first_telemetry = RCCNCommand(
139
+ system=service,
140
+ name="RQ_my_first_telemetry",
141
+ short_description="Request-command for reading MyFirstTelemetry",
142
+ specify_target_telemetry='MyFirstTelemetry'
143
+ )
144
+
145
+ request_my_second_telemetry = RCCNCommand(
146
+ system=service,
147
+ name="RQ_my_second_telemetry",
148
+ short_description="Request-command for reading MyFirstTelemetry",
149
+ specify_target_telemetry = "MySecondTelemetry"
150
+ )
151
+
152
+
153
+ my_first_telemetry = RCCNContainer(
154
+ system=service,
155
+ name="MyFirstTelemetry",
156
+ entries=[
157
+ ParameterEntry(
158
+ IntegerParameter(
159
+ system=service,
160
+ name="State",
161
+ encoding=uint8_t,
162
+
163
+ )
164
+ ),
165
+ ParameterEntry(
166
+ IntegerParameter(
167
+ system=service,
168
+ name="VS",
169
+ encoding=uint8_t
170
+ )
171
+ ),
172
+ ParameterEntry(
173
+ BooleanParameter(
174
+ system=service,
175
+ name="ad_out_flag",
176
+ encoding=uint1_t
177
+ )
178
+ ),
179
+ ParameterEntry(
180
+ BooleanParameter(
181
+ system=service,
182
+ name="bd_out_flag",
183
+ encoding=uint1_t
184
+ )
185
+ ),
186
+ ParameterEntry(
187
+ BooleanParameter(
188
+ system=service,
189
+ name="bc_out_flag",
190
+ encoding=uint1_t
191
+ )
192
+ ),
193
+ ParameterEntry(
194
+ IntegerParameter(
195
+ system=service,
196
+ name="ExpectedAcknowledgementFrameSequenceNumber",
197
+ encoding=uint8_t
198
+ )
199
+ ),
200
+ ParameterEntry(
201
+ IntegerParameter(
202
+ system=service,
203
+ name="T1_Initial",
204
+ encoding=uint32_t
205
+ )
206
+ ),
207
+ ParameterEntry(
208
+ IntegerParameter(
209
+ system=service,
210
+ name="TransmissionLimitTM",
211
+ encoding=uint8_t
212
+ )
213
+ ),
214
+ ParameterEntry(
215
+ IntegerParameter(
216
+ system=service,
217
+ name="TransmissionCount",
218
+ encoding=uint8_t
219
+ )
220
+ ),
221
+ ParameterEntry(
222
+ IntegerParameter(
223
+ system=service,
224
+ name="FopSlidingWindwWidth",
225
+ encoding=uint8_t
226
+ )
227
+ ),
228
+ ]
229
+ )
230
+
231
+ my_second_telemetry = RCCNContainer(
232
+ system=service,
233
+ name="MySecondTelemetry",
234
+ entries=[
235
+ ParameterEntry(
236
+ IntegerParameter(
237
+ system=service,
238
+ name="MyOtherState",
239
+ encoding=uint8_t
240
+ )
241
+ ),
242
+ ]
243
+ )
244
+
245
+ if __name__ == '__main__':
246
+ ## change target userspace directory to put generated rust code into
247
+ ## "../../rccn-userspace-ws/src/"
248
+ target_dir_userspace = "./"
249
+
250
+ ## change target mdb directory to put generated .xml into
251
+ ## "../../yamcs-instance/src/main/yamcs/mdb/"
252
+ target_dir_mdb = "./"
253
+
254
+ ## Specify name of autogenerated *.xml
255
+ target_file_xml = "AUTOGENERATED_USER_GS.xml"
256
+
257
+ app.generate_rccn_code(export_directory=target_dir_userspace)
258
+ with open(target_dir_mdb + target_file_xml, "wt") as f:
259
+ app.dump(f)
260
+
261
+ print(f"----- Autogeneration done -----")
262
+ print(f"[ TODO 1/2 ] Add the path to your generated src/ directory to '[packages]' in the '<project>/Cargo.toml'")
263
+ print(f"[ TODO 2/2 ] Add the path to your generated '{target_file_xml}' to the 'mdb'-section of your *.yaml file")
@@ -4,13 +4,14 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "rccn_gen"
7
- version = "1.3.2"
7
+ version = "1.3.4"
8
8
  authors = [
9
9
  { name="Fabian Krech", email="f.krech@tu-berlin.de" },
10
+ { name="Alexander Balke", email="a.balke@tu-berlin.de" },
10
11
  ]
11
12
  description = "A python based generator for RACCOON OS source files in Rust from yamcs-pymdb config files."
12
13
  readme = "README.md"
13
- requires-python = ">=3.8"
14
+ requires-python = ">=3.12"
14
15
  dependencies = [
15
16
  "yamcs-pymdb>=0.1.0",
16
17
  "inflect>=7.5.0",
@@ -19,6 +19,8 @@ from yamcs.pymdb.parameters import (
19
19
  StringParameter
20
20
  )
21
21
  from yamcs.pymdb.commands import Command, CommandLevel, CommandEntry, Argument, TransmissionConstraint
22
+ import logging
23
+ logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO)
22
24
 
23
25
  class Application(Subsystem):
24
26
  """
@@ -162,13 +164,16 @@ class Application(Subsystem):
162
164
  A dictionary mapping file types to their absolute paths.
163
165
  """
164
166
  paths = {
165
- 'main': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.name), 'src', 'main.rs'),
166
- 'main_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', 'rccn_usr_'+snakecase(self.name), 'src', 'main.rs'),
167
- 'main_user_snapshot': os.path.join(self.user_snapshot_path(), 'rccn_usr_'+snakecase(self.name), 'src', 'main.rs'),
168
- 'main_diff': os.path.join(self.diff_directory, 'rccn_usr_'+snakecase(self.name), 'src', 'main.diff'),
167
+ 'main': os.path.join(self.export_directory, snakecase(self.name), 'src', 'main.rs'),
168
+ 'main_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', snakecase(self.name), 'src', 'main.rs'),
169
+ 'main_user_snapshot': os.path.join(self.user_snapshot_path(), snakecase(self.name), 'src', 'main.rs'),
170
+ 'main_diff': os.path.join(self.diff_directory, snakecase(self.name), 'src', 'main.diff'),
169
171
  'main_template': os.path.join(self.text_modules_main_path, 'main.txt'),
170
- 'cargo_toml': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.name), 'Cargo.toml'),
172
+ 'cargo_toml': os.path.join(self.export_directory, snakecase(self.name), 'Cargo.toml'),
171
173
  'cargo_toml_template': os.path.join(self.text_modules_path, 'cargo_toml', 'cargo.txt'),
174
+ 'cargo_toml_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', snakecase(self.name), 'cargo.toml'),
175
+ 'cargo_toml_user_snapshot': os.path.join(self.user_snapshot_path(), snakecase(self.name), 'cargo.toml'),
176
+ 'cargo_toml_diff': os.path.join(self.diff_directory, snakecase(self.name), 'cargo.diff'),
172
177
  }
173
178
  return paths
174
179
 
@@ -201,7 +206,7 @@ class Application(Subsystem):
201
206
  Creates the necessary directory structure for the application and its services
202
207
  in the export directory.
203
208
  """
204
- app_src_dir = os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.name), 'src')
209
+ app_src_dir = os.path.join(self.export_directory, snakecase(self.name), 'src')
205
210
  if not os.path.exists(app_src_dir):
206
211
  os.makedirs(app_src_dir)
207
212
  for service in self.services():
@@ -230,9 +235,11 @@ class Application(Subsystem):
230
235
  # Generate main.rs file
231
236
  with open(self.file_paths()['main_template'], 'r') as file:
232
237
  main_template_text = file.read()
238
+ logging.debug(f"[{__class__.__name__}] {self.file_paths()['main_template']} read")
233
239
  with open(self.file_paths()['main'], 'w') as file:
234
240
  new_main_text = self.find_and_replace_keywords(main_template_text)
235
241
  file.write("".join(new_main_text))
242
+ logging.info(f"[{__class__.__name__}] {self.file_paths()['main']} written")
236
243
  # Create snapshot of newly generated main.rs if instructed
237
244
  if self.snapshots:
238
245
  self.generate_snapshot('main', 'main_generated_snapshot')
@@ -321,7 +328,7 @@ class Application(Subsystem):
321
328
  if not os.path.exists(service.file_paths()['mod']):
322
329
  service.generate_mod_file()
323
330
  service.generate_telemetry_file()
324
- service.generate_rccn_command_file(os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.name), 'src'), os.path.join(self.text_modules_path, 'command'))
331
+ service.generate_rccn_command_file(os.path.join(self.export_directory, snakecase(self.name), 'src'), os.path.join(self.text_modules_path, 'command'))
325
332
  self.delete_old_snapshots()
326
333
 
327
334
  def generate_snapshot(self, current_file_reference, snapshot_file_reference):
@@ -407,12 +414,34 @@ class Application(Subsystem):
407
414
 
408
415
  def generate_cargo_toml_file(self):
409
416
  """
410
- Generate the Cargo.toml file for the application.
417
+ Generate the cargo.toml file for the application.
418
+
419
+ Performs several tasks:
420
+ 1. Creates diff file if both current and snapshot files exist
421
+ 2. Creates snapshot of current main with user changes if snapshots are enabled
422
+ 3. Generates new cargo.toml file from template
423
+ 4. Creates snapshot of newly generated main if snapshots are enabled
424
+ 5. Applies user changes from diff file if rebase_changes is enabled
411
425
  """
426
+ # Create cargo_toml.diff file
427
+ if os.path.exists(self.file_paths()['cargo_toml']) and os.path.exists(self.file_paths()['cargo_toml_generated_snapshot']):
428
+ os.maked(os.path.dirname(self.file_paths()['cargo_toml_diff']), exist_ok=True)
429
+ self.generate_diff_file('cargo_toml', 'cargo_toml_generated_snapshot', 'cargo_toml_diff')
430
+ # Create snapshot of cargo_toml with user changes if instructed
431
+ if self.snapshots and os.path.exists(self.file_paths()['cargo_toml']):
432
+ self.generate_snapshot('cargo_toml', 'cargo_toml_user_snapshot')
433
+ # Generate cargo_toml file
412
434
  with open(self.file_paths()['cargo_toml_template'], 'r') as file:
413
435
  cargo_toml_template_text = file.read()
414
436
  with open(self.file_paths()['cargo_toml'], 'w') as file:
415
- file.write(cargo_toml_template_text)
437
+ new_cargo_toml_text = self.find_and_replace_keywords(cargo_toml_template_text)
438
+ file.write("".join(new_cargo_toml_text))
439
+ # Create snapshot of newly generated cargo_toml if instructed
440
+ if self.snapshots:
441
+ self.generate_snapshot('cargo_toml', 'cargo_toml_generated_snapshot')
442
+ # Rebase cargo_toml.diff on cargo_toml if instructed
443
+ if self.rebase_changes and os.path.exists(self.file_paths()['cargo_toml_diff']):
444
+ os.system('patch '+self.file_paths()['cargo_toml']+' < '+self.file_paths()['cargo_toml_diff'])
416
445
 
417
446
 
418
447
  class Service(Subsystem):
@@ -532,22 +561,22 @@ class Service(Subsystem):
532
561
  source files, generated snapshots, user snapshots, diff files, and templates.
533
562
  """
534
563
  paths = {
535
- 'service': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
536
- 'service_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
537
- 'service_user_snapshot': os.path.join(self.snapshot_directory, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
538
- 'service_diff': os.path.join(self.diff_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.diff'),
564
+ 'service': os.path.join(self.export_directory, snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
565
+ 'service_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
566
+ 'service_user_snapshot': os.path.join(self.snapshot_directory, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
567
+ 'service_diff': os.path.join(self.diff_directory, snakecase(self.system.name), 'src', snakecase(self.name), 'service.diff'),
539
568
  'service_template': os.path.join(self.text_modules_service_path, 'service.txt'),
540
- 'command': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
541
- 'command_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
542
- 'command_user_snapshot': os.path.join(self.snapshot_directory, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
543
- 'command_diff': os.path.join(self.diff_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.diff'),
569
+ 'command': os.path.join(self.export_directory, snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
570
+ 'command_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
571
+ 'command_user_snapshot': os.path.join(self.snapshot_directory, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
572
+ 'command_diff': os.path.join(self.diff_directory, snakecase(self.system.name), 'src', snakecase(self.name), 'command.diff'),
544
573
  'command_template': os.path.join(self.text_modules_command_path, 'command.txt'),
545
- 'mod': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'mod.rs'),
574
+ 'mod': os.path.join(self.export_directory, snakecase(self.system.name), 'src', snakecase(self.name), 'mod.rs'),
546
575
  'mod_template': os.path.join(self.text_modules_path, 'mod', 'mod.txt'),
547
- 'telemetry': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.rs'),
548
- 'telemetry_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.rs'),
549
- 'telemetry_user_snapshot': os.path.join(self.snapshot_directory, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
550
- 'telemetry_diff': os.path.join(self.diff_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.diff'),
576
+ 'telemetry': os.path.join(self.export_directory, snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.rs'),
577
+ 'telemetry_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.rs'),
578
+ 'telemetry_user_snapshot': os.path.join(self.snapshot_directory, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
579
+ 'telemetry_diff': os.path.join(self.diff_directory, snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.diff'),
551
580
  'telemetry_template': os.path.join(self.text_modules_telemetry_path, 'telemetry.txt'),
552
581
  }
553
582
  return paths
@@ -605,6 +634,10 @@ class Service(Subsystem):
605
634
  for command in self.rccn_commands():
606
635
  text = command.find_and_replace_keywords(text, text_modules_path)
607
636
 
637
+ containers = self.rccn_container()
638
+ cnames = [c.name for c in containers]
639
+ telemetries = ", ".join(cnames)
640
+
608
641
  # Find and replace service variable keywords
609
642
  var_keywords = get_var_keywords(text)
610
643
  service_var_translation = {
@@ -612,6 +645,7 @@ class Service(Subsystem):
612
645
  '<<VAR_SERVICE_ID>>': lambda: str(self.service_id),
613
646
  '<<VAR_SERVICE_NAME_UCASE>>': lambda: pascalcase(self.name),
614
647
  '<<VAR_SERVICE_TELEMETRY>>': lambda: self.generate_rust_telemetry_definition(),
648
+ '<<VAR_IMPORTS_TELEMETRIES>>' : lambda: "use super::{command" + ("};" if len(containers) == 0 else ", telemetry};"),
615
649
  }
616
650
  for var_keyword in var_keywords:
617
651
  if var_keyword in service_var_translation.keys():
@@ -644,6 +678,7 @@ class Service(Subsystem):
644
678
  service_template_file_text = file.read()
645
679
  with open(self.file_paths()['service'], 'w') as file:
646
680
  file.write(self.find_and_replace_keywords(service_template_file_text, self.text_modules_service_path))
681
+ logging.info(f"[{__class__.__name__}] {self.file_paths()['service']} written")
647
682
  # Create snapshot of service.rs if instructed
648
683
  if self.snapshots:
649
684
  self.generate_snapshot('service', 'service_generated_snapshot')
@@ -678,7 +713,7 @@ class Service(Subsystem):
678
713
  self.generate_snapshot('command', 'command_user_snapshot')
679
714
  # Generate command.rs file
680
715
  if len(self.rccn_commands()) == 0:
681
- print('RCCN-Information: Service \''+self.name+'\' has no commands other than base command. Generation of command.rs file will be skipped.')
716
+ logging.debug(f'[{__class__.__name__}] RCCN-Information: Service \''+self.name+'\' has no commands other than base command. Generation of command.rs file will be skipped.')
682
717
  return
683
718
  command_file_path = self.file_paths()['command_template']
684
719
  with open(command_file_path, 'r') as file:
@@ -686,6 +721,7 @@ class Service(Subsystem):
686
721
  command_export_directory = os.path.join(export_file_dir, snakecase(self.name), 'command.rs')
687
722
  with open(command_export_directory, 'w') as file:
688
723
  file.write(self.find_and_replace_keywords(command_file_text, text_modules_path))
724
+ logging.info(f"[{__class__.__name__}] {command_export_directory} written")
689
725
  # Create snapshot of command.rs if instructed
690
726
  if self.snapshots:
691
727
  self.generate_snapshot('command', 'command_generated_snapshot')
@@ -762,6 +798,7 @@ class Service(Subsystem):
762
798
  telemetry_template_file_text = file.read()
763
799
  with open(self.file_paths()['telemetry'], 'w') as file:
764
800
  file.write(self.find_and_replace_keywords(telemetry_template_file_text, self.text_modules_telemetry_path))
801
+ logging.info(f"[{__class__.__name__}] {self.file_paths()['telemetry']} written")
765
802
  # Create snapshot of telemetry.rs if instructed
766
803
  if self.snapshots:
767
804
  self.generate_snapshot('telemetry', 'telemetry_generated_snapshot')
@@ -886,6 +923,7 @@ class RCCNCommand(Command):
886
923
  long_description: str | None = None,
887
924
  extra: Mapping[str, str] | None = None,
888
925
  abstract: bool = False,
926
+ specify_target_telemetry: str = None,
889
927
  base: Command | str | None = None,
890
928
  assignments: Mapping[str, Any] | None = None,
891
929
  arguments: Sequence[Argument] | None = None,
@@ -914,6 +952,8 @@ class RCCNCommand(Command):
914
952
  Arbitrary information about the command, keyed by name.
915
953
  abstract : bool, optional
916
954
  Whether this command is abstract. Default is False.
955
+ specify_target_telemetry : str, optional
956
+ Specify target telemetry that this command is supposed to request.
917
957
  base : Command | str, optional
918
958
  The base command or reference to a base command.
919
959
  assignments : Mapping[str, Any], optional
@@ -949,6 +989,7 @@ class RCCNCommand(Command):
949
989
  'constraint': constraint,
950
990
  **kwargs
951
991
  }
992
+ self.target_telemetry = specify_target_telemetry
952
993
  if 'system' in kwargs and isinstance(kwargs['system'], Service):
953
994
  self.add_to_service(kwargs['system'])
954
995
  elif base and (isinstance(base, Command) or isinstance(base, RCCNCommand)):
@@ -975,7 +1016,7 @@ class RCCNCommand(Command):
975
1016
  - The command's APID is automatically set to match the service's application APID
976
1017
  """
977
1018
  if self.init_kwargs['base'] is None and not any(command.name == 'base' for command in service.commands):
978
- print("RCCN-Information: Command \'"+self.init_kwargs['name']+"\' doesn\'t have a base argument and no base command was found in service \'"+service.name+"\'.\nStandard base command will be created with system = \'"+service.name+"\' and type = "+str(service.service_id)+".")
1019
+ logging.debug(f"[{__class__.__name__}] 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)+".")
979
1020
  self.init_kwargs['base'] = Command(
980
1021
  system=service,
981
1022
  name='base',
@@ -984,7 +1025,7 @@ class RCCNCommand(Command):
984
1025
  assignments={'type': service.service_id}
985
1026
  )
986
1027
  elif self.init_kwargs['base'] is None and any(command.name == 'base' for command in service.commands):
987
- print("RCCN-Information: Command \'"+self.init_kwargs['name']+"\' doesn\'t have a \'base\' argument. Existing base command for service \'"+service.name+"\' will be used.")
1028
+ logging.debug(f"[{__class__.__name__}] RCCN-Information: Command \'"+self.init_kwargs['name']+"\' doesn\'t have a \'base\' argument. Existing base command for service \'"+service.name+"\' will be used.")
988
1029
  self.init_kwargs['base'] = next(command for command in service.commands if command.name == 'base')
989
1030
  if 'system' in self.init_kwargs and isinstance(self.init_kwargs['system'], Service):
990
1031
  super().__init__(*self.init_args, **self.init_kwargs)
@@ -996,12 +1037,13 @@ class RCCNCommand(Command):
996
1037
  new_subtype = 1
997
1038
  while new_subtype in used_subtypes:
998
1039
  new_subtype = new_subtype + 1
999
- print('RCCN-Information: Command \''+self.name+'\' has no subtype specified. Subtype will be set to '+str(new_subtype)+'.')
1040
+ logging.debug(f'[{__class__.__name__}] RCCN-Information: Command \''+self.name+'\' has no subtype specified. Subtype will be set to '+str(new_subtype)+'.')
1000
1041
  self.assignments['subtype'] = new_subtype
1001
- self.struct_name = self.name + 'Args'
1042
+ if len(self.arguments) != 0:
1043
+ self.struct_name = self.name + 'Args'
1002
1044
 
1003
1045
 
1004
- def find_and_replace_keywords(self, text, text_modules_path):
1046
+ def find_and_replace_keywords(self, text, text_modules_path): # , service : Service):
1005
1047
  """
1006
1048
  Replace template keywords with actual command values.
1007
1049
 
@@ -1040,15 +1082,24 @@ class RCCNCommand(Command):
1040
1082
  replacement_text = (self.find_and_replace_keywords(module_text, text_modules_path) + '\n')
1041
1083
  text = insert_before_with_indentation(text, command_module_keyword, replacement_text)
1042
1084
 
1085
+ cmd_requests_tm = (self.target_telemetry is not None)
1086
+
1043
1087
  # Find and replace command variable keywords
1044
1088
  command_var_keywords = get_var_keywords(text)
1045
1089
  command_var_translation = {
1046
1090
  '<<VAR_COMMAND_NAME_UCASE>>': lambda: pascalcase(self.name),
1091
+ '<<VAR_ARGS>>' : lambda: "(args)" if len(self.arguments) != 0 else "",
1092
+ '<<VAR_HANDLER_FUNCTION>>' : lambda: "tc.handle_with_tm" if cmd_requests_tm else "tc.handle",
1047
1093
  '<<VAR_COMMAND_NAME>>': lambda: self.name,
1048
- '<<VAR_COMMAND_STRUCT_NAME>>': lambda: self.struct_name,
1094
+ '<<VAR_COMMAND_STRUCT_NAME>>': lambda: f"({self.struct_name})" if len(self.arguments) != 0 else '',
1049
1095
  '<<VAR_COMMAND_SUBTYPE>>': lambda: str(self.assignments['subtype']),
1050
1096
  '<<VAR_COMMAND_STRUCT>>': lambda: self.struct_definition(),
1051
- '<<VAR_SHORT_DESCRIPTION>>': lambda: "\n/// " + self.short_description if self.short_description is not None else "",
1097
+ '<<VAR_SHORT_DESCRIPTION>>': lambda: "// " + self.short_description if self.short_description is not None else "No short description provided",
1098
+ '<<VAR_INFO_TEXT>>' : lambda: "User functionality missing in" if not cmd_requests_tm else "Returning default telemetry from",
1099
+ '<<VAR_RETURN_TYPE>>' : lambda : "if false { return Err(()); }" if cmd_requests_tm else "return false;",
1100
+ '<<VAR_RETURN_TARGET_TM_TEMPLATE>>' : lambda : "" if not cmd_requests_tm else \
1101
+ f"\nlet mut tm = telemetry::{self.target_telemetry}::default();\n" \
1102
+ f"\nOk(tm)",
1052
1103
  }
1053
1104
  for command_var_keyword in command_var_keywords:
1054
1105
  if command_var_keyword in command_var_translation.keys():
@@ -1223,16 +1274,17 @@ class RCCNContainer(Container):
1223
1274
  if eq_expression.ref == self.base+'/subtype':
1224
1275
  condition_subtype = eq_expression.value
1225
1276
  if condition_type is not None and condition_type != self.type:
1226
- 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.')
1277
+ logging.debug(f'[{__class__.__name__}] RCCN-Warning: Container '+self.name+' has a user-defined type of '+str(eq_expression.value)+', which doesn\'t match the service ID. User-defined type will be used.')
1227
1278
  self.type = condition_type
1228
1279
  if condition_subtype is not None and self.subtype is not None and condition_subtype != self.subtype:
1229
- print('RCCN-Warning: Container '+self.name+' has an ambiguous user-defined subtype. \'subtype\' argument should match the \'condition\' argument.')
1280
+ logging.debug(f'[{__class__.__name__}] RCCN-Warning: Container '+self.name+' has an ambiguous user-defined subtype. \'subtype\' argument should match the \'condition\' argument.')
1230
1281
  elif condition_subtype is not None:
1231
1282
  self.subtype = condition_subtype
1232
1283
  elif self.subtype is not None and self.init_kwargs['condition'] is not None:
1233
1284
  self.init_kwargs['condition'] = AndExpression(
1234
1285
  EqExpression(self.base+'/type', self.type),
1235
- EqExpression(self.base+'/subtype', self.subtype)
1286
+ EqExpression(self.base+'/subtype', self.subtype),
1287
+ EqExpression('/PUS/ccsds/apid', service.system.apid)
1236
1288
  )
1237
1289
  else:
1238
1290
  used_subtypes = [container.subtype for container in service.rccn_container()]
@@ -1242,9 +1294,10 @@ class RCCNContainer(Container):
1242
1294
  self.subtype = new_subtype
1243
1295
  self.init_kwargs['condition'] = AndExpression(
1244
1296
  EqExpression(self.base+'/type', self.type),
1245
- EqExpression(self.base+'/subtype', self.subtype)
1297
+ EqExpression(self.base+'/subtype', self.subtype),
1298
+ EqExpression('/PUS/ccsds/apid', service.system.apid)
1246
1299
  )
1247
- print('RCCN-Information: Subtype for Container '+self.name+' is not specified through \'subtype\' or \'condition\' arguments. Subtype will be set to '+str(self.subtype)+'.')
1300
+ logging.debug(f'[{__class__.__name__}] RCCN-Information: Subtype for Container '+self.name+' is not specified through \'subtype\' or \'condition\' arguments. Subtype will be set to '+str(self.subtype)+'.')
1248
1301
 
1249
1302
  if 'system' in self.init_kwargs and isinstance(self.init_kwargs['system'], Service):
1250
1303
  super().__init__(**self.init_kwargs)
@@ -1269,7 +1322,7 @@ class RCCNContainer(Container):
1269
1322
  rccn_telemetry_text = ""
1270
1323
  if hasattr(self, 'short_description') and self.short_description is not None:
1271
1324
  rccn_telemetry_text += "/// "+str(self.short_description)+"\n"
1272
- rccn_telemetry_text += "#[derive(ServiceTelemetry, BitStruct, Debug)]\n"
1325
+ rccn_telemetry_text += "#[derive(ServiceTelemetry, BitStruct, Debug, Default)]\n"
1273
1326
  if hasattr(self, 'subtype') and self.subtype is not None:
1274
1327
  rccn_telemetry_text += "#[subtype("+str(self.subtype)+")]\n"
1275
1328
  rccn_telemetry_text += "pub struct " + self.name + " {\n"
@@ -1,7 +1,7 @@
1
1
  [package]
2
- name = "rccn_usr_example_app"
2
+ name = "<<VAR_APP_NAME_SCASE>>"
3
3
  version = "0.1.0"
4
- edition = "2021"
4
+ edition = "2024"
5
5
 
6
6
  [dependencies]
7
7
  anyhow = "1.0.91"
@@ -14,4 +14,7 @@ spacepackets = "0.12.0"
14
14
  tokio = "1.41.1"
15
15
  env_logger = { version = "0.11.7", default-features = false, features = ["color", "humantime"] }
16
16
  num-derive = "0.4"
17
- num-traits = "0.2"
17
+ num-traits = "0.2"
18
+ rccn_usr_bitstruct_derive = { version = "0.1.0", path = "../rccn_usr_bitstruct_derive" }
19
+ rccn_usr_pus_macros = { version = "0.1.0", path = "../rccn_usr_pus_macros" }
20
+ chrono = "0.4.40"
@@ -5,6 +5,7 @@ use rccn_usr_pus_macros::ServiceCommand;
5
5
 
6
6
  #[derive(ServiceCommand)]
7
7
  pub enum Command {
8
+
8
9
  <<COMMAND_MODULE_ENUM>>
9
10
  }
10
11
 
@@ -1,3 +1,3 @@
1
1
  <<VAR_SHORT_DESCRIPTION>>
2
2
  #[subservice(<<VAR_COMMAND_SUBTYPE>>)]
3
- <<VAR_COMMAND_NAME_UCASE>>(<<VAR_COMMAND_STRUCT_NAME>>),
3
+ <<VAR_COMMAND_NAME_UCASE>><<VAR_COMMAND_STRUCT_NAME>>,
@@ -0,0 +1,5 @@
1
+ command::Command::<<VAR_COMMAND_NAME_UCASE>><<VAR_ARGS>> => <<VAR_HANDLER_FUNCTION>>(|| {
2
+ <<VAR_SHORT_DESCRIPTION>>
3
+ println!("[ INFO ] <<VAR_INFO_TEXT>> <<VAR_COMMAND_NAME_UCASE>><<VAR_ARGS>>.");
4
+ <<VAR_RETURN_TYPE>><<VAR_RETURN_TARGET_TM_TEMPLATE>>
5
+ }),
@@ -1,6 +1,6 @@
1
1
  use std::process::Command;
2
2
  use rccn_usr::service::{AcceptanceResult, AcceptedTc, PusService};
3
- use super::command;
3
+ <<VAR_IMPORTS_TELEMETRIES>>
4
4
  use anyhow::Result;
5
5
  use std::error::Error;
6
6
  use chrono::Local;
@@ -8,6 +8,7 @@ use std::fs;
8
8
 
9
9
 
10
10
  pub struct <<VAR_SERVICE_NAME_UCASE>> {
11
+ // tm_parameter
11
12
  }
12
13
 
13
14
 
@@ -1,17 +0,0 @@
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"
15
- env_logger = { version = "0.11.7", default-features = false, features = ["color", "humantime"] }
16
- num-derive = "0.4"
17
- num-traits = "0.2"
@@ -1,3 +0,0 @@
1
- command::Command::<<VAR_COMMAND_NAME_UCASE>>(args) => tc.handle(|| {
2
-
3
- }),
File without changes
File without changes
File without changes
File without changes