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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: aa49c5c4a0083affe39ca9c6379fb2a2cefd9006
4
- data.tar.gz: 63505440aceff03b3e612d6280b944e4a37c1031
3
+ metadata.gz: 1d697f91ad2086bedf9e01c21539cede24ef4cf6
4
+ data.tar.gz: f751654b48d18c21fd2a9a9a96fb4523fe324c2e
5
5
  SHA512:
6
- metadata.gz: c70c6e2a7dbecf7e66aed4fecd9bb63b45c755305da03a23f8a78dc8c4095adbb121cbef74caa85a10d6e6026d989ea0037cbf993e4d1f755451345f36b542e6
7
- data.tar.gz: 2b7f8c257b4456c87d0ee3f02a50eac9c3bef48c6702870640eac4f3a21baba6a3d64696df152fad54d321b1df38a2857fc3e3af612c03f9c7bc459c2fde805a
6
+ metadata.gz: 8e48ac117ce8fba65784726791673c24d34989f347f4b17a4aaf0922c7c18ce5bccc1056ec4381b845929339fd1246ad99f640fd98417987df32e60fa28fed34
7
+ data.tar.gz: 2b625506c37558dad6409404d5b5201b31713444d4b20dabc1c7e1c5031101100897071b4309205527f16ed49918918637027ba528cff4562c047fe84ca7a90e
@@ -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
@@ -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
- class Savant
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?(server, port)
13
- http = Net::HTTP.start(server, port, {open_timeout: 1, read_timeout: 1})
14
- response = http.head("/")
15
- if response.code == "200"
16
- @builder["buttonHabridgeStatus"].label = "HA Bridge is Online\nClick to open in browser."
17
- @habridgeStatus = "Online"
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
- #####Method to check to see if Racepoint and SCLI bridge ports are open on host#####
55
+ #####Check to see if Racepoint and SCLI bridge ports are open on host#####
26
56
  def is_port_open?(ip, port)
27
- begin
28
- Timeout::timeout(1) do
29
- begin
30
- s = TCPSocket.new(ip, port)
31
- s.close
32
- return true
33
- rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
34
- return false
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
- return false
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
- #####Check HA bridge server status for feedback when textbox is edited#####
92
- def savantURL__focus_out_event(*argv)
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
- def savantHelpURL__focus_out_event(*argv)
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 12000."
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) #####
@@ -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="GtkEntry" id="haName">
114
+ <object class="GtkGrid">
115
115
  <property name="visible">True</property>
116
- <property name="can_focus">True</property>
117
- <property name="halign">start</property>
118
- <property name="width_chars">50</property>
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="GtkButtonBox">
354
+ <object class="GtkGrid">
330
355
  <property name="visible">True</property>
331
356
  <property name="can_focus">False</property>
332
- <property name="margin_top">12</property>
333
- <property name="margin_bottom">12</property>
334
- <property name="spacing">7</property>
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="GtkButton" id="buttonAdd">
338
- <property name="label" translatable="yes">Add</property>
361
+ <object class="GtkButtonBox">
339
362
  <property name="visible">True</property>
340
- <property name="can_focus">True</property>
341
- <property name="receives_default">True</property>
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="expand">True</property>
345
- <property name="fill">True</property>
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="GtkButton" id="buttonExit">
351
- <property name="label" translatable="yes">Exit</property>
401
+ <object class="GtkButtonBox">
352
402
  <property name="visible">True</property>
353
- <property name="can_focus">True</property>
354
- <property name="receives_default">True</property>
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="expand">True</property>
358
- <property name="fill">True</property>
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">&lt;big&gt;&lt;b&gt;Savant Scenes&lt;/b&gt;&lt;/big&gt;
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">&lt;big&gt;&lt;b&gt;Automatic Scenes Sync&lt;/b&gt;&lt;/big&gt;
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">&lt;big&gt;&lt;b&gt;HA Bridge Devices&lt;/b&gt;&lt;/big&gt;
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">&lt;big&gt;&lt;b&gt;Backup and Restore&lt;/b&gt;&lt;/big&gt;</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.5
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-03-08 00:00:00.000000000 Z
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.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/