ownet 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/connection.rb +68 -5
- data/lib/ownet.rb +1 -1
- data/ownet.gemspec +4 -2
- data/test/mock_connection_test.rb +77 -0
- data/test/mock_owserver.rb +160 -0
- data/test/test_helper.rb +10 -0
- metadata +8 -4
data/lib/connection.rb
CHANGED
@@ -34,7 +34,7 @@ module OWNet
|
|
34
34
|
|
35
35
|
def initialize(opts={})
|
36
36
|
opts.each {|name, value| self.send(name.to_s+'=', value)}
|
37
|
-
@flags = 258
|
37
|
+
@flags = 258 #FIXME: What is this?
|
38
38
|
end
|
39
39
|
|
40
40
|
private
|
@@ -43,7 +43,7 @@ module OWNet
|
|
43
43
|
data_len = 0
|
44
44
|
case self.function
|
45
45
|
when READ
|
46
|
-
data_len = 8192
|
46
|
+
data_len = 8192 #FIXME: What is this?
|
47
47
|
when WRITE
|
48
48
|
payload_len = path.size + 1 + value.size + 1
|
49
49
|
data_len = value.size
|
@@ -73,11 +73,12 @@ module OWNet
|
|
73
73
|
def initialize(socket)
|
74
74
|
data = socket.read(24)
|
75
75
|
raise ShortRead if !data || data.size != 24
|
76
|
-
|
76
|
+
|
77
77
|
version, @payload_len, self.return_value, @format_flags,
|
78
78
|
@data_len, @offset = data.unpack('NNNNNN')
|
79
79
|
|
80
80
|
if @payload_len > 0 && !isping?
|
81
|
+
#FIXME: Guard against a short read here
|
81
82
|
@data = socket.read(@payload_len)[@offset..@data_len+@offset-1]
|
82
83
|
end
|
83
84
|
end
|
@@ -102,6 +103,65 @@ module OWNet
|
|
102
103
|
# Connection.new(:port=>20200)
|
103
104
|
# #Connect to a remote server on a non-standard port:
|
104
105
|
# Connection.new(:server=>"my.server.com", :port=>20200)
|
106
|
+
def initialize(opts={})
|
107
|
+
@conn = RawConnection.new(opts)
|
108
|
+
@serialcache = {}
|
109
|
+
end
|
110
|
+
|
111
|
+
def read(path); do_op(:read, path); end
|
112
|
+
def dir(path); do_op(:dir, path); end
|
113
|
+
def write(path, value); @conn.send(:write, path, value); end
|
114
|
+
|
115
|
+
private
|
116
|
+
def do_op(op, path)
|
117
|
+
basepath = "/"
|
118
|
+
if path[0..8] == "/uncached"
|
119
|
+
path = path[9..-1]
|
120
|
+
basepath = "/uncached"
|
121
|
+
end
|
122
|
+
serial = path[1..15]
|
123
|
+
ret = if cachepath = @serialcache[serial]
|
124
|
+
@conn.send(op, cachepath+path[16..-1])
|
125
|
+
else
|
126
|
+
@conn.send(op, path)
|
127
|
+
end
|
128
|
+
if (ret.nil? or ret == []) and serial =~ /[0-9A-Z]{2,2}\.[0-9A-Z]{12,12}/
|
129
|
+
if newbasepath = find_recursive(serial, basepath)
|
130
|
+
@serialcache[serial] = newbasepath
|
131
|
+
newpath = newbasepath+path[16..-1]
|
132
|
+
ret = @conn.send(op, newpath)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
ret
|
136
|
+
end
|
137
|
+
|
138
|
+
def find_recursive(serial,path,depth=0)
|
139
|
+
if depth > 5
|
140
|
+
return nil
|
141
|
+
end
|
142
|
+
dirs = @conn.send(:dir, path)||[]
|
143
|
+
dirs.each do |dir|
|
144
|
+
dir = dir.split("/")[-1]
|
145
|
+
if dir == serial
|
146
|
+
return path+"/"+serial
|
147
|
+
elsif dir =~ /1F\.[0-9A-Z]{12,12}/ #DS2409
|
148
|
+
['main','aux'].each do |side|
|
149
|
+
split = path.split("/")
|
150
|
+
split.delete("")
|
151
|
+
split += [dir,side]
|
152
|
+
newpath = "/"+split.join("/")
|
153
|
+
ret = find_recursive(serial,newpath,depth+1)
|
154
|
+
return ret if ret
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
nil
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
class RawConnection
|
163
|
+
# Connection without any of the hub discovery niceties
|
164
|
+
|
105
165
|
def initialize(opts={})
|
106
166
|
@server = opts[:server] || 'localhost'
|
107
167
|
@port = opts[:port] || 4304
|
@@ -127,8 +187,11 @@ module OWNet
|
|
127
187
|
def owconnect(&block)
|
128
188
|
socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
129
189
|
socket.connect(Socket.pack_sockaddr_in(@port, @server))
|
130
|
-
|
131
|
-
|
190
|
+
begin
|
191
|
+
yield socket
|
192
|
+
ensure
|
193
|
+
socket.close
|
194
|
+
end
|
132
195
|
end
|
133
196
|
|
134
197
|
public
|
data/lib/ownet.rb
CHANGED
data/ownet.gemspec
CHANGED
@@ -6,8 +6,8 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.platform = Gem::Platform::RUBY
|
7
7
|
|
8
8
|
s.name = 'ownet'
|
9
|
-
s.version = '0.
|
10
|
-
s.date = '
|
9
|
+
s.version = '0.2.0'
|
10
|
+
s.date = '2011-07-05'
|
11
11
|
|
12
12
|
s.summary = "Client to connect to one-wire devices through owserver of the OWFS project"
|
13
13
|
s.description = "A simple client that interfaces with owserver from the owfs project"
|
@@ -33,6 +33,8 @@ Gem::Specification.new do |s|
|
|
33
33
|
lib/ownet.rb
|
34
34
|
ownet.gemspec
|
35
35
|
test/connection_test.rb
|
36
|
+
test/mock_connection_test.rb
|
37
|
+
test/mock_owserver.rb
|
36
38
|
test/test_helper.rb
|
37
39
|
]
|
38
40
|
# = MANIFEST =
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/test_helper.rb'
|
2
|
+
|
3
|
+
class TestMockConnection < Test::Unit::TestCase
|
4
|
+
BASE_DIR = ["/1F.67C6697351FF", "/10.4AEC29CDBAAB", "/bus.0", "/uncached",
|
5
|
+
"/settings", "/system", "/statistics", "/structure",
|
6
|
+
"/simultaneous", "/alarm"]
|
7
|
+
|
8
|
+
def test_read
|
9
|
+
with_mock_owserver('/10.4AEC29CDBAAB/temperature'=>'22.35') do
|
10
|
+
c = OWNet::Connection.new
|
11
|
+
assert_equal 22.35, c.read("/10.4AEC29CDBAAB/temperature")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_dir
|
16
|
+
with_mock_owserver('/'=>BASE_DIR) do
|
17
|
+
c = OWNet::Connection.new
|
18
|
+
assert_equal BASE_DIR, c.dir("/")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_write
|
23
|
+
with_mock_owserver do |server|
|
24
|
+
c = OWNet::Connection.new
|
25
|
+
assert_equal 0, c.write("/test","abc")
|
26
|
+
assert_equal "abc", server.req_data
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_ops_with_hub
|
31
|
+
fakedir = ["no_such_file","another"]
|
32
|
+
temperature = "/10.85EC3B020800"
|
33
|
+
humidity = "/26.CFD6F1000000"
|
34
|
+
hubs = ["/1F.A65A05000000", "/1F.365B05000000", "/1F.B15A05000000"]
|
35
|
+
paths = {'/'=>hubs}
|
36
|
+
channels = hubs.map{|hub| ['main','aux'].map {|suffix| hub+'/'+suffix}}.flatten
|
37
|
+
channels.each {|channel| paths[channel] = []}
|
38
|
+
paths[channels[0]] = [temperature,humidity].map{|suffix| channels[0]+suffix}
|
39
|
+
paths[channels[0]+temperature+'/temperature'] = '25'
|
40
|
+
paths[channels[0]+temperature] = fakedir
|
41
|
+
paths[channels[0]+humidity+'/humidity'] = '50'
|
42
|
+
paths[channels[0]+humidity] = fakedir
|
43
|
+
with_mock_owserver(paths) do |server|
|
44
|
+
['','/uncached'].each do |prefix|
|
45
|
+
c = OWNet::Connection.new
|
46
|
+
server.nrequests = 0
|
47
|
+
assert_equal 25, c.read(prefix+temperature+"/temperature")
|
48
|
+
assert_reqs server, 4
|
49
|
+
server.nrequests = 0
|
50
|
+
assert_equal fakedir, c.dir(prefix+temperature)
|
51
|
+
assert_reqs server, 1
|
52
|
+
assert_equal 50, c.read(prefix+humidity+"/humidity")
|
53
|
+
assert_equal fakedir, c.dir(prefix+humidity)
|
54
|
+
assert_equal nil, c.read(prefix+"/no_such_path")
|
55
|
+
assert_equal [], c.dir(prefix+"/no_such_path")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def assert_reqs(server,reqs)
|
61
|
+
assert_equal reqs, server.nrequests, "Expecting to do #{reqs} requests but did #{server.nrequests}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_recursive_stop_with_hub
|
65
|
+
hub = "/1F.A65A05000000"
|
66
|
+
paths = {}
|
67
|
+
(0..20).each do |i|
|
68
|
+
paths["/"+(hub+"/main")*i] = [(hub+"/main")*i+"/"+hub]
|
69
|
+
paths["/"+(hub+"/aux")*i] = [(hub+"/aux")*i+"/"+hub]
|
70
|
+
end
|
71
|
+
with_mock_owserver(paths) do |server|
|
72
|
+
c = OWNet::Connection.new
|
73
|
+
assert_equal nil, c.read("/10.85EC3B020800/temperature")
|
74
|
+
assert_reqs server, 20
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module MockOWServer
|
5
|
+
ERROR = 0
|
6
|
+
NOP = 1
|
7
|
+
READ = 2
|
8
|
+
WRITE = 3
|
9
|
+
DIR = 4
|
10
|
+
SIZE = 5
|
11
|
+
PRESENCE = 6
|
12
|
+
|
13
|
+
# Exception raised when there's a short read from the client
|
14
|
+
class ClientShortRead < RuntimeError
|
15
|
+
def to_s
|
16
|
+
"Short read communicating with owserver"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Encapsulates a request to the server
|
21
|
+
class Request
|
22
|
+
attr_accessor :function, :path, :data, :flags
|
23
|
+
|
24
|
+
def initialize(socket)
|
25
|
+
data = socket.read(24)
|
26
|
+
raise ClientShortRead if !data || data.size != 24
|
27
|
+
zero, payload_len, self.function, self.flags, data_len, zero = data.unpack('NNNNNN')
|
28
|
+
if payload_len > 0
|
29
|
+
payload = socket.read(payload_len)
|
30
|
+
raise ClientShortRead if !payload || payload.size != payload_len
|
31
|
+
if self.function == WRITE
|
32
|
+
self.path = payload[0..-(data_len+2)]
|
33
|
+
self.data = payload[-(data_len+1)..-2]
|
34
|
+
else
|
35
|
+
self.path = payload[0..-2]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Encapsulates a response from owserver
|
42
|
+
class Response
|
43
|
+
attr_accessor :data, :return_value, :flags
|
44
|
+
|
45
|
+
def initialize(opts={})
|
46
|
+
opts.each {|name, value| self.send(name.to_s+'=', value)}
|
47
|
+
@return_value ||= 0
|
48
|
+
@flags ||= 258
|
49
|
+
end
|
50
|
+
|
51
|
+
def header
|
52
|
+
data_len = (@data ? @data.size : 0)
|
53
|
+
payload_len = (@data ? @data.size+1 : 0)
|
54
|
+
[0, payload_len, self.return_value, self.flags, data_len, 0].pack('NNNNNN')
|
55
|
+
end
|
56
|
+
|
57
|
+
def write(socket)
|
58
|
+
socket.write(header)
|
59
|
+
socket.write(data + "\000") if @data
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Server
|
64
|
+
attr_accessor :paths
|
65
|
+
|
66
|
+
def req_path
|
67
|
+
@mutex.synchronize do
|
68
|
+
@req_path
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def req_data
|
73
|
+
@mutex.synchronize do
|
74
|
+
@req_data
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def nrequests
|
79
|
+
@mutex.synchronize do
|
80
|
+
@nrequests
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def nrequests=(value)
|
85
|
+
@mutex.synchronize do
|
86
|
+
@nrequests=value
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def initialize(opts={})
|
91
|
+
@address = opts[:address] || 'localhost'
|
92
|
+
@port = opts[:port] || 4304
|
93
|
+
paths = opts[:paths] || {}
|
94
|
+
@paths = {}
|
95
|
+
paths.each do |k,v|
|
96
|
+
canon = canonical_path(k)
|
97
|
+
@paths[canon] = v
|
98
|
+
@paths[canonical_path("uncached/"+canon)] = v
|
99
|
+
end
|
100
|
+
@mutex = Mutex.new
|
101
|
+
end
|
102
|
+
|
103
|
+
def run!
|
104
|
+
@stopped = false
|
105
|
+
@nrequests = 0
|
106
|
+
@thread = Thread.new do
|
107
|
+
socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
108
|
+
socket.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
109
|
+
socket.bind(Socket.pack_sockaddr_in(@port, @address))
|
110
|
+
socket.listen 10
|
111
|
+
while !@stopped
|
112
|
+
begin
|
113
|
+
client, client_sockaddr = socket.accept_nonblock
|
114
|
+
respond(client)
|
115
|
+
rescue Errno::EAGAIN
|
116
|
+
sleep 0.1
|
117
|
+
end
|
118
|
+
end
|
119
|
+
socket.close
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def respond(client)
|
124
|
+
@mutex.synchronize do
|
125
|
+
req = Request.new(client)
|
126
|
+
@req_path = req.path
|
127
|
+
@req_data = req.data
|
128
|
+
@nrequests += 1
|
129
|
+
if req.path[0..1] == '//'
|
130
|
+
$stderr.puts "Double slash path asked for: #{req.path}"
|
131
|
+
Response.new.write(client)
|
132
|
+
else
|
133
|
+
case req.function
|
134
|
+
when READ
|
135
|
+
Response.new(:data => @paths[canonical_path(req.path)]).write(client)
|
136
|
+
when DIR
|
137
|
+
(@paths[canonical_path(req.path)]||[]).each do |dir|
|
138
|
+
Response.new(:data => dir).write(client)
|
139
|
+
end
|
140
|
+
Response.new.write(client)
|
141
|
+
when WRITE
|
142
|
+
Response.new.write(client)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def stop!
|
149
|
+
@stopped = true
|
150
|
+
@thread.join
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
def canonical_path(path)
|
155
|
+
split = path.split("/")
|
156
|
+
split.delete("")
|
157
|
+
split.join("/")
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'test/unit'
|
2
2
|
require 'yaml'
|
3
3
|
require 'fileutils'
|
4
|
+
require File.dirname(__FILE__)+'/mock_owserver'
|
4
5
|
require File.dirname(__FILE__)+'/../lib/ownet.rb'
|
5
6
|
|
6
7
|
class Test::Unit::TestCase
|
@@ -16,4 +17,13 @@ class Test::Unit::TestCase
|
|
16
17
|
Process.kill("TERM", pid)
|
17
18
|
Process.waitpid(pid)
|
18
19
|
end
|
20
|
+
def with_mock_owserver(paths=nil)
|
21
|
+
server = MockOWServer::Server.new(:paths => paths)
|
22
|
+
server.run!
|
23
|
+
begin
|
24
|
+
yield server
|
25
|
+
ensure
|
26
|
+
server.stop!
|
27
|
+
end
|
28
|
+
end
|
19
29
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ownet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 2
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- "Pedro C\xC3\xB4rte-Real"
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-07-05 00:00:00 +01:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -38,6 +38,8 @@ files:
|
|
38
38
|
- lib/ownet.rb
|
39
39
|
- ownet.gemspec
|
40
40
|
- test/connection_test.rb
|
41
|
+
- test/mock_connection_test.rb
|
42
|
+
- test/mock_owserver.rb
|
41
43
|
- test/test_helper.rb
|
42
44
|
has_rdoc: true
|
43
45
|
homepage: https://github.com/pedrocr/ownet
|
@@ -78,4 +80,6 @@ specification_version: 2
|
|
78
80
|
summary: Client to connect to one-wire devices through owserver of the OWFS project
|
79
81
|
test_files:
|
80
82
|
- test/connection_test.rb
|
83
|
+
- test/mock_connection_test.rb
|
84
|
+
- test/mock_owserver.rb
|
81
85
|
- test/test_helper.rb
|