hacky_hal 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.
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: