asterism 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +189 -13
- data/lib/asterism/cruby/objects.rb +224 -0
- data/lib/asterism/cruby/ros.rb +305 -0
- data/lib/asterism/cruby/runner.rb +253 -0
- data/lib/asterism/cruby/zenoh.rb +422 -0
- data/lib/asterism/cruby.rb +6 -0
- data/lib/asterism/version.rb +1 -1
- data/lib/asterism.rb +5 -0
- data/mrblib/asterism.rb +81 -9
- data/mrblib/future.rb +6 -1
- data/mrblib/ros.rb +182 -12
- metadata +7 -3
data/mrblib/ros.rb
CHANGED
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
# add = Asterism::ROS.require_type("example_interfaces/srv/AddTwoInts")
|
|
12
12
|
# node.service("/add_two_ints_fmrb", add) { |req| { sum: req.a + req.b } }
|
|
13
13
|
# cli = node.client("/add_two_ints", add)
|
|
14
|
+
# node.subscribe("/cmd", "std_msgs/msg/String") { |msg, info| puts msg.data }
|
|
15
|
+
# node.every(1.0) { pub << { data: "tick" } }
|
|
14
16
|
# loop do
|
|
15
|
-
# node.poll # session.poll
|
|
17
|
+
# node.poll # session.poll, services, then the
|
|
18
|
+
# # subscribe and every blocks
|
|
16
19
|
# pub << { data: "hello" } # or pub.publish(str.new(data: "hello"))
|
|
17
20
|
# sub.each_pending { |msg, info| puts msg.data } # info: Attachment or nil
|
|
18
21
|
# end
|
|
@@ -140,6 +143,11 @@ module Asterism
|
|
|
140
143
|
@gid = gid
|
|
141
144
|
end
|
|
142
145
|
|
|
146
|
+
# For pattern matching: case info in {sequence:, gid:}.
|
|
147
|
+
def deconstruct_keys(_keys)
|
|
148
|
+
{ sequence: @sequence, stamp_ns: @stamp_ns, gid: @gid }
|
|
149
|
+
end
|
|
150
|
+
|
|
143
151
|
def encode
|
|
144
152
|
g = @gid.to_s
|
|
145
153
|
::Asterism::CDR.le_bytes(@sequence, 8) + ::Asterism::CDR.le_bytes(@stamp_ns, 8) +
|
|
@@ -290,6 +298,20 @@ module Asterism
|
|
|
290
298
|
end
|
|
291
299
|
end
|
|
292
300
|
|
|
301
|
+
# For pattern matching: case msg in {linear: {x:}, angular: {z:}}.
|
|
302
|
+
# Nested messages stay messages (they match the same way).
|
|
303
|
+
def deconstruct_keys(keys)
|
|
304
|
+
h = {}
|
|
305
|
+
fs = self.class::FIELDS
|
|
306
|
+
i = 0
|
|
307
|
+
while i < fs.size
|
|
308
|
+
f = fs[i]
|
|
309
|
+
h[f] = __send__(f) if keys.nil? || keys.include?(f)
|
|
310
|
+
i += 1
|
|
311
|
+
end
|
|
312
|
+
h
|
|
313
|
+
end
|
|
314
|
+
|
|
293
315
|
def ==(other)
|
|
294
316
|
other.class == self.class && to_h == other.to_h
|
|
295
317
|
end
|
|
@@ -318,6 +340,8 @@ module Asterism
|
|
|
318
340
|
@next_id = 1
|
|
319
341
|
@entities = []
|
|
320
342
|
@services = []
|
|
343
|
+
@handled = [] # subscriptions with a block
|
|
344
|
+
@timers = []
|
|
321
345
|
@key = "#{LIVELINESS_ROOT}/#{@domain}/#{@zid}/#{@nid}/#{@nid}/NN/" \
|
|
322
346
|
"#{::Asterism::ROS.mangle(@enclave)}/#{::Asterism::ROS.mangle(@namespace)}/#{@name}"
|
|
323
347
|
@token = session.liveliness(@key)
|
|
@@ -353,6 +377,35 @@ module Asterism
|
|
|
353
377
|
e
|
|
354
378
|
end
|
|
355
379
|
|
|
380
|
+
# Subscribes and gives each message (and its Attachment, or nil) to
|
|
381
|
+
# the block, from node.poll. Returns the Subscription (close ends it).
|
|
382
|
+
# The block runs on the update loop's stack: do not wait in it (no
|
|
383
|
+
# service call, no proxy call); start a call_async and look at it
|
|
384
|
+
# later instead.
|
|
385
|
+
def subscribe(topic, type, qos: DEFAULT_QOS, depth: 16, &handler)
|
|
386
|
+
raise ArgumentError, "subscribe needs a block (or use subscription)" unless handler
|
|
387
|
+
e = subscription(topic, type, qos: qos, depth: depth)
|
|
388
|
+
e.handler = handler
|
|
389
|
+
@handled << e
|
|
390
|
+
e
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# Calls the block every `seconds` (the first time one period from now),
|
|
394
|
+
# from node.poll. Returns a Timer (cancel). It keeps the period; after
|
|
395
|
+
# a long stall it starts again from now instead of firing the missed
|
|
396
|
+
# times at once. As for subscribe: do not wait in the block.
|
|
397
|
+
def every(seconds, &blk)
|
|
398
|
+
raise ArgumentError, "every needs a block" unless blk
|
|
399
|
+
t = ::Asterism::ROS::Timer.new(self, seconds, blk)
|
|
400
|
+
@timers << t
|
|
401
|
+
t
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def cancel_timer(timer)
|
|
405
|
+
@timers.delete(timer)
|
|
406
|
+
nil
|
|
407
|
+
end
|
|
408
|
+
|
|
356
409
|
# node.call("/add_two_ints", AddTwoInts, a: 1, b: 2) -> response.
|
|
357
410
|
# A client per service name is made on first use and kept.
|
|
358
411
|
def call(service, type, request = nil, timeout_ms: ::Asterism::ROS::Client::DEFAULT_TIMEOUT_MS, **fields)
|
|
@@ -365,21 +418,85 @@ module Asterism
|
|
|
365
418
|
c.call(request, timeout_ms: timeout_ms, **fields)
|
|
366
419
|
end
|
|
367
420
|
|
|
368
|
-
# Polls the session (Asterism::Zenoh::Session#poll)
|
|
369
|
-
# requests that came in for this node's services
|
|
370
|
-
#
|
|
421
|
+
# Polls the session (Asterism::Zenoh::Session#poll), answers the
|
|
422
|
+
# requests that came in for this node's services, then gives the
|
|
423
|
+
# messages that came in to the subscribe blocks and fires the timers
|
|
424
|
+
# that are due. Returns what session.poll returns (false once the
|
|
425
|
+
# session is closed).
|
|
371
426
|
def poll(steps = 8)
|
|
427
|
+
ok = pump(steps)
|
|
428
|
+
deliver
|
|
429
|
+
fire_timers
|
|
430
|
+
ok
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
# What a waiting service call polls: the session and the services, not
|
|
434
|
+
# the subscribe / every blocks (those run from node.poll only).
|
|
435
|
+
def pump(steps = 8)
|
|
372
436
|
ok = @session.poll(steps)
|
|
373
|
-
|
|
437
|
+
i = 0
|
|
438
|
+
while i < @services.size
|
|
439
|
+
@services[i].handle_pending
|
|
440
|
+
i += 1
|
|
441
|
+
end
|
|
374
442
|
ok
|
|
375
443
|
end
|
|
376
444
|
|
|
445
|
+
# While loops, not each with a block: the application's blocks are
|
|
446
|
+
# called from here, and nothing else should stand between them and
|
|
447
|
+
# the update loop on the stack.
|
|
448
|
+
def deliver
|
|
449
|
+
i = 0
|
|
450
|
+
while i < @handled.size
|
|
451
|
+
sub = @handled[i]
|
|
452
|
+
if sub.closed?
|
|
453
|
+
@handled.delete_at(i)
|
|
454
|
+
next
|
|
455
|
+
end
|
|
456
|
+
got = sub.each_pending
|
|
457
|
+
j = 0
|
|
458
|
+
while j < got.size
|
|
459
|
+
handle(sub, got[j][0], got[j][1])
|
|
460
|
+
j += 1
|
|
461
|
+
end
|
|
462
|
+
i += 1
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
def fire_timers
|
|
467
|
+
return if @timers.empty?
|
|
468
|
+
now = ::Asterism::ROS.now_ms
|
|
469
|
+
due = []
|
|
470
|
+
i = 0
|
|
471
|
+
while i < @timers.size
|
|
472
|
+
due << @timers[i] if @timers[i].due?(now)
|
|
473
|
+
i += 1
|
|
474
|
+
end
|
|
475
|
+
i = 0
|
|
476
|
+
while i < due.size
|
|
477
|
+
fire(due[i], now)
|
|
478
|
+
i += 1
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
# One message for one subscribe block, one timer firing (CRuby routes
|
|
483
|
+
# what they raise to on_error).
|
|
484
|
+
def handle(sub, msg, info)
|
|
485
|
+
sub.handler.call(msg, info)
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
def fire(timer, now)
|
|
489
|
+
timer.fire(now)
|
|
490
|
+
end
|
|
491
|
+
|
|
377
492
|
# Withdraws the node and its publishers, subscriptions, services and
|
|
378
493
|
# clients. Idempotent.
|
|
379
494
|
def close
|
|
380
495
|
@entities.each { |e| e.close }
|
|
381
496
|
@entities = []
|
|
382
497
|
@services = []
|
|
498
|
+
@handled = []
|
|
499
|
+
@timers = []
|
|
383
500
|
@clients = {}
|
|
384
501
|
@token.close if @token
|
|
385
502
|
@token = nil
|
|
@@ -419,8 +536,10 @@ module Asterism
|
|
|
419
536
|
# take their defaults).
|
|
420
537
|
def publish(msg)
|
|
421
538
|
raise ::Asterism::Zenoh::Error, "publisher closed" if @token.nil?
|
|
422
|
-
|
|
423
|
-
|
|
539
|
+
# Read once (a second thread may publish meanwhile on CRuby).
|
|
540
|
+
seq = @sequence + 1
|
|
541
|
+
@sequence = seq
|
|
542
|
+
att = ::Asterism::ROS::Attachment.new(seq, ::Asterism::ROS.now_ns, @gid)
|
|
424
543
|
@session.put(@topic_key, @type.encode(msg), attachment: att.encode)
|
|
425
544
|
nil
|
|
426
545
|
end
|
|
@@ -440,6 +559,8 @@ module Asterism
|
|
|
440
559
|
|
|
441
560
|
class Subscription
|
|
442
561
|
attr_reader :topic_key, :token_key, :errors
|
|
562
|
+
# The block of node.subscribe (nil for node.subscription).
|
|
563
|
+
attr_accessor :handler
|
|
443
564
|
|
|
444
565
|
def initialize(node, session, topic, type, qos, depth)
|
|
445
566
|
@type = type
|
|
@@ -480,6 +601,47 @@ module Asterism
|
|
|
480
601
|
@token = nil
|
|
481
602
|
nil
|
|
482
603
|
end
|
|
604
|
+
|
|
605
|
+
def closed?
|
|
606
|
+
@token.nil?
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
# node.every: a block called every period, from node.poll.
|
|
611
|
+
class Timer
|
|
612
|
+
# period: seconds as given; fired: how many times it ran.
|
|
613
|
+
attr_reader :period, :fired
|
|
614
|
+
|
|
615
|
+
def initialize(node, seconds, blk)
|
|
616
|
+
@period_ms = (seconds * 1000).to_i
|
|
617
|
+
raise ArgumentError, "the period must be positive" unless @period_ms > 0
|
|
618
|
+
@node = node
|
|
619
|
+
@period = seconds
|
|
620
|
+
@blk = blk
|
|
621
|
+
@next = ::Asterism::ROS.now_ms + @period_ms
|
|
622
|
+
@fired = 0
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
def due?(now_ms)
|
|
626
|
+
now_ms >= @next
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
# Keeps the period without drifting; after a long stall it starts
|
|
630
|
+
# again from now instead of firing the missed times at once.
|
|
631
|
+
def fire(now_ms)
|
|
632
|
+
@next += @period_ms
|
|
633
|
+
@next = now_ms + @period_ms if @next <= now_ms
|
|
634
|
+
@fired += 1
|
|
635
|
+
@blk.call
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
def cancel
|
|
639
|
+
@node.cancel_timer(self)
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
def inspect
|
|
643
|
+
"#<Asterism::ROS::Timer every #{@period} s>"
|
|
644
|
+
end
|
|
483
645
|
end
|
|
484
646
|
end
|
|
485
647
|
end
|
|
@@ -600,6 +762,8 @@ module Asterism
|
|
|
600
762
|
def collect
|
|
601
763
|
return if @finished
|
|
602
764
|
g = @get
|
|
765
|
+
# done? before taking the replies, as in Asterism::Future#collect.
|
|
766
|
+
ended = g.done?
|
|
603
767
|
# The Array form, as in Asterism::Future (no block called from C).
|
|
604
768
|
g.each_reply.each do |r|
|
|
605
769
|
next unless @response.nil?
|
|
@@ -613,7 +777,7 @@ module Asterism
|
|
|
613
777
|
end
|
|
614
778
|
end
|
|
615
779
|
now = ::Asterism::ROS.now_ms
|
|
616
|
-
if !@response.nil? ||
|
|
780
|
+
if !@response.nil? || ended || now >= @deadline
|
|
617
781
|
@nobody = @response.nil? && now < @deadline
|
|
618
782
|
@finished = true
|
|
619
783
|
@took_ms = now - @started
|
|
@@ -647,11 +811,15 @@ module Asterism
|
|
|
647
811
|
raise ::Asterism::Zenoh::Error, "client closed" if @token.nil?
|
|
648
812
|
req = request.nil? ? fields : request
|
|
649
813
|
payload = @type::Request.encode(req)
|
|
650
|
-
|
|
651
|
-
|
|
814
|
+
# Read once into a local: the request and its Call carry the same
|
|
815
|
+
# number even when another thread (CRuby) calls through this client
|
|
816
|
+
# meanwhile.
|
|
817
|
+
seq = @sequence + 1
|
|
818
|
+
@sequence = seq
|
|
819
|
+
att = ::Asterism::ROS::Attachment.new(seq, ::Asterism::ROS.now_ns, @gid)
|
|
652
820
|
g = @session.get(@service_key, timeout_ms, nil, payload, attachment: att.encode,
|
|
653
821
|
target: :all_complete, consolidation: :none)
|
|
654
|
-
::Asterism::ROS::Call.new(self, g,
|
|
822
|
+
::Asterism::ROS::Call.new(self, g, seq, timeout_ms)
|
|
655
823
|
end
|
|
656
824
|
|
|
657
825
|
# Sends the request and waits for the response (polling the node, so
|
|
@@ -661,9 +829,11 @@ module Asterism
|
|
|
661
829
|
call_async(request, timeout_ms: timeout_ms, **fields).value
|
|
662
830
|
end
|
|
663
831
|
|
|
832
|
+
# Polls the node's session and services (not its subscribe / every
|
|
833
|
+
# blocks) until the call is done.
|
|
664
834
|
def wait_for(c)
|
|
665
835
|
until c.done?
|
|
666
|
-
break unless @node.
|
|
836
|
+
break unless @node.pump
|
|
667
837
|
break if c.done?
|
|
668
838
|
::Asterism::ROS.pause(WAIT_STEP_MS)
|
|
669
839
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: asterism
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Katsuhiko Kageyama
|
|
@@ -16,14 +16,14 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: 0.
|
|
19
|
+
version: 0.2.0
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: 0.
|
|
26
|
+
version: 0.2.0
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: msgpack
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -115,6 +115,10 @@ files:
|
|
|
115
115
|
- data/msgs/std_msgs/msg/UInt8MultiArray.rb
|
|
116
116
|
- lib/asterism.rb
|
|
117
117
|
- lib/asterism/cruby.rb
|
|
118
|
+
- lib/asterism/cruby/objects.rb
|
|
119
|
+
- lib/asterism/cruby/ros.rb
|
|
120
|
+
- lib/asterism/cruby/runner.rb
|
|
121
|
+
- lib/asterism/cruby/zenoh.rb
|
|
118
122
|
- lib/asterism/version.rb
|
|
119
123
|
- mrblib/asterism.rb
|
|
120
124
|
- mrblib/cdr.rb
|