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,243 @@
|
|
1
|
+
################################################################################
|
2
|
+
#This file defines the ClientWorldModel class, an object that connects to an
|
3
|
+
#Owl world model, simplifying requesting and handling data. This class does not
|
4
|
+
#keep a threaded connection open as in the ClientWorldConnection so message
|
5
|
+
#handling must be scheduled by the client.
|
6
|
+
#
|
7
|
+
# Copyright (c) 2013 Bernhard Firner
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# This program is free software; you can redistribute it and/or
|
11
|
+
# modify it under the terms of the GNU General Public License
|
12
|
+
# as published by the Free Software Foundation; either version 2
|
13
|
+
# of the License, or (at your option) any later version.
|
14
|
+
#
|
15
|
+
# This program is distributed in the hope that it will be useful,
|
16
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
17
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
18
|
+
# GNU General Public License for more details.
|
19
|
+
#
|
20
|
+
# You should have received a copy of the GNU General Public License
|
21
|
+
# along with this program; if not, write to the Free Software
|
22
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
23
|
+
# or visit http://www.gnu.org/licenses/gpl-2.0.html
|
24
|
+
#
|
25
|
+
################################################################################
|
26
|
+
|
27
|
+
require 'socket'
|
28
|
+
require 'libowl/message_constants.rb'
|
29
|
+
require 'libowl/buffer_manip.rb'
|
30
|
+
require 'libowl/wm_data.rb'
|
31
|
+
require 'libowl/transient_request.rb'
|
32
|
+
|
33
|
+
##
|
34
|
+
#This class abstracts the network details of connecting to a World Model.
|
35
|
+
#However, this class does not use a thread to handle networking in the
|
36
|
+
#background as in the ClientWorldConnection class, so handleMessage must
|
37
|
+
#be called manually. If threads are available then the ClientWorldConnection
|
38
|
+
#class is simpler to use.
|
39
|
+
class ClientWorldModel
|
40
|
+
attr_accessor :alias_to_attr_name, :alias_to_origin_name
|
41
|
+
attr_reader :connected
|
42
|
+
@alias_to_attr_name
|
43
|
+
@alias_to_origin_name
|
44
|
+
@connected
|
45
|
+
|
46
|
+
@data_callback
|
47
|
+
@uri_response_callback
|
48
|
+
|
49
|
+
def initialize(host, port, data_callback = nil, uri_response_callback = nil)
|
50
|
+
@connected = false
|
51
|
+
@host = host
|
52
|
+
@port = port
|
53
|
+
@socket = TCPSocket.open(host, port)
|
54
|
+
handshake = ""
|
55
|
+
ver_string = "GRAIL client protocol"
|
56
|
+
#The handshake is the length of the message, the protocol string, and the version (0).
|
57
|
+
handshake << [ver_string.length].pack('N') << ver_string << "\x00\x00"
|
58
|
+
#Send a handshake and then receive one
|
59
|
+
@socket.send(handshake, 0)
|
60
|
+
inshake = @socket.recvfrom(handshake.length)[0]
|
61
|
+
while (inshake.length < handshake.length)
|
62
|
+
#puts "Waiting for #{handshake.length - inshake.length} byte more of handshake."
|
63
|
+
inshake += @socket.recvfrom(handshake.length - inshake.length)[0]
|
64
|
+
end
|
65
|
+
|
66
|
+
@connected = true
|
67
|
+
for i in 1..handshake.length
|
68
|
+
if handshake[i] != inshake[i]
|
69
|
+
puts "Handshake failure!"
|
70
|
+
puts "For byte i we sent #{handshake[i]} but got #{inshake[i]}"
|
71
|
+
@connected = false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
@alias_to_attr_name = {}
|
76
|
+
@alias_to_origin_name = {}
|
77
|
+
|
78
|
+
@data_callback = data_callback
|
79
|
+
@uri_response_callback = uri_response_callback
|
80
|
+
end
|
81
|
+
|
82
|
+
def close()
|
83
|
+
@socket.close()
|
84
|
+
@connected = false
|
85
|
+
end
|
86
|
+
|
87
|
+
#Handle a message of currently unknown type
|
88
|
+
def handleMessage()
|
89
|
+
#puts "Handling message..."
|
90
|
+
#Get the message length as n unsigned integer
|
91
|
+
inlen = (@socket.recvfrom(4)[0]).unpack('N')[0]
|
92
|
+
inbuff = @socket.recvfrom(inlen)[0]
|
93
|
+
#Keep reading until the entire packet is read
|
94
|
+
#TODO This can block forever if a communication error occurs.
|
95
|
+
while (inbuff.length < inlen)
|
96
|
+
inbuff += @socket.recvfrom(inlen-inbuff.length)[0]
|
97
|
+
end
|
98
|
+
#Byte that indicates message type
|
99
|
+
control = inbuff.unpack('C')[0]
|
100
|
+
if control == ATTRIBUTE_ALIAS
|
101
|
+
decodeAttributeAlias(inbuff[1, inbuff.length - 1])
|
102
|
+
elsif control == ORIGIN_ALIAS
|
103
|
+
decodeOriginAlias(inbuff[1, inbuff.length - 1])
|
104
|
+
elsif control == REQUEST_COMPLETE
|
105
|
+
ticket = decodeTicketMessage(inbuff[1, inbuff.length-1])
|
106
|
+
#puts "World Model completed request for ticket #{ticket}"
|
107
|
+
elsif control == DATA_RESPONSE
|
108
|
+
if (@data_callback != nil)
|
109
|
+
@data_callback.call(decodeDataResponse(inbuff[1, inbuff.length - 1]))
|
110
|
+
end
|
111
|
+
elsif control == URI_RESPONSE
|
112
|
+
if (@uri_response_callback != nil)
|
113
|
+
@uri_response_callback.call(decodeURIResponse(inbuff[1, inbuff.length - 1]))
|
114
|
+
end
|
115
|
+
end
|
116
|
+
#puts "processed message with id #{control}"
|
117
|
+
return control
|
118
|
+
end
|
119
|
+
|
120
|
+
#Decode attribute alias message
|
121
|
+
def decodeAttributeAlias(inbuff)
|
122
|
+
num_aliases = inbuff.unpack('N')[0]
|
123
|
+
rest = inbuff[4, inbuff.length - 1]
|
124
|
+
for i in 1..num_aliases do
|
125
|
+
attr_alias = rest.unpack('N')[0]
|
126
|
+
name, rest = splitURIFromRest(rest[4, rest.length - 1])
|
127
|
+
#Assign this name to the given alias
|
128
|
+
@alias_to_attr_name[attr_alias] = name
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
#Decode origin alias message
|
133
|
+
def decodeOriginAlias(inbuff)
|
134
|
+
num_aliases = inbuff.unpack('N')[0]
|
135
|
+
rest = inbuff[4, inbuff.length - 1]
|
136
|
+
for i in 1..num_aliases do
|
137
|
+
origin_alias = rest.unpack('N')[0]
|
138
|
+
name, rest = splitURIFromRest(rest[4, rest.length - 1])
|
139
|
+
#Assign this name to the given alias
|
140
|
+
@alias_to_origin_name[origin_alias] = name
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
#Decode a ticket message or a request complete message.
|
145
|
+
def decodeTicketMessage(inbuff)
|
146
|
+
return inbuff.unpack('N')[0]
|
147
|
+
end
|
148
|
+
|
149
|
+
def decodeURIResponse(inbuff)
|
150
|
+
uris = []
|
151
|
+
if (inbuff != nil)
|
152
|
+
rest = inbuff
|
153
|
+
while (rest.length > 4)
|
154
|
+
name, rest = splitURIFromRest(rest)
|
155
|
+
uris.push(name)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
return uris
|
159
|
+
end
|
160
|
+
|
161
|
+
def decodeDataResponse(inbuff)
|
162
|
+
attributes = []
|
163
|
+
object_uri, rest = splitURIFromRest(inbuff)
|
164
|
+
ticket = rest.unpack('N')[0]
|
165
|
+
total_attributes = rest[4, rest.length - 1].unpack('N')[0]
|
166
|
+
rest = rest[8, rest.length]
|
167
|
+
#puts "Decoding #{total_attributes} attributes"
|
168
|
+
for i in 1..total_attributes do
|
169
|
+
name_alias = rest.unpack('N')[0]
|
170
|
+
creation_date = unpackuint64(rest[4, rest.length - 1])
|
171
|
+
expiration_date = unpackuint64(rest[12, rest.length - 1])
|
172
|
+
origin_alias = rest[20, rest.length - 1].unpack('N')[0]
|
173
|
+
data_len = rest[24, rest.length - 1].unpack('N')[0]
|
174
|
+
data = rest[28, data_len]
|
175
|
+
rest = rest[28+data_len, rest.length - 1]
|
176
|
+
attributes.push(WMAttribute.new(@alias_to_attr_name[name_alias], data, creation_date, expiration_date, @alias_to_origin_name[origin_alias]))
|
177
|
+
end
|
178
|
+
return WMData.new(object_uri, attributes, ticket)
|
179
|
+
end
|
180
|
+
|
181
|
+
def sendSnapshotRequest(name_pattern, attribute_patterns, ticket, start_time = 0, stop_time = 0)
|
182
|
+
buff = [SNAPSHOT_REQUEST].pack('C')
|
183
|
+
|
184
|
+
buff += [ticket].pack('N')
|
185
|
+
buff += strToSizedUTF16(name_pattern)
|
186
|
+
buff += [attribute_patterns.length].pack('N')
|
187
|
+
|
188
|
+
attribute_patterns.each{|pattern|
|
189
|
+
buff += strToSizedUTF16(pattern)
|
190
|
+
}
|
191
|
+
|
192
|
+
buff += packuint64(start_time)
|
193
|
+
buff += packuint64(stop_time)
|
194
|
+
|
195
|
+
#Send the message with its length prepended to the front
|
196
|
+
@socket.send("#{[buff.length].pack('N')}#{buff}", 0)
|
197
|
+
end
|
198
|
+
|
199
|
+
def sendRangeRequest(name_pattern, attribute_patterns, ticket, start_time, stop_time)
|
200
|
+
buff = [RANGE_REQUEST].pack('C')
|
201
|
+
|
202
|
+
buff += [ticket].pack('N')
|
203
|
+
buff += strToSizedUTF16(name_pattern)
|
204
|
+
buff += [attribute_patterns.length].pack('N')
|
205
|
+
|
206
|
+
attribute_patterns.each{|pattern|
|
207
|
+
buff += strToSizedUTF16(pattern)
|
208
|
+
}
|
209
|
+
|
210
|
+
buff += packuint64(start_time)
|
211
|
+
buff += packuint64(stop_time)
|
212
|
+
|
213
|
+
#Send the message with its length prepended to the front
|
214
|
+
@socket.send("#{[buff.length].pack('N')}#{buff}", 0)
|
215
|
+
end
|
216
|
+
|
217
|
+
def sendStreamRequest(name_pattern, attribute_patterns, update_interval, ticket)
|
218
|
+
buff = [STREAM_REQUEST].pack('C')
|
219
|
+
|
220
|
+
buff += [ticket].pack('N')
|
221
|
+
buff += strToSizedUTF16(name_pattern)
|
222
|
+
buff += [attribute_patterns.length].pack('N')
|
223
|
+
|
224
|
+
attribute_patterns.each{|pattern|
|
225
|
+
buff += strToSizedUTF16(pattern)
|
226
|
+
}
|
227
|
+
|
228
|
+
buff += packuint64(0)
|
229
|
+
buff += packuint64(update_interval)
|
230
|
+
|
231
|
+
#Send the message with its length prepended to the front
|
232
|
+
@socket.send("#{[buff.length].pack('N')}#{buff}", 0)
|
233
|
+
end
|
234
|
+
|
235
|
+
def sendURISearch(name_pattern)
|
236
|
+
buff = [URI_SEARCH].pack('C')
|
237
|
+
buff += strToUnicode(name_pattern)
|
238
|
+
#Send the message with its length prepended to the front
|
239
|
+
@socket.send("#{[buff.length].pack('N')}#{buff}", 0)
|
240
|
+
end
|
241
|
+
|
242
|
+
end
|
243
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#Message constants that indicate the purpose of a message
|
2
|
+
|
3
|
+
#Keep alive message
|
4
|
+
KEEP_ALIVE = 0;
|
5
|
+
#Request a snapshot of the current world model state
|
6
|
+
SNAPSHOT_REQUEST = 1;
|
7
|
+
#Request a snapshot of the wm state in a time range
|
8
|
+
RANGE_REQUEST = 2;
|
9
|
+
#Request a stream of data from the world model
|
10
|
+
STREAM_REQUEST = 3;
|
11
|
+
#Alias an attribute from the world model
|
12
|
+
ATTRIBUTE_ALIAS = 4;
|
13
|
+
#Alias an origin from the world model
|
14
|
+
ORIGIN_ALIAS = 5;
|
15
|
+
#Finish a request
|
16
|
+
REQUEST_COMPLETE = 6;
|
17
|
+
#Cancel a request
|
18
|
+
CANCEL_REQUEST = 7;
|
19
|
+
#Message contains a data response from the world model
|
20
|
+
DATA_RESPONSE = 8;
|
21
|
+
#Search names in the world model
|
22
|
+
URI_SEARCH = 9;
|
23
|
+
#Response to a uri search message.
|
24
|
+
URI_RESPONSE = 10;
|
25
|
+
#Set a preference for some data origins
|
26
|
+
ORIGIN_PREFERENCE = 11;
|
27
|
+
|
@@ -0,0 +1,72 @@
|
|
1
|
+
################################################################################
|
2
|
+
#This file defines the Response class, an object that represents data from
|
3
|
+
#an owl world model that is sent for a non-streaming request.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2013 Bernhard Firner
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
# This program is free software; you can redistribute it and/or
|
9
|
+
# modify it under the terms of the GNU General Public License
|
10
|
+
# as published by the Free Software Foundation; either version 2
|
11
|
+
# of the License, or (at your option) any later version.
|
12
|
+
#
|
13
|
+
# This program is distributed in the hope that it will be useful,
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
# GNU General Public License for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU General Public License
|
19
|
+
# along with this program; if not, write to the Free Software
|
20
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
21
|
+
# or visit http://www.gnu.org/licenses/gpl-2.0.html
|
22
|
+
#
|
23
|
+
################################################################################
|
24
|
+
|
25
|
+
#Response of a client request to the world model
|
26
|
+
class Response
|
27
|
+
##
|
28
|
+
#Initialize with the ClientWorldConnection that spawed this Response and
|
29
|
+
#the key of the request.
|
30
|
+
def initialize(cwc, key)
|
31
|
+
@cwc = cwc
|
32
|
+
@request_key = key
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
#Get the data of this Response, blocking until that data is ready or
|
37
|
+
#an error occurs.
|
38
|
+
def get()
|
39
|
+
while (not (ready() or isError()))
|
40
|
+
sleep(1)
|
41
|
+
end
|
42
|
+
if (isError())
|
43
|
+
raise getError()
|
44
|
+
else
|
45
|
+
return @cwc.getNext(@request_key)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
#Returns true if data is available for a call to get().
|
51
|
+
def ready()
|
52
|
+
return @cwc.hasNext(@request_key)
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
#Returns true if an error has occured.
|
57
|
+
def isError()
|
58
|
+
return @cwc.hasError(@request_key)
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
#Get the error that occured.
|
63
|
+
def getError()
|
64
|
+
return @cwc.getError(@request_key)
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
#Cancel this request.
|
69
|
+
def cancel()
|
70
|
+
return @cwc.cancelRequest(@request_key)
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#Sensor sample format
|
2
|
+
class SensorSample
|
3
|
+
attr_accessor :phy_layer, :device_id, :receiver_id, :timestamp, :rssi, :sense_data
|
4
|
+
|
5
|
+
def initialize(phy, device_id, receiver_id, timestamp, rssi, sense_data)
|
6
|
+
@phy_layer = phy
|
7
|
+
@device_id = device_id
|
8
|
+
@receiver_id = receiver_id
|
9
|
+
@timestamp = timestamp
|
10
|
+
@rssi = rssi
|
11
|
+
@sense_data = sense_data
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s()
|
15
|
+
return "#{@timestamp}: (phy #{@phy_layer}) #{@device_id} -> #{@receiver_id}, RSS:#{@rssi}, Datalength:#{@sense_data.length} Data:#{@sense_data.unpack('C*').join(', ')}"
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#Solution types used when communicating with the distributor
|
2
|
+
|
3
|
+
class SolutionType
|
4
|
+
#Used for auto-incremented alias numbers
|
5
|
+
@@num_aliases = 0
|
6
|
+
attr_accessor :type_alias, :data_uri
|
7
|
+
#Number to refer to this type
|
8
|
+
@type_alias
|
9
|
+
#The uri of this type
|
10
|
+
@data_uri
|
11
|
+
|
12
|
+
def initialize(uri, type_alias = -1)
|
13
|
+
@data_uri = uri
|
14
|
+
#Auto-increment to the next alias number if one wasn't explicitly provided
|
15
|
+
if (type_alias >= 0)
|
16
|
+
@type_alias = type_alias
|
17
|
+
else
|
18
|
+
@type_alias = @@num_aliases
|
19
|
+
@@num_aliases += 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class SolutionData
|
25
|
+
attr_accessor :region, :time, :target, :sol_name, :data
|
26
|
+
#Region of the solution
|
27
|
+
@region
|
28
|
+
#Time of the solution (milliseconds since midnight Jan 1, 1970)
|
29
|
+
@time
|
30
|
+
#Target of the solution
|
31
|
+
@target
|
32
|
+
#Name of this solution
|
33
|
+
@sol_name
|
34
|
+
#Binary buffer with solution data
|
35
|
+
@data
|
36
|
+
|
37
|
+
def initialize(region, time, target, sol_name, data)
|
38
|
+
@region = region
|
39
|
+
@time = time
|
40
|
+
@target = target
|
41
|
+
@sol_name = sol_name
|
42
|
+
@data = data
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s()
|
46
|
+
return "#{@region}:#{@target}:#{@sol_name} @ time #{@time} -> #{@data.unpack('H*')}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,126 @@
|
|
1
|
+
#This class abstracts the details of connecting to a
|
2
|
+
#GRAIL3 aggregator as a solver.
|
3
|
+
#Solvers subscribe to the aggregator and then receive packets.
|
4
|
+
|
5
|
+
require 'socket'
|
6
|
+
require 'libowl/buffer_manip.rb'
|
7
|
+
require 'libowl/aggregator_rules.rb'
|
8
|
+
require 'libowl/sensor_sample.rb'
|
9
|
+
|
10
|
+
class SolverAggregator
|
11
|
+
#Message constants
|
12
|
+
KEEP_ALIVE = 0
|
13
|
+
CERTIFICATE = 1
|
14
|
+
ACK_CERTIFICATE = 2 #There is no message for certificate denial
|
15
|
+
SUBSCRIPTION_REQUEST = 3
|
16
|
+
SUBSCRIPTION_RESPONSE = 4
|
17
|
+
DEVICE_POSITION = 5
|
18
|
+
SERVER_SAMPLE = 6
|
19
|
+
BUFFER_OVERRUN = 7
|
20
|
+
|
21
|
+
attr_accessor :available_packets, :cur_rules, :connected
|
22
|
+
@available_packets
|
23
|
+
@cur_rules
|
24
|
+
|
25
|
+
def initialize(host, port)
|
26
|
+
@connected = false
|
27
|
+
@host = host
|
28
|
+
@port = port
|
29
|
+
@socket = TCPSocket.open(host, port)
|
30
|
+
handshake = ""
|
31
|
+
ver_string = "GRAIL solver protocol"
|
32
|
+
#The handshake is the length of the message, the protocol string, and the version (0).
|
33
|
+
handshake << [ver_string.length].pack('N') << ver_string << "\x00\x00"
|
34
|
+
#Receive a handshake and then send one
|
35
|
+
@socket.recvfrom(handshake.length)
|
36
|
+
@socket.send(handshake, 0)
|
37
|
+
|
38
|
+
@available_packets = []
|
39
|
+
@cur_rules = []
|
40
|
+
end
|
41
|
+
|
42
|
+
def close()
|
43
|
+
@socket.close()
|
44
|
+
@connected = false
|
45
|
+
end
|
46
|
+
|
47
|
+
#Handle a message of currently unknown type
|
48
|
+
def handleMessage()
|
49
|
+
#Get the message length as n unsigned integer
|
50
|
+
inlen = (@socket.recvfrom(4)[0]).unpack('N')[0]
|
51
|
+
if (nil == inlen) then
|
52
|
+
return nil
|
53
|
+
end
|
54
|
+
inbuff = @socket.recvfrom(inlen)[0]
|
55
|
+
#Byte that indicates message type
|
56
|
+
control = inbuff.unpack('C')[0]
|
57
|
+
case control
|
58
|
+
when SUBSCRIPTION_RESPONSE
|
59
|
+
decodeSubResponse(inbuff[1, inbuff.length - 1])
|
60
|
+
return SUBSCRIPTION_RESPONSE
|
61
|
+
when SERVER_SAMPLE
|
62
|
+
decodeServerSample(inbuff[1, inbuff.length - 1])
|
63
|
+
return SERVER_SAMPLE
|
64
|
+
else
|
65
|
+
KEEP_ALIVE
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
#Decode a subscription response and store the current rules in @cur_rules
|
70
|
+
def decodeSubResponse(inbuff)
|
71
|
+
puts "Got subscription response!"
|
72
|
+
num_rules = inbuff.unpack('N')[0]
|
73
|
+
rest = inbuff[4, inbuff.length - 1]
|
74
|
+
rules = []
|
75
|
+
for i in 1..num_rules do
|
76
|
+
phy_layer, num_txers = rest.unpack('CN')
|
77
|
+
txlist = []
|
78
|
+
rest = rest[5, rest.length - 1]
|
79
|
+
for j in 1..num_txers do
|
80
|
+
txlist.push([rest[0, 16], rest[16, 16]])
|
81
|
+
rest = rest[32, rest.length - 1]
|
82
|
+
end
|
83
|
+
update_interval = unpackuint64(rest)
|
84
|
+
rest = rest[8, rest.length - 1]
|
85
|
+
rule = AggrRule.new(phy_layer, txlist, update_interval)
|
86
|
+
rules.push(rule)
|
87
|
+
end
|
88
|
+
@cur_rules = rules
|
89
|
+
end
|
90
|
+
|
91
|
+
#Decode a server sample message
|
92
|
+
def decodeServerSample(inbuff)
|
93
|
+
if (inbuff != nil)
|
94
|
+
phy_layer = inbuff.unpack('C')
|
95
|
+
rest = inbuff[1, inbuff.length - 1]
|
96
|
+
txid = unpackuint128(rest)
|
97
|
+
rxid = unpackuint128(rest[16, rest.length - 1])
|
98
|
+
rest = rest[32, rest.length - 1]
|
99
|
+
timestamp, rssi = rest.unpack('Gg')
|
100
|
+
sense_data = rest[12, rest.length - 1]
|
101
|
+
@available_packets.push(SensorSample.new(phy_layer, txid, rxid, timestamp, rssi, sense_data))
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def sendSubscription(rules)
|
106
|
+
#Start assembling a request message
|
107
|
+
buff = [SUBSCRIPTION_REQUEST].pack('C')
|
108
|
+
#Number of rules
|
109
|
+
buff += [rules.length].pack('N')
|
110
|
+
for rule in rules do
|
111
|
+
buff += [rule.phy_layer].pack('C')
|
112
|
+
buff += [rule.txers.length].pack('N')
|
113
|
+
#Push each transmitter/mask pair
|
114
|
+
for txer in rule.txers do
|
115
|
+
buff += txer.id + txer.mask
|
116
|
+
end
|
117
|
+
buff += packuint64(rule.update_interval)
|
118
|
+
end
|
119
|
+
#Send the message prepended with the length
|
120
|
+
@socket.send("#{[buff.length].pack('N')}#{buff}", 0)
|
121
|
+
|
122
|
+
#Get the subscription response
|
123
|
+
handleMessage()
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|