asterism 0.3.0 → 0.4.1

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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +149 -0
  3. data/NOTICE +12 -7
  4. data/README.md +305 -74
  5. data/data/msgs/LICENSE-BSD-3-Clause-tf2_msgs.txt +29 -0
  6. data/data/msgs/NOTICE +19 -13
  7. data/data/msgs/builtin_interfaces/msg/Duration.rb +4 -0
  8. data/data/msgs/builtin_interfaces/msg/Time.rb +4 -0
  9. data/data/msgs/diagnostic_msgs/msg/DiagnosticArray.rb +40 -0
  10. data/data/msgs/diagnostic_msgs/msg/DiagnosticStatus.rb +55 -0
  11. data/data/msgs/diagnostic_msgs/msg/KeyValue.rb +37 -0
  12. data/data/msgs/example_interfaces/srv/AddTwoInts.rb +7 -0
  13. data/data/msgs/geometry_msgs/msg/Accel.rb +4 -0
  14. data/data/msgs/geometry_msgs/msg/AccelStamped.rb +4 -0
  15. data/data/msgs/geometry_msgs/msg/Point.rb +5 -0
  16. data/data/msgs/geometry_msgs/msg/Point32.rb +5 -0
  17. data/data/msgs/geometry_msgs/msg/PointStamped.rb +4 -0
  18. data/data/msgs/geometry_msgs/msg/Pose.rb +4 -0
  19. data/data/msgs/geometry_msgs/msg/Pose2D.rb +5 -0
  20. data/data/msgs/geometry_msgs/msg/PoseStamped.rb +4 -0
  21. data/data/msgs/geometry_msgs/msg/PoseWithCovariance.rb +39 -0
  22. data/data/msgs/geometry_msgs/msg/Quaternion.rb +6 -0
  23. data/data/msgs/geometry_msgs/msg/QuaternionStamped.rb +4 -0
  24. data/data/msgs/geometry_msgs/msg/Transform.rb +4 -0
  25. data/data/msgs/geometry_msgs/msg/TransformStamped.rb +5 -0
  26. data/data/msgs/geometry_msgs/msg/Twist.rb +4 -0
  27. data/data/msgs/geometry_msgs/msg/TwistStamped.rb +4 -0
  28. data/data/msgs/geometry_msgs/msg/TwistWithCovariance.rb +39 -0
  29. data/data/msgs/geometry_msgs/msg/Vector3.rb +5 -0
  30. data/data/msgs/geometry_msgs/msg/Vector3Stamped.rb +4 -0
  31. data/data/msgs/geometry_msgs/msg/Wrench.rb +4 -0
  32. data/data/msgs/geometry_msgs/msg/WrenchStamped.rb +4 -0
  33. data/data/msgs/nav_msgs/msg/MapMetaData.rb +52 -0
  34. data/data/msgs/nav_msgs/msg/OccupancyGrid.rb +44 -0
  35. data/data/msgs/nav_msgs/msg/Odometry.rb +49 -0
  36. data/data/msgs/nav_msgs/msg/Path.rb +40 -0
  37. data/data/msgs/rcl_interfaces/msg/Log.rb +64 -0
  38. data/data/msgs/rcl_interfaces/msg/Parameter.rb +39 -0
  39. data/data/msgs/rcl_interfaces/msg/ParameterEvent.rb +52 -0
  40. data/data/msgs/rcl_interfaces/msg/ParameterValue.rb +69 -0
  41. data/data/msgs/sensor_msgs/msg/BatteryState.rb +18 -0
  42. data/data/msgs/sensor_msgs/msg/CompressedImage.rb +43 -0
  43. data/data/msgs/sensor_msgs/msg/FluidPressure.rb +5 -0
  44. data/data/msgs/sensor_msgs/msg/Illuminance.rb +5 -0
  45. data/data/msgs/sensor_msgs/msg/Imu.rb +9 -0
  46. data/data/msgs/sensor_msgs/msg/JointState.rb +7 -0
  47. data/data/msgs/sensor_msgs/msg/LaserScan.rb +71 -0
  48. data/data/msgs/sensor_msgs/msg/MagneticField.rb +5 -0
  49. data/data/msgs/sensor_msgs/msg/NavSatFix.rb +9 -0
  50. data/data/msgs/sensor_msgs/msg/NavSatStatus.rb +4 -0
  51. data/data/msgs/sensor_msgs/msg/PointCloud2.rb +68 -0
  52. data/data/msgs/sensor_msgs/msg/PointField.rb +53 -0
  53. data/data/msgs/sensor_msgs/msg/Range.rb +9 -0
  54. data/data/msgs/sensor_msgs/msg/RelativeHumidity.rb +5 -0
  55. data/data/msgs/sensor_msgs/msg/Temperature.rb +5 -0
  56. data/data/msgs/std_msgs/msg/Bool.rb +3 -0
  57. data/data/msgs/std_msgs/msg/Byte.rb +3 -0
  58. data/data/msgs/std_msgs/msg/ByteMultiArray.rb +4 -0
  59. data/data/msgs/std_msgs/msg/Char.rb +3 -0
  60. data/data/msgs/std_msgs/msg/ColorRGBA.rb +6 -0
  61. data/data/msgs/std_msgs/msg/Empty.rb +1 -0
  62. data/data/msgs/std_msgs/msg/Float32.rb +3 -0
  63. data/data/msgs/std_msgs/msg/Float32MultiArray.rb +4 -0
  64. data/data/msgs/std_msgs/msg/Float64.rb +3 -0
  65. data/data/msgs/std_msgs/msg/Float64MultiArray.rb +4 -0
  66. data/data/msgs/std_msgs/msg/Header.rb +4 -0
  67. data/data/msgs/std_msgs/msg/Int16.rb +3 -0
  68. data/data/msgs/std_msgs/msg/Int16MultiArray.rb +4 -0
  69. data/data/msgs/std_msgs/msg/Int32.rb +3 -0
  70. data/data/msgs/std_msgs/msg/Int32MultiArray.rb +4 -0
  71. data/data/msgs/std_msgs/msg/Int64.rb +3 -0
  72. data/data/msgs/std_msgs/msg/Int64MultiArray.rb +4 -0
  73. data/data/msgs/std_msgs/msg/Int8.rb +3 -0
  74. data/data/msgs/std_msgs/msg/Int8MultiArray.rb +4 -0
  75. data/data/msgs/std_msgs/msg/MultiArrayDimension.rb +5 -0
  76. data/data/msgs/std_msgs/msg/MultiArrayLayout.rb +4 -0
  77. data/data/msgs/std_msgs/msg/String.rb +3 -0
  78. data/data/msgs/std_msgs/msg/UInt16.rb +3 -0
  79. data/data/msgs/std_msgs/msg/UInt16MultiArray.rb +4 -0
  80. data/data/msgs/std_msgs/msg/UInt32.rb +3 -0
  81. data/data/msgs/std_msgs/msg/UInt32MultiArray.rb +4 -0
  82. data/data/msgs/std_msgs/msg/UInt64.rb +3 -0
  83. data/data/msgs/std_msgs/msg/UInt64MultiArray.rb +4 -0
  84. data/data/msgs/std_msgs/msg/UInt8.rb +3 -0
  85. data/data/msgs/std_msgs/msg/UInt8MultiArray.rb +4 -0
  86. data/data/msgs/tf2_msgs/msg/TFMessage.rb +35 -0
  87. data/data/msgs/visualization_msgs/msg/Marker.rb +132 -0
  88. data/data/msgs/visualization_msgs/msg/MarkerArray.rb +35 -0
  89. data/data/msgs/visualization_msgs/msg/MeshFile.rb +37 -0
  90. data/data/msgs/visualization_msgs/msg/UVCoordinate.rb +37 -0
  91. data/lib/asterism/cruby/objects.rb +25 -10
  92. data/lib/asterism/cruby/ros.rb +6 -4
  93. data/lib/asterism/cruby/zenoh.rb +190 -60
  94. data/lib/asterism/version.rb +1 -1
  95. data/lib/asterism.rb +2 -2
  96. data/mrblib/asterism.rb +112 -19
  97. data/mrblib/cdr.rb +5 -1
  98. data/mrblib/future.rb +11 -2
  99. data/mrblib/proxy.rb +13 -3
  100. data/mrblib/ros.rb +189 -44
  101. data/tools/asterism_msggen.rb +32 -5
  102. metadata +29 -4
