rccn-gen 1.1.0__tar.gz → 1.2.0__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 (23) hide show
  1. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/.gitignore +2 -2
  2. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/CHANGELOG.md +17 -1
  3. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/PKG-INFO +3 -3
  4. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/README.md +2 -2
  5. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/pyproject.toml +1 -1
  6. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/src/rccn_gen/systems.py +272 -77
  7. rccn_gen-1.2.0/src/rccn_gen/text_modules/cargo_toml/cargo.txt +17 -0
  8. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/.gitlab-ci.yml +0 -0
  9. /rccn_gen-1.1.0/src/rccn_gen/text_modules/cargo_toml/cargo.txt → /rccn_gen-1.2.0/rccn_usr_bi_x1_cntrl_app/Cargo.toml +0 -0
  10. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/src/rccn_gen/LICENSE +0 -0
  11. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/src/rccn_gen/__init__.py +0 -0
  12. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/src/rccn_gen/text_modules/command/command.txt +0 -0
  13. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/src/rccn_gen/text_modules/command/command_module_enum.txt +0 -0
  14. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/src/rccn_gen/text_modules/command/command_module_struct.txt +0 -0
  15. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/src/rccn_gen/text_modules/main/main.txt +0 -0
  16. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/src/rccn_gen/text_modules/main/service_module_import_service.txt +0 -0
  17. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/src/rccn_gen/text_modules/main/service_module_mod_service.txt +0 -0
  18. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/src/rccn_gen/text_modules/main/service_module_register_service.txt +0 -0
  19. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/src/rccn_gen/text_modules/mod/mod.txt +0 -0
  20. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/src/rccn_gen/text_modules/service/command_module_match_cmd.txt +0 -0
  21. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/src/rccn_gen/text_modules/service/service.txt +0 -0
  22. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/src/rccn_gen/text_modules/telemetry/telemetry.txt +0 -0
  23. {rccn_gen-1.1.0 → rccn_gen-1.2.0}/src/rccn_gen/utils.py +0 -0
@@ -2,7 +2,7 @@
2
2
  *.DS_Store
3
3
  *.ipynb
4
4
  *.pyc
5
- venv/
5
+ .venv/
6
6
  examples/
7
7
  tests/
8
8
  *.diff
@@ -15,7 +15,7 @@ __pycache__/
15
15
  *.xml
16
16
  *.whl
17
17
  *.egg-info/
18
- /dist
18
+ dist/
19
19
  rccn_usr_com_app/Cargo.toml
20
20
  .rccn_diffs/
21
21
  .rccn_snapshots/
@@ -1,5 +1,21 @@
1
1
  # CHANGE LOG
2
2
 
3
+ ## [1.2.0] - 2025-05-05
4
+ - Added argument hints for RCCNCommand, Service and RCCNContainer
5
+ - New create_and_add_service() method for the Application
6
+ - New create_and_add_command() method for the Service
7
+ - Better error messages
8
+ - Subtypes of RCCNCommands and RCCNContainers are defined automatically, if not specified by the user
9
+ - General refactoring for more readable code
10
+ - Added add_integer_parameter method to RCCNContainer
11
+ - Bug fixes
12
+
13
+ ### [1.1.2] - 2025-04-23
14
+ - The `export_directory` argument for the `Application` is now mandatory.
15
+
16
+ ### [1.1.1] - 2025-04-22
17
+ - Changed the user input checking to allow for multiple command with the same name and subtype, as long as they are associated to different services.
18
+
3
19
  ## [1.1.0] - 2025-04-14
4
20
  - Added derive statements to the rust container structs in the telemetry.rs file.
5
21
  - Inherit subtype of a container from the `condition` argument, if given, or from the new (optional) `subtype` argument. If only the latter is given, condition is created from that. Subtype is included in the derive statements.
@@ -10,4 +26,4 @@
10
26
  - The `tc` parameter in the service.rs file is now mutable.
11
27
  - `System` of a command is obtained from the base command, if no system argument is given.
12
28
  - Added support for long and short descriptions of commands, arguments and supported telemetry parameters.
13
- - Bug fixes.
29
+ - Bug fixes.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rccn_gen
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: A python based generator for RACCOON OS source files in Rust from yamcs-pymdb config files.
5
5
  Project-URL: Homepage, https://gitlab.com/rccn/pymdb_code_generation
