signalk-cli 1.2.0__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: signalk-cli
3
- Version: 1.2.0
4
- Summary: Query the SignalK v2 History API and export data as CSV or Apache Arrow Feather
3
+ Version: 2.1.0
4
+ Summary: Query SignalK v2 APIs and export data as CSV, JSON or Apache Arrow Feather
5
5
  Keywords: signalk,sailing,marine,nmea,boating
6
6
  Author: jey burrows
7
7
  Author-email: jey burrows <jrb@rhizomatics.org.uk>
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python
15
15
  Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Programming Language :: Python :: 3.14
17
17
  Requires-Dist: click>=8.3.3
18
- Requires-Dist: niquests>=2.28
18
+ Requires-Dist: niquests[ws]>=2.28
19
19
  Requires-Dist: zeroconf>=0.149.16,<0.150.0
20
20
  Requires-Dist: pyarrow>=17.0 ; extra == 'feather'
21
21
  Requires-Python: >=3.13
@@ -28,14 +28,25 @@ Description-Content-Type: text/markdown
28
28
 
29
29
  # SignalK CLI
30
30
 
31
- Query and explore SignalK APIs from the command line, and export data as CSV, Apache Arrow Feather, or JSON.
31
+ Query and explore NMEA and other boat data from SignalK APIs using the command line, and export data as CSV, Apache Arrow Feather, or JSON.
32
32
 
33
33
  APIs supported:
34
34
 
