libowl 1.2
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 +15 -0
- data/LICENSE +502 -0
- data/README.md +23 -0
- data/lib/libowl/aggregator_rules.rb +24 -0
- data/lib/libowl/buffer_manip.rb +121 -0
- data/lib/libowl/client_world_connection.rb +434 -0
- data/lib/libowl/client_world_model.rb +243 -0
- data/lib/libowl/message_constants.rb +27 -0
- data/lib/libowl/response.rb +72 -0
- data/lib/libowl/sensor_sample.rb +17 -0
- data/lib/libowl/solution_types.rb +49 -0
- data/lib/libowl/solver_aggregator.rb +126 -0
- data/lib/libowl/solver_world_model.rb +220 -0
- data/lib/libowl/step_response.rb +80 -0
- data/lib/libowl/transient_request.rb +14 -0
- data/lib/libowl/wm_data.rb +61 -0
- data/lib/libowl.rb +6 -0
- metadata +61 -0
@@ -0,0 +1,220 @@
|
|
1
|
+
#This class abstracts the details of connecting to a
|
2
|
+
#GRAIL3 world model as a solver.
|
3
|
+
|
4
|
+
require 'socket'
|
5
|
+
require 'libowl/buffer_manip.rb'
|
6
|
+
require 'libowl/wm_data.rb'
|
7
|
+
require 'libowl/transient_request.rb'
|
8
|
+
|
9
|
+
class SolverWorldModel
|
10
|
+
#TODO Move constants to their own file
|
11
|
+
|
12
|
+
#Message constants
|
13
|
+
KEEP_ALIVE = 0;
|
14
|
+
TYPE_ANNOUNCE = 1;
|
15
|
+
START_TRANSIENT = 2;
|
16
|
+
STOP_TRANSIENT = 3;
|
17
|
+
SOLVER_DATA = 4;
|
18
|
+
CREATE_URI = 5;
|
19
|
+
EXPIRE_URI = 6;
|
20
|
+
DELETE_URI = 7;
|
21
|
+
EXPIRE_ATTRIBUTE = 8;
|
22
|
+
DELETE_ATTRIBUTE = 9;
|
23
|
+
|
24
|
+
attr_accessor :name_to_alias, :alias_to_name, :connected
|
25
|
+
@name_to_alias
|
26
|
+
@alias_to_name
|
27
|
+
|
28
|
+
#The origin string of this solver
|
29
|
+
@origin
|
30
|
+
|
31
|
+
#Callback for when the world model requests transient data
|
32
|
+
@start_transient_callback
|
33
|
+
#Callback for when the world model no longer wants a transient type
|
34
|
+
@stop_transient_callback
|
35
|
+
|
36
|
+
#Handle a message of currently unknown type
|
37
|
+
def handleMessage()
|
38
|
+
#Get the message length as n unsigned integer
|
39
|
+
inlen = (@socket.recvfrom(4)[0]).unpack('N')[0]
|
40
|
+
inbuff = @socket.recvfrom(inlen)[0]
|
41
|
+
#Byte that indicates message type
|
42
|
+
control = inbuff.unpack('C')[0]
|
43
|
+
if control == START_TRANSIENT
|
44
|
+
if (@start_transient_callback != nil)
|
45
|
+
@start_transient_callback.call(decodeStartTransient(inbuff[1, inbuff.length - 1]))
|
46
|
+
end
|
47
|
+
elsif control == STOP_TRANSIENT
|
48
|
+
if (@stop_transient_callback != nil)
|
49
|
+
@stop_transient_callback.call(decodeStopTransient(inbuff[1, inbuff.length - 1]))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
return control
|
53
|
+
end
|
54
|
+
|
55
|
+
#Decode a start transient message
|
56
|
+
def decodeStartTransient(inbuff)
|
57
|
+
num_aliases = inbuff.unpack('N')[0]
|
58
|
+
rest = inbuff[4, inbuff.length - 1]
|
59
|
+
new_trans_requests = []
|
60
|
+
for i in 1..num_aliases do
|
61
|
+
type_alias = rest.unpack('N')[0]
|
62
|
+
total_expressions = rest.unpack('N')[0]
|
63
|
+
t_request = TransientRequest.new(type_alias, [])
|
64
|
+
for j in 1..total_expressions do
|
65
|
+
exp, rest = splitURIFromRest(rest[4, rest.length - 1])
|
66
|
+
t_request.expressions.push(exp)
|
67
|
+
end
|
68
|
+
new_trans_requests.push(t_request)
|
69
|
+
end
|
70
|
+
return new_trans_requests
|
71
|
+
end
|
72
|
+
|
73
|
+
#Decode a stop transient message
|
74
|
+
def decodeStopTransient(inbuff)
|
75
|
+
num_aliases = inbuff.unpack('N')[0]
|
76
|
+
rest = inbuff[4, inbuff.length - 1]
|
77
|
+
new_trans_requests = []
|
78
|
+
for i in 1..num_aliases do
|
79
|
+
type_alias = rest.unpack('N')[0]
|
80
|
+
total_expressions = rest.unpack('N')[0]
|
81
|
+
t_request = TransientRequest.new(type_alias, [])
|
82
|
+
for j in 1..total_expressions do
|
83
|
+
exp, rest = splitURIFromRest(rest[4, rest.length - 1])
|
84
|
+
t_request.expressions.push(exp)
|
85
|
+
end
|
86
|
+
new_trans_requests.push(t_request)
|
87
|
+
end
|
88
|
+
return new_trans_requests
|
89
|
+
end
|
90
|
+
|
91
|
+
def initialize(host, port, origin, start_transient_callback = nil, stop_transient_callback = nil)
|
92
|
+
@origin = origin
|
93
|
+
@connected = false
|
94
|
+
@host = host
|
95
|
+
@port = port
|
96
|
+
@socket = TCPSocket.open(host, port)
|
97
|
+
handshake = ""
|
98
|
+
ver_string = "GRAIL world model protocol"
|
99
|
+
#The handshake is the length of the message, the protocol string, and the version (0).
|
100
|
+
handshake << [ver_string.length].pack('N') << ver_string << "\x00\x00"
|
101
|
+
#Receive a handshake and then send one
|
102
|
+
@socket.send(handshake, 0)
|
103
|
+
inshake = @socket.recvfrom(handshake.length)[0]
|
104
|
+
while (inshake.length < handshake.length)
|
105
|
+
puts "Waiting for #{handshake.length - inshake.length} byte more of handshake."
|
106
|
+
inshake += @socket.recvfrom(handshake.length - inshake.length)[0]
|
107
|
+
end
|
108
|
+
|
109
|
+
@connected = true
|
110
|
+
for i in 1..handshake.length
|
111
|
+
if handshake[i] != inshake[i]
|
112
|
+
puts "Handshake failure!"
|
113
|
+
puts "For byte i we sent #{handshake[i]} but got #{inshake[i]}"
|
114
|
+
@connected = false
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
@name_to_alias = {}
|
119
|
+
@alias_to_name = {}
|
120
|
+
|
121
|
+
@start_transient_callback = start_transient_callback
|
122
|
+
@stop_transient_callback = stop_transient_callback
|
123
|
+
end
|
124
|
+
|
125
|
+
#Add some SolutionType objects to the known list
|
126
|
+
def addSolutionTypes(attributes)
|
127
|
+
new_aliases = []
|
128
|
+
attributes.each { |attr|
|
129
|
+
if (@name_to_alias[attr.name] == nil)
|
130
|
+
new_alias = @name_to_alias.length
|
131
|
+
@name_to_alias[attr.name] = new_alias
|
132
|
+
@alias_to_name[new_alias] = attr.name
|
133
|
+
new_aliases.push([attr.name, new_alias])
|
134
|
+
end
|
135
|
+
}
|
136
|
+
if (new_aliases.length > 0)
|
137
|
+
makeTypeAnnounce(new_aliases)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def makeTypeAnnounce(type_pairs)
|
142
|
+
buff = [TYPE_ANNOUNCE].pack('C') + [type_pairs.length].pack('N')
|
143
|
+
for pair in type_pairs do
|
144
|
+
#TODO Not supporting transient types for now so always write a zero
|
145
|
+
#for the transient on/off byte
|
146
|
+
buff += [pair[1]].pack('N') + strToSizedUTF16(pair[0]) + [0].pack('C')
|
147
|
+
end
|
148
|
+
#Add the origin string to the end of the message
|
149
|
+
buff += strToUnicode(@origin)
|
150
|
+
@socket.send("#{[buff.length].pack('N')}#{buff}", 0)
|
151
|
+
end
|
152
|
+
|
153
|
+
##
|
154
|
+
#Push URI attributes, automatically declaring new solution types
|
155
|
+
#as non-stremaing types if they were not previously declared.
|
156
|
+
def pushData(wmdata_vector, create_uris = false)
|
157
|
+
buff = [SOLVER_DATA].pack('C')
|
158
|
+
if (create_uris)
|
159
|
+
buff += [1].pack('C')
|
160
|
+
else
|
161
|
+
buff += [0].pack('C')
|
162
|
+
end
|
163
|
+
|
164
|
+
#Push back the total number of solutions
|
165
|
+
total_solns = wmdata_vector.inject(0){|sum, wmdata|
|
166
|
+
sum + wmdata.attributes.length
|
167
|
+
}
|
168
|
+
buff += [total_solns].pack('N')
|
169
|
+
|
170
|
+
#Now create each solution and push it back into the buffer
|
171
|
+
wmdata_vector.each{|wmdata|
|
172
|
+
#First make sure all of the solutions types have been declared
|
173
|
+
addSolutionTypes(wmdata.attributes)
|
174
|
+
#Now push back this attribute's data using an alias for the name
|
175
|
+
wmdata.attributes.each{|attr|
|
176
|
+
buff += [@name_to_alias[attr.name]].pack('N') +
|
177
|
+
packuint64(attr.creation) +
|
178
|
+
strToSizedUTF16(wmdata.uri) + [attr.data.length].pack('N') + attr.data
|
179
|
+
}
|
180
|
+
}
|
181
|
+
#Send the message with its length prepended to the front
|
182
|
+
@socket.send("#{[buff.length].pack('N')}#{buff}", 0)
|
183
|
+
end
|
184
|
+
|
185
|
+
##
|
186
|
+
#Create an object with the given name in the world model.
|
187
|
+
def createURI(uri, creation_time)
|
188
|
+
buff = [CREATE_URI].pack('C')
|
189
|
+
buff += strToSizedUTF16(uri)
|
190
|
+
buff += packuint64(creation_time)
|
191
|
+
buff += strToUnicode(@origin)
|
192
|
+
#Send the message with its length prepended to the front
|
193
|
+
@socket.send("#{[buff.length].pack('N')}#{buff}", 0)
|
194
|
+
end
|
195
|
+
|
196
|
+
##
|
197
|
+
#Expire the object with the given name in the world model, indicating that it
|
198
|
+
#is no longer valid after the given time.
|
199
|
+
def expireURI(uri, expiration_time)
|
200
|
+
buff = [EXPIRE_URI].pack('C')
|
201
|
+
buff += strToSizedUTF16(uri)
|
202
|
+
buff += packuint64(expiration_time)
|
203
|
+
buff += strToUnicode(@origin)
|
204
|
+
#Send the message with its length prepended to the front
|
205
|
+
@socket.send("#{[buff.length].pack('N')}#{buff}", 0)
|
206
|
+
end
|
207
|
+
|
208
|
+
##
|
209
|
+
#Delete an object in the world model.
|
210
|
+
def deleteURI(uri)
|
211
|
+
buff = [DELETE_URI].pack('C')
|
212
|
+
buff += strToSizedUTF16(uri)
|
213
|
+
buff += strToUnicode(@origin)
|
214
|
+
#Send the message with its length prepended to the front
|
215
|
+
@socket.send("#{[buff.length].pack('N')}#{buff}", 0)
|
216
|
+
end
|
217
|
+
|
218
|
+
#TODO Expire a URI's attribute
|
219
|
+
#TODO Delete a URI's attribute
|
220
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
################################################################################
|
2
|
+
#This file defines the StepResponse class, an object that represents data from
|
3
|
+
#an owl world model that is sent for a streaming request. An instance of this
|
4
|
+
#class will continually yield new data until the streaming request is cancelled.
|
5
|
+
#
|
6
|
+
# Copyright (c) 2013 Bernhard Firner
|
7
|
+
# All rights reserved.
|
8
|
+
#
|
9
|
+
# This program is free software; you can redistribute it and/or
|
10
|
+
# modify it under the terms of the GNU General Public License
|
11
|
+
# as published by the Free Software Foundation; either version 2
|
12
|
+
# of the License, or (at your option) any later version.
|
13
|
+
#
|
14
|
+
# This program is distributed in the hope that it will be useful,
|
15
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
16
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
17
|
+
# GNU General Public License for more details.
|
18
|
+
#
|
19
|
+
# You should have received a copy of the GNU General Public License
|
20
|
+
# along with this program; if not, write to the Free Software
|
21
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
22
|
+
# or visit http://www.gnu.org/licenses/gpl-2.0.html
|
23
|
+
#
|
24
|
+
################################################################################
|
25
|
+
|
26
|
+
#Incrementeal response of a client streaming request to the world model
|
27
|
+
class StepResponse
|
28
|
+
##
|
29
|
+
#Initialize with the ClientWorldConnection that spawed this Response and
|
30
|
+
#the key of the request.
|
31
|
+
def initialize(cwc, key)
|
32
|
+
@cwc = cwc
|
33
|
+
@request_key = key
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
#Get the data of this StepResponse, blocking until that data is ready or
|
38
|
+
#an error occurs.
|
39
|
+
def next()
|
40
|
+
while (not (hasNext() or isError()))
|
41
|
+
sleep(1)
|
42
|
+
end
|
43
|
+
if (isError())
|
44
|
+
raise getError()
|
45
|
+
else
|
46
|
+
return @cwc.getNext(@request_key)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
#Returns true if data is available for a call to next().
|
52
|
+
def hasNext()
|
53
|
+
return @cwc.hasNext(@request_key)
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
#Returns true if an error has occured.
|
58
|
+
def isError()
|
59
|
+
return @cwc.hasError(@request_key)
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
#Get the error that occured.
|
64
|
+
def getError()
|
65
|
+
return @cwc.getError(@request_key)
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
#True if this streaming request will have no more data.
|
70
|
+
def isComplete()
|
71
|
+
return @cwc.isComplete(@request_key)
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
#Cancel the streaming request.
|
76
|
+
def cancel()
|
77
|
+
return @cwc.cancelRequest(@request_key)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#Transient specification - one attribute name an a list of name expressions
|
2
|
+
|
3
|
+
class TransientRequest
|
4
|
+
#Attribute name
|
5
|
+
@name
|
6
|
+
#Requested expressions
|
7
|
+
@expressions
|
8
|
+
|
9
|
+
def initialize(name, expressions)
|
10
|
+
@name = name
|
11
|
+
@expressions = expressions
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
#Solution types used when communicating with the world model
|
2
|
+
|
3
|
+
##
|
4
|
+
#Function to fetch the current time in milliseconds. This is the time format
|
5
|
+
#used in the Owl system and is included in every attribute pushed into the
|
6
|
+
#world model and every attribute retrieved from the world model.
|
7
|
+
def getMsecTime()
|
8
|
+
t = Time.now
|
9
|
+
return t.tv_sec * 1000 + t.usec/10**3
|
10
|
+
end
|
11
|
+
|
12
|
+
class WMAttribute
|
13
|
+
attr_accessor :name, :data, :creation, :expiration, :origin
|
14
|
+
#Name of this attribute
|
15
|
+
@name
|
16
|
+
#Binary buffer of attribute data
|
17
|
+
@data
|
18
|
+
#The creation and expiration dates of this attribute
|
19
|
+
#in milliseconds since midnight Jan 1, 1970
|
20
|
+
@creation
|
21
|
+
@expiration
|
22
|
+
#The origin of this data
|
23
|
+
@origin
|
24
|
+
|
25
|
+
def initialize(name, data, creation, expiration = 0, origin = "empty")
|
26
|
+
@name = name
|
27
|
+
@data = data
|
28
|
+
@creation = creation
|
29
|
+
@expiration = expiration
|
30
|
+
@origin = origin
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s()
|
34
|
+
return "\t#{@name}, #{@creation}, #{@expiration}, #{@origin}: #{@data.unpack('H*')}\n"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class WMData
|
39
|
+
attr_accessor :uri, :attributes, :ticket
|
40
|
+
#The object that this data modifies
|
41
|
+
@uri
|
42
|
+
#The attributes of the object
|
43
|
+
@attributes
|
44
|
+
#The request ticket that this data is associated with
|
45
|
+
@ticket
|
46
|
+
|
47
|
+
def initialize(uri, attributes, ticket = 0)
|
48
|
+
@uri = uri
|
49
|
+
@attributes = attributes
|
50
|
+
@ticket = ticket
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s()
|
54
|
+
str = "#{@uri}:\n"
|
55
|
+
for attr in @attributes do
|
56
|
+
str += "\t#{attr.name}, #{attr.creation}, #{attr.expiration}, #{attr.origin}: #{attr.data.unpack('H*')}\n"
|
57
|
+
end
|
58
|
+
return str
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
data/lib/libowl.rb
ADDED
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: libowl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '1.2'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bernhard Firner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-08-20 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: The libowl network protocols are the protocols used to interact with
|
14
|
+
the Owl platform or any other system that uses the GRAIL network protocols. Go to
|
15
|
+
www.owlplatform.com and github.com/OwlPlatform for more information.
|
16
|
+
email: bfirner@eden.rutgers.edu
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- README.md
|
22
|
+
- LICENSE
|
23
|
+
- lib/libowl.rb
|
24
|
+
- lib/libowl/solution_types.rb
|
25
|
+
- lib/libowl/transient_request.rb
|
26
|
+
- lib/libowl/aggregator_rules.rb
|
27
|
+
- lib/libowl/client_world_connection.rb
|
28
|
+
- lib/libowl/client_world_model.rb
|
29
|
+
- lib/libowl/solver_world_model.rb
|
30
|
+
- lib/libowl/buffer_manip.rb
|
31
|
+
- lib/libowl/response.rb
|
32
|
+
- lib/libowl/step_response.rb
|
33
|
+
- lib/libowl/message_constants.rb
|
34
|
+
- lib/libowl/wm_data.rb
|
35
|
+
- lib/libowl/sensor_sample.rb
|
36
|
+
- lib/libowl/solver_aggregator.rb
|
37
|
+
homepage: https://github.com/OwlPlatform/libruby
|
38
|
+
licenses:
|
39
|
+
- LGPL-2.1
|
40
|
+
metadata: {}
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
requirements: []
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 2.0.7
|
58
|
+
signing_key:
|
59
|
+
specification_version: 3
|
60
|
+
summary: Protocols for interacting with the owl platform.
|
61
|
+
test_files: []
|