asterism 0.2.0 → 0.3.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/README.md +41 -8
- data/lib/asterism/cruby/zenoh.rb +266 -40
- data/lib/asterism/version.rb +1 -1
- data/mrblib/ros.rb +3 -3
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f03c6be1c5461da008a067c55b609a009b31b7552810836b7272bcdd35443ba7
|
|
4
|
+
data.tar.gz: df8c8641c8039408dcb3ca803b7fdcfe0a24e3eef3199da0b1277e980977f936
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 663fbb3e79d9b824128b541b899028809d899916af70a28fe761581fa2e28285f9841ef78dfafd088c2583f2c9df705328566b1f465ec31886df277c013c90a2
|
|
7
|
+
data.tar.gz: d447307b7bba1387d35316044c483f974a814ab1b0a4c7f5d32e17e8fc0b954ed2388ee53aaef9bf780bb7ccb98a93669fdf30f0efb7357d007535cae26a33ec
|
data/README.md
CHANGED
|
@@ -23,7 +23,15 @@ each Ruby:
|
|
|
23
23
|
- mruby / PicoRuby: [picoruby-asterism-zenoh](https://github.com/ruby-asterism/picoruby-asterism-zenoh)
|
|
24
24
|
(over zenoh-pico)
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Which zenoh-c features the CRuby binding exposes (configuration and TLS,
|
|
27
|
+
scouting, publishers, queriers, the advanced publisher / subscriber,
|
|
28
|
+
events, key expressions, timestamps) and which zenoh-pico could offer is
|
|
29
|
+
in asterism-zenoh's
|
|
30
|
+
[feature coverage](https://github.com/ruby-asterism/asterism-zenoh/blob/main/docs/feature_coverage.md).
|
|
31
|
+
The ones added in 0.3.0 are CRuby only: the shared layer (`mrblib/`) does
|
|
32
|
+
not use them.
|
|
33
|
+
|
|
34
|
+
Neither gem is on rubygems.org yet (version 0.3.0 builds from these
|
|
27
35
|
repositories with `rake gem`).
|
|
28
36
|
|
|
29
37
|
## Using it
|
|
@@ -31,7 +39,7 @@ repositories with `rake gem`).
|
|
|
31
39
|
CRuby (needs CRuby 3.2+ and a C compiler):
|
|
32
40
|
|
|
33
41
|
```
|
|
34
|
-
gem install asterism # also installs asterism-zenoh (~> 0.
|
|
42
|
+
gem install asterism # also installs asterism-zenoh (~> 0.3.0) and msgpack
|
|
35
43
|
ruby -e 'require "asterism"'
|
|
36
44
|
```
|
|
37
45
|
|
|
@@ -44,7 +52,7 @@ build but not yet tested. To install from the repositories instead:
|
|
|
44
52
|
|
|
45
53
|
```
|
|
46
54
|
(cd ../asterism-zenoh && rake gem) && rake gem
|
|
47
|
-
gem install --local ../asterism-zenoh/pkg/asterism-zenoh-0.
|
|
55
|
+
gem install --local ../asterism-zenoh/pkg/asterism-zenoh-0.3.0.gem pkg/asterism-0.3.0.gem
|
|
48
56
|
```
|
|
49
57
|
|
|
50
58
|
From the working trees, without installing:
|
|
@@ -115,17 +123,41 @@ Asterism::Zenoh.open("tcp/192.0.2.2:7447") do |s| # closed when the block ends
|
|
|
115
123
|
s.liveliness_watch("asterism/**") { |key, alive| puts "#{key} #{alive}" }
|
|
116
124
|
s.run # receive on this thread until Ctrl-C
|
|
117
125
|
end
|
|
126
|
+
|
|
127
|
+
# asterism-zenoh 0.3.0 (CRuby only)
|
|
128
|
+
Asterism::Zenoh.open("tls/192.0.2.2:7447",
|
|
129
|
+
config: { "transport/link/tls/root_ca_certificate" => "ca.pem" }) do |s|
|
|
130
|
+
pub = s.publisher("home/pc/temp", encoding: "text/plain", priority: :data_high)
|
|
131
|
+
pub.on_matching { |listening| puts "listened to: #{listening}" }
|
|
132
|
+
pub.put(21.5)
|
|
133
|
+
s.delete("home/pc/old")
|
|
134
|
+
s.subscribe("home/**") { |sm| p [sm.kind, sm.encoding, sm.timestamp&.to_time] }
|
|
135
|
+
q = s.querier("home/**/status", timeout: 1.0)
|
|
136
|
+
q.get(errors: true).each { |r| puts r.error? ? "error: #{r.payload}" : r.payload }
|
|
137
|
+
latched = s.advanced_publisher("home/pc/mode", cache: 1, sample_miss_detection: true)
|
|
138
|
+
latched.put("eco") # late subscribers with history: get it
|
|
139
|
+
s.advanced_subscriber("home/**/mode", history: true) { |sm| puts sm.payload }
|
|
140
|
+
s.on_transport { |ev| puts "#{ev.zid} #{ev.kind}" }
|
|
141
|
+
s.run
|
|
142
|
+
end
|
|
118
143
|
```
|
|
119
144
|
|
|
120
145
|
| Call | Notes |
|
|
121
146
|
|---|---|
|
|
122
|
-
| `Asterism::Zenoh.open(locator = nil, mode:, listen:, interval: 0.002)` | `Session.open` wrapped in a `Connection`. With a block: yields it, closes it after (also on an exception), returns the block's value |
|
|
147
|
+
| `Asterism::Zenoh.open(locator = nil, mode:, listen:, scouting:, timestamping:, config:, config_file:, interval: 0.002)` | `Session.open` wrapped in a `Connection`. With a block: yields it, closes it after (also on an exception), returns the block's value |
|
|
123
148
|
| `Connection.new(session)` | wraps a session opened with the polled API |
|
|
124
|
-
| `s.put(key, payload, attachment: nil)` | a payload that is not a String is sent as `to_s` |
|
|
149
|
+
| `s.put(key, payload, attachment: nil, **opts)` | a payload that is not a String is sent as `to_s`. opts: `encoding:`, `priority:`, `congestion_control:`, `express:`, `reliability:`, `timestamp:`, `allowed_destination:` (asterism-zenoh's `Session#put`) |
|
|
150
|
+
| `s.delete(key, **opts)` | subscribers get a `Sample` of kind `:delete` |
|
|
151
|
+
| `s.publisher(key, **opts)` | a `Publisher`: `put(payload, attachment:, encoding:, timestamp:)`, `delete`, `matching?`, `on_matching { \|listening\| }` (on the receiving thread), `close` |
|
|
152
|
+
| `s.advanced_publisher(key, cache:, sample_miss_detection:, publisher_detection:, **opts)` | the same `Publisher`, keeping the last samples for late subscribers (ROS 2's transient local) |
|
|
153
|
+
| `s.advanced_subscriber(key, depth: 16, history:, recovery:, **opts) { \|sample\| }` | a `Subscription` that also gets the publishers' history; `on_publisher { \|key, alive\| }`, `on_miss { \|miss\| }` |
|
|
154
|
+
| `s.querier(key, timeout: 2.0, **opts)` | a `Querier`: `get(params:, payload:, attachment:, encoding:, errors: false)` like `s.get`, `matching?`, `on_matching`, `close` |
|
|
155
|
+
| `s.on_transport(history: false) { \|ev\| }` / `s.on_link { \|ev\| }` | a `TransportEvent` / `LinkEvent` (`added?` / `removed?`, `zid`, ...) on the receiving thread; returns an `Events` (`close`) |
|
|
156
|
+
| `s.peer_zids`, `router_zids`, `transports`, `links`, `new_timestamp`, `declare_keyexpr(key)` | as in asterism-zenoh |
|
|
125
157
|
| `s.subscribe(key, depth: 16) { \|sample\| }` | the block gets each `Sample` on the receiving thread. Returns a `Subscription` (`close` ends it) |
|
|
126
158
|
| `s.subscribe(key, depth: 16)` | a `Subscription` to take from: `each(timeout: nil)` waits for samples (an Enumerator without a block; Enumerable), `each_pending` gives what is there now as Samples, `pending` / `received` / `dropped` / `closed?` / `close` |
|
|
127
159
|
| `s.queryable(key, depth: 16, complete: false) { \|q\| }` | each query on the receiving thread, finished when the block returns. `q.reply(payload)` answers on the queryable's key (when it has no wildcard), `q.reply(key, payload, attachment:)` as before. Without a block, `each(timeout:)` yields the queries |
|
|
128
|
-
| `s.get(key, timeout: 2.0, params:, payload:, attachment:, target:, consolidation:)` | an Enumerator of `Reply`; each iteration sends the get again. With a block: yields each, returns their number |
|
|
160
|
+
| `s.get(key, timeout: 2.0, params:, payload:, attachment:, target:, consolidation:, errors: false, **opts)` | an Enumerator of `Reply`; each iteration sends the get again. With a block: yields each, returns their number. `errors: true` also yields the error replies (`reply.error?`); opts: `encoding:`, `priority:`, `congestion_control:`, `express:`, `accept_replies:` |
|
|
129
161
|
| `s.liveliness(key)` | a token (`close`) |
|
|
130
162
|
| `s.liveliness_watch(key) { \|key, alive\| }` | on the receiving thread; the tokens alive now come first. Without a block, `each` yields `Liveliness` (`key`, `alive?`) |
|
|
131
163
|
| `s.liveliness_get(key, timeout: 2.0)` | the keys alive now (an Array) |
|
|
@@ -134,8 +166,9 @@ end
|
|
|
134
166
|
| `s.on_error { \|error, where\| }` | what the blocks raise (`StandardError`); without a handler it is printed with `warn`. Receiving goes on |
|
|
135
167
|
| `s.session`, `zid`, `peers`, `poll`, `closed?`, `close` | |
|
|
136
168
|
|
|
137
|
-
`Sample`, `Reply` (`key`, `payload`, `attachment
|
|
138
|
-
|
|
169
|
+
`Sample`, `Reply` (`key`, `payload`, `attachment`, and since 0.3.0 `kind`,
|
|
170
|
+
`encoding`, `timestamp` and the rest; `text` is the payload as UTF-8; both
|
|
171
|
+
are asterism-zenoh's) and `Liveliness` are `Data` values, so they work with pattern
|
|
139
172
|
matching: `case sample in {key: %r{/temp\z}, payload:}`. Times are seconds
|
|
140
173
|
here (the polled API counts milliseconds).
|
|
141
174
|
|
data/lib/asterism/cruby/zenoh.rb
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
# s.put("home/pc/hello", "hi")
|
|
9
9
|
# s.get("home/**/status").each { |reply| p reply }
|
|
10
10
|
# s.subscribe("sensor/temp").each.lazy.map { _1.payload.to_f }.first(3)
|
|
11
|
+
# pub = s.publisher("home/pc/temp", encoding: "text/plain")
|
|
12
|
+
# pub.on_matching { |listening| puts "someone listens: #{listening}" }
|
|
13
|
+
# s.on_transport { |ev| puts "#{ev.zid} #{ev.kind}" }
|
|
11
14
|
# s.run # until Ctrl-C (or s.start / s.stop)
|
|
12
15
|
# end
|
|
13
16
|
#
|
|
@@ -19,28 +22,8 @@ module Asterism
|
|
|
19
22
|
# Pause between looks while an Enumerator waits for data (seconds).
|
|
20
23
|
ENUM_STEP = 0.002
|
|
21
24
|
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
Sample = Data.define(:key, :payload, :attachment) do
|
|
25
|
-
def text
|
|
26
|
-
payload.dup.force_encoding(Encoding::UTF_8)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def to_s
|
|
30
|
-
"#{key}: #{payload}"
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# A reply to a get: the key it was sent with, payload and attachment.
|
|
35
|
-
Reply = Data.define(:key, :payload, :attachment) do
|
|
36
|
-
def text
|
|
37
|
-
payload.dup.force_encoding(Encoding::UTF_8)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def to_s
|
|
41
|
-
"#{key}: #{payload}"
|
|
42
|
-
end
|
|
43
|
-
end
|
|
25
|
+
# Sample and Reply (the received values, with kind, encoding,
|
|
26
|
+
# timestamp and the rest) are asterism-zenoh's (asterism/zenoh/values).
|
|
44
27
|
|
|
45
28
|
# A liveliness change: key of the token, and whether it appeared.
|
|
46
29
|
Liveliness = Data.define(:key, :alive) do
|
|
@@ -57,6 +40,20 @@ module Asterism
|
|
|
57
40
|
args.unshift(@asterism_key) if args.size == 1 && @asterism_key
|
|
58
41
|
super(*args, **kw)
|
|
59
42
|
end
|
|
43
|
+
|
|
44
|
+
# reply_del without a key: the queryable's own key, as reply.
|
|
45
|
+
def reply_del(key = nil, **kw)
|
|
46
|
+
super(key || @asterism_key, **kw)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def reply_err(payload, **kw)
|
|
50
|
+
super(payload.is_a?(String) ? payload : payload.to_s, **kw)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# A payload as a String (anything else is sent as to_s).
|
|
55
|
+
def self.bytes(payload)
|
|
56
|
+
payload.is_a?(String) ? payload : payload.to_s
|
|
60
57
|
end
|
|
61
58
|
|
|
62
59
|
# The queryable's key when it names one key (no wildcard).
|
|
@@ -92,10 +89,10 @@ module Asterism
|
|
|
92
89
|
# Waits on the calling thread until a sample comes into sub (an
|
|
93
90
|
# Asterism::Zenoh::Subscriber or LivelinessWatch), yielding each.
|
|
94
91
|
# Ends when sub or the session closes, or after timeout seconds.
|
|
95
|
-
def self.drain(sub, session, timeout)
|
|
92
|
+
def self.drain(sub, session, timeout, take: :each_pending)
|
|
96
93
|
deadline = timeout && (Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout)
|
|
97
94
|
loop do
|
|
98
|
-
got = sub.
|
|
95
|
+
got = sub.public_send(take)
|
|
99
96
|
got.each { |e| yield e }
|
|
100
97
|
break if sub.closed? || session.closed?
|
|
101
98
|
break if deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
@@ -115,11 +112,65 @@ module Asterism
|
|
|
115
112
|
@runner.add { @session.poll ? 0 : false }
|
|
116
113
|
end
|
|
117
114
|
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
# opts: those of Session#put (attachment:, encoding:, priority:,
|
|
116
|
+
# congestion_control:, express:, reliability:, timestamp:,
|
|
117
|
+
# allowed_destination:). A payload that is not a String is sent as
|
|
118
|
+
# to_s.
|
|
119
|
+
def put(key, payload, **opts)
|
|
120
|
+
@session.put(key, Zenoh.bytes(payload), **opts)
|
|
121
|
+
nil
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Deletes key (subscribers get a Sample of kind :delete). opts: those
|
|
125
|
+
# of Session#delete.
|
|
126
|
+
def delete(key, **opts)
|
|
127
|
+
@session.delete(key, **opts)
|
|
120
128
|
nil
|
|
121
129
|
end
|
|
122
130
|
|
|
131
|
+
# A declared publisher (Connection::Publisher): put / delete,
|
|
132
|
+
# matching?, on_matching { |listening| }. opts: those of
|
|
133
|
+
# Session#publisher (encoding:, priority:, ...).
|
|
134
|
+
def publisher(key, **opts)
|
|
135
|
+
Publisher.new(self, @session.publisher(key, **opts), key)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# An advanced publisher (cache:, sample_miss_detection:,
|
|
139
|
+
# publisher_detection: and the publisher options): late subscribers
|
|
140
|
+
# with history get what it kept. Same object as publisher.
|
|
141
|
+
def advanced_publisher(key, **opts)
|
|
142
|
+
Publisher.new(self, @session.advanced_publisher(key, **opts), key)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# An advanced subscriber (history:, recovery:, subscriber_detection:,
|
|
146
|
+
# query_timeout_ms:): like subscribe, plus on_publisher and on_miss.
|
|
147
|
+
def advanced_subscriber(key, depth: 16, **opts, &blk)
|
|
148
|
+
sub = AdvancedSubscription.new(self, @session.advanced_subscriber(key, depth, **opts), key)
|
|
149
|
+
sub.handle(&blk) if blk
|
|
150
|
+
sub
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# A declared get (Connection::Querier): get { |reply| }, matching?,
|
|
154
|
+
# on_matching. timeout in seconds; the rest as Session#querier.
|
|
155
|
+
def querier(key, timeout: 2.0, **opts)
|
|
156
|
+
Querier.new(self, @session.querier(key, timeout_ms: Zenoh.ms(timeout, 2000), **opts), key)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# on_transport(history: false) { |event| }: a TransportEvent (kind
|
|
160
|
+
# :added / :removed, zid, whatami) on the receiving thread each time a
|
|
161
|
+
# peer or router connects or goes. Returns an Events (close it).
|
|
162
|
+
def on_transport(history: false, depth: 16, &blk)
|
|
163
|
+
raise ArgumentError, "a block is needed" unless blk
|
|
164
|
+
Events.new(self, @session.transport_events(depth, history: history), "on_transport", blk)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# on_link(history: false) { |event| }: a LinkEvent each time a link
|
|
168
|
+
# opens or closes.
|
|
169
|
+
def on_link(history: false, depth: 16, &blk)
|
|
170
|
+
raise ArgumentError, "a block is needed" unless blk
|
|
171
|
+
Events.new(self, @session.link_events(depth, history: history), "on_link", blk)
|
|
172
|
+
end
|
|
173
|
+
|
|
123
174
|
# With a block: every sample is given to it (as a Sample) on the
|
|
124
175
|
# receiving thread; returns the Subscription (close it to stop).
|
|
125
176
|
# Without: a Subscription to take from (each, each_pending).
|
|
@@ -141,11 +192,17 @@ module Asterism
|
|
|
141
192
|
# The replies to a get: yields each Reply until every answer came or
|
|
142
193
|
# timeout (seconds) ran out, and returns their number. Without a
|
|
143
194
|
# block, an Enumerator (each iteration sends the get again).
|
|
195
|
+
# errors: true also yields the error replies (Reply#error?); opts:
|
|
196
|
+
# encoding:, priority:, congestion_control:, express:,
|
|
197
|
+
# accept_replies: (Session#get).
|
|
144
198
|
def get(key, timeout: 2.0, params: nil, payload: nil, attachment: nil, target: :all,
|
|
145
|
-
consolidation: :none, &blk)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
199
|
+
consolidation: :none, errors: false, **opts, &blk)
|
|
200
|
+
start = lambda do
|
|
201
|
+
@session.get(key, Zenoh.ms(timeout, 2000), params, payload,
|
|
202
|
+
attachment: attachment, target: target, consolidation: consolidation, **opts)
|
|
203
|
+
end
|
|
204
|
+
return enum_for(:get_each, start, errors) unless blk
|
|
205
|
+
get_each(start, errors, &blk)
|
|
149
206
|
end
|
|
150
207
|
|
|
151
208
|
# A liveliness token (close it to withdraw).
|
|
@@ -221,6 +278,20 @@ module Asterism
|
|
|
221
278
|
@session.peers
|
|
222
279
|
end
|
|
223
280
|
|
|
281
|
+
# The Zenoh IDs of the peers / routers connected now.
|
|
282
|
+
def peer_zids = @session.peer_zids
|
|
283
|
+
def router_zids = @session.router_zids
|
|
284
|
+
|
|
285
|
+
# What is connected now: Arrays of Transport / Link.
|
|
286
|
+
def transports = @session.transports
|
|
287
|
+
def links = @session.links
|
|
288
|
+
|
|
289
|
+
# A new Timestamp from the session's clock (put(timestamp:)).
|
|
290
|
+
def new_timestamp = @session.new_timestamp
|
|
291
|
+
|
|
292
|
+
# A KeyExpr declared on the session (use it as the key afterwards).
|
|
293
|
+
def declare_keyexpr(key) = @session.declare_keyexpr(key)
|
|
294
|
+
|
|
224
295
|
# Stops receiving and closes the session (with everything declared on
|
|
225
296
|
# it). Idempotent.
|
|
226
297
|
def close
|
|
@@ -235,17 +306,21 @@ module Asterism
|
|
|
235
306
|
|
|
236
307
|
private
|
|
237
308
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
309
|
+
# Sends the get (start) and yields each Reply; error replies only
|
|
310
|
+
# with errors. The done? before taking the replies: once the get is
|
|
311
|
+
# done, every reply it had is already in the queue.
|
|
312
|
+
def get_each(start, errors)
|
|
313
|
+
g = start.call
|
|
241
314
|
n = 0
|
|
242
315
|
loop do
|
|
243
|
-
|
|
316
|
+
done = g.done?
|
|
317
|
+
got = g.each_result
|
|
244
318
|
got.each do |r|
|
|
319
|
+
next if r.error? && !errors
|
|
245
320
|
n += 1
|
|
246
|
-
yield
|
|
321
|
+
yield r
|
|
247
322
|
end
|
|
248
|
-
break if
|
|
323
|
+
break if done && g.pending == 0
|
|
249
324
|
sleep(ENUM_STEP) if got.empty?
|
|
250
325
|
end
|
|
251
326
|
n
|
|
@@ -272,8 +347,8 @@ module Asterism
|
|
|
272
347
|
where = "subscribe #{@key}"
|
|
273
348
|
r = @conn.runner
|
|
274
349
|
@task = r.add do
|
|
275
|
-
got = @subscriber.
|
|
276
|
-
got.each { |
|
|
350
|
+
got = @subscriber.each_sample
|
|
351
|
+
got.each { |s| r.guard(where) { blk.call(s) } }
|
|
277
352
|
got.size
|
|
278
353
|
end
|
|
279
354
|
self
|
|
@@ -285,13 +360,13 @@ module Asterism
|
|
|
285
360
|
def each(timeout: nil, &blk)
|
|
286
361
|
return enum_for(:each, timeout: timeout) unless blk
|
|
287
362
|
raise ::Asterism::Error, "this subscription has a block; its samples go there" if @task
|
|
288
|
-
Zenoh.drain(@subscriber, @conn.session, timeout) { |
|
|
363
|
+
Zenoh.drain(@subscriber, @conn.session, timeout, take: :each_sample) { |s| blk.call(s) }
|
|
289
364
|
self
|
|
290
365
|
end
|
|
291
366
|
|
|
292
367
|
# The samples there now as Samples (an Array), or yields them.
|
|
293
368
|
def each_pending
|
|
294
|
-
got = @subscriber.
|
|
369
|
+
got = @subscriber.each_sample
|
|
295
370
|
return got unless block_given?
|
|
296
371
|
got.each { |s| yield s }
|
|
297
372
|
got.size
|
|
@@ -417,6 +492,157 @@ module Asterism
|
|
|
417
492
|
nil
|
|
418
493
|
end
|
|
419
494
|
end
|
|
495
|
+
|
|
496
|
+
# A publisher (or advanced publisher) of a Connection.
|
|
497
|
+
class Publisher
|
|
498
|
+
attr_reader :key, :publisher
|
|
499
|
+
|
|
500
|
+
def initialize(conn, publisher, key)
|
|
501
|
+
@conn = conn
|
|
502
|
+
@publisher = publisher
|
|
503
|
+
@key = key.to_s
|
|
504
|
+
@tasks = []
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
# opts: attachment:, encoding:, timestamp:. A payload that is not a
|
|
508
|
+
# String is sent as to_s.
|
|
509
|
+
def put(payload, **opts)
|
|
510
|
+
@publisher.put(Zenoh.bytes(payload), **opts)
|
|
511
|
+
nil
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
def delete(**opts)
|
|
515
|
+
@publisher.delete(**opts)
|
|
516
|
+
nil
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
# True when a subscriber matches the key now.
|
|
520
|
+
def matching? = @publisher.matching?
|
|
521
|
+
|
|
522
|
+
# on_matching { |listening| }: true when the first subscriber
|
|
523
|
+
# appears, false when the last one goes, on the receiving thread.
|
|
524
|
+
def on_matching(depth: 16, &blk)
|
|
525
|
+
raise ArgumentError, "a block is needed" unless blk
|
|
526
|
+
@tasks << Events.new(@conn, @publisher.matching_listener(depth), "on_matching #{@key}", blk)
|
|
527
|
+
self
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
def closed? = @publisher.closed?
|
|
531
|
+
|
|
532
|
+
def close
|
|
533
|
+
@tasks.each(&:close)
|
|
534
|
+
@tasks.clear
|
|
535
|
+
@publisher.close
|
|
536
|
+
nil
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
def inspect
|
|
540
|
+
"#<Asterism::Zenoh::Connection::Publisher #{@key}>"
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
# A querier of a Connection: the same get, sent again and again.
|
|
545
|
+
class Querier
|
|
546
|
+
attr_reader :key, :querier
|
|
547
|
+
|
|
548
|
+
def initialize(conn, querier, key)
|
|
549
|
+
@conn = conn
|
|
550
|
+
@querier = querier
|
|
551
|
+
@key = key.to_s
|
|
552
|
+
@tasks = []
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
# Yields each Reply until every answer came or the querier's time
|
|
556
|
+
# ran out; returns their number. errors: true also yields the error
|
|
557
|
+
# replies. Without a block, an Enumerator.
|
|
558
|
+
def get(params: nil, payload: nil, attachment: nil, encoding: nil, errors: false, &blk)
|
|
559
|
+
start = -> { @querier.get(params, payload, attachment: attachment, encoding: encoding) }
|
|
560
|
+
return @conn.send(:enum_for, :get_each, start, errors) unless blk
|
|
561
|
+
@conn.send(:get_each, start, errors, &blk)
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
def matching? = @querier.matching?
|
|
565
|
+
|
|
566
|
+
# on_matching { |answered| }: true when a queryable matches, false
|
|
567
|
+
# when none does any more.
|
|
568
|
+
def on_matching(depth: 16, &blk)
|
|
569
|
+
raise ArgumentError, "a block is needed" unless blk
|
|
570
|
+
@tasks << Events.new(@conn, @querier.matching_listener(depth), "on_matching #{@key}", blk)
|
|
571
|
+
self
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
def closed? = @querier.closed?
|
|
575
|
+
|
|
576
|
+
def close
|
|
577
|
+
@tasks.each(&:close)
|
|
578
|
+
@tasks.clear
|
|
579
|
+
@querier.close
|
|
580
|
+
nil
|
|
581
|
+
end
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
# A subscription with history and recovery (advanced_subscriber).
|
|
585
|
+
class AdvancedSubscription < Subscription
|
|
586
|
+
def initialize(conn, subscriber, key)
|
|
587
|
+
super
|
|
588
|
+
@extra = []
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
# on_publisher { |key, alive| }: the advanced publishers on the key
|
|
592
|
+
# that declared publisher_detection, as they come and go (the ones
|
|
593
|
+
# there now first).
|
|
594
|
+
def on_publisher(depth: 16, history: true, &blk)
|
|
595
|
+
raise ArgumentError, "a block is needed" unless blk
|
|
596
|
+
w = Watch.new(@conn, @subscriber.detect_publishers(depth, history: history), @key)
|
|
597
|
+
w.handle(&blk)
|
|
598
|
+
@extra << w
|
|
599
|
+
self
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
# on_miss { |miss| }: samples that never came (Miss: source_zid,
|
|
603
|
+
# source_eid, count); needs sample_miss_detection on the publisher.
|
|
604
|
+
def on_miss(depth: 16, &blk)
|
|
605
|
+
raise ArgumentError, "a block is needed" unless blk
|
|
606
|
+
@extra << Events.new(@conn, @subscriber.miss_listener(depth), "on_miss #{@key}", blk)
|
|
607
|
+
self
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
def close
|
|
611
|
+
@extra.each(&:close)
|
|
612
|
+
@extra.clear
|
|
613
|
+
super
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
def inspect
|
|
617
|
+
"#<Asterism::Zenoh::Connection::AdvancedSubscription #{@key}#{@task ? ' (block)' : ''}>"
|
|
618
|
+
end
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
# The values of a listener (MatchingListener or EventListener) given
|
|
622
|
+
# to a block on the receiving thread. close stops it.
|
|
623
|
+
class Events
|
|
624
|
+
attr_reader :listener
|
|
625
|
+
|
|
626
|
+
def initialize(conn, listener, where, blk)
|
|
627
|
+
@conn = conn
|
|
628
|
+
@listener = listener
|
|
629
|
+
r = conn.runner
|
|
630
|
+
@task = r.add do
|
|
631
|
+
got = @listener.each_pending
|
|
632
|
+
got.each { |v| r.guard(where) { blk.call(v) } }
|
|
633
|
+
got.size
|
|
634
|
+
end
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
def closed? = @listener.closed?
|
|
638
|
+
|
|
639
|
+
def close
|
|
640
|
+
@conn.runner.remove(@task) if @task
|
|
641
|
+
@task = nil
|
|
642
|
+
@listener.close
|
|
643
|
+
nil
|
|
644
|
+
end
|
|
645
|
+
end
|
|
420
646
|
end
|
|
421
647
|
end
|
|
422
648
|
end
|
data/lib/asterism/version.rb
CHANGED
data/mrblib/ros.rb
CHANGED
|
@@ -58,7 +58,7 @@ module Asterism
|
|
|
58
58
|
def self.mangle(name)
|
|
59
59
|
s = name.to_s
|
|
60
60
|
return "%" if s.empty? || s == "/"
|
|
61
|
-
out = ""
|
|
61
|
+
out = "".dup
|
|
62
62
|
i = 0
|
|
63
63
|
n = s.bytesize
|
|
64
64
|
while i < n
|
|
@@ -104,7 +104,7 @@ module Asterism
|
|
|
104
104
|
# rmw_zenoh hashes the entity's liveliness key the same way (with XXH3);
|
|
105
105
|
# receivers only use the GID to tell publishers apart.
|
|
106
106
|
def self.gid_for(key)
|
|
107
|
-
out = ""
|
|
107
|
+
out = "".dup
|
|
108
108
|
lane = 0
|
|
109
109
|
while lane < 4
|
|
110
110
|
h = 0x811c9dc5 ^ (lane * 0x9e3779b9 & 0xffffffff)
|
|
@@ -225,7 +225,7 @@ module Asterism
|
|
|
225
225
|
|
|
226
226
|
# Asterism::ROS::<Pkg>::<Name>, or nil.
|
|
227
227
|
def self.type_constant(pkg, name)
|
|
228
|
-
mod_name = ""
|
|
228
|
+
mod_name = "".dup # a fresh String: literals may be frozen (Ruby 4)
|
|
229
229
|
pkg.split("_").each do |w|
|
|
230
230
|
mod_name << w.byteslice(0, 1).upcase << w.byteslice(1, w.bytesize - 1) if w.bytesize > 0
|
|
231
231
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: asterism
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Katsuhiko Kageyama
|
|
@@ -16,14 +16,14 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: 0.
|
|
19
|
+
version: 0.3.0
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: 0.
|
|
26
|
+
version: 0.3.0
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: msgpack
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|