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.
@@ -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