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