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
data/mrblib/asterism.rb CHANGED
@@ -3,7 +3,8 @@
3
3
  # Asterism.connect("tcp/192.0.2.2:7447", node: "fmruby-bbbbbb", app: "demo")
4
4
  # Asterism.expose("apu", apu, methods: [:play, :stop])
5
5
  # p = Asterism["linux/demo/apu"] # <node>/<app>/<object>
6
- # p.play("cde") # waits for the answer (2 s by default)
6
+ # p.play("cde") # waits for the answer (2 s by default;
7
+ # # Asterism["...", timeout: 5.0])
7
8
  # Asterism.poll # call regularly (answers incoming calls)
8
9
  #
9
10
  # Keys (doc/ruby_asterism/design.md ch. 4):
@@ -19,13 +20,21 @@
19
20
  # that come in, so two machines calling each other do not lock up.
20
21
  module Asterism
21
22
  ROOT = "asterism"
23
+ # The time limit of a call (Asterism[path] without timeout:), in seconds
24
+ # and in milliseconds.
25
+ DEFAULT_TIMEOUT = 2.0
22
26
  DEFAULT_TIMEOUT_MS = 2000
27
+ # How long Asterism.connect looks for another copy of the same
28
+ # <node>/<app> (check_timeout:), in seconds.
29
+ CHECK_TIMEOUT = 1.5
23
30
  # Waits that may be open inside each other (a call answered while waiting
24
31
  # that itself calls and waits, and so on).
25
32
  MAX_NESTING = 4
26
33
  # Pause between polls while a call waits (ms).
27
34
  WAIT_STEP_MS = 2
28
35
 
36
+ # Every Asterism error is an Asterism::Error (the Zenoh bindings define
37
+ # it; Asterism::Zenoh::Error and the ROS errors are under it too).
29
38
  class Error < StandardError; end
30
39
 
31
40
  # A value that MessagePack cannot carry (only nil, true, false, Integer,
@@ -33,7 +42,9 @@ module Asterism
33
42
  class EncodeError < Error; end
34
43
 
35
44
  # No answer within the time limit (the object is gone, or slow).
36
- class Timeout < Error; end
45
+ # Asterism::ROS::TimeoutError is one too. (Its old name Asterism::Timeout
46
+ # still works, with a deprecation warning.)
47
+ class TimeoutError < Error; end
37
48
 
38
49
  # The connection is closed or was lost (Asterism::Zenoh::Error inside).
39
50
  class Disconnected < Error; end
@@ -50,6 +61,17 @@ module Asterism
50
61
  end
51
62
  end
52
63
 
64
+ # The old names of renamed constants: Asterism::Timeout (deprecated) is
65
+ # TimeoutError. A constant alias would shadow Ruby's Timeout module inside
66
+ # `module Asterism`, and cannot warn.
67
+ def self.const_missing(name)
68
+ if name == :Timeout
69
+ deprecated("Asterism::Timeout", "Asterism::TimeoutError")
70
+ return TimeoutError
71
+ end
72
+ super
73
+ end
74
+
53
75
  # ------------------------------------------------------------ values
54
76
 
55
77
  module Codec
@@ -91,6 +113,7 @@ module Asterism
91
113
 
92
114
  # Milliseconds and a short pause. PicoRuby's Machine when it is there,
93
115
  # otherwise Time and sleep.
116
+ # @api private
94
117
  def self.now_ms
95
118
  if machine?
96
119
  ::Machine.board_millis
@@ -99,6 +122,7 @@ module Asterism
99
122
  end
100
123
  end
101
124
 
125
+ # @api private
102
126
  def self.pause(ms)
103
127
  if machine?
104
128
  ::Machine.delay_ms(ms)
@@ -107,6 +131,7 @@ module Asterism
107
131
  end
108
132
  end
109
133
 
134
+ # @api private
110
135
  def self.machine?
111
136
  @machine = (Object.const_defined?(:Machine) && ::Machine.respond_to?(:delay_ms)) if @machine.nil?
