This commit is contained in:
@@ -39,8 +39,8 @@ and validated against real hardware.
|
||||
lives under `/readsb/` on `:8088` and under `/data/` on the tar1090 root `:8504`.
|
||||
- **Composable aircraft filters** — position, altitude, range, squawk, emergency,
|
||||
callsign, category, MLAT, signal strength, and more, AND/OR‑composable.
|
||||
- **Streaming built in.** Any endpoint becomes a `<-chan Update[T]` with a
|
||||
configurable poll interval and buffer.
|
||||
- **Streaming built in.** Any endpoint becomes a Go 1.23 iterator
|
||||
(`iter.Seq2[*T, error]`) you can `range` over at a configurable interval.
|
||||
- **Bring your own everything** — `context.Context` on every call, pluggable
|
||||
`*http.Client`, per‑endpoint TLS, custom ports/schemes/paths.
|
||||
- **Zero non‑stdlib dependencies**, including a small Prometheus text parser for
|
||||
@@ -152,24 +152,47 @@ plus the `Not` and `Any` combinators.
|
||||
|
||||
### Streaming
|
||||
|
||||
Every endpoint has a `Stream*` variant. It emits immediately, then on each tick,
|
||||
and closes the channel when the context is cancelled. Each `Update[T]` carries
|
||||
either a value or the error from that poll.
|
||||
Every endpoint has a `Poll*` variant that returns a Go 1.23 iterator
|
||||
(`iter.Seq2[*T, error]`). It yields a fresh result immediately, then once per
|
||||
interval, until the context is cancelled or you `break`. Each step yields either
|
||||
a value or the error from that poll.
|
||||
|
||||
```go
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
for u := range c.StreamAircraft(ctx, 2*time.Second, readsb.WithPosition()) {
|
||||
if u.Err != nil {
|
||||
log.Printf("poll failed: %v", u.Err)
|
||||
for report, err := range c.PollAircraft(ctx, 2*time.Second, readsb.WithPosition()) {
|
||||
if err != nil {
|
||||
log.Printf("poll failed: %v", err)
|
||||
continue
|
||||
}
|
||||
log.Printf("%d aircraft in view", len(u.Value.Aircraft))
|
||||
log.Printf("%d aircraft in view", len(report.Aircraft))
|
||||
}
|
||||
```
|
||||
|
||||
Set the channel buffer with `client.WithStreamBufferSize(n)`.
|
||||
Because the iterator runs in your goroutine, breaking the loop stops polling
|
||||
immediately — there is no background goroutine to leak and no buffer to tune.
|
||||
Need a channel for fan-out or `select`? Wrap the iterator at the call site.
|
||||
|
||||
The `Poll*` methods are thin wrappers over the exported generic engine,
|
||||
`client.Poll`, so you never re-implement the interval loop — apply the same
|
||||
cadence to any custom or composed fetch:
|
||||
|
||||
```go
|
||||
for v, err := range client.Poll(ctx, time.Second, func(ctx context.Context) (int, error) {
|
||||
r, err := c.Aircraft(ctx, readsb.InEmergency())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(r.Aircraft), nil
|
||||
}) {
|
||||
log.Printf("emergencies: %d (err=%v)", v, err)
|
||||
}
|
||||
```
|
||||
|
||||
Filtering exposes an iterator too: `report.All(filters...)` yields matching
|
||||
aircraft lazily (handy with `break` or `slices.Collect`), while
|
||||
`report.Filter(...)` is the eager slice form.
|
||||
|
||||
### Station health & feeder telemetry
|
||||
|
||||
@@ -245,7 +268,6 @@ c, err := client.New(
|
||||
Host: "station.example", Port: 8504, DataPath: "data",
|
||||
}),
|
||||
client.WithHTTPClient(myHTTPClient), // optional; TLS above is ignored if set
|
||||
client.WithStreamBufferSize(8),
|
||||
client.WithUserAgent("my-app/1.0"),
|
||||
)
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user