6
6
  Project-URL: Issues, https://gitlab.com/rccn/pymdb_code_generation/issues
@@ -44,7 +44,7 @@ An application can be defined with the following statement.
44
44
  ```python
45
45
  app = Application(system=root_system, name="ExampleApp", apid=42)
46
46
  ```
47
- It has the obligatory arguments **system**, **name** and **apid**. After all Applications, Services and RCCNCommands are defined, the rust code generator can be called on the application with `app.generate_rccn_code()`.
47
+ It has the obligatory arguments **system**, **name**, **apid** and **export_directory**. After all Applications, Services and RCCNCommands are defined, the rust code generator can be called on the application with `app.generate_rccn_code(export_directory='.')`.
48
48
 
49
49
  ### Service
50
50
 
@@ -155,4 +155,4 @@ Snapshots of the .rs files in the export directory are stored in `/user/`. These
155
155
  app.keep_snapshots = 15 # Whatever value you want
156
156
  ```
157
157
 
158
- With the sequence from above, it becomes apperant that changes to the .rs file in the export directory always trump changes to the pymdb config. If for example, a main.rs file is generated for an application with and APID of 42, and this apid is changed in the main.rs file to 45, this change will persist after regenerating from the python config. Even if changes to the pymdb config where made after the changes to the main.rs file.
158
+ With the sequence from above, it becomes apperant that changes to the .rs file in the export directory always trump changes to the pymdb config. If for example, a main.rs file is generated for an application with and APID of 42, and this apid is changed in the main.rs file to 45, this change will persist after regenerating from the python config. Even if changes to the pymdb config where made after the changes to the main.rs file.
@@ -28,7 +28,7 @@ An application can be defined with the following statement.
28
28
  ```python
29
29
  app = Application(system=root_system, name="ExampleApp", apid=42)
30
30
  ```
31
- It has the obligatory arguments **system**, **name** and **apid**. After all Applications, Services and RCCNCommands are defined, the rust code generator can be called on the application with `app.generate_rccn_code()`.
31
+ It has the obligatory arguments **system**, **name**, **apid** and **export_directory**. After all Applications, Services and RCCNCommands are defined, the rust code generator can be called on the application with `app.generate_rccn_code(export_directory='.')`.
32
32
 
33
33
  ### Service
34
34
 
@@ -139,4 +139,4 @@ Snapshots of the .rs files in the export directory are stored in `/user/`. These
139
139
  app.keep_snapshots = 15 # Whatever value you want
140
140
  ```
141
141
 
142
- With the sequence from above, it becomes apperant that changes to the .rs file in the export directory always trump changes to the pymdb config. If for example, a main.rs file is generated for an application with and APID of 42, and this apid is changed in the main.rs file to 45, this change will persist after regenerating from the python config. Even if changes to the pymdb config where made after the changes to the main.rs file.
142
+ With the sequence from above, it becomes apperant that changes to the .rs file in the export directory always trump changes to the pymdb config. If for example, a main.rs file is generated for an application with and APID of 42, and this apid is changed in the main.rs file to 45, this change will persist after regenerating from the python config. Even if changes to the pymdb config where made after the changes to the main.rs file.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "rccn_gen"
7
- version = "1.1.0"
7
+ version = "1.2.0"
8
8
  authors = [
9
9
  { name="Fabian Krech", email="f.krech@tu-berlin.de" },
10
10
  ]
@@ -1,10 +1,11 @@
1
- from yamcs.pymdb import System, Subsystem, Command, Container, AndExpression, EqExpression
1
+ from yamcs.pymdb import *
2
2
  import shutil
3
3
  import difflib
4
4
  import datetime
5
5
  from caseconverter import *
6
6
  from importlib.resources import files
7
7
  from .utils import *
8
+ from collections.abc import Mapping, Sequence
8
9
 
9
10
  class Application(Subsystem):
