savant-echo 1.0.5 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/habridge/SCLI_HTTP.rb +1 -1
- data/bin/habridge/com.habridgescenesync.plist +19 -0
- data/bin/habridge/habridgescenesync.rb +198 -0
- data/bin/src/Savant.rb +427 -29
- data/bin/src/glade/Savant.glade +449 -23
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d697f91ad2086bedf9e01c21539cede24ef4cf6
|
4
|
+
data.tar.gz: f751654b48d18c21fd2a9a9a96fb4523fe324c2e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8e48ac117ce8fba65784726791673c24d34989f347f4b17a4aaf0922c7c18ce5bccc1056ec4381b845929339fd1246ad99f640fd98417987df32e60fa28fed34
|
7
|
+
data.tar.gz: 2b625506c37558dad6409404d5b5201b31713444d4b20dabc1c7e1c5031101100897071b4309205527f16ed49918918637027ba528cff4562c047fe84ca7a90e
|
data/bin/habridge/SCLI_HTTP.rb
CHANGED
@@ -58,7 +58,7 @@ def connThread(rti)
|
|
58
58
|
break
|
59
59
|
end
|
60
60
|
#puts data.inspect
|
61
|
-
if /(readstate|writestate|servicerequestcommand|servicerequest|userzones|statenames|settrigger)/.match(data)
|
61
|
+
if /(readstate|writestate|servicerequestcommand|servicerequest|userzones|statenames|settrigger|getSceneNames|activateScene)/.match(data)
|
62
62
|
#puts $1.inspect
|
63
63
|
if $1 == "servicerequestcommand" && $savConn == true
|
64
64
|
r = writeToRequest(data)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3
|
+
<plist version="1.0">
|
4
|
+
<dict>
|
5
|
+
<key>Label</key>
|
6
|
+
<string>com.habridgescenesync</string>
|
7
|
+
<key>ProgramArguments</key>
|
8
|
+
<array>
|
9
|
+
<string>ruby</string>
|
10
|
+
<string>/Users/RPM/habridge/habridgescenesync.rb</string>
|
11
|
+
</array>
|
12
|
+
<key>RunAtLoad</key>
|
13
|
+
<true/>
|
14
|
+
<key>KeepAlive</key>
|
15
|
+
<true/>
|
16
|
+
<key>WorkingDirectory</key>
|
17
|
+
<string>/Users/RPM/habridge/</string>
|
18
|
+
</dict>
|
19
|
+
</plist>
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'json'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
#-----------------NOTES-----------------------
|
6
|
+
# This script can be installed as a launchagent on a Savant Pro Host running the habridge for Alexa interation.
|
7
|
+
# It scans for new scenes on the host every 5 seconds and automaticalyl adds them to the harbdige
|
8
|
+
# or removes them from the habridge if they are no longer a scene.
|
9
|
+
# Devices would still need to be deleted from the Alexa app.
|
10
|
+
|
11
|
+
#-------------INITIALIZATION------------------
|
12
|
+
$read = "readstate "
|
13
|
+
$write = "writestate "
|
14
|
+
$request = "servicerequest "
|
15
|
+
|
16
|
+
@savantURL = "127.0.0.1"
|
17
|
+
@haURLstr = "http://127.0.0.1:8888/api/devices"
|
18
|
+
@savantURLstr = "http://127.0.0.1:12000"
|
19
|
+
|
20
|
+
deviceNameArray = []
|
21
|
+
deviceIDArray = []
|
22
|
+
deviceOnUrlArray = []
|
23
|
+
sceneNameArray = []
|
24
|
+
sceneIDArray = []
|
25
|
+
sceneUserArray = []
|
26
|
+
|
27
|
+
if RUBY_PLATFORM.include? "linux"
|
28
|
+
$bridge = "/usr/local/bin/sclibridge "
|
29
|
+
else
|
30
|
+
$bridge = "~/Applications/RacePointMedia/sclibridge "
|
31
|
+
end
|
32
|
+
|
33
|
+
#----------------SCRIPT-----------------------
|
34
|
+
|
35
|
+
def EscapeString(s)
|
36
|
+
s.gsub!(' ','\ ')
|
37
|
+
return s
|
38
|
+
end
|
39
|
+
|
40
|
+
def readState(strState)
|
41
|
+
strState = EscapeString(strState)
|
42
|
+
s = $bridge + $read + strState
|
43
|
+
s = `#{s}`
|
44
|
+
return s
|
45
|
+
end
|
46
|
+
|
47
|
+
def writeState(strState,strVal)
|
48
|
+
strState = EscapeString(strState)
|
49
|
+
strVal = EscapeString(strVal)
|
50
|
+
s = $bridge + $write + strState + " " + strVal
|
51
|
+
`#{s}`
|
52
|
+
end
|
53
|
+
|
54
|
+
def serviceRequest(strZone, strcomponent, strlogical, strvariant, strtype, strCommand)
|
55
|
+
strZone = " " + EscapeString(strZone)
|
56
|
+
strcomponent = " " + EscapeString(strcomponent)
|
57
|
+
strCommand = " " + EscapeString(strCommand)
|
58
|
+
strlogical = " " + strlogical
|
59
|
+
strvariant = " " + strvariant
|
60
|
+
strtype = " " + strtype
|
61
|
+
s = $bridge + $request + strZone + strcomponent + strlogical + strvariant + strtype + strCommand
|
62
|
+
puts "Service request sent = " + s
|
63
|
+
`#{s}`
|
64
|
+
end
|
65
|
+
|
66
|
+
def queryScenes()
|
67
|
+
s = $bridge + "getSceneNames"
|
68
|
+
`#{s}`
|
69
|
+
end
|
70
|
+
|
71
|
+
def activateScene(sceneName, sceneID, sceneUser)
|
72
|
+
s = $bridge + "activateScene \"" + sceneName.to_s + "\" \"" + sceneID.to_s + "\" \"" + sceneUser.to_s + "\""
|
73
|
+
puts "Scene activated = " + sceneName.to_s
|
74
|
+
`#{s}`
|
75
|
+
end
|
76
|
+
|
77
|
+
loop do
|
78
|
+
#------Query current devices on habridge
|
79
|
+
uri = URI(@haURLstr)
|
80
|
+
response = Net::HTTP.get(uri)
|
81
|
+
habridgeArray = JSON.parse(response, object_class: OpenStruct)
|
82
|
+
|
83
|
+
#------build arrays of habridge devices
|
84
|
+
habridgeArray.each { |device| deviceNameArray.push(device.name)}
|
85
|
+
habridgeArray.each { |device| deviceIDArray.push(device.id)}
|
86
|
+
habridgeArray.each { |device|
|
87
|
+
if device.to_s.include? "onUrl"
|
88
|
+
deviceOnUrlArray.push(device.onUrl)
|
89
|
+
else
|
90
|
+
deviceOnUrlArray.push("none")
|
91
|
+
end
|
92
|
+
}
|
93
|
+
|
94
|
+
#------convert names to all lowercase for duplicates and missing comparisons
|
95
|
+
deviceNameArray.map! {|str| str.downcase.chomp}
|
96
|
+
|
97
|
+
#print device names on habridge
|
98
|
+
puts "Devices on HA Bridge = "
|
99
|
+
puts deviceNameArray
|
100
|
+
|
101
|
+
#------Query savant scenes from host
|
102
|
+
scenesString = queryScenes()
|
103
|
+
sceneArray = scenesString.split(/\n|,/)
|
104
|
+
|
105
|
+
#------Separate out the elements of the scene into their own arrays
|
106
|
+
until sceneArray.empty?
|
107
|
+
sceneNameArray << sceneArray.shift
|
108
|
+
sceneIDArray << sceneArray.shift
|
109
|
+
sceneUserArray << sceneArray.shift
|
110
|
+
end
|
111
|
+
|
112
|
+
#------Print scene names
|
113
|
+
puts "\nScenes on host = "
|
114
|
+
puts sceneNameArray
|
115
|
+
|
116
|
+
#------Compare habridge device list to Savant scene list
|
117
|
+
sceneNameArrayLowercase = sceneNameArray.map {|str| str.downcase.chomp}
|
118
|
+
duplicateScenes = deviceNameArray & sceneNameArrayLowercase
|
119
|
+
missingScenes = deviceNameArray - sceneNameArrayLowercase
|
120
|
+
|
121
|
+
#------Print duplicates
|
122
|
+
puts "\nDuplicates = "
|
123
|
+
puts duplicateScenes
|
124
|
+
|
125
|
+
#------Print bridge devices that are not scenes on Savant
|
126
|
+
puts "\nMissing = "
|
127
|
+
puts missingScenes
|
128
|
+
|
129
|
+
#------Loop through each habridge device and see if it should be deleted
|
130
|
+
#------because it is a scene that is no longer on the savant host
|
131
|
+
until deviceNameArray.empty?
|
132
|
+
if missingScenes.include? deviceNameArray[0].downcase
|
133
|
+
puts deviceNameArray[0] + " will be scanned for scene."
|
134
|
+
#------Check to see if the habridge device is a savant scene
|
135
|
+
if deviceOnUrlArray[0].include? "activateScene"
|
136
|
+
puts "\n" + deviceNameArray[0] + " is marked for deletion!\n"
|
137
|
+
#------Delete the device from habridge if it is no longer a savant scene
|
138
|
+
uri = "http://127.0.0.1:8888/api/devices/"
|
139
|
+
uri += deviceIDArray[0].chomp
|
140
|
+
uri = URI(uri)
|
141
|
+
begin
|
142
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
143
|
+
request = Net::HTTP::Delete.new(uri.request_uri)
|
144
|
+
response = http.request(request)
|
145
|
+
puts response.code
|
146
|
+
puts response
|
147
|
+
if response.code == "201" || "200"
|
148
|
+
puts 'Command deleted from HA Bridge.'
|
149
|
+
elsif response.code.include?("4" || "5")
|
150
|
+
puts 'Command failed to be deleted from HA Bridge.'
|
151
|
+
end
|
152
|
+
rescue Timeout::Error, SocketError, Errno::EHOSTUNREACH, Errno::ECONNREFUSED
|
153
|
+
puts "Unable to reach host."
|
154
|
+
end
|
155
|
+
end
|
156
|
+
else
|
157
|
+
puts deviceNameArray[0] + " will NOT be scanned for scene."
|
158
|
+
end
|
159
|
+
deviceNameArray.delete_at(0)
|
160
|
+
deviceIDArray.delete_at(0)
|
161
|
+
deviceOnUrlArray.delete_at(0)
|
162
|
+
end
|
163
|
+
|
164
|
+
#------Loop through each Savant scene and add to habridge if not already there
|
165
|
+
until sceneNameArray.empty?
|
166
|
+
@haScene = %q{\{'name' : '} + sceneNameArray[0] + %q{','deviceType' : 'custom','onUrl' : '} + @savantURLstr + %q{/activateScene%20\'} + sceneNameArray[0].gsub(" ", "%20") + %q{\'%20\'} + sceneIDArray[0] + %q{\'%20\'} + sceneUserArray[0] + %q{\''\}}
|
167
|
+
if !(duplicateScenes.include? sceneNameArray[0].downcase)
|
168
|
+
uri = URI.parse(@haURLstr)
|
169
|
+
begin
|
170
|
+
http = Net::HTTP.start(uri.host, uri.port, {open_timeout: 3, read_timeout: 3})
|
171
|
+
begin
|
172
|
+
request = Net::HTTP::Post.new(
|
173
|
+
uri.request_uri,
|
174
|
+
'Content-Type' => 'application/json'
|
175
|
+
)
|
176
|
+
request.body = @haScene
|
177
|
+
response = http.request(request)
|
178
|
+
puts response.code
|
179
|
+
if response.code == "201" || "200"
|
180
|
+
puts "Scene added = " + sceneNameArray[0] + ' ' + sceneIDArray[0] + ' ' + sceneUserArray[0]
|
181
|
+
elsif response.code.include?("4" || "5")
|
182
|
+
puts 'Failed to add to HA Bridge.'
|
183
|
+
end
|
184
|
+
rescue Timeout::Error, SocketError, Errno::EHOSTUNREACH, Errno::ECONNREFUSED
|
185
|
+
puts "Unable to reach host."
|
186
|
+
end
|
187
|
+
rescue Timeout::Error, SocketError, Errno::EHOSTUNREACH, Errno::ECONNREFUSED
|
188
|
+
puts "Unable to reach host."
|
189
|
+
end
|
190
|
+
else
|
191
|
+
puts "Scene already exists = " + sceneNameArray[0]
|
192
|
+
end
|
193
|
+
sceneNameArray.delete_at(0)
|
194
|
+
sceneIDArray.delete_at(0)
|
195
|
+
sceneUserArray.delete_at(0)
|
196
|
+
end
|
197
|
+
sleep 5
|
198
|
+
end
|
data/bin/src/Savant.rb
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
#to do
|
2
|
+
#habridge updater?
|
3
|
+
#check for sclibridge version and popup updater on scene entry
|
4
|
+
#sort scenes and devices
|
5
|
+
#browse commands?
|
6
|
+
#delete scenes?
|
7
|
+
|
1
8
|
require 'rubygems'
|
2
9
|
require 'net/http'
|
3
10
|
require 'net/scp'
|
@@ -5,44 +12,140 @@ require 'uri'
|
|
5
12
|
require 'socket'
|
6
13
|
require 'timeout'
|
7
14
|
require 'ipaddress'
|
8
|
-
|
15
|
+
require 'json'
|
16
|
+
require 'ostruct'
|
17
|
+
|
18
|
+
class Savant #< VR::ListView
|
19
|
+
|
20
|
+
attr_accessor :ftv
|
21
|
+
|
9
22
|
include GladeGUI
|
10
23
|
|
24
|
+
def initialize
|
25
|
+
@listviewSavantScenes = VR::ListView.new(:add => TrueClass, :scene => String, :id => String, :user => String)
|
26
|
+
@listviewhabridgeDevices = VR::ListView.new(:device => String)
|
27
|
+
end
|
28
|
+
|
11
29
|
#####Method to check to see if HA Bridge is running on host#####
|
12
|
-
def up?(
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
30
|
+
def up?(ip, port)
|
31
|
+
if IPAddress.valid? ip
|
32
|
+
begin
|
33
|
+
http = Net::HTTP.start(ip, port, {open_timeout: 1, read_timeout: 1})
|
34
|
+
response = http.head("/")
|
35
|
+
if response.code == "200"
|
36
|
+
@builder["buttonHabridgeStatus"].label = "HA Bridge is Online\nClick to open in browser."
|
37
|
+
@builder["buttonBackupRestore"].show
|
38
|
+
if savantVersion?(@savantURL)
|
39
|
+
@builder["buttonSceneSync"].label = "Savant Scene Sync"
|
40
|
+
@builder["buttonSceneSync"].show
|
41
|
+
|
42
|
+
end
|
43
|
+
@habridgeStatus = "Online"
|
44
|
+
end
|
45
|
+
rescue StandardError
|
46
|
+
false
|
47
|
+
@builder["buttonHabridgeStatus"].label = "HA Bridge is Offline\nClick for help."
|
48
|
+
@builder["buttonSceneSync"].hide
|
49
|
+
@builder["buttonBackupRestore"].hide
|
50
|
+
@habridgeStatus = "Offline"
|
51
|
+
end
|
18
52
|
end
|
19
|
-
rescue Timeout::Error, SocketError, Errno::EHOSTUNREACH, Errno::ECONNREFUSED
|
20
|
-
false
|
21
|
-
@builder["buttonHabridgeStatus"].label = "HA Bridge is Offline\nClick for help."
|
22
|
-
@habridgeStatus = "Offline"
|
23
53
|
end
|
24
54
|
|
25
|
-
#####
|
55
|
+
#####Check to see if Racepoint and SCLI bridge ports are open on host#####
|
26
56
|
def is_port_open?(ip, port)
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
57
|
+
if IPAddress.valid? ip
|
58
|
+
begin
|
59
|
+
Timeout::timeout(1) do
|
60
|
+
begin
|
61
|
+
s = TCPSocket.new(ip, port)
|
62
|
+
s.close
|
63
|
+
return true
|
64
|
+
rescue StandardError
|
65
|
+
return false
|
66
|
+
end
|
35
67
|
end
|
68
|
+
rescue Timeout::Error
|
36
69
|
end
|
37
|
-
rescue Timeout::Error
|
38
70
|
end
|
71
|
+
end
|
72
|
+
|
73
|
+
#####Checks to see if Savant version is greater than 8.3 to support scene features.
|
74
|
+
def savantVersion?(ip)
|
75
|
+
login = 'RPM'
|
76
|
+
@password = 'RPM'
|
77
|
+
begin
|
78
|
+
Net::SSH.start(ip, login, :password => @password, :paranoid => false, :timeout => 3) do |ssh|
|
79
|
+
#Check host type and update sclibridge location for reference, though only Pro Host is supported
|
80
|
+
ssh.open_channel do |ch, success|
|
81
|
+
ch.exec("uname")
|
82
|
+
ch.on_data do |ch, data|
|
83
|
+
if data.to_s.include? 'Darwin'
|
84
|
+
@scliBridge = "~/Applications/RacePointMedia/sclibridge "
|
85
|
+
else
|
86
|
+
@scliBridge = "/usr/local/bin/sclibridge "
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
ssh.loop
|
91
|
+
#parse the rpmSystemInfo.plist form the pro host to get host name
|
92
|
+
if @scliBridge == "~/Applications/RacePointMedia/sclibridge "
|
93
|
+
ssh.open_channel do |ch, success|
|
94
|
+
|
95
|
+
ch.exec("plutil -p 'library/application support/racepointmedia/userconfig.rpmconfig/rpmSystemInfo.plist'")
|
96
|
+
ch.on_data do |ch, data|
|
97
|
+
if data.to_s.include? 'module'
|
98
|
+
x = data.split("\"RPMSystemName\" => ")
|
99
|
+
x = x[1].split("\n")
|
100
|
+
@str = @scliBridge + "readstate \"" + x[0][1..-2] + ".SoftwareVersion\""
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
ssh.loop
|
105
|
+
#parse the rpmSystemInfo.plist from the smart host to get host name
|
106
|
+
elsif @scliBridge == "/usr/local/bin/sclibridge "
|
107
|
+
ssh.open_channel do |ch, success|
|
108
|
+
ch.exec("strings 'GNUstep/Library/ApplicationSupport/RacePointMedia/userConfig.rpmConfig/rpmSystemInfo.plist'")
|
109
|
+
ch.on_data do |ch, data|
|
110
|
+
if data.to_s.include? 'DOCTYPE'
|
111
|
+
x = data.split("RPMUserDefinedName</key><string>")
|
112
|
+
y = x[1].split("</string>")
|
113
|
+
x = x[1].split("VERSION</key><integer>")
|
114
|
+
x = x[1].split("</integer>")
|
115
|
+
elsif data.to_s.include? 'module'
|
116
|
+
x = data.split('module')
|
117
|
+
x = x[1].split("\n")
|
118
|
+
@str = @scliBridge + "readstate \"" + x[0][1..-1] + ".SoftwareVersion\""
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
ssh.loop
|
123
|
+
end
|
124
|
+
#Use host name to query SoftwareVersion state from host
|
125
|
+
ssh.open_channel do |ch, success|
|
126
|
+
ch.exec(@str)
|
127
|
+
ch.on_data do |ch, data|
|
128
|
+
@version = data
|
129
|
+
end
|
130
|
+
end
|
131
|
+
ssh.loop
|
132
|
+
end
|
133
|
+
#return true if version is 8.3 or later
|
134
|
+
if @version.to_r > 8.2 && !@version.empty?
|
135
|
+
return true
|
136
|
+
else
|
137
|
+
return false
|
138
|
+
end
|
139
|
+
rescue Net::SSH::ConnectionTimeout, SocketError, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Errno::EHOSTDOWN
|
39
140
|
|
40
|
-
|
141
|
+
end
|
41
142
|
end
|
42
143
|
|
43
144
|
#####Set variables before windows load#####
|
44
145
|
def before_show()
|
146
|
+
|
45
147
|
@savantURL = "192.168.77.40"
|
148
|
+
|
46
149
|
@haName = ""
|
47
150
|
@habridgeStatus = "Offline"
|
48
151
|
@installStatus = ""
|
@@ -85,18 +188,102 @@ class Savant
|
|
85
188
|
@requestDimValue8 = "percent"
|
86
189
|
@requestDimValue9 = "percent"
|
87
190
|
|
88
|
-
up?(@savantURL,"8888")
|
191
|
+
up?(@savantURL,"8888")
|
192
|
+
|
193
|
+
@ftv = VR::FileTreeView.new(Dir.home, File.join(File.dirname(__FILE__), "/../img"), glob = "*", validate_block = nil)
|
194
|
+
@builder["scrolledwindow_BackupRestore"].add(@ftv)
|
195
|
+
@ftv.set_show_expanders(true)
|
196
|
+
|
89
197
|
end
|
90
198
|
|
91
|
-
|
92
|
-
|
199
|
+
|
200
|
+
|
201
|
+
#FileTreeView expand and collapse for file backup and restore
|
202
|
+
def ftv__row_activated(_self, path, col)
|
203
|
+
@ftv.expand_or_collapse_folder()
|
204
|
+
end
|
205
|
+
|
206
|
+
#Restore files from local filesystem to remote host
|
207
|
+
def buttonRestore__clicked(*a)
|
208
|
+
return unless row = @ftv.selected_rows.first
|
209
|
+
login = 'RPM'
|
210
|
+
@password = 'RPM'
|
211
|
+
backupDir = row[:path]
|
212
|
+
#Get folder name if a file is selected.
|
213
|
+
if File.file?(backupDir)
|
214
|
+
backupDir = File.dirname(backupDir)
|
215
|
+
end
|
216
|
+
#Check that folder contains correct files
|
217
|
+
if File.file?(backupDir + "/device.db") && File.file?(backupDir + "/habridge.config")
|
218
|
+
begin
|
219
|
+
Net::SSH.start(@savantURL, login, :password => @password, :paranoid => false, :timeout => 3) do |ssh|
|
220
|
+
# test to see if remote folder exists, if so, transfer files and restart harbridge
|
221
|
+
dirExists = ssh.exec!("[ -d ~/habridge/data ] && echo 'true' || echo 'false'")
|
222
|
+
if dirExists == 'true'
|
223
|
+
ssh.scp.upload!(backupDir + "/device.db", '/Users/RPM/habridge/data/')
|
224
|
+
ssh.scp.upload!(backupDir + "/habridge.config",'/Users/RPM/habridge/data/')
|
225
|
+
ssh.exec!("launchctl unload /Users/RPM/Library/LaunchAgents/com.habridge.plist")
|
226
|
+
ssh.exec!("launchctl load /Users/RPM/Library/LaunchAgents/com.habridge.plist")
|
227
|
+
alert "Files have been restored from " + row[:path] + " and habridge has been re-initialized."
|
228
|
+
else
|
229
|
+
alert "Users/RPM/habridge/data folder not found on remote host."
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
else
|
234
|
+
alert "Selected directory does not contain device.db and habridge.config."
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
#Backup files from remote host to local filesystem
|
239
|
+
def buttonBackup__clicked(*a)
|
240
|
+
return unless row = @ftv.selected_rows.first
|
241
|
+
login = 'RPM'
|
242
|
+
@password = 'RPM'
|
243
|
+
backupDir = row[:path]
|
244
|
+
#Get folder name if a file is selected.
|
245
|
+
if File.file?(backupDir)
|
246
|
+
backupDir = File.dirname(backupDir)
|
247
|
+
end
|
248
|
+
begin
|
249
|
+
Net::SSH.start(@savantURL, login, :password => @password, :paranoid => false, :timeout => 3) do |ssh|
|
250
|
+
# test to see if remote files exist and transfer them if they do
|
251
|
+
fileExists = ssh.exec!("[ -f ~/habridge/data/device.db ] && echo 'true' || echo 'false'")
|
252
|
+
if fileExists == 'true'
|
253
|
+
ssh.scp.download!('/Users/RPM/habridge/data/device.db', backupDir)
|
254
|
+
else
|
255
|
+
alert "Users/RPM/habridge/data/device.db not found on remote host."
|
256
|
+
end
|
257
|
+
fileExists = ssh.exec!("[ -f ~/habridge/data/habridge.config ] && echo 'true' || echo 'false'")
|
258
|
+
if fileExists == 'true'
|
259
|
+
ssh.scp.download!('/Users/RPM/habridge/data/habridge.config', backupDir)
|
260
|
+
else
|
261
|
+
alert "Users/RPM/habridge/data/habridge.config not found on remote host."
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
alert "Files backed up to " + row[:path]
|
266
|
+
end
|
267
|
+
|
268
|
+
def buttonBackupRestore__clicked(*argv)
|
269
|
+
@builder["windowBackupRestore"].show
|
270
|
+
end
|
271
|
+
|
272
|
+
def buttonBackupRestoreCancel__clicked(*argv)
|
273
|
+
@builder["windowBackupRestore"].hide
|
274
|
+
end
|
275
|
+
|
276
|
+
#Test to see if habridge is running when IP address is changed
|
277
|
+
def savantURL__changed(*argv)
|
93
278
|
@savantURL = @builder["savantURL"].text
|
94
279
|
up?(@savantURL,"8888")
|
95
280
|
end
|
96
|
-
|
97
|
-
|
281
|
+
|
282
|
+
#Test to see if habridge is running when IP address is changed
|
283
|
+
def savantHelpURL__changed(*argv)
|
98
284
|
@savantURL = @builder["savantHelpURL"].text
|
99
285
|
up?(@savantURL,"8888")
|
286
|
+
|
100
287
|
end
|
101
288
|
|
102
289
|
#####Launch HA Bridge if online or help window if offline#####
|
@@ -132,7 +319,7 @@ class Savant
|
|
132
319
|
if is_port_open?(@savantURL,"12000")
|
133
320
|
system("open http://" + @savantURL + ":12000/userzones")
|
134
321
|
else
|
135
|
-
alert "SCLI_HTTP bridge is not online at specified IP address.\nPlease change IP address or install SCLI_HTTP bridge and set to port
|
322
|
+
alert "SCLI_HTTP bridge is not online at specified IP address.\nPlease change IP address or install SCLI_HTTP bridge and set to port 120000."
|
136
323
|
end
|
137
324
|
else
|
138
325
|
alert "Invalid IP Address."
|
@@ -296,7 +483,7 @@ class Savant
|
|
296
483
|
##### Repeat install process in reverse for uninstall. BUG: Java uninstall can be flaky and might fail. #####
|
297
484
|
def buttonHelpUninstall__clicked(*argv)
|
298
485
|
if IPAddress.valid? @savantURL
|
299
|
-
if is_port_open?(@savantURL,"11263")
|
486
|
+
if is_port_open?(@savantURL,"11263") || is_port_open?(@savantURL,"48664")
|
300
487
|
@builder["dialogInstall"].show
|
301
488
|
login = 'RPM'
|
302
489
|
@password = 'RPM'
|
@@ -406,6 +593,216 @@ class Savant
|
|
406
593
|
##### Close window #####
|
407
594
|
def buttonHelpCancel__clicked(*argv)
|
408
595
|
@builder["dialogHelp"].hide
|
596
|
+
@builder["savantURL"].text = @builder["savantHelpURL"].text
|
597
|
+
end
|
598
|
+
|
599
|
+
|
600
|
+
##### Refresh scenesync listview window #####
|
601
|
+
def refresh()
|
602
|
+
#find scenes
|
603
|
+
habridgeArray = []
|
604
|
+
@deviceNameArray = []
|
605
|
+
@deviceIDArray = []
|
606
|
+
@deviceOnUrlArray = []
|
607
|
+
@sceneNameArray = []
|
608
|
+
@sceneIDArray = []
|
609
|
+
@sceneUserArray = []
|
610
|
+
@scenesURLstr = "http://" + @savantURL + ":12000/getSceneNames"
|
611
|
+
|
612
|
+
#Query current devices on habridge
|
613
|
+
uri = URI(@haURLstr)
|
614
|
+
response = Net::HTTP.get(uri)
|
615
|
+
habridgeArray = JSON.parse(response, object_class: OpenStruct)
|
616
|
+
|
617
|
+
#build array of habridge device names
|
618
|
+
unless habridgeArray.to_s.empty?
|
619
|
+
habridgeArray.each { |device| @deviceNameArray.push(device.name)}
|
620
|
+
@deviceNameArray.each do |device|
|
621
|
+
row = @listviewhabridgeDevices.add_row
|
622
|
+
row[:device] = device
|
623
|
+
end
|
624
|
+
#convert names to all lowercase for duplicates comparison
|
625
|
+
@deviceNameArray.map! {|str| str.downcase.chomp}
|
626
|
+
end
|
627
|
+
|
628
|
+
#------Query savant scenes from host
|
629
|
+
uri = URI(@scenesURLstr)
|
630
|
+
begin
|
631
|
+
http = Net::HTTP.start(uri.host, uri.port, {open_timeout: 1, read_timeout: 1})
|
632
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
633
|
+
response = http.request(request)
|
634
|
+
if response.code == "201" || response.code == "200"
|
635
|
+
unless response.body.to_s.empty?
|
636
|
+
scenesString = response.body
|
637
|
+
end
|
638
|
+
elsif response.code.include?("4" || "5")
|
639
|
+
scenesString = ""
|
640
|
+
end
|
641
|
+
rescue Timeout::Error, SocketError, Errno::EHOSTUNREACH, Errno::ECONNREFUSED
|
642
|
+
alert "Unable to get scene information from host. If you had echo
|
643
|
+
integration installed on this host previous to Savant version 8.3,
|
644
|
+
you need to update the SCLI bridge on the next screen."
|
645
|
+
scenesString = ""
|
646
|
+
end
|
647
|
+
unless scenesString.to_s.empty?
|
648
|
+
@sceneArray = scenesString.split(/\n|,/)
|
649
|
+
until @sceneArray.empty?
|
650
|
+
@sceneNameArray << @sceneArray.shift
|
651
|
+
@sceneIDArray << @sceneArray.shift
|
652
|
+
@sceneUserArray << @sceneArray.shift
|
653
|
+
end
|
654
|
+
|
655
|
+
#add scene names to UI
|
656
|
+
count = 0
|
657
|
+
@sceneNameArray.each do |scene|
|
658
|
+
row = @listviewSavantScenes.add_row
|
659
|
+
row[:scene] = scene
|
660
|
+
row[:id] = @sceneIDArray[count]
|
661
|
+
row[:user] = @sceneUserArray[count]
|
662
|
+
row[:add] = false
|
663
|
+
count += 1
|
664
|
+
end
|
665
|
+
end
|
666
|
+
@listviewhabridgeDevices.col_sortable(true)
|
667
|
+
@listviewSavantScenes.col_sortable(true)
|
668
|
+
@listviewhabridgeDevices.col_sort_column_id(:device => @listviewhabridgeDevices.id(:device))
|
669
|
+
@listviewSavantScenes.col_sort_column_id(:scene => @listviewSavantScenes.id(:scene), :user => @listviewSavantScenes.id(:user))
|
670
|
+
end
|
671
|
+
|
672
|
+
##### Add selected scenes to habridge #####
|
673
|
+
def buttonAddScenes__clicked(*argv)
|
674
|
+
#See which scenes were checked to be added
|
675
|
+
get_glade_variables()
|
676
|
+
@sceneNameArray = []
|
677
|
+
@sceneNameArray = []
|
678
|
+
@sceneNameArray = []
|
679
|
+
count = 0
|
680
|
+
@listviewSavantScenes.each_row { |row|
|
681
|
+
if row[:add]
|
682
|
+
@sceneArray << row[:scene]
|
683
|
+
@sceneArray << row[:id]
|
684
|
+
@sceneArray << row[:user]
|
685
|
+
count += 1
|
686
|
+
end
|
687
|
+
}
|
688
|
+
#Put scenes into habridge
|
689
|
+
unless count == 0
|
690
|
+
until @sceneArray.empty?
|
691
|
+
@sceneNameArray << @sceneArray.shift
|
692
|
+
@sceneIDArray << @sceneArray.shift
|
693
|
+
@sceneUserArray << @sceneArray.shift
|
694
|
+
end
|
695
|
+
sceneNameArrayLowercase = @sceneNameArray.map {|str| str.downcase.chomp}
|
696
|
+
@duplicateScenes = @deviceNameArray & sceneNameArrayLowercase
|
697
|
+
|
698
|
+
addScenes()
|
699
|
+
else
|
700
|
+
alert "No scenes selected."
|
701
|
+
end
|
702
|
+
|
703
|
+
end
|
704
|
+
|
705
|
+
def buttonCancelScenes__clicked(*argv)
|
706
|
+
@listviewSavantScenes.model.clear
|
707
|
+
@builder["dialogScenes"].hide
|
708
|
+
@listviewSavantScenes.model.clear
|
709
|
+
end
|
710
|
+
|
711
|
+
##### Add scenes to habridge #####
|
712
|
+
def addScenes()
|
713
|
+
message = ""
|
714
|
+
until @sceneNameArray.empty?
|
715
|
+
#Skip scenes that are already on the habridge
|
716
|
+
if !(@duplicateScenes.include? @sceneNameArray[0].downcase)
|
717
|
+
#build json string
|
718
|
+
@haScene = %q{\{'name' : '} + @sceneNameArray[0] + %q{','deviceType' : 'custom','onUrl' : '} + @savantURLstr + %q{/activateScene%20\'} + @sceneNameArray[0].gsub(" ", "%20") + %q{\'%20\'} + @sceneIDArray[0] + %q{\'%20\'} + @sceneUserArray[0] + %q{\''\}}
|
719
|
+
#post device to habridge
|
720
|
+
uri = URI.parse(@haURLstr)
|
721
|
+
begin
|
722
|
+
http = Net::HTTP.start(uri.host, uri.port, {open_timeout: 3, read_timeout: 3})
|
723
|
+
begin
|
724
|
+
request = Net::HTTP::Post.new(
|
725
|
+
uri.request_uri,
|
726
|
+
'Content-Type' => 'application/json'
|
727
|
+
)
|
728
|
+
request.body = @haScene
|
729
|
+
response = http.request(request)
|
730
|
+
if response.code == "201" || response.code == "200"
|
731
|
+
message += "\nScene added = " + @sceneNameArray[0]
|
732
|
+
elsif response.code.include?("4" || "5")
|
733
|
+
message += "\nFailed to add scene = " + @sceneNameArray[0]
|
734
|
+
end
|
735
|
+
rescue Timeout::Error, SocketError, Errno::EHOSTUNREACH, Errno::ECONNREFUSED
|
736
|
+
message "Unable to reach host."
|
737
|
+
end
|
738
|
+
rescue Timeout::Error, SocketError, Errno::EHOSTUNREACH, Errno::ECONNREFUSED
|
739
|
+
message "Unable to reach host."
|
740
|
+
end
|
741
|
+
else
|
742
|
+
message += "\nScene already exists = " + @sceneNameArray[0]
|
743
|
+
end
|
744
|
+
@sceneNameArray.delete_at(0)
|
745
|
+
@sceneIDArray.delete_at(0)
|
746
|
+
@sceneUserArray.delete_at(0)
|
747
|
+
|
748
|
+
end
|
749
|
+
alert message + "\n\nPlease tell Alexa to find devices."
|
750
|
+
end
|
751
|
+
|
752
|
+
##### Update SCLI bridge to add scene support in case SCLI bridge was added before 8.3 #####
|
753
|
+
def buttonUpdateSCLI__clicked(*argv)
|
754
|
+
login = 'RPM'
|
755
|
+
@password = 'RPM'
|
756
|
+
Net::SSH.start(@savantURL, login, :password => @password, :paranoid => false) do |ssh|
|
757
|
+
path = File.expand_path("..",__dir__)
|
758
|
+
ssh.exec!("launchctl unload /Users/RPM/Library/LaunchAgents/com.SCLI_HTTP.plist")
|
759
|
+
ssh.scp.upload!(path + "/habridge/SCLI_HTTP.rb", '/Users/RPM/habridge')
|
760
|
+
ssh.exec!("launchctl load /Users/RPM/Library/LaunchAgents/com.SCLI_HTTP.plist")
|
761
|
+
end
|
762
|
+
alert "SCLI Bridge Updated."
|
763
|
+
refresh()
|
764
|
+
end
|
765
|
+
|
766
|
+
##### Install scenesync script on host and start service to auto update scenes to habridge
|
767
|
+
def buttonInstallSceneSync__clicked(*argv)
|
768
|
+
login = 'RPM'
|
769
|
+
@password = 'RPM'
|
770
|
+
Net::SSH.start(@savantURL, login, :password => @password, :paranoid => false) do |ssh|
|
771
|
+
path = File.expand_path("..",__dir__)
|
772
|
+
ssh.exec!("launchctl unload /Users/RPM/Library/LaunchAgents/com.SCLI_HTTP.plist")
|
773
|
+
ssh.exec!("launchctl unload /Users/RPM/Library/LaunchAgents/com.habridgescenesync.plist")
|
774
|
+
ssh.scp.upload!(path + "/habridge/com.habridgescenesync.plist", '/Users/RPM/habridge')
|
775
|
+
ssh.scp.upload!(path + "/habridge/habridgescenesync.rb", '/Users/RPM/habridge')
|
776
|
+
ssh.scp.upload!(path + "/habridge/SCLI_HTTP.rb", '/Users/RPM/habridge')
|
777
|
+
ssh.exec!("mv /Users/RPM/habridge/com.habridgescenesync.plist /Users/RPM/Library/LaunchAgents/")
|
778
|
+
ssh.exec!("launchctl load /Users/RPM/Library/LaunchAgents/com.habridgescenesync.plist")
|
779
|
+
ssh.exec!("launchctl load /Users/RPM/Library/LaunchAgents/com.SCLI_HTTP.plist")
|
780
|
+
end
|
781
|
+
alert "HA Bridge Scene Sync installed."
|
782
|
+
end
|
783
|
+
|
784
|
+
##### Uninstall scene sync service on host and delete script
|
785
|
+
def buttonUninstallSceneSync__clicked(*argv)
|
786
|
+
login = 'RPM'
|
787
|
+
@password = 'RPM'
|
788
|
+
Net::SSH.start(@savantURL, login, :password => @password, :paranoid => false) do |ssh|
|
789
|
+
ssh.exec!("launchctl unload /Users/RPM/Library/LaunchAgents/com.habridgescenesync.plist")
|
790
|
+
ssh.exec!("rm -f ~/Library/LaunchAgents/com.habridgescenesync.plist")
|
791
|
+
ssh.exec!("rm –f ~/habridge/habridgescenesync.rb")
|
792
|
+
end
|
793
|
+
alert "HA Bridge Scene Sync uninstalled."
|
794
|
+
end
|
795
|
+
|
796
|
+
|
797
|
+
def buttonSceneSync__clicked(*argv)
|
798
|
+
if @habridgeStatus == "Offline"
|
799
|
+
@builder["dialogHelp"].show
|
800
|
+
elsif @habridgeStatus == "Online"
|
801
|
+
@haURLstr = "http://" + @savantURL + ":8888/api/devices"
|
802
|
+
@savantURLstr = "http://" + @savantURL + ":12000"
|
803
|
+
refresh()
|
804
|
+
@builder["dialogScenes"].show_all
|
805
|
+
end
|
409
806
|
end
|
410
807
|
|
411
808
|
##### Launch HABridge and SCLIbridge in browser and close installation status window #####
|
@@ -475,6 +872,7 @@ class Savant
|
|
475
872
|
|
476
873
|
end
|
477
874
|
|
875
|
+
|
478
876
|
##### #####
|
479
877
|
##### IP FIltering #####
|
480
878
|
##### (not yet implemented) #####
|
data/bin/src/glade/Savant.glade
CHANGED
@@ -111,11 +111,36 @@
|
|
111
111
|
<property name="hexpand">True</property>
|
112
112
|
<property name="left_padding">12</property>
|
113
113
|
<child>
|
114
|
-
<object class="
|
114
|
+
<object class="GtkGrid">
|
115
115
|
<property name="visible">True</property>
|
116
|
-
<property name="can_focus">
|
117
|
-
<
|
118
|
-
|
116
|
+
<property name="can_focus">False</property>
|
117
|
+
<child>
|
118
|
+
<object class="GtkEntry" id="haName">
|
119
|
+
<property name="visible">True</property>
|
120
|
+
<property name="can_focus">True</property>
|
121
|
+
<property name="halign">start</property>
|
122
|
+
<property name="width_chars">50</property>
|
123
|
+
</object>
|
124
|
+
<packing>
|
125
|
+
<property name="left_attach">0</property>
|
126
|
+
<property name="top_attach">0</property>
|
127
|
+
</packing>
|
128
|
+
</child>
|
129
|
+
<child>
|
130
|
+
<object class="GtkButton" id="buttonSceneSync">
|
131
|
+
<property name="label" translatable="yes">button</property>
|
132
|
+
<property name="visible">True</property>
|
133
|
+
<property name="can_focus">True</property>
|
134
|
+
<property name="receives_default">True</property>
|
135
|
+
<property name="halign">end</property>
|
136
|
+
<property name="hexpand">True</property>
|
137
|
+
<property name="image_position">right</property>
|
138
|
+
</object>
|
139
|
+
<packing>
|
140
|
+
<property name="left_attach">1</property>
|
141
|
+
<property name="top_attach">0</property>
|
142
|
+
</packing>
|
143
|
+
</child>
|
119
144
|
</object>
|
120
145
|
</child>
|
121
146
|
</object>
|
@@ -326,37 +351,74 @@
|
|
326
351
|
</packing>
|
327
352
|
</child>
|
328
353
|
<child>
|
329
|
-
<object class="
|
354
|
+
<object class="GtkGrid">
|
330
355
|
<property name="visible">True</property>
|
331
356
|
<property name="can_focus">False</property>
|
332
|
-
<property name="
|
333
|
-
<property name="
|
334
|
-
<property name="
|
335
|
-
<property name="layout_style">end</property>
|
357
|
+
<property name="margin_left">12</property>
|
358
|
+
<property name="margin_right">12</property>
|
359
|
+
<property name="column_homogeneous">True</property>
|
336
360
|
<child>
|
337
|
-
<object class="
|
338
|
-
<property name="label" translatable="yes">Add</property>
|
361
|
+
<object class="GtkButtonBox">
|
339
362
|
<property name="visible">True</property>
|
340
|
-
<property name="can_focus">
|
341
|
-
<property name="
|
363
|
+
<property name="can_focus">False</property>
|
364
|
+
<property name="margin_top">12</property>
|
365
|
+
<property name="margin_bottom">12</property>
|
366
|
+
<property name="spacing">7</property>
|
367
|
+
<property name="layout_style">end</property>
|
368
|
+
<child>
|
369
|
+
<object class="GtkButton" id="buttonAdd">
|
370
|
+
<property name="label" translatable="yes">Add</property>
|
371
|
+
<property name="visible">True</property>
|
372
|
+
<property name="can_focus">True</property>
|
373
|
+
<property name="receives_default">True</property>
|
374
|
+
</object>
|
375
|
+
<packing>
|
376
|
+
<property name="expand">True</property>
|
377
|
+
<property name="fill">True</property>
|
378
|
+
<property name="position">0</property>
|
379
|
+
</packing>
|
380
|
+
</child>
|
381
|
+
<child>
|
382
|
+
<object class="GtkButton" id="buttonExit">
|
383
|
+
<property name="label" translatable="yes">Exit</property>
|
384
|
+
<property name="visible">True</property>
|
385
|
+
<property name="can_focus">True</property>
|
386
|
+
<property name="receives_default">True</property>
|
387
|
+
</object>
|
388
|
+
<packing>
|
389
|
+
<property name="expand">True</property>
|
390
|
+
<property name="fill">True</property>
|
391
|
+
<property name="position">1</property>
|
392
|
+
</packing>
|
393
|
+
</child>
|
342
394
|
</object>
|
343
395
|
<packing>
|
344
|
-
<property name="
|
345
|
-
<property name="
|
346
|
-
<property name="position">0</property>
|
396
|
+
<property name="left_attach">1</property>
|
397
|
+
<property name="top_attach">0</property>
|
347
398
|
</packing>
|
348
399
|
</child>
|
349
400
|
<child>
|
350
|
-
<object class="
|
351
|
-
<property name="label" translatable="yes">Exit</property>
|
401
|
+
<object class="GtkButtonBox">
|
352
402
|
<property name="visible">True</property>
|
353
|
-
<property name="can_focus">
|
354
|
-
<property name="
|
403
|
+
<property name="can_focus">False</property>
|
404
|
+
<property name="layout_style">start</property>
|
405
|
+
<child>
|
406
|
+
<object class="GtkButton" id="buttonBackupRestore">
|
407
|
+
<property name="label" translatable="yes">Backup/Restore</property>
|
408
|
+
<property name="visible">True</property>
|
409
|
+
<property name="can_focus">True</property>
|
410
|
+
<property name="receives_default">True</property>
|
411
|
+
</object>
|
412
|
+
<packing>
|
413
|
+
<property name="expand">True</property>
|
414
|
+
<property name="fill">True</property>
|
415
|
+
<property name="position">0</property>
|
416
|
+
</packing>
|
417
|
+
</child>
|
355
418
|
</object>
|
356
419
|
<packing>
|
357
|
-
<property name="
|
358
|
-
<property name="
|
359
|
-
<property name="position">1</property>
|
420
|
+
<property name="left_attach">0</property>
|
421
|
+
<property name="top_attach">0</property>
|
360
422
|
</packing>
|
361
423
|
</child>
|
362
424
|
</object>
|
@@ -1819,4 +1881,368 @@ Enter "percent" to derive 0-100 value from voice command.</property>
|
|
1819
1881
|
</object>
|
1820
1882
|
</child>
|
1821
1883
|
</object>
|
1884
|
+
<object class="GtkDialog" id="dialogScenes">
|
1885
|
+
<property name="width_request">800</property>
|
1886
|
+
<property name="can_focus">False</property>
|
1887
|
+
<property name="modal">True</property>
|
1888
|
+
<property name="window_position">center-on-parent</property>
|
1889
|
+
<property name="default_width">320</property>
|
1890
|
+
<property name="default_height">260</property>
|
1891
|
+
<property name="destroy_with_parent">True</property>
|
1892
|
+
<property name="type_hint">dialog</property>
|
1893
|
+
<property name="urgency_hint">True</property>
|
1894
|
+
<property name="transient_for">window1</property>
|
1895
|
+
<child internal-child="vbox">
|
1896
|
+
<object class="GtkBox">
|
1897
|
+
<property name="can_focus">False</property>
|
1898
|
+
<property name="margin_top">12</property>
|
1899
|
+
<property name="margin_bottom">12</property>
|
1900
|
+
<property name="orientation">vertical</property>
|
1901
|
+
<property name="spacing">2</property>
|
1902
|
+
<child internal-child="action_area">
|
1903
|
+
<object class="GtkButtonBox">
|
1904
|
+
<property name="can_focus">False</property>
|
1905
|
+
<property name="layout_style">end</property>
|
1906
|
+
<child>
|
1907
|
+
<object class="GtkButton" id="buttonCancelScenes">
|
1908
|
+
<property name="label" translatable="yes">Done</property>
|
1909
|
+
<property name="visible">True</property>
|
1910
|
+
<property name="can_focus">True</property>
|
1911
|
+
<property name="receives_default">True</property>
|
1912
|
+
<property name="margin_right">12</property>
|
1913
|
+
<property name="margin_top">6</property>
|
1914
|
+
</object>
|
1915
|
+
<packing>
|
1916
|
+
<property name="expand">True</property>
|
1917
|
+
<property name="fill">True</property>
|
1918
|
+
<property name="position">1</property>
|
1919
|
+
</packing>
|
1920
|
+
</child>
|
1921
|
+
</object>
|
1922
|
+
<packing>
|
1923
|
+
<property name="expand">False</property>
|
1924
|
+
<property name="fill">False</property>
|
1925
|
+
<property name="position">0</property>
|
1926
|
+
</packing>
|
1927
|
+
</child>
|
1928
|
+
<child>
|
1929
|
+
<object class="GtkGrid">
|
1930
|
+
<property name="visible">True</property>
|
1931
|
+
<property name="can_focus">False</property>
|
1932
|
+
<child>
|
1933
|
+
<object class="GtkLabel">
|
1934
|
+
<property name="visible">True</property>
|
1935
|
+
<property name="can_focus">False</property>
|
1936
|
+
<property name="margin_bottom">5</property>
|
1937
|
+
<property name="label" translatable="yes"><big><b>Savant Scenes</b></big>
|
1938
|
+
|
1939
|
+
Check the box next to a scene and select "add" to create an Alexa device with name of the scene.
|
1940
|
+
|
1941
|
+
The following scenes have been found on this host:</property>
|
1942
|
+
<property name="use_markup">True</property>
|
1943
|
+
<property name="justify">center</property>
|
1944
|
+
</object>
|
1945
|
+
<packing>
|
1946
|
+
<property name="left_attach">0</property>
|
1947
|
+
<property name="top_attach">0</property>
|
1948
|
+
</packing>
|
1949
|
+
</child>
|
1950
|
+
<child>
|
1951
|
+
<object class="GtkScrolledWindow" id="listviewSavantScenes">
|
1952
|
+
<property name="visible">True</property>
|
1953
|
+
<property name="can_focus">True</property>
|
1954
|
+
<property name="margin_left">12</property>
|
1955
|
+
<property name="margin_right">12</property>
|
1956
|
+
<property name="margin_bottom">12</property>
|
1957
|
+
<property name="hexpand">True</property>
|
1958
|
+
<property name="shadow_type">in</property>
|
1959
|
+
<property name="min_content_height">200</property>
|
1960
|
+
<property name="max_content_height">400</property>
|
1961
|
+
<property name="propagate_natural_height">True</property>
|
1962
|
+
<child>
|
1963
|
+
<placeholder/>
|
1964
|
+
</child>
|
1965
|
+
</object>
|
1966
|
+
<packing>
|
1967
|
+
<property name="left_attach">0</property>
|
1968
|
+
<property name="top_attach">1</property>
|
1969
|
+
</packing>
|
1970
|
+
</child>
|
1971
|
+
<child>
|
1972
|
+
<object class="GtkScrolledWindow" id="listviewhabridgeDevices">
|
1973
|
+
<property name="visible">True</property>
|
1974
|
+
<property name="can_focus">True</property>
|
1975
|
+
<property name="margin_left">12</property>
|
1976
|
+
<property name="margin_right">12</property>
|
1977
|
+
<property name="margin_top">12</property>
|
1978
|
+
<property name="margin_bottom">12</property>
|
1979
|
+
<property name="hexpand">True</property>
|
1980
|
+
<property name="shadow_type">in</property>
|
1981
|
+
<property name="min_content_height">200</property>
|
1982
|
+
<property name="max_content_height">400</property>
|
1983
|
+
<property name="propagate_natural_height">True</property>
|
1984
|
+
<child>
|
1985
|
+
<placeholder/>
|
1986
|
+
</child>
|
1987
|
+
</object>
|
1988
|
+
<packing>
|
1989
|
+
<property name="left_attach">0</property>
|
1990
|
+
<property name="top_attach">4</property>
|
1991
|
+
</packing>
|
1992
|
+
</child>
|
1993
|
+
<child>
|
1994
|
+
<object class="GtkLabel">
|
1995
|
+
<property name="visible">True</property>
|
1996
|
+
<property name="can_focus">False</property>
|
1997
|
+
<property name="label" translatable="yes"><big><b>Automatic Scenes Sync</b></big>
|
1998
|
+
|
1999
|
+
Scene sync is a background service that runs on the pro host to automatically sync Savant user scenes to the ha bridge.
|
2000
|
+
When a scene is created in Savant, it will be added to the habridge.
|
2001
|
+
When a scene is deleted in Savant, it will be deleted from the habridge.
|
2002
|
+
The user will simply need to ask Alexa to find devices after creating a scene.
|
2003
|
+
To call the scene, use the syntax: Alexa, Turn On (Scene Name).</property>
|
2004
|
+
<property name="use_markup">True</property>
|
2005
|
+
<property name="justify">center</property>
|
2006
|
+
</object>
|
2007
|
+
<packing>
|
2008
|
+
<property name="left_attach">0</property>
|
2009
|
+
<property name="top_attach">5</property>
|
2010
|
+
</packing>
|
2011
|
+
</child>
|
2012
|
+
<child>
|
2013
|
+
<object class="GtkGrid">
|
2014
|
+
<property name="visible">True</property>
|
2015
|
+
<property name="can_focus">False</property>
|
2016
|
+
<property name="halign">center</property>
|
2017
|
+
<property name="margin_top">12</property>
|
2018
|
+
<property name="column_homogeneous">True</property>
|
2019
|
+
<child>
|
2020
|
+
<object class="GtkButton" id="buttonInstallSceneSync">
|
2021
|
+
<property name="label" translatable="yes">Install</property>
|
2022
|
+
<property name="visible">True</property>
|
2023
|
+
<property name="can_focus">True</property>
|
2024
|
+
<property name="receives_default">True</property>
|
2025
|
+
<property name="margin_right">12</property>
|
2026
|
+
</object>
|
2027
|
+
<packing>
|
2028
|
+
<property name="left_attach">0</property>
|
2029
|
+
<property name="top_attach">0</property>
|
2030
|
+
</packing>
|
2031
|
+
</child>
|
2032
|
+
<child>
|
2033
|
+
<object class="GtkButton" id="buttonUninstallSceneSync">
|
2034
|
+
<property name="label" translatable="yes">Uninstall</property>
|
2035
|
+
<property name="visible">True</property>
|
2036
|
+
<property name="can_focus">True</property>
|
2037
|
+
<property name="receives_default">True</property>
|
2038
|
+
<property name="margin_left">12</property>
|
2039
|
+
</object>
|
2040
|
+
<packing>
|
2041
|
+
<property name="left_attach">1</property>
|
2042
|
+
<property name="top_attach">0</property>
|
2043
|
+
</packing>
|
2044
|
+
</child>
|
2045
|
+
</object>
|
2046
|
+
<packing>
|
2047
|
+
<property name="left_attach">0</property>
|
2048
|
+
<property name="top_attach">6</property>
|
2049
|
+
</packing>
|
2050
|
+
</child>
|
2051
|
+
<child>
|
2052
|
+
<object class="GtkLabel">
|
2053
|
+
<property name="visible">True</property>
|
2054
|
+
<property name="can_focus">False</property>
|
2055
|
+
<property name="label" translatable="yes"><big><b>HA Bridge Devices</b></big>
|
2056
|
+
|
2057
|
+
The following devices have been found on this habridge:</property>
|
2058
|
+
<property name="use_markup">True</property>
|
2059
|
+
<property name="justify">center</property>
|
2060
|
+
</object>
|
2061
|
+
<packing>
|
2062
|
+
<property name="left_attach">0</property>
|
2063
|
+
<property name="top_attach">3</property>
|
2064
|
+
</packing>
|
2065
|
+
</child>
|
2066
|
+
<child>
|
2067
|
+
<object class="GtkGrid">
|
2068
|
+
<property name="visible">True</property>
|
2069
|
+
<property name="can_focus">False</property>
|
2070
|
+
<property name="margin_left">12</property>
|
2071
|
+
<property name="margin_right">12</property>
|
2072
|
+
<property name="column_homogeneous">True</property>
|
2073
|
+
<child>
|
2074
|
+
<object class="GtkButton" id="buttonAddScenes">
|
2075
|
+
<property name="label" translatable="yes">Add Scenes</property>
|
2076
|
+
<property name="visible">True</property>
|
2077
|
+
<property name="can_focus">True</property>
|
2078
|
+
<property name="receives_default">True</property>
|
2079
|
+
<property name="halign">end</property>
|
2080
|
+
<property name="margin_top">6</property>
|
2081
|
+
</object>
|
2082
|
+
<packing>
|
2083
|
+
<property name="left_attach">1</property>
|
2084
|
+
<property name="top_attach">0</property>
|
2085
|
+
</packing>
|
2086
|
+
</child>
|
2087
|
+
<child>
|
2088
|
+
<object class="GtkButton" id="buttonUpdateSCLI">
|
2089
|
+
<property name="label" translatable="yes">Update SCLI</property>
|
2090
|
+
<property name="visible">True</property>
|
2091
|
+
<property name="can_focus">True</property>
|
2092
|
+
<property name="receives_default">True</property>
|
2093
|
+
<property name="halign">start</property>
|
2094
|
+
<property name="margin_left">12</property>
|
2095
|
+
</object>
|
2096
|
+
<packing>
|
2097
|
+
<property name="left_attach">0</property>
|
2098
|
+
<property name="top_attach">0</property>
|
2099
|
+
</packing>
|
2100
|
+
</child>
|
2101
|
+
</object>
|
2102
|
+
<packing>
|
2103
|
+
<property name="left_attach">0</property>
|
2104
|
+
<property name="top_attach">2</property>
|
2105
|
+
</packing>
|
2106
|
+
</child>
|
2107
|
+
</object>
|
2108
|
+
<packing>
|
2109
|
+
<property name="expand">False</property>
|
2110
|
+
<property name="fill">True</property>
|
2111
|
+
<property name="position">1</property>
|
2112
|
+
</packing>
|
2113
|
+
</child>
|
2114
|
+
</object>
|
2115
|
+
</child>
|
2116
|
+
</object>
|
2117
|
+
<object class="GtkWindow" id="windowBackupRestore">
|
2118
|
+
<property name="width_request">300</property>
|
2119
|
+
<property name="height_request">300</property>
|
2120
|
+
<property name="can_focus">False</property>
|
2121
|
+
<property name="modal">True</property>
|
2122
|
+
<property name="window_position">center-on-parent</property>
|
2123
|
+
<property name="gravity">center</property>
|
2124
|
+
<property name="transient_for">window1</property>
|
2125
|
+
<property name="has_resize_grip">True</property>
|
2126
|
+
<child>
|
2127
|
+
<object class="GtkBox">
|
2128
|
+
<property name="visible">True</property>
|
2129
|
+
<property name="can_focus">False</property>
|
2130
|
+
<property name="orientation">vertical</property>
|
2131
|
+
<child>
|
2132
|
+
<object class="GtkLabel">
|
2133
|
+
<property name="visible">True</property>
|
2134
|
+
<property name="can_focus">False</property>
|
2135
|
+
<property name="margin_top">20</property>
|
2136
|
+
<property name="margin_bottom">20</property>
|
2137
|
+
<property name="label" translatable="yes"><big><b>Backup and Restore</b></big></property>
|
2138
|
+
<property name="use_markup">True</property>
|
2139
|
+
</object>
|
2140
|
+
<packing>
|
2141
|
+
<property name="expand">False</property>
|
2142
|
+
<property name="fill">True</property>
|
2143
|
+
<property name="position">0</property>
|
2144
|
+
</packing>
|
2145
|
+
</child>
|
2146
|
+
<child>
|
2147
|
+
<object class="GtkLabel">
|
2148
|
+
<property name="visible">True</property>
|
2149
|
+
<property name="can_focus">False</property>
|
2150
|
+
<property name="margin_left">20</property>
|
2151
|
+
<property name="margin_right">20</property>
|
2152
|
+
<property name="margin_bottom">12</property>
|
2153
|
+
<property name="label" translatable="yes"> There are two files to be backed up and restored.
|
2154
|
+
The first is device.db that contains all the virtual devices in the habridge.
|
2155
|
+
The second is the habridge.config file that contains information about the habridge itself.
|
2156
|
+
|
2157
|
+
To backup the files, select a destination folder.
|
2158
|
+
|
2159
|
+
To restore the backed up files to the habridge, select the folder that contains both files. </property>
|
2160
|
+
<property name="wrap">True</property>
|
2161
|
+
</object>
|
2162
|
+
<packing>
|
2163
|
+
<property name="expand">False</property>
|
2164
|
+
<property name="fill">False</property>
|
2165
|
+
<property name="position">1</property>
|
2166
|
+
</packing>
|
2167
|
+
</child>
|
2168
|
+
<child>
|
2169
|
+
<object class="GtkScrolledWindow" id="scrolledwindow_BackupRestore">
|
2170
|
+
<property name="width_request">300</property>
|
2171
|
+
<property name="height_request">300</property>
|
2172
|
+
<property name="visible">True</property>
|
2173
|
+
<property name="can_focus">True</property>
|
2174
|
+
<property name="margin_left">20</property>
|
2175
|
+
<property name="margin_right">20</property>
|
2176
|
+
<property name="shadow_type">in</property>
|
2177
|
+
<property name="min_content_width">300</property>
|
2178
|
+
<property name="min_content_height">300</property>
|
2179
|
+
<property name="max_content_width">300</property>
|
2180
|
+
<property name="max_content_height">300</property>
|
2181
|
+
<child>
|
2182
|
+
<placeholder/>
|
2183
|
+
</child>
|
2184
|
+
</object>
|
2185
|
+
<packing>
|
2186
|
+
<property name="expand">False</property>
|
2187
|
+
<property name="fill">False</property>
|
2188
|
+
<property name="position">2</property>
|
2189
|
+
</packing>
|
2190
|
+
</child>
|
2191
|
+
<child>
|
2192
|
+
<object class="GtkButtonBox">
|
2193
|
+
<property name="visible">True</property>
|
2194
|
+
<property name="can_focus">False</property>
|
2195
|
+
<property name="margin_top">20</property>
|
2196
|
+
<property name="margin_bottom">12</property>
|
2197
|
+
<property name="layout_style">spread</property>
|
2198
|
+
<child>
|
2199
|
+
<object class="GtkButton" id="buttonBackup">
|
2200
|
+
<property name="label" translatable="yes">Backup</property>
|
2201
|
+
<property name="visible">True</property>
|
2202
|
+
<property name="can_focus">True</property>
|
2203
|
+
<property name="receives_default">True</property>
|
2204
|
+
</object>
|
2205
|
+
<packing>
|
2206
|
+
<property name="expand">True</property>
|
2207
|
+
<property name="fill">True</property>
|
2208
|
+
<property name="position">0</property>
|
2209
|
+
</packing>
|
2210
|
+
</child>
|
2211
|
+
<child>
|
2212
|
+
<object class="GtkButton" id="buttonRestore">
|
2213
|
+
<property name="label" translatable="yes">Restore</property>
|
2214
|
+
<property name="visible">True</property>
|
2215
|
+
<property name="can_focus">True</property>
|
2216
|
+
<property name="receives_default">True</property>
|
2217
|
+
</object>
|
2218
|
+
<packing>
|
2219
|
+
<property name="expand">True</property>
|
2220
|
+
<property name="fill">True</property>
|
2221
|
+
<property name="position">1</property>
|
2222
|
+
</packing>
|
2223
|
+
</child>
|
2224
|
+
<child>
|
2225
|
+
<object class="GtkButton" id="buttonBackupRestoreCancel">
|
2226
|
+
<property name="label" translatable="yes">Cancel</property>
|
2227
|
+
<property name="visible">True</property>
|
2228
|
+
<property name="can_focus">True</property>
|
2229
|
+
<property name="receives_default">True</property>
|
2230
|
+
</object>
|
2231
|
+
<packing>
|
2232
|
+
<property name="expand">True</property>
|
2233
|
+
<property name="fill">True</property>
|
2234
|
+
<property name="position">2</property>
|
2235
|
+
</packing>
|
2236
|
+
</child>
|
2237
|
+
</object>
|
2238
|
+
<packing>
|
2239
|
+
<property name="expand">True</property>
|
2240
|
+
<property name="fill">True</property>
|
2241
|
+
<property name="pack_type">end</property>
|
2242
|
+
<property name="position">3</property>
|
2243
|
+
</packing>
|
2244
|
+
</child>
|
2245
|
+
</object>
|
2246
|
+
</child>
|
2247
|
+
</object>
|
1822
2248
|
</interface>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: savant-echo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Corey Miller
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir:
|
10
10
|
- bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2017-
|
12
|
+
date: 2017-04-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: net-scp
|
@@ -92,7 +92,7 @@ dependencies:
|
|
92
92
|
- !ruby/object:Gem::Version
|
93
93
|
version: 1.2.0
|
94
94
|
description: Savant Integration with Amazon Echo
|
95
|
-
email: cmiller@premieresystems.com
|
95
|
+
email: cmiller@premieresystems.com
|
96
96
|
executables:
|
97
97
|
- alexa
|
98
98
|
extensions: []
|
@@ -102,8 +102,10 @@ files:
|
|
102
102
|
- bin/habridge/SCLI_HTTP.rb
|
103
103
|
- bin/habridge/com.SCLI_HTTP.plist
|
104
104
|
- bin/habridge/com.habridge.plist
|
105
|
+
- bin/habridge/com.habridgescenesync.plist
|
105
106
|
- bin/habridge/data/habridge.config
|
106
107
|
- bin/habridge/ha-bridge.jar
|
108
|
+
- bin/habridge/habridgescenesync.rb
|
107
109
|
- bin/src/Savant.rb
|
108
110
|
- bin/src/glade/Savant.glade
|
109
111
|
homepage: http://www.yoursite.org/
|