socket_switcher 0.1.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.
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