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