112
137
  @machine
@@ -116,6 +141,7 @@ module Asterism
116
141
 
117
142
  # A node, app or object name: one key chunk, no wildcard or special
118
143
  # characters.
144
+ # @api private
119
145
  def self.check_name(what, name)
120
146
  s = name.to_s
121
147
  bad = s.empty? || s.start_with?("@")
@@ -125,6 +151,7 @@ module Asterism
125
151
  end
126
152
 
127
153
  # "node/app/object" -> [node, app, object]
154
+ # @api private
128
155
  def self.split_path(path)
129
156
  parts = path.to_s.split("/")
130
157
  raise ArgumentError, "expected <node>/<app>/<object>, got #{path.inspect}" unless parts.size == 3
@@ -135,10 +162,12 @@ module Asterism
135
162
  end
136
163
 
137
164
  # Glob over "/"-separated chunks: * is one chunk, ** any number of them.
165
+ # @api private
138
166
  def self.glob?(pattern, path)
139
167
  glob_parts?(pattern.split("/"), 0, path.split("/"), 0)
140
168
  end
141
169
 
170
+ # @api private
142
171
  def self.glob_parts?(pat, i, parts, j)
143
172
  while i < pat.size
144
173
  if pat[i] == "**"
@@ -161,27 +190,37 @@ module Asterism
161
190
 
162
191
  # Opens the connection. locator, mode: and listen: go to
163
192
  # Asterism::Zenoh::Session.open (client of a router by default; mode: :peer
164
- # with or without listen: for no router). node: is this machine's ID and
165
- # app: this application's name. Raises Disconnected when it cannot connect,
166
- # and Error when the same <node>/<app> is already on the network.
167
- def self.connect(locator, node:, app:, mode: nil, listen: nil)
193
+ # with or without listen: for no router), and so do config: (a Hash of
194
+ # "key/path" => value or a JSON5 String, e.g. TLS certificates) and
195
+ # connect_timeout: (seconds) when they are given (CRuby's asterism-zenoh
196
+ # only; the boards raise ArgumentError). node: is this machine's ID and
197
+ # app: this application's name. check_timeout: how long to look for
198
+ # another copy of the same <node>/<app> (seconds, CHECK_TIMEOUT). Raises
199
+ # Disconnected when it cannot connect, and Error when the same
200
+ # <node>/<app> is already on the network.
201
+ def self.connect(locator, node:, app:, mode: nil, listen: nil, config: nil, connect_timeout: nil,
202
+ check_timeout: nil)
168
203
  raise Error, "already connected (Asterism.close first)" if connected?
169
204
  release
170
205
  node = check_name("node", node)
171
206
  app = check_name("app", app)
207
+ check_ms = time_ms("Asterism.connect check_timeout:", check_timeout, nil, nil, (CHECK_TIMEOUT * 1000).round)
208
+ opts = { mode: mode, listen: listen }
209
+ opts[:config] = config unless config.nil?
210
+ opts[:connect_timeout] = connect_timeout unless connect_timeout.nil?
172
211
  begin
173
- s = Asterism::Zenoh::Session.open(locator, mode: mode, listen: listen)
212
+ s = Asterism::Zenoh::Session.open(locator, **opts)
174
213
  rescue Asterism::Zenoh::Error => e
175
214
  raise Disconnected, e.message
176
215
  end
177
216
  begin
178
- if already_there?(s, node, app)
217
+ if already_there?(s, node, app, check_ms)
179
218
  s.close
180
219
  raise Error, "#{node}/#{app} is already on the network"
181
220
  end
182
221
  @node_token = s.liveliness("#{ROOT}/#{node}")
183
- @queryable = s.queryable("#{ROOT}/#{node}/#{app}/**", 32)
184
- @watch = s.liveliness_watch("#{ROOT}/**", 64)
222
+ @queryable = s.queryable("#{ROOT}/#{node}/#{app}/**", depth: 32)
223
+ @watch = s.liveliness_watch("#{ROOT}/**", depth: watch_depth)
185
224
  rescue Asterism::Zenoh::Error => e
