asterism 0.0.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/LICENSE +21 -0
- data/NOTICE +27 -0
- data/README.md +581 -3
- data/data/msgs/LICENSE-Apache-2.0.txt +202 -0
- data/data/msgs/NOTICE +17 -0
- data/data/msgs/builtin_interfaces/msg/Duration.rb +33 -0
- data/data/msgs/builtin_interfaces/msg/Time.rb +33 -0
- data/data/msgs/example_interfaces/srv/AddTwoInts.rb +64 -0
- data/data/msgs/geometry_msgs/msg/Accel.rb +35 -0
- data/data/msgs/geometry_msgs/msg/AccelStamped.rb +36 -0
- data/data/msgs/geometry_msgs/msg/Point.rb +36 -0
- data/data/msgs/geometry_msgs/msg/Point32.rb +36 -0
- data/data/msgs/geometry_msgs/msg/PointStamped.rb +36 -0
- data/data/msgs/geometry_msgs/msg/Pose.rb +36 -0
- data/data/msgs/geometry_msgs/msg/Pose2D.rb +36 -0
- data/data/msgs/geometry_msgs/msg/PoseStamped.rb +36 -0
- data/data/msgs/geometry_msgs/msg/Quaternion.rb +39 -0
- data/data/msgs/geometry_msgs/msg/QuaternionStamped.rb +36 -0
- data/data/msgs/geometry_msgs/msg/Transform.rb +36 -0
- data/data/msgs/geometry_msgs/msg/TransformStamped.rb +39 -0
- data/data/msgs/geometry_msgs/msg/Twist.rb +35 -0
- data/data/msgs/geometry_msgs/msg/TwistStamped.rb +36 -0
- data/data/msgs/geometry_msgs/msg/Vector3.rb +36 -0
- data/data/msgs/geometry_msgs/msg/Vector3Stamped.rb +36 -0
- data/data/msgs/geometry_msgs/msg/Wrench.rb +35 -0
- data/data/msgs/geometry_msgs/msg/WrenchStamped.rb +36 -0
- data/data/msgs/sensor_msgs/msg/BatteryState.rb +100 -0
- data/data/msgs/sensor_msgs/msg/FluidPressure.rb +38 -0
- data/data/msgs/sensor_msgs/msg/Illuminance.rb +38 -0
- data/data/msgs/sensor_msgs/msg/Imu.rb +52 -0
- data/data/msgs/sensor_msgs/msg/JointState.rb +44 -0
- data/data/msgs/sensor_msgs/msg/MagneticField.rb +39 -0
- data/data/msgs/sensor_msgs/msg/NavSatFix.rb +55 -0
- data/data/msgs/sensor_msgs/msg/NavSatStatus.rb +43 -0
- data/data/msgs/sensor_msgs/msg/Range.rb +52 -0
- data/data/msgs/sensor_msgs/msg/RelativeHumidity.rb +38 -0
- data/data/msgs/sensor_msgs/msg/Temperature.rb +38 -0
- data/data/msgs/std_msgs/msg/Bool.rb +30 -0
- data/data/msgs/std_msgs/msg/Byte.rb +30 -0
- data/data/msgs/std_msgs/msg/ByteMultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/Char.rb +30 -0
- data/data/msgs/std_msgs/msg/ColorRGBA.rb +39 -0
- data/data/msgs/std_msgs/msg/Empty.rb +26 -0
- data/data/msgs/std_msgs/msg/Float32.rb +30 -0
- data/data/msgs/std_msgs/msg/Float32MultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/Float64.rb +30 -0
- data/data/msgs/std_msgs/msg/Float64MultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/Header.rb +35 -0
- data/data/msgs/std_msgs/msg/Int16.rb +30 -0
- data/data/msgs/std_msgs/msg/Int16MultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/Int32.rb +30 -0
- data/data/msgs/std_msgs/msg/Int32MultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/Int64.rb +30 -0
- data/data/msgs/std_msgs/msg/Int64MultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/Int8.rb +30 -0
- data/data/msgs/std_msgs/msg/Int8MultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/MultiArrayDimension.rb +36 -0
- data/data/msgs/std_msgs/msg/MultiArrayLayout.rb +35 -0
- data/data/msgs/std_msgs/msg/String.rb +30 -0
- data/data/msgs/std_msgs/msg/UInt16.rb +30 -0
- data/data/msgs/std_msgs/msg/UInt16MultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/UInt32.rb +30 -0
- data/data/msgs/std_msgs/msg/UInt32MultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/UInt64.rb +30 -0
- data/data/msgs/std_msgs/msg/UInt64MultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/UInt8.rb +30 -0
- data/data/msgs/std_msgs/msg/UInt8MultiArray.rb +35 -0
- 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 +30 -0
- data/lib/asterism/version.rb +5 -0
- data/lib/asterism.rb +22 -4
- data/mrblib/asterism.rb +617 -0
- data/mrblib/cdr.rb +512 -0
- data/mrblib/future.rb +88 -0
- data/mrblib/proxy.rb +97 -0
- data/mrblib/ros.rb +861 -0
- data/tools/asterism_msggen.rb +826 -0
- metadata +122 -9
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# The object layer (Asterism.connect / expose / []) with blocks and a
|
|
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.
|
|
4
|
+
#
|
|
5
|
+
# Asterism.connect("tcp/192.0.2.2:7447", node: "mypc", app: "demo") do |net|
|
|
6
|
+
# net.expose("screen", Screen.new, methods: [:say])
|
|
7
|
+
# net.on_join { |node| puts "joined #{node}" }
|
|
8
|
+
# net.on_leave { |node| puts "left #{node}" }
|
|
9
|
+
# net["fmruby-aaaaaa/demo/screen"].say("hello")
|
|
10
|
+
# net.each("*/demo/info").map(&:status)
|
|
11
|
+
# net.run # answer calls until Ctrl-C
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# Locking: the object layer is one connection per process with its state in
|
|
15
|
+
# the Asterism module, written for one thread. Every entry into it (polling,
|
|
16
|
+
# answering, calling, exposing, listing) holds Asterism::LOCK, so the
|
|
17
|
+
# receiving thread and the application's threads take turns. A call that
|
|
18
|
+
# waits for its answer while the receiving thread runs does not poll; it
|
|
19
|
+
# waits for the receiving thread's ticks (Asterism::Runner#wait_until).
|
|
20
|
+
require "monitor"
|
|
21
|
+
|
|
22
|
+
module Asterism
|
|
23
|
+
# Held by every entry into the object layer (reentrant).
|
|
24
|
+
LOCK = Monitor.new
|
|
25
|
+
|
|
26
|
+
# The polled API, entered under LOCK. Prepended to the Asterism module's
|
|
27
|
+
# singleton class, so the shared methods themselves are not changed.
|
|
28
|
+
module Locked
|
|
29
|
+
%i[expose unexpose poll nodes [] call_async meta connected? exposed on_join on_leave].each do |m|
|
|
30
|
+
define_method(m) do |*args, **kw, &blk|
|
|
31
|
+
::Asterism::LOCK.synchronize { super(*args, **kw, &blk) }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# With a block: yields an Asterism::Net and closes when the block ends
|
|
36
|
+
# (also on an exception); returns the block's value. Without a block,
|
|
37
|
+
# as before (returns Asterism).
|
|
38
|
+
def connect(locator, node:, app:, mode: nil, listen: nil, &blk)
|
|
39
|
+
::Asterism::LOCK.synchronize do
|
|
40
|
+
super(locator, node: node, app: app, mode: mode, listen: listen)
|
|
41
|
+
# The Net of a connection that was lost (not closed) belongs to the
|
|
42
|
+
# old session.
|
|
43
|
+
old = @net
|
|
44
|
+
@net = nil
|
|
45
|
+
old&.release
|
|
46
|
+
end
|
|
47
|
+
return self unless blk
|
|
48
|
+
net = ::Asterism.net
|
|
49
|
+
begin
|
|
50
|
+
blk.call(net)
|
|
51
|
+
ensure
|
|
52
|
+
net.close
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Stops the receiving thread first (outside the lock: it may be inside
|
|
57
|
+
# a tick), then closes.
|
|
58
|
+
def close
|
|
59
|
+
net = @net
|
|
60
|
+
@net = nil
|
|
61
|
+
net&.release
|
|
62
|
+
::Asterism::LOCK.synchronize { super }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# The on_join / on_leave blocks (called from Asterism.poll): with a Net,
|
|
66
|
+
# what they raise goes to its on_error and the other blocks still run.
|
|
67
|
+
def tell(where, blk, node)
|
|
68
|
+
net = @net
|
|
69
|
+
return super unless net
|
|
70
|
+
net.runner.guard(where) { super }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Lists under the lock, yields outside it (the block may make calls).
|
|
74
|
+
def each(pattern = "**", &blk)
|
|
75
|
+
list = ::Asterism::LOCK.synchronize { super(pattern, &nil) }
|
|
76
|
+
return list unless blk
|
|
77
|
+
list.each(&blk)
|
|
78
|
+
list.size
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# A call waiting for its answer. On the receiving thread, or when it
|
|
82
|
+
# does not run: polls, as on the boards. On another thread while it
|
|
83
|
+
# runs: waits for its ticks to bring the answer.
|
|
84
|
+
def wait_until(&cond)
|
|
85
|
+
loop do
|
|
86
|
+
r = @net&.runner
|
|
87
|
+
if r && r.running? && !r.current?
|
|
88
|
+
r.wait_until(&cond)
|
|
89
|
+
return if ::Asterism::LOCK.synchronize(&cond)
|
|
90
|
+
# The receiving thread stopped before the answer: poll here.
|
|
91
|
+
else
|
|
92
|
+
return ::Asterism::LOCK.synchronize { super(&cond) }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
singleton_class.prepend(Locked)
|
|
98
|
+
|
|
99
|
+
class Future
|
|
100
|
+
# Prepended: done? reads the connection state, under the lock.
|
|
101
|
+
module Locked
|
|
102
|
+
def done?
|
|
103
|
+
::Asterism::LOCK.synchronize { super }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
prepend Locked
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# The connection of this process (Asterism is one per process) as an
|
|
110
|
+
# object, with a receiving thread. nil when not connected.
|
|
111
|
+
def self.net
|
|
112
|
+
LOCK.synchronize do
|
|
113
|
+
return nil unless connected?
|
|
114
|
+
@net ||= Net.new
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# The block API of the object layer. Made by Asterism.connect with a
|
|
119
|
+
# block, or Asterism.net after a connect without one.
|
|
120
|
+
class Net
|
|
121
|
+
attr_reader :runner
|
|
122
|
+
|
|
123
|
+
def initialize
|
|
124
|
+
@runner = Runner.new(::Asterism.instance_variable_get(:@session), lock: ::Asterism::LOCK, name: "asterism")
|
|
125
|
+
# One tick: Asterism.poll (answers the calls, tells on_join /
|
|
126
|
+
# on_leave); false once the connection is gone.
|
|
127
|
+
@runner.add { ::Asterism.poll ? 0 : false }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def node_id = ::Asterism.node_id
|
|
131
|
+
def app = ::Asterism.app
|
|
132
|
+
def connected? = ::Asterism.connected?
|
|
133
|
+
def lost_reason = ::Asterism.lost_reason
|
|
134
|
+
def nodes = ::Asterism.nodes
|
|
135
|
+
def exposed = ::Asterism.exposed
|
|
136
|
+
|
|
137
|
+
# Asterism.expose: methods: an Array of names or a Hash name => number
|
|
138
|
+
# of arguments. The object's methods run on the receiving thread (or in
|
|
139
|
+
# run, or in whatever polls).
|
|
140
|
+
def expose(name, obj, methods:)
|
|
141
|
+
::Asterism.expose(name, obj, methods: methods)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def unexpose(name)
|
|
145
|
+
::Asterism.unexpose(name)
|
|
146
|
+
end
|
|
147
|
+
|
|
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)]
|
|
151
|
+
end
|
|
152
|
+
alias proxy []
|
|
153
|
+
|
|
154
|
+
# 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)).
|
|
157
|
+
def each(pattern = "**", &blk)
|
|
158
|
+
return enum_for(:each, pattern) unless blk
|
|
159
|
+
::Asterism.each(pattern, &blk)
|
|
160
|
+
end
|
|
161
|
+
include Enumerable
|
|
162
|
+
|
|
163
|
+
# on_join { |node| } / on_leave { |node| }: Asterism.on_join /
|
|
164
|
+
# on_leave, the same as on the boards (called from Asterism.poll), here
|
|
165
|
+
# on the receiving thread. What they raise goes to on_error.
|
|
166
|
+
def on_join(&blk)
|
|
167
|
+
::Asterism.on_join(&blk)
|
|
168
|
+
self
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def on_leave(&blk)
|
|
172
|
+
::Asterism.on_leave(&blk)
|
|
173
|
+
self
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# on_error { |error, where| }: what on_join / on_leave blocks raise.
|
|
177
|
+
# (An exposed method that raises is answered to its caller as a
|
|
178
|
+
# RemoteError, as before; it does not come here.)
|
|
179
|
+
def on_error(&blk)
|
|
180
|
+
@runner.on_error(&blk)
|
|
181
|
+
self
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Answers calls on a thread of its own. Returns self.
|
|
185
|
+
def start
|
|
186
|
+
@runner.start
|
|
187
|
+
self
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def stop
|
|
191
|
+
@runner.stop
|
|
192
|
+
self
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Answers calls on this thread until stop, the connection closing, or
|
|
196
|
+
# Ctrl-C. Returns nil.
|
|
197
|
+
def run
|
|
198
|
+
@runner.run
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def running?
|
|
202
|
+
@runner.running?
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def poll
|
|
206
|
+
::Asterism.poll
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Stops the receiving thread and closes the connection (Asterism.close).
|
|
210
|
+
def close
|
|
211
|
+
::Asterism.close
|
|
212
|
+
nil
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# For Asterism.close: stop receiving and forget the session.
|
|
216
|
+
def release
|
|
217
|
+
@runner.close
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def inspect
|
|
221
|
+
"#<Asterism::Net #{node_id}/#{app}#{running? ? ' running' : ''}>"
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# Asterism::ROS with a receiving thread and Enumerators (CRuby only).
|
|
2
|
+
# Asterism::ROS::Node, node.poll, the subscribe / every blocks and pattern
|
|
3
|
+
# matching on messages are the shared ones (mrblib/ros.rb), as on the
|
|
4
|
+
# boards; this layer runs node.poll on a receiving thread, routes what the
|
|
5
|
+
# blocks raise to on_error, and adds the Enumerators.
|
|
6
|
+
#
|
|
7
|
+
# Asterism::ROS.connect("tcp/192.0.2.2:7447", domain: 0) do |ros|
|
|
8
|
+
# node = ros.node("ruby_talker")
|
|
9
|
+
# chatter = node.publisher("/chatter", "std_msgs/msg/String")
|
|
10
|
+
# chatter << { data: "hello from Ruby" }
|
|
11
|
+
# node.subscribe("/odom", "nav_msgs/msg/Odometry") { |odom| p odom.pose.pose.position.x }
|
|
12
|
+
# node.every(0.1) { chatter << { data: Time.now.to_s } }
|
|
13
|
+
# node.service("/add_ruby", "example_interfaces/srv/AddTwoInts") { |req| { sum: req.a + req.b } }
|
|
14
|
+
# p node.call("/add_two_ints", "example_interfaces/srv/AddTwoInts", a: 1, b: 2).sum
|
|
15
|
+
# node.topic("/scan", "sensor_msgs/msg/LaserScan").each.lazy.first(1)
|
|
16
|
+
# ros.spin
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# Locking: each connection has one lock (its Runner's). A tick (node.poll:
|
|
20
|
+
# services answered, subscription blocks, timers) holds it; so do the calls
|
|
21
|
+
# that change a node (making publishers, subscriptions, services, clients,
|
|
22
|
+
# timers) and publishing. A service call made on another thread while the connection
|
|
23
|
+
# spins waits for the ticks (Asterism::Runner#wait_until) instead of
|
|
24
|
+
# polling by itself.
|
|
25
|
+
module Asterism
|
|
26
|
+
module ROS
|
|
27
|
+
# Opens a Zenoh session and makes a Connection for nodes on it. domain:
|
|
28
|
+
# the ROS domain of the nodes made by ros.node. type_path: directories
|
|
29
|
+
# with generated types to add to Asterism::ROS::TYPE_PATH. The other
|
|
30
|
+
# options go to Asterism::Zenoh::Session.open. With a block: yields the
|
|
31
|
+
# Connection, closes it when the block ends and returns its value.
|
|
32
|
+
def self.connect(locator = nil, domain: 0, type_path: nil, interval: Runner::DEFAULT_INTERVAL, **opts)
|
|
33
|
+
Array(type_path).each { |d| TYPE_PATH << File.expand_path(d) unless TYPE_PATH.include?(File.expand_path(d)) }
|
|
34
|
+
zconn = ::Asterism::Zenoh.open(locator, interval: interval, **opts)
|
|
35
|
+
conn = Connection.new(zconn, domain: domain)
|
|
36
|
+
return conn unless block_given?
|
|
37
|
+
begin
|
|
38
|
+
yield conn
|
|
39
|
+
ensure
|
|
40
|
+
conn.close
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The ROS side of a Zenoh connection: makes nodes and spins them.
|
|
45
|
+
class Connection
|
|
46
|
+
attr_reader :zenoh, :domain
|
|
47
|
+
|
|
48
|
+
def initialize(zconn, domain: 0)
|
|
49
|
+
@zenoh = zconn
|
|
50
|
+
@domain = domain.to_i
|
|
51
|
+
@nodes = []
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def session = @zenoh.session
|
|
55
|
+
def runner = @zenoh.runner
|
|
56
|
+
|
|
57
|
+
# An Asterism::ROS::Node on this connection, with the block API
|
|
58
|
+
# (subscribe, every, topic; services answered while it spins).
|
|
59
|
+
def node(name, namespace: "/", enclave: "/")
|
|
60
|
+
runner.lock.synchronize do
|
|
61
|
+
n = Node.new(session, name, namespace: namespace, domain: @domain, enclave: enclave)
|
|
62
|
+
n.extend(Spinning)
|
|
63
|
+
n.asterism_spin_on(runner)
|
|
64
|
+
@nodes << n
|
|
65
|
+
n
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def nodes = @nodes.dup
|
|
70
|
+
|
|
71
|
+
# Spins on this thread (services, subscription blocks, timers) until
|
|
72
|
+
# stop, the connection closing, or Ctrl-C. Returns nil.
|
|
73
|
+
def spin
|
|
74
|
+
runner.run
|
|
75
|
+
end
|
|
76
|
+
alias run spin
|
|
77
|
+
|
|
78
|
+
# Spins on a thread of its own. Returns self.
|
|
79
|
+
def start
|
|
80
|
+
runner.start
|
|
81
|
+
self
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def stop
|
|
85
|
+
runner.stop
|
|
86
|
+
self
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def running? = runner.running?
|
|
90
|
+
|
|
91
|
+
# on_error { |error, where| }: what subscription, timer and service
|
|
92
|
+
# blocks raise (the service request then gets no answer).
|
|
93
|
+
def on_error(&blk)
|
|
94
|
+
runner.on_error(&blk)
|
|
95
|
+
self
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Stops spinning, withdraws the nodes and closes the session.
|
|
99
|
+
def close
|
|
100
|
+
runner.stop
|
|
101
|
+
@nodes.each { |n| n.close rescue ::Asterism::Zenoh::Error }
|
|
102
|
+
@nodes = []
|
|
103
|
+
@zenoh.close
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def inspect
|
|
108
|
+
"#<Asterism::ROS::Connection domain #{@domain}#{running? ? ' spinning' : ''}>"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# A service block raised; it was reported through on_error already.
|
|
113
|
+
class ServiceFailed < ::StandardError; end
|
|
114
|
+
|
|
115
|
+
# Added to the nodes a Connection makes (Node#extend). The methods of
|
|
116
|
+
# Node keep their meaning; these come on top.
|
|
117
|
+
module Spinning
|
|
118
|
+
def asterism_spin_on(runner)
|
|
119
|
+
@asterism_runner = runner
|
|
120
|
+
node = self
|
|
121
|
+
@asterism_task = runner.add { node.poll ? 0 : false }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# As Node#pump (node.poll and waiting service calls go through it). A
|
|
125
|
+
# service block that raised was reported (on_error) and its request
|
|
126
|
+
# left unanswered; polling goes on.
|
|
127
|
+
def pump(steps = 8)
|
|
128
|
+
super
|
|
129
|
+
rescue ::Asterism::ROS::ServiceFailed
|
|
130
|
+
!@session.closed?
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# The subscribe and every blocks (called from node.poll): what they
|
|
134
|
+
# raise goes to on_error, and the others still run.
|
|
135
|
+
def handle(sub, msg, info)
|
|
136
|
+
@asterism_runner.guard("subscribe #{sub.topic_key}") { super }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def fire(timer, now)
|
|
140
|
+
@asterism_runner.guard("every #{timer.period}") { super }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def asterism_runner = @asterism_runner
|
|
144
|
+
|
|
145
|
+
# The calls that change the node take the connection's lock.
|
|
146
|
+
%i[subscription client close every cancel_timer].each do |m|
|
|
147
|
+
define_method(m) do |*args, **kw, &blk|
|
|
148
|
+
@asterism_runner.lock.synchronize { super(*args, **kw, &blk) }
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# As Node#publisher; publishing takes the lock too (a timer on the
|
|
153
|
+
# receiving thread and the application may publish at once).
|
|
154
|
+
def publisher(topic, type, qos: DEFAULT_QOS)
|
|
155
|
+
r = @asterism_runner
|
|
156
|
+
r.lock.synchronize do
|
|
157
|
+
pub = super
|
|
158
|
+
pub.extend(LockedPublisher)
|
|
159
|
+
pub.instance_variable_set(:@asterism_lock, r.lock)
|
|
160
|
+
pub
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# As Node#service: the block answers while the connection spins (or
|
|
165
|
+
# from node.poll). What it raises goes to on_error and that request
|
|
166
|
+
# gets no answer.
|
|
167
|
+
def service(name, type, qos: DEFAULT_QOS, depth: 8, &handler)
|
|
168
|
+
raise ArgumentError, "service needs a block" unless handler
|
|
169
|
+
r = @asterism_runner
|
|
170
|
+
where = "service #{name}"
|
|
171
|
+
wrapped = proc do |req|
|
|
172
|
+
handler.call(req)
|
|
173
|
+
rescue StandardError => e
|
|
174
|
+
r.report(e, where)
|
|
175
|
+
raise ::Asterism::ROS::ServiceFailed, e.message
|
|
176
|
+
end
|
|
177
|
+
r.lock.synchronize { super(name, type, qos: qos, depth: depth, &wrapped) }
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# With a block: as on the boards (Node#subscribe), the block gets each
|
|
181
|
+
# message (and its Attachment, or nil) from node.poll, here while the
|
|
182
|
+
# connection spins; returns the Subscription. Without: a Subscription
|
|
183
|
+
# whose each waits for messages (an Enumerator).
|
|
184
|
+
def subscribe(topic, type, qos: DEFAULT_QOS, depth: 16, &blk)
|
|
185
|
+
r = @asterism_runner
|
|
186
|
+
r.lock.synchronize do
|
|
187
|
+
sub = blk ? super : subscription(topic, type, qos: qos, depth: depth)
|
|
188
|
+
sub.extend(Stream)
|
|
189
|
+
sub.asterism_stream_on(session)
|
|
190
|
+
sub
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# A topic to iterate: each subscribes for the iteration and withdraws
|
|
195
|
+
# after it (node.topic("/scan", "sensor_msgs/msg/LaserScan").each.lazy.first(1)).
|
|
196
|
+
def topic(topic, type, qos: DEFAULT_QOS, depth: 16)
|
|
197
|
+
Topic.new(self, topic, type, qos, depth)
|
|
198
|
+
end
|
|
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)
|
|
203
|
+
c = @asterism_runner.lock.synchronize do
|
|
204
|
+
@clients ||= {}
|
|
205
|
+
cl = @clients[service]
|
|
206
|
+
if cl.nil? || cl.closed?
|
|
207
|
+
cl = client(service, type)
|
|
208
|
+
@clients[service] = cl
|
|
209
|
+
end
|
|
210
|
+
cl
|
|
211
|
+
end
|
|
212
|
+
c.call(request, timeout_ms: ms, **fields)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
module LockedPublisher
|
|
217
|
+
def publish(msg)
|
|
218
|
+
@asterism_lock.synchronize { super }
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Added to the subscriptions node.subscribe makes.
|
|
223
|
+
module Stream
|
|
224
|
+
include Enumerable
|
|
225
|
+
|
|
226
|
+
def asterism_stream_on(session)
|
|
227
|
+
@asterism_session = session
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Yields each message (and its Attachment) as it comes, waiting for
|
|
231
|
+
# the next; ends when the subscription or the session closes, or after
|
|
232
|
+
# timeout seconds. Without a block, an Enumerator.
|
|
233
|
+
def each(timeout: nil, &blk)
|
|
234
|
+
return enum_for(:each, timeout: timeout) unless blk
|
|
235
|
+
raise ::Asterism::Error, "this subscription has a block; its messages go there" if handler
|
|
236
|
+
deadline = timeout && (Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout)
|
|
237
|
+
loop do
|
|
238
|
+
got = each_pending
|
|
239
|
+
got.each { |m| blk.call(m[0], m[1]) }
|
|
240
|
+
break if closed? || @asterism_session.closed?
|
|
241
|
+
break if deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
242
|
+
sleep(::Asterism::Zenoh::ENUM_STEP) if got.empty?
|
|
243
|
+
end
|
|
244
|
+
self
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# node.topic: subscribes while it is iterated.
|
|
250
|
+
class Topic
|
|
251
|
+
include Enumerable
|
|
252
|
+
|
|
253
|
+
attr_reader :name, :type
|
|
254
|
+
|
|
255
|
+
def initialize(node, name, type, qos, depth)
|
|
256
|
+
@node = node
|
|
257
|
+
@name = name
|
|
258
|
+
@type = type
|
|
259
|
+
@qos = qos
|
|
260
|
+
@depth = depth
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Yields each message and its Attachment, from a subscription made
|
|
264
|
+
# for this iteration (withdrawn when it ends: break, first(n), the
|
|
265
|
+
# time running out). Without a block, an Enumerator.
|
|
266
|
+
def each(timeout: nil, &blk)
|
|
267
|
+
return enum_for(:each, timeout: timeout) unless blk
|
|
268
|
+
sub = @node.subscribe(@name, @type, qos: @qos, depth: @depth)
|
|
269
|
+
begin
|
|
270
|
+
sub.each(timeout: timeout, &blk)
|
|
271
|
+
ensure
|
|
272
|
+
sub.close
|
|
273
|
+
end
|
|
274
|
+
self
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def inspect
|
|
278
|
+
"#<Asterism::ROS::Topic #{@name}>"
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
class Client
|
|
283
|
+
# Prepended: a call made on another thread while the connection
|
|
284
|
+
# spins waits for the ticks instead of polling the node by itself.
|
|
285
|
+
# (Calls from a board's update loop are on one thread; here the
|
|
286
|
+
# application may call from several.)
|
|
287
|
+
# (The sequence number is read once in the shared call_async, so
|
|
288
|
+
# several threads may call through one client without a lock.)
|
|
289
|
+
module Waiting
|
|
290
|
+
def wait_for(c)
|
|
291
|
+
loop do
|
|
292
|
+
r = ::Asterism::Runner.for(@session)
|
|
293
|
+
if r && r.running? && !r.current?
|
|
294
|
+
r.wait_until { c.done? }
|
|
295
|
+
return if c.done?
|
|
296
|
+
else
|
|
297
|
+
return r ? r.lock.synchronize { super } : super
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
prepend Waiting
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
end
|