asterism-zenoh 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2855bdb36755d80af0a3e56ff6b270d9b641063b80acd59807579d06fbd17636
4
+ data.tar.gz: ffe16a1e240950235c16d339bd18688aed345bffb72d54ccd5a271315deaa0f7
5
+ SHA512:
6
+ metadata.gz: 99b027cbc1b7af3e4ce63edd8acfcae3ba305c9d0320d9f6ba219b4b740cfa072957bc074045f255104d22bc7ac94f4f285f9371cc94f0dd69d64daa036994d1
7
+ data.tar.gz: 75c62b48351b64c4feb546113de28632602c23fdea6ad07b42ce8507db2a3a2468884c8b7afee4c86e25dbdc54ef2d578784d5ec0da687b832c7965ccdc9b853
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Katsuhiko Kageyama
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,180 @@
1
+ # asterism-zenoh
2
+
3
+ An unofficial Zenoh binding for CRuby, `Asterism::Zenoh`, over the prebuilt
4
+ [zenoh-c](https://github.com/eclipse-zenoh/zenoh-c) 1.10.1. Its Ruby API is
5
+ the one of Asterism's mruby / PicoRuby binding (over zenoh-pico), so the
6
+ same Ruby code runs on a PC and on the boards.
7
+
8
+ ```ruby
9
+ require "asterism/zenoh"
10
+
11
+ s = Asterism::Zenoh::Session.open("tcp/192.0.2.2:7447") # client of a router
12
+ sub = s.subscribe("demo/in")
13
+ loop do
14
+ break unless s.poll # false once the connection is lost
15
+ s.put("demo/out", "hello")
16
+ sub.each_pending { |key, payload, attachment| puts "#{key}: #{payload}" }
17
+ sleep 0.05
18
+ end
19
+ ```
20
+
21
+ ## API
22
+
23
+ The same calls, arguments and results as the mruby / PicoRuby gem
24
+ ([picoruby-asterism-zenoh](https://github.com/ruby-asterism/picoruby-asterism-zenoh)):
25
+
26
+ | Call | Notes |
27
+ |---|---|
28
+ | `Session.open(locator = nil, mode: :client, listen: nil)` | client of a router, or `mode: :peer` connecting to `locator` and/or listening on `listen:`. `Error` when nobody answers within `CONNECT_TIMEOUT_MS`. Releases the GVL while connecting |
29
+ | `session.put(key, payload, attachment: nil)` | `payload` / `attachment` are Strings (bytes). Releases the GVL |
30
+ | `session.subscribe(key, depth = 16)` -> `Subscriber` | `each_pending { \|key, payload, attachment\| }` (or an Array), `pending` / `received` / `dropped`, `close` / `closed?` |
31
+ | `session.get(key, timeout_ms = 2000, params = nil, payload = nil, attachment: nil, target: :all, consolidation: :none)` -> `Get` | returns at once; `each_reply { \|key, payload, attachment\| }`, `done?`, `pending` / `received` / `dropped` / `errors` |
32
+ | `session.queryable(key, depth = 16, complete: false)` -> `Queryable` | `each_pending { \|q\| }` (each query finished after the block) or an Array of `Query` |
33
+ | `q.key` / `params` / `payload` / `attachment`, `q.reply([key,] payload, attachment: nil)`, `q.finish` / `finished?` | |
34
+ | `session.liveliness(key)` -> `LivelinessToken` | `close` / `closed?` |
35
+ | `session.liveliness_watch(key, depth = 16)` -> `LivelinessWatch` | `each_pending { \|key, alive\| }`; the tokens alive now come first |
36
+ | `session.liveliness_get(key, timeout_ms = 2000)` -> `Get` | |
37
+ | `session.poll(steps = 8)` / `closed?` / `close` / `zid` / `peers` | |
38
+ | `Asterism::Zenoh::Error` | |
39
+ | `CONNECT_TIMEOUT_MS`, `SEND_TIMEOUT_MS` (3000), `PEER` (true), `MAX_PEERS`, `C_VERSION` | `MAX_PEERS` is zenoh-c's `transport/unicast/max_sessions` (1000); `C_VERSION` stands for the mruby gem's `PICO_VERSION` |
40
+
41
+ Keys come back as UTF-8 Strings, payloads and attachments as binary
42
+ (ASCII-8BIT) Strings.
43
+
44
+ `require "asterism/zenoh/global"` defines `Zenoh = Asterism::Zenoh` for
45
+ those who want the short name; nothing defines it by default.
46
+
47
+ ## How receiving works
48
+
49
+ zenoh-c runs the protocol on its own threads. Every subscriber, liveliness
50
+ watch, queryable and get has a zenoh-c FIFO channel; the gem's callback in
51
+ front of it (plain C: no Ruby, no GVL) counts what arrives and, when the
52
+ channel is full, drops the oldest entry, like the mruby gem's ring. The
53
+ application takes the entries out with `each_pending` / `each_reply` from
54
+ its own thread. Nothing calls into Ruby behind its back, and `poll` does not
55
+ need to run for data to arrive: it only checks the connection.
56
+
57
+ Waiting calls (`Session.open`, `put`, `get`, `liveliness_get`, `close`)
58
+ release the GVL. One `Session` and its objects are meant to be used from
59
+ one Ruby thread at a time, as on the boards.
60
+
61
+ ## Behaviour kept from the mruby gem
62
+
63
+ - **No scouting**: the locator is given. A peer that only connects does not
64
+ listen.
65
+ - **Remote only**: a session's own puts do not reach its own subscribers,
66
+ and its gets do not reach its own queryables (zenoh-pico's behaviour;
67
+ Asterism calls its own objects in place).
68
+ - **Losing the connection**: a client session is closed when it has no
69
+ router left, a peer session that only connects when it has no peer left;
70
+ a listening session stays open. From then on `poll` is false, `closed?`
71
+ true, and `put` raises `Asterism::Zenoh::Error`. No reconnection.
72
+ - **Full queues drop the oldest** entry and count it in `dropped` (a dropped
73
+ query is finished, so its requester gets no answer from it).
74
+
75
+ Differences: `C_VERSION` instead of `PICO_VERSION`; `MAX_PEERS` is zenoh-c's
76
+ limit, not 3; the time limits of gets are kept by zenoh-c (exact, not
77
+ checked once a second); liveliness watches may also report the session's
78
+ own tokens.
79
+
80
+ ## Installing
81
+
82
+ ```
83
+ gem install asterism-zenoh # or `gem install asterism`, which depends on it
84
+ ```
85
+
86
+ Needs CRuby 3.2+ and a C compiler; nothing else (no Rust, no git, curl or
87
+ unzip). `gem install` compiles the C extension against the official
88
+ prebuilt zenoh-c release pinned in `ZENOH_C_PIN`:
89
+
90
+ 1. With `ZENOH_C_DIR` set (or `gem install asterism-zenoh --
91
+ --with-zenoh-c-dir=DIR`), that zenoh-c (its `include/` and `lib/`) is
92
+ used and nothing is downloaded.
93
+ 2. Otherwise extconf.rb downloads the release archive for the machine from
94
+ `https://github.com/eclipse-zenoh/zenoh-c/releases/download/<tag>/`
95
+ (Ruby's net/http; proxies from the usual environment variables), checks
96
+ it against the sha256 pinned for that machine, and unpacks `include/`
97
+ and the shared library with Ruby's zlib. An archive whose sha256 differs
98
+ is not used.
99
+ 3. Machines without a pinned release, and machines that cannot download
100
+ it, stop with what to do instead (`ZENOH_C_DIR`, or a mirror).
101
+
102
+ Pinned machines: x86_64 and aarch64 Linux (glibc and musl), x86_64 and
103
+ arm64 macOS. The glibc builds of zenoh-c need glibc 2.34 or newer (Ubuntu
104
+ 22.04, Debian 12 and later). macOS is handled by extconf (the dylib's install
105
+ name is rewritten to `@rpath`) but has not been tested yet. `ASTERISM_ZENOH_C_MIRROR=<base>` downloads
106
+ `<base>/<tag>/<asset>` instead (an http(s) or `file://` URL, or a local
107
+ directory), for a mirror or a machine without access to GitHub.
108
+
109
+ The installed gem has, in `lib/asterism/`, the extension, zenoh-c's shared
110
+ library next to it (found through the rpath: `$ORIGIN` on Linux,
111
+ `@loader_path` on macOS), and `zenoh-c/` with the text of the Apache
112
+ License 2.0, zenoh-c's NOTICE.md and `SOURCE` (which archive was used, and
113
+ its sha256). The downloaded archive is not kept. `gem uninstall` removes
114
+ all of it.
115
+
116
+ ## Building and testing
117
+
118
+ In the repository (needs CRuby 3.2+ and a C compiler):
119
+
120
+ ```
121
+ rake # zenoh_c:fetch, compile, test
122
+ rake zenoh_c:fetch # download the release pinned in ZENOH_C_PIN to vendor/zenoh-c (sha256 checked)
123
+ rake compile # build lib/asterism/asterism_zenoh.so (+ libzenohc.so)
124
+ rake test # two sessions over a local peer link; no router needed
125
+ ASTERISM_TEST_ROUTER=tcp/127.0.0.1:7447 rake test # the same through a zenohd router
126
+ ZENOH_C_DIR=/path/to/zenoh-c rake compile # use another zenoh-c (include/ and lib/)
127
+ rake gem # build pkg/asterism-zenoh-<version>.gem
128
+ ```
129
+
130
+ `rake zenoh_c:fetch` uses the same code as the installation
131
+ (`ext/asterism_zenoh/zenoh_c.rb`). The extension links zenoh-c's shared
132
+ library, which `rake compile` copies next to it, so `ruby -I lib` works
133
+ without installing anything.
134
+
135
+ The object layer, the ROS 2 node and the message types on top of this
136
+ binding are the `asterism` gem
137
+ ([ruby-asterism/asterism](https://github.com/ruby-asterism/asterism)).
138
+
139
+ ## License
140
+
141
+ MIT (see LICENSE) for everything in this repository except
142
+ `licenses/zenoh-c/` (zenoh-c's notices and the Apache License text, below).
143
+ The C extension
144
+ (`ext/asterism_zenoh/zenoh.c`) is this gem's own code: it calls zenoh-c's
145
+ API and copies no code from zenoh-c's examples or headers.
146
+
147
+ ### zenoh-c is not in this repository or in the gem file
148
+
149
+ [zenoh-c](https://github.com/eclipse-zenoh/zenoh-c) (Eclipse Zenoh's C
150
+ binding, Copyright ZettaScale Technology) is offered under the Eclipse
151
+ Public License 2.0 or the Apache License, Version 2.0 (EPL-2.0 OR
152
+ Apache-2.0). This gem uses it under the **Apache License, Version 2.0**.
153
+ Neither this repository nor the gem file contains it: `gem install` (and
154
+ `rake zenoh_c:fetch`) downloads the official prebuilt release pinned in
155
+ `ZENOH_C_PIN` (sha256 checked) on the user's machine, from zenoh-c's own
156
+ GitHub releases (or a mirror the user names).
157
+
158
+ The prebuilt release archives contain only `include/` and `lib/`, not
159
+ zenoh-c's LICENSE or NOTICE.md. So the gem carries, in `licenses/zenoh-c/`,
160
+ the text of the Apache License, Version 2.0 (`LICENSE-APACHE`) and zenoh-c's
161
+ NOTICE.md at the pinned tag, unchanged, and installs both next to the
162
+ shared library (`lib/asterism/zenoh-c/`). The gemspec lists `MIT` and
163
+ `Apache-2.0`: MIT for this gem's own code, Apache-2.0 for zenoh-c, which
164
+ the installed gem holds.
165
+
166
+ ### Distribution notes
167
+
168
+ A package that **carries** zenoh-c itself (a prebuilt, platform-specific
169
+ gem with the shared library inside, a container image, an archive of an
170
+ installed gem or a built `lib/`) is a redistribution of zenoh-c under the
171
+ Apache License, Version 2.0, and must carry:
172
+
173
+ - the text of the Apache License, Version 2.0, and zenoh-c's NOTICE.md
174
+ (its notices, including the Eclipse trademark notice), unchanged: the
175
+ installed `lib/asterism/zenoh-c/` has both;
176
+ - the licenses and notices of the Rust crates compiled into the shared
177
+ library (zenoh and its dependencies, listed in zenoh-c's Cargo.lock;
178
+ zenoh-c's NOTICE.md does not list them). Collect them from that
179
+ Cargo.lock for the pinned release with a tool such as cargo-about
180
+ before publishing such a package.
data/ZENOH_C_PIN ADDED
@@ -0,0 +1,31 @@
1
+ # Pinned zenoh-c (Eclipse Zenoh's C binding of the Rust implementation), used
2
+ # by the asterism-zenoh gem (the CRuby Asterism::Zenoh).
3
+ # Same scheme as ZENOH_PICO_PIN of picoruby-asterism-zenoh:
4
+ # - At `gem install`, extconf.rb downloads the official prebuilt release for
5
+ # the machine (asset.<platform> below) from <repo>/releases/download/<tag>/,
6
+ # checks its sha256 and builds the extension against it
7
+ # (ext/asterism_zenoh/zenoh_c.rb). `rake zenoh_c:fetch` does the same into
8
+ # vendor/zenoh-c (gitignored) for working in the repository. Nothing is
9
+ # built with Rust.
10
+ # - Upstream tag 1.10.1: the same number as zenoh-pico (the boards, pinned
11
+ # in picoruby-asterism-zenoh) and the zenohd router used in the tests of
12
+ # the Family mruby project. Zenoh keeps the wire compatible within 1.x;
13
+ # keep them together anyway.
14
+ # - <platform> is <cpu>-<os> as ZenohCFetch.platform_key names this machine.
15
+ # The sha256 values were computed from the downloaded archives and agree
16
+ # with the digests GitHub lists for the release assets.
17
+ # - To update: bump the tag, the asset names and their sha256 here.
18
+ repo: https://github.com/eclipse-zenoh/zenoh-c
19
+ tag: 1.10.1
20
+ asset.x86_64-linux: zenoh-c-1.10.1-x86_64-unknown-linux-gnu-standalone.zip
21
+ sha256.x86_64-linux: 9ee0f2d732b0f3042a7e1cd3076042a2bc3ac0415587c40bc3ed7b8b62fbde11
22
+ asset.aarch64-linux: zenoh-c-1.10.1-aarch64-unknown-linux-gnu-standalone.zip
23
+ sha256.aarch64-linux: 65970bbed6dc10fec4fa39d05f3876e85fcb9b0f87d5be0a54bd7517240db501
24
+ asset.x86_64-linux-musl: zenoh-c-1.10.1-x86_64-unknown-linux-musl-standalone.zip
25
+ sha256.x86_64-linux-musl: 293866bb632fd579fb0603bbfdb8d383e7c2d4eadc8ec3ae199fafa1dbc99c55
26
+ asset.aarch64-linux-musl: zenoh-c-1.10.1-aarch64-unknown-linux-musl-standalone.zip
27
+ sha256.aarch64-linux-musl: de99cc82c7ae93eaa2aa5a0c8bcfcefed7624b0e0f85c679f7dba9c54ccdff9c
28
+ asset.x86_64-darwin: zenoh-c-1.10.1-x86_64-apple-darwin-standalone.zip
29
+ sha256.x86_64-darwin: 6578ef42b0460a0522e7af4b1baa965ec75ce39f4c652cdd938252fdf6695219
30
+ asset.aarch64-darwin: zenoh-c-1.10.1-aarch64-apple-darwin-standalone.zip
31
+ sha256.aarch64-darwin: 82da6e95eb895413369f55d1a53eb5b621b22ab8afc446b041b7760eec240ff4
@@ -0,0 +1,98 @@
1
+ # Builds the C extension of asterism-zenoh against a prebuilt zenoh-c.
2
+ #
3
+ # Where zenoh-c comes from:
4
+ # 1. ZENOH_C_DIR (or `gem install asterism-zenoh -- --with-zenoh-c-dir=DIR`):
5
+ # a zenoh-c of your own, its include/ and lib/. The Rakefile passes its
6
+ # vendor/zenoh-c this way.
7
+ # 2. Otherwise the official prebuilt release pinned in ZENOH_C_PIN for this
8
+ # machine is downloaded and checked against the pinned sha256
9
+ # (zenoh_c.rb). Machines without a pinned release, and machines that
10
+ # cannot download it, stop here with what to do instead.
11
+ #
12
+ # zenoh-c is linked as a shared library. `make install` puts it next to the
13
+ # extension (lib/asterism/ of the installed gem), with the text of the
14
+ # Apache License 2.0 and zenoh-c's NOTICE.md in lib/asterism/zenoh-c/; the
15
+ # extension finds it through its rpath ($ORIGIN on Linux, @loader_path on
16
+ # macOS). Uninstalling the gem removes all of it.
17
+ require "mkmf"
18
+ require_relative "zenoh_c"
19
+
20
+ LIB = ZenohCFetch.lib_name
21
+ DARWIN = RbConfig::CONFIG["host_os"].match?(/darwin/)
22
+ LICENSES = File.expand_path("../../licenses/zenoh-c", __dir__)
23
+ STAMP = ".zenoh-c-installed"
24
+
25
+ zdir = with_config("zenoh-c-dir") || ENV["ZENOH_C_DIR"]
26
+ if zdir && !zdir.empty?
27
+ zdir = File.expand_path(zdir)
28
+ source = "zenoh-c from ZENOH_C_DIR (not fetched by this gem)\n"
29
+ fetched = false
30
+ message "using zenoh-c in #{zdir}\n"
31
+ else
32
+ zdir = File.expand_path("zenoh-c")
33
+ begin
34
+ got = ZenohCFetch.fetch(zdir)
35
+ rescue ZenohCFetch::Error => e
36
+ abort "asterism-zenoh: #{e.message}"
37
+ end
38
+ source = got.map { |k, v| "#{k}: #{v}\n" }.join
39
+ fetched = true
40
+ message "fetched zenoh-c #{got['tag']} (#{got['platform']}), sha256 #{got['sha256']} OK\n"
41
+ end
42
+ inc = File.join(zdir, "include")
43
+ lib = File.join(zdir, "lib")
44
+ unless File.exist?(File.join(inc, "zenoh.h")) && File.exist?(File.join(lib, LIB))
45
+ abort "asterism-zenoh: zenoh-c not found in #{zdir} (needs include/zenoh.h and lib/#{LIB}).\n" +
46
+ ZenohCFetch.help
47
+ end
48
+ File.write("zenoh-c-SOURCE", source)
49
+
50
+ $INCFLAGS << " -I#{inc}"
51
+ $CFLAGS << " -std=gnu11 -Wall -Wno-unused-parameter"
52
+ $LDFLAGS << " -L#{lib}"
53
+ $LDFLAGS << (DARWIN ? " -Wl,-rpath,@loader_path" : " -Wl,-rpath,'$$ORIGIN'")
54
+ have_library("pthread") || abort("asterism-zenoh: pthread is needed")
55
+ have_library("zenohc", "z_open", "zenoh.h") || abort("asterism-zenoh: cannot link #{lib}/#{LIB}")
56
+
57
+ create_makefile("asterism/asterism_zenoh")
58
+
59
+ # The prebuilt dylib names itself by the path it was built at; refer to it
60
+ # through the rpath instead (and sign the changed bundle again).
61
+ postlink = []
62
+ if DARWIN
63
+ id = `otool -D #{lib}/#{LIB}`.lines.last.to_s.strip
64
+ abort "asterism-zenoh: cannot read the install name of #{lib}/#{LIB} (otool)" if id.empty?
65
+ unless id.start_with?("@rpath/")
66
+ postlink << "\t$(Q) install_name_tool -change '#{id}' '@rpath/#{LIB}' $@"
67
+ postlink << "\t-$(Q) codesign --force --sign - $@"
68
+ end
69
+ end
70
+
71
+ mk = File.read("Makefile")
72
+ unless postlink.empty?
73
+ mk.sub!(/^(\$\(TARGET_SO\):.*\n(?:\t.*\n)*?\t.*\$\(LDSHARED\).*\n)/) { $1 + postlink.join("\n") + "\n" } or
74
+ abort "asterism-zenoh: no link rule in the Makefile"
75
+ end
76
+ mk << <<~MAKE
77
+
78
+ # zenoh-c next to the extension, with its license text and notices.
79
+ ZENOHC_LIB = #{lib}/#{LIB}
80
+ ZENOHC_DOCS = #{LICENSES}/LICENSE-APACHE #{LICENSES}/NOTICE.md
81
+ install-so: install-zenoh-c
82
+ install-zenoh-c: $(TARGET_SO)
83
+ \t$(Q) $(MAKEDIRS) $(RUBYARCHDIR)/zenoh-c
84
+ \t$(INSTALL_PROG) $(ZENOHC_LIB) $(RUBYARCHDIR)
85
+ \t$(INSTALL_DATA) $(ZENOHC_DOCS) $(RUBYARCHDIR)/zenoh-c
86
+ \t$(INSTALL_DATA) zenoh-c-SOURCE $(RUBYARCHDIR)/zenoh-c/SOURCE
87
+ MAKE
88
+ if fetched
89
+ # RubyGems runs `make clean` before building and again after installing;
90
+ # the downloaded zenoh-c goes with the second one only.
91
+ mk << <<~MAKE
92
+ \t$(Q) touch #{STAMP}
93
+ clean: clean-zenoh-c
94
+ clean-zenoh-c:
95
+ \t-$(Q) if test -f #{STAMP}; then $(RM_RF) zenoh-c zenoh-c-SOURCE #{STAMP}; fi
96
+ MAKE
97
+ end
98
+ File.write("Makefile", mk)