hacky_hal 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.gitignore +3 -0
  2. data/Gemfile +12 -0
  3. data/LICENSE +20 -0
  4. data/README.markdown +57 -0
  5. data/hacky_hal.gemspec +20 -0
  6. data/lib/hacky_hal/device_controllers/base.rb +14 -0
  7. data/lib/hacky_hal/device_controllers/epson_projector.rb +180 -0
  8. data/lib/hacky_hal/device_controllers/generic_serial_port.rb +108 -0
  9. data/lib/hacky_hal/device_controllers/generic_ssh.rb +74 -0
  10. data/lib/hacky_hal/device_controllers/io_gear_avior_hdmi_switch.rb +58 -0
  11. data/lib/hacky_hal/device_controllers/linux_computer.rb +36 -0
  12. data/lib/hacky_hal/device_controllers/osx_computer.rb +19 -0
  13. data/lib/hacky_hal/device_controllers/roku.rb +121 -0
  14. data/lib/hacky_hal/device_controllers/yamaha_av_receiver.rb +202 -0
  15. data/lib/hacky_hal/device_resolvers/base.rb +9 -0
  16. data/lib/hacky_hal/device_resolvers/ssdp.rb +36 -0
  17. data/lib/hacky_hal/device_resolvers/static_uri.rb +17 -0
  18. data/lib/hacky_hal/log.rb +33 -0
  19. data/lib/hacky_hal/options.rb +21 -0
  20. data/lib/hacky_hal/registry.rb +22 -0
  21. data/lib/hacky_hal/util.rb +23 -0
  22. data/lib/hacky_hal.rb +17 -0
  23. data/spec/hacky_hal/device_controllers/base_spec.rb +16 -0
  24. data/spec/hacky_hal/device_controllers/generic_serial_port_spec.rb +167 -0
  25. data/spec/hacky_hal/device_controllers/generic_ssh_spec.rb +141 -0
  26. data/spec/hacky_hal/device_controllers/roku_spec.rb +30 -0
  27. data/spec/hacky_hal/device_controllers/yamaha_av_receiver_spec.rb +29 -0
  28. data/spec/hacky_hal/device_resolvers/base_spec.rb +8 -0
  29. data/spec/hacky_hal/device_resolvers/ssdp_spec.rb +49 -0
  30. data/spec/hacky_hal/device_resolvers/static_uri_spec.rb +16 -0
  31. data/spec/hacky_hal/log_spec.rb +26 -0
  32. data/spec/hacky_hal/options_spec.rb +22 -0
  33. data/spec/hacky_hal/registry_spec.rb +30 -0
  34. data/spec/hacky_hal/util_spec.rb +27 -0
  35. data/spec/spec_helper.rb +11 -0
  36. data/support/hal-9000-small.png +0 -0
  37. data/support/hal-9000.png +0 -0
  38. metadata +144 -0
@@ -0,0 +1,141 @@
1
+ require "spec_helper"
2
+ require "hacky_hal/device_controllers/generic_ssh"
3
+
4
+ describe HackyHAL::DeviceControllers::GenericSsh do
5
+ it "should require host option" do
6
+ expect { described_class.new({}) }.
7
+ to raise_error(ArgumentError,
8
+ "HackyHAL::DeviceControllers::GenericSsh must set host option.")
9
+ end
10
+
11
+ it "should require user option" do
12
+ expect { described_class.new(host: "foo") }.
13
+ to raise_error(ArgumentError,
14
+ "HackyHAL::DeviceControllers::GenericSsh must set user option.")
15
+ end
16
+
17
+ describe "with valid options" do
18
+ before(:each) do
19
+ @generic_ssh = described_class.new(
20
+ host: "foo",
21
+ user: "bar"
22
+ )
23
+
24
+ @ssh = double("ssh connection")
25
+ @ssh.stub(:exec!).and_yield(nil, nil, "dummy output")
26
+ @ssh.stub(:close)
27
+ @ssh.stub(:closed?).and_return(false)
28
+ Net::SSH.stub(:start).and_return(@ssh)
29
+ end
30
+
31
+ describe "#exec" do
32
+ it "should pass command to ssh.exec!" do
33
+ @ssh.should_receive(:exec!).with("dummy_command")
34
+ @generic_ssh.exec("dummy_command")
35
+ end
36
+
37
+ it "should connect if not connected" do
38
+ @generic_ssh.should_receive(:connected?).and_return(false)
39
+ @generic_ssh.should_receive(:connect)
40
+ @generic_ssh.stub(:ssh_exec)
41
+ @generic_ssh.stub(:disconnect)
42
+ @generic_ssh.exec("dummy_command")
43
+ end
44
+
45
+ it "should not connect if already connected" do
46
+ @generic_ssh.should_receive(:connected?).and_return(true)
47
+ @generic_ssh.should_not_receive(:connect)
48
+ @generic_ssh.stub(:ssh_exec)
49
+ @generic_ssh.stub(:disconnect)
50
+ @generic_ssh.exec("dummy_command")
51
+ end
52
+
53
+ it "should join output from ssh.exec!" do
54
+ @ssh.stub(:exec!).and_yield(nil, nil, "foo").and_yield(nil, nil, "bar")
55
+ @generic_ssh.exec("dummy_command").should == "foobar"
56
+ end
57
+
58
+ it "should log debug message" do
59
+ @generic_ssh.should_receive(:log).with("Command: \"dummy_command\"", :debug)
60
+ @generic_ssh.should_receive(:log).with("Output: \"dummy output\"", :debug)
61
+ @generic_ssh.exec("dummy_command")
62
+ end
63
+
64
+ describe "on command failure" do
65
+ before(:each) do
66
+ @generic_ssh.stub(:ssh_exec).and_raise(EOFError.new("foo"))
67
+ end
68
+
69
+ it "should log warn message" do
70
+ @generic_ssh.should_receive(:log).with("Command: \"dummy_command\"", :debug).twice
71
+ @generic_ssh.should_receive(:log).with("Command failed: EOFError - foo", :warn).twice
72
+ @generic_ssh.should_receive(:log).with("Retrying last command", :warn).once
73
+ @generic_ssh.exec("dummy_command")
74
+ end
75
+
76
+ it "should retry once after reconnecting" do
77
+ @generic_ssh.should_receive(:disconnect).twice
78
+ @generic_ssh.should_receive(:connect).twice
79
+ @generic_ssh.exec("dummy_command")
80
+ end
81
+ end
82
+ end
83
+
84
+ describe "#connect" do
85
+ it "should disconnect if already connected" do
86
+ @generic_ssh.connect
87
+ @generic_ssh.should_receive(:disconnect)
88
+ @generic_ssh.connect
89
+ end
90
+
91
+ it "should open a new Net:SSH connection" do
92
+ Net::SSH.should_receive(:start)
93
+ @generic_ssh.connect
94
+ end
95
+
96
+ it "should log warn message on error" do
97
+ Net::SSH.should_receive(:start).and_raise(SocketError.new("dummy message"))
98
+ @generic_ssh.should_receive(:log).with("Failed to connect: SocketError - dummy message", :warn)
99
+ @generic_ssh.connect
100
+ end
101
+ end
102
+
103
+ describe "disconnect" do
104
+ it "should close the ssh connection if connected" do
105
+ @generic_ssh.connect
106
+ @ssh.should_receive(:close)
107
+ @generic_ssh.disconnect.should be_nil
108
+ end
109
+
110
+ it "should not close the ssh connection if not connected" do
111
+ @generic_ssh.should_receive(:connected?).and_return(false)
112
+ @ssh.should_not_receive(:close)
113
+ @generic_ssh.disconnect.should be_nil
114
+ end
115
+
116
+ it "should return nil on Net::SSH::Disconnect" do
117
+ @generic_ssh.connect
118
+ @ssh.should_receive(:close).and_raise(Net::SSH::Disconnect)
119
+ @generic_ssh.disconnect.should be_nil
120
+ end
121
+
122
+ it "should still disconnect on Net::SSH::Disconnect" do
123
+ @generic_ssh.connect
124
+ @ssh.should_receive(:close).and_raise(Net::SSH::Disconnect)
125
+ @generic_ssh.disconnect
126
+ @generic_ssh.connected?.should be_false
127
+ end
128
+ end
129
+
130
+ describe "connect?" do
131
+ it "should return true if connected" do
132
+ @generic_ssh.connect
133
+ @generic_ssh.connected?.should be_true
134
+ end
135
+
136
+ it "should return false if not connected" do
137
+ @generic_ssh.connected?.should be_false
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,30 @@
1
+ require "spec_helper"
2
+ require "hacky_hal/device_controllers/roku"
3
+
4
+ module HackyHAL
5
+ module DeviceResolvers
6
+ module Dummy
7
+ end
8
+ end
9
+ end
10
+
11
+ describe HackyHAL::DeviceControllers::Roku do
12
+ it "should require device_resolver" do
13
+ expect { described_class.new({}) }.to raise_error(
14
+ ArgumentError,
15
+ "HackyHAL::DeviceControllers::Roku must set device_resolver option."
16
+ )
17
+ end
18
+
19
+ # FIXME: identical in YamahaAvReceiver spec
20
+ it "should resolve host through device resolver" do
21
+ resolved_uri = double("resolved URI")
22
+ resolved_uri.stub(:path=)
23
+ resolved_uri.stub(:port=)
24
+ device_resolver = double("device resolver")
25
+ device_resolver.should_receive(:uri).and_return(resolved_uri)
26
+ HackyHAL::DeviceResolvers::Dummy.should_receive(:new).with(foo: 1).and_return(device_resolver)
27
+ controller = described_class.new(device_resolver: {type: 'Dummy', foo: 1})
28
+ controller.host_uri.should == resolved_uri
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ require "spec_helper"
2
+ require "hacky_hal/device_controllers/yamaha_av_receiver"
3
+
4
+ module HackyHAL
5
+ module DeviceResolvers
6
+ module Dummy
7
+ end
8
+ end
9
+ end
10
+
11
+ describe HackyHAL::DeviceControllers::YamahaAvReceiver do
12
+ it "should require device_resolver" do
13
+ expect { described_class.new({}) }.to raise_error(
14
+ ArgumentError,
15
+ "HackyHAL::DeviceControllers::YamahaAvReceiver must set device_resolver option."
16
+ )
17
+ end
18
+
19
+ it "should resolve host through device resolver" do
20
+ resolved_uri = double("resolved URI")
21
+ resolved_uri.stub(:path=)
22
+ resolved_uri.stub(:port=)
23
+ device_resolver = double("device resolver")
24
+ device_resolver.should_receive(:uri).and_return(resolved_uri)
25
+ HackyHAL::DeviceResolvers::Dummy.should_receive(:new).with(foo: 1).and_return(device_resolver)
26
+ controller = described_class.new(device_resolver: {type: 'Dummy', foo: 1})
27
+ controller.host_uri.should == resolved_uri
28
+ end
29
+ end
@@ -0,0 +1,8 @@
1
+ require "spec_helper"
2
+ require "hacky_hal/device_resolvers/base"
3
+
4
+ describe HackyHAL::DeviceResolvers::Base do
5
+ it "should include Options" do
6
+ described_class.ancestors.should include(HackyHAL::Options)
7
+ end
8
+ end
@@ -0,0 +1,49 @@
1
+ require "spec_helper"
2
+ require "hacky_hal/device_resolvers/ssdp"
3
+
4
+ describe HackyHAL::DeviceResolvers::SSDP do
5
+ it "should require usn option" do
6
+ expect { described_class.new({}) }.to raise_error(
7
+ ArgumentError,
8
+ "#{described_class.name} must set usn option."
9
+ )
10
+ end
11
+
12
+ it "should default to search for root devices" do
13
+ resolver = described_class.new(usn: "dummy-usn")
14
+ resolver.options[:search].should == "upnp:rootdevice"
15
+ end
16
+
17
+ describe "instance" do
18
+ before(:each) do
19
+ @resolver = described_class.new(search: "dummy-device-search", usn: "dummy-usn")
20
+ end
21
+
22
+ it "should return uri resolved via SSDP" do
23
+ UPnP::SSDP.stub(:search).and_return([
24
+ {location: "http://resolved-host/path", usn: "dummy-usn"}
25
+ ])
26
+ @resolver.uri.host.should == "resolved-host"
27
+ end
28
+
29
+ it "should raise SsdpUnresolvedDevice if no device found" do
30
+ UPnP::SSDP.stub(:search).and_return([])
31
+ expect { @resolver.uri }.to raise_error(HackyHAL::DeviceResolvers::SsdpUnresolvedDevice)
32
+ end
33
+
34
+ it "should return the host of device with given USN" do
35
+ UPnP::SSDP.stub(:search).and_return([
36
+ {location: "http://other-resolved-host/path", usn: "other-usn"},
37
+ {location: "http://resolved-host/path", usn: "dummy-usn"}
38
+ ])
39
+ @resolver.uri.host.should == "resolved-host"
40
+ end
41
+
42
+ it "should raise SsdpUnresolvedDevice if device with request USN not found" do
43
+ UPnP::SSDP.stub(:search).and_return([
44
+ {location: "http://other-resolved-host/path", usn: "other-usn"}
45
+ ])
46
+ expect { @resolver.uri }.to raise_error(HackyHAL::DeviceResolvers::SsdpUnresolvedDevice)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,16 @@
1
+ require "spec_helper"
2
+ require "hacky_hal/device_resolvers/static_uri"
3
+
4
+ describe HackyHAL::DeviceResolvers::StaticURI do
5
+ it "should require uri option" do
6
+ expect { described_class.new({}) }.to raise_error(
7
+ ArgumentError,
8
+ "#{described_class.name} must set uri option."
9
+ )
10
+ end
11
+
12
+ it "should return static host" do
13
+ resolver = described_class.new(uri: "http://device-host.local")
14
+ resolver.uri.host.should == "device-host.local"
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ require "spec_helper"
2
+ require "hacky_hal/log"
3
+
4
+ describe HackyHAL::Log do
5
+ before(:all) do
6
+ @old_logger_state = HackyHAL::Log.instance.enabled
7
+ HackyHAL::Log.instance.enabled = true
8
+ end
9
+
10
+ after(:all) do
11
+ HackyHAL::Log.instance.enabled = @old_logger_state
12
+ end
13
+
14
+ it "should use custom formatter" do
15
+ described_class.instance.formatter.should be_a(described_class::Formatter)
16
+ described_class.instance.formatter.should_receive(:call)
17
+ described_class.instance.info("foo")
18
+ end
19
+
20
+ describe "#write" do
21
+ it "should alias to self.<<" do
22
+ described_class.instance.should_receive(:<<).with("foo")
23
+ described_class.instance.write("foo")
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ require "spec_helper"
2
+ require "hacky_hal/options"
3
+
4
+ class DummyClass
5
+ include HackyHAL::Options
6
+ end
7
+
8
+ describe HackyHAL::Options do
9
+ describe "#initialize" do
10
+ it "should set options" do
11
+ object = DummyClass.new(dummy_argument: "dummy value")
12
+ object.options.should == {dummy_argument: "dummy value"}
13
+ end
14
+ end
15
+
16
+ describe "#[]" do
17
+ it "should return option values" do
18
+ object = DummyClass.new(dummy_argument: "dummy value")
19
+ object[:dummy_argument].should == "dummy value"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ require "spec_helper"
2
+ require "hacky_hal/registry"
3
+
4
+ describe HackyHAL::Registry do
5
+ describe "#load_yaml_file" do
6
+ before(:each) do
7
+ @device_config = {
8
+ "foo_device" => {
9
+ "type" => "Base",
10
+ "foo" => "bar"
11
+ }
12
+ }
13
+ File.stub(:read)
14
+ YAML.stub(:load).and_return(@device_config)
15
+ end
16
+
17
+ it "should load and initialize device controllers into registry" do
18
+ described_class.instance.load_yaml_file("foo.yml")
19
+ devices = described_class.instance.devices
20
+ devices.length.should == 1
21
+ devices[:foo_device].should be_instance_of(HackyHAL::DeviceControllers::Base)
22
+ end
23
+
24
+ it "should convert keys to symbols" do
25
+ described_class.instance.load_yaml_file("foo.yml")
26
+ devices = described_class.instance.devices
27
+ devices[:foo_device][:foo].should == "bar"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ require "spec_helper"
2
+ require "hacky_hal/util"
3
+
4
+ module DummyModule
5
+ class DummyClass
6
+ end
7
+ end
8
+
9
+ describe HackyHAL::Util do
10
+ describe ".object_from_hash" do
11
+ it "should require type" do
12
+ expect { described_class.object_from_hash({foo: 1}, DummyModule) }.to raise_error(ArgumentError)
13
+ end
14
+
15
+ it "should return instance of built class" do
16
+ dummy_instance = double("dummy instance")
17
+ DummyModule::DummyClass.should_receive(:new).with(foo: 1).and_return(dummy_instance)
18
+ described_class.object_from_hash({type: 'DummyClass', foo: 1}, DummyModule).should == dummy_instance
19
+ end
20
+ end
21
+
22
+ describe ".symbolize_keys_deep" do
23
+ it "should symbolize hash keys" do
24
+ described_class.symbolize_keys_deep("a" => 1, :b => {"c" => 2}).should == {a: 1, b: {c: 2}}
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ require "bundler"
2
+ Bundler.require
3
+ require_relative "../lib/hacky_hal/log"
4
+
5
+ require "rspec/core"
6
+
7
+ RSpec.configure do |c|
8
+ c.before(:suite) do
9
+ HackyHAL::Log.instance.enabled = false
10
+ end
11
+ end
Binary file
Binary file
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hacky_hal
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Nick Ewing
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-11-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: upnp-nickewing
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.1.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 0.1.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: serialport
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.1.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.1.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: net-ssh
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 2.6.0
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 2.6.0
62
+ description: HackyHAL - Hacky Home Automation Library
63
+ email:
64
+ - nick@nickewing.net
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - Gemfile
71
+ - LICENSE
72
+ - README.markdown
73
+ - hacky_hal.gemspec
74
+ - lib/hacky_hal.rb
75
+ - lib/hacky_hal/device_controllers/base.rb
76
+ - lib/hacky_hal/device_controllers/epson_projector.rb
77
+ - lib/hacky_hal/device_controllers/generic_serial_port.rb
78
+ - lib/hacky_hal/device_controllers/generic_ssh.rb
79
+ - lib/hacky_hal/device_controllers/io_gear_avior_hdmi_switch.rb
80
+ - lib/hacky_hal/device_controllers/linux_computer.rb
81
+ - lib/hacky_hal/device_controllers/osx_computer.rb
82
+ - lib/hacky_hal/device_controllers/roku.rb
83
+ - lib/hacky_hal/device_controllers/yamaha_av_receiver.rb
84
+ - lib/hacky_hal/device_resolvers/base.rb
85
+ - lib/hacky_hal/device_resolvers/ssdp.rb
86
+ - lib/hacky_hal/device_resolvers/static_uri.rb
87
+ - lib/hacky_hal/log.rb
88
+ - lib/hacky_hal/options.rb
89
+ - lib/hacky_hal/registry.rb
90
+ - lib/hacky_hal/util.rb
91
+ - spec/hacky_hal/device_controllers/base_spec.rb
92
+ - spec/hacky_hal/device_controllers/generic_serial_port_spec.rb
93
+ - spec/hacky_hal/device_controllers/generic_ssh_spec.rb
94
+ - spec/hacky_hal/device_controllers/roku_spec.rb
95
+ - spec/hacky_hal/device_controllers/yamaha_av_receiver_spec.rb
96
+ - spec/hacky_hal/device_resolvers/base_spec.rb
97
+ - spec/hacky_hal/device_resolvers/ssdp_spec.rb
98
+ - spec/hacky_hal/device_resolvers/static_uri_spec.rb
99
+ - spec/hacky_hal/log_spec.rb
100
+ - spec/hacky_hal/options_spec.rb
101
+ - spec/hacky_hal/registry_spec.rb
102
+ - spec/hacky_hal/util_spec.rb
103
+ - spec/spec_helper.rb
104
+ - support/hal-9000-small.png
105
+ - support/hal-9000.png
106
+ homepage: ''
107
+ licenses: []
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ! '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 1.8.24
127
+ signing_key:
128
+ specification_version: 3
129
+ summary: HackyHAL - Hacky Home Automation Library
130
+ test_files:
131
+ - spec/hacky_hal/device_controllers/base_spec.rb
132
+ - spec/hacky_hal/device_controllers/generic_serial_port_spec.rb
133
+ - spec/hacky_hal/device_controllers/generic_ssh_spec.rb
134
+ - spec/hacky_hal/device_controllers/roku_spec.rb
135
+ - spec/hacky_hal/device_controllers/yamaha_av_receiver_spec.rb
136
+ - spec/hacky_hal/device_resolvers/base_spec.rb
137
+ - spec/hacky_hal/device_resolvers/ssdp_spec.rb
138
+ - spec/hacky_hal/device_resolvers/static_uri_spec.rb
139
+ - spec/hacky_hal/log_spec.rb
140
+ - spec/hacky_hal/options_spec.rb
141
+ - spec/hacky_hal/registry_spec.rb
142
+ - spec/hacky_hal/util_spec.rb
143
+ - spec/spec_helper.rb
144
+ has_rdoc: