From dbaa1f0cf7844ce3b041bf8430058afbc1ccdb7b Mon Sep 17 00:00:00 2001 From: Ivan Nikolic Date: Sun, 18 Jan 2026 12:40:38 +0800 Subject: [PATCH 1/6] update advanced_streams.md with reactivex example --- docs/api/sensor_streams/advanced_streams.md | 24 +++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/api/sensor_streams/advanced_streams.md b/docs/api/sensor_streams/advanced_streams.md index 2ab6f04725..73ff60779a 100644 --- a/docs/api/sensor_streams/advanced_streams.md +++ b/docs/api/sensor_streams/advanced_streams.md @@ -122,14 +122,34 @@ class MLModel(Module): ``` +## Getting Values Synchronously +Sometimes you don't want a stream, you just want to call a function and get the latest value. +If you are doing this periodically as a part of a processing loop, it is very likely that your code will be much cleaner and safer using actual reactivex pipeline. So bias towards checking our [reactivex quick guide](reactivex.md) and [official docs](https://rxpy.readthedocs.io/) +(TODO we should actually make this example actually executable) +```python skip + self.color_image.observable().pipe( + # takes the best image from a stream every 200ms, + # ensuring we are feeding our detector with highest quality frames + quality_barrier(lambda x: x["quality"], target_frequency=0.2), + + # converts Image into Person detections + ops.map(detect_person) + + # converts Detection2D to Twist pointing in the direction of a detection + ops.map(detection2d_to_twist) + + # emits the latest value every 50ms making our control loop run at 20hz + # despire detections running at 200ms + ops.sample(0.05), + ).subscribe(self.twist.publish) # shoots off the Twist out of the module +``` -## Getting Values Synchronously -Sometimes you don't want a stream, you just want to call a function and get the latest value. We provide two approaches: +If you'd still like to switch to synchronious fetching from streams, we provide two approaches, getter_hot and getter_cold: | | `getter_hot()` | `getter_cold()` | |------------------|--------------------------------|----------------------------------| From ddb9ea1e69563f8ca4117293e0110b20eacc07a4 Mon Sep 17 00:00:00 2001 From: Ivan Nikolic Date: Sun, 18 Jan 2026 12:43:50 +0800 Subject: [PATCH 2/6] typos --- docs/api/sensor_streams/advanced_streams.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api/sensor_streams/advanced_streams.md b/docs/api/sensor_streams/advanced_streams.md index 73ff60779a..41c0d05565 100644 --- a/docs/api/sensor_streams/advanced_streams.md +++ b/docs/api/sensor_streams/advanced_streams.md @@ -137,19 +137,19 @@ If you are doing this periodically as a part of a processing loop, it is very li quality_barrier(lambda x: x["quality"], target_frequency=0.2), # converts Image into Person detections - ops.map(detect_person) + ops.map(detect_person), # converts Detection2D to Twist pointing in the direction of a detection - ops.map(detection2d_to_twist) + ops.map(detection2d_to_twist), # emits the latest value every 50ms making our control loop run at 20hz - # despire detections running at 200ms + # despite detections running at 200ms ops.sample(0.05), ).subscribe(self.twist.publish) # shoots off the Twist out of the module ``` -If you'd still like to switch to synchronious fetching from streams, we provide two approaches, getter_hot and getter_cold: +If you'd still like to switch to synchronous fetching, we provide two approaches, `getter_hot()` and `getter_cold()` | | `getter_hot()` | `getter_cold()` | |------------------|--------------------------------|----------------------------------| From 5804e74306e062b5878c71ccf2801b7215c72991 Mon Sep 17 00:00:00 2001 From: Ivan Nikolic Date: Sun, 18 Jan 2026 12:54:53 +0800 Subject: [PATCH 3/6] pikchr experiment --- docs/api/sensor_streams/advanced_streams.md | 63 +++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/docs/api/sensor_streams/advanced_streams.md b/docs/api/sensor_streams/advanced_streams.md index 41c0d05565..0caa95d3e2 100644 --- a/docs/api/sensor_streams/advanced_streams.md +++ b/docs/api/sensor_streams/advanced_streams.md @@ -158,6 +158,69 @@ If you'd still like to switch to synchronous fetching, we provide two approaches | **Resources** | Keeps connection open | Opens/closes each call | | **Use when** | Frequent reads, need latest | Occasional reads, save resources | +
+diagram source + +
Pikchr + +```pikchr fold output=assets/getter_hot_cold.svg +color = white +fill = none + +# getter_hot section +text "getter_hot()" bold at (0.5in, 1.1in) + +# Stream emitting values continuously +Stream: box "Stream" rad 5px fit wid 170% ht 170% at (0.5in, 0.5in) +arrow right 0.4in from Stream.e +Cache: box "Cache" rad 5px fit wid 170% ht 170% + +# Values flowing into cache +text "v1 v2 v3..." italic small at (Stream.x + 0.5in, Stream.y + 0.3in) + +# get_val() calls reading from cache instantly +arrow from Cache.e right 0.3in then down 0.25in then right 0.25in +Call1: text "get_val() → instant" small +arrow from Cache.e right 0.3in then down 0.6in then right 0.25in +Call2: text "get_val() → instant" small + +# Continuous subscription indicator +line dashed from Stream.s down 0.2in +text "always subscribed" italic small at (Stream.x, Stream.y - 0.45in) + + +# getter_cold section +text "getter_cold()" bold at (0.5in, -0.6in) + +# Each call creates fresh subscription +Cold1: box "get_val()" rad 5px fit wid 170% ht 170% at (0.5in, -1.0in) +arrow right 0.3in from Cold1.e +text "subscribe" small +arrow right 0.3in +text "wait" small +arrow right 0.3in +text "value" small +arrow right 0.3in +text "dispose" small + +Cold2: box "get_val()" rad 5px fit wid 170% ht 170% at (0.5in, -1.5in) +arrow right 0.3in from Cold2.e +text "subscribe" small +arrow right 0.3in +text "wait" small +arrow right 0.3in +text "value" small +arrow right 0.3in +text "dispose" small +``` + +
+ + +![output](assets/getter_hot_cold.svg) + +
+ **Prefer `getter_cold()`** when you can afford to wait and warmup isn't expensive. It's simpler (no cleanup needed) and doesn't hold resources. Only use `getter_hot()` when you need instant reads or the source is expensive to start. ### `getter_hot()` - Background subscription, instant reads From 6a29088361a6eb155053337dc5b7045f1b7b149c Mon Sep 17 00:00:00 2001 From: Ivan Nikolic Date: Sun, 18 Jan 2026 21:17:05 +0800 Subject: [PATCH 4/6] hot/cold getter graphs --- docs/api/sensor_streams/advanced_streams.md | 104 +++++++++++------- .../sensor_streams/assets/getter_hot_cold.svg | 71 ++++++++++++ 2 files changed, 133 insertions(+), 42 deletions(-) create mode 100644 docs/api/sensor_streams/assets/getter_hot_cold.svg diff --git a/docs/api/sensor_streams/advanced_streams.md b/docs/api/sensor_streams/advanced_streams.md index 0caa95d3e2..064f14f814 100644 --- a/docs/api/sensor_streams/advanced_streams.md +++ b/docs/api/sensor_streams/advanced_streams.md @@ -167,51 +167,71 @@ If you'd still like to switch to synchronous fetching, we provide two approaches color = white fill = none -# getter_hot section -text "getter_hot()" bold at (0.5in, 1.1in) +# === getter_hot section === +H_Title: box "getter_hot()" rad 5px fit wid 170% ht 170% -# Stream emitting values continuously -Stream: box "Stream" rad 5px fit wid 170% ht 170% at (0.5in, 0.5in) -arrow right 0.4in from Stream.e +Sub: box "subscribe" rad 5px fit wid 170% ht 170% with .n at H_Title.s + (0, -0.5in) +arrow from H_Title.s to Sub.n +arrow right from Sub.e Cache: box "Cache" rad 5px fit wid 170% ht 170% -# Values flowing into cache -text "v1 v2 v3..." italic small at (Stream.x + 0.5in, Stream.y + 0.3in) - -# get_val() calls reading from cache instantly -arrow from Cache.e right 0.3in then down 0.25in then right 0.25in -Call1: text "get_val() → instant" small -arrow from Cache.e right 0.3in then down 0.6in then right 0.25in -Call2: text "get_val() → instant" small - -# Continuous subscription indicator -line dashed from Stream.s down 0.2in -text "always subscribed" italic small at (Stream.x, Stream.y - 0.45in) - - -# getter_cold section -text "getter_cold()" bold at (0.5in, -0.6in) - -# Each call creates fresh subscription -Cold1: box "get_val()" rad 5px fit wid 170% ht 170% at (0.5in, -1.0in) -arrow right 0.3in from Cold1.e -text "subscribe" small -arrow right 0.3in -text "wait" small -arrow right 0.3in -text "value" small -arrow right 0.3in -text "dispose" small - -Cold2: box "get_val()" rad 5px fit wid 170% ht 170% at (0.5in, -1.5in) -arrow right 0.3in from Cold2.e -text "subscribe" small -arrow right 0.3in -text "wait" small -arrow right 0.3in -text "value" small -arrow right 0.3in -text "dispose" small +# blocking box around subscribe->cache (one-time setup) +Blk0: box dashed color 0x5c9ff0 with .nw at Sub.nw + (-0.1in, 0.25in) wid (Cache.e.x - Sub.w.x + 0.2in) ht 0.7in rad 5px +text "blocking" italic with .n at Blk0.n + (0, -0.05in) + +arrow right from Cache.e +Getter: box "getter" rad 5px fit wid 170% ht 170% + +arrow from Getter.e right 0.3in then down 0.25in then right 0.2in +G1: box invis "call()" color 0x8cbdf2 fit wid 150% +arrow right 0.4in from G1.e +box invis "instant" fit wid 150% + +arrow from Getter.e right 0.3in then down 0.7in then right 0.2in +G2: box invis "call()" color 0x8cbdf2 fit wid 150% +arrow right 0.4in from G2.e +box invis "instant" fit wid 150% + +text "always subscribed" italic with .n at Blk0.s + (0, -0.1in) + + +# === getter_cold section === +C_Title: box "getter_cold()" rad 5px fit wid 170% ht 170% with .nw at H_Title.sw + (0, -1.6in) + +arrow down 0.3in from C_Title.s +ColdGetter: box "getter" rad 5px fit wid 170% ht 170% + +# Branch to first call +arrow from ColdGetter.e right 0.3in then down 0.3in then right 0.2in +Cold1: box invis "call()" color 0x8cbdf2 fit wid 150% +arrow right 0.4in from Cold1.e +Sub1: box invis "subscribe" fit wid 150% +arrow right 0.4in from Sub1.e +Wait1: box invis "wait" fit wid 150% +arrow right 0.4in from Wait1.e +Val1: box invis "value" fit wid 150% +arrow right 0.4in from Val1.e +Disp1: box invis "dispose " fit wid 150% + +# blocking box around first row +Blk1: box dashed color 0x5c9ff0 with .nw at Cold1.nw + (-0.1in, 0.25in) wid (Disp1.e.x - Cold1.w.x + 0.2in) ht 0.7in rad 5px +text "blocking" italic with .n at Blk1.n + (0, -0.05in) + +# Branch to second call +arrow from ColdGetter.e right 0.3in then down 1.2in then right 0.2in +Cold2: box invis "call()" color 0x8cbdf2 fit wid 150% +arrow right 0.4in from Cold2.e +Sub2: box invis "subscribe" fit wid 150% +arrow right 0.4in from Sub2.e +Wait2: box invis "wait" fit wid 150% +arrow right 0.4in from Wait2.e +Val2: box invis "value" fit wid 150% +arrow right 0.4in from Val2.e +Disp2: box invis "dispose " fit wid 150% + +# blocking box around second row +Blk2: box dashed color 0x5c9ff0 with .nw at Cold2.nw + (-0.1in, 0.25in) wid (Disp2.e.x - Cold2.w.x + 0.2in) ht 0.7in rad 5px +text "blocking" italic with .n at Blk2.n + (0, -0.05in) ``` diff --git a/docs/api/sensor_streams/assets/getter_hot_cold.svg b/docs/api/sensor_streams/assets/getter_hot_cold.svg new file mode 100644 index 0000000000..d2f336459c --- /dev/null +++ b/docs/api/sensor_streams/assets/getter_hot_cold.svg @@ -0,0 +1,71 @@ + + +getter_hot() + +subscribe + + + + + +Cache + +blocking + + + +getter + + +call() + + +instant + + +call() + + +instant +always subscribed + +getter_cold() + + + +getter + + +call() + + +subscribe + + +wait + + +value + + +dispose   + +blocking + + +call() + + +subscribe + + +wait + + +value + + +dispose   + +blocking + From 9631d8872cd6eb47e240c34a9f2196d59c8b39f8 Mon Sep 17 00:00:00 2001 From: Ivan Nikolic Date: Sun, 18 Jan 2026 21:20:05 +0800 Subject: [PATCH 5/6] diagram fold fix --- docs/api/sensor_streams/advanced_streams.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/api/sensor_streams/advanced_streams.md b/docs/api/sensor_streams/advanced_streams.md index 064f14f814..4d2e00dec1 100644 --- a/docs/api/sensor_streams/advanced_streams.md +++ b/docs/api/sensor_streams/advanced_streams.md @@ -161,8 +161,6 @@ If you'd still like to switch to synchronous fetching, we provide two approaches
diagram source -
Pikchr - ```pikchr fold output=assets/getter_hot_cold.svg color = white fill = none @@ -239,7 +237,6 @@ text "blocking" italic with .n at Blk2.n + (0, -0.05in) ![output](assets/getter_hot_cold.svg) -
**Prefer `getter_cold()`** when you can afford to wait and warmup isn't expensive. It's simpler (no cleanup needed) and doesn't hold resources. Only use `getter_hot()` when you need instant reads or the source is expensive to start. From f8860b2a128f08db9acff41332ec1eb350eb486b Mon Sep 17 00:00:00 2001 From: Ivan Nikolic Date: Tue, 20 Jan 2026 17:31:34 +0800 Subject: [PATCH 6/6] random change to trigger CI --- docs/api/sensor_streams/advanced_streams.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/api/sensor_streams/advanced_streams.md b/docs/api/sensor_streams/advanced_streams.md index 4d2e00dec1..187d432af2 100644 --- a/docs/api/sensor_streams/advanced_streams.md +++ b/docs/api/sensor_streams/advanced_streams.md @@ -165,7 +165,6 @@ If you'd still like to switch to synchronous fetching, we provide two approaches color = white fill = none -# === getter_hot section === H_Title: box "getter_hot()" rad 5px fit wid 170% ht 170% Sub: box "subscribe" rad 5px fit wid 170% ht 170% with .n at H_Title.s + (0, -0.5in)