-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* update node documentation * update visualization * update visualization and use example box * may the node do stuff
- Loading branch information
Showing
5 changed files
with
145 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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="<font data-font-src="https://fonts.googleapis.com/css?family=Fira+Code" face="Fira Code">Node</font>" 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="<font style="font-size: 13px;" data-font-src="https://fonts.googleapis.com/css?family=Fira+Code" face="Fira Code">State</font>" 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="<font style="font-size: 13px;" data-font-src="https://fonts.googleapis.com/css?family=Fira+Code" face="Fira Code">cycle()</font>" 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="<div><font data-font-src="https://fonts.googleapis.com/css?family=Fira+Code" face="Fira Code">Inputs</font></div>" 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="<font data-font-src="https://fonts.googleapis.com/css?family=Fira+Code" face="Fira Code">Outputs</font>" 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> |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
|
||
 | ||
<figure markdown="span"> | ||
 | ||
 | ||
</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. |