Skip to content

Commit

Permalink
Update docs node (#1446)
Browse files Browse the repository at this point in the history
* update node documentation

* update visualization

* update visualization and use example box

* may the node do stuff
  • Loading branch information
schluis authored Oct 11, 2024
1 parent 960de36 commit 89c01c4
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 66 deletions.
39 changes: 38 additions & 1 deletion docs/framework/node.drawio
Original file line number Diff line number Diff line change
@@ -1 +1,38 @@
<mxfile host="app.diagrams.net" modified="2022-12-19T20:50:58.172Z" agent="5.0 (X11)" etag="7sCr4pDmIB3cz9DKTJv3" version="20.5.1" type="device"><diagram id="XcJZ0W03TQX2NoV_ZfGi" name="Page-1">3VfJbtswEP0aAe0hhSV5iY+JszRAusEHO70R0lhiS4sCNfLSry9pDS3KUrO0TtJWBgzOmxmSem+4yAsny821Ynn6QcYgvKAXb7zwwguC0Wmg/w2wrYBBf1ABieJxBfk1MOU/gMAeoSWPoWgEopQCed4EI5llEGEDY0rJdTNsIUVz1JwlNGKvBqYRE9AKm/EY0wo9HTjR74EnqR3Z75FnyWwwAUXKYrl2oPDSCydKSqxay80EhOHO8lLlXf3Cu5+YggwfkzDv5XN1doP9q9uvKl+cfJmtZidh1cuKiZJe+KNWkCaMW8uCkmUWg+nI98LzdcoRpjmLjHetZddYiktB7hUo5JrBM8GTTGMoTQBTEanrm6AFF2IihVS7AcLFwPwMLjN08OrReIFKfgfHM9w92tPmgagx84CNAxEv1yCXgGqrQ6zXakRFOiJzXSvuDwlLXbUtyKjKkn3XtRC6QVo8QZd+S5cpMrxPmN7DwhyDqX6TqX25u1QFHVT1n4upYYsTiPUKJlMqTGUiMyYua/S8yVodcytNqe64+gaIWypYVqJsMgkbjnOT/m5A1h11ZtoXG9fYWiPTr+skGfPO9meMOm1n2bwCmcIzs5VpIBKsKHhk4SsuHhS3kKWK4B4GaUPW/SWAD9ekoffeUlEgGPJVcwM9uu6j19Tdd1Xf10C37n8szPEIp9TPkuup7Nd06B+s6fHBWq0qg7IOZNtP4/eVPH3VFfwEJR+5EiGLbVAmM6gQ8v8TBTF+3XoYtM6+aBsJePP27zv9ui4KL3r6jVtcecFQ6FHPY77SzcQ0b7K8xMI69DiOr8WppgKbxDWvXlTS7v2NIEb3vUgTDKrjIrjkcbxbvV1KNbU8glijA62GYUurrjtd+FxS2V3W0epTiZUy/6sGwWj8UiJos/6Wqrai+oM0vPwJ</diagram></mxfile>
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (X11; Linux x86_64; rv:129.0) Gecko/20100101 Firefox/129.0" version="24.7.14">
<diagram id="XcJZ0W03TQX2NoV_ZfGi" name="Page-1">
<mxGraphModel dx="512" dy="283" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="0" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="X0pXrAIt4FLZrpf-QWvW-3" value="&lt;font data-font-src=&quot;https://fonts.googleapis.com/css?family=Fira+Code&quot; face=&quot;Fira Code&quot;&gt;Node&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;verticalAlign=top;arcSize=11;fillColor=#DEDEDE;fontColor=#333333;strokeColor=default;fillStyle=auto;gradientColor=none;glass=0;" parent="1" vertex="1">
<mxGeometry x="120" y="70" width="160" height="160" as="geometry" />
</mxCell>
<mxCell id="X0pXrAIt4FLZrpf-QWvW-4" value="&lt;font style=&quot;font-size: 13px;&quot; data-font-src=&quot;https://fonts.googleapis.com/css?family=Fira+Code&quot; face=&quot;Fira Code&quot;&gt;State&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=default;fillStyle=auto;" parent="1" vertex="1">
<mxGeometry x="140" y="100" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="X0pXrAIt4FLZrpf-QWvW-6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;startArrow=blockThin;startFill=1;endArrow=blockThin;endFill=1;" parent="1" source="X0pXrAIt4FLZrpf-QWvW-5" target="X0pXrAIt4FLZrpf-QWvW-4" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="X0pXrAIt4FLZrpf-QWvW-7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;endArrow=blockThin;endFill=1;" parent="1" source="X0pXrAIt4FLZrpf-QWvW-5" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="310" y="190" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="X0pXrAIt4FLZrpf-QWvW-8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;startArrow=blockThin;startFill=1;endArrow=none;endFill=0;strokeColor=default;" parent="1" source="X0pXrAIt4FLZrpf-QWvW-5" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="90" y="190" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="X0pXrAIt4FLZrpf-QWvW-5" value="&lt;font style=&quot;font-size: 13px;&quot; data-font-src=&quot;https://fonts.googleapis.com/css?family=Fira+Code&quot; face=&quot;Fira Code&quot;&gt;cycle()&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="140" y="170" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="X0pXrAIt4FLZrpf-QWvW-9" value="&lt;div&gt;&lt;font data-font-src=&quot;https://fonts.googleapis.com/css?family=Fira+Code&quot; face=&quot;Fira Code&quot;&gt;Inputs&lt;/font&gt;&lt;/div&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="70" y="163" width="50" height="30" as="geometry" />
</mxCell>
<mxCell id="X0pXrAIt4FLZrpf-QWvW-10" value="&lt;font data-font-src=&quot;https://fonts.googleapis.com/css?family=Fira+Code&quot; face=&quot;Fira Code&quot;&gt;Outputs&lt;/font&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="280" y="163" width="60" height="30" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
3 changes: 3 additions & 0 deletions docs/framework/node.drawio-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions docs/framework/node.drawio-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 0 additions & 3 deletions docs/framework/node.drawio.png

This file was deleted.

163 changes: 101 additions & 62 deletions docs/framework/nodes.md
Original file line number Diff line number Diff line change
@@ -1,74 +1,113 @@
# Nodes

Nodes usually contain robotics code and are interchangeable components within cyclers.
Each node is characterized by a `cycle()` function which is called in each cycle.
The function gets node's inputs as parameters to the `cycle()` function and returns node's outputs from it.
In addition, nodes consist of a state which is perserved between cycles.
Each node is characterized by a `new()` function which is called once at creation and a `cycle()` function which is called in each cycle.
The function gets other node's inputs as parameters to the `cycle()` function and may compute an output from that.
In addition, nodes may contain a state which is preserved between cycles.

![node](./node.drawio.png)
<figure markdown="span">
![node](./node.drawio-light.png#only-light)
![node](./node.drawio-dark.png#only-dark)
</figure>

Nodes are normal Rust structs where the struct's fields represent the state and a method called `cycle()` in the `impl` of the node represents the `cycle()` function.
This concept allows to write nodes in a very Rusty way.
A node may have multiple inputs of different kinds which can be annotated to the node.
Here is an example node, but for more information see [Macros](./macros.md):

```rust
pub struct SolePressureFilter { // (1)
left_sole_pressure: LowPassFilter<f32>,
right_sole_pressure: LowPassFilter<f32>,
}

#[node(control)] // (2)
#[parameter(path = low_pass_alpha, data_type = f32)] // (3)
#[input(path = sensor_data, data_type = SensorData)] // (4)
#[main_output(data_type = SolePressure)] // (5)
impl SolePressureFilter {} // (6)

impl SolePressureFilter {
fn new(context: NewContext) -> anyhow::Result<Self> { // (7)
Ok(Self {
left_sole_pressure: LowPassFilter::with_alpha(
0.0,
*context.low_pass_alpha, // (8)
),
right_sole_pressure: LowPassFilter::with_alpha(
0.0,
*context.low_pass_alpha,
),
})

!!! example

```rust
use std::{collections::VecDeque, time::SystemTime};

use color_eyre::Result;
use context_attribute::context;
use framework::{MainOutput, PerceptionInput};
use serde::{Deserialize, Serialize};
use types::{cycle_time::CycleTime, filtered_whistle::FilteredWhistle, whistle::Whistle};

#[derive(Deserialize, Serialize)]
pub struct WhistleFilter { // (1)
detection_buffer: VecDeque<bool>,
was_detected_last_cycle: bool,
last_detection: Option<SystemTime>,
}

#[context]
pub struct CreationContext {} // (2)

#[context]
pub struct CycleContext { // (3)
buffer_length: Parameter<usize, "whistle_filter.buffer_length">, // (4)
minimum_detections: Parameter<usize, "whistle_filter.minimum_detections">,

cycle_time: Input<CycleTime, "cycle_time">, // (5)
detected_whistle: PerceptionInput<Whistle, "Audio", "detected_whistle">, // (6)
}

fn cycle(&mut self, context: CycleContext) -> anyhow::Result<MainOutputs> { // (9)
let force_sensitive_resistors =
&require_some!(context.sensor_data).force_sensitive_resistors;

let left_sole_pressure = force_sensitive_resistors.left.sum();
self.left_sole_pressure.update(left_sole_pressure);
let right_sole_pressure = force_sensitive_resistors.right.sum();
self.right_sole_pressure.update(right_sole_pressure);

Ok(MainOutputs {
sole_pressure: Some(SolePressure {
left: self.left_sole_pressure.state(),
right: self.right_sole_pressure.state(),
}),
})
#[context]
#[derive(Default)]
pub struct MainOutputs {
pub filtered_whistle: MainOutput<FilteredWhistle>,
}
}
```

1. Node's state
2. Node declaration with `node` [macro](./macros.md)
3. Configuration parameter of type `f32`
4. Input of type `SensorData`
5. Output of type `SolePressure`
6. Empty `impl` to improve usability of language servers and code linters. If the node declaration would be attached to the `impl` below, when writing incomplete code, the macros would produce errors. This happens a lot if writing node implementation code.
7. Will be called at construction of the node
8. Use declared configuration parameter. Since it is a reference, we need to dereference it with `*`.
9. Will be called every cycle

This node consumes the type `SensorData` as input and produces the output `SolePressure`.
It has two state variables `left_sole_pressure` and `right_sole_pressure`.

This specification of node inputs and outputs leads to a dependency graph which allows to topologically sort nodes s.t. all dependencies are met before executing the node's `cycle()`.

impl WhistleFilter {
pub fn new(_context: CreationContext) -> Result<Self> { // (7)
Ok(Self {
detection_buffer: Default::default(),
was_detected_last_cycle: false,
last_detection: None,
})
}

pub fn cycle(&mut self, context: CycleContext) -> Result<MainOutputs> { // (9)
let cycle_start_time = context.cycle_time.start_time;

for &is_detected in context
.detected_whistle
.persistent
.values()
.flatten()
.flat_map(|whistle| &whistle.is_detected)
{
self.detection_buffer.push_front(is_detected);
}
self.detection_buffer.truncate(*context.buffer_length); // (8)
let number_of_detections = self
.detection_buffer
.iter()
.filter(|&&was_detected| was_detected)
.count();
let is_detected = number_of_detections > *context.minimum_detections;
let started_this_cycle = is_detected && !self.was_detected_last_cycle;
if started_this_cycle {
self.last_detection = Some(cycle_start_time);
}
self.was_detected_last_cycle = is_detected;

Ok(MainOutputs {
filtered_whistle: FilteredWhistle {
is_detected,
last_detection: self.last_detection,
started_this_cycle,
}
.into(),
})
}
}
```

1. Node's state
2. Creation context. Its contents are available in the `new(context: CreationContext) -> Result<Self>` function
3. Cycle context. Its contents are available in the `cycle(&mut self, context: CycleContext) -> Result<MainOutputs>` function
4. Parameter from the `default.json`. Can be changed during runtime by e.g. using [twix](../tooling/twix.md).
5. Input from another node of type `CycleTime`.
6. Input from another node, but with persistent and transient data.
7. Will be called at construction of the node
8. Use declared configuration parameter. Since it is a reference, we need to dereference it with `*`.
9. Will be called every cycle

This node consumes the types `CycleTime` and `Whistle` as inputs and produces the output `FilteredWhistle`.
It has three state variables; `detection_buffer`, `was_detected_last_cycle` and `last_detection`.

The specification of node inputs and outputs leads to a dependency graph which allows to topologically sort nodes s.t. all dependencies are met before executing the node's `cycle()`.
The `build.rs` file automatically sorts nodes based on this graph.

0 comments on commit 89c01c4

Please sign in to comment.