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,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
|