diff --git a/devicetypes/smartthings/samsung-smart-tv.src/samsung-smart-tv.groovy b/devicetypes/smartthings/samsung-smart-tv.src/samsung-smart-tv.groovy deleted file mode 100644 index f53a6c3..0000000 --- a/devicetypes/smartthings/samsung-smart-tv.src/samsung-smart-tv.groovy +++ /dev/null @@ -1,235 +0,0 @@ -/** - * Copyright 2015 SmartThings - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License - * for the specific language governing permissions and limitations under the License. - * - * Samsung TV - * - * Author: SmartThings (juano23@gmail.com) - * Date: 2015-01-08 - */ - -metadata { - definition (name: "Samsung Smart TV", namespace: "smartthings", author: "SmartThings") { - capability "switch" - - command "mute" - command "source" - command "menu" - command "tools" - command "HDMI" - command "Sleep" - command "Up" - command "Down" - command "Left" - command "Right" - command "chup" - command "chdown" - command "prech" - command "volup" - command "voldown" - command "Enter" - command "Return" - command "Exit" - command "Info" - command "Size" - } - - standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) { - state "default", label:'TV', action:"switch.off", icon:"st.Electronics.electronics15", backgroundColor:"#ffffff" - } - standardTile("power", "device.switch", width: 1, height: 1, canChangeIcon: false) { - state "default", label:'', action:"switch.off", decoration: "flat", icon:"st.thermostat.heating-cooling-off", backgroundColor:"#ffffff" - } - standardTile("mute", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Mute', action:"mute", icon:"st.custom.sonos.muted", backgroundColor:"#ffffff" - } - standardTile("source", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Source', action:"source", icon:"st.Electronics.electronics15" - } - standardTile("tools", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Tools', action:"tools", icon:"st.secondary.tools" - } - standardTile("menu", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Menu', action:"menu", icon:"st.vents.vent" - } - standardTile("HDMI", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Source', action:"HDMI", icon:"st.Electronics.electronics15" - } - standardTile("Sleep", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Sleep', action:"Sleep", icon:"st.Bedroom.bedroom10" - } - standardTile("Up", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Up', action:"Up", icon:"st.thermostat.thermostat-up" - } - standardTile("Down", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Down', action:"Down", icon:"st.thermostat.thermostat-down" - } - standardTile("Left", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Left', action:"Left", icon:"st.thermostat.thermostat-left" - } - standardTile("Right", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Right', action:"Right", icon:"st.thermostat.thermostat-right" - } - standardTile("chup", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'CH Up', action:"chup", icon:"st.thermostat.thermostat-up" - } - standardTile("chdown", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'CH Down', action:"chdown", icon:"st.thermostat.thermostat-down" - } - standardTile("prech", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Pre CH', action:"prech", icon:"st.secondary.refresh-icon" - } - standardTile("volup", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Vol Up', action:"volup", icon:"st.thermostat.thermostat-up" - } - standardTile("voldown", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Vol Down', action:"voldown", icon:"st.thermostat.thermostat-down" - } - standardTile("Enter", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Enter', action:"Enter", icon:"st.illuminance.illuminance.dark" - } - standardTile("Return", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Return', action:"Return", icon:"st.secondary.refresh-icon" - } - standardTile("Exit", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Exit', action:"Exit", icon:"st.locks.lock.unlocked" - } - standardTile("Info", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Info', action:"Info", icon:"st.motion.acceleration.active" - } - standardTile("Size", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Picture Size', action:"Size", icon:"st.contact.contact.open" - } - main "switch" - details (["power","HDMI","Sleep","chup","prech","volup","chdown","mute","voldown", "menu", "Up", "tools", "Left", "Enter", "Right", "Return", "Down", "Exit", "Info","Size"]) -} - -def parse(String description) { - return null -} - -def off() { - log.debug "Turning TV OFF" - parent.tvAction("POWEROFF",device.deviceNetworkId) - sendEvent(name:"Command", value: "Power Off", displayed: true) -} - -def mute() { - log.trace "MUTE pressed" - parent.tvAction("MUTE",device.deviceNetworkId) - sendEvent(name:"Command", value: "Mute", displayed: true) -} - -def source() { - log.debug "SOURCE pressed" - parent.tvAction("SOURCE",device.deviceNetworkId) - sendEvent(name:"Command", value: "Source", displayed: true) -} - -def menu() { - log.debug "MENU pressed" - parent.tvAction("MENU",device.deviceNetworkId) -} - -def tools() { - log.debug "TOOLS pressed" - parent.tvAction("TOOLS",device.deviceNetworkId) - sendEvent(name:"Command", value: "Tools", displayed: true) -} - -def HDMI() { - log.debug "HDMI pressed" - parent.tvAction("HDMI",device.deviceNetworkId) - sendEvent(name:"Command sent", value: "Source", displayed: true) -} - -def Sleep() { - log.debug "SLEEP pressed" - parent.tvAction("SLEEP",device.deviceNetworkId) - sendEvent(name:"Command", value: "Sleep", displayed: true) -} - -def Up() { - log.debug "UP pressed" - parent.tvAction("UP",device.deviceNetworkId) -} - -def Down() { - log.debug "DOWN pressed" - parent.tvAction("DOWN",device.deviceNetworkId) -} - -def Left() { - log.debug "LEFT pressed" - parent.tvAction("LEFT",device.deviceNetworkId) -} - -def Right() { - log.debug "RIGHT pressed" - parent.tvAction("RIGHT",device.deviceNetworkId) -} - -def chup() { - log.debug "CHUP pressed" - parent.tvAction("CHUP",device.deviceNetworkId) - sendEvent(name:"Command", value: "Channel Up", displayed: true) -} - -def chdown() { - log.debug "CHDOWN pressed" - parent.tvAction("CHDOWN",device.deviceNetworkId) - sendEvent(name:"Command", value: "Channel Down", displayed: true) -} - -def prech() { - log.debug "PRECH pressed" - parent.tvAction("PRECH",device.deviceNetworkId) - sendEvent(name:"Command", value: "Prev Channel", displayed: true) -} - -def Exit() { - log.debug "EXIT pressed" - parent.tvAction("EXIT",device.deviceNetworkId) -} - -def volup() { - log.debug "VOLUP pressed" - parent.tvAction("VOLUP",device.deviceNetworkId) - sendEvent(name:"Command", value: "Volume Up", displayed: true) -} - -def voldown() { - log.debug "VOLDOWN pressed" - parent.tvAction("VOLDOWN",device.deviceNetworkId) - sendEvent(name:"Command", value: "Volume Down", displayed: true) -} - -def Enter() { - log.debug "ENTER pressed" - parent.tvAction("ENTER",device.deviceNetworkId) -} - -def Return() { - log.debug "RETURN pressed" - parent.tvAction("RETURN",device.deviceNetworkId) -} - -def Info() { - log.debug "INFO pressed" - parent.tvAction("INFO",device.deviceNetworkId) - sendEvent(name:"Command", value: "Info", displayed: true) -} - -def Size() { - log.debug "PICTURE_SIZE pressed" - parent.tvAction("PICTURE_SIZE",device.deviceNetworkId) - sendEvent(name:"Command", value: "Picture Size", displayed: true) -} \ No newline at end of file diff --git a/smartapps/smartthings/samsung-tv-connect.src/samsung-tv-connect.groovy b/smartapps/smartthings/samsung-tv-connect.src/samsung-tv-connect.groovy deleted file mode 100644 index 79bcb68..0000000 --- a/smartapps/smartthings/samsung-tv-connect.src/samsung-tv-connect.groovy +++ /dev/null @@ -1,401 +0,0 @@ -/** - * Copyright 2015 SmartThings - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License - * for the specific language governing permissions and limitations under the License. - * - * Samsung TV Service Manager - * - * Author: SmartThings (Juan Risso) - */ - -definition( - name: "Samsung TV (Connect)", - namespace: "smartthings", - author: "SmartThings", - description: "Allows you to control your Samsung TV from the SmartThings app. Perform basic functions like power Off, source, volume, channels and other remote control functions.", - category: "SmartThings Labs", - iconUrl: "https://s3.amazonaws.com/smartapp-icons/Samsung/samsung-remote%402x.png", - iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Samsung/samsung-remote%403x.png", - singleInstance: true -) - -preferences { - page(name:"samsungDiscovery", title:"Samsung TV Setup", content:"samsungDiscovery", refreshTimeout:5) -} - -def getDeviceType() { - return "urn:samsung.com:device:RemoteControlReceiver:1" -} - -//PAGES -def samsungDiscovery() -{ - if(canInstallLabs()) - { - int samsungRefreshCount = !state.samsungRefreshCount ? 0 : state.samsungRefreshCount as int - state.samsungRefreshCount = samsungRefreshCount + 1 - def refreshInterval = 3 - - def options = samsungesDiscovered() ?: [] - - def numFound = options.size() ?: 0 - - if(!state.subscribe) { - log.trace "subscribe to location" - subscribe(location, null, locationHandler, [filterEvents:false]) - state.subscribe = true - } - - //samsung discovery request every 5 //25 seconds - if((samsungRefreshCount % 5) == 0) { - log.trace "Discovering..." - discoversamsunges() - } - - //setup.xml request every 3 seconds except on discoveries - if(((samsungRefreshCount % 1) == 0) && ((samsungRefreshCount % 8) != 0)) { - log.trace "Verifing..." - verifysamsungPlayer() - } - - return dynamicPage(name:"samsungDiscovery", title:"Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) { - section("Please wait while we discover your Samsung TV. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") { - input "selectedsamsung", "enum", required:false, title:"Select Samsung TV (${numFound} found)", multiple:true, options:options - } - } - } - else - { - def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date. - -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".""" - - return dynamicPage(name:"samsungDiscovery", title:"Upgrade needed!", nextPage:"", install:true, uninstall: true) { - section("Upgrade") { - paragraph "$upgradeNeeded" - } - } - } -} - -def installed() { - log.trace "Installed with settings: ${settings}" - initialize() -} - -def updated() { - log.trace "Updated with settings: ${settings}" - unschedule() - initialize() -} - -def uninstalled() { - def devices = getChildDevices() - log.trace "deleting ${devices.size()} samsung" - devices.each { - deleteChildDevice(it.deviceNetworkId) - } -} - -def initialize() { - // remove location subscription afterwards - if (selectedsamsung) { - addsamsung() - } - //Check every 5 minutes for IP change - runEvery5Minutes("discoversamsunges") -} - -//CHILD DEVICE METHODS -def addsamsung() { - def players = getVerifiedsamsungPlayer() - log.trace "Adding childs" - selectedsamsung.each { dni -> - def d = getChildDevice(dni) - if(!d) { - def newPlayer = players.find { (it.value.ip + ":" + it.value.port) == dni } - log.trace "newPlayer = $newPlayer" - log.trace "dni = $dni" - d = addChildDevice("smartthings", "Samsung Smart TV", dni, newPlayer?.value.hub, [label:"${newPlayer?.value.name}"]) - log.trace "created ${d.displayName} with id $dni" - - d.setModel(newPlayer?.value.model) - log.trace "setModel to ${newPlayer?.value.model}" - } else { - log.trace "found ${d.displayName} with id $dni already exists" - } - } -} - -private tvAction(key,deviceNetworkId) { - log.debug "Executing ${tvCommand}" - - def tvs = getVerifiedsamsungPlayer() - def thetv = tvs.find { (it.value.ip + ":" + it.value.port) == deviceNetworkId } - - // Standard Connection Data - def appString = "iphone..iapp.samsung" - def appStringLength = appString.getBytes().size() - - def tvAppString = "iphone.UN60ES8000.iapp.samsung" - def tvAppStringLength = tvAppString.getBytes().size() - - def remoteName = "SmartThings".encodeAsBase64().toString() - def remoteNameLength = remoteName.getBytes().size() - - // Device Connection Data - def ipAddress = convertHexToIP(thetv?.value.ip).encodeAsBase64().toString() - def ipAddressHex = deviceNetworkId.substring(0,8) - def ipAddressLength = ipAddress.getBytes().size() - - def macAddress = thetv?.value.mac.encodeAsBase64().toString() - def macAddressLength = macAddress.getBytes().size() - - // The Authentication Message - def authenticationMessage = "${(char)0x64}${(char)0x00}${(char)ipAddressLength}${(char)0x00}${ipAddress}${(char)macAddressLength}${(char)0x00}${macAddress}${(char)remoteNameLength}${(char)0x00}${remoteName}" - def authenticationMessageLength = authenticationMessage.getBytes().size() - - def authenticationPacket = "${(char)0x00}${(char)appStringLength}${(char)0x00}${appString}${(char)authenticationMessageLength}${(char)0x00}${authenticationMessage}" - - // If our initial run, just send the authentication packet so the prompt appears on screen - if (key == "AUTHENTICATE") { - sendHubCommand(new physicalgraph.device.HubAction(authenticationPacket, physicalgraph.device.Protocol.LAN, "${ipAddressHex}:D6D8")) - } else { - // Build the command we will send to the Samsung TV - def command = "KEY_${key}".encodeAsBase64().toString() - def commandLength = command.getBytes().size() - - def actionMessage = "${(char)0x00}${(char)0x00}${(char)0x00}${(char)commandLength}${(char)0x00}${command}" - def actionMessageLength = actionMessage.getBytes().size() - - def actionPacket = "${(char)0x00}${(char)tvAppStringLength}${(char)0x00}${tvAppString}${(char)actionMessageLength}${(char)0x00}${actionMessage}" - - // Send both the authentication and action at the same time - sendHubCommand(new physicalgraph.device.HubAction(authenticationPacket + actionPacket, physicalgraph.device.Protocol.LAN, "${ipAddressHex}:D6D8")) - } -} - -private discoversamsunges() -{ - sendHubCommand(new physicalgraph.device.HubAction("lan discovery ${getDeviceType()}", physicalgraph.device.Protocol.LAN)) -} - - -private verifysamsungPlayer() { - def devices = getsamsungPlayer().findAll { it?.value?.verified != true } - - if(devices) { - log.warn "UNVERIFIED PLAYERS!: $devices" - } - - devices.each { - verifysamsung((it?.value?.ip + ":" + it?.value?.port), it?.value?.ssdpPath) - } -} - -private verifysamsung(String deviceNetworkId, String devicessdpPath) { - log.trace "dni: $deviceNetworkId, ssdpPath: $devicessdpPath" - String ip = getHostAddress(deviceNetworkId) - log.trace "ip:" + ip - sendHubCommand(new physicalgraph.device.HubAction("""GET ${devicessdpPath} HTTP/1.1\r\nHOST: $ip\r\n\r\n""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}")) -} - -Map samsungesDiscovered() { - def vsamsunges = getVerifiedsamsungPlayer() - def map = [:] - vsamsunges.each { - def value = "${it.value.name}" - def key = it.value.ip + ":" + it.value.port - map["${key}"] = value - } - log.trace "Devices discovered $map" - map -} - -def getsamsungPlayer() -{ - state.samsunges = state.samsunges ?: [:] -} - -def getVerifiedsamsungPlayer() -{ - getsamsungPlayer().findAll{ it?.value?.verified == true } -} - -def locationHandler(evt) { - def description = evt.description - def hub = evt?.hubId - def parsedEvent = parseEventMessage(description) - parsedEvent << ["hub":hub] - log.trace "${parsedEvent}" - log.trace "${getDeviceType()} - ${parsedEvent.ssdpTerm}" - if (parsedEvent?.ssdpTerm?.contains(getDeviceType())) - { //SSDP DISCOVERY EVENTS - - log.trace "TV found" - def samsunges = getsamsungPlayer() - - if (!(samsunges."${parsedEvent.ssdpUSN.toString()}")) - { //samsung does not exist - log.trace "Adding Device to state..." - samsunges << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] - } - else - { // update the values - - log.trace "Device was already found in state..." - - def d = samsunges."${parsedEvent.ssdpUSN.toString()}" - boolean deviceChangedValues = false - - if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) { - d.ip = parsedEvent.ip - d.port = parsedEvent.port - deviceChangedValues = true - log.trace "Device's port or ip changed..." - } - - if (deviceChangedValues) { - def children = getChildDevices() - children.each { - if (it.getDeviceDataByName("mac") == parsedEvent.mac) { - log.trace "updating dni for device ${it} with mac ${parsedEvent.mac}" - it.setDeviceNetworkId((parsedEvent.ip + ":" + parsedEvent.port)) //could error if device with same dni already exists - } - } - } - } - } - else if (parsedEvent.headers && parsedEvent.body) - { // samsung RESPONSES - def deviceHeaders = parseLanMessage(description, false) - def type = deviceHeaders.headers."content-type" - def body - log.trace "REPONSE TYPE: $type" - if (type?.contains("xml")) - { // description.xml response (application/xml) - body = new XmlSlurper().parseText(deviceHeaders.body) - log.debug body.device.deviceType.text() - if (body?.device?.deviceType?.text().contains(getDeviceType())) - { - def samsunges = getsamsungPlayer() - def player = samsunges.find {it?.key?.contains(body?.device?.UDN?.text())} - if (player) - { - player.value << [name:body?.device?.friendlyName?.text(),model:body?.device?.modelName?.text(), serialNumber:body?.device?.serialNum?.text(), verified: true] - } - else - { - log.error "The xml file returned a device that didn't exist" - } - } - } - else if(type?.contains("json")) - { //(application/json) - body = new groovy.json.JsonSlurper().parseText(bodyString) - log.trace "GOT JSON $body" - } - - } - else { - log.trace "TV not found..." - //log.trace description - } -} - -private def parseEventMessage(String description) { - def event = [:] - def parts = description.split(',') - parts.each { part -> - part = part.trim() - if (part.startsWith('devicetype:')) { - part -= "devicetype:" - event.devicetype = part.trim() - } - else if (part.startsWith('mac:')) { - part -= "mac:" - event.mac = part.trim() - } - else if (part.startsWith('networkAddress:')) { - part -= "networkAddress:" - event.ip = part.trim() - } - else if (part.startsWith('deviceAddress:')) { - part -= "deviceAddress:" - event.port = part.trim() - } - else if (part.startsWith('ssdpPath:')) { - part -= "ssdpPath:" - event.ssdpPath = part.trim() - } - else if (part.startsWith('ssdpUSN:')) { - part -= "ssdpUSN:" - event.ssdpUSN = part.trim() - } - else if (part.startsWith('ssdpTerm:')) { - part -= "ssdpTerm:" - event.ssdpTerm = part.trim() - } - else if (part.startsWith('headers')) { - part -= "headers:" - event.headers = part.trim() - } - else if (part.startsWith('body')) { - part -= "body:" - event.body = part.trim() - } - } - event -} - -def parse(childDevice, description) { - def parsedEvent = parseEventMessage(description) - - if (parsedEvent.headers && parsedEvent.body) { - def headerString = new String(parsedEvent.headers.decodeBase64()) - def bodyString = new String(parsedEvent.body.decodeBase64()) - log.trace "parse() - ${bodyString}" - - def body = new groovy.json.JsonSlurper().parseText(bodyString) - } else { - log.trace "parse - got something other than headers,body..." - return [] - } -} - -private Integer convertHexToInt(hex) { - Integer.parseInt(hex,16) -} - -private String convertHexToIP(hex) { - [convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".") -} - -private getHostAddress(d) { - def parts = d.split(":") - def ip = convertHexToIP(parts[0]) - def port = convertHexToInt(parts[1]) - return ip + ":" + port -} - -private Boolean canInstallLabs() -{ - return hasAllHubsOver("000.011.00603") -} - -private Boolean hasAllHubsOver(String desiredFirmware) -{ - return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware } -} - -private List getRealHubFirmwareVersions() -{ - return location.hubs*.firmwareVersionString.findAll { it } -} \ No newline at end of file