35
- * [SignalK v2 History API](https://signalk.org/https://demo.signalk.org/documentation/Developing/REST_APIs/History_API.html)
35
+ * [SignalK v2 History API](https://signalk.org/https://demo.signalk.org/documentation/Developing/REST_APIs/History_API.html). Commands available:
36
+ - list-paths
37
+ - list-providers
38
+ - list-contexts
39
+ - query
40
+ - cardinality
41
+ * [SignalK v1 Streaming API](https://signalk.org/specification/1.8.2/doc/streaming_api.html). Commands available:
42
+ - deltas
36
43
 
37
44
  ## Installation
38
45
 
46
+ `signalk-cli` is published to PyPi at https://pypi.org/project/signalk-cli/
47
+
48
+ Python is required to run this, version 3.13 or above. [uv](https://docs.astral.sh/uv/) is the recommended way to install the package ( and can install Python ) but is not required.
49
+
39
50
  ### PyPi
40
51
 
41
52
  ```pip install signalk-cli``` or ```uv pip install signalk-cli```
@@ -60,14 +71,17 @@ Use `uv` to run without installing the module permanently, for example:
60
71
  uv run --with signalk-cli signalk_cli.history list-providers
61
72
  ```
62
73
 
74
+ ```bash
75
+ uv run --with signalk-cli signalk_cli.stream deltas navigation.position --follow
76
+ ```
77
+
63
78
  ## Running
64
79
 
65
- Run via `python -m signalk_cli.history <command>`.
80
+ Run via `python -m signalk_cli.history <command>` or `python -m signalk_cli.stream <command>` or without installing the module with `uv run --with signalk-cli signalk_cli.history <command>`.
66
81
 
67
82
  ## Determining SignalK host name
68
83
 
69
- If no host name is set as an argument, the CLI will look for a `SIGNALK_HOST` environment variable, and failing that attempt to automatically discover
70
- the host using mDNS (aka Bonjour) and locally cached (see [Default Caching](#default-caching)).
84
+ If no host name is set as an argument, the CLI will look for a `SIGNALK_HOST` environment variable, and failing that attempt to automatically discover the host using mDNS (aka Bonjour) and locally cached (see [Default Caching](#default-caching)).
71
85
 
72
86
  ```bash
73
87
  export SIGNALK_HOST=192.168.6.99 # http:// is added automatically if omitted
@@ -395,6 +409,137 @@ python -m signalk_cli.history list-contexts --host 10.36.10.21 --format raw --ba
395
409
 
396
410
  ---
397
411
 
412
+ ## SignalK v1 Streaming API
413
+
414
+ The `signalk_cli.stream` module connects to a running SignalK server's live delta
415
+ WebSocket feed (as opposed to `signalk_cli.history`, which queries recorded history).
416
+
417
+ ### `deltas`
418
+
419
+ Stream live delta updates from the SignalK v1 Streaming API.
420
+
421
+ ```
422
+ python -m signalk_cli.stream deltas [OPTIONS] [PATH...]
423
+ ```
424
+
425
+ By default, prints the next delta message and exits — useful for a quick check.
426
+ Use `--follow` to keep tailing until interrupted with Ctrl-C, optionally capped
427
+ with `--count`.
428
+
429
+ **PATH** arguments are sent verbatim to the server as an explicit subscription,
430
+ one per path. They may be literal SignalK paths (e.g. `navigation.speedOverGround`)
431
+ or use the SignalK subscription wildcard `*`, matched server-side per the
432
+ [Subscription Protocol](https://signalk.org/specification/1.8.2/doc/subscription_protocol.html)
433
+ — unlike `history`'s PATH patterns, which the client resolves by matching
434
+ against the server's enumerated `/paths` list:
435
+
436
+ - `*` at the end of a path matches any suffix, e.g. `navigation.*`
437
+ - `*` as a middle segment matches any single segment there, e.g. `propulsion.*.oilTemperature`
438
+ - a bare `*` subscribes to every path in the context
439
+
440
+ Quote wildcarded paths (e.g. `'navigation.*'`) so the shell doesn't expand them
441
+ against local filenames first.
442
+
443
+ `deltas` always sends its own explicit subscribe message for `--context`
444
+ (default `vessels.self`), covering PATH arguments if given, otherwise every
445
+ path (`*`). `--context` also accepts the SignalK wildcard: `'*'` or
446
+ `'vessels.*'` subscribes to every vessel, not just your own. `--policy`,
447
+ `--period`, and `--min-period` control that subscription per the
448
+ [Subscription Protocol](https://signalk.org/specification/1.8.2/doc/subscription_protocol.html)'s
449
+ `policy`/`period`/`minPeriod` fields:
450
+
451
+ - **`--policy`** (`instant` / `ideal` / `fixed`, default `ideal`): `instant` sends every change (throttled by `--min-period`); `ideal` behaves like `instant` but resends the last value if nothing changes within `--period`; `fixed` always sends the last known value every `--period`, regardless of changes.
452
+ - **`--period`** (seconds, default `60`): the resend interval for `ideal`/`fixed`. Converted to milliseconds on the wire.
453
+ - **`--min-period`** (seconds): fastest allowed transmission rate, only meaningful with `--policy instant`.
454
+
455
+ The protocol also defines a per-path `format` field (`delta`/`full`), but
456
+ this CLI doesn't expose it: signalk-server rejects `full` outright ("Only
457
+ delta format supported, using it") and always sends delta messages
458
+ regardless, so the choice would be misleading.
459
+
460
+ **`--subscribe` is a separate, easily-confused mechanism**: it's the
461
+ connection-level `subscribe` query parameter (`none`/`self`/`all`), which
462
+ controls whether the server *additionally* auto-subscribes the connection
463
+ at **its own** default policy/period — you don't get to choose the rate.
464
+ This CLI defaults it to `none`, diverging from the SignalK spec's own
465
+ default of `self`, because `deltas` always sends its own explicit `--context`
466
+ subscription anyway; leaving the connection-level default at `self` would
467
+ just double up on updates for your own vessel.
468
+
469
+ So there are two different ways to see every vessel, and they're not
470
+ interchangeable:
471
+
472
+ | Want | Use | Policy/period used |
473
+ |---|---|---|
474
+ | Just your own vessel | (defaults) | Yours (`--policy`/`--period`) |
475
+ | Every vessel, at your chosen rate | `--context '*'` | Yours (`--policy`/`--period`) |
476
+ | Every vessel, at whatever rate the server defaults to | `--subscribe all` | The server's own default |
477
+
478
+ #### Options
479
+
480
+ | Option | Default | Description |
481
+ |---|---|---|
482
+ | `--host` | `$SIGNALK_HOST` | Server base URL. `http://` added if scheme omitted; converted to `ws://`/`wss://` for the stream connection. |
483
+ | `--no-cache` | — | Ignore the cached host |
484
+ | `-c, --context TEXT` | `vessels.self` | SignalK context |
485
+ | `--subscribe [none\|self\|all]` | `none` | Connection-level subscribe policy (SignalK's own `subscribe` query parameter); see above |
486
+ | `--policy [instant\|ideal\|fixed]` | `ideal` | Per-path subscribe `policy` field; see above |
487
+ | `--period SECONDS` | `60` | Per-path subscribe `period` field, in seconds (converted to ms) |
488
+ | `--min-period SECONDS` | — | Per-path subscribe `minPeriod` field, in seconds (converted to ms); only meaningful with `--policy instant` |
489
+ | `--format [csv\|json\|raw\|feather]` | from extension, else csv | Output format. `json` is JSON Lines (one row object per line, suitable for a live stream). `raw` is the exact delta message text, one per line. `feather` requires `pip install 'signalk-cli[feather]'` and `--output` (cannot stream to stdout). |
490
+ | `--no-header` | — | Suppress the CSV header row |
491
+ | `--include-meta` | — | Also emit rows for `meta` entries (units, description, zones, etc.), not just `values`. Adds a `kind` column (`value`/`meta`) to csv/json/feather output. Ignored for `--format raw`, which always includes meta as-is. |
492
+ | `-o, --output [FILE]` | stdout | Write to a file. Omit the filename (`--output` alone) to auto-name as `signalk-stream-<server>-<timestamp>.<ext>`. Required for `--format feather`. |
493
+ | `-f, --follow` | — | Keep streaming until interrupted (Ctrl-C) or `--count` is reached. Without this, print the next message then exit. |
494
+ | `-n, --count N` | 1 without `--follow`, unlimited with it | Number of delta messages to output |
495
+ | `--bare` | — | Print to stdout with **no informational messages**. Ideal for piping to other tools. |
496
+
497
+ #### Output formats
498
+
499
+ **csv** / **json**: `timestamp, context, source, path, value` — one row per `path`/`value` pair in each delta's updates, written incrementally as messages arrive. Structured values (e.g. `navigation.position`) are JSON-encoded in the `value` column. With `--include-meta`, a `kind` column (`value`/`meta`) is inserted before `value`, and `meta` entries (units, description, zones, etc. — also JSON-encoded) are included as rows too; without it, `meta` entries are silently dropped from csv/json/feather (they're still present in `raw`).
500
+
501
+ **raw**: the exact delta message JSON as received from the server, one message per line.
502
+
503
+ **feather**: Apache Arrow Feather binary format, same columns as csv/json. Requires `pip install 'signalk-cli[feather]'`. Unlike the other formats, rows are buffered in memory across all received messages and written once the session ends (`--count` reached, or Ctrl-C with `--follow`) — cannot be streamed to stdout.
504
+
505
+ #### Examples
506
+
507
+ ```bash
508
+ # Next update for one path, then exit
509
+ python -m signalk_cli.stream deltas --host 10.36.10.21 navigation.speedOverGround
510
+
511
+ # Tail all navigation updates until Ctrl-C
512
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow 'navigation.*'
513
+
514
+ # Tail oil temperature across every engine (mid-path wildcard)
515
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow 'propulsion.*.oilTemperature'
516
+
517
+ # Next 20 messages across all subscribed paths, as JSON Lines
518
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --format json --count 20
519
+
520
+ # Tail two specific paths, piping raw deltas to another tool
521
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow --format raw --bare \
522
+ navigation.speedOverGround navigation.courseOverGroundTrue
523
+
524
+ # Everything the server has (equivalent to ?subscribe=all), following
525
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --subscribe all --follow
526
+
527
+ # Faster resend interval (5s instead of the 60s default)
528
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow --period 5 'navigation.*'
529
+
530
+ # Send every change immediately, no more than 5 times/second
531
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow \
532
+ --policy instant --min-period 0.2 navigation.speedOverGround
533
+
534
+ # Capture 500 messages to an auto-named Feather file
535
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --count 500 --format feather --output 'navigation.*'
536
+
537
+ # Capture until Ctrl-C to a named Feather file
538
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow -o capture.feather 'navigation.*'
539
+ ```
540
+
541
+ ---
542
+
398
543
  ## Time range
399
544
 
400
545
  All commands that query data accept the same three time parameters. At least one of `--from` or `--duration` must be provided (or the tool supplies a one-hour default).
@@ -1,13 +1,24 @@
1
1
  # SignalK CLI
2
2
 
3
- Query and explore SignalK APIs from the command line, and export data as CSV, Apache Arrow Feather, or JSON.
3
+ Query and explore NMEA and other boat data from SignalK APIs using the command line, and export data as CSV, Apache Arrow Feather, or JSON.
4
4
 
5
5
  APIs supported:
6
6
 
7
- * [SignalK v2 History API](https://signalk.org/https://demo.signalk.org/documentation/Developing/REST_APIs/History_API.html)
7
+ * [SignalK v2 History API](https://signalk.org/https://demo.signalk.org/documentation/Developing/REST_APIs/History_API.html). Commands available:
8
+ - list-paths
9
+ - list-providers
10
+ - list-contexts
11
+ - query
12
+ - cardinality
13
+ * [SignalK v1 Streaming API](https://signalk.org/specification/1.8.2/doc/streaming_api.html). Commands available:
14
+ - deltas
8
15
 
9
16
  ## Installation
10
17
 
18
+ `signalk-cli` is published to PyPi at https://pypi.org/project/signalk-cli/
19
+
20
+ Python is required to run this, version 3.13 or above. [uv](https://docs.astral.sh/uv/) is the recommended way to install the package ( and can install Python ) but is not required.
21
+
11
22
  ### PyPi
12
23
 
13
24
  ```pip install signalk-cli``` or ```uv pip install signalk-cli```
@@ -32,14 +43,17 @@ Use `uv` to run without installing the module permanently, for example:
32
43
  uv run --with signalk-cli signalk_cli.history list-providers
33
44
  ```
34
45
 
46
+ ```bash
47
+ uv run --with signalk-cli signalk_cli.stream deltas navigation.position --follow
48
+ ```
49
+
35
50
  ## Running
36
51
 
37
- Run via `python -m signalk_cli.history <command>`.
52
+ Run via `python -m signalk_cli.history <command>` or `python -m signalk_cli.stream <command>` or without installing the module with `uv run --with signalk-cli signalk_cli.history <command>`.
38
53
 
39
54
  ## Determining SignalK host name
40
55
 
41
- If no host name is set as an argument, the CLI will look for a `SIGNALK_HOST` environment variable, and failing that attempt to automatically discover
42
- the host using mDNS (aka Bonjour) and locally cached (see [Default Caching](#default-caching)).
56
+ If no host name is set as an argument, the CLI will look for a `SIGNALK_HOST` environment variable, and failing that attempt to automatically discover the host using mDNS (aka Bonjour) and locally cached (see [Default Caching](#default-caching)).
43
57
 
44
58
  ```bash
45
59
  export SIGNALK_HOST=192.168.6.99 # http:// is added automatically if omitted
@@ -367,6 +381,137 @@ python -m signalk_cli.history list-contexts --host 10.36.10.21 --format raw --ba
367
381
 
368
382
  ---
369
383
 
384
+ ## SignalK v1 Streaming API
385
+
386
+ The `signalk_cli.stream` module connects to a running SignalK server's live delta
387
+ WebSocket feed (as opposed to `signalk_cli.history`, which queries recorded history).
388
+
389
+ ### `deltas`
390
+
391
+ Stream live delta updates from the SignalK v1 Streaming API.
392
+
393
+ ```
394
+ python -m signalk_cli.stream deltas [OPTIONS] [PATH...]
395
+ ```
396
+
397
+ By default, prints the next delta message and exits — useful for a quick check.
398
+ Use `--follow` to keep tailing until interrupted with Ctrl-C, optionally capped
399
+ with `--count`.
400
+
401
+ **PATH** arguments are sent verbatim to the server as an explicit subscription,
402
+ one per path. They may be literal SignalK paths (e.g. `navigation.speedOverGround`)
403
+ or use the SignalK subscription wildcard `*`, matched server-side per the
404
+ [Subscription Protocol](https://signalk.org/specification/1.8.2/doc/subscription_protocol.html)
405
+ — unlike `history`'s PATH patterns, which the client resolves by matching
406
+ against the server's enumerated `/paths` list:
407
+
408
+ - `*` at the end of a path matches any suffix, e.g. `navigation.*`
409
+ - `*` as a middle segment matches any single segment there, e.g. `propulsion.*.oilTemperature`
410
+ - a bare `*` subscribes to every path in the context
411
+
412
+ Quote wildcarded paths (e.g. `'navigation.*'`) so the shell doesn't expand them
413
+ against local filenames first.
414
+
415
+ `deltas` always sends its own explicit subscribe message for `--context`
416
+ (default `vessels.self`), covering PATH arguments if given, otherwise every
417
+ path (`*`). `--context` also accepts the SignalK wildcard: `'*'` or
418
+ `'vessels.*'` subscribes to every vessel, not just your own. `--policy`,
419
+ `--period`, and `--min-period` control that subscription per the
420
+ [Subscription Protocol](https://signalk.org/specification/1.8.2/doc/subscription_protocol.html)'s
421
+ `policy`/`period`/`minPeriod` fields:
422
+
423
+ - **`--policy`** (`instant` / `ideal` / `fixed`, default `ideal`): `instant` sends every change (throttled by `--min-period`); `ideal` behaves like `instant` but resends the last value if nothing changes within `--period`; `fixed` always sends the last known value every `--period`, regardless of changes.
424
+ - **`--period`** (seconds, default `60`): the resend interval for `ideal`/`fixed`. Converted to milliseconds on the wire.
425
+ - **`--min-period`** (seconds): fastest allowed transmission rate, only meaningful with `--policy instant`.
426
+
427
+ The protocol also defines a per-path `format` field (`delta`/`full`), but
428
+ this CLI doesn't expose it: signalk-server rejects `full` outright ("Only
429
+ delta format supported, using it") and always sends delta messages
430
+ regardless, so the choice would be misleading.
431
+
432
+ **`--subscribe` is a separate, easily-confused mechanism**: it's the
433
+ connection-level `subscribe` query parameter (`none`/`self`/`all`), which
434
+ controls whether the server *additionally* auto-subscribes the connection
435
+ at **its own** default policy/period — you don't get to choose the rate.
436
+ This CLI defaults it to `none`, diverging from the SignalK spec's own
437
+ default of `self`, because `deltas` always sends its own explicit `--context`
438
+ subscription anyway; leaving the connection-level default at `self` would
439
+ just double up on updates for your own vessel.
440
+
441
+ So there are two different ways to see every vessel, and they're not
442
+ interchangeable:
443
+
444
+ | Want | Use | Policy/period used |
445
+ |---|---|---|
446
+ | Just your own vessel | (defaults) | Yours (`--policy`/`--period`) |
447
+ | Every vessel, at your chosen rate | `--context '*'` | Yours (`--policy`/`--period`) |
448
+ | Every vessel, at whatever rate the server defaults to | `--subscribe all` | The server's own default |
449
+
450
+ #### Options
451
+
452
+ | Option | Default | Description |
453
+ |---|---|---|
454
+ | `--host` | `$SIGNALK_HOST` | Server base URL. `http://` added if scheme omitted; converted to `ws://`/`wss://` for the stream connection. |
455
+ | `--no-cache` | — | Ignore the cached host |
456
+ | `-c, --context TEXT` | `vessels.self` | SignalK context |
457
+ | `--subscribe [none\|self\|all]` | `none` | Connection-level subscribe policy (SignalK's own `subscribe` query parameter); see above |
458
+ | `--policy [instant\|ideal\|fixed]` | `ideal` | Per-path subscribe `policy` field; see above |
459
+ | `--period SECONDS` | `60` | Per-path subscribe `period` field, in seconds (converted to ms) |
460
+ | `--min-period SECONDS` | — | Per-path subscribe `minPeriod` field, in seconds (converted to ms); only meaningful with `--policy instant` |
461
+ | `--format [csv\|json\|raw\|feather]` | from extension, else csv | Output format. `json` is JSON Lines (one row object per line, suitable for a live stream). `raw` is the exact delta message text, one per line. `feather` requires `pip install 'signalk-cli[feather]'` and `--output` (cannot stream to stdout). |
462
+ | `--no-header` | — | Suppress the CSV header row |
463
+ | `--include-meta` | — | Also emit rows for `meta` entries (units, description, zones, etc.), not just `values`. Adds a `kind` column (`value`/`meta`) to csv/json/feather output. Ignored for `--format raw`, which always includes meta as-is. |
464
+ | `-o, --output [FILE]` | stdout | Write to a file. Omit the filename (`--output` alone) to auto-name as `signalk-stream-<server>-<timestamp>.<ext>`. Required for `--format feather`. |
465
+ | `-f, --follow` | — | Keep streaming until interrupted (Ctrl-C) or `--count` is reached. Without this, print the next message then exit. |
466
+ | `-n, --count N` | 1 without `--follow`, unlimited with it | Number of delta messages to output |
467
+ | `--bare` | — | Print to stdout with **no informational messages**. Ideal for piping to other tools. |
468
+
469
+ #### Output formats
470
+
471
+ **csv** / **json**: `timestamp, context, source, path, value` — one row per `path`/`value` pair in each delta's updates, written incrementally as messages arrive. Structured values (e.g. `navigation.position`) are JSON-encoded in the `value` column. With `--include-meta`, a `kind` column (`value`/`meta`) is inserted before `value`, and `meta` entries (units, description, zones, etc. — also JSON-encoded) are included as rows too; without it, `meta` entries are silently dropped from csv/json/feather (they're still present in `raw`).
472
+
473
+ **raw**: the exact delta message JSON as received from the server, one message per line.
474
+
475
+ **feather**: Apache Arrow Feather binary format, same columns as csv/json. Requires `pip install 'signalk-cli[feather]'`. Unlike the other formats, rows are buffered in memory across all received messages and written once the session ends (`--count` reached, or Ctrl-C with `--follow`) — cannot be streamed to stdout.
476
+
477
+ #### Examples
478
+
479
+ ```bash
480
+ # Next update for one path, then exit
481
+ python -m signalk_cli.stream deltas --host 10.36.10.21 navigation.speedOverGround
482
+
483
+ # Tail all navigation updates until Ctrl-C
484
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow 'navigation.*'
485
+
486
+ # Tail oil temperature across every engine (mid-path wildcard)
487
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow 'propulsion.*.oilTemperature'
488
+
489
+ # Next 20 messages across all subscribed paths, as JSON Lines
490
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --format json --count 20
491
+
492
+ # Tail two specific paths, piping raw deltas to another tool
493
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow --format raw --bare \
494
+ navigation.speedOverGround navigation.courseOverGroundTrue
495
+
496
+ # Everything the server has (equivalent to ?subscribe=all), following
497
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --subscribe all --follow
498
+
499
+ # Faster resend interval (5s instead of the 60s default)
500
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow --period 5 'navigation.*'
501
+
502
+ # Send every change immediately, no more than 5 times/second
503
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow \
504
+ --policy instant --min-period 0.2 navigation.speedOverGround
505
+
506
+ # Capture 500 messages to an auto-named Feather file
507
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --count 500 --format feather --output 'navigation.*'
508
+
509
+ # Capture until Ctrl-C to a named Feather file
510
+ python -m signalk_cli.stream deltas --host 10.36.10.21 --follow -o capture.feather 'navigation.*'
511
+ ```
512
+
513
+ ---
514
+
370
515
  ## Time range
371
516
 
372
517
  All commands that query data accept the same three time parameters. At least one of `--from` or `--duration` must be provided (or the tool supplies a one-hour default).
@@ -0,0 +1,117 @@
1
+ [project]
2
+ name = "signalk-cli"
3
+ version = "2.1.0"
4
+ description = "Query SignalK v2 APIs and export data as CSV, JSON or Apache Arrow Feather"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ license = "Apache-2.0"
8
+ keywords = [
9
+ "signalk",
10
+ "sailing",
11
+ "marine",
12
+ "nmea",
13
+ "boating",
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Environment :: Console",
18
+ "Natural Language :: English",
19
+ "Intended Audience :: Developers",
20
+ "Topic :: Scientific/Engineering",
21
+ "Programming Language :: Python",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
24
+ ]
25
+ dependencies = [
26
+ "click>=8.3.3",
27
+ "niquests[ws]>=2.28",
28
+ "zeroconf>=0.149.16,<0.150.0",
29
+ ]
30
+
31
+ [[project.authors]]
32
+ name = "jey burrows"
33
+ email = "jrb@rhizomatics.org.uk"
34
+
35
+ [project.optional-dependencies]
36
+ feather = ["pyarrow>=17.0"]
37
+
38
+ [project.urls]
39
+ Homepage = "https://github.com/rhizomatics/signalk-cli"
40
+ Repository = "https://github.com/rhizomatics/signalk-cli"
41
+ Issues = "https://github.com/rhizomatics/signalk-cli/issues"
42
+ Changelog = "https://github.com/rhizomatics/signalk-cli/blob/main/CHANGELOG.md"
43
+
44
+ [project.scripts]
45
+ "signalk_cli.history" = "signalk_cli.history.cli:cli"
46
+ "signalk_cli.stream" = "signalk_cli.stream.cli:cli"
47
+
48
+ [dependency-groups]
49
+ dev = [
50
+ "ruff>=0.16.0",
51
+ "pre-commit>=4.4.0",
52
+ "pytest>=9.0.0",
53
+ "pytest-mock>=3.15.0",
54
+ "pytest-cov>=7.0.0",
55
+ "pytest-xdist",
56
+ "mypy",
57
+ "coverage",
58
+ "icdiff",
59
+ "codespell",
60
+ ]
61
+
62
+ [tool.bandit]
63
+ exclude_dirs = ["tests"]
64
+
65
+ [tool.ruff.format]
66
+ quote-style = "double"
67
+ indent-style = "space"
68
+ skip-magic-trailing-comma = false
69
+ line-ending = "auto"
70
+
71
+ [tool.pytest.ini_options]
72
+ pythonpath = [
73
+ "src",
74
+ ".",
75
+ ]
76
+ asyncio_mode = "auto"
77
+ testpaths = ["tests"]
78
+ norecursedirs = [".git"]
79
+
80
+ [tool.coverage.run]
81
+ branch = true
82
+ source = ["src"]
83
+
84
+ [tool.coverage.report]
85
+ fail_under = 0
86
+
87
+ [tool.coverage.xml]
88
+ output = "cov.xml"
89
+
90
+ [tool.coverage.html]
91
+ directory = "htmlcov"
92
+
93
+ [tool.codespell]
94
+ skip = ".venv,.git,htmlcov,site,./.*,*.csv,*.json,README.*.md,support"
95
+ count = true
96
+ quiet-level = 2
97
+ ignore-words-list = "hass,referer,uint"
98
+
99
+ [tool.mypy]
100
+ mypy_path = ["./src"]
101
+
102
+ [tool.uv]
103
+ compile-bytecode = true
104
+ managed = true
105
+ exclude-newer = "7 days"
106
+ add-bounds = "major"
107
+
108
+ [tool.uv.exclude-newer-package]
109
+ uv_build = "2 days"
110
+
111
+ [tool.uv.build-backend]
112
+ module-root = "src"
113
+ module-name = "signalk_cli"
114
+
115
+ [build-system]
116
+ requires = ["uv_build>=0.12.0,<0.13.0"]
117
+ build-backend = "uv_build"
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "signalk-cli"
3
- version = "1.2.0"
4
- description = "Query the SignalK v2 History API and export data as CSV or Apache Arrow Feather"
3
+ version = "2.1.0"
4
+ description = "Query SignalK v2 APIs and export data as CSV, JSON or Apache Arrow Feather"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
7
7
  license="Apache-2.0"
@@ -23,7 +23,7 @@ classifiers = [
23
23
 
24
24
  dependencies = [
25
25
  "click>=8.3.3",
26
- "niquests>=2.28",
26
+ "niquests[ws]>=2.28",
27
27
  "zeroconf>=0.149.16,<0.150.0",
28
28
  ]
29
29
 
@@ -31,7 +31,7 @@ dependencies = [
31
31
  feather = ["pyarrow>=17.0"]
32
32
  [dependency-groups]
33
33
  dev = [
34
- "ruff>=0.15.0",
34
+ "ruff>=0.16.0",
35
35
  "pre-commit>=4.4.0",
36
36
  "pytest>=9.0.0",
37
37
  "pytest-mock>=3.15.0",
@@ -101,11 +101,12 @@ mypy_path=["./src"]
101
101
  [tool.uv]
102
102
  compile-bytecode = true
103
103
  managed = true
104
- exclude-newer = "5 days"
104
+ exclude-newer = "7 days"
105
+ exclude-newer-package = {"uv_build"="2 days"}
105
106
  add-bounds = "major"
106
107
 
107
108
  [build-system]
108
- requires = ["uv_build>=0.11.13,<0.12.0"]
109
+ requires = ["uv_build>=0.12.0,<0.13.0"]
109
110
  build-backend = "uv_build"
110
111
 
111
112
 
@@ -115,3 +116,4 @@ module-name = "signalk_cli"
115
116
 
116
117
  [project.scripts]
117
118
  "signalk_cli.history" = "signalk_cli.history.cli:cli"
119
+ "signalk_cli.stream" = "signalk_cli.stream.cli:cli"
@@ -2,6 +2,8 @@
2
2
 
3
3
  print("Supported SignalK APIs:")
4
4
  print(" signalk_cli.history — SignalK v2 History API")
5
+ print(" signalk_cli.stream — SignalK v1 Streaming (delta) API")
5
6
  print()
6
7
  print("Usage: python -m signalk_cli.<api> [COMMAND] [OPTIONS]")
7
8
  print(" python -m signalk_cli.history --help")
9
+ print(" python -m signalk_cli.stream --help")
@@ -1,35 +1,30 @@
1
1
  """Click CLI for the SignalK v2 History API."""
2
2
 
3
- import contextlib
4
3
  import csv
5
- import io
6
4
  import json
7
5
  import re
8
6
  import sys
9
- from datetime import datetime, timezone
7
+ from datetime import UTC, datetime
10
8
  from pathlib import Path
11
9
  from urllib.parse import urlparse
12
10
 
13
11
  import click
14
12
  import niquests
15
13
 
14
+ from ..net import bare_option, host_option, resolve_host, stderr_ctx
16
15
  from .history_api import (
17
16
  HISTORY_BASE,
18
17
  api_error,
19
18
  apply_time_default,
20
- discover_host,
21
19
  expand_paths,
22
20
  fetch_server_paths,
23
- get_cached_host,
24
21
  normalise_duration,
25
- normalise_host,
26
22
  resolve_provider,
27
- save_cached_host,
28
23
  )
29
24
  from .output import (
25
+ _POSITION_RE,
30
26
  CARDINALITY_COLUMNS,
31
27
  FEATHER_EXTENSIONS,
32
- _POSITION_RE,
33
28
  compute_cardinality,
34
29
  write_csv,
35
30
  write_csv_wide,
@@ -72,35 +67,8 @@ def _list_fmt_callback(ctx, param, value):
72
67
  raise click.BadParameter(f"'{value}' is not one of 'csv', 'json', 'raw'")
73
68
 
74
69
 
75
- def _host_option(f):
76
- return click.option(
77
- "--host",
78
- default=None,
79
- envvar="SIGNALK_HOST",
80
- help="SignalK server base URL. http:// added if scheme omitted. "
81
- "Discovered via mDNS if omitted.",
82
- )(f)
83
-
84
-
85
- def _resolve_host(host: str | None, no_cache: bool = False) -> str:
86
- """Return a normalised host URL, discovering via mDNS if none provided."""
87
- if host:
88
- return normalise_host(host)
89
- if not no_cache:
90
- cached = get_cached_host()
91
- if cached:
92
- click.echo(f"Using cached host: {cached}", err=True)
93
- return cached
94
- click.echo("No host specified — searching for SignalK via mDNS...", err=True)
95
- discovered = discover_host()
96
- if not discovered:
97
- raise click.UsageError(
98
- "No SignalK server found via mDNS. Use --host or set SIGNALK_HOST."
99
- )
100
- click.echo(f"Discovered: {discovered}", err=True)
101
- if not no_cache:
102
- save_cached_host(discovered)
103
- return discovered
70
+ _host_option = host_option
71
+ _resolve_host = resolve_host
104
72
 
105
73
 
106
74
  def _provider_options(f):
@@ -127,18 +95,8 @@ def _time_options(f):
127
95
  return f
128
96
 
129
97
 
130
- def _bare_option(f):
131
- return click.option(
132
- "--bare",
133
- is_flag=True,
134
- help="Suppress all informational messages, outputting data only.",
135
- )(f)
136
-
137
-
138
- def _stderr_ctx(bare: bool) -> contextlib.AbstractContextManager:
139
- return (
140
- contextlib.redirect_stderr(io.StringIO()) if bare else contextlib.nullcontext()
141
- )
98
+ _bare_option = bare_option
99
+ _stderr_ctx = stderr_ctx
142
100
 
143
101
 
144
102
  def _build_time_params(from_: str | None, to: str | None, duration: str | None) -> dict:
@@ -341,7 +299,7 @@ def query(
341
299
  # Generate auto-named file path now that format is known
342
300
  if auto_name:
343
301
  server_name = urlparse(host).hostname or re.sub(r"[^\w.-]", "_", host)
344
- ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
302
+ ts = datetime.now(UTC).strftime("%Y%m%dT%H%M%SZ")
345
303
  ext = (
346
304
  ".feather"
347
305
  if fmt == "feather"