socket_switcher 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.utilsrc +26 -0
  5. data/Gemfile +8 -0
  6. data/README.md +0 -0
  7. data/Rakefile +54 -0
  8. data/VERSION +1 -0
  9. data/arduino-receiver/receiver/receiver.ino +36 -0
  10. data/arduino/lib/.holder +0 -0
  11. data/arduino/lib/RCSwitch/RCSwitch.cpp +823 -0
  12. data/arduino/lib/RCSwitch/RCSwitch.h +144 -0
  13. data/arduino/lib/RCSwitch/examples/ReceiveDemo_Advanced/ReceiveDemo_Advanced.pde +24 -0
  14. data/arduino/lib/RCSwitch/examples/ReceiveDemo_Advanced/helperfunctions.ino +20 -0
  15. data/arduino/lib/RCSwitch/examples/ReceiveDemo_Advanced/output.ino +52 -0
  16. data/arduino/lib/RCSwitch/examples/ReceiveDemo_Simple/ReceiveDemo_Simple.pde +35 -0
  17. data/arduino/lib/RCSwitch/examples/SendDemo/SendDemo.pde +57 -0
  18. data/arduino/lib/RCSwitch/examples/TypeA_WithDIPSwitches/TypeA_WithDIPSwitches.pde +40 -0
  19. data/arduino/lib/RCSwitch/examples/TypeA_WithDIPSwitches_Lightweight/TypeA_WithDIPSwitches_Lightweight.ino +43 -0
  20. data/arduino/lib/RCSwitch/examples/TypeB_WithRotaryOrSlidingSwitches/TypeB_WithRotaryOrSlidingSwitches.pde +40 -0
  21. data/arduino/lib/RCSwitch/examples/TypeC_Intertechno/TypeC_Intertechno.pde +40 -0
  22. data/arduino/lib/RCSwitch/examples/TypeD_REV/TypeD_REV.ino +41 -0
  23. data/arduino/lib/RCSwitch/examples/Webserver/Webserver.pde +154 -0
  24. data/arduino/lib/RCSwitch/keywords.txt +57 -0
  25. data/arduino/src/switcher/switcher.ino.erb +79 -0
  26. data/config/default.yml +11 -0
  27. data/config/traffic_lights.yml +8 -0
  28. data/examples/blinkenlights.rb +25 -0
  29. data/lib/socket_switcher.rb +7 -0
  30. data/lib/socket_switcher/device.rb +24 -0
  31. data/lib/socket_switcher/errors.rb +26 -0
  32. data/lib/socket_switcher/port.rb +134 -0
  33. data/lib/socket_switcher/version.rb +8 -0
  34. data/socket_switcher.gemspec +49 -0
  35. data/spec/socket_switcher/device_spec.rb +37 -0
  36. data/spec/socket_switcher/port_spec.rb +216 -0
  37. data/spec/spec_helper.rb +9 -0
  38. metadata +176 -0
