4 * Copyright 2015 Roomie Remote, Inc.
10 name: "Simple Sync Connect",
11 namespace: "roomieremote-raconnect",
12 author: "Roomie Remote, Inc.",
13 description: "Integrate SmartThings with your Simple Control activities via Simple Sync.",
15 iconUrl: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-60.png",
16 iconX2Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png",
17 iconX3Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png")
21 page(name: "mainPage", title: "Simple Sync Setup", content: "mainPage", refreshTimeout: 5)
22 page(name:"agentDiscovery", title:"Simple Sync Discovery", content:"agentDiscovery", refreshTimeout:5)
23 page(name:"manualAgentEntry")
24 page(name:"verifyManualEntry")
31 return agentDiscovery()
35 def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date.
37 To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub"."""
39 return dynamicPage(name:"mainPage", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) {
42 paragraph "$upgradeNeeded"
48 def agentDiscovery(params=[:])
50 int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int
51 state.refreshCount = refreshCount + 1
52 def refreshInterval = refreshCount == 0 ? 2 : 5
56 subscribe(location, null, locationHandler, [filterEvents:false])
57 state.subscribe = true
60 //ssdp request every fifth refresh
61 if ((refreshCount % 5) == 0)
66 def agentsDiscovered = agentsDiscovered()
68 return dynamicPage(name:"agentDiscovery", title:"Pair with Simple Sync", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) {
69 section("Pair with Simple Sync")
71 input "selectedAgent", "enum", required:true, title:"Select Simple Sync\n(${agentsDiscovered.size() ?: 0} found)", multiple:false, options:agentsDiscovered
72 href(name:"manualAgentEntry",
73 title:"Manually Configure Simple Sync",
75 page:"manualAgentEntry")
80 def manualAgentEntry()
82 dynamicPage(name:"manualAgentEntry", title:"Manually Configure Simple Sync", nextPage:"verifyManualEntry", install:false, uninstall:true) {
83 section("Manually Configure Simple Sync")
85 paragraph "In the event that Simple Sync cannot be automatically discovered by your SmartThings hub, you may enter Simple Sync's IP address here."
86 input(name: "manualIPAddress", type: "text", title: "IP Address", required: true)
91 def verifyManualEntry()
93 def hexIP = convertIPToHexString(manualIPAddress)
94 def hexPort = convertToHexString(47147)
95 def uuid = "593C03D2-1DA9-4CDB-A335-6C6DC98E56C3"
98 for (hub in location.hubs)
100 if (hub.localIP != null)
107 def manualAgent = [deviceType: "04",
111 ssdpPath: "/upnp/Roomie.xml",
112 ssdpUSN: "uuid:$uuid::urn:roomieremote-com:device:roomie:1",
115 name: "Simple Sync $manualIPAddress"]
117 state.agents[uuid] = manualAgent
119 addOrUpdateAgent(state.agents[uuid])
121 dynamicPage(name: "verifyManualEntry", title: "Manual Configuration Complete", nextPage: "", install:true, uninstall:true) {
124 paragraph("Tap Done to complete the installation process.")
133 sendHubCommand(new physicalgraph.device.HubAction("lan discovery $urn", physicalgraph.device.Protocol.LAN))
136 def agentsDiscovered()
138 def gAgents = getAgents()
139 def agents = gAgents.findAll { it?.value?.verified == true }
143 map["${it.value.uuid}"] = it.value.name
173 state.subscribe = false
178 addOrUpdateAgent(state.agents[selectedAgent])
182 def addOrUpdateAgent(agent)
184 def children = getChildDevices()
185 def dni = agent.ip + ":" + agent.port
190 if ((it.getDeviceDataByName("mac") == agent.mac))
194 if (it.getDeviceNetworkId() != dni)
196 it.setDeviceNetworkId(dni)
199 else if (it.getDeviceNetworkId() == dni)
207 addChildDevice("roomieremote-agent", "Simple Sync", dni, agent.hub, [label: "Simple Sync"])
211 def locationHandler(evt)
213 def description = evt?.description
216 def parsedEvent = parseEventMessage(description)
218 parsedEvent?.putAt("hub", hub)
220 //SSDP DISCOVERY EVENTS
221 if (parsedEvent?.ssdpTerm?.contains(urn))
223 def agent = parsedEvent
224 def ip = convertHexToIP(agent.ip)
225 def agents = getAgents()
227 agent.verified = true
228 agent.name = "Simple Sync $ip"
230 if (!agents[agent.uuid])
232 state.agents[agent.uuid] = agent
237 private def parseEventMessage(String description)
240 def parts = description.split(',')
245 if (part.startsWith('devicetype:'))
247 def valueString = part.split(":")[1].trim()
248 event.devicetype = valueString
250 else if (part.startsWith('mac:'))
252 def valueString = part.split(":")[1].trim()
255 event.mac = valueString
258 else if (part.startsWith('networkAddress:'))
260 def valueString = part.split(":")[1].trim()
263 event.ip = valueString
266 else if (part.startsWith('deviceAddress:'))
268 def valueString = part.split(":")[1].trim()
271 event.port = valueString
274 else if (part.startsWith('ssdpPath:'))
276 def valueString = part.split(":")[1].trim()
279 event.ssdpPath = valueString
282 else if (part.startsWith('ssdpUSN:'))
285 def valueString = part.trim()
288 event.ssdpUSN = valueString
290 def uuid = getUUIDFromUSN(valueString)
298 else if (part.startsWith('ssdpTerm:'))
301 def valueString = part.trim()
304 event.ssdpTerm = valueString
307 else if (part.startsWith('headers'))
310 def valueString = part.trim()
313 event.headers = valueString
316 else if (part.startsWith('body'))
319 def valueString = part.trim()
322 event.body = valueString
332 return "urn:roomieremote-com:device:roomie:1"
335 def getUUIDFromUSN(usn)
337 def parts = usn.split(":")
339 for (int i = 0; i < parts.size(); ++i)
341 if (parts[i] == "uuid")
348 def String convertHexToIP(hex)
350 [convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
353 def Integer convertHexToInt(hex)
355 Integer.parseInt(hex,16)
358 def String convertToHexString(n)
360 String hex = String.format("%X", n.toInteger())
363 def String convertIPToHexString(ipString)
365 String hex = ipString.tokenize(".").collect {
366 String.format("%02X", it.toInteger())
370 def Boolean canInstallLabs()
372 return hasAllHubsOver("000.011.00603")
375 def Boolean hasAllHubsOver(String desiredFirmware)
377 return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
380 def List getRealHubFirmwareVersions()
382 return location.hubs*.firmwareVersionString.findAll { it }