@@ -0,0 +1,35 @@
1
+ # visualization_msgs/msg/MarkerArray (ROS 2 jazzy), generated by asterism_msggen.rb from
2
+ # MarkerArray.msg of the package visualization_msgs.
3
+ # License: Apache License 2.0, as the definition (package.xml of visualization_msgs).
4
+ # Do not edit; regenerate instead.
5
+
6
+ ::Asterism::ROS.require_type("visualization_msgs/msg/Marker")
7
+
8
+ module ::Asterism::ROS::VisualizationMsgs
9
+ end
10
+
11
+ class ::Asterism::ROS::VisualizationMsgs::MarkerArray < ::Asterism::ROS::Message
12
+ ROS_NAME = "visualization_msgs/msg/MarkerArray"
13
+ TYPE_NAME = "visualization_msgs::msg::dds_::MarkerArray_"
14
+ TYPE_HASH = "RIHS01_86cb8800b6fb05b5eff1abd7a56f62a5641d3ae9a1c29e78e67e704f1d067dcf"
15
+ FIELDS = [:markers]
16
+ FIELD_TYPES = [
17
+ ["markers", "visualization_msgs/msg/Marker", "sequence", "visualization_msgs/msg/Marker", nil, nil]
18
+ ]
19
+
20
+ attr_accessor :markers
21
+
22
+ def initialize(markers: nil)
23
+ @markers = markers.nil? ? [] : markers.map { |e| ::Asterism::ROS::VisualizationMsgs::Marker.from(e) }
24
+ end
25
+
26
+ def self.write(w, m)
27
+ w.structs(::Asterism::ROS::VisualizationMsgs::Marker, m.markers, nil, nil)
28
+ end
29
+
30
+ def self.read(r)
31
+ m = new
32
+ m.markers = r.structs(::Asterism::ROS::VisualizationMsgs::Marker, nil)
33
+ m
34
+ end
35
+ end
@@ -0,0 +1,37 @@
1
+ # visualization_msgs/msg/MeshFile (ROS 2 jazzy), generated by asterism_msggen.rb from
2
+ # MeshFile.msg of the package visualization_msgs.
3
+ # License: Apache License 2.0, as the definition (package.xml of visualization_msgs).
4
+ # Do not edit; regenerate instead.
5
+
6
+ module ::Asterism::ROS::VisualizationMsgs
7
+ end
8
+
9
+ class ::Asterism::ROS::VisualizationMsgs::MeshFile < ::Asterism::ROS::Message
10
+ ROS_NAME = "visualization_msgs/msg/MeshFile"
11
+ TYPE_NAME = "visualization_msgs::msg::dds_::MeshFile_"
12
+ TYPE_HASH = "RIHS01_7710ece15a148fb7c9b546364cfb215bb06098087bd6394fe5b73a493508f8c4"
13
+ FIELDS = [:filename, :data]
14
+ FIELD_TYPES = [
15
+ ["filename", "string", "scalar", nil, nil, nil],
16
+ ["data", "uint8", "sequence", nil, nil, nil]
17
+ ]
18
+
19
+ attr_accessor :filename, :data
20
+
21
+ def initialize(filename: nil, data: nil)
22
+ @filename = filename.nil? ? "" : filename
23
+ @data = data.nil? ? "" : data
24
+ end
25
+
26
+ def self.write(w, m)
27
+ w.string(m.filename)
28
+ w.bytes(m.data, nil, nil)
29
+ end
30
+
31
+ def self.read(r)
32
+ m = new
33
+ m.filename = r.string
34
+ m.data = r.bytes(nil)
35
+ m
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ # visualization_msgs/msg/UVCoordinate (ROS 2 jazzy), generated by asterism_msggen.rb from
2
+ # UVCoordinate.msg of the package visualization_msgs.
3
+ # License: Apache License 2.0, as the definition (package.xml of visualization_msgs).
4
+ # Do not edit; regenerate instead.
5
+
6
+ module ::Asterism::ROS::VisualizationMsgs
7
+ end
8
+
9
+ class ::Asterism::ROS::VisualizationMsgs::UVCoordinate < ::Asterism::ROS::Message
10
+ ROS_NAME = "visualization_msgs/msg/UVCoordinate"
11
+ TYPE_NAME = "visualization_msgs::msg::dds_::UVCoordinate_"
12
+ TYPE_HASH = "RIHS01_f27f7ed21fe360c6066944f856b801a0c0d1e94e815b6886444b42d90b196a26"
13
+ FIELDS = [:u, :v]
14
+ FIELD_TYPES = [
15
+ ["u", "float32", "scalar", nil, nil, nil],
16
+ ["v", "float32", "scalar", nil, nil, nil]
17
+ ]
18
+
19
+ attr_accessor :u, :v
20
+
21
+ def initialize(u: nil, v: nil)
22
+ @u = u.nil? ? 0.0 : u
23
+ @v = v.nil? ? 0.0 : v
24
+ end
25
+
26
+ def self.write(w, m)
27
+ w.float32(m.u)
28
+ w.float32(m.v)
29
+ end
30
+
31
+ def self.read(r)
32
+ m = new
33
+ m.u = r.float32
34
+ m.v = r.float32
35
+ m
36
+ end
37
+ end
@@ -1,6 +1,6 @@
1
1
  # The object layer (Asterism.connect / expose / []) with blocks and a
