hacky_hal 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/Gemfile +12 -0
- data/LICENSE +20 -0
- data/README.markdown +57 -0
- data/hacky_hal.gemspec +20 -0
- data/lib/hacky_hal/device_controllers/base.rb +14 -0
- data/lib/hacky_hal/device_controllers/epson_projector.rb +180 -0
- data/lib/hacky_hal/device_controllers/generic_serial_port.rb +108 -0
- data/lib/hacky_hal/device_controllers/generic_ssh.rb +74 -0
- data/lib/hacky_hal/device_controllers/io_gear_avior_hdmi_switch.rb +58 -0
- data/lib/hacky_hal/device_controllers/linux_computer.rb +36 -0
- data/lib/hacky_hal/device_controllers/osx_computer.rb +19 -0
- data/lib/hacky_hal/device_controllers/roku.rb +121 -0
- data/lib/hacky_hal/device_controllers/yamaha_av_receiver.rb +202 -0
- data/lib/hacky_hal/device_resolvers/base.rb +9 -0
- data/lib/hacky_hal/device_resolvers/ssdp.rb +36 -0
- data/lib/hacky_hal/device_resolvers/static_uri.rb +17 -0
- data/lib/hacky_hal/log.rb +33 -0
- data/lib/hacky_hal/options.rb +21 -0
- data/lib/hacky_hal/registry.rb +22 -0
- data/lib/hacky_hal/util.rb +23 -0
- data/lib/hacky_hal.rb +17 -0
- data/spec/hacky_hal/device_controllers/base_spec.rb +16 -0
- data/spec/hacky_hal/device_controllers/generic_serial_port_spec.rb +167 -0
- data/spec/hacky_hal/device_controllers/generic_ssh_spec.rb +141 -0
- data/spec/hacky_hal/device_controllers/roku_spec.rb +30 -0
- data/spec/hacky_hal/device_controllers/yamaha_av_receiver_spec.rb +29 -0
- data/spec/hacky_hal/device_resolvers/base_spec.rb +8 -0
- data/spec/hacky_hal/device_resolvers/ssdp_spec.rb +49 -0
- data/spec/hacky_hal/device_resolvers/static_uri_spec.rb +16 -0
- data/spec/hacky_hal/log_spec.rb +26 -0
- data/spec/hacky_hal/options_spec.rb +22 -0
- data/spec/hacky_hal/registry_spec.rb +30 -0
- data/spec/hacky_hal/util_spec.rb +27 -0
- data/spec/spec_helper.rb +11 -0
- data/support/hal-9000-small.png +0 -0
- data/support/hal-9000.png +0 -0
- metadata +144 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (C) 2013 by Nick Ewing
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
20
|
+
|
data/README.markdown
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
![HackyHal](https://raw.github.com/nickewing/hacky_hal/master/support/hal-9000-small.png) HackyHAL
|
2
|
+
========
|
3
|
+
|
4
|
+
What is it?
|
5
|
+
-----------
|
6
|
+
|
7
|
+
HackyHAL (Hacky Home Automation Library) is in its current form is a small
|
8
|
+
Ruby library meant to control devices through the network or serial ports.
|
9
|
+
The number of supported devices is currently very limited, however hopefully
|
10
|
+
the library will grow to support more devices over time.
|
11
|
+
|
12
|
+
Who is this for?
|
13
|
+
----------------
|
14
|
+
This project is for anyone wishing to write their own custom home automation
|
15
|
+
software/scripts. It is not user friendly and does not have any form of
|
16
|
+
built-in UI.
|
17
|
+
|
18
|
+
What devices are supported?
|
19
|
+
---------------------------
|
20
|
+
Supported functionality varies greatly with each device.
|
21
|
+
|
22
|
+
* **Epson Projector** (via serial port. Tested on HC8350, though likely also
|
23
|
+
works with other models)
|
24
|
+
* **Yamaha AV Receiver** (via network. Tested on RX-A1020. Should work with
|
25
|
+
RX-A2020 and RX-A3020 as well)
|
26
|
+
* **Roku** (via network)
|
27
|
+
* **Iogear AVIOR HDMI Switch** (via serial port. 8x1 GHSW8181 or 4x1 GHSW8141)
|
28
|
+
* **SSH accessible computer**
|
29
|
+
|
30
|
+
How can I use it?
|
31
|
+
-------------------
|
32
|
+
HackyHAL is simply a library to be used however you want.
|
33
|
+
|
34
|
+
You'll likely want to run a server utilizing HackyHAL on a networked computer (a
|
35
|
+
Raspberry Pi works great).
|
36
|
+
You'd then attach any serial port devices to that computer. You could then
|
37
|
+
create a mobile app to control the server. See the examples directory.
|
38
|
+
|
39
|
+
Can I contribue?
|
40
|
+
----------------
|
41
|
+
Please do! It would be great to see this library grow to support many more
|
42
|
+
devices. Just fork, make your changes, and send a pull request. Please be sure
|
43
|
+
to write tests for contributions.
|
44
|
+
|
45
|
+
Contributors
|
46
|
+
------------
|
47
|
+
* Nick Ewing
|
48
|
+
|
49
|
+
Special thanks to [Mischa McLachlan](https://www.iconfinder.com/icons/27626/9000_hal_light_red_space_icon) for the HAL 9000 icon.
|
50
|
+
|
51
|
+
License and Copyright
|
52
|
+
---------------------
|
53
|
+
|
54
|
+
HackyHAL is distributed under the MIT License. See LICENSE.
|
55
|
+
|
56
|
+
Copyright © 2013 Nick Ewing
|
57
|
+
|
data/hacky_hal.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = "hacky_hal"
|
5
|
+
gem.version = "0.2.0"
|
6
|
+
|
7
|
+
gem.authors = ["Nick Ewing"]
|
8
|
+
gem.email = ["nick@nickewing.net"]
|
9
|
+
gem.description = "HackyHAL - Hacky Home Automation Library"
|
10
|
+
gem.summary = gem.description
|
11
|
+
gem.homepage = ""
|
12
|
+
|
13
|
+
gem.add_dependency("upnp-nickewing", "~> 0.1.0")
|
14
|
+
gem.add_dependency("serialport", "~> 1.1.0")
|
15
|
+
gem.add_dependency("net-ssh", "~> 2.6.0")
|
16
|
+
|
17
|
+
gem.files = `git ls-files`.split($\)
|
18
|
+
gem.test_files = gem.files.grep(/^spec/)
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative "../log"
|
2
|
+
require_relative "../options"
|
3
|
+
|
4
|
+
module HackyHAL
|
5
|
+
module DeviceControllers
|
6
|
+
class Base
|
7
|
+
include Options
|
8
|
+
|
9
|
+
def log(message, level = :info)
|
10
|
+
Log.instance.send(level, "#{options[:name]}: #{message}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require_relative "generic_serial_port"
|
2
|
+
|
3
|
+
module HackyHAL
|
4
|
+
module DeviceControllers
|
5
|
+
class EpsonProjector < GenericSerialPort
|
6
|
+
|
7
|
+
SERIAL_PORT_OPTIONS = {
|
8
|
+
baud_rate: 9600,
|
9
|
+
data_bits: 8,
|
10
|
+
stop_bits: 1,
|
11
|
+
parity: SerialPort::NONE,
|
12
|
+
flow_control: SerialPort::NONE
|
13
|
+
}
|
14
|
+
|
15
|
+
SOURCE_COMMAND_TIMEOUT = 5
|
16
|
+
|
17
|
+
POWER_STATUS_TO_SYMBOL = {
|
18
|
+
"00" => :standby, # standby, network off
|
19
|
+
"01" => :on,
|
20
|
+
"02" => :warming_up,
|
21
|
+
"03" => :cooling_down,
|
22
|
+
"04" => :standby, # standby, network on
|
23
|
+
"05" => :standby # standby, abnormal
|
24
|
+
}
|
25
|
+
|
26
|
+
DEVICE_SOURCE_ID_TO_NAME = {
|
27
|
+
"HC8350" => {
|
28
|
+
"30" => "HDMI1",
|
29
|
+
"A0" => "HDMI2",
|
30
|
+
"14" => "Component (YCbCr)",
|
31
|
+
"15" => "Component (YPbPr)",
|
32
|
+
"21" => "PC",
|
33
|
+
"41" => "Video (RCA)",
|
34
|
+
"42" => "S-Video"
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
DEVICE_CMODE_ID_TO_NAME = {
|
39
|
+
"HC8350" => {
|
40
|
+
"06" => "Dynamic",
|
41
|
+
"0C" => "Living Room",
|
42
|
+
"07" => "Natural",
|
43
|
+
"15" => "Cinema",
|
44
|
+
"0B" => "x.v.Color"
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
DEVICE_ASPECT_RATIO_ID_TO_NAME = {
|
49
|
+
"HC8350" => {
|
50
|
+
"00" => "Normal"
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
ERROR_CODE_TO_MESSAGE = {
|
55
|
+
"00" => "There is no error or the error is recovered",
|
56
|
+
"01" => "Fan error",
|
57
|
+
"03" => "Lamp failure at power on",
|
58
|
+
"04" => "High internal temperature error",
|
59
|
+
"06" => "Lamp error",
|
60
|
+
"07" => "Open Lamp cover door error",
|
61
|
+
"08" => "Cinema filter error",
|
62
|
+
"09" => "Electric dual-layered capacitor is disconnected",
|
63
|
+
"0A" => "Auto iris error",
|
64
|
+
"0B" => "Subsystem Error",
|
65
|
+
"0C" => "Low air flow error",
|
66
|
+
"0D" => "Air filter air flow sensor error",
|
67
|
+
"0E" => "Power supply unit error (Ballast)",
|
68
|
+
"0F" => "Shutter error",
|
69
|
+
"10" => "Cooling system error (peltiert element)",
|
70
|
+
"11" => "Cooling system error (Pump)"
|
71
|
+
}
|
72
|
+
|
73
|
+
attr_reader :model
|
74
|
+
|
75
|
+
def initialize(options = {})
|
76
|
+
options[:serial_options] = SERIAL_PORT_OPTIONS
|
77
|
+
super(options)
|
78
|
+
@model = options[:model]
|
79
|
+
end
|
80
|
+
|
81
|
+
def on
|
82
|
+
power_status == :on
|
83
|
+
end
|
84
|
+
|
85
|
+
def on=(value)
|
86
|
+
value = value ? "ON" : "OFF"
|
87
|
+
write_command("PWR #{value}")
|
88
|
+
read_command(1)
|
89
|
+
end
|
90
|
+
|
91
|
+
def power_status
|
92
|
+
write_command("PWR?")
|
93
|
+
status_code = get_command_output(read_command)
|
94
|
+
POWER_STATUS_TO_SYMBOL[status_code] || :unknown
|
95
|
+
end
|
96
|
+
|
97
|
+
def lamp_hours
|
98
|
+
write_command("LAMP?")
|
99
|
+
get_command_output(read_command).to_i
|
100
|
+
end
|
101
|
+
|
102
|
+
def source
|
103
|
+
write_command("SOURCE?")
|
104
|
+
source_id = get_command_output(read_command)
|
105
|
+
|
106
|
+
source_name_hash = DEVICE_SOURCE_ID_TO_NAME[model]
|
107
|
+
source_name = source_name_hash[source_id] || "Unknown"
|
108
|
+
|
109
|
+
{id: source_id, name: source_name}
|
110
|
+
end
|
111
|
+
|
112
|
+
def source=(source_id)
|
113
|
+
write_command("SOURCE #{source_id}")
|
114
|
+
read_command(SOURCE_COMMAND_TIMEOUT)
|
115
|
+
end
|
116
|
+
|
117
|
+
def color_mode
|
118
|
+
write_command("CMODE?")
|
119
|
+
color_mode_id = get_command_output(read_command)
|
120
|
+
|
121
|
+
color_mode_hash = DEVICE_CMODE_ID_TO_NAME[model]
|
122
|
+
color_mode_name = color_mode_hash[color_mode_id] || "Unknown"
|
123
|
+
|
124
|
+
{id: color_mode_id, name: color_mode_name}
|
125
|
+
end
|
126
|
+
|
127
|
+
def color_mode=(color_mode_id)
|
128
|
+
write_command("CMODE #{color_mode_id}")
|
129
|
+
read_command
|
130
|
+
end
|
131
|
+
|
132
|
+
def aspect_ratio
|
133
|
+
write_command("ASPECT?")
|
134
|
+
aspect_ratio_id = get_command_output(read_command)
|
135
|
+
|
136
|
+
aspect_ratio_hash = DEVICE_ASPECT_RATIO_ID_TO_NAME[model]
|
137
|
+
aspect_ratio_name = aspect_ratio_hash[aspect_ratio_id] || "Unknown"
|
138
|
+
|
139
|
+
{id: aspect_ratio_id, name: aspect_ratio_name}
|
140
|
+
end
|
141
|
+
|
142
|
+
def aspect_ratio=(aspect_ratio_id)
|
143
|
+
write_command("ASPECT #{aspect_ratio_id}")
|
144
|
+
read_command
|
145
|
+
end
|
146
|
+
|
147
|
+
def error
|
148
|
+
write_command("ERR?")
|
149
|
+
set_read_timeout(DEFAULT_COMMAND_TIMEOUT)
|
150
|
+
error_code = get_command_output(read_line)
|
151
|
+
if error_code
|
152
|
+
error_message = ERROR_CODE_TO_MESSAGE[error_code] || "Unknown"
|
153
|
+
{code: error_code, message: error_message}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
# overrides GenericSerialPort#error_response?
|
160
|
+
def error_response?(response)
|
161
|
+
response =~ /^:?ERR/
|
162
|
+
end
|
163
|
+
|
164
|
+
def handle_error
|
165
|
+
error_details = error
|
166
|
+
|
167
|
+
if error_details
|
168
|
+
raise CommandError, "Projector returned error code #{error_details[:code]}: #{error_details[:message]}."
|
169
|
+
else
|
170
|
+
raise CommandError, "Projector returned an error."
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def get_command_output(output)
|
175
|
+
output =~ /^:?\w+=(.+)\r:$/
|
176
|
+
$1 ? $1.chomp : nil
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require "serialport"
|
2
|
+
require_relative "base"
|
3
|
+
|
4
|
+
module HackyHAL
|
5
|
+
module DeviceControllers
|
6
|
+
class GenericSerialPort < Base
|
7
|
+
MAX_READ_WRITE_RETRIES = 1
|
8
|
+
|
9
|
+
DEFAULT_COMMAND_TIMEOUT = 3
|
10
|
+
|
11
|
+
DEFAULT_OPTIONS = {
|
12
|
+
baud_rate: 9600,
|
13
|
+
data_bits: 8,
|
14
|
+
stop_bits: 1,
|
15
|
+
parity: SerialPort::NONE,
|
16
|
+
flow_control: SerialPort::NONE
|
17
|
+
}
|
18
|
+
|
19
|
+
class CommandError < Exception; end
|
20
|
+
|
21
|
+
attr_reader :serial_device_path, :serial_options
|
22
|
+
|
23
|
+
def initialize(options = {})
|
24
|
+
super(options)
|
25
|
+
ensure_option(:serial_device_path)
|
26
|
+
|
27
|
+
@serial_device_path = options[:serial_device_path]
|
28
|
+
@serial_options = DEFAULT_OPTIONS.merge(options[:serial_options] || {})
|
29
|
+
end
|
30
|
+
|
31
|
+
def write_command(command)
|
32
|
+
read_write_retry do
|
33
|
+
serial_port.flush
|
34
|
+
command = "#{command}\r\n"
|
35
|
+
log("Wrote: #{command.inspect}", :debug)
|
36
|
+
serial_port.write(command)
|
37
|
+
true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def read_command(timeout = DEFAULT_COMMAND_TIMEOUT)
|
42
|
+
set_read_timeout(timeout) if timeout
|
43
|
+
|
44
|
+
begin
|
45
|
+
output = read_line
|
46
|
+
log("Read: #{output.inspect}", :debug)
|
47
|
+
handle_error if error_response?(output)
|
48
|
+
output
|
49
|
+
rescue EOFError
|
50
|
+
log("Read EOFError", :warn)
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def disconnect
|
56
|
+
@serial_port.close if @serial_port
|
57
|
+
@serial_port = nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def serial_port
|
61
|
+
@serial_port ||= SerialPort.new(serial_device_path).tap do |s|
|
62
|
+
s.baud = serial_options[:baud_rate]
|
63
|
+
s.data_bits = serial_options[:data_bits]
|
64
|
+
s.stop_bits = serial_options[:stop_bits]
|
65
|
+
s.parity = serial_options[:parity]
|
66
|
+
s.flow_control = serial_options[:flow_control]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
def read_write_retry
|
73
|
+
retries = 0
|
74
|
+
|
75
|
+
begin
|
76
|
+
yield
|
77
|
+
rescue Errno::EIO => e
|
78
|
+
if retries < MAX_READ_WRITE_RETRIES
|
79
|
+
retries += 1
|
80
|
+
disconnect
|
81
|
+
retry
|
82
|
+
else
|
83
|
+
raise e
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def handle_error
|
89
|
+
raise CommandError, "Serial device returned an error."
|
90
|
+
end
|
91
|
+
|
92
|
+
def set_read_timeout(timeout)
|
93
|
+
serial_port.read_timeout = timeout * 1000
|
94
|
+
end
|
95
|
+
|
96
|
+
def read_line
|
97
|
+
read_write_retry do
|
98
|
+
serial_port.readline
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def error_response?(response)
|
103
|
+
false
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
require_relative "base"
|
3
|
+
|
4
|
+
module HackyHAL
|
5
|
+
module DeviceControllers
|
6
|
+
class GenericSsh < Base
|
7
|
+
MAX_COMMAND_RETRIES = 1
|
8
|
+
|
9
|
+
attr_reader :host, :user, :ssh_options
|
10
|
+
|
11
|
+
def initialize(options)
|
12
|
+
super(options)
|
13
|
+
|
14
|
+
ensure_option(:host)
|
15
|
+
ensure_option(:user)
|
16
|
+
|
17
|
+
@host = options[:host]
|
18
|
+
@user = options[:user]
|
19
|
+
@ssh_options = options[:ssh_options] || {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def exec(command)
|
23
|
+
out = nil
|
24
|
+
retries = 0
|
25
|
+
|
26
|
+
begin
|
27
|
+
connect unless connected?
|
28
|
+
log("Command: #{command.inspect}", :debug)
|
29
|
+
out = ssh_exec(command)
|
30
|
+
log("Output: #{out.inspect}", :debug)
|
31
|
+
rescue Net::SSH::Disconnect, EOFError => e
|
32
|
+
log("Command failed: #{e.class.name} - #{e.message}", :warn)
|
33
|
+
disconnect
|
34
|
+
|
35
|
+
if retries < MAX_COMMAND_RETRIES
|
36
|
+
log("Retrying last command", :warn)
|
37
|
+
retries += 1
|
38
|
+
retry
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
out
|
43
|
+
end
|
44
|
+
|
45
|
+
def connect
|
46
|
+
disconnect if @ssh
|
47
|
+
@ssh = Net::SSH.start(host, user, ssh_options)
|
48
|
+
rescue SocketError, Net::SSH::Exception, Errno::EHOSTUNREACH => e
|
49
|
+
log("Failed to connect: #{e.class.name} - #{e.message}", :warn)
|
50
|
+
end
|
51
|
+
|
52
|
+
def connected?
|
53
|
+
@ssh && !@ssh.closed?
|
54
|
+
end
|
55
|
+
|
56
|
+
def disconnect
|
57
|
+
@ssh.close if connected?
|
58
|
+
rescue Net::SSH::Disconnect
|
59
|
+
ensure
|
60
|
+
@ssh = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def ssh_exec(command)
|
66
|
+
out = []
|
67
|
+
@ssh.exec!(command) do |channel, stream, data|
|
68
|
+
out << data
|
69
|
+
end
|
70
|
+
out.join("")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative "generic_serial_port"
|
2
|
+
|
3
|
+
module HackyHAL
|
4
|
+
module DeviceControllers
|
5
|
+
class IoGearAviorHdmiSwitch < GenericSerialPort
|
6
|
+
|
7
|
+
SERIAL_PORT_OPTIONS = {
|
8
|
+
baud_rate: 19200,
|
9
|
+
data_bits: 8,
|
10
|
+
stop_bits: 1,
|
11
|
+
parity: SerialPort::NONE,
|
12
|
+
flow_control: SerialPort::NONE
|
13
|
+
}
|
14
|
+
|
15
|
+
def initialize(options = {})
|
16
|
+
options[:serial_options] = SERIAL_PORT_OPTIONS
|
17
|
+
super(options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def input=(value)
|
21
|
+
unless value.is_a?(Fixnum)
|
22
|
+
raise ArgumentError, "Input value must be an integer."
|
23
|
+
end
|
24
|
+
|
25
|
+
unless value > 0
|
26
|
+
raise ArgumentError, "Input value must be positive."
|
27
|
+
end
|
28
|
+
|
29
|
+
value = value.to_s.rjust(2, "0")
|
30
|
+
write_command("sw i#{value}")
|
31
|
+
read_command
|
32
|
+
end
|
33
|
+
|
34
|
+
def switch_to_next_input
|
35
|
+
write_command("sw +")
|
36
|
+
read_command
|
37
|
+
end
|
38
|
+
|
39
|
+
def switch_to_previous_input
|
40
|
+
write_command("sw -")
|
41
|
+
read_command
|
42
|
+
end
|
43
|
+
|
44
|
+
def power_on_detection=(value)
|
45
|
+
value = value ? "on" : "off"
|
46
|
+
write_command("pod #{value}")
|
47
|
+
read_command
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# overrides GenericSerialPort#error_response?
|
53
|
+
def error_response?(response)
|
54
|
+
response =~ /Command incorrect/
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative "generic_ssh"
|
2
|
+
|
3
|
+
module HackyHAL
|
4
|
+
module DeviceControllers
|
5
|
+
class LinuxComputer < GenericSsh
|
6
|
+
def mirror_screens(source_screen, dest_screen)
|
7
|
+
xrandr_command("--output #{dest_screen} --same-as #{source_screen}")
|
8
|
+
end
|
9
|
+
|
10
|
+
def set_screen_position(screen_1, screen_2, position)
|
11
|
+
xrandr_command("--output #{screen_1} --#{position}-of #{screen_2}")
|
12
|
+
end
|
13
|
+
|
14
|
+
def reset_display_settings(screen)
|
15
|
+
xrandr_command("--output #{screen} --auto")
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def xrandr_command(options)
|
21
|
+
x_env_variables = [
|
22
|
+
"CONSOLE=$(sudo fgconsole)",
|
23
|
+
"SESSION=$(who -s | grep tty$CONSOLE | tr '()' ' ')",
|
24
|
+
"XUSER=$(echo $SESSION | awk '{print $1}')",
|
25
|
+
"DISPLAY=$(echo $SESSION | awk '{print $5}')",
|
26
|
+
"XAUTHORITY=/home/$XUSER/.Xauthority",
|
27
|
+
"export DISPLAY",
|
28
|
+
"export XAUTHORITY"
|
29
|
+
].join("; ")
|
30
|
+
|
31
|
+
command = "#{x_env_variables}; xrandr -d $DISPLAY #{options}"
|
32
|
+
exec(command)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative "generic_ssh"
|
2
|
+
|
3
|
+
module HackyHAL
|
4
|
+
module DeviceControllers
|
5
|
+
class OsxComputer < GenericSsh
|
6
|
+
def mirror_screens
|
7
|
+
exec("mirror -on")
|
8
|
+
end
|
9
|
+
|
10
|
+
def unmirror_screens
|
11
|
+
exec("mirror -off")
|
12
|
+
end
|
13
|
+
|
14
|
+
def set_audio_output_device(name)
|
15
|
+
exec("audiodevice output '#{name}'")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|