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