2
2
  # receiving thread (CRuby only). Asterism.connect without a block,
3
- # Asterism.poll and the rest of the polled API work as on the boards.
3
+ # Asterism.poll and the rest of the portable API work as on the boards.
4
4
  #
5
5
  # Asterism.connect("tcp/192.0.2.2:7447", node: "mypc", app: "demo") do |net|
6
6
  # net.expose("screen", Screen.new, methods: [:say])
@@ -23,10 +23,10 @@ module Asterism
23
23
  # Held by every entry into the object layer (reentrant).
24
24
  LOCK = Monitor.new
25
25
 
26
- # The polled API, entered under LOCK. Prepended to the Asterism module's
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].each do |m|
29
+ %i[expose unexpose poll nodes [] call_async meta connected? exposed on_join on_leave off_join off_leave].each do |m|
30
30
  define_method(m) do |*args, **kw, &blk|
31
31
  ::Asterism::LOCK.synchronize { super(*args, **kw, &blk) }
32
32
  end
@@ -35,9 +35,11 @@ module Asterism
35
35
  # With a block: yields an Asterism::Net and closes when the block ends
36
36
  # (also on an exception); returns the block's value. Without a block,
37
37
  # as before (returns Asterism).
38
- def connect(locator, node:, app:, mode: nil, listen: nil, &blk)
38
+ def connect(locator, node:, app:, mode: nil, listen: nil, config: nil, connect_timeout: nil, check_timeout: nil,
39
+ &blk)
39
40
  ::Asterism::LOCK.synchronize do
40
- super(locator, node: node, app: app, mode: mode, listen: listen)
41
+ super(locator, node: node, app: app, mode: mode, listen: listen, config: config,
42
+ connect_timeout: connect_timeout, check_timeout: check_timeout)
41
43
  # The Net of a connection that was lost (not closed) belongs to the
42
44
  # old session.
43
45
  old = @net
@@ -145,15 +147,17 @@ module Asterism
145
147
  ::Asterism.unexpose(name)
146
148
  end
147
149
 
148
- # A proxy for <node>/<app>/<object>; timeout in seconds.
149
- def [](path, timeout: nil)
150
- ::Asterism[path, Zenoh.ms(timeout, ::Asterism::DEFAULT_TIMEOUT_MS)]
150
+ # A proxy for <node>/<app>/<object>; timeout: in seconds (or
151
+ # timeout_ms:).
152
+ def [](path, timeout: nil, timeout_ms: nil)
153
+ ::Asterism[path, timeout: timeout, timeout_ms: timeout_ms]
151
154
  end
152
155
  alias proxy []
153
156
 
154
157
  # The exposed objects alive now matching the pattern, as proxies. With
155
- # a block, yields each and returns their number; without, an
156
- # Enumerator (net.each("*/demo/info").map(&:status)).
158
+ # a block, yields each and returns their number (1.0: returns self, as
159
+ # Ruby's each does; net.count is the number); without, an Enumerator
160
+ # (net.each("*/demo/info").map(&:status)).
157
161
  def each(pattern = "**", &blk)
158
162
  return enum_for(:each, pattern) unless blk
159
163
  ::Asterism.each(pattern, &blk)
@@ -173,6 +177,17 @@ module Asterism
173
177
  self
174
178
  end
175
179
 
180
+ # Removes a block given to on_join / on_leave (keep the Proc).
181
+ def off_join(blk)
182
+ ::Asterism.off_join(blk)
183
+ self
184
+ end
185
+
186
+ def off_leave(blk)
187
+ ::Asterism.off_leave(blk)
188
+ self
189
+ end
190
+
176
191
  # on_error { |error, where| }: what on_join / on_leave blocks raise.
177
192
  # (An exposed method that raises is answered to its caller as a
178
193
  # RemoteError, as before; it does not come here.)
@@ -197,9 +197,11 @@ module Asterism
197
197
  Topic.new(self, topic, type, qos, depth)
198
198
  end
199
199
 
200
- # Node#call with the time limit in seconds as well: timeout: 2.0.
201
- def call(service, type, request = nil, timeout: nil, timeout_ms: nil, **fields)
202
- ms = timeout_ms || ::Asterism::Zenoh.ms(timeout, Client::DEFAULT_TIMEOUT_MS)
200
+ # Node#call; here timeout: is always the time limit, also for a
201
+ # request type with a field named timeout (give such a request as
202
+ # request: { ... }).
203
+ def call(service, type, req = nil, request: nil, timeout: nil, timeout_ms: nil, **fields)
204
+ ms = ::Asterism.time_ms("Node#call", timeout, timeout_ms, nil, nil)
203
205
  c = @asterism_runner.lock.synchronize do
204
206
  @clients ||= {}
205
207
  cl = @clients[service]
@@ -209,7 +211,7 @@ module Asterism
209
211
  end
210
212
  cl
211
213
  end
212
- c.call(request, timeout_ms: ms, **fields)
214
+ c.call(req, request: request, timeout_ms: ms, **fields)
213
215
  end
214
216
  end
215
217
 
@@ -1,5 +1,5 @@
1
1
  # Asterism::Zenoh with blocks, threads and Enumerators (CRuby only). The
2
- # polled API of asterism-zenoh (Session, each_pending, each_reply) is the
2
+ # portable API of asterism-zenoh (Session, each_pending, each_reply) is the
3
3
  # same on the boards and stays as it is; this is a layer on top of it.
4
4
  #
5
5
  # Asterism::Zenoh.open("tcp/192.0.2.2:7447") do |s|
@@ -80,10 +80,63 @@ module Asterism
80
80
  end
81
81
  end
82
82
 
83
- # Seconds (Float) or nil -> milliseconds for the polled API.
83
+ # Seconds (Float) or nil -> milliseconds for the portable API.
84
+ # @api private
84
85
  def self.ms(seconds, default_ms)
85
- return default_ms if seconds.nil?
86
- (seconds * 1000).round
86
+ ::Asterism.time_ms("timeout:", seconds, nil, nil, default_ms)
87
+ end
88
+
89
+ # Warns once for owner (a wrapper, or a Get) when raw (what it wraps)
90
+ # dropped something. Only for queues whose depth the application left
91
+ # at the default: one that chose depth: reads dropped itself.
92
+ # @api private
93
+ def self.note_dropped(owner, raw, what, unit)
94
+ d = raw.dropped
95
+ return false if d == 0
96
+ ::Asterism.warn_once(owner, "asterism: #{what}: #{d} #{unit} dropped, the queue was full " \
97
+ "(pass a larger depth:, or read dropped)")
98
+ end
99
+
100
+ # The keys liveliness_get returns: an Array, with the counts of the get
101
+ # behind it.
102
+ class KeyList < Array
103
+ # Replies that did not fit the get's queue (oldest first), and all
104
+ # that came.
105
+ attr_reader :dropped, :received
106
+
107
+ # @api private
108
+ def finish(get)
109
+ @dropped = get.dropped
110
+ @received = get.received
111
+ self
112
+ end
113
+ end
114
+
115
+ # What Connection#get and Querier#get return without a block: an
116
+ # Enumerator (each iteration sends the get again) that also has the
117
+ # counts of the last get it sent: dropped, received, errors (nil before
118
+ # the first).
119
+ class GetEnumerator < Enumerator
120
+ attr_reader :dropped, :received, :errors
121
+
122
+ # @api private
123
+ def finish(get)
124
+ @dropped = get.dropped
125
+ @received = get.received
126
+ @errors = get.errors
127
+ self
128
+ end
129
+ end
130
+
131
+ # Yields obj and closes it after the block (the block's value), or
132
+ # returns obj without a block. @api private
133
+ def self.scoped(obj)
134
+ return obj unless block_given?
135
+ begin
136
+ yield obj
137
+ ensure
138
+ obj.close
139
+ end
87
140
  end
88
141
 
89
142
  # Waits on the calling thread until a sample comes into sub (an
@@ -130,30 +183,34 @@ module Asterism
130
183
 
131
184
  # A declared publisher (Connection::Publisher): put / delete,
132
185
  # 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)
186
+ # Session#publisher (encoding:, priority:, ...). With a block: yields
187
+ # it and closes it after the block (returns the block's value).
188
+ def publisher(key, **opts, &blk)
189
+ Zenoh.scoped(Publisher.new(self, @session.publisher(key, **opts), key), &blk)
136
190
  end
137
191
 
138
192
  # An advanced publisher (cache:, sample_miss_detection:,
139
193
  # 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)
194
+ # with history get what it kept. Same object as publisher (and the
195
+ # same block form).
196
+ def advanced_publisher(key, **opts, &blk)
197
+ Zenoh.scoped(Publisher.new(self, @session.advanced_publisher(key, **opts), key), &blk)
143
198
  end
144
199
 
145
200
  # An advanced subscriber (history:, recovery:, subscriber_detection:,
146
- # query_timeout_ms:): like subscribe, plus on_publisher and on_miss.
201
+ # query_timeout: in seconds or query_timeout_ms:): like subscribe,
202
+ # plus on_publisher and on_miss.
147
203
  def advanced_subscriber(key, depth: 16, **opts, &blk)
148
- sub = AdvancedSubscription.new(self, @session.advanced_subscriber(key, depth, **opts), key)
204
+ sub = AdvancedSubscription.new(self, @session.advanced_subscriber(key, depth: depth, **opts), key)
149
205
  sub.handle(&blk) if blk
150
206
  sub
151
207
  end
152
208
 
153
209
  # 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)
210
+ # on_matching. timeout: in seconds (2.0) or timeout_ms:; the rest as
211
+ # Session#querier.
212
+ def querier(key, timeout: nil, **opts)
213
+ Querier.new(self, @session.querier(key, timeout: timeout, **opts), key)
157
214
  end
158
215
 
159
216
  # on_transport(history: false) { |event| }: a TransportEvent (kind
@@ -161,21 +218,22 @@ module Asterism
161
218
  # peer or router connects or goes. Returns an Events (close it).
162
219
  def on_transport(history: false, depth: 16, &blk)
163
220
  raise ArgumentError, "a block is needed" unless blk
164
- Events.new(self, @session.transport_events(depth, history: history), "on_transport", blk)
221
+ Events.new(self, @session.transport_events(depth: depth, history: history), "on_transport", blk)
165
222
  end
166
223
 
167
224
  # on_link(history: false) { |event| }: a LinkEvent each time a link
168
225
  # opens or closes.
169
226
  def on_link(history: false, depth: 16, &blk)
170
227
  raise ArgumentError, "a block is needed" unless blk
171
- Events.new(self, @session.link_events(depth, history: history), "on_link", blk)
228
+ Events.new(self, @session.link_events(depth: depth, history: history), "on_link", blk)
172
229
  end
173
230
 
174
231
  # With a block: every sample is given to it (as a Sample) on the
175
232
  # receiving thread; returns the Subscription (close it to stop).
176
- # Without: a Subscription to take from (each, each_pending).
177
- def subscribe(key, depth: 16, &blk)
178
- sub = Subscription.new(self, @session.subscribe(key, depth), key)
233
+ # Without: a Subscription to take from (each, each_sample).
234
+ def subscribe(key, depth: nil, &blk)
235
+ d = depth.nil? ? DEFAULT_DEPTH : depth
236
+ sub = Subscription.new(self, @session.subscribe(key, depth: d), key, warn: depth.nil?)
179
237
  sub.handle(&blk) if blk
180
238
  sub
181
239
  end
@@ -184,53 +242,65 @@ module Asterism
184
242
  # finished when it returns (reply in the block with q.reply). Without:
185
243
  # a Queryable whose each yields the queries (finished after each).
186
244
  def queryable(key, depth: 16, complete: false, &blk)
187
- qa = Queryable.new(self, @session.queryable(key, depth, complete: complete), key)
245
+ qa = Queryable.new(self, @session.queryable(key, depth: depth, complete: complete), key)
188
246
  qa.handle(&blk) if blk
189
247
  qa
190
248
  end
191
249
 
192
250
  # The replies to a get: yields each Reply until every answer came or
193
- # timeout (seconds) ran out, and returns their number. Without a
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).
198
- def get(key, timeout: 2.0, params: nil, payload: nil, attachment: nil, target: :all,
199
- consolidation: :none, errors: false, **opts, &blk)
251
+ # timeout (seconds; or timeout_ms:) ran out, and returns their number
252
+ # (not named each, so the count stays). Without a block, a
253
+ # GetEnumerator (each iteration sends the get again; dropped /
254
+ # received / errors of the last one). errors: true also yields the
255
+ # error replies (Reply#error?). depth: the replies kept until taken
256
+ # (DEFAULT_GET_DEPTH, 1024); replies past it are dropped, which warns
257
+ # once unless depth: was given. opts: encoding:, priority:,
258
+ # congestion_control:, express:, accept_replies: (Session#get).
259
+ def get(key, timeout: nil, timeout_ms: nil, params: nil, payload: nil, attachment: nil, target: :all,
260
+ consolidation: :none, errors: false, depth: nil, **opts, &blk)
261
+ ms = ::Asterism.time_ms("Connection#get", timeout, timeout_ms, nil, 2000)
262
+ opts[:depth] = depth unless depth.nil?
200
263
  start = lambda do
201
- @session.get(key, Zenoh.ms(timeout, 2000), params, payload,
264
+ @session.get(key, timeout_ms: ms, params: params, payload: payload,
202
265
  attachment: attachment, target: target, consolidation: consolidation, **opts)
203
266
  end
204
- return enum_for(:get_each, start, errors) unless blk
205
- get_each(start, errors, &blk)
267
+ get_run(start, errors, "get #{key}", depth.nil?, &blk)
206
268
  end
207
269
 
208
- # A liveliness token (close it to withdraw).
209
- def liveliness(key)
210
- @session.liveliness(key)
270
+ # A liveliness token (close it to withdraw). With a block: the token
271
+ # is withdrawn after the block (returns the block's value).
272
+ def liveliness(key, &blk)
273
+ Zenoh.scoped(@session.liveliness(key), &blk)
211
274
  end
212
275
 
213
276
  # With a block: yields key and alive (true when the token appeared) on
214
277
  # the receiving thread for each change; the tokens alive now come
215
- # first. Without: a Watch whose each yields Liveliness values.
216
- def liveliness_watch(key, depth: 16, &blk)
217
- w = Watch.new(self, @session.liveliness_watch(key, depth), key)
278
+ # first, in one burst. Without: a Watch whose each yields Liveliness
279
+ # values. depth: DEFAULT_WATCH_DEPTH (1024); changes past it are
280
+ # dropped (Watch#dropped), which warns once unless depth: was given.
281
+ def liveliness_watch(key, depth: nil, &blk)
282
+ d = depth.nil? ? DEFAULT_WATCH_DEPTH : depth
283
+ w = Watch.new(self, @session.liveliness_watch(key, depth: d), key, warn: depth.nil?)
218
284
  w.handle(&blk) if blk
219
285
  w
220
286
  end
221
287
 
222
- # The keys of the liveliness tokens alive now (an Array; waits for
223
- # the answers, at most timeout seconds).
224
- def liveliness_get(key, timeout: 2.0)
225
- g = @session.liveliness_get(key, Zenoh.ms(timeout, 2000))
226
- keys = []
288
+ # The keys of the liveliness tokens alive now (a KeyList, an Array
289
+ # with dropped and received; waits for the answers, at most timeout
290
+ # seconds, or timeout_ms:). depth: DEFAULT_GET_DEPTH (1024); keys
291
+ # past it are dropped, which warns once unless depth: was given.
292
+ def liveliness_get(key, timeout: nil, timeout_ms: nil, depth: nil)
293
+ ms = ::Asterism.time_ms("Connection#liveliness_get", timeout, timeout_ms, nil, 2000)
294
+ g = @session.liveliness_get(key, timeout_ms: ms, depth: depth)
295
+ keys = KeyList.new
227
296
  loop do
297
+ ended = g.done? # before taking the replies (see get_each)
228
298
  g.each_reply.each { |r| keys << r[0] }
229
- break if g.done?
299
+ break if ended && g.pending == 0
230
300
  sleep(ENUM_STEP)
231
301
  end
232
- g.each_reply.each { |r| keys << r[0] }
233
- keys
302
+ Zenoh.note_dropped(g, g, "liveliness_get #{key}", "keys") if depth.nil?
303
+ keys.finish(g)
234
304
  end
235
305
 
236
306
  # Receives on a thread of its own (blocks run there). Returns self.
@@ -274,8 +344,15 @@ module Asterism
274
344
  @session.zid
275
345
  end
276
346
 
347
+ # The routers (client) or peers (peer mode) connected now.
348
+ def connection_count
349
+ @session.connection_count
350
+ end
351
+
352
+ # Deprecated: connection_count.
277
353
  def peers
278
- @session.peers
354
+ ::Asterism.deprecated("Connection#peers", "Connection#connection_count")
355
+ @session.connection_count
279
356
  end
280
357
 
281
358
  # The Zenoh IDs of the peers / routers connected now.
@@ -306,10 +383,19 @@ module Asterism
306
383
 
307
384
  private
308
385
 
386
+ # The block form (get_each, the count) or a GetEnumerator over it.
387
+ def get_run(start, errors, what, warn, &blk)
388
+ return get_each(start, errors, what, warn, nil, &blk) if blk
389
+ e = nil
390
+ e = GetEnumerator.new { |y| get_each(start, errors, what, warn, e) { |r| y << r } }
391
+ end
392
+
309
393
  # Sends the get (start) and yields each Reply; error replies only
310
394
  # 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)
395
+ # done, every reply it had is already in the queue. Warns once when
396
+ # replies were dropped (warn: depth left at the default); stats (a
397
+ # GetEnumerator or nil) gets the counts.
398
+ def get_each(start, errors, what = "get", warn = true, stats = nil)
313
399
  g = start.call
314
400
  n = 0
315
401
  loop do
@@ -323,23 +409,28 @@ module Asterism
323
409
  break if done && g.pending == 0
324
410
  sleep(ENUM_STEP) if got.empty?
325
411
  end
412
+ Zenoh.note_dropped(g, g, what, "replies") if warn
413
+ stats&.finish(g)
326
414
  n
327
415
  end
328
416
 
329
417
  # A subscription of a Connection. Either its block gets the samples
330
418
  # (on the receiving thread), or the application takes them: each (an
331
419
  # Enumerator that waits for samples) or each_pending (what is there
332
- # now, as the polled API).
420
+ # now, as the portable API).
333
421
  class Subscription
334
422
  include Enumerable
335
423
 
336
424
  attr_reader :key, :subscriber
337
425
 
338
- def initialize(conn, subscriber, key)
426
+ # warn: say once when samples were dropped (the depth was left at
427
+ # the default).
428
+ def initialize(conn, subscriber, key, warn: false)
339
429
  @conn = conn
340
430
  @subscriber = subscriber
341
431
  @key = key
342
432
  @task = nil
433
+ @warn = warn
343
434
  end
344
435
 
345
436
  def handle(&blk)
@@ -349,29 +440,48 @@ module Asterism
349
440
  @task = r.add do
350
441
  got = @subscriber.each_sample
351
442
  got.each { |s| r.guard(where) { blk.call(s) } }
443
+ note_dropped unless got.empty?
352
444
  got.size
353
445
  end
354
446
  self
355
447
  end
356
448
 
449
+ # @api private
450
+ def note_dropped
451
+ @warn && Zenoh.note_dropped(self, @subscriber, "subscribe #{@key}", "samples")
452
+ end
453
+
357
454
  # Yields each Sample as it comes, waiting for the next; ends when the
358
455
  # subscription or the session closes, or after timeout seconds.
359
456
  # Without a block, an Enumerator (each.lazy.map { ... }.first(3)).
360
457
  def each(timeout: nil, &blk)
361
458
  return enum_for(:each, timeout: timeout) unless blk
362
459
  raise ::Asterism::Error, "this subscription has a block; its samples go there" if @task
363
- Zenoh.drain(@subscriber, @conn.session, timeout, take: :each_sample) { |s| blk.call(s) }
460
+ Zenoh.drain(@subscriber, @conn.session, timeout, take: :each_sample) do |s|
461
+ note_dropped
462
+ blk.call(s)
463
+ end
364
464
  self
365
465
  end
366
466
 
367
- # The samples there now as Samples (an Array), or yields them.
368
- def each_pending
467
+ # The samples there now as Samples (an Array), or yields them and
468
+ # returns their number. (The portable API's each_sample.)
469
+ def each_sample
369
470
  got = @subscriber.each_sample
471
+ note_dropped unless got.empty?
370
472
  return got unless block_given?
371
473
  got.each { |s| yield s }
372
474
  got.size
373
475
  end
374
476
 
477
+ # Deprecated: each_sample. In the portable API each_pending gives
478
+ # [key, payload, attachment] Arrays; here it gave Samples, so one
479
+ # name had two shapes.
480
+ def each_pending(&blk)
481
+ ::Asterism.deprecated("Connection::Subscription#each_pending", "each_sample")
482
+ each_sample(&blk)
483
+ end
484
+
375
485
  def pending = @subscriber.pending
376
486
  def received = @subscriber.received
377
487
  def dropped = @subscriber.dropped
@@ -455,11 +565,14 @@ module Asterism
455
565
 
456
566
  attr_reader :key, :watch
457
567
 
458
- def initialize(conn, watch, key)
568
+ # warn: say once when changes were dropped (the depth was left at
569
+ # the default).
570
+ def initialize(conn, watch, key, warn: false)
459
571
  @conn = conn
460
572
  @watch = watch
461
573
  @key = key
462
574
  @task = nil
575
+ @warn = warn
463
576
  end
464
577
 
465
578
  def handle(&blk)
@@ -469,6 +582,7 @@ module Asterism
469
582
  @task = r.add do
470
583
  got = @watch.each_pending
471
584
  got.each { |e| r.guard(where) { blk.call(e[0], e[1]) } }
585
+ note_dropped unless got.empty?
472
586
  got.size
473
587
  end
474
588
  self
@@ -479,10 +593,24 @@ module Asterism
479
593
  def each(timeout: nil, &blk)
480
594
  return enum_for(:each, timeout: timeout) unless blk
481
595
  raise ::Asterism::Error, "this watch has a block; its changes go there" if @task
482
- Zenoh.drain(@watch, @conn.session, timeout) { |e| blk.call(Liveliness.new(e[0], e[1])) }
596
+ Zenoh.drain(@watch, @conn.session, timeout) do |e|
597
+ note_dropped
598
+ blk.call(Liveliness.new(e[0], e[1]))
599
+ end
483
600
  self
484
601
  end
485
602
 
603
+ # @api private
604
+ def note_dropped
605
+ @warn && Zenoh.note_dropped(self, @watch, "liveliness_watch #{@key}", "changes")
606
+ end
607
+
608
+ # Changes waiting / received so far / dropped because the queue was
609
+ # full (the oldest go; the watch no longer knows which tokens are
610
+ # alive, so declare it again).
611
+ def pending = @watch.pending
612
+ def received = @watch.received
613
+ def dropped = @watch.dropped
486
614
  def closed? = @watch.closed?
487
615
 
488
616
  def close
@@ -554,11 +682,13 @@ module Asterism
554
682
 
555
683
  # Yields each Reply until every answer came or the querier's time
556
684
  # 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)
685
+ # replies. depth: as Connection#get. Without a block, a
686
+ # GetEnumerator.
687
+ def get(params: nil, payload: nil, attachment: nil, encoding: nil, errors: false, depth: nil, &blk)
688
+ opts = { attachment: attachment, encoding: encoding }
689
+ opts[:depth] = depth unless depth.nil?
690
+ start = -> { @querier.get(params, payload, **opts) }
691
+ @conn.send(:get_run, start, errors, "querier get #{@key}", depth.nil?, &blk)
562
692
  end
563
693
 
564
694
  def matching? = @querier.matching?
@@ -1,5 +1,5 @@
1
1
  module Asterism
2
2
  # The gem's version (the CRuby gem asterism; it depends on asterism-zenoh
3
3
  # of the same version).
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.1"
5
5
  end
data/lib/asterism.rb CHANGED
@@ -16,8 +16,8 @@ require_relative "asterism/version"
16
16
  require_relative "../mrblib/#{f}"
17
17
  end
18
18
  require_relative "asterism/cruby"
19
- # The Ruby-like API on top (blocks, a receiving thread, Enumerators,
20
- # pattern matching): CRuby only, see README "The Ruby-like API".
19
+ # The CRuby API on top (blocks, a receiving thread, Enumerators,
20
+ # pattern matching): CRuby only, see README "The CRuby API".
21
21
  %w[runner zenoh objects ros].each do |f|
22
22
  require_relative "asterism/cruby/#{f}"
23
23
  end