asterism 0.5.0 → 0.7.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +112 -0
- data/README.md +163 -31
- data/lib/asterism/cruby/objects.rb +23 -4
- data/lib/asterism/cruby/ros.rb +16 -4
- data/lib/asterism/cruby/zenoh.rb +32 -6
- data/lib/asterism/cruby.rb +51 -7
- data/lib/asterism/version.rb +4 -3
- data/mrblib/asterism.rb +340 -75
- data/mrblib/future.rb +11 -5
- data/mrblib/profile.rb +17 -4
- data/mrblib/proxy.rb +23 -5
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 54a2e8d7f4b96b04dc56b290714d60a4a8d10cdc87d789ccf01bbb2b474f0459
|
|
4
|
+
data.tar.gz: be9f2f6c820e67ea513ea19a3d6f88437589cb2020615bc558b553f83e882673
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cb166817303dfd933dd1fd6a4444aecd966ce06b66347a4b058759c49dc0b0cd1397bbef60d05753c72ccf511a8f70ea5c49c8ffdfaa591eb2d2cf2255d8b1da
|
|
7
|
+
data.tar.gz: d0d0549e336a50af534813aa4ab74c068952bf3d43148cc7c2032c0f4447df0aae73c9d1d813f8aa99819310c13ae3e220d59376276195fed408506b34144399
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,118 @@ the earlier ones are only in this repository. From 0.4.0 the two Zenoh
|
|
|
6
6
|
bindings (asterism-zenoh, picoruby-asterism-zenoh) carry the same minor
|
|
7
7
|
version (a patch release of one may come without the other).
|
|
8
8
|
|
|
9
|
+
## 0.7.0
|
|
10
|
+
|
|
11
|
+
**This release breaks compatibility: it speaks wire protocol 1 only.** A
|
|
12
|
+
node of 0.7.0 and a node of 0.6.0 or earlier (protocol 0) do not see each
|
|
13
|
+
other's objects, and a call between them fails: a protocol 0 call to a
|
|
14
|
+
0.7.0 node is refused with the error kind `protocol`, a 0.7.0 call to an
|
|
15
|
+
older node is not answered, and their replies are malformed answers to
|
|
16
|
+
each other. Move every node of a network to 0.7.0 together (boards,
|
|
17
|
+
browsers, consoles, CRuby programs). The Ruby API stays as it was, with
|
|
18
|
+
the additions below. Needs asterism-zenoh / picoruby-asterism-zenoh 0.4.0
|
|
19
|
+
(unchanged), and on mruby a MessagePack that reads and writes bin (README,
|
|
20
|
+
"Using it").
|
|
21
|
+
|
|
22
|
+
Protocol 1 (`docs/protocol.md`, normative; `docs/protocol_review.md` says
|
|
23
|
+
what was decided and why):
|
|
24
|
+
|
|
25
|
+
- A call is a get on `asterism/<node>/<app>/<object>/call/<caller>`, where
|
|
26
|
+
`<caller>` is the calling node's ID. Its payload is a map
|
|
27
|
+
`{"method" => name, "args" => [...], "kwargs" => {...}}` (args and
|
|
28
|
+
kwargs left out when empty) instead of the array `[method, args,
|
|
29
|
+
kwargs]`.
|
|
30
|
+
- A reply is `{"value" => value}` or `{"error" => {"kind" => ..., "class"
|
|
31
|
+
=> ..., "message" => ...}}` instead of `["ok", value]` /
|
|
32
|
+
`["error", class, message]`. The kinds: `raised`, `not_exposed`,
|
|
33
|
+
`no_object`, `arguments`, `malformed`, `unsendable`, `nesting`,
|
|
34
|
+
`protocol`.
|
|
35
|
+
- The meta is `{"methods" => [{"name" => name, "arity" => n}, ...]}`.
|
|
36
|
+
- Binary Strings go as MessagePack bin (CRuby: ASCII-8BIT Strings, no more
|
|
37
|
+
compatibility mode; mruby: `String#b`), and a bin arrives as a binary
|
|
38
|
+
String. The profile's icon is a bin.
|
|
39
|
+
- The profile has `"protocol" => 1`.
|
|
40
|
+
- Each application declares its own liveliness token
|
|
41
|
+
`asterism/<node>/<app>`; the node token `asterism/<node>`, which the
|
|
42
|
+
applications of a node shared, is gone. A node is present while any of
|
|
43
|
+
its applications or objects is.
|
|
44
|
+
- A payload is exactly one value: bytes after it, ext types and integers
|
|
45
|
+
outside the signed 64-bit range make it malformed. CRuby raises
|
|
46
|
+
`EncodeError` before sending an Integer outside int64 (it raised
|
|
47
|
+
`RangeError` before).
|
|
48
|
+
- A protocol 0 call (on the five-chunk `.../call` key) is answered with the
|
|
49
|
+
error kind `protocol`.
|
|
50
|
+
|
|
51
|
+
Added
|
|
52
|
+
|
|
53
|
+
- `Asterism.current_call` while an exposed method runs for a call (per
|
|
54
|
+
thread on CRuby, `net.current_call` too): `from` (the calling node),
|
|
55
|
+
`verified` (`:claimed`; `:local` for a call from the same application),
|
|
56
|
+
`object`, `method`. `Asterism::CurrentCall`.
|
|
57
|
+
- `Asterism.expose(name, obj, methods:, caller: :from)` (and
|
|
58
|
+
`net.expose`): passes the calling node's ID as the keyword `from:` and
|
|
59
|
+
refuses a call that brings `from:` itself.
|
|
60
|
+
- `RemoteError#remote_kind`: the error's kind, as above (nil when the
|
|
61
|
+
answer did not say).
|
|
62
|
+
- `proxy.asterism_call(name, *args, **kw)` and `proxy.async.asterism_call`:
|
|
63
|
+
a call by the method's name, for names a proxy cannot forward (`display`,
|
|
64
|
+
`hash`, `class`, `fail`, ...) and names that are not Ruby identifiers.
|
|
65
|
+
- `Asterism::PROTOCOL` (1).
|
|
66
|
+
- The conformance tests (`test/protocol/`) are protocol 1's: 55 values, 54
|
|
67
|
+
incoming queries, 24 outgoing steps; CRuby and mruby pass them all.
|
|
68
|
+
|
|
69
|
+
Changed
|
|
70
|
+
|
|
71
|
+
- `proxy.asterism_meta` returns the new meta form.
|
|
72
|
+
- `Asterism.connect`'s duplicate check also finds a copy of the
|
|
73
|
+
application that exposes nothing (it has its application token now).
|
|
74
|
+
- `host_test/msgpack.rb` (the mruby test's MessagePack) writes a binary
|
|
75
|
+
String as bin and reads a bin as one; the mruby test build adds
|
|
76
|
+
mruby-encoding (to tell a binary String).
|
|
77
|
+
- The documentation of the wire protocol (docs/protocol.md,
|
|
78
|
+
docs/protocol_review.md) and the conformance tests (X0, unreleased in
|
|
79
|
+
0.6.0) are rewritten for protocol 1.
|
|
80
|
+
|
|
81
|
+
## 0.6.0
|
|
82
|
+
|
|
83
|
+
One connection for everything and a lighter `Asterism.poll`; every 0.5.x
|
|
84
|
+
call works as before. Needs asterism-zenoh / picoruby-asterism-zenoh 0.4.0
|
|
85
|
+
(unchanged).
|
|
86
|
+
|
|
87
|
+
Added
|
|
88
|
+
|
|
89
|
+
- `Asterism.session` (README "One connection"): the Zenoh session of
|
|
90
|
+
`Asterism.connect` (an `Asterism::Zenoh::Session`), for put, subscribe, a
|
|
91
|
+
ROS 2 node (`Asterism::ROS::Node.new(Asterism.session, ...)`) and the rest
|
|
92
|
+
of the Zenoh API on the same connection. `Disconnected` when not
|
|
93
|
+
connected. The session stays Asterism's: its `close` does nothing (it
|
|
94
|
+
warns once); `Asterism.close` and a lost connection close it, with
|
|
95
|
+
everything declared on it. A router that lets a one-time password in
|
|
96
|
+
once now takes such an application, and its ROS 2 node has the node's
|
|
97
|
+
Zenoh ID (a console draws it inside the node).
|
|
98
|
+
- The CRuby API: `net.session`, the same session as a `Zenoh::Connection`
|
|
99
|
+
on the `Net`'s receiving thread (`shared?`; its `close` does nothing);
|
|
100
|
+
`Asterism::ROS::Connection.new(net.session)` makes ROS 2 nodes on it, and
|
|
101
|
+
its `close` withdraws only its nodes. `Zenoh::Connection#shared?`.
|
|
102
|
+
`ROS::Node#close` on a `ROS::Connection` also stops polling the node.
|
|
103
|
+
- `rake bench:poll` (`test/bench_poll.rb`): the time and the allocations
|
|
104
|
+
of one `Asterism.poll` by the number of nodes and objects.
|
|
105
|
+
- Tests: `test/test_session.rb`, `test/test_api_session.rb`, and on mruby
|
|
106
|
+
`host_test/test_session.rb` with `host_test/peer.rb` (the mruby test
|
|
107
|
+
build adds mruby-eval for the ROS 2 type files).
|
|
108
|
+
|
|
109
|
+
Changed
|
|
110
|
+
|
|
111
|
+
- `Asterism.poll` does less when nothing came: each queue (calls, profile
|
|
112
|
+
queries, liveliness) is read only when its count is not zero, and the
|
|
113
|
+
list of nodes and the comparison behind `on_join` / `on_leave` are made
|
|
114
|
+
again only after a liveliness change, with Hashes instead of `include?`.
|
|
115
|
+
The behaviour is the same (the order and number of the `on_join` /
|
|
116
|
+
`on_leave` calls, the order of `Asterism.nodes`). With 4 other nodes and
|
|
117
|
+
5 objects, a poll in which nothing changed: CRuby 3.2 us and 49
|
|
118
|
+
allocations -> 0.6 us and 7; mruby (host) 14-17 us -> 2.2 us.
|
|
119
|
+
`Asterism.each` drops duplicates with a Hash.
|
|
120
|
+
|
|
9
121
|
## 0.5.0
|
|
10
122
|
|
|
11
123
|
Node profiles; every 0.4.x call works as before. Needs asterism-zenoh /
|
data/README.md
CHANGED
|
@@ -13,8 +13,9 @@ repository holds the pure Ruby layers, written once for every Ruby:
|
|
|
13
13
|
| `data/icon.png` | CRuby's default profile icon (32x32 PNG; [Profiles](#profiles)) |
|
|
14
14
|
| `data/msgs/` | ROS 2 message types generated from ROS 2 Jazzy's definitions (Apache-2.0; tf2_msgs BSD-3-Clause; see NOTICE) |
|
|
15
15
|
| `tools/` | The type generator `asterism_msggen.rb`, the Jazzy definitions it reads, `ros2_types.rb` (refreshes them from a ROS 2 image) |
|
|
16
|
-
| `test/` | `test/msgs` (types, CDR; CRuby only), `test_asterism.rb` (objects and ROS between CRuby sessions), `test_profile.rb` (node profiles), `test_api_*.rb` (the CRuby API) |
|
|
17
|
-
| `
|
|
16
|
+
| `test/` | `test/msgs` (types, CDR; CRuby only), `test_asterism.rb` (objects and ROS between CRuby sessions), `test_profile.rb` (node profiles), `test_session.rb` (`Asterism.session`, `on_join` / `on_leave`), `test_api_*.rb` (the CRuby API), `bench_poll.rb` (`rake bench:poll`) |
|
|
17
|
+
| `test/protocol/` | the conformance samples of the wire protocol (`samples.json`), the driver that checks an implementation against them, and the Ruby node it checks (`rake test:protocol`, `rake test:protocol_mruby`; [docs/protocol.md](docs/protocol.md)) |
|
|
18
|
+
| `host_test/` | `rake test:mruby`: the shared layer on mruby (profiles between two mruby processes; `Asterism.session`, `on_join` / `on_leave` and the time of a poll against `peer.rb`) |
|
|
18
19
|
| `examples/` | CRuby: `node.rb` (objects with a board), `node_polled.rb` (the same with the portable API of the boards), `ros2_talker.rb` (topics, a timer, services with ROS 2), `ros2_rover.rb` (drive a ROS 2 robot: `/cmd_vel`, `/odom`, `/imu`), `zenoh.rb` (plain Zenoh), `mujoco/` (MuJoCo run from CRuby through Fiddle, its robot exposed as Asterism objects; see its README) |
|
|
19
20
|
|
|
20
21
|
The Zenoh binding underneath is a separate gem with the same Ruby API on
|
|
@@ -39,7 +40,15 @@ asterism-zenoh too). What changed in each version is in
|
|
|
39
40
|
([docs/api_review.md](docs/api_review.md)) and only adds and deprecates
|
|
40
41
|
(see [Deprecations and 1.0](#deprecations-and-10)); 0.5.0 adds node
|
|
41
42
|
profiles ([Profiles](#profiles): what a node runs on, where, a name and an
|
|
42
|
-
icon).
|
|
43
|
+
icon); 0.6.0 adds `Asterism.session`
|
|
44
|
+
([One connection](#one-connection-asterismsession): the object layer's
|
|
45
|
+
session for plain Zenoh and ROS 2 too) and makes `Asterism.poll` cheap when
|
|
46
|
+
nothing changed. **0.7.0 speaks wire protocol 1 only**
|
|
47
|
+
([docs/protocol.md](docs/protocol.md)): it does not talk to the object
|
|
48
|
+
layer of earlier versions, so every node of a network moves to 0.7.0
|
|
49
|
+
together. It adds who called (`Asterism.current_call`, `expose(...,
|
|
50
|
+
caller: :from)`; [Who called](#who-called)), the kind of a remote error
|
|
51
|
+
(`RemoteError#remote_kind`) and `proxy.asterism_call`.
|
|
43
52
|
|
|
44
53
|
## Which API?
|
|
45
54
|
|
|
@@ -59,6 +68,7 @@ Entry points and what they give:
|
|
|
59
68
|
| `Asterism::Zenoh::Session.open` | a `Session` (portable) | zenoh-c / zenoh-pico |
|
|
60
69
|
| `Asterism::Zenoh.open` | a `Zenoh::Connection` (CRuby) | `c.session` is the `Session` |
|
|
61
70
|
| `Asterism.connect` | `Asterism` itself (portable; one connection per process or VM), or a `Net` to the block (CRuby) | a `Session` of its own |
|
|
71
|
+
| `Asterism.session` | that `Session` (portable), for put, subscribe and a ROS 2 node on the same connection; `net.session` is it as a `Zenoh::Connection` (CRuby) | the object layer's (Asterism closes it) |
|
|
62
72
|
| `Asterism::ROS::Node.new(session, ...)` | a `Node` (portable) | the `Session` given |
|
|
63
73
|
| `Asterism::ROS.connect` | a `ROS::Connection` (CRuby) | `ros.zenoh` is the `Zenoh::Connection` |
|
|
64
74
|
|
|
@@ -87,7 +97,7 @@ build but not yet tested. To install from the repositories instead:
|
|
|
87
97
|
|
|
88
98
|
```
|
|
89
99
|
(cd ../asterism-zenoh && rake gem) && rake gem
|
|
90
|
-
gem install --local ../asterism-zenoh/pkg/asterism-zenoh-0.4.0.gem pkg/asterism-0.
|
|
100
|
+
gem install --local ../asterism-zenoh/pkg/asterism-zenoh-0.4.0.gem pkg/asterism-0.7.0.gem
|
|
91
101
|
```
|
|
92
102
|
|
|
93
103
|
From the working trees, without installing:
|
|
@@ -97,12 +107,14 @@ ruby -I lib -I ../asterism-zenoh/lib your_script.rb # after `rake compile` in
|
|
|
97
107
|
```
|
|
98
108
|
|
|
99
109
|
`Asterism::ROS::TYPE_PATH` is `data/msgs` there; add your own directories.
|
|
100
|
-
MessagePack sends
|
|
101
|
-
|
|
110
|
+
MessagePack sends a binary (ASCII-8BIT) String as bin and any other String
|
|
111
|
+
as str (protocol 1; [Values](#values)).
|
|
102
112
|
|
|
103
113
|
mruby / PicoRuby: add this repository and picoruby-asterism-zenoh as gems
|
|
104
114
|
(`conf.gem "path/to/asterism"`; a `MessagePack` module with `pack` /
|
|
105
|
-
`unpack` is needed too
|
|
115
|
+
`unpack` is needed too: for protocol 1 it must write a binary String
|
|
116
|
+
(`String#b`) as bin and read a bin as one, and refuse ext types, integers
|
|
117
|
+
outside int64 and bytes after the value), and put `data/msgs` on the device's file system at
|
|
106
118
|
`/usr/share/asterism/msgs` (or add the place to
|
|
107
119
|
`Asterism::ROS::TYPE_PATH`). Family mruby does this from pinned commits of
|
|
108
120
|
these repositories.
|
|
@@ -116,6 +128,9 @@ rake test:objects # objects and Asterism::ROS between CRuby sessions over a
|
|
|
116
128
|
rake test:api # the CRuby API (blocks, receiving threads, Enumerators)
|
|
117
129
|
ASTERISM_TEST_ROUTER=tcp/127.0.0.1:7447 rake test:objects test:api # the same through a zenohd router
|
|
118
130
|
rake test:mruby # the shared layer on mruby (builds mruby; not part of `rake`)
|
|
131
|
+
rake test:protocol # the wire protocol's conformance samples and driver against CRuby (docs/protocol.md)
|
|
132
|
+
rake test:protocol_mruby # the same driver against mruby (builds mruby; not part of `rake`)
|
|
133
|
+
rake bench:poll # the time and allocations of one Asterism.poll by nodes and objects (prints a table)
|
|
119
134
|
```
|
|
120
135
|
|
|
121
136
|
`test:mruby` builds mruby with picoruby-asterism-zenoh and this gem
|
|
@@ -246,12 +261,14 @@ end
|
|
|
246
261
|
|---|---|
|
|
247
262
|
| `Asterism.connect(...) { \|net\| }` | yields an `Asterism::Net`, `Asterism.close` when the block ends; returns the block's value. Without a block, as before |
|
|
248
263
|
| `Asterism.net` | the `Net` of the connection made without a block (nil when not connected) |
|
|
249
|
-
| `net.expose` / `unexpose` / `exposed` | as `Asterism.expose
|
|
264
|
+
| `net.expose` / `unexpose` / `exposed` | as `Asterism.expose` (also `caller: :from`). The methods run on the receiving thread, in `run`, or in whatever polls |
|
|
265
|
+
| `net.current_call` | `Asterism.current_call`: the call being answered on this thread (the receiving thread while it answers), or nil |
|
|
250
266
|
| `net[path, timeout: 2.0]` | a proxy (`timeout` in seconds, or `timeout_ms:`) |
|
|
251
267
|
| `net.each(pattern = "**")` | the proxies alive now; an Enumerator without a block. With a block it returns their number (1.0: `self`, as Ruby's `each` does; `net.count` is the number) |
|
|
252
268
|
| `net.on_join { \|node\| }` / `net.on_leave { \|node\| }` | `Asterism.on_join` / `on_leave` of the portable API (the same blocks, told from `Asterism.poll`), here on the receiving thread; what they raise goes to `on_error`. `net.off_join(proc)` / `off_leave(proc)` remove one |
|
|
253
269
|
| `net.start` / `stop` / `run` / `running?` / `on_error` | as for Zenoh. A method of an exposed object that raises is answered to its caller as a `RemoteError`, as before; `on_error` gets what `on_join` / `on_leave` raise |
|
|
254
270
|
| `net.profile(**fields)` / `net.profile_of(node, icon: true, timeout: 2.0)` | `Asterism.profile` / `profile_of` ([Profiles](#profiles)); `profile_of` waits for the receiving thread's ticks while it runs |
|
|
271
|
+
| `net.session` | `Asterism.session` as a `Zenoh::Connection` (`subscribe { }`, `put`, `publisher`, ...) on this receiving thread: its blocks run there, between the calls answered. `shared?` is true; its `close` does nothing and `start` / `stop` / `run` are the `Net`'s. A ROS 2 node on it: `Asterism::ROS::Connection.new(net.session).node("name")`. See [One connection](#one-connection-asterismsession) |
|
|
255
272
|
| `net.nodes`, `node_id`, `app`, `connected?`, `lost_reason`, `poll`, `close` | |
|
|
256
273
|
|
|
257
274
|
### Over TLS (a router on the internet)
|
|
@@ -307,6 +324,7 @@ end
|
|
|
307
324
|
| `node.call(name, type, request = nil, request:, timeout: 2.0, **fields)` | waits for the response. Here `timeout:` is always the time limit (see [Reserved keywords](#reserved-keywords)) |
|
|
308
325
|
| `ros.spin` (`run`) / `start` / `stop` / `running?` / `on_error` / `close` | |
|
|
309
326
|
| `ros.zenoh` | the `Asterism::Zenoh::Connection` underneath (same receiving thread) |
|
|
327
|
+
| `Asterism::ROS::Connection.new(net.session, domain: 0)` | nodes on the object layer's session (the same Zenoh ID as the objects), spun by the `Net`'s receiving thread. Its `close` withdraws its nodes only |
|
|
310
328
|
|
|
311
329
|
`type` is a generated type or its name (`"geometry_msgs/msg/Twist"`).
|
|
312
330
|
Messages and `Attachment` take part in pattern matching (`deconstruct_keys`,
|
|
@@ -410,20 +428,23 @@ Asterism.nodes # => ["linux", "fmruby-bbbbbb"]
|
|
|
410
428
|
| Call | Returns | Raises / notes |
|
|
411
429
|
|---|---|---|
|
|
412
430
|
| `Asterism.connect(locator, node:, app:, mode: nil, listen: nil, config: nil, connect_timeout: nil, check_timeout: 1.5)` | `Asterism` | `locator`, `mode:`, `listen:` (and `config:` / `connect_timeout:` when given, CRuby only: TLS and the rest of zenoh's configuration; the boards raise `ArgumentError`) go to `Asterism::Zenoh::Session.open` (client of a router by default; `mode: :peer` with or without `listen:` without one). `node:` is this machine's ID, `app:` the application's name (one key chunk each: no `/ * $ ? #`, not starting with `@`; else `ArgumentError`). `Disconnected` when it cannot connect; `Error` when objects of the same `<node>/<app>` are already alive (a second copy of the application), or when already connected. Waits up to `check_timeout:` seconds for that check (a router answers at once; give more over a slow relay). Returns the module itself: on a board the module is the connection (one per application). The default is plain text without authentication; see [Over TLS](#over-tls-a-router-on-the-internet). |
|
|
413
|
-
| `Asterism.expose(name, obj, methods:)` | `"<node>/<app>/<name>"` | `methods:` is an Array of names, or a Hash `name => number of arguments` (checked before the call; `-1` or the Array form: any). `ArgumentError` when `obj` has no such public method. Exposing a name again replaces it. |
|
|
431
|
+
| `Asterism.expose(name, obj, methods:, caller: nil)` | `"<node>/<app>/<name>"` | `methods:` is an Array of names, or a Hash `name => number of arguments` (checked before the call; `-1` or the Array form: any). `ArgumentError` when `obj` has no such public method. Exposing a name again replaces it. `caller: :from` passes the calling node's ID to every method as the keyword `from:` and refuses a call that brings `from:` itself ([Who called](#who-called)). |
|
|
432
|
+
| `Asterism.current_call` | `Asterism::CurrentCall` or nil | While an exposed method runs for a call: `from` (the calling node's ID), `verified` (`:claimed`, or `:local` for a call from this application), `object`, `method`. nil outside a call ([Who called](#who-called)). |
|
|
414
433
|
| `Asterism.unexpose(name)` | true / false | |
|
|
415
|
-
| `Asterism.poll` | true / false | Call from the update loop: polls Zenoh, answers the calls that came in, follows who is alive and calls the `on_join` / `on_leave` blocks. `false` once the connection is closed or lost. |
|
|
416
|
-
| `Asterism.on_join { \|node\| }` / `Asterism.on_leave { \|node\| }` | `Asterism` | Another node appeared (
|
|
434
|
+
| `Asterism.poll` | true / false | Call from the update loop: polls Zenoh, answers the calls that came in, follows who is alive and calls the `on_join` / `on_leave` blocks. `false` once the connection is closed or lost. Cheap when nothing came (0.6.0): each queue is looked at by its count, and the list of nodes is made again only after a liveliness change. |
|
|
435
|
+
| `Asterism.on_join { \|node\| }` / `Asterism.on_leave { \|node\| }` | `Asterism` | Another node appeared (one of its applications or objects) / is gone (no application and no object left). The nodes there already join on the first polls after connecting; when the connection is lost, every node known until then leaves (on the next `Asterism.poll`). The blocks run from `Asterism.poll` only, never from the polling inside a waiting call; what they raise comes out of `Asterism.poll`. Each call adds a block; `Asterism.off_join(proc)` / `off_leave(proc)` remove one; `Asterism.close` forgets them (a lost connection does not). |
|
|
417
436
|
| `Asterism[path, timeout: 2.0]` | `Proxy` | `path` is `<node>/<app>/<object>` without wildcards (`ArgumentError`). `timeout:` (seconds) or `timeout_ms:` is the time limit of its calls. Nothing is sent until a method is called. |
|
|
418
|
-
| `proxy.<method>(*args, **kw)` | the remote return value | Waits for the answer (the proxy's time limit). `RemoteError` when it raised there or could not be called (not exposed
|
|
437
|
+
| `proxy.<method>(*args, **kw)` | the remote return value | Waits for the answer (the proxy's time limit). `RemoteError` when it raised there or could not be called (`remote_kind` says which: `raised`, or not exposed `not_exposed` (`NoMethodError`), wrong number of arguments `arguments` (`ArgumentError`), no such object `no_object` (`NameError`); [Errors](#errors)), `TimeoutError` when no answer came in time (also at once when nobody answers that key), `Disconnected`, `EncodeError` (before anything is sent) for a value MessagePack cannot carry. A block cannot be sent (`ArgumentError`). |
|
|
438
|
+
| `proxy.asterism_call(name, *args, **kw)` | the remote return value | The same call by the method's name (a String or Symbol): for names a proxy cannot forward (those `Object` and `Kernel` have: `display`, `hash`, `class`, `fail`, ...) and names that are not Ruby identifiers. `proxy.async.asterism_call(...)` returns a `Future`. |
|
|
419
439
|
| `proxy.async.<method>(...)` | `Future` | Sends and returns at once. `EncodeError` / `Disconnected` are raised here. |
|
|
420
440
|
| `future.done?` | true / false | Never waits (`Asterism.poll` moves it on). True once the answer came, the time ran out or the connection closed. |
|
|
421
441
|
| `future.value` | the return value | Waits (polling) if not done; raises like a waiting call. `future.took` (seconds) / `took_ms`: time to the answer. |
|
|
422
442
|
| `proxy.respond_to?(name)` / `proxy.remote_methods` | true / false, `[Symbol]` | From the object's meta (fetched once, `proxy.asterism_refresh` forgets it). `respond_to?` is false when the object does not answer; `remote_methods` raises then. (`proxy.methods` returns the same with a deprecation warning; from 1.0 it is `Object#methods`.) |
|
|
423
|
-
| `proxy.asterism_meta` / `proxy.asterism_path` | Hash, String | `{"methods" => [
|
|
443
|
+
| `proxy.asterism_meta` / `proxy.asterism_path` | Hash, String | `{"methods" => [{"name" => name, "arity" => n}, ...]}` |
|
|
424
444
|
| `Asterism.each(pattern = "**") { \|proxy\| }` | count (Array without a block) | The exposed objects alive now (this application's own included) whose `<node>/<app>/<object>` matches; `*` is one chunk, `**` any number. 1.0 returns the proxies from the block form instead of their number (Ruby's `each` returns its receiver); write `Asterism.each(pattern).size` for the count. |
|
|
425
445
|
| `Asterism.nodes` | `[String]` | Node IDs alive now, this one first. |
|
|
426
446
|
| `Asterism.connected?` / `node_id` / `app` / `exposed` / `lost_reason` | | |
|
|
447
|
+
| `Asterism.session` | `Asterism::Zenoh::Session` | The connection's own session, for put / subscribe / a ROS 2 node on the same connection ([One connection](#one-connection-asterismsession)). The same object until the connection closes. `Disconnected` when not connected. Its `close` does nothing (warns once); `Asterism.close` closes it. |
|
|
427
448
|
| `Asterism.close` | nil | Withdraws every exposed object. Idempotent. |
|
|
428
449
|
| `Asterism.profile(kind:, name:, host:, icon:)` / `Asterism.profile` | Hash | This node's profile: sets the fields given and returns it; without arguments, returns it. See [Profiles](#profiles). |
|
|
429
450
|
| `Asterism.profile_of(node, icon: true, timeout: 2.0)` | Hash or nil | Another node's profile, nil when it does not answer (an older version, a ROS 2 node). |
|
|
@@ -436,19 +457,20 @@ and an icon. A console draws it with that; anyone can ask.
|
|
|
436
457
|
```ruby
|
|
437
458
|
Asterism.profile(name: "Rover", icon: File.binread("rover.png")) # this node's (any time)
|
|
438
459
|
Asterism.profile
|
|
439
|
-
# => {"kind" => "cruby", "name" => "Rover", "icon" => "\x89PNG...", "icon_hash" => "2a8c0626"}
|
|
460
|
+
# => {"protocol" => 1, "kind" => "cruby", "name" => "Rover", "icon" => "\x89PNG...", "icon_hash" => "2a8c0626"}
|
|
440
461
|
Asterism.profile_of("fmruby-aaaaaa")
|
|
441
|
-
# => {"kind" => "family_mruby", "host" => "atoms3r", "icon" => "\x89PNG...", "icon_hash" => "..."}
|
|
462
|
+
# => {"protocol" => 1, "kind" => "family_mruby", "host" => "atoms3r", "icon" => "\x89PNG...", "icon_hash" => "..."}
|
|
442
463
|
Asterism.profile_of("fmruby-aaaaaa", icon: false) # the same without the icon bytes
|
|
443
464
|
Asterism.profile_of("an-older-node") # => nil
|
|
444
465
|
```
|
|
445
466
|
|
|
446
467
|
| Field | Value | Default |
|
|
447
468
|
|---|---|---|
|
|
469
|
+
| `protocol` | set by Asterism, never given: the wire protocol the node speaks (`Asterism::PROTOCOL`, 1). A node whose profile has none speaks protocol 0 (asterism before 0.7.0), which 0.7.0 does not talk to | |
|
|
448
470
|
| `kind` | what Ruby runs the node: `family_mruby`, `picoruby`, `cruby`, `console` (a lower-case word: `a-z`, `0-9`, `_`, up to 32 bytes; other words are allowed) | `cruby` on CRuby, `picoruby` on mruby / PicoRuby; Family mruby's firmware sets `family_mruby` |
|
|
449
471
|
| `host` | where it runs, e.g. `atoms3r`, `tab5`, `p4nano`, `browser`, `sim` (up to 32 bytes) | none; Family mruby's firmware sets it from the board |
|
|
450
472
|
| `name` | a display name (up to 64 bytes) | none |
|
|
451
|
-
| `icon` | the bytes of a PNG: at most 8 KB and 256x256 (32x32 is the size meant). PNG only: an SVG can carry scripts | the file at `Asterism.default_icon_path` when it is there: on CRuby the gem's `data/icon.png` (a ruby), on the boards `/usr/share/asterism/icon.png` |
|
|
473
|
+
| `icon` | the bytes of a PNG (a binary String; sent as a MessagePack bin): at most 8 KB and 256x256 (32x32 is the size meant). PNG only: an SVG can carry scripts | the file at `Asterism.default_icon_path` when it is there: on CRuby the gem's `data/icon.png` (a ruby), on the boards `/usr/share/asterism/icon.png` |
|
|
452
474
|
| `icon_hash` | set by Asterism, never given: the icon's summary (8 hex digits: the Adler-32 of the PNG's chunk CRCs, so a board computes it from a few bytes) | |
|
|
453
475
|
|
|
454
476
|
- **Setting.** `Asterism.profile(**fields)` changes the fields given and keeps
|
|
@@ -480,6 +502,59 @@ Asterism.profile_of("an-older-node") # => nil
|
|
|
480
502
|
an acceptable PNG (nil otherwise), for a tool that wants to check an icon
|
|
481
503
|
first.
|
|
482
504
|
|
|
505
|
+
## One connection: `Asterism.session`
|
|
506
|
+
|
|
507
|
+
An application that also puts, subscribes or runs a ROS 2 node uses the
|
|
508
|
+
object layer's own session for it (0.6.0) instead of opening a second one:
|
|
509
|
+
|
|
510
|
+
```ruby
|
|
511
|
+
Asterism.connect("tcp/192.0.2.2:7447", node: "fmruby-aaaaaa", app: "game")
|
|
512
|
+
Asterism.expose("othello", game, methods: [:move, :state])
|
|
513
|
+
s = Asterism.session # an Asterism::Zenoh::Session
|
|
514
|
+
s.put("asterism/fmruby-aaaaaa/game/othello/state", MessagePack.pack(state))
|
|
515
|
+
moves = s.subscribe("asterism/*/game/othello/state") # moves.each_pending in the update loop
|
|
516
|
+
node = Asterism::ROS::Node.new(s, "brain") # a ROS 2 node with the same Zenoh ID
|
|
517
|
+
cmd = node.publisher("/cmd_vel", "geometry_msgs/msg/Twist")
|
|
518
|
+
# update loop: Asterism.poll; node.poll
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
CRuby API:
|
|
522
|
+
|
|
523
|
+
```ruby
|
|
524
|
+
Asterism.connect(router, node: "mypc", app: "demo") do |net|
|
|
525
|
+
net.session.subscribe("asterism/*/game/**") { |sample| p sample.key } # on the receiving thread
|
|
526
|
+
ros = Asterism::ROS::Connection.new(net.session)
|
|
527
|
+
ros.node("brain").subscribe("/odom", "nav_msgs/msg/Odometry") { |odom| puts odom.pose.pose.position.x }
|
|
528
|
+
net.run
|
|
529
|
+
end
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
- **Why one.** A router that lets each pass in once (a one-time password
|
|
533
|
+
at a `wss://` door) lets only the first session in; and a ROS 2 node on
|
|
534
|
+
the object layer's session has the node's Zenoh ID, so a console draws it
|
|
535
|
+
inside the node.
|
|
536
|
+
- **Who closes it.** The session is Asterism's. `Asterism.session.close`
|
|
537
|
+
(and `net.session.close`) does nothing but warn once, so a library handed
|
|
538
|
+
the session cannot break the connection. `Asterism.close`, the end of the
|
|
539
|
+
connect block and a lost connection close it, and with it everything
|
|
540
|
+
declared on it: the application's subscribers, publishers, ROS 2 nodes.
|
|
541
|
+
Close those yourself when you are done with them earlier (`sub.close`,
|
|
542
|
+
`node.close`; on CRuby `ros.close` withdraws its nodes and leaves the
|
|
543
|
+
rest).
|
|
544
|
+
- **After a lost connection** (`Asterism.poll` returned false), connect
|
|
545
|
+
again, take `Asterism.session` again (a new object) and declare again
|
|
546
|
+
what you had on the old one.
|
|
547
|
+
- **Polling.** `Asterism.poll` polls the session; `node.poll` polls it too
|
|
548
|
+
and only then serves the node. Each subscriber, queryable and node keeps
|
|
549
|
+
what came for it, so the order does not matter and nothing is taken from
|
|
550
|
+
another. Your own `put` does not come back to your own subscriber on the
|
|
551
|
+
same session.
|
|
552
|
+
- **Keys.** Keep your keys out of the object layer's: `asterism/<node>/<app>/**`
|
|
553
|
+
is answered by its queryable (`get` there is a call), `asterism/<node>/<app>` and
|
|
554
|
+
`asterism/<node>/<app>/<object>` are its liveliness tokens. Putting under
|
|
555
|
+
`asterism/<node>/<app>/<object>/<anything>` (a state, a log) is fine:
|
|
556
|
+
puts and subscriptions do not meet queryables or liveliness.
|
|
557
|
+
|
|
483
558
|
## Errors
|
|
484
559
|
|
|
485
560
|
Every error Asterism raises is an `Asterism::Error` (a `StandardError`):
|
|
@@ -492,12 +567,18 @@ Asterism::Error (the Zenoh bindings define it)
|
|
|
492
567
|
├── Asterism::TimeoutError (no answer in time; old name Asterism::Timeout)
|
|
493
568
|
│ └── Asterism::ROS::TimeoutError (old name Asterism::ROS::Timeout)
|
|
494
569
|
├── Asterism::EncodeError
|
|
495
|
-
├── Asterism::RemoteError (remote_class, remote_message)
|
|
570
|
+
├── Asterism::RemoteError (remote_kind, remote_class, remote_message)
|
|
496
571
|
├── Asterism::ROS::UnknownType
|
|
497
572
|
├── Asterism::CDR::DecodeError
|
|
498
573
|
└── Asterism::DeprecationError (only with Asterism.deprecations = :raise)
|
|
499
574
|
```
|
|
500
575
|
|
|
576
|
+
`RemoteError#remote_kind` says what happened on the other side, as a word
|
|
577
|
+
of the protocol (docs/protocol.md 4.4): `raised` (the method raised;
|
|
578
|
+
`remote_class` is its class there), `not_exposed`, `no_object`,
|
|
579
|
+
`arguments`, `malformed`, `unsendable`, `nesting`, `protocol` (the other
|
|
580
|
+
side speaks a different protocol); nil when the answer did not say.
|
|
581
|
+
|
|
501
582
|
The old names `Asterism::Timeout` and `Asterism::ROS::Timeout` still work
|
|
502
583
|
(they warn once). Inside `module Asterism`, `Timeout` is Ruby's `Timeout`
|
|
503
584
|
module again. The ROS layer raises `Zenoh::ClosedError` when its session
|
|
@@ -591,26 +672,77 @@ Asterism follows `Asterism::Zenoh`: the connection is closed when the router
|
|
|
591
672
|
why, and calls raise `Asterism::Disconnected`. **`Asterism::Zenoh::Error`
|
|
592
673
|
never comes out of Asterism; it is always wrapped in `Disconnected`** (the
|
|
593
674
|
message is kept). The exposed objects are gone with the connection: to go
|
|
594
|
-
on, the application calls `connect` and `expose` again
|
|
675
|
+
on, the application calls `connect` and `expose` again, and declares again
|
|
676
|
+
what it had on `Asterism.session` (subscribers, ROS 2 nodes; the old
|
|
677
|
+
session is closed, its entities raise `Zenoh::ClosedError`).
|
|
595
678
|
|
|
596
679
|
## Values
|
|
597
680
|
|
|
598
|
-
Only what MessagePack carries: `nil`, `true`, `false`, `Integer` (
|
|
599
|
-
`Float`, `String`, `Array`, `Hash`. A `Symbol` is sent as a
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
681
|
+
Only what MessagePack carries: `nil`, `true`, `false`, `Integer` (signed
|
|
682
|
+
64 bit), `Float`, `String`, `Array`, `Hash`. A `Symbol` is sent as a
|
|
683
|
+
`String` and arrives as one (also as a Hash key; keyword names become
|
|
684
|
+
Symbols again). A binary String (`String#b`: ASCII-8BIT on CRuby, the
|
|
685
|
+
binary flag on mruby) goes as a MessagePack bin and arrives as a binary
|
|
686
|
+
String; any other String goes as text (str). Anything else in the
|
|
687
|
+
arguments, or an Integer outside the signed 64-bit range, raises
|
|
688
|
+
`EncodeError` before sending; in a return value the caller gets
|
|
689
|
+
`RemoteError` (`unsendable`). Nesting is limited to 16 levels.
|
|
690
|
+
|
|
691
|
+
- Hash keys: use String (or Symbol) and Integer keys for anything another
|
|
692
|
+
language reads (Python and JavaScript refuse others by default).
|
|
693
|
+
- Times: send an Integer of nanoseconds since the epoch (or name the unit:
|
|
694
|
+
`timeout_ms`); there is no time type.
|
|
695
|
+
- A keyword given as `nil` and one left out differ: the callee's default
|
|
696
|
+
applies only to the one left out.
|
|
697
|
+
- A JavaScript caller's `1.0` arrives as the Integer `1`.
|
|
698
|
+
|
|
699
|
+
## Who called
|
|
700
|
+
|
|
701
|
+
The caller's node ID travels in the call's key (`.../call/<caller>`). An
|
|
702
|
+
exposed method can read it:
|
|
703
|
+
|
|
704
|
+
```ruby
|
|
705
|
+
class Rover
|
|
706
|
+
def stop
|
|
707
|
+
c = Asterism.current_call # nil outside a call
|
|
708
|
+
log("stop from #{c.from}") # c.verified: :claimed (:local from this app)
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
def drive(v, w, from:) # caller: :from fills from: in
|
|
712
|
+
log("drive from #{from}")
|
|
713
|
+
end
|
|
714
|
+
end
|
|
715
|
+
Asterism.expose("rover", Rover.new, methods: { stop: 0 })
|
|
716
|
+
Asterism.expose("wheels", Rover.new, methods: { drive: 2 }, caller: :from)
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
- `Asterism.current_call` is set only while a method runs for a call (per
|
|
720
|
+
thread on CRuby). Calls answered while that method waits for its own
|
|
721
|
+
call come on top of it and go when they end.
|
|
722
|
+
- With `caller: :from`, a call that passes `from:` itself is refused
|
|
723
|
+
(`RemoteError`, `remote_kind` `arguments`): `from` is always the one
|
|
724
|
+
Asterism took from the call.
|
|
725
|
+
- **It is a claim.** Any client can call with any name. A router's access
|
|
726
|
+
control makes it binding: with mutual TLS, zenohd's ACL can let each
|
|
727
|
+
certificate query only keys ending in its own name
|
|
728
|
+
(`asterism/*/*/*/call/<name>`; docs/protocol.md 4.7). Without such a
|
|
729
|
+
router, use `from` to avoid mistakes, not to decide who may do what.
|
|
604
730
|
|
|
605
731
|
## Keys and encoding
|
|
606
732
|
|
|
733
|
+
Wire protocol 1. The full description, for implementations in other
|
|
734
|
+
languages, is [docs/protocol.md](docs/protocol.md) (with the conformance
|
|
735
|
+
tests); why it has this shape (the review of protocol 0's Ruby-shaped
|
|
736
|
+
parts, and what was decided) is
|
|
737
|
+
[docs/protocol_review.md](docs/protocol_review.md).
|
|
738
|
+
|
|
607
739
|
| Key | Zenoh | Payload |
|
|
608
740
|
|---|---|---|
|
|
609
|
-
| `asterism/<node>/<app>/<object>/call
|
|
610
|
-
| `asterism/<node>/<app>/<object>/meta` | get / queryable | reply: `{"methods" => [
|
|
611
|
-
| `asterism/<node>` | liveliness | the
|
|
741
|
+
| `asterism/<node>/<app>/<object>/call/<caller>` | get / queryable | query: `{"method" => name, "args" => [...], "kwargs" => {...}}` (args and kwargs left out when empty); reply: `{"value" => value}` or `{"error" => {"kind" => ..., "class" => ..., "message" => ...}}`. `<caller>` is the calling node's ID |
|
|
742
|
+
| `asterism/<node>/<app>/<object>/meta` | get / queryable | reply: `{"methods" => [{"name" => name, "arity" => n}, ...]}` (arity `-1` when not declared). The object may be `*` in the query: one reply per object |
|
|
743
|
+
| `asterism/<node>/<app>` | liveliness | the application is connected (a node is there while any of its applications or objects is) |
|
|
612
744
|
| `asterism/<node>/<app>/<object>` | liveliness | the object is exposed |
|
|
613
|
-
| `asterism/<node>/@profile` | get / queryable, and put | query (parameter `icon=0` for no icon): reply the node's profile, a Hash (`kind`, `host`, `name`, `icon`, `icon_hash`; [Profiles](#profiles)). The node also puts the profile without the icon at connect and on each change. `@profile` is a verbatim chunk, so `asterism/**` does not match it: the object layer's wildcards never see it. An application's own profile would go at `asterism/<node>/<app>/@profile` (not
|
|
745
|
+
| `asterism/<node>/@profile` | get / queryable, and put | query (parameter `icon=0` for no icon): reply the node's profile, a Hash (`kind`, `host`, `name`, `icon`, `icon_hash`; [Profiles](#profiles)). The node also puts the profile without the icon at connect and on each change. `@profile` is a verbatim chunk, so `asterism/**` does not match it: the object layer's wildcards never see it. An application's own profile would go at `asterism/<node>/<app>/@profile` (not yet) |
|
|
614
746
|
|
|
615
747
|
One queryable per application (`asterism/<node>/<app>/**`), and one for the
|
|
616
748
|
profile (`asterism/<node>/@profile`). A call whose
|
|
@@ -665,9 +797,9 @@ nested type (`geometry_msgs/msg/Twist`) at start-up took 0.6 KB more.
|
|
|
665
797
|
|
|
666
798
|
- Objects cannot be passed by reference (a return value is a copy), blocks
|
|
667
799
|
cannot be sent, there are no events (later stages).
|
|
668
|
-
- The
|
|
669
|
-
|
|
670
|
-
|
|
800
|
+
- The wire protocol is protocol 1 only: nodes of asterism before 0.7.0
|
|
801
|
+
(protocol 0) do not see 0.7.0's objects and a call between the two
|
|
802
|
+
fails (a protocol 0 call is refused with `remote_kind` `protocol`).
|
|
671
803
|
- No authentication by default (plain TCP): a trusted LAN is assumed, or
|
|
672
804
|
TLS through a router (CRuby; [Over TLS](#over-tls-a-router-on-the-internet)).
|
|
673
805
|
- One object-layer connection per process (per VM on the boards).
|
|
@@ -27,7 +27,7 @@ module Asterism
|
|
|
27
27
|
# singleton class, so the shared methods themselves are not changed.
|
|
28
28
|
module Locked
|
|
29
29
|
%i[expose unexpose poll nodes [] call_async meta connected? exposed on_join on_leave off_join off_leave
|
|
30
|
-
profile profile_async].each do |m|
|
|
30
|
+
profile profile_async session].each do |m|
|
|
31
31
|
define_method(m) do |*args, **kw, &blk|
|
|
32
32
|
::Asterism::LOCK.synchronize { super(*args, **kw, &blk) }
|
|
33
33
|
end
|
|
@@ -130,6 +130,21 @@ module Asterism
|
|
|
130
130
|
@runner.add { ::Asterism.poll ? 0 : false }
|
|
131
131
|
end
|
|
132
132
|
|
|
133
|
+
# Asterism.session as a Zenoh::Connection (the block API of
|
|
134
|
+
# Asterism::Zenoh.open) on the same receiving thread as the objects:
|
|
135
|
+
# subscribe / queryable blocks run there, between the calls answered.
|
|
136
|
+
# For a ROS 2 node on the same session:
|
|
137
|
+
# Asterism::ROS::Connection.new(net.session).node("name"). The session
|
|
138
|
+
# stays Asterism's: net.session.close does nothing (shared?), net.close
|
|
139
|
+
# closes it. Disconnected when not connected.
|
|
140
|
+
def session
|
|
141
|
+
::Asterism::LOCK.synchronize do
|
|
142
|
+
s = ::Asterism.session
|
|
143
|
+
@session = nil if @session && !@session.session.equal?(s)
|
|
144
|
+
@session ||= ::Asterism::Zenoh::Connection.new(s, runner: @runner)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
133
148
|
def node_id = ::Asterism.node_id
|
|
134
149
|
def app = ::Asterism.app
|
|
135
150
|
def connected? = ::Asterism.connected?
|
|
@@ -138,12 +153,16 @@ module Asterism
|
|
|
138
153
|
def exposed = ::Asterism.exposed
|
|
139
154
|
|
|
140
155
|
# Asterism.expose: methods: an Array of names or a Hash name => number
|
|
141
|
-
# of arguments
|
|
156
|
+
# of arguments; caller: :from passes the calling node's ID as that
|
|
157
|
+
# keyword. The object's methods run on the receiving thread (or in
|
|
142
158
|
# run, or in whatever polls).
|
|
143
|
-
def expose(name, obj, methods:)
|
|
144
|
-
::Asterism.expose(name, obj, methods: methods)
|
|
159
|
+
def expose(name, obj, methods:, caller: nil)
|
|
160
|
+
::Asterism.expose(name, obj, methods: methods, caller: caller)
|
|
145
161
|
end
|
|
146
162
|
|
|
163
|
+
# Asterism.current_call: the call being answered on this thread, or nil.
|
|
164
|
+
def current_call = ::Asterism.current_call
|
|
165
|
+
|
|
147
166
|
def unexpose(name)
|
|
148
167
|
::Asterism.unexpose(name)
|
|
149
168
|
end
|
data/lib/asterism/cruby/ros.rb
CHANGED
|
@@ -95,12 +95,15 @@ module Asterism
|
|
|
95
95
|
self
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
-
# Stops spinning, withdraws the nodes and closes the session.
|
|
98
|
+
# Stops spinning, withdraws the nodes and closes the session. On
|
|
99
|
+
# Asterism's session (ROS::Connection.new(net.session)) it only
|
|
100
|
+
# withdraws the nodes: the receiving thread and the session stay the
|
|
101
|
+
# object layer's.
|
|
99
102
|
def close
|
|
100
|
-
runner.stop
|
|
103
|
+
runner.stop unless @zenoh.shared?
|
|
101
104
|
@nodes.each { |n| n.close rescue ::Asterism::Zenoh::Error }
|
|
102
105
|
@nodes = []
|
|
103
|
-
@zenoh.close
|
|
106
|
+
@zenoh.close unless @zenoh.shared?
|
|
104
107
|
nil
|
|
105
108
|
end
|
|
106
109
|
|
|
@@ -142,8 +145,17 @@ module Asterism
|
|
|
142
145
|
|
|
143
146
|
def asterism_runner = @asterism_runner
|
|
144
147
|
|
|
148
|
+
# Withdraws the node and stops polling it.
|
|
149
|
+
def close
|
|
150
|
+
@asterism_runner.lock.synchronize do
|
|
151
|
+
super
|
|
152
|
+
@asterism_runner.remove(@asterism_task) if @asterism_task
|
|
153
|
+
@asterism_task = nil
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
145
157
|
# The calls that change the node take the connection's lock.
|
|
146
|
-
%i[subscription client
|
|
158
|
+
%i[subscription client every cancel_timer].each do |m|
|
|
147
159
|
define_method(m) do |*args, **kw, &blk|
|
|
148
160
|
@asterism_runner.lock.synchronize { super(*args, **kw, &blk) }
|
|
149
161
|
end
|
data/lib/asterism/cruby/zenoh.rb
CHANGED
|
@@ -158,11 +158,29 @@ module Asterism
|
|
|
158
158
|
class Connection
|
|
159
159
|
attr_reader :session, :runner
|
|
160
160
|
|
|
161
|
-
|
|
161
|
+
# runner: an existing Runner to receive on instead of a new one
|
|
162
|
+
# (Asterism::Net#session gives the object layer's: the session and
|
|
163
|
+
# its receiving thread are Asterism's, see shared?). @api private
|
|
164
|
+
def initialize(session, interval: Runner::DEFAULT_INTERVAL, runner: nil)
|
|
162
165
|
@session = session
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
+
if runner
|
|
167
|
+
@runner = runner
|
|
168
|
+
@shared = true
|
|
169
|
+
else
|
|
170
|
+
@runner = Runner.new(session, interval: interval, name: "zenoh")
|
|
171
|
+
# The first task: false (the runner stops) once the session closed.
|
|
172
|
+
@runner.add { @session.poll ? 0 : false }
|
|
173
|
+
@shared = false
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# True for the Connection of Asterism's own session (net.session):
|
|
178
|
+
# its close does nothing, start / stop / run are the object layer's
|
|
179
|
+
# receiving thread (net.start / net.stop / net.run), and
|
|
180
|
+
# Asterism.close (or the end of the connect block) closes the session
|
|
181
|
+
# with everything declared on it.
|
|
182
|
+
def shared?
|
|
183
|
+
@shared
|
|
166
184
|
end
|
|
167
185
|
|
|
168
186
|
# opts: those of Session#put (attachment:, encoding:, priority:,
|
|
@@ -370,15 +388,23 @@ module Asterism
|
|
|
370
388
|
def declare_keyexpr(key) = @session.declare_keyexpr(key)
|
|
371
389
|
|
|
372
390
|
# Stops receiving and closes the session (with everything declared on
|
|
373
|
-
# it). Idempotent.
|
|
391
|
+
# it). Idempotent. On a shared? Connection (net.session) it does
|
|
392
|
+
# nothing: close what you declared on it (subscriptions, publishers,
|
|
393
|
+
# ROS nodes) one by one, or let Asterism.close close them.
|
|
374
394
|
def close
|
|
395
|
+
if @shared
|
|
396
|
+
::Asterism.warn_once(self, "asterism: net.session belongs to Asterism; its close is ignored " \
|
|
397
|
+
"(net.close closes it)")
|
|
398
|
+
return nil
|
|
399
|
+
end
|
|
375
400
|
@runner.close
|
|
376
401
|
@session.close
|
|
377
402
|
nil
|
|
378
403
|
end
|
|
379
404
|
|
|
380
405
|
def inspect
|
|
381
|
-
"#<Asterism::Zenoh::Connection #{closed? ? 'closed' : zid}#{
|
|
406
|
+
"#<Asterism::Zenoh::Connection #{closed? ? 'closed' : zid}#{@shared ? ' shared' : ''}" \
|
|
407
|
+
"#{running? ? ' running' : ''}>"
|
|
382
408
|
end
|
|
383
409
|
|
|
384
410
|
private
|