@@ -0,0 +1,26 @@
1
+ module SocketSwitcher
2
+ class SocketSwitcherError < StandardError
3
+ def self.for_device(device, message)
4
+ new(message).tap do |error|
5
+ error.device = device
6
+ end
7
+ end
8
+
9
+ def self.wrap(device, exception)
10
+ for_device(device, "wrapped #{exception.class}: #{exception}").tap do |e|
11
+ e.set_backtrace exception.backtrace
12
+ end
13
+ end
14
+
15
+ attr_accessor :device
16
+ end
17
+
18
+ class CommunicationError < SocketSwitcherError
19
+ end
20
+
21
+ class InvalidResponse < SocketSwitcherError
22
+ end
23
+
24
+ class TryAgainError < SocketSwitcherError
25
+ end
26
+ end
@@ -0,0 +1,134 @@
1
+ require 'serialport'
2
+ require 'thread'
3
+
4
+ class SocketSwitcher::Port
5
+ def initialize(specifier, debug: false, timeout: 2_000, attempts: 3)
6
+ @serial_port = SerialPort.new(
7
+ specifier,
8
+ "baud" => 9600,
9
+ "data_bits" => 8,
10
+ "stop_bits" => 1,
11
+ "parity" => 0
12
+ )
13
+ @debug = debug
14
+ @timeout = timeout
15
+ @attempts = attempts
16
+ @devices = {}
17
+ @mutex = Mutex.new
18
+ rescue => e
19
+ raise SocketSwitcher::CommunicationError.wrap(nil, e)
20
+ end
21
+
22
+ attr_accessor :debug
23
+
24
+ attr_accessor :timeout
25
+
26
+ def ready?
27
+ request do |response|
28
+ if response =~ /^RDY (\d+) (\d+)/
29
+ # initalize all devices
30
+ ($1.to_i..$2.to_i).each { |i| device(i) }
31
+ return true
32
+ else
33
+ try_again or return false
34
+ end
35
+ end
36
+ end
37
+
38
+ def devices(range = nil)
39
+ if range
40
+ devices.select { |d| range.member?(d.number) }
41
+ else
42
+ ready?
43
+ @devices.values
44
+ end
45
+ end
46
+
47
+ def device(number)
48
+ @devices[number] ||= SocketSwitcher::Device.new(self, number)
49
+ end
50
+
51
+ private
52
+
53
+ def debug_output(message)
54
+ if debug
55
+ STDERR.puts message
56
+ end
57
+ end
58
+
59
+ def set_state(device, state)
60
+ request(device, state) do |response|
61
+ case response
62
+ when /^ACK/
63
+ return true
64
+ when /^RDY/
65
+ try_again SocketSwitcher::TryAgainError.for_device(
66
+ device, "not ready after #@attempt attempts"
67
+ )
68
+ when /^NAK device/
69
+ raise SocketSwitcher::InvalidResponse.for_device(
70
+ device, "invalid device number: #{device.number}")
71
+ when /^NAK state/
72
+ raise SocketSwitcher::InvalidResponse.for_device(
73
+ device, "invalid state number: #{state}")
74
+ else
75
+ try_again SocketSwitcher::TryAgainError.for_device(
76
+ device, "unexpected response #{response.inspect} after #@attempt attempts")
77
+ end
78
+ end
79
+ end
80
+
81
+ def synchronize(&block)
82
+ @mutex.synchronize(&block)
83
+ end
84
+
85
+ def try_again(exception = nil)
86
+ if @attempt < @attempts
87
+ @attempt += 1
88
+ sleep @timeout.to_f / 1000
89
+ throw :again
90
+ elsif exception
91
+ raise exception
92
+ else
93
+ return false
94
+ end
95
+ end
96
+
97
+ def set_timeout
98
+ @serial_port.read_timeout = @timeout
99
+ begin
100
+ @serial_port.write_timeout = @timeout
101
+ rescue NotImplementedError
102
+ end
103
+ end
104
+
105
+ def request(device = nil, state = nil, &block)
106
+ synchronize do
107
+ begin
108
+ @attempt = 1
109
+ loop do
110
+ set_timeout
111
+ catch :again do
112
+ query = ''
113
+ device && state and query << "#{device.number} #{state}"
114
+ query << "\r\n"
115
+ debug_output "<< #{query.inspect}"
116
+ @serial_port.print(query)
117
+ unless response = @serial_port.gets
118
+ try_again SocketSwitcher::TryAgainError.for_device(
119
+ device, "timeout after #@attempt attempts"
120
+ )
121
+ end
122
+ debug_output ">> #{response.inspect}"
123
+ block[response]
124
+ end
125
+ end
126
+ rescue => e
127
+ unless SocketSwitcher::SocketSwitcherError === e
128
+ e = SocketSwitcher::CommunicationError.wrap(device, e)
129
+ end
130
+ raise e
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,8 @@
1
+ module SocketSwitcher
2
+ # SocketSwitcher version
3
+ VERSION = '0.1.0'
4
+ VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
+ VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
+ VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
7
+ VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
8
+ end
@@ -0,0 +1,49 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # stub: socket_switcher 0.1.0 ruby lib
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "socket_switcher"
6
+ s.version = "0.1.0"
7
+
8
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
9
+ s.require_paths = ["lib"]
10
+ s.authors = ["Florian Frank"]
11
+ s.date = "2015-01-02"
12
+ s.description = "Switches devices on and off. Yes, it does\u{2026}"
13
+ s.email = "flori@ping.de"
14
+ s.extra_rdoc_files = ["README.md", "lib/socket_switcher.rb", "lib/socket_switcher/device.rb", "lib/socket_switcher/errors.rb", "lib/socket_switcher/port.rb", "lib/socket_switcher/version.rb"]
15
+ s.files = [".gitignore", ".rspec", ".utilsrc", "Gemfile", "README.md", "Rakefile", "VERSION", "arduino-receiver/receiver/receiver.ino", "arduino/lib/.holder", "arduino/lib/RCSwitch/RCSwitch.cpp", "arduino/lib/RCSwitch/RCSwitch.h", "arduino/lib/RCSwitch/examples/ReceiveDemo_Advanced/ReceiveDemo_Advanced.pde", "arduino/lib/RCSwitch/examples/ReceiveDemo_Advanced/helperfunctions.ino", "arduino/lib/RCSwitch/examples/ReceiveDemo_Advanced/output.ino", "arduino/lib/RCSwitch/examples/ReceiveDemo_Simple/ReceiveDemo_Simple.pde", "arduino/lib/RCSwitch/examples/SendDemo/SendDemo.pde", "arduino/lib/RCSwitch/examples/TypeA_WithDIPSwitches/TypeA_WithDIPSwitches.pde", "arduino/lib/RCSwitch/examples/TypeA_WithDIPSwitches_Lightweight/TypeA_WithDIPSwitches_Lightweight.ino", "arduino/lib/RCSwitch/examples/TypeB_WithRotaryOrSlidingSwitches/TypeB_WithRotaryOrSlidingSwitches.pde", "arduino/lib/RCSwitch/examples/TypeC_Intertechno/TypeC_Intertechno.pde", "arduino/lib/RCSwitch/examples/TypeD_REV/TypeD_REV.ino", "arduino/lib/RCSwitch/examples/Webserver/Webserver.pde", "arduino/lib/RCSwitch/keywords.txt", "arduino/src/switcher/switcher.ino.erb", "config/default.yml", "config/traffic_lights.yml", "examples/blinkenlights.rb", "lib/socket_switcher.rb", "lib/socket_switcher/device.rb", "lib/socket_switcher/errors.rb", "lib/socket_switcher/port.rb", "lib/socket_switcher/version.rb", "socket_switcher.gemspec", "spec/socket_switcher/device_spec.rb", "spec/socket_switcher/port_spec.rb", "spec/spec_helper.rb"]
16
+ s.homepage = "http://github.com/flori/socket_switcher"
17
+ s.licenses = ["GPL-2"]
18
+ s.rdoc_options = ["--title", "SocketSwitcher - Switches devices on and off", "--main", "README.md"]
19
+ s.rubygems_version = "2.2.2"
20
+ s.summary = "Switches devices on and off"
21
+ s.test_files = ["spec/socket_switcher/device_spec.rb", "spec/socket_switcher/port_spec.rb", "spec/spec_helper.rb"]
22
+
23
+ if s.respond_to? :specification_version then
24
+ s.specification_version = 4
25
+
26
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
27
+ s.add_development_dependency(%q<gem_hadar>, ["~> 1.1.2"])
28
+ s.add_development_dependency(%q<simplecov>, ["~> 0.9"])
29
+ s.add_development_dependency(%q<rspec>, ["~> 3.0"])
30
+ s.add_runtime_dependency(%q<tins>, ["~> 1.0"])
31
+ s.add_runtime_dependency(%q<serialport>, ["~> 1.3"])
32
+ s.add_runtime_dependency(%q<complex_config>, [">= 0"])
33
+ else
34
+ s.add_dependency(%q<gem_hadar>, ["~> 1.1.2"])
35
+ s.add_dependency(%q<simplecov>, ["~> 0.9"])
36
+ s.add_dependency(%q<rspec>, ["~> 3.0"])
37
+ s.add_dependency(%q<tins>, ["~> 1.0"])
38
+ s.add_dependency(%q<serialport>, ["~> 1.3"])
39
+ s.add_dependency(%q<complex_config>, [">= 0"])
40
+ end
41
+ else
42
+ s.add_dependency(%q<gem_hadar>, ["~> 1.1.2"])
43
+ s.add_dependency(%q<simplecov>, ["~> 0.9"])
44
+ s.add_dependency(%q<rspec>, ["~> 3.0"])
45
+ s.add_dependency(%q<tins>, ["~> 1.0"])
46
+ s.add_dependency(%q<serialport>, ["~> 1.3"])
47
+ s.add_dependency(%q<complex_config>, [">= 0"])
48
+ end
49
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe SocketSwitcher::Device do
4
+ let :port do
5
+ double('Port')
6
+ end
7
+
8
+ let :device do
9
+ SocketSwitcher::Device.new(port, 23)
10
+ end
11
+
12
+ it 'knows about port' do
13
+ expect(device.port).to eq port
14
+ end
15
+
16
+ it 'knows its own number' do
17
+ expect(device.number).to eq 23
18
+ end
19
+
20
+ it 'sets state to 1 after receiving on' do
21
+ expect(port).to receive(:set_state).with(device, 1)
22
+ device.on
23
+ end
24
+
25
+ it 'sets state to 0 after receiving off' do
26
+ expect(port).to receive(:set_state).with(device, 0)
27
+ device.off
28
+ end
29
+
30
+ it 'has a nice #to_s' do
31
+ expect(device.to_s).to eq '#<SocketSwitcher::Device number=23>'
32
+ end
33
+
34
+ it 'has a nice #inspect' do
35
+ expect(device.inspect).to eq '#<SocketSwitcher::Device number=23>'
36
+ end
37
+ end
@@ -0,0 +1,216 @@
1
+ require 'spec_helper'
2
+
3
+ describe SocketSwitcher::Port do
4
+ let :serial_port do
5
+ double('SerialPort').tap do |sp|
6
+ allow(sp).to receive(:read_timeout=)
7
+ allow(sp).to receive(:write_timeout=)
8
+ allow(sp).to receive(:print).with("\r\n")
9
+ end
10
+ end
11
+
12
+ before do
13
+ allow(SerialPort).to receive(:new).and_return serial_port
14
+ end
15
+
16
+ let :port do
17
+ SocketSwitcher::Port.new('foo')
18
+ end
19
+
20
+ context 'instantiation and configuration' do
21
+ it 'can be instantiated' do
22
+ expect(port).to be_a SocketSwitcher::Port
23
+ expect(port.debug).to eq false
24
+ expect(port.timeout).to eq 2_000
25
+ end
26
+
27
+ it 'can be instantiated in debugging mode' do
28
+ expect(SocketSwitcher::Port.new('foo', debug: true).debug).to eq true
29
+ end
30
+
31
+ it 'has a configurable debugging mode' do
32
+ port.debug = true
33
+ expect(port.debug).to eq true
34
+ end
35
+
36
+ it 'can be instantiated with a timeout' do
37
+ expect(SocketSwitcher::Port.new('foo', timeout: 666).timeout).to eq 666
38
+ end
39
+
40
+ it 'has a configurable timeout' do
41
+ port.timeout = 4_000
42
+ expect(port.timeout).to eq 4_000
43
+ end
44
+
45
+ it 'wraps all unknown errors in SocketSwitcher::CommunicationError' do
46
+ expect(SerialPort).to receive(:new).and_raise(Errno::ENXIO)
47
+ expect { port }.to raise_error SocketSwitcher::CommunicationError
48
+ end
49
+ end
50
+
51
+ describe '#ready?' do
52
+ context 'sucessfull' do
53
+ before do
54
+ allow(serial_port).to receive(:gets).and_return("RDY 0 3\r\n")
55
+ end
56
+
57
+ it 'can query the ready state' do
58
+ expect(port).to be_ready
59
+ end
60
+
61
+ context 'with debugging' do
62
+ let :port do
63
+ SocketSwitcher::Port.new('foo', debug: true)
64
+ end
65
+
66
+ it 'can query the ready state with debugging' do
67
+ expect(STDERR).to receive(:puts).at_least(1)
68
+ expect(port).to receive(:debug_output).with('<< "\r\n"').and_call_original
69
+ expect(port).to receive(:debug_output).with('>> "RDY 0 3\r\n"').and_call_original
70
+ expect(port).to be_ready
71
+ end
72
+ end
73
+ end
74
+
75
+ context 'some problem' do
76
+ it 'does not handle too many garbled responses, but returns false' do
77
+ expect(serial_port).to receive(:print).with("\r\n").at_least(1)
78
+ expect(serial_port).to receive(:gets).and_return("RD…garbled…").at_least(1)
79
+ expect(port).to receive(:sleep).with(port.timeout.to_f / 1000).at_least(1)
80
+ expect(port).not_to be_ready
81
+ end
82
+ end
83
+ end
84
+
85
+ context 'timeout' do
86
+ let :serial_port do
87
+ double('SerialPort').tap do |sp|
88
+ allow(sp).to receive(:print).with("\r\n")
89
+ allow(sp).to receive(:gets).and_return("RDY 0 3\r\n")
90
+ end
91
+ end
92
+
93
+ it 'sets the read_timeout and write_timeout' do
94
+ expect(serial_port).to receive(:read_timeout=)
95
+ expect(serial_port).to receive(:write_timeout=)
96
+ expect(port).to be_ready
97
+ end
98
+
99
+ it 'ingores not implemented write_timeout' do
100
+ expect(serial_port).to receive(:read_timeout=)
101
+ expect(serial_port).to receive(:write_timeout=).and_raise(NotImplementedError)
102
+ expect(port).to be_ready
103
+ end
104
+ end
105
+
106
+ describe '#devices' do
107
+ context 'successful' do
108
+ before do
109
+ allow(serial_port).to receive(:gets).and_return("RDY 0 3\r\n")
110
+ end
111
+
112
+ it 'returns all supported devices' do
113
+ expect(port.devices.size).to eq 4
114
+ end
115
+
116
+ it 'can return a range of devices' do
117
+ expect(port.devices(0..1).size).to eq 2
118
+ expect(port.devices([0, 3]).size).to eq 2
119
+ end
120
+ end
121
+
122
+ context 'some problem' do
123
+ it 'returns all supported devices' do
124
+ expect(serial_port).to receive(:gets).and_return("RD…garbled…")
125
+ expect(serial_port).to receive(:gets).and_return("RDY 0 3\r\n")
126
+ expect(port).to receive(:sleep).with(port.timeout.to_f / 1000)
127
+ expect(port).to receive(:try_again).and_call_original
128
+ expect(port.devices.size).to eq 4
129
+ end
130
+ end
131
+ end
132
+
133
+ describe '#device' do
134
+ let :device do
135
+ port.device(23)
136
+ end
137
+
138
+ it 'returns a device for number' do
139
+ expect(device.number).to eq 23
140
+ end
141
+
142
+ it 'can be switched on' do
143
+ expect(serial_port).to receive(:print).with("23 1\r\n")
144
+ expect(serial_port).to receive(:gets).and_return("ACK\r\n")
145
+ expect(device.on).to eq true
146
+ end
147
+
148
+ it 'can be switched off' do
149
+ expect(serial_port).to receive(:print).with("23 0\r\n")
150
+ expect(serial_port).to receive(:gets).and_return("ACK\r\n")
151
+ expect(device.off).to eq true
152
+ end
153
+
154
+ it 'throws exceptions for device errors' do
155
+ expect(serial_port).to receive(:print).with("23 0\r\n")
156
+ expect(serial_port).to receive(:gets).and_return("NAK device\r\n")
157
+ expect { device.off }.to raise_error SocketSwitcher::InvalidResponse
158
+ end
159
+
160
+ it 'throws exceptions for state errors' do
161
+ expect(serial_port).to receive(:print).with("23 1\r\n")
162
+ expect(serial_port).to receive(:gets).and_return("NAK state\r\n")
163
+ expect { device.on }.to raise_error SocketSwitcher::InvalidResponse
164
+ end
165
+
166
+ it 'handles some garbled responses' do
167
+ expect(serial_port).to receive(:print).with("23 1\r\n").twice
168
+ expect(serial_port).to receive(:gets).and_return("RD…garbled…")
169
+ expect(port).to receive(:sleep).with(port.timeout.to_f / 1000)
170
+ expect(serial_port).to receive(:gets).and_return("ACK\r\n")
171
+ expect(device.on).to eq true
172
+ end
173
+
174
+ it 'does not handle too many unexpected responses' do
175
+ expect(serial_port).to receive(:print).with("23 1\r\n").at_least(1)
176
+ expect(serial_port).to receive(:gets).and_return("RD…garbled…").at_least(1)
177
+ expect(port).to receive(:sleep).with(port.timeout.to_f / 1000).at_least(1)
178
+ expect { device.on }.to raise_error SocketSwitcher::TryAgainError
179
+ end
180
+
181
+ it 'can handle some ready responses' do
182
+ expect(serial_port).to receive(:print).with("23 0\r\n").twice
183
+ expect(serial_port).to receive(:gets).and_return("RDY 0 3\r\n")
184
+ expect(port).to receive(:sleep).with(port.timeout.to_f / 1000)
185
+ expect(serial_port).to receive(:gets).and_return("ACK\r\n")
186
+ expect(device.off).to eq true
187
+ end
188
+
189
+ it 'does not handle too many ready responses' do
190
+ expect(serial_port).to receive(:print).with("23 0\r\n").at_least(1)
191
+ expect(serial_port).to receive(:gets).and_return("RDY 0 3\r\n").at_least(1)
192
+ expect(port).to receive(:sleep).with(port.timeout.to_f / 1000).at_least(1)
193
+ expect { device.off }.to raise_error SocketSwitcher::TryAgainError
194
+ end
195
+
196
+ it 'can handle some timeouts' do
197
+ expect(serial_port).to receive(:print).with("23 0\r\n").twice
198
+ expect(serial_port).to receive(:gets).and_return(nil)
199
+ expect(port).to receive(:sleep).with(port.timeout.to_f / 1000)
200
+ expect(serial_port).to receive(:gets).and_return("ACK\r\n")
201
+ expect(device.off).to eq true
202
+ end
203
+
204
+ it 'does not handle too many ready responses' do
205
+ expect(serial_port).to receive(:print).with("23 0\r\n").at_least(1)
206
+ expect(serial_port).to receive(:gets).and_return(nil).at_least(1)
207
+ expect(port).to receive(:sleep).with(port.timeout.to_f / 1000).at_least(1)
208
+ expect { device.off }.to raise_error SocketSwitcher::TryAgainError
209
+ end
210
+
211
+ it 'wraps all unknown errors in SocketSwitcher::CommunicationError' do
212
+ expect(serial_port).to receive(:print).and_raise(Errno::ENXIO)
213
+ expect { device.on }.to raise_error SocketSwitcher::CommunicationError
214
+ end
215
+ end
216
+ end