asterism 0.4.1 → 0.5.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 +35 -0
- data/README.md +88 -6
- data/data/icon.png +0 -0
- data/lib/asterism/cruby/objects.rb +13 -1
- data/lib/asterism/cruby.rb +38 -0
- data/lib/asterism/version.rb +4 -3
- data/lib/asterism.rb +1 -1
- data/mrblib/asterism.rb +5 -0
- data/mrblib/profile.rb +362 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b55c825f8859d86ba09baad0585c0f416a168023b8032c3f871d63bebef0538c
|
|
4
|
+
data.tar.gz: 4d6fd26b326e62348e4fba8c6881c48b5b7145dd0b0c701eb7ba1dcc1bf5b261
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0acc8e1c4e4f028124d247a54e35ebda3451449dcfc46da503d345c857502200b1b68b4f15a4d59fbb7a603d9331f6996aa9072b1426e039fea87b43efbddd5e
|
|
7
|
+
data.tar.gz: 03b912800f24b881a7e01eb6479c2d8c3c2c32a3346538ea60ae5f65c431f3035e097f5fc90fdcaec7734efbf95496544c4b0dd174b12e6be20cabfec2db3635
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,41 @@ 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.5.0
|
|
10
|
+
|
|
11
|
+
Node profiles; every 0.4.x call works as before. Needs asterism-zenoh /
|
|
12
|
+
picoruby-asterism-zenoh 0.4.0 (unchanged).
|
|
13
|
+
|
|
14
|
+
Added
|
|
15
|
+
|
|
16
|
+
- Node profiles (README "Profiles"): `Asterism.profile(kind:, name:, host:,
|
|
17
|
+
icon:)` sets this node's profile (the fields given; the rest stay) and
|
|
18
|
+
returns it, `Asterism.profile` returns it, `Asterism.profile_of(node,
|
|
19
|
+
icon: true, timeout: 2.0)` asks another node's (nil when it does not
|
|
20
|
+
answer). The CRuby API: `net.profile`, `net.profile_of`. Answered on
|
|
21
|
+
`asterism/<node>/@profile` (a queryable of every connected application,
|
|
22
|
+
MessagePack Hash: `kind`, `host`, `name`, `icon`, `icon_hash`; the
|
|
23
|
+
parameter `icon=0` leaves the icon out), and put on the same key without
|
|
24
|
+
the icon at connect and on each change.
|
|
25
|
+
- Defaults: `kind` is `cruby` on CRuby and `picoruby` on mruby / PicoRuby;
|
|
26
|
+
the icon is the file at `Asterism.default_icon_path` when it is there:
|
|
27
|
+
the gem's `data/icon.png` on CRuby (new; a ruby, 32x32), and
|
|
28
|
+
`/usr/share/asterism/icon.png` on the boards.
|
|
29
|
+
- `Asterism::Profile`: `png_info` (the icon checks: PNG signature, chunks
|
|
30
|
+
from IHDR to IEND, 8 KB, 256x256; and `icon_hash`, the Adler-32 of the
|
|
31
|
+
chunks' CRCs), `check` (cleans a received profile: an icon that does not
|
|
32
|
+
pass is dropped), the limits as constants.
|
|
33
|
+
- `rake test:mruby` and `host_test/`: the shared layer on mruby (built with
|
|
34
|
+
picoruby-asterism-zenoh, MRB_UTF8_STRING as on the boards), profiles
|
|
35
|
+
between two mruby processes. CI runs it.
|
|
36
|
+
|
|
37
|
+
Changed
|
|
38
|
+
|
|
39
|
+
- The "Ruby profile" (0.4.0) is now called the Ruby feature table, so that
|
|
40
|
+
it is not taken for a node's profile: `docs/ruby_profile.md` is
|
|
41
|
+
`docs/ruby_features.md`. The probes' directory `profile/` and its code
|
|
42
|
+
keep their names.
|
|
43
|
+
|
|
9
44
|
## 0.4.1
|
|
10
45
|
|
|
11
46
|
Additions and a fix; every 0.4.0 call works as before. Needs
|
data/README.md
CHANGED
|
@@ -10,10 +10,12 @@ repository holds the pure Ruby layers, written once for every Ruby:
|
|
|
10
10
|
| `mrblib/` | The layers: remote objects (`Asterism`), `Asterism::CDR`, `Asterism::ROS`. The source of truth for both forms below |
|
|
11
11
|
| `mrbgem.rake` | The mrbgem `picoruby-asterism` (mruby / PicoRuby): compiles `mrblib/` |
|
|
12
12
|
| `asterism.gemspec`, `lib/` | The CRuby gem `asterism`: `lib/asterism.rb` loads `mrblib/` as it is, `lib/asterism/cruby.rb` adds what CRuby needs, `lib/asterism/cruby/` the CRuby API (CRuby only) |
|
|
13
|
+
| `data/icon.png` | CRuby's default profile icon (32x32 PNG; [Profiles](#profiles)) |
|
|
13
14
|
| `data/msgs/` | ROS 2 message types generated from ROS 2 Jazzy's definitions (Apache-2.0; tf2_msgs BSD-3-Clause; see NOTICE) |
|
|
14
15
|
| `tools/` | The type generator `asterism_msggen.rb`, the Jazzy definitions it reads, `ros2_types.rb` (refreshes them from a ROS 2 image) |
|
|
15
|
-
| `test/` | `test/msgs` (types, CDR; CRuby only), `test_asterism.rb` (objects and ROS between CRuby sessions), `test_api_*.rb` (the CRuby API) |
|
|
16
|
-
| `
|
|
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
|
+
| `host_test/` | `rake test:mruby`: the shared layer on mruby (profiles between two mruby processes) |
|
|
18
|
+
| `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) |
|
|
17
19
|
|
|
18
20
|
The Zenoh binding underneath is a separate gem with the same Ruby API on
|
|
19
21
|
each Ruby:
|
|
@@ -35,7 +37,9 @@ Both gems are on rubygems.org (`gem install asterism` installs
|
|
|
35
37
|
asterism-zenoh too). What changed in each version is in
|
|
36
38
|
[CHANGELOG.md](CHANGELOG.md); 0.4.0 is the first step toward 1.0
|
|
37
39
|
([docs/api_review.md](docs/api_review.md)) and only adds and deprecates
|
|
38
|
-
(see [Deprecations and 1.0](#deprecations-and-10)).
|
|
40
|
+
(see [Deprecations and 1.0](#deprecations-and-10)); 0.5.0 adds node
|
|
41
|
+
profiles ([Profiles](#profiles): what a node runs on, where, a name and an
|
|
42
|
+
icon).
|
|
39
43
|
|
|
40
44
|
## Which API?
|
|
41
45
|
|
|
@@ -83,7 +87,7 @@ build but not yet tested. To install from the repositories instead:
|
|
|
83
87
|
|
|
84
88
|
```
|
|
85
89
|
(cd ../asterism-zenoh && rake gem) && rake gem
|
|
86
|
-
gem install --local ../asterism-zenoh/pkg/asterism-zenoh-0.4.0.gem pkg/asterism-0.
|
|
90
|
+
gem install --local ../asterism-zenoh/pkg/asterism-zenoh-0.4.0.gem pkg/asterism-0.5.0.gem
|
|
87
91
|
```
|
|
88
92
|
|
|
89
93
|
From the working trees, without installing:
|
|
@@ -111,8 +115,17 @@ rake test:msgs # generator, type hashes, CDR, bundled types (CRuby only;
|
|
|
111
115
|
rake test:objects # objects and Asterism::ROS between CRuby sessions over a local peer link
|
|
112
116
|
rake test:api # the CRuby API (blocks, receiving threads, Enumerators)
|
|
113
117
|
ASTERISM_TEST_ROUTER=tcp/127.0.0.1:7447 rake test:objects test:api # the same through a zenohd router
|
|
118
|
+
rake test:mruby # the shared layer on mruby (builds mruby; not part of `rake`)
|
|
114
119
|
```
|
|
115
120
|
|
|
121
|
+
`test:mruby` builds mruby with picoruby-asterism-zenoh and this gem
|
|
122
|
+
(MRB_UTF8_STRING, as on Family mruby's boards) and runs `host_test/`: two
|
|
123
|
+
mruby processes over a local peer link. It takes the mruby and zenoh-pico
|
|
124
|
+
pinned by a picoruby-asterism-zenoh checkout next to this one (its
|
|
125
|
+
`rake mruby:fetch zenoh:fetch`), or `PICORUBY_ASTERISM_ZENOH_DIR` /
|
|
126
|
+
`MRUBY_DIR`. A small MessagePack for the test is in
|
|
127
|
+
`host_test/msgpack.rb` (the boards bring their own).
|
|
128
|
+
|
|
116
129
|
`test:objects` and `test:api` need asterism-zenoh: built next to this repository
|
|
117
130
|
(`../asterism-zenoh`, its `rake compile`), or `ASTERISM_ZENOH_DIR`, or the
|
|
118
131
|
installed gem. The tests run with deprecated calls raising
|
|
@@ -131,6 +144,17 @@ ruby examples/node_polled.rb --router tcp/192.0.2.2:7447 # the same, polled
|
|
|
131
144
|
# /cruby/add_two_ints served, AddTwoInts calls
|
|
132
145
|
ruby examples/ros2_talker.rb --router tcp/192.0.2.2:7447 [--service NAME]
|
|
133
146
|
|
|
147
|
+
# ROS 2: drive a robot (made for family-mruby's MuJoCo rover,
|
|
148
|
+
# docker-compose.mujoco.yml): 1 m forward and 90 degrees left by /odom,
|
|
149
|
+
# compared with /ground_truth/odom; or the keyboard
|
|
150
|
+
ruby examples/ros2_rover.rb --router tcp/192.0.2.2:7447 [--keys]
|
|
151
|
+
|
|
152
|
+
# MuJoCo from CRuby (Fiddle), the robot as objects mujoco/rover/{drive,state,world};
|
|
153
|
+
# the MuJoCo release is fetched into examples/mujoco/vendor (examples/mujoco/README.md)
|
|
154
|
+
ruby examples/mujoco/fetch.rb
|
|
155
|
+
ruby examples/mujoco/rover.rb --router tcp/192.0.2.2:7447
|
|
156
|
+
ruby examples/mujoco/drive.rb --router tcp/192.0.2.2:7447 [--keys]
|
|
157
|
+
|
|
134
158
|
# plain Zenoh: run two with different --name
|
|
135
159
|
ruby examples/zenoh.rb --router tcp/192.0.2.2:7447 --name pc1
|
|
136
160
|
```
|
|
@@ -227,6 +251,7 @@ end
|
|
|
227
251
|
| `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) |
|
|
228
252
|
| `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 |
|
|
229
253
|
| `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
|
+
| `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 |
|
|
230
255
|
| `net.nodes`, `node_id`, `app`, `connected?`, `lost_reason`, `poll`, `close` | |
|
|
231
256
|
|
|
232
257
|
### Over TLS (a router on the internet)
|
|
@@ -400,6 +425,60 @@ Asterism.nodes # => ["linux", "fmruby-bbbbbb"]
|
|
|
400
425
|
| `Asterism.nodes` | `[String]` | Node IDs alive now, this one first. |
|
|
401
426
|
| `Asterism.connected?` / `node_id` / `app` / `exposed` / `lost_reason` | | |
|
|
402
427
|
| `Asterism.close` | nil | Withdraws every exposed object. Idempotent. |
|
|
428
|
+
| `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
|
+
| `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). |
|
|
430
|
+
|
|
431
|
+
## Profiles
|
|
432
|
+
|
|
433
|
+
A node tells what it is (0.5.0): what Ruby runs it, where, a display name
|
|
434
|
+
and an icon. A console draws it with that; anyone can ask.
|
|
435
|
+
|
|
436
|
+
```ruby
|
|
437
|
+
Asterism.profile(name: "Rover", icon: File.binread("rover.png")) # this node's (any time)
|
|
438
|
+
Asterism.profile
|
|
439
|
+
# => {"kind" => "cruby", "name" => "Rover", "icon" => "\x89PNG...", "icon_hash" => "2a8c0626"}
|
|
440
|
+
Asterism.profile_of("fmruby-aaaaaa")
|
|
441
|
+
# => {"kind" => "family_mruby", "host" => "atoms3r", "icon" => "\x89PNG...", "icon_hash" => "..."}
|
|
442
|
+
Asterism.profile_of("fmruby-aaaaaa", icon: false) # the same without the icon bytes
|
|
443
|
+
Asterism.profile_of("an-older-node") # => nil
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
| Field | Value | Default |
|
|
447
|
+
|---|---|---|
|
|
448
|
+
| `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
|
+
| `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
|
+
| `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` |
|
|
452
|
+
| `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
|
+
|
|
454
|
+
- **Setting.** `Asterism.profile(**fields)` changes the fields given and keeps
|
|
455
|
+
the others; `nil` leaves `name` / `host` out, goes back to the default
|
|
456
|
+
`kind`, and means no icon for `icon:`. It raises `ArgumentError` for a
|
|
457
|
+
field it does not accept (an unknown name, a `kind` that is not a word, an
|
|
458
|
+
icon that is not a PNG, larger than 8 KB or 256x256). It may be called
|
|
459
|
+
before connecting and at any time after; the profile belongs to the
|
|
460
|
+
process (the VM on a board) and survives `close`. A firmware can set its
|
|
461
|
+
defaults with the same call before the application runs (Family mruby
|
|
462
|
+
sets `kind`, `host` and, through `/usr/share/asterism/icon.png`, the icon).
|
|
463
|
+
- **Reading.** `Asterism.profile_of(node)` asks `asterism/<node>/@profile`
|
|
464
|
+
and waits like a call (`timeout:` / `timeout_ms:`); this node's own comes
|
|
465
|
+
without the network. The answer is cleaned (`Asterism::Profile.check`):
|
|
466
|
+
only the fields above, each checked; an icon that is not an acceptable
|
|
467
|
+
PNG is dropped with its hash, an answer without a valid `kind` is `nil`.
|
|
468
|
+
`icon: false` asks without the icon (the parameter `icon=0`): a reader
|
|
469
|
+
that already has the icon of that `icon_hash` asks this way.
|
|
470
|
+
- **Following.** At each connect and at each change while connected, the
|
|
471
|
+
node also puts its profile without the icon on `asterism/<node>/@profile`;
|
|
472
|
+
subscribe to `asterism/*/@profile` to follow the changes (and fetch the
|
|
473
|
+
icon again only when `icon_hash` changed). Older nodes neither answer nor
|
|
474
|
+
put.
|
|
475
|
+
- **Several applications of one node** (a board running two): each answers
|
|
476
|
+
for the node, and `profile_of` takes the first answer. Give them the same
|
|
477
|
+
profile (Family mruby's firmware defaults are the same in each), or set it
|
|
478
|
+
in one and read the notices, where the last change wins.
|
|
479
|
+
- `Asterism::Profile.png_info(bytes)` gives `[width, height, icon_hash]` of
|
|
480
|
+
an acceptable PNG (nil otherwise), for a tool that wants to check an icon
|
|
481
|
+
first.
|
|
403
482
|
|
|
404
483
|
## Errors
|
|
405
484
|
|
|
@@ -441,6 +520,7 @@ is closed (1.0: `Disconnected`, as the object layer).
|
|
|
441
520
|
| Receiving interval of the CRuby API | 2 ms | `interval:` |
|
|
442
521
|
| Calls waiting inside each other | 4 | `MAX_NESTING` |
|
|
443
522
|
| Encoding depth of a value | 16 | `Codec::MAX_DEPTH` |
|
|
523
|
+
| A profile icon | 8 KB, 256x256, PNG only | `Asterism::Profile::ICON_MAX`, `ICON_SIDE_MAX` |
|
|
444
524
|
| `get` consolidation | `:none` (every reply; zenoh's own default is `:auto`, but services and the object layer want every reply) | `consolidation:` |
|
|
445
525
|
|
|
446
526
|
### Queues
|
|
@@ -530,8 +610,10 @@ caller gets `RemoteError` (`Asterism::EncodeError`). Nesting is limited to
|
|
|
530
610
|
| `asterism/<node>/<app>/<object>/meta` | get / queryable | reply: `{"methods" => [[name, arity], ...]}` (arity `-1` when not declared). The object may be `*` in the query: one reply per object |
|
|
531
611
|
| `asterism/<node>` | liveliness | the node is connected |
|
|
532
612
|
| `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 in 0.5.0) |
|
|
533
614
|
|
|
534
|
-
One queryable per application (`asterism/<node>/<app>/**`)
|
|
615
|
+
One queryable per application (`asterism/<node>/<app>/**`), and one for the
|
|
616
|
+
profile (`asterism/<node>/@profile`). A call whose
|
|
535
617
|
node or app is a wildcard reaches every application that exposes that
|
|
536
618
|
object, each answering with its own key; `Asterism` itself only sends calls
|
|
537
619
|
to one object.
|
|
@@ -601,7 +683,7 @@ nested type (`geometry_msgs/msg/Twist`) at start-up took 0.6 KB more.
|
|
|
601
683
|
different on Family mruby's PicoRuby: `^x` of an outer local inside a
|
|
602
684
|
block, `^(expr)`, `Const[...]` and `**rest` next to another key. The full
|
|
603
685
|
list of what the boards' Ruby can and cannot do is in
|
|
604
|
-
[docs/
|
|
686
|
+
[docs/ruby_features.md](docs/ruby_features.md) (the Ruby feature table).
|
|
605
687
|
|
|
606
688
|
## ROS 2 (rmw_zenoh): `Asterism::ROS` and `Asterism::CDR`
|
|
607
689
|
|
data/data/icon.png
ADDED
|
Binary file
|
|
@@ -26,7 +26,8 @@ module Asterism
|
|
|
26
26
|
# The portable API, entered under LOCK. Prepended to the Asterism module's
|
|
27
27
|
# singleton class, so the shared methods themselves are not changed.
|
|
28
28
|
module Locked
|
|
29
|
-
%i[expose unexpose poll nodes [] call_async meta connected? exposed on_join on_leave off_join off_leave
|
|
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
31
|
define_method(m) do |*args, **kw, &blk|
|
|
31
32
|
::Asterism::LOCK.synchronize { super(*args, **kw, &blk) }
|
|
32
33
|
end
|
|
@@ -147,6 +148,17 @@ module Asterism
|
|
|
147
148
|
::Asterism.unexpose(name)
|
|
148
149
|
end
|
|
149
150
|
|
|
151
|
+
# Asterism.profile: this node's profile (with keywords, sets fields).
|
|
152
|
+
def profile(**fields)
|
|
153
|
+
::Asterism.profile(**fields)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Asterism.profile_of: another node's profile, or nil. Waits like a
|
|
157
|
+
# call (for the receiving thread's ticks while it runs).
|
|
158
|
+
def profile_of(node, icon: true, timeout: nil, timeout_ms: nil)
|
|
159
|
+
::Asterism.profile_of(node, icon: icon, timeout: timeout, timeout_ms: timeout_ms)
|
|
160
|
+
end
|
|
161
|
+
|
|
150
162
|
# A proxy for <node>/<app>/<object>; timeout: in seconds (or
|
|
151
163
|
# timeout_ms:).
|
|
152
164
|
def [](path, timeout: nil, timeout_ms: nil)
|
data/lib/asterism/cruby.rb
CHANGED
|
@@ -24,6 +24,44 @@ module Asterism
|
|
|
24
24
|
Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
# The profile's defaults on CRuby (mrblib/profile.rb): kind "cruby" and
|
|
28
|
+
# the icon bundled with the gem (replace it with Asterism.profile(icon:)).
|
|
29
|
+
ICON_FILE = File.expand_path("../../data/icon.png", __dir__)
|
|
30
|
+
|
|
31
|
+
def self.default_profile_kind
|
|
32
|
+
"cruby"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.default_icon_path
|
|
36
|
+
ICON_FILE
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.read_icon_file(path)
|
|
40
|
+
File.binread(path)
|
|
41
|
+
rescue SystemCallError
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
module Profile
|
|
46
|
+
# Icons come out of MessagePack as UTF-8 Strings: hand them out as the
|
|
47
|
+
# bytes they are.
|
|
48
|
+
class << self
|
|
49
|
+
alias_method :check_portable, :check
|
|
50
|
+
|
|
51
|
+
def check(h)
|
|
52
|
+
out = check_portable(h)
|
|
53
|
+
out["icon"] = out["icon"].b if out&.key?("icon")
|
|
54
|
+
out
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
alias_method :icon_portable!, :icon!
|
|
58
|
+
|
|
59
|
+
def icon!(v)
|
|
60
|
+
icon_portable!(v).b.freeze
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
27
65
|
def self.error_reply(klass, message)
|
|
28
66
|
::MessagePack.pack(["error", klass.to_s, message.to_s], compatibility_mode: true)
|
|
29
67
|
end
|
data/lib/asterism/version.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
module Asterism
|
|
2
|
-
# The gem's version (the CRuby gem asterism
|
|
3
|
-
#
|
|
4
|
-
|
|
2
|
+
# The gem's version (the CRuby gem asterism). The Zenoh binding it needs
|
|
3
|
+
# is in asterism.gemspec (asterism-zenoh ~> 0.4.0 for 0.5.0: the bindings
|
|
4
|
+
# did not change; see CHANGELOG.md).
|
|
5
|
+
VERSION = "0.5.0"
|
|
5
6
|
end
|
data/lib/asterism.rb
CHANGED
|
@@ -12,7 +12,7 @@ require "asterism/zenoh"
|
|
|
12
12
|
require_relative "asterism/version"
|
|
13
13
|
|
|
14
14
|
# The same order as an mrbgem's mrblib (alphabetical).
|
|
15
|
-
%w[asterism cdr future proxy ros].each do |f|
|
|
15
|
+
%w[asterism cdr future profile proxy ros].each do |f|
|
|
16
16
|
require_relative "../mrblib/#{f}"
|
|
17
17
|
end
|
|
18
18
|
require_relative "asterism/cruby"
|
data/mrblib/asterism.rb
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
# asterism/<node>/<app>/<object>/meta get: reply {"methods" => [[name, arity], ...]}
|
|
15
15
|
# asterism/<node> liveliness: the node is up
|
|
16
16
|
# asterism/<node>/<app>/<object> liveliness: the object is exposed
|
|
17
|
+
# asterism/<node>/@profile get: reply the node's profile (profile.rb)
|
|
17
18
|
#
|
|
18
19
|
# Everything is polled: nothing runs behind the application's back. A call
|
|
19
20
|
# that waits for its answer keeps polling meanwhile and answers the calls
|
|
@@ -221,6 +222,7 @@ module Asterism
|
|
|
221
222
|
@node_token = s.liveliness("#{ROOT}/#{node}")
|
|
222
223
|
@queryable = s.queryable("#{ROOT}/#{node}/#{app}/**", depth: 32)
|
|
223
224
|
@watch = s.liveliness_watch("#{ROOT}/**", depth: watch_depth)
|
|
225
|
+
@profile_queryable = s.queryable("#{ROOT}/#{node}/#{Profile::KEY}", depth: 4)
|
|
224
226
|
rescue Asterism::Zenoh::Error => e
|
|
225
227
|
s.close
|
|
226
228
|
raise Disconnected, e.message
|
|
@@ -235,6 +237,7 @@ module Asterism
|
|
|
235
237
|
@depth = 0
|
|
236
238
|
@lost = nil
|
|
237
239
|
@known = [] # the other nodes told to on_join so far
|
|
240
|
+
tell_profile # for those who follow the profiles (the reply is the queryable's)
|
|
238
241
|
self
|
|
239
242
|
end
|
|
240
243
|
|
|
@@ -300,6 +303,7 @@ module Asterism
|
|
|
300
303
|
@node_token = nil
|
|
301
304
|
@queryable = nil
|
|
302
305
|
@watch = nil
|
|
306
|
+
@profile_queryable = nil
|
|
303
307
|
end
|
|
304
308
|
|
|
305
309
|
# @api private
|
|
@@ -447,6 +451,7 @@ module Asterism
|
|
|
447
451
|
return false
|
|
448
452
|
end
|
|
449
453
|
answer_calls
|
|
454
|
+
answer_profile
|
|
450
455
|
follow_liveliness
|
|
451
456
|
!@session.nil?
|
|
452
457
|
rescue Asterism::Zenoh::Error => e
|
data/mrblib/profile.rb
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
# A node's profile: what it runs on, where, a display name and an icon,
|
|
2
|
+
# told by the node itself (0.5.0).
|
|
3
|
+
#
|
|
4
|
+
# Asterism.profile(name: "Rover", icon: png) # this node's; returns it
|
|
5
|
+
# Asterism.profile # => {"kind" => "picoruby", "name" => "Rover", ...}
|
|
6
|
+
# Asterism.profile_of("fmruby-aaaaaa") # another node's, or nil
|
|
7
|
+
#
|
|
8
|
+
# Key: asterism/<node>/@profile (a queryable of every application connected
|
|
9
|
+
# as that node). The reply is a MessagePack Hash:
|
|
10
|
+
# "kind" what Ruby runs the node: "family_mruby", "picoruby",
|
|
11
|
+
# "cruby", "console" (others are allowed)
|
|
12
|
+
# "host" where it runs, optional ("atoms3r", "tab5", "sim", ...)
|
|
13
|
+
# "name" a display name, optional
|
|
14
|
+
# "icon" PNG bytes, optional (8 KB and 256x256 at most)
|
|
15
|
+
# "icon_hash" the icon's summary (Profile.png_info): a reader that has
|
|
16
|
+
# that icon already asks again with the parameter icon=0 and
|
|
17
|
+
# gets the rest without it
|
|
18
|
+
# Each connect, and each change while connected, also puts the profile
|
|
19
|
+
# without its icon on the same key, for those who follow it (a console).
|
|
20
|
+
#
|
|
21
|
+
# Room left for later: an application's own profile would be
|
|
22
|
+
# asterism/<node>/<app>/@profile, with the same Hash.
|
|
23
|
+
module Asterism
|
|
24
|
+
module Profile
|
|
25
|
+
# The kinds this version knows. Others pass: a kind is any lower-case
|
|
26
|
+
# word (a-z, 0-9, _; up to KIND_MAX bytes) starting with a letter.
|
|
27
|
+
KINDS = ["family_mruby", "picoruby", "cruby", "console"]
|
|
28
|
+
KEY = "@profile"
|
|
29
|
+
ICON_MAX = 8192 # bytes
|
|
30
|
+
ICON_SIDE_MAX = 256 # pixels, each side
|
|
31
|
+
KIND_MAX = 32
|
|
32
|
+
HOST_MAX = 32
|
|
33
|
+
NAME_MAX = 64
|
|
34
|
+
PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10]
|
|
35
|
+
|
|
36
|
+
# [width, height, icon_hash] of a PNG that a profile may carry, or nil.
|
|
37
|
+
# Checks the signature, the size (ICON_MAX bytes), that the chunks tile
|
|
38
|
+
# the bytes from IHDR to IEND, and the image's sides (ICON_SIDE_MAX).
|
|
39
|
+
#
|
|
40
|
+
# icon_hash is the Adler-32 (8 hex digits) of the chunks' CRC-32 fields,
|
|
41
|
+
# in order: each CRC already sums up its chunk, so the summary costs a
|
|
42
|
+
# few bytes of arithmetic instead of a pass over the whole file (small
|
|
43
|
+
# integers only, the same on every Ruby). On CRuby it equals
|
|
44
|
+
# Zlib.adler32 of those fields joined.
|
|
45
|
+
def self.png_info(b)
|
|
46
|
+
return nil unless b.is_a?(String)
|
|
47
|
+
size = b.bytesize
|
|
48
|
+
return nil if size < 8 + 25 + 12 || size > ICON_MAX
|
|
49
|
+
i = 0
|
|
50
|
+
while i < 8
|
|
51
|
+
return nil unless b.getbyte(i) == PNG_SIGNATURE[i]
|
|
52
|
+
i += 1
|
|
53
|
+
end
|
|
54
|
+
pos = 8
|
|
55
|
+
first = true
|
|
56
|
+
w = 0
|
|
57
|
+
h = 0
|
|
58
|
+
a = 1
|
|
59
|
+
s = 0
|
|
60
|
+
while pos + 12 <= size
|
|
61
|
+
# The length is 4 bytes; anything past 16 bits is larger than ICON_MAX.
|
|
62
|
+
return nil unless b.getbyte(pos) == 0 && b.getbyte(pos + 1) == 0
|
|
63
|
+
len = b.getbyte(pos + 2) * 256 + b.getbyte(pos + 3)
|
|
64
|
+
nxt = pos + 12 + len
|
|
65
|
+
return nil if nxt > size
|
|
66
|
+
if first
|
|
67
|
+
return nil unless type?(b, pos, 73, 72, 68, 82) && len == 13 # IHDR
|
|
68
|
+
return nil unless b.getbyte(pos + 8) == 0 && b.getbyte(pos + 9) == 0
|
|
69
|
+
return nil unless b.getbyte(pos + 12) == 0 && b.getbyte(pos + 13) == 0
|
|
70
|
+
w = b.getbyte(pos + 10) * 256 + b.getbyte(pos + 11)
|
|
71
|
+
h = b.getbyte(pos + 14) * 256 + b.getbyte(pos + 15)
|
|
72
|
+
return nil if w < 1 || h < 1 || w > ICON_SIDE_MAX || h > ICON_SIDE_MAX
|
|
73
|
+
first = false
|
|
74
|
+
end
|
|
75
|
+
k = nxt - 4
|
|
76
|
+
while k < nxt
|
|
77
|
+
a = (a + b.getbyte(k)) % 65521
|
|
78
|
+
s = (s + a) % 65521
|
|
79
|
+
k += 1
|
|
80
|
+
end
|
|
81
|
+
if type?(b, pos, 73, 69, 78, 68) # IEND
|
|
82
|
+
return nil unless nxt == size
|
|
83
|
+
return [w, h, hex4(s) + hex4(a)]
|
|
84
|
+
end
|
|
85
|
+
pos = nxt
|
|
86
|
+
end
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @api private
|
|
91
|
+
def self.type?(b, pos, c0, c1, c2, c3)
|
|
92
|
+
b.getbyte(pos + 4) == c0 && b.getbyte(pos + 5) == c1 && b.getbyte(pos + 6) == c2 && b.getbyte(pos + 7) == c3
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @api private
|
|
96
|
+
def self.hex4(n)
|
|
97
|
+
t = n.to_s(16)
|
|
98
|
+
t = "0" + t while t.length < 4
|
|
99
|
+
t
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# A received profile, cleaned: the known fields that pass their checks
|
|
103
|
+
# (a field that does not is left out; an icon that is not a valid PNG
|
|
104
|
+
# goes with its hash). nil when it is not a profile (no valid kind).
|
|
105
|
+
def self.check(h)
|
|
106
|
+
return nil unless h.is_a?(Hash)
|
|
107
|
+
kind = h["kind"]
|
|
108
|
+
return nil unless kind.is_a?(String) && kind?(kind)
|
|
109
|
+
out = { "kind" => kind }
|
|
110
|
+
host = h["host"]
|
|
111
|
+
out["host"] = host if host.is_a?(String) && !host.empty? && host.bytesize <= HOST_MAX
|
|
112
|
+
name = h["name"]
|
|
113
|
+
out["name"] = name if name.is_a?(String) && !name.empty? && name.bytesize <= NAME_MAX
|
|
114
|
+
icon = h["icon"]
|
|
115
|
+
if icon.is_a?(String)
|
|
116
|
+
info = png_info(icon)
|
|
117
|
+
if info
|
|
118
|
+
out["icon"] = icon
|
|
119
|
+
out["icon_hash"] = info[2]
|
|
120
|
+
end
|
|
121
|
+
elsif hash_text?(h["icon_hash"])
|
|
122
|
+
out["icon_hash"] = h["icon_hash"]
|
|
123
|
+
end
|
|
124
|
+
out
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @api private
|
|
128
|
+
def self.kind?(s)
|
|
129
|
+
n = s.bytesize
|
|
130
|
+
return false if n < 1 || n > KIND_MAX
|
|
131
|
+
i = 0
|
|
132
|
+
while i < n
|
|
133
|
+
c = s.getbyte(i)
|
|
134
|
+
lower = c >= 97 && c <= 122
|
|
135
|
+
return false unless lower || (i > 0 && ((c >= 48 && c <= 57) || c == 95))
|
|
136
|
+
i += 1
|
|
137
|
+
end
|
|
138
|
+
true
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @api private
|
|
142
|
+
def self.hash_text?(s)
|
|
143
|
+
return false unless s.is_a?(String) && s.bytesize == 8
|
|
144
|
+
i = 0
|
|
145
|
+
while i < 8
|
|
146
|
+
c = s.getbyte(i)
|
|
147
|
+
return false unless (c >= 48 && c <= 57) || (c >= 97 && c <= 102)
|
|
148
|
+
i += 1
|
|
149
|
+
end
|
|
150
|
+
true
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# The checks of Asterism.profile (ArgumentError, with the reason).
|
|
154
|
+
# @api private
|
|
155
|
+
def self.kind!(v)
|
|
156
|
+
s = v.is_a?(Symbol) ? v.to_s : v
|
|
157
|
+
unless s.is_a?(String) && kind?(s)
|
|
158
|
+
raise ArgumentError, "profile kind: #{v.inspect} is not a lower-case word (a-z, 0-9, _; " \
|
|
159
|
+
"#{KIND_MAX} bytes at most), e.g. :cruby"
|
|
160
|
+
end
|
|
161
|
+
s
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# @api private
|
|
165
|
+
def self.text!(what, v, max)
|
|
166
|
+
s = v.is_a?(Symbol) ? v.to_s : v
|
|
167
|
+
raise ArgumentError, "profile #{what}: expected a String, got #{v.class}" unless s.is_a?(String)
|
|
168
|
+
raise ArgumentError, "profile #{what}: empty (pass nil to leave it out)" if s.empty?
|
|
169
|
+
raise ArgumentError, "profile #{what}: #{s.bytesize} bytes (#{max} at most)" if s.bytesize > max
|
|
170
|
+
s
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# @api private
|
|
174
|
+
def self.icon!(v)
|
|
175
|
+
raise ArgumentError, "profile icon: expected the bytes of a PNG (a String), got #{v.class}" unless v.is_a?(String)
|
|
176
|
+
if v.bytesize > ICON_MAX
|
|
177
|
+
raise ArgumentError, "profile icon: #{v.bytesize} bytes (#{ICON_MAX} at most)"
|
|
178
|
+
end
|
|
179
|
+
unless png_info(v)
|
|
180
|
+
raise ArgumentError, "profile icon: not a PNG, or not one this accepts (up to " \
|
|
181
|
+
"#{ICON_SIDE_MAX}x#{ICON_SIDE_MAX}, chunks from IHDR to IEND)"
|
|
182
|
+
end
|
|
183
|
+
v
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Whether a query's parameters ask for the icon (icon=0 says no).
|
|
187
|
+
# @api private
|
|
188
|
+
def self.wants_icon?(params)
|
|
189
|
+
return true if params.nil? || params.empty?
|
|
190
|
+
params.split(";").each do |pair|
|
|
191
|
+
kv = pair.split("=")
|
|
192
|
+
next unless kv[0] == "icon"
|
|
193
|
+
v = kv[1].to_s
|
|
194
|
+
return false if v == "0" || v == "no" || v == "false"
|
|
195
|
+
end
|
|
196
|
+
true
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# This node's profile. With keywords, sets those fields and returns the
|
|
201
|
+
# profile; without, returns it. The fields: kind: (a Symbol or String;
|
|
202
|
+
# nil goes back to the default), name:, host: (Strings; nil leaves them
|
|
203
|
+
# out), icon: (the bytes of a PNG; nil for no icon). The fields not given
|
|
204
|
+
# stay as they are. Raises ArgumentError for a field it does not accept
|
|
205
|
+
# (see Asterism::Profile).
|
|
206
|
+
#
|
|
207
|
+
# The defaults: kind "picoruby" on mruby / PicoRuby, "cruby" on CRuby; no
|
|
208
|
+
# host, no name; the icon is the file at Asterism.default_icon_path when
|
|
209
|
+
# there is one (the boards: /usr/share/asterism/icon.png; CRuby: the gem's
|
|
210
|
+
# data/icon.png). A board's firmware may set its own defaults with this
|
|
211
|
+
# same call before the application runs (Family mruby does).
|
|
212
|
+
#
|
|
213
|
+
# Kept across connect and close (it belongs to the process, or the VM on
|
|
214
|
+
# a board). While connected, a change is also put on the profile's key.
|
|
215
|
+
def self.profile(**fields)
|
|
216
|
+
return profile_hash(true) if fields.empty?
|
|
217
|
+
changes = {}
|
|
218
|
+
fields.each do |k, v|
|
|
219
|
+
key = k.to_s
|
|
220
|
+
if key == "kind"
|
|
221
|
+
changes[key] = v.nil? ? nil : Profile.kind!(v)
|
|
222
|
+
elsif key == "name"
|
|
223
|
+
changes[key] = v.nil? ? nil : Profile.text!("name", v, Profile::NAME_MAX)
|
|
224
|
+
elsif key == "host"
|
|
225
|
+
changes[key] = v.nil? ? nil : Profile.text!("host", v, Profile::HOST_MAX)
|
|
226
|
+
elsif key == "icon"
|
|
227
|
+
changes[key] = v.nil? ? nil : Profile.icon!(v)
|
|
228
|
+
else
|
|
229
|
+
raise ArgumentError, "unknown profile field #{key} (kind, name, host, icon)"
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
set = (@profile_set ||= {})
|
|
233
|
+
changes.each do |k, v|
|
|
234
|
+
if v.nil? && k != "icon"
|
|
235
|
+
set.delete(k)
|
|
236
|
+
else
|
|
237
|
+
set[k] = v # icon: nil stays, so the default file is not used
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
@profile_icon = nil if changes.key?("icon")
|
|
241
|
+
tell_profile
|
|
242
|
+
profile_hash(true)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Another node's profile (Asterism::Profile.check has cleaned it), or nil
|
|
246
|
+
# when it does not answer in time (an older version of Asterism, a ROS 2
|
|
247
|
+
# node, a node that is gone). icon: false asks for it without the icon
|
|
248
|
+
# bytes (icon_hash still says which icon it is). timeout: in seconds (or
|
|
249
|
+
# timeout_ms:), 2.0 by default. Waits like a call (polling, answering
|
|
250
|
+
# calls meanwhile); raises Disconnected when not connected. This node's
|
|
251
|
+
# own profile comes without the network.
|
|
252
|
+
def self.profile_of(node, icon: true, timeout: nil, timeout_ms: nil)
|
|
253
|
+
f = profile_async(node, icon, timeout, timeout_ms)
|
|
254
|
+
begin
|
|
255
|
+
raw = f.raw_value
|
|
256
|
+
rescue Disconnected
|
|
257
|
+
raise
|
|
258
|
+
rescue Error
|
|
259
|
+
return nil # no answer in time, or not MessagePack
|
|
260
|
+
end
|
|
261
|
+
Profile.check(raw)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# @api private
|
|
265
|
+
def self.profile_async(node, icon, timeout, timeout_ms)
|
|
266
|
+
node = check_name("node", node)
|
|
267
|
+
ms = time_ms("Asterism.profile_of", timeout, timeout_ms, nil, DEFAULT_TIMEOUT_MS)
|
|
268
|
+
path = "#{node}/#{Profile::KEY}"
|
|
269
|
+
return Future.answered(path, "profile", Codec.pack(profile_hash(icon))) if @session && node == @node
|
|
270
|
+
begin
|
|
271
|
+
g = session!.get("#{ROOT}/#{path}", timeout_ms: ms, params: icon ? nil : "icon=0")
|
|
272
|
+
rescue Asterism::Zenoh::Error => e
|
|
273
|
+
lost(e.message)
|
|
274
|
+
raise Disconnected, e.message
|
|
275
|
+
end
|
|
276
|
+
Future.new(path, "profile", g, now_ms + ms, ms)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# The kind when the application did not set one.
|
|
280
|
+
# @api private
|
|
281
|
+
def self.default_profile_kind
|
|
282
|
+
"picoruby"
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Where the default icon is read from (when the application gave none);
|
|
286
|
+
# nil for none. CRuby: the gem's data/icon.png.
|
|
287
|
+
def self.default_icon_path
|
|
288
|
+
"/usr/share/asterism/icon.png"
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# The bytes of a file, nil when it cannot be read.
|
|
292
|
+
# @api private
|
|
293
|
+
def self.read_icon_file(path)
|
|
294
|
+
return nil unless Object.const_defined?(:File) && File.exist?(path)
|
|
295
|
+
File.open(path, "r") { |f| f.read }
|
|
296
|
+
rescue
|
|
297
|
+
nil
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# The profile as sent: String keys, the icon only when with_icon.
|
|
301
|
+
# @api private
|
|
302
|
+
def self.profile_hash(with_icon)
|
|
303
|
+
set = @profile_set || {}
|
|
304
|
+
h = { "kind" => set["kind"] || default_profile_kind }
|
|
305
|
+
h["host"] = set["host"] if set["host"]
|
|
306
|
+
h["name"] = set["name"] if set["name"]
|
|
307
|
+
icon = profile_icon
|
|
308
|
+
if icon
|
|
309
|
+
h["icon"] = icon[0] if with_icon
|
|
310
|
+
h["icon_hash"] = icon[1]
|
|
311
|
+
end
|
|
312
|
+
h
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# [bytes, icon_hash] of this node's icon, or nil. The default file is
|
|
316
|
+
# read once, when the profile is first needed.
|
|
317
|
+
# @api private
|
|
318
|
+
def self.profile_icon
|
|
319
|
+
if @profile_icon.nil?
|
|
320
|
+
set = @profile_set || {}
|
|
321
|
+
bytes = set.key?("icon") ? set["icon"] : nil
|
|
322
|
+
unless set.key?("icon")
|
|
323
|
+
path = default_icon_path
|
|
324
|
+
bytes = path ? read_icon_file(path) : nil
|
|
325
|
+
end
|
|
326
|
+
info = bytes ? Profile.png_info(bytes) : nil
|
|
327
|
+
@profile_icon = info ? [bytes, info[2]] : false
|
|
328
|
+
end
|
|
329
|
+
@profile_icon ? @profile_icon : nil
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Puts the profile without its icon on its key (while connected).
|
|
333
|
+
# @api private
|
|
334
|
+
def self.tell_profile
|
|
335
|
+
s = @session
|
|
336
|
+
return unless s
|
|
337
|
+
s.put("#{ROOT}/#{@node}/#{Profile::KEY}", Codec.pack(profile_hash(false)))
|
|
338
|
+
rescue Asterism::Zenoh::Error
|
|
339
|
+
nil # the next poll notices a lost connection
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# @api private
|
|
343
|
+
def self.answer_profile
|
|
344
|
+
qa = @profile_queryable
|
|
345
|
+
return unless qa
|
|
346
|
+
queries = qa.each_pending
|
|
347
|
+
i = 0
|
|
348
|
+
while i < queries.size
|
|
349
|
+
q = queries[i]
|
|
350
|
+
i += 1
|
|
351
|
+
begin
|
|
352
|
+
q.reply("#{ROOT}/#{@node}/#{Profile::KEY}", Codec.pack(profile_hash(Profile.wants_icon?(q.params))))
|
|
353
|
+
ensure
|
|
354
|
+
begin
|
|
355
|
+
q.finish
|
|
356
|
+
rescue Asterism::Zenoh::Error
|
|
357
|
+
# the session went away meanwhile
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: asterism
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Katsuhiko Kageyama
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: asterism-zenoh
|
|
@@ -50,6 +50,7 @@ files:
|
|
|
50
50
|
- LICENSE
|
|
51
51
|
- NOTICE
|
|
52
52
|
- README.md
|
|
53
|
+
- data/icon.png
|
|
53
54
|
- data/msgs/LICENSE-Apache-2.0.txt
|
|
54
55
|
- data/msgs/LICENSE-BSD-3-Clause-tf2_msgs.txt
|
|
55
56
|
- data/msgs/NOTICE
|
|
@@ -147,6 +148,7 @@ files:
|
|
|
147
148
|
- mrblib/asterism.rb
|
|
148
149
|
- mrblib/cdr.rb
|
|
149
150
|
- mrblib/future.rb
|
|
151
|
+
- mrblib/profile.rb
|
|
150
152
|
- mrblib/proxy.rb
|
|
151
153
|
- mrblib/ros.rb
|
|
152
154
|
- tools/asterism_msggen.rb
|