10
11
  """
@@ -16,9 +17,9 @@ class Application(Subsystem):
16
17
  name: str,
17
18
  apid: int,
18
19
  vcid: int = 0,
19
- export_file_path = '.',
20
- snapshot_file_path = './.rccn_snapshots',
21
- diff_file_path = './.rccn_diffs',
20
+ export_directory = '.',
21
+ snapshot_directory = './.rccn_snapshots',
22
+ diff_directory = './.rccn_diffs',
22
23
  snapshots = True,
23
24
  *args,
24
25
  **kwargs
@@ -27,15 +28,13 @@ class Application(Subsystem):
27
28
 
28
29
  self.apid = apid
29
30
  self.vcid = vcid
31
+ self.export_directory = export_directory
30
32
  system._subsystems_by_name[name] = self
31
- self.export_file_path = export_file_path
32
- self.snapshot_file_path = snapshot_file_path
33
- self.snapshot_generated_file_path = os.path.join(snapshot_file_path, 'auto_generated')
34
- self.diff_file_path = diff_file_path
33
+ self.snapshot_directory = snapshot_directory
34
+ self.snapshot_generated_file_path = os.path.join(snapshot_directory, 'auto_generated')
35
+ self.diff_directory = diff_directory
35
36
  self.text_modules_path = files('rccn_gen').joinpath('text_modules')
36
- print(self.text_modules_path)
37
37
  self.text_modules_main_path = os.path.join(self.text_modules_path, 'main')
38
- print(self.text_modules_main_path)
39
38
  self.snapshots = snapshots
40
39
  self.keep_snapshots = 10
41
40
 
@@ -43,27 +42,52 @@ class Application(Subsystem):
43
42
  if not isinstance(service, Service):
44
43
  raise TypeError('Service '+service.name+' is not a RCCNCommand.')
45
44
  service.add_to_application(self)
46
-
45
+
46
+ def create_and_add_service(
47
+ self,
48
+ name: str,
49
+ service_id: int,
50
+ aliases: Mapping[str, str] | None = None,
51
+ short_description: str | None = None,
52
+ long_description: str | None = None,
53
+ extra: Mapping[str, str] | None = None,
54
+ *args,
55
+ **kwargs
56
+ ):
57
+ if 'system' not in kwargs:
58
+ kwargs['system'] = self
59
+ else:
60
+ raise ValueError('RCCN-Error: \'create_and_add_service\' function can not be called with a \'system\' argument.')
61
+ Service(
62
+ name=name,
63
+ service_id=service_id,
64
+ aliases=aliases,
65
+ short_description=short_description,
66
+ long_description=long_description,
67
+ *args,
68
+ **kwargs
69
+ )
70
+
47
71
  def file_paths(self):
48
72
  paths = {
49
- 'main': os.path.join(self.export_file_path, 'rccn_usr_'+snakecase(self.name), 'src', 'main.rs'),
50
- 'main_generated_snapshot': os.path.join(self.snapshot_file_path, 'generated', 'rccn_usr_'+snakecase(self.name), 'src', 'main.rs'),
73
+ 'main': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.name), 'src', 'main.rs'),
74
+ 'main_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', 'rccn_usr_'+snakecase(self.name), 'src', 'main.rs'),
51
75
  'main_user_snapshot': os.path.join(self.user_snapshot_path(), 'rccn_usr_'+snakecase(self.name), 'src', 'main.rs'),
52
- 'main_diff': os.path.join(self.diff_file_path, 'rccn_usr_'+snakecase(self.name), 'src', 'main.diff'),
76
+ 'main_diff': os.path.join(self.diff_directory, 'rccn_usr_'+snakecase(self.name), 'src', 'main.diff'),
53
77
  'main_template': os.path.join(self.text_modules_main_path, 'main.txt'),
54
- 'cargo_toml': os.path.join(self.export_file_path, 'rccn_usr_'+snakecase(self.name), 'Cargo.toml'),
78
+ 'cargo_toml': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.name), 'Cargo.toml'),
55
79
  'cargo_toml_template': os.path.join(self.text_modules_path, 'cargo_toml', 'cargo.txt'),
56
80
  }
57
81
  return paths
58
82
 
59
83
  def user_snapshot_path(self):
60
- return os.path.join(self.snapshot_file_path, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
84
+ return os.path.join(self.snapshot_directory, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
61
85
 
62
86
  def services(self):
63
87
  return [subsystem for subsystem in self.subsystems if isinstance(subsystem, Service)]
64
88
 
65
89
  def create_rccn_directories(self):
66
- app_src_dir = os.path.join(self.export_file_path, 'rccn_usr_'+snakecase(self.name), 'src')
90
+ app_src_dir = os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.name), 'src')
67
91
  if not os.path.exists(app_src_dir):
68
92
  os.makedirs(app_src_dir)
69
93
  for service in self.services():
@@ -111,8 +135,20 @@ class Application(Subsystem):
111
135
  text = delete_all_keywords(text)
112
136
  return text
113
137
 
114
- def generate_rccn_code(self, rebase_changes=True, check=True, export_file_path='.'):
115
- self.export_file_path = export_file_path
138
+ def generate_rccn_code(self, export_directory:str, snapshot_directory='', diff_directory='', rebase_changes=True, check=True):
139
+ # Update export, snapshot and diff directory for the Application and all Services
140
+ self.export_directory = export_directory
141
+ if snapshot_directory == '':
142
+ snapshot_directory = os.path.join(self.export_directory, '.rccn-snapshots')
143
+ if diff_directory == '':
144
+ diff_directory = os.path.join(self.export_directory, '.rccn-diffs')
145
+ self.snapshot_directory = snapshot_directory
146
+ self.diff_directory = diff_directory
147
+ for service in self.services():
148
+ service.export_directory = self.export_directory
149
+ service.diff_directory = self.diff_directory
150
+ service.snapshot_directory = self.snapshot_directory
151
+
116
152
  if check:
117
153
  self.check_user_input()
118
154
  self.rebase_changes = rebase_changes
@@ -122,12 +158,12 @@ class Application(Subsystem):
122
158
  if not os.path.exists(self.file_paths()['cargo_toml']):
123
159
  self.generate_cargo_toml_file()
124
160
  for service in self.services():
125
- service.export_file_path = self.export_file_path
161
+ service.export_directory = self.export_directory
126
162
  service.generate_rccn_service_file()
127
163
  if not os.path.exists(service.file_paths()['mod']):
128
164
  service.generate_mod_file()
129
165
  service.generate_telemetry_file()
130
- service.generate_rccn_command_file(os.path.join(self.export_file_path, 'rccn_usr_'+snakecase(self.name), 'src'), os.path.join(self.text_modules_path, 'command'))
166
+ service.generate_rccn_command_file(os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.name), 'src'), os.path.join(self.text_modules_path, 'command'))
131
167
  self.delete_old_snapshots()
132
168
 
133
169
  def generate_snapshot(self, current_file_reference, snapshot_file_reference):
@@ -146,37 +182,37 @@ class Application(Subsystem):
146
182
  diff_file.write(diff_text)
147
183
 
148
184
  def delete_old_snapshots(self):
149
- if os.path.exists(os.path.join(self.snapshot_file_path, 'user')):
150
- user_snapshots_path = os.path.join(self.snapshot_file_path, 'user')
185
+ if os.path.exists(os.path.join(self.snapshot_directory, 'user')):
186
+ user_snapshots_path = os.path.join(self.snapshot_directory, 'user')
151
187
  snapshots = [os.path.join(user_snapshots_path, d) for d in os.listdir(user_snapshots_path) if os.path.isdir(os.path.join(user_snapshots_path, d))]
152
188
  snapshots.sort(key=os.path.getctime)
153
189
  while len(snapshots) > self.keep_snapshots:
154
190
  shutil.rmtree(snapshots.pop(0))
155
191
 
156
192
  def check_user_input(self):
157
- # Check if all services have unique names
193
+ # Check if all services in the application have unique names
158
194
  service_names = [service.name for service in self.services()]
159
195
  if len(service_names) != len(set(service_names)):
160
- raise ValueError('All services must have unique names.')
196
+ raise ValueError('RCCN-Error: App \''+self.name+'\' has multiple services with the same name.')
161
197
 
162
- # Check if all services have unique service_ids
198
+ # Check if all services in the application have unique service_ids
163
199
  service_ids = [service.service_id for service in self.services()]
164
200
  if len(service_ids) != len(set(service_ids)):
165
- raise ValueError('All services must have unique service_ids.')
201
+ raise ValueError('RCCN-Error: App \''+self.name+'\' has multiple services with the same ID.')
166
202
 
167
- # Check if all commands have unique names
168
- command_names = []
203
+ # Check if all commands in each service have unique names
169
204
  for service in self.services():
205
+ command_names = []
170
206
  command_names += [command.name for command in service.rccn_commands()]
171
- if len(command_names) != len(set(command_names)):
172
- raise ValueError('All commands must have unique names.')
207
+ if len(command_names) != len(set(command_names)):
208
+ raise ValueError('RCCN-Error: Service \''+service.name+'\' has multiple commands with the same name.')
173
209
 
174
- # Check if all commands have unique subtypes
175
- command_subtypes = []
210
+ # Check if all commands in each service have unique subtypes
176
211
  for service in self.services():
212
+ command_subtypes = []
177
213
  command_subtypes += [command.assignments['subtype'] for command in service.rccn_commands()]
178
- if len(command_subtypes) != len(set(command_subtypes)):
179
- raise ValueError('All commands must have unique subtypes.')
214
+ if len(command_subtypes) != len(set(command_subtypes)):
215
+ raise ValueError('RCCN-Error: Service \''+service.name+'\' has multiple commands with the same subtype.')
180
216
 
181
217
  def generate_cargo_toml_file(self):
182
218
  with open(self.file_paths()['cargo_toml_template'], 'r') as file:
@@ -218,10 +254,7 @@ class Service(Subsystem):
218
254
  name=self.name,
219
255
  *self.init_args, **self.init_kwargs
220
256
  )
221
- self.export_file_path = self.system.export_file_path
222
- self.snapshot_file_path = self.system.snapshot_file_path
223
257
  self.snapshots = self.system.snapshots
224
- self.diff_file_path = self.system.diff_file_path
225
258
 
226
259
  def add_container(self, container):
227
260
  if not isinstance(container, RCCNContainer):
@@ -235,22 +268,22 @@ class Service(Subsystem):
235
268
 
236
269
  def file_paths(self):
237
270
  paths = {
238
- 'service': os.path.join(self.export_file_path, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
239
- 'service_generated_snapshot': os.path.join(self.snapshot_file_path, 'generated', 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
240
- 'service_user_snapshot': os.path.join(self.snapshot_file_path, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
241
- 'service_diff': os.path.join(self.diff_file_path, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.diff'),
271
+ 'service': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
272
+ 'service_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
273
+ 'service_user_snapshot': os.path.join(self.snapshot_directory, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.rs'),
274
+ 'service_diff': os.path.join(self.diff_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'service.diff'),
242
275
  'service_template': os.path.join(self.text_modules_service_path, 'service.txt'),
243
- 'command': os.path.join(self.export_file_path, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
244
- 'command_generated_snapshot': os.path.join(self.snapshot_file_path, 'generated', 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
245
- 'command_user_snapshot': os.path.join(self.snapshot_file_path, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
246
- 'command_diff': os.path.join(self.diff_file_path, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.diff'),
276
+ 'command': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
277
+ 'command_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
278
+ 'command_user_snapshot': os.path.join(self.snapshot_directory, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
279
+ 'command_diff': os.path.join(self.diff_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.diff'),
247
280
  'command_template': os.path.join(self.text_modules_command_path, 'command.txt'),
248
- 'mod': os.path.join(self.export_file_path, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'mod.rs'),
281
+ 'mod': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'mod.rs'),
249
282
  'mod_template': os.path.join(self.text_modules_path, 'mod', 'mod.txt'),
250
- 'telemetry': os.path.join(self.export_file_path, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.rs'),
251
- 'telemetry_generated_snapshot': os.path.join(self.snapshot_file_path, 'generated', 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.rs'),
252
- 'telemetry_user_snapshot': os.path.join(self.snapshot_file_path, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
253
- 'telemetry_diff': os.path.join(self.diff_file_path, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.diff'),
283
+ 'telemetry': os.path.join(self.export_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.rs'),
284
+ 'telemetry_generated_snapshot': os.path.join(self.snapshot_directory, 'generated', 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.rs'),
285
+ 'telemetry_user_snapshot': os.path.join(self.snapshot_directory, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'command.rs'),
286
+ 'telemetry_diff': os.path.join(self.diff_directory, 'rccn_usr_'+snakecase(self.system.name), 'src', snakecase(self.name), 'telemetry.diff'),
254
287
  'telemetry_template': os.path.join(self.text_modules_telemetry_path, 'telemetry.txt'),
255
288
  }
256
289
  return paths
@@ -327,8 +360,8 @@ class Service(Subsystem):
327
360
  command_file_path = self.file_paths()['command_template']
328
361
  with open(command_file_path, 'r') as file:
329
362
  command_file_text = file.read()
330
- command_export_file_path = os.path.join(export_file_dir, snakecase(self.name), 'command.rs')
331
- with open(command_export_file_path, 'w') as file:
363
+ command_export_directory = os.path.join(export_file_dir, snakecase(self.name), 'command.rs')
364
+ with open(command_export_directory, 'w') as file:
332
365
  file.write(self.find_and_replace_keywords(command_file_text, text_modules_path))
333
366
  # Create snapshot of command.rs if instructed
334
367
  if self.snapshots:
@@ -384,20 +417,93 @@ class Service(Subsystem):
384
417
  container.__class__ = RCCNContainer
385
418
  telemetry_definition_text += container.generate_rccn_telemetry()
386
419
  return telemetry_definition_text
420
+
421
+ def create_and_add_command(
422
+ self,
423
+ name: str,
424
+ *,
425
+ aliases: Mapping[str, str] | None = None,
426
+ short_description: str | None = None,
427
+ long_description: str | None = None,
428
+ extra: Mapping[str, str] | None = None,
429
+ abstract: bool = False,
430
+ base: Command | str | None = None,
431
+ assignments: Mapping[str, Any] | None = None,
432
+ arguments: Sequence[Argument] | None = None,
433
+ entries: Sequence[CommandEntry] | None = None,
434
+ level: CommandLevel = CommandLevel.NORMAL,
435
+ warning_message: str | None = None,
436
+ constraint: (
437
+ Union[TransmissionConstraint, Sequence[TransmissionConstraint]] | None
438
+ ) = None,
439
+ ):
440
+ Command(
441
+ name=name,
442
+ system=self,
443
+ aliases=aliases,
444
+ short_description=short_description,
445
+ long_description=long_description,
446
+ extra=extra,
447
+ abstract=abstract,
448
+ base=base,
449
+ assignments=assignments,
450
+ arguments=arguments,
451
+ entries=entries,
452
+ level=level,
453
+ warning_message=warning_message,
454
+ constraint=constraint
455
+ )
456
+
457
+ def rccn_container(self):
458
+ return [container for container in self.containers if isinstance(container, RCCNContainer)]
387
459
 
388
460
 
389
461
  class RCCNCommand(Command):
390
- def __init__(self, *args, **kwargs):
391
- self.init_args = args
392
- self.init_kwargs = kwargs
462
+ def __init__(
463
+ self,
464
+ name: str,
465
+ *,
466
+ aliases: Mapping[str, str] | None = None,
467
+ short_description: str | None = None,
468
+ long_description: str | None = None,
469
+ extra: Mapping[str, str] | None = None,
470
+ abstract: bool = False,
471
+ base: Command | str | None = None,
472
+ assignments: Mapping[str, Any] | None = None,
473
+ arguments: Sequence[Argument] | None = None,
474
+ entries: Sequence[CommandEntry] | None = None,
475
+ level: CommandLevel = CommandLevel.NORMAL,
476
+ warning_message: str | None = None,
477
+ constraint: (
478
+ Union[TransmissionConstraint, Sequence[TransmissionConstraint]] | None
479
+ ) = None,
480
+ **kwargs
481
+ ):
482
+ self.init_args = ()
483
+ self.init_kwargs = {
484
+ 'name': name,
485
+ 'aliases': aliases,
486
+ 'short_description': short_description,
487
+ 'long_description': long_description,
488
+ 'extra': extra,
489
+ 'abstract': abstract,
490
+ 'base': base,
491
+ 'assignments': assignments,
492
+ 'arguments': arguments,
493
+ 'entries': entries,
494
+ 'level': level,
495
+ 'warning_message': warning_message,
496
+ 'constraint': constraint,
497
+ **kwargs
498
+ }
393
499
  if 'system' in kwargs and isinstance(kwargs['system'], Service):
394
500
  self.add_to_service(kwargs['system'])
395
- elif 'base' in kwargs and (isinstance(kwargs['base'], Command) or isinstance(kwargs['base'], RCCNCommand)):
396
- self.add_to_service(kwargs['base'].system)
501
+ elif base and (isinstance(base, Command) or isinstance(base, RCCNCommand)):
502
+ self.add_to_service(base.system)
397
503
 
398
504
  def add_to_service(self, service):
399
505
  if not 'base' in self.init_kwargs and not any(command.name == 'base' for command in service.commands):
400
- print("RCCN-Information: Command \'"+self.init_kwargs['name']+"\' doesn\'t have a base argument and no base command was found in service \'"+service.name+"\'.\nStandard base command will be used with system = \'"+service.name+"\' and type = "+str(service.service_id)+".")
506
+ print("RCCN-Information: Command \'"+self.init_kwargs['name']+"\' doesn\'t have a base argument and no base command was found in service \'"+service.name+"\'.\nStandard base command will be created with system = \'"+service.name+"\' and type = "+str(service.service_id)+".")
401
507
  self.init_kwargs['base'] = Command(
402
508
  system=service,
403
509
  name='base',
@@ -413,6 +519,13 @@ class RCCNCommand(Command):
413
519
  else:
414
520
  super().__init__(system=service, *self.init_args, **self.init_kwargs)
415
521
  self.assignments['apid'] = self.system.system.apid
522
+ if not 'subtype' in self.assignments and self.name is not 'base':
523
+ used_subtypes = [command.assignments['subtype'] if 'subtype' in command.assignments else None for command in self.system.rccn_commands()]
524
+ new_subtype = 1
525
+ while new_subtype in used_subtypes:
526
+ new_subtype = new_subtype + 1
527
+ print('RCCN-Information: Command \''+self.name+'\' has no subtype specified. Subtype will be set to '+str(new_subtype)+'.')
528
+ self.assignments['subtype'] = new_subtype
416
529
  self.struct_name = self.name + 'Args'
417
530
 
418
531
 
@@ -445,12 +558,8 @@ class RCCNCommand(Command):
445
558
  text = replace_with_indentation(text, command_var_keyword, command_var_translation[command_var_keyword]())
446
559
  return text
447
560
 
448
- def check_user_input(self):
449
- if self.assignments['subtype'] == None and self.name != 'base':
450
- raise ValueError('Command '+self.name+' does not have a subtype assigned to it.')
451
-
452
561
  def user_snapshot_path(self):
453
- return os.path.join(self.snapshot_file_path, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
562
+ return os.path.join(self.snapshot_directory, 'user', datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
454
563
 
455
564
  def struct_definition(self):
456
565
  struct_definition_text = ""
@@ -470,22 +579,57 @@ class RCCNCommand(Command):
470
579
  struct_definition_text += append
471
580
  return struct_definition_text
472
581
 
582
+
583
+
473
584
  class RCCNContainer(Container):
474
- def __init__(self, base="/PUS/pus-tm", subtype = None, *args, **kwargs):
585
+ def __init__(
586
+ self,
587
+ base="/PUS/pus-tm",
588
+ subtype = None,
589
+ *,
590
+ system: System = None,
591
+ name: str = None,
592
+ entries: Sequence[ParameterEntry | ContainerEntry] | None = None,
593
+ abstract: bool = False,
594
+ condition: Expression | None = None,
595
+ aliases: Mapping[str, str] | None = None,
596
+ short_description: str | None = None,
597
+ long_description: str | None = None,
598
+ extra: Mapping[str, str] | None = None,
599
+ bits: int | None = None,
600
+ rate: float | None = None,
601
+ hint_partition: bool = False,
602
+ ):
475
603
  self.base = base
476
604
  self.subtype = subtype
477
- self.init_args = args
478
- self.init_kwargs = kwargs
479
- self.init_kwargs['base'] = base
480
- self.name = kwargs['name']
481
- if 'system' in kwargs and isinstance(kwargs['system'], Service):
482
- self.add_to_service(kwargs['system'])
605
+ self.init_kwargs = {
606
+ 'system': system,
607
+ 'name': name,
608
+ 'entries': entries,
609
+ 'base': base,
610
+ 'abstract': abstract,
611
+ 'condition': condition,
612
+ 'aliases': aliases,
613
+ 'short_description': short_description,
614
+ 'long_description': long_description,
615
+ 'extra': extra,
616
+ 'bits': bits,
617
+ 'rate': rate,
618
+ 'hint_partition': hint_partition,
619
+ }
620
+
621
+ if name is None:
622
+ raise ValueError('RCCN-Error: Container must have a name.')
623
+
624
+ self.name = name
625
+ if system is not None and isinstance(system, Service):
626
+ self.add_to_service(system)
483
627
 
484
628
  def add_to_service(self, service):
485
629
  self.type = service.service_id
486
630
  condition_type = None
487
631
  condition_subtype = None
488
- if 'condition' in self.init_kwargs:
632
+ if self.init_kwargs['condition'] is not None:
489
633
  for eq_expression in self.init_kwargs['condition'].expressions:
490
634
  if eq_expression.ref == self.base+'/type':
491
635
  condition_type = eq_expression.value
@@ -498,18 +642,27 @@ class RCCNContainer(Container):
498
642
  print('RCCN-Warning: Container '+self.name+' has an ambiguous user-defined subtype. \'subtype\' argument should match the \'condition\' argument.')
499
643
  elif condition_subtype is not None:
500
644
  self.subtype = condition_subtype
501
- elif self.subtype is not None and not 'condition' in self.init_kwargs:
645
+ elif self.subtype is not None and self.init_kwargs['condition'] is not None:
502
646
  self.init_kwargs['condition'] = AndExpression(
503
647
  EqExpression(self.base+'/type', self.type),
504
648
  EqExpression(self.base+'/subtype', self.subtype)
505
649
  )
506
650
  else:
507
- raise ValueError('RCCN-Error: Container '+self.name+' has no \'condition\' argument and condition could not be created due to a missing \'subtype\' argument.')
651
+ used_subtypes = [container.subtype for container in service.rccn_container()]
652
+ new_subtype = 1
653
+ while new_subtype in used_subtypes:
654
+ new_subtype = new_subtype + 1
655
+ self.subtype = new_subtype
656
+ self.init_kwargs['condition'] = AndExpression(
657
+ EqExpression(self.base+'/type', self.type),
658
+ EqExpression(self.base+'/subtype', self.subtype)
659
+ )
660
+ print('RCCN-Information: Subtype for Container '+self.name+' is not specified through \'subtype\' or \'condition\' arguments. Subtype will be set to '+str(self.subtype)+'.')
508
661
 
509
662
  if 'system' in self.init_kwargs and isinstance(self.init_kwargs['system'], Service):
510
- super().__init__(*self.init_args, **self.init_kwargs)
663
+ super().__init__(**self.init_kwargs)
511
664
  else:
512
- super().__init__(system=service, *self.init_args, **self.init_kwargs)
665
+ super().__init__(system=service, **self.init_kwargs)
513
666
 
514
667
  def generate_rccn_telemetry(self):
515
668
  rccn_telemetry_text = ""
@@ -527,4 +680,46 @@ class RCCNContainer(Container):
527
680
  rccn_telemetry_text += insert
528
681
  rccn_telemetry_text += "}\n\n"
529
682
  rccn_telemetry_text += append
530
- return rccn_telemetry_text
683
+ return rccn_telemetry_text
684
+
685
+ def add_integer_parameter(
686
+ self,
687
+ name: str,
688
+ signed: bool = True,
689
+ bits: int = 32,
690
+ minimum: int | None = None,
691
+ maximum: int | None = None,
692
+ aliases: Mapping[str, str] | None = None,
693
+ data_source: DataSource = DataSource.TELEMETERED,
694
+ initial_value: Any = None,
695
+ persistent: bool = True,
696
+ short_description: str | None = None,
697
+ long_description: str | None = None,
698
+ extra: Mapping[str, str] | None = None,
699
+ units: str | None = None,
700
+ encoding: Encoding | None = None,
701
+ calibrator: Calibrator | None = None,
702
+ alarm: ThresholdAlarm | None = None,
703
+ context_alarms: Sequence[ThresholdContextAlarm] | None = None,
704
+ ):
705
+ int_parameter = IntegerParameter(
706
+ system=self,
707
+ name=name,
708
+ signed=signed,
709
+ bits=bits,
710
+ minimum=minimum,
711
+ maximum=maximum,
712
+ aliases=aliases,
713
+ data_source=data_source,
714
+ initial_value=initial_value,
715
+ persistent=persistent,
716
+ short_description=short_description,
717
+ long_description=long_description,
718
+ extra=extra,
719
+ units=units,
720
+ encoding=encoding,
721
+ calibrator=calibrator,
722
+ alarm=alarm,
723
+ context_alarms=context_alarms
724
+ )
725
+ self.entries.append(int_parameter)
@@ -0,0 +1,17 @@
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"
File without changes
File without changes
File without changes