ownet 0.1.0 → 0.2.0
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.
- 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
|