libowl 1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|