Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ serde = { version = "1.0.228", features = ["derive"] }
# Test-only helper binary dependencies (enabled via feature)
caps = { version = "0.5", optional = true }
libc = { version = "0.2", optional = true }
toml = "1.1.2"

[features]
# Enables the `ferroflow-vcan` helper binary used by integration tests.
Expand Down
39 changes: 36 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Ferroflow
Ferroflow is the new control software for all Liquid Rocketry projects at the TU Wien Space Team.

Ferroflow is the new control software for all Liquid Rocketry projects at the TU Wien Space Team.
It interfaces with our custom Engine Control Units ECUs, through our custom [LiquidCAN protocol](https://github.com/SpaceTeam/LiquidCAN/).
On the other end, it provides a high-level API for our [ECUI](https://github.com/SpaceTeam/web_ecui_houbolt), which is the user interface for our ECUs.

Expand All @@ -10,51 +11,81 @@ On the other end, it provides a high-level API for our [ECUI](https://github.com
Some integration tests talk to the ECUemulator over SocketCAN. For that you use a virtual CAN interface.

### Test helper: `ferroflow-vcan`

For test environments, this repo provides a small helper binary that can be granted `CAP_NET_ADMIN` once via `setcap`.
Integration tests will automatically use it (if it’s available on `PATH`) to create/delete `vcan` interfaces without sudo.

Build the helper (feature-gated; not part of normal builds):

```bash
cargo build --release --features test-vcan --bin ferroflow-vcan
```

Put it on PATH (recommended for tests):

```bash
install -m 0755 ./target/release/ferroflow-vcan ~/.local/bin/ferroflow-vcan
sudo setcap cap_net_admin+ep ~/.local/bin/ferroflow-vcan
```

Manual usage:

```bash
ferroflow-vcan up vcan0
ferroflow-vcan down vcan0
```


## Development

### Mapping Configuration

`mapping_path` in `config.yml` points to a directory containing `.toml` files, which are loaded in sorted order and validated together.

Mappings are grouped by node name:

```toml
[[mapping.FuelECU]]
name = "fuel_level"
type = "telemetry"
raw_field = "level_adc"
value = { slope = 0.5, offset = 1.0, unit = "mAh" }

logical = [
{ range = { min = 100 }, value = "High", color = "#ff0000" },
{ range = { min = 50, max = 100 }, value = "Normal" },
{ range = { max = 50 }, value = "Low" },
]
```

The repository includes [schemas/mapping.schema.json](schemas/mapping.schema.json) and [taplo.toml](taplo.toml) so Taplo-compatible editors, including VS Code with Even Better TOML, can validate mapping files before the application loads them. The schema is associated with `mapping.toml` files and TOML files under `mapping/` or `mappings/` directories.

### Running CI Checks

The repository includes a CI script (`ci-rust.sh`) that runs all quality checks on the Rust implementation. This script is used both locally and in GitHub Actions

**Run all checks:**

```bash
./ci-rust.sh
# or explicitly
./ci-rust.sh all
```

**Run individual checks:**

```bash
./ci-rust.sh build # Build the project
./ci-rust.sh test # Run tests
./ci-rust.sh fmt # Check code formatting
./ci-rust.sh clippy # Run clippy linter
```

You can fix formatting or linter issues by adding the -fix suffix to the command. e.g: `./ci-rust.sh clippy-fix`

### Running `fmt` and `clippy` as a pre-commit hook

A pre-commit hook script is available in `.githooks`, which executes the CI script with `fmt` and `clippy` only and without the `fix` option. To setup the hook, configure git to use the `.githooks` directory and make the `pre-commit` file executable.

```bash
git config core.hooksPath .githooks
chmod u+x .githooks/pre-commit
Expand All @@ -66,6 +97,7 @@ chmod u+x .githooks/pre-commit

We use TimescaleDB, which is an extension of PostgreSQL optimized for time-series data. You can install it by following the instructions on the [TimescaleDB installation page](https://docs.timescale.com/install/latest/).
Using docker is recommended for local development (if you already have another instance of postgres running, use e.g. `-p 5433:5432` instead of `-p 5432:5432`):

```bash
docker run -d --name timescaledb -p 5432:5432 -e POSTGRES_PASSWORD=yourpassword timescale/timescaledb:latest-pg18
```
Expand All @@ -76,6 +108,7 @@ The project uses Diesel for database interactions. Diesel CLI is recommended for

**Running Diesel CLI**
Here's some common commands:

```bash
export DATABASE_URL=postgres://postgres:yourpassword@localhost:5432/ferroflow # Set the database URL
diesel setup # Set up the database
Expand All @@ -97,4 +130,4 @@ Database tests use `testcontainers` to start a temporary TimescaleDB/PostgreSQL
There are two examples in the repository:

- a unit test in `src/db/mod.rs`
- an integration test in `tests/db_logging.rs`
- an integration test in `tests/db_logging.rs`
121 changes: 121 additions & 0 deletions schemas/mapping.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://spaceteam.at/ferroflow/schemas/mapping.schema.json",
"title": "FerroFlow Mapping",
"description": "TOML schema for FerroFlow node mapping files.",
"type": "object",
"required": ["mapping"],
"additionalProperties": false,
"properties": {
"mapping": {
"type": "object",
"description": "Mappings grouped by LiquidCAN node/device name.",
"minProperties": 1,
"additionalProperties": false,
"patternProperties": {
".+": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/mappingEntry"
}
}
}
}
},
"$defs": {
"mappingEntry": {
"type": "object",
"additionalProperties": false,
"required": ["name", "type", "raw_field"],
"properties": {
"name": {
"type": "string",
"minLength": 1,
"description": "Unique application-facing mapping name."
},
"type": {
"type": "string",
"enum": ["telemetry", "parameter"],
"description": "Whether the raw field is telemetry or a writable parameter."
},
"raw_field": {
"type": "string",
"minLength": 1,
"description": "Raw LiquidCAN field name on the enclosing node."
},
"value": {
"$ref": "#/$defs/valueParams"
},
"logical": {
"type": "array",
"description": "Logical labels for mapped numeric ranges. Runtime validation requires these ranges to be non-overlapping and exhaustive when present.",
"items": {
"$ref": "#/$defs/logicalRule"
}
}
}
},
"valueParams": {
"type": "object",
"additionalProperties": false,
"required": ["slope", "offset"],
"properties": {
"slope": {
"type": "number",
"not": { "const": 0 },
"default": 1.0,
"description": "Linear conversion slope: mapped = raw * slope + offset."
},
"offset": {
"type": "number",
"default": 0.0,
"description": "Linear conversion offset: mapped = raw * slope + offset."
},
"unit": {
"type": "string",
"default": ""
}
}
},
"logicalRule": {
"type": "object",
"additionalProperties": false,
"required": ["range", "value"],
"properties": {
"range": {
"$ref": "#/$defs/logicalRange"
},
"value": {
"description": "Logical value returned when the mapped numeric value is inside the range."
},
"color": {
"type": "string",
"description": "Optional display color, commonly a hex color such as #ff0000."
}
}
},
"logicalRange": {
"type": "object",
"additionalProperties": false,
"properties": {
"min": {
"type": "number",
"description": "Lower bound. Omit for an unbounded lower range."
},
"max": {
"type": "number",
"description": "Upper bound. Omit for an unbounded upper range."
},
"min_inclusive": {
"type": "boolean",
"default": true
},
"max_inclusive": {
"type": "boolean",
"default": false
}
}
}
}
}
1 change: 1 addition & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub struct Config {
pub can_bus_interfaces: Vec<String>,
pub heartbeat_period: u64,
pub database_url: String,
pub mapping_path: String,
}

pub fn load_config(path: &str) -> Result<Config> {
Expand Down
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ pub mod socket;
pub fn run_with_config(config: Config) -> anyhow::Result<()> {
let event_dispatcher = events::EventDispatcher::new();

let node_manager = nodes::NodeManager::new(&event_dispatcher);
let mapping = nodes::mapping::Mapping::load_mapping_from_path(&config.mapping_path)?;

let node_manager = nodes::NodeManager::new(&event_dispatcher, mapping);

run_with_dependencies(&event_dispatcher, &node_manager, config)
}
Expand Down
Loading
Loading