ownet 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- yield socket
131
- socket.close
190
+ begin
191
+ yield socket
192
+ ensure
193
+ socket.close
194
+ end
132
195
  end
133
196
 
134
197
  public
@@ -1,5 +1,5 @@
1
1
  require File.dirname(__FILE__)+'/connection.rb'
2
2
 
3
3
  module OWNet
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
@@ -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.1.0'
10
- s.date = '2010-12-29'
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
@@ -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: 27
4
+ hash: 23
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
8
+ - 2
9
9
  - 0
10
- version: 0.1.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: 2010-12-29 00:00:00 +00:00
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