savant-echo 1.0.5 → 1.1.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.
- 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/
|