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.
@@ -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
@@ -0,0 +1,6 @@
1
+ require 'libowl/solver_aggregator'
2
+ require 'libowl/client_world_connection'
3
+ require 'libowl/solver_world_model'
4
+ require 'libowl/wm_data'
5
+ require 'libowl/buffer_manip'
6
+
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: []