186
225
  s.close
187
226
  raise Disconnected, e.message
@@ -200,16 +239,18 @@ module Asterism
200
239
  end
201
240
 
202
241
  # Whether objects of node/app are alive already (a second copy of the
203
- # same application). Waits for the answer (a router answers at once).
204
- def self.already_there?(s, node, app)
205
- g = s.liveliness_get("#{ROOT}/#{node}/#{app}/**", 1000)
242
+ # same application). Waits for the answer (a router answers at once), at
243
+ # most check_ms. @api private
244
+ def self.already_there?(s, node, app, check_ms = 1500)
245
+ get_ms = check_ms > 1000 ? check_ms - 500 : check_ms
246
+ g = s.liveliness_get("#{ROOT}/#{node}/#{app}/**", timeout_ms: get_ms)
206
247
  t0 = now_ms
207
248
  found = false
208
249
  while true
209
250
  s.poll
210
251
  ended = g.done? # before taking the replies (see Future#collect)
211
252
  found = true if g.each_reply.size > 0
212
- break if found || ended || now_ms - t0 > 1500
253
+ break if found || ended || now_ms - t0 > check_ms
213
254
  pause(WAIT_STEP_MS)
214
255
  end
215
256
  found
@@ -247,6 +288,7 @@ module Asterism
247
288
  nil
248
289
  end
249
290
 
291
+ # @api private
250
292
  def self.release
251
293
  s = @session
252
294
  @session = nil
@@ -260,6 +302,7 @@ module Asterism
260
302
  @watch = nil
261
303
  end
262
304
 
305
+ # @api private
263
306
  def self.session!
264
307
  raise Disconnected, (@lost || "not connected") unless @session
265
308
  @session
@@ -267,6 +310,7 @@ module Asterism
267
310
 
268
311
  # The connection went away: closed, but the next Asterism.poll still tells
269
312
  # on_leave about every node known until then.
313
+ # @api private
270
314
  def self.lost(reason)
271
315
  @lost = reason
272
316
  release
@@ -341,8 +385,22 @@ module Asterism
341
385
  self
342
386
  end
343
387
 
388
+ # Removes a block given to on_join / on_leave (keep the Proc to remove
389
+ # it: h = proc { |n| ... }; Asterism.on_join(&h); Asterism.off_join(h)).
390
+ # on_* adds a block; on_error (CRuby) is the only one that replaces.
391
+ def self.off_join(blk)
392
+ @on_join.delete(blk) if @on_join
393
+ self
394
+ end
395
+
396
+ def self.off_leave(blk)
397
+ @on_leave.delete(blk) if @on_leave
398
+ self
399
+ end
400
+
344
401
  # Compares the nodes alive now with those told before and calls the
345
402
  # blocks for the difference.
403
+ # @api private
346
404
  def self.tell_nodes
347
405
  known = @known
348
406
  return if known.nil?
@@ -360,6 +418,7 @@ module Asterism
360
418
 
361
419
  # While loops, not each with a block: the blocks are the application's,
362
420
  # and nothing else stands between them and the update loop on the stack.
421
+ # @api private
363
422
  def self.tell_each(where, blocks, names)
364
423
  return if blocks.nil? || names.empty?
365
424
  i = 0
@@ -374,10 +433,12 @@ module Asterism
374
433
  end
375
434
 
376
435
  # One block for one node (CRuby routes what it raises to on_error).
436
+ # @api private
377
437
  def self.tell(_where, blk, node)
378
438
  blk.call(node)
379
439
  end
380
440
 
441
+ # @api private
381
442
  def self.pump
382
443
  s = @session
383
444
  return false unless s
@@ -394,6 +455,7 @@ module Asterism
394
455
  end
395
456
 
396
457
  # Polls until the block is true. Answers incoming calls meanwhile.
458
+ # @api private
397
459
  def self.wait_until
398
460
  raise Error, "calls nested too deep (max #{MAX_NESTING})" if (@depth || 0) >= MAX_NESTING
399
461
  @depth = (@depth || 0) + 1
@@ -408,15 +470,31 @@ module Asterism
408
470
  end
409
471
  end
410
472
 
473
+ # @api private
411
474
  def self.depth
412
475
  (@depth || 0)
413
476
  end
414
477
 
478
+ # The depth of the liveliness watch behind each / nodes. The tokens alive
479
+ # when it is declared (every node and object on the network) come in one
480
+ # burst: 64 on the boards (the ring comes from the VM's pool), the
481
+ # binding's deeper default where it has one (CRuby: 1024).
482
+ # @api private
483
+ def self.watch_depth
484
+ d = Asterism::Zenoh::DEFAULT_WATCH_DEPTH
485
+ d > 64 ? d : 64
486
+ end
487
+
488
+ # @api private
415
489
  def self.follow_liveliness
416
490
  w = @watch
417
491
  return unless w
418
492
  # Array forms instead of blocks called from C (see Future#collect).
419
493
  events = w.each_pending
494
+ if events.size > 0 && w.dropped > 0
495
+ warn_once(w, "asterism: #{w.dropped} liveliness changes dropped (more than #{watch_depth} at once); " \
496
+ "Asterism.each and Asterism.nodes may miss some objects")
497
+ end
420
498
  i = 0
421
499
  while i < events.size
422
500
  key = events[i][0]
@@ -454,7 +532,9 @@ module Asterism
454
532
 
455
533
  # The exposed objects alive now whose <node>/<app>/<object> matches the
456
534
  # pattern (* is one chunk, ** any number), this application's own
457
- # included. With a block, yields a proxy for each; else returns them.
535
+ # included. With a block, yields a proxy for each and returns their
536
+ # number (1.0 will return the proxies instead, as Ruby's each returns
537
+ # its receiver; use the Array form for the count); else returns them.
458
538
  def self.each(pattern = "**")
459
539
  paths = []
460
540
  if @session
@@ -471,8 +551,12 @@ module Asterism
471
551
  end
472
552
 
473
553
  # A proxy for <node>/<app>/<object>. Nothing is sent until a method is
474
- # called on it.
475
- def self.[](path, timeout_ms = DEFAULT_TIMEOUT_MS)
554
+ # called on it. timeout: the time limit of its calls in seconds (2.0), or
555
+ # timeout_ms:. (Asterism[path, ms], the time as a positional argument, is
556
+ # deprecated.)
557
+ def self.[](path, *args, timeout: nil, timeout_ms: nil)
558
+ raise ArgumentError, "Asterism[]: wrong number of arguments (given #{args.size + 1}, expected 1)" if args.size > 1
559
+ timeout_ms = time_ms("Asterism[]", timeout, timeout_ms, args[0], DEFAULT_TIMEOUT_MS)
476
560
  split_path(path)
477
561
  @proxies ||= {}
478
562
  key = "#{path}|#{timeout_ms}"
@@ -483,6 +567,7 @@ module Asterism
483
567
 
484
568
  # Starts a call; returns a Future. Raises EncodeError (before sending)
485
569
  # and Disconnected.
570
+ # @api private
486
571
  def self.call_async(path, method, args, kwargs, timeout_ms)
487
572
  parts = split_path(path)
488
573
  payload = Codec.pack([method.to_s, args, kwargs])
@@ -492,7 +577,7 @@ module Asterism
492
577
  return Future.answered(path, method, dispatch(parts[2], payload))
493
578
  end
494
579
  begin
495
- g = session!.get("#{ROOT}/#{path}/call", timeout_ms, nil, payload)
580
+ g = session!.get("#{ROOT}/#{path}/call", timeout_ms: timeout_ms, payload: payload)
496
581
  rescue Asterism::Zenoh::Error => e
497
582
  lost(e.message)
498
583
  raise Disconnected, e.message
@@ -500,11 +585,13 @@ module Asterism
500
585
  Future.new(path, method, g, now_ms + timeout_ms, timeout_ms)
501
586
  end
502
587
 
588
+ # @api private
503
589
  def self.call(path, method, args, kwargs, timeout_ms)
504
590
  call_async(path, method, args, kwargs, timeout_ms).value
505
591
  end
506
592
 
507
593
  # The meta reply of path: {"methods" => [[name, arity], ...]}.
594
+ # @api private
508
595
  def self.meta(path, timeout_ms = DEFAULT_TIMEOUT_MS)
509
596
  parts = split_path(path)
510
597
  if parts[0] == @node && parts[1] == @app && @session
@@ -513,7 +600,7 @@ module Asterism
513
600
  return m
514
601
  end
515
602
  begin
516
- g = session!.get("#{ROOT}/#{path}/meta", timeout_ms)
603
+ g = session!.get("#{ROOT}/#{path}/meta", timeout_ms: timeout_ms)
517
604
  rescue Asterism::Zenoh::Error => e
518
605
  lost(e.message)
519
606
  raise Disconnected, e.message
@@ -524,6 +611,7 @@ module Asterism
524
611
 
525
612
  # ------------------------------------------------------------ answering
526
613
 
614
+ # @api private
527
615
  def self.answer_calls
528
616
  qa = @queryable
529
617
  return unless qa
@@ -548,6 +636,7 @@ module Asterism
548
636
 
549
637
  # asterism/<node>/<app>/<object>/<call|meta>; the node and app may come
550
638
  # as wildcards (the query matched this application's queryable).
639
+ # @api private
551
640
  def self.answer(q)
552
641
  parts = q.key.split("/")
553
642
  return unless parts.size == 5
@@ -565,6 +654,7 @@ module Asterism
565
654
  end
566
655
  end
567
656
 
657
+ # @api private
568
658
  def self.meta_of(name)
569
659
  entry = @objects[name]
570
660
  return nil unless entry
@@ -574,6 +664,7 @@ module Asterism
574
664
  end
575
665
 
576
666
  # Runs a call on an exposed object; returns the packed reply.
667
+ # @api private
577
668
  def self.dispatch(obj_name, payload)
578
669
  req = Codec.unpack(payload)
579
670
  unless req.is_a?(Array) && req.size == 3 && req[0].is_a?(String) && req[1].is_a?(Array) && req[2].is_a?(Hash)
@@ -601,6 +692,7 @@ module Asterism
601
692
  end
602
693
  end
603
694
 
695
+ # @api private
604
696
  def self.invoke(obj, name, args, kw)
605
697
  if kw.empty?
606
698
  obj.public_send(name, *args)
@@ -611,6 +703,7 @@ module Asterism
611
703
  end
612
704
  end
613
705
 
706
+ # @api private
614
707
  def self.error_reply(klass, message)
615
708
  ::MessagePack.pack(["error", klass.to_s, message.to_s])
616
709
  end
data/mrblib/cdr.rb CHANGED
@@ -18,9 +18,13 @@
18
18
  # String#unpack when the VM has them (mruby-pack, CRuby) and fall back to
19
19
  # arithmetic (CDR.f64_bytes and friends) when it does not.
20
20
  module Asterism
21
+ # (asterism.rb and the Zenoh binding define it too; this file is also
22
+ # loaded on its own by the type tools.)
23
+ class Error < StandardError; end
24
+
21
25
  module CDR
22
26
  # Malformed or truncated CDR.
23
- class DecodeError < ::StandardError; end
27
+ class DecodeError < ::Asterism::Error; end
24
28
 
25
29
  HEADER_LE = "\x00\x01\x00\x00".freeze
26
30
  HEADER_BE = "\x00\x00\x00\x00".freeze
data/mrblib/future.rb CHANGED
@@ -5,6 +5,7 @@ module Asterism
5
5
  class Future
6
6
  attr_reader :path, :method_name
7
7
 
8
+ # @api private
8
9
  def self.answered(path, method, reply)
9
10
  f = new(path, method, nil, 0, 0)
10
11
  f.set_reply(reply)
@@ -23,6 +24,7 @@ module Asterism
23
24
  @took = nil
24
25
  end
25
26
 
27
+ # @api private
26
28
  def set_reply(reply)
27
29
  @reply = reply
28
30
  @finished = true
@@ -34,6 +36,11 @@ module Asterism
34
36
  @took
35
37
  end
36
38
 
39
+ # The same in seconds (a Float; nil until done).
40
+ def took
41
+ @took.nil? ? nil : @took / 1000.0
42
+ end
43
+
37
44
  # True once the answer came, the time ran out or the connection closed.
38
45
  # Call Asterism.poll in the update loop for it to move on.
39
46
  def done?
@@ -58,15 +65,17 @@ module Asterism
58
65
  if @took && @took < @timeout_ms
59
66
  # Finished early without a reply: nobody answers that key (the
60
67
  # object is not exposed, or its application is gone).
61
- raise ::Asterism::Timeout, "no answer from #{@path} #{@method_name} (nobody answers)"
68
+ raise ::Asterism::TimeoutError, "no answer from #{@path} #{@method_name} (nobody answers)"
62
69
  end
63
- raise ::Asterism::Timeout, "no answer from #{@path} #{@method_name} within #{@timeout_ms} ms"
70
+ raise ::Asterism::TimeoutError,
71
+ "no answer from #{@path} #{@method_name} within #{@timeout_ms / 1000.0} s (#{@timeout_ms} ms)"
64
72
  end
65
73
  raw = ::Asterism::Codec.unpack(@reply)
66
74
  raise ::Asterism::Error, "malformed answer from #{@path}" if raw.nil?
67
75
  raw
68
76
  end
69
77
 
78
+ # @api private
70
79
  def collect
71
80
  return if @finished
72
81
  g = @get
data/mrblib/proxy.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  module Asterism
2
2
  # A stand-in for an object on another machine (Asterism["node/app/obj"]).
3
3
  # Any method it does not have itself becomes a call that waits for the
4
- # answer. respond_to? and methods come from the object's meta.
4
+ # answer. respond_to? and remote_methods come from the object's meta.
5
5
  #
6
6
  # Its own methods (not forwarded): async, asterism_path, asterism_meta,
7
- # asterism_refresh, respond_to?, methods, inspect, to_s and those of Object.
7
+ # asterism_refresh, remote_methods, respond_to?, methods, inspect, to_s
8
+ # and those of Object.
8
9
  class Proxy
9
10
  # Conversions Ruby asks for implicitly (puts, Array#flatten, ...): never
10
11
  # forwarded.
@@ -39,10 +40,18 @@ module Asterism
39
40
 
40
41
  # The exposed methods (Symbols), from the meta. Raises when the object
41
42
  # does not answer.
42
- def methods(*_args)
43
+ def remote_methods
43
44
  remote_names.map { |n| n.to_sym }
44
45
  end
45
46
 
47
+ # Deprecated: returns remote_methods. From 1.0 it is Object#methods
48
+ # again (irb, pp and test doubles rely on it), and remote_methods is the
49
+ # way to list the remote ones.
50
+ def methods(*_args)
51
+ ::Asterism.deprecated("Asterism::Proxy#methods", "Asterism::Proxy#remote_methods")
52
+ remote_methods
53
+ end
54
+
46
55
  # The meta reply, fetched once: {"methods" => [[name, arity], ...]}.
47
56
  def asterism_meta
48
57
  @meta ||= ::Asterism.meta(@path, @timeout_ms)
@@ -54,6 +63,7 @@ module Asterism
54
63
  self
55
64
  end
56
65
 
66
+ # @api private
57
67
  def remote_names
58
68
  list = asterism_meta["methods"]
59
69
  return [] unless list.is_a?(Array)