/**
* Copyright 2018 LeeF Automation
*
* 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.
*
* Alarm Bridge Controller
*
* Author: LeeF Automation
* Date: 2018-05-06
*
* Special thanks to CJCharles who created the Visonic alarm system integration that this device handler is evolved from
*/
import groovy.json.JsonSlurper
metadata
{
definition (name: "Alarm System Bridge", namespace: "leefauto", author: "LeeF Automation")
{
capability "Refresh"
capability "Configuration"
capability "Alarm"
capability "Polling"
attribute "device.status", "string"
attribute "armaway", "string"
attribute "armhome", "string"
attribute "disarm", "string"
attribute "alarm", "enum"
attribute "lastaction", "string"
command "AlarmArmAway"
command "AlarmArmHome"
command "AlarmDisarm"
command "AlarmTrigger"
command "ZoneCreateZoneDevices"
command "ZoneRemoveZoneDevices"
command "ZoneTestFunction"
command "updateZonesNS"
command "stop"
command "off"
command "strobe"
command "siren"
command "both"
}
simulator {
}
preferences {
input name: "ip", type: "string", title:"Alarm IP Address", description: "e.g. 192.168.1.10", required: true, displayDuringSetup: true
input name: "prename", type: "string", title:"Add before zone name", description: "e.g. 'Zone' would give 'Zone Kitchen'", required: false, displayDuringSetup: true
input name: "postname", type: "string", title:"Add after zone name", description: "e.g. 'Zone' would give 'Kitchen Zone'", required: false, displayDuringSetup: true
input name: "inactivityseconds", type: "string", title:"Motion sensor inactivity timeout", description: "override the default of 20s (60s max)", required: false, displayDuringSetup: false
input name: "password", type: "password", title:"Password", required:false, displayDuringSetup:false
}
tiles (scale: 2)
{
multiAttributeTile(name:"AlarmStatus", type: "generic", width: 6, height: 3, decoration:"flat")
{
tileAttribute ("device.status", key: "PRIMARY_CONTROL")
{
attributeState "disarmed", label:'${name}', icon: "st.security.alarm.off", backgroundColor: "#505050"
attributeState "home", label:'${name}', icon: "st.Home.home4", backgroundColor: "#00BEAC"
attributeState "away", label:'${name}', icon: "st.security.alarm.on", backgroundColor: "#008CC1"
attributeState "alarm", label:'${name}', icon: "st.security.alarm.on", backgroundColor: "#d44556"
}
tileAttribute("device.events", key: "SECONDARY_CONTROL", wordWrap: true)
{
attributeState("default", label:'${currentValue}')
}
}
standardTile("AlarmArmAway", "armaway", height: 2, width:2, decoration:"flat", inactiveLabel: false)
{
state "inactive", label:"Away", action:"AlarmArmAway", backgroundColor:"#D8D8D8"
state "changing", label:"Arming Away", action:"", backgroundColor:"#FF9900"
state "active", label:"Armed Away", action:"", backgroundColor:"#00CC00"
}
standardTile("AlarmArmHome", "armhome", height: 2, width: 2, decoration:"flat", inactiveLabel: false)
{
state "inactive", label:"Home", action:"AlarmArmHome", backgroundColor:"#D8D8D8"
state "changing", label:"Arming Home", action:"", backgroundColor:"#FF9900"
state "active", label:"Armed Home", action:"", backgroundColor:"#00CC00"
}
standardTile("AlarmDisarm", "disarm", height: 2, width: 2, decoration:"flat", inactiveLabel: false)
{
// disarming via SmartThings is currently disabled due to security concerns
state "inactive", label:"Disarm", action:"AlarmDisarm", backgroundColor:"#D8D8D8"
state "inactive", label:"Disarm", action:"", backgroundColor:"#D8D8D8"
state "changing", label:"Disarming", action:"", backgroundColor:"#FF9900"
state "active", label:"Disarmed", action:"", backgroundColor:"#00CC00"
}
//This will create a tile for all zones up to 30 (which should cover most boards including Wired ones)
//We can have all of these tiles, but not display them
(1..30).each { n ->
valueTile("zonename$n", "panelzonename$n", height: 1, width: 2) {
state "default", label:'${currentValue}', backgroundColor:"#FFFFFF"}
standardTile("zone$n", "panelzone$n", height: 1, width: 1) {
state "inactive", label:"Inactive", action:"", icon:""
state "active", label:"Active", action:"", icon:"", backgroundColor:"#00CC00"
state "closed", label:"Closed", action:"", icon:""
state "open", label:"Open", action:"", icon:"", backgroundColor:"#00CC00"
state "bypass", label:"Bypass", action:"", icon:"", backgroundColor:"#FFD800"
state "smoke", label:"Smoke", action:"", icon:"", backgroundColor:"#FF3F00"
state "clear", label:"Clear", action:"", icon:""
}
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 1, height: 1)
{
state "default", label:"", action:"refresh", icon:"st.secondary.refresh"
}
standardTile("configure", "device.configure", inactiveLabel: false, width: 1, height: 1, decoration: "flat")
{
state "configure", label:'', action:"configure", icon:"st.secondary.configure"
}
standardTile("customzones", "device.zoneupdate", inactiveLabel: false, width: 1, height: 1, decoration: "flat")
{
state "configure", label:'Zone Names', action:"updateZonesNS"
}
valueTile("ip", "ip", decoration: "flat", width: 2, height: 1)
{
state "ip", label:'ST IP Addr:\r\n${currentValue}'
}
standardTile("createtile", "device.createzonedevices", inactiveLabel: false, decoration: "flat", width: 2, height: 1)
{
state "default", label:'Create Zone Devices', action:"ZoneCreateZoneDevices", icon: "st.unknown.zwave.remote-controller"
}
standardTile("removetile", "device.removezonedevices", inactiveLabel: false, decoration: "flat", width: 2, height: 1)
{
state "default", label:'Remove Zone Devices', action:"ZoneRemoveZoneDevices", icon: "st.samsung.da.washer_ic_cancel"
}
standardTile("testtile", "device.testfunction", inactiveLabel: false, decoration: "flat", width: 2, height: 1)
{
state "default", label:'test_function', action:"ZoneTestFunction"
}
// This alarm tile is used so that the state of the alarm is recorded in a useful existing ST capability.
// This allows you to trigger other things in CoRE (or similar) when the state changes, or to trigger state changes
// To use this, you can make a CoRE trigger if 'Alarm' state changes to 'Both' (this will tell you the alarm is going off)
// or if 'Alarm' state changes to 'Off' (this will tell you the alarm has been disarmed).
// Alternatively if you set the 'Alarm' capability to 'Siren' then it will arm Away.
standardTile("alarm", "device.alarm", inactiveLabel: false, width: 2, height: 1, decoration: "flat")
{
state "off", label:'Alarm state = Off (Disarmed)'
state "strobe", label:'Alarm state = Strobe (Home)'
state "siren", label:'Alarm state = Siren (Away)'
state "both", label:'Alarm state = Both (Alarm)'
}
}
main(["AlarmStatus"])
details(["AlarmStatus", "AlarmArmAway", "AlarmArmHome", "AlarmDisarm",
// Add all your zones here (zonenameX first then zoneX)- e.g. add "zonename29", "zone29" if you want to display zone 29 in the alarm panel
"zonename1", "zone1", "zonename2", "zone2", "zonename3", "zone3", "zonename4", "zone4",
"zonename5", "zone5", "zonename6", "zone6", "zonename7", "zone7", "zonename8", "zone8",
"refresh", "configure", "ip", "alarm", "createtile", "removetile"
, "testtile" //If you have trouble creating your child zones you can uncomment the start of this line (remove // before the comma)
//After pressing the "test_function" button on your phone you can re-comment it, to tidy up your alarm panel device.
//This is only needed if you have trouble creating your zones.
])
}
def AlarmArmAway()
{
// Send an arm away command to the alarm and log that it is changing
log.debug "armaway()"
sendEvent(name: "armaway", value: "changing")
sendEvent(name: "armhome", value: "inactive", displayed: false)
sendEvent(name: "disarm", value: "inactive", displayed: false)
getAction("/armaway")
}
def AlarmArmHome()
{
// Send an arm home command to the alarm and log that it is changing
log.debug "armhome()"
sendEvent(name: "armaway", value: "inactive", displayed: false)
sendEvent(name: "armhome", value: "changing")
sendEvent(name: "disarm", value: "inactive", displayed: false)
getAction("/armhome")
}
// This will only work if the appropriate output from the trigger module is connected to a zone wired as a keyswitch zone
def AlarmDisarm()
{
// Send a disarm command to the alarm and log that it is changing
log.debug "disarm()"
sendEvent(name: "armaway", value: "inactive", displayed: false)
sendEvent(name: "armhome", value: "inactive", displayed: false)
sendEvent(name: "disarm", value: "changing")
getAction("/disarm")
}
def AlarmTrigger()
{
log.debug "Triggering alarm"
getAction("/triggeralarm?output=7")
}
def ZoneCreateZoneDevices()
{
// This will create child devices
// The child devices are created in the handler for the /getzonenames response
log.debug "Requesting List of Alarm Zones"
getAction("/getzonenames")
}
def ZoneRemoveZoneDevices()
{
// This will remove all child devices
log.debug "Removing Child Zone Devices"
try
{
getChildDevices()?.each
{
try
{
deleteChildDevice(it.deviceNetworkId)
}
catch (e)
{
log.debug "Error deleting ${it.deviceNetworkId}, probably locked into a SmartApp: ${e}"
}
}
}
catch (err)
{
log.debug "Either no child devices exist or there was an error finding child devices: ${err}"
}
}
def ZoneTestFunction()
{
// This function creates a test device, which may be needed due to some random problems that exist when creating child devices
// due to issues in the SmartThings API
// log.debug "Got here inside the test function"
log.debug prename
if (device.currentValue("createzonedevices") == "cancreatezonedevices")
{
log.debug "state correct"
}
try
{
def curdevice = getChildDevices()?.find { it.deviceNetworkId == "alarmchildzonetest"}
if (curdevice)
{
//Do nothing as we already have a test device
}
else
{
addChildDevice("smartthings", "Motion Detector", "alarmchildzonetest", device.hub.id, [name: "alarm zone test device", label: "Alarm Zone Test Device", completedSetup: true])
}
}
catch (e)
{
log.error "Couldnt find device, probably doesn't exist so safe to add a new one: ${e}"
addChildDevice("smartthings", "Motion Detector", "alarmchildzonetest", device.hub.id, [name: "alarm zone test device", label: "Alarm Zone Test Device", completedSetup: true])
}
}
// This will configure the device to talk to SmartThings
def configure()
{
def cmds = []
log.debug "Configuring Alarm (getting zones+types, configuring IP/port/timeout)"
cmds << getAction("/status")
def requeststring = "/config?ip_for_st=${device.hub.getDataValue("localIP")}&port_for_st=${device.hub.getDataValue("localSrvPortTCP")}"
if (inactivityseconds?.isInteger())
{
// Inactivityseconds is both populated and an integer, so lets send it to the Wemos
requeststring = requeststring + "&inactivity_seconds=${settings.inactivityseconds}"
}
// log.debug requeststring
// Send the details to SmartThings
cmds << getAction(requeststring)
return cmds
}
def refresh()
{
log.debug "refresh()"
// SendEvents should be before any getAction, otherwise getAction does nothing
sendEvent(name: "ip", value: device.hub.getDataValue("localIP")+"\r\nPort: "+device.hub.getDataValue("localSrvPortTCP"), displayed: false)
// Now refresh Alarm status
getAction("/refresh")
// getAction("/getzonenames")
}
def installed()
{
log.debug "installed()"
//configure()
}
def updated()
{
log.debug "updated()"
configure()
}
def ping()
{
log.debug "ping()"
getAction("/ping")
}
// These functions are needed due to the alarm device capability and hence will serve to arm/disarm the alarm (though unlikely to be called)
def stop()
{
// Disarming the alarm system via SmartThings is currently disabled
// AlarmDisarm()
}
def off()
{
// Disarming the alarm system via SmartThings is currently disabled
// AlarmDisarm()
}
def strobe()
{
AlarmArmHome()
}
def siren()
{
AlarmArmAway()
}
def both()
{
AlarmTrigger()
}
def parse(description)
{
def map = [:]
def events = []
def cmds = []
if(description == "updated") return
def descMap = parseDescriptionAsMap(description)
if (descMap == null)
{
log.debug "Not valid json response/message"
log.debug description
return
}
def body = new String(descMap["body"].decodeBase64())
def slurper = new JsonSlurper()
def result;
try
{
result = slurper.parseText(body)
}
catch (e)
{
log.debug "Invalid response from system: " + body
return
}
// Received an alarm stat string so update tile status but dont display it since the event string below will be logged
if (result.containsKey("stat_str"))
{
handleAlarmStatus(result.stat_str)
}
// If we receive a key containing 'stat_update_from' then it is an alarm status so add it to the event log and update tile
if (result.containsKey("stat_update_from"))
{
def dateTime = new Date()
def sensorStateChangedDate = dateTime.format("yyyy-MM-dd HH:mm", location.timeZone)
def status_string = result.stat_str + " by " + result.stat_update_from + " at " + sensorStateChangedDate
// Send the status string that we have built
sendEvent(name: "events", value: "${status_string}", displayed: true, isStateChange: true)
}
if (result.containsKey("zone_status"))
{
handleZoneStatus(result.zone_id, result.zone_status)
}
// This code will pull zone information out of the /getzonenamespage (and allows for zones not in order)
if (result.containsKey("maxzoneid"))
{
log.debug "Handling getzonenames page"
handleCreateZones(result.zones)
}
}
private handleAlarmStatus(statusString)
{
switch (statusString)
{
case ["Disarmed", "Disarm", "Ready"]:
sendEvent(name: "disarm", value: "active", displayed: false)
sendEvent(name: "armaway", value: "inactive", displayed: false)
sendEvent(name: "armhome", value: "inactive", displayed: false)
sendEvent(name: "status", value: "disarmed", displayed: false)
sendEvent(name: "alarm", value: "off", displayed: false)
log.debug "Disarmed Status found"
break
case ["Not Ready"]:
sendEvent(name: "disarm", value: "inactive", displayed: false)
sendEvent(name: "armaway", value: "inactive", displayed: false)
sendEvent(name: "armhome", value: "inactive", displayed: false)
sendEvent(name: "status", value: "disarmed", displayed: false)
sendEvent(name: "alarm", value: "off", displayed: false)
log.debug "Not-ready Status found"
break
case ["Armed Away", "Arm Away"]:
sendEvent(name: "disarm", value: "inactive", displayed: false)
sendEvent(name: "armaway", value: "active", displayed: false)
sendEvent(name: "armhome", value: "inactive", displayed: false)
sendEvent(name: "status", value: "away", displayed: false)
sendEvent(name: "alarm", value: "siren", displayed: false)
log.debug "Armed Away Status found"
break
case ["Armed Home", "Arm Home"]:
sendEvent(name: "disarm", value: "inactive", displayed: false)
sendEvent(name: "armaway", value: "inactive", displayed: false)
sendEvent(name: "armhome", value: "active", displayed: false)
sendEvent(name: "status", value: "home", displayed: false)
sendEvent(name: "alarm", value: "strobe", displayed: false)
log.debug "Armed Home Status found"
break
case ["Exit Delay"]:
sendEvent(name: "disarm", value: "inactive", displayed: false)
sendEvent(name: "armaway", value: "changing")
sendEvent(name: "armhome", value: "inactive", displayed: false)
log.debug "Exit Delay Status found"
break
case ["Delay Alarm", "Confirm Alarm"]:
sendEvent(name: "disarm", value: "inactive", displayed: false)
sendEvent(name: "armaway", value: "inactive", displayed: false)
sendEvent(name: "armhome", value: "inactive", displayed: false)
sendEvent(name: "status", value: "alarm", displayed: false)
sendEvent(name: "alarm", value: "both", displayed: false)
log.debug "Alarm Status found - Uh Oh!!"
break
default:
log.debug "Unknown Alarm state received = ${result.stat_str}"
break
}
}
private handleZoneStatus(zoneId, zoneStatus)
{
def thisZoneDeviceId = "alarmchildzone"+zoneId
def curdevice = null
try
{
// Got a zone status so first try to find the correct child device
curdevice = getChildDevices()?.find { it.deviceNetworkId == thisZoneDeviceId }
}
catch (e)
{
log.debug "Failed to find child zone for zone " + zoneId + "exception ${e}"
}
if (curdevice == null)
{
log.debug "Failed to find child device for zone: " + zoneId + " expecting " + thisZoneDeviceId
/*
def children = getChildDevices()
log.debug "device has ${children.size()} children"
children.each { child ->
log.debug "child ${child.displayName} has deviceNetworkId ${child.deviceNetworkId}"
}
*/
}
else
{
// Check the device type for this child, since the different child device types need different event types
boolean isMotionDevice = (curdevice.capabilities.find { it.name == "Motion Sensor" } != null)
boolean isSmokeDevice = (curdevice.capabilities.find { it.name == "Smoke Detector" } != null)
// Handle the specific zone status result
switch (zoneStatus)
{
case "Active":
log.debug "Got Active zone: " + zoneId + ", which is called - " + curdevice
if (isMotionDevice)
{
sendEvent(name: "panelzone"+zoneId, value: "active", displayed: false, isStateChange: true)
curdevice?.sendEvent(name: "motion", value: "active")
}
else if (isSmokeDevice)
{
sendEvent(name: "panelzone"+zoneId, value: "smoke", displayed: false, isStateChange: true)
curdevice?.sendEvent(name: "smoke", value: "detected")
}
else
{
sendEvent(name: "panelzone"+zoneId, value: "open", displayed: false, isStateChange: true)
curdevice?.sendEvent(name: "contact", value: "open")
}
break
case "Inactive":
log.debug "Got Inactive zone: " + zoneId + ", which is called - " + curdevice
if (isMotionDevice)
{
sendEvent(name: "panelzone"+zoneId, value: "inactive", displayed: false, isStateChange: true)
curdevice?.sendEvent(name: "motion", value: "inactive")
}
else if (isSmokeDevice)
{
sendEvent(name: "panelzone"+zoneId, value: "clear", displayed: false, isStateChange: true)
curdevice?.sendEvent(name: "smoke", value: "clear")
}
else
{
sendEvent(name: "panelzone"+zoneId, value: "closed", displayed: false, isStateChange: true)
curdevice?.sendEvent(name: "contact", value: "closed")
}
break
case "Bypassed - Active":
log.debug "Got Active Bypassed zone: " + zoneId + ", which is called - " + curdevice
sendEvent(name: "panelzone"+zoneId, value: "bypass", displayed: false, isStateChange: true)
if (isMotionDevice)
{
curdevice?.sendEvent(name: "motion", value: "active")
}
else if (isSmokeDevice)
{
curdevice?.sendEvent(name: "smoke", value: "detected")
}
else
{
curdevice?.sendEvent(name: "contact", value: "open")
}
break
case "Bypassed - Inactive":
log.debug "Got Inactive Bypassed zone: " + zoneId + ", which is called - " + curdevice
sendEvent(name: "panelzone"+zoneId, value: "bypass", displayed: false, isStateChange: true)
if (isMotionDevice)
{
curdevice?.sendEvent(name: "motion", value: "inactive")
}
else if (isSmokeDevice)
{
curdevice?.sendEvent(name: "smoke", value: "clear")
}
else
{
curdevice?.sendEvent(name: "contact", value: "closed")
}
break
case "Tamper":
log.debug "Got Tamper for zone: " + zoneId + ", which is called - " + curdevice
// We'll set it to open for now, since at least that gives an indication something is wrong!
if (isMotionDevice)
{
sendEvent(name: "panelzone"+zoneId, value: "active", displayed: false, isStateChange: true)
curdevice?.sendEvent(name: "motion", value: "active")
}
else if (isSmokeDevice)
{
sendEvent(name: "panelzone"+zoneId, value: "smoke", displayed: false, isStateChange: true)
curdevice?.sendEvent(name: "smoke", value: "detected")
}
else
{
sendEvent(name: "panelzone"+zoneId, value: "open", displayed: false, isStateChange: true)
curdevice?.sendEvent(name: "contact", value: "open")
}
break
default:
log.debug "Unknown status received: ${zoneId} is ${zoneStatus}"
break
}
}
}
private handleCreateZones(zones)
{
for (def curzone in zones)
{
// First update the name of the tile on this device
sendEvent(name: "panelzonename"+(curzone.zoneid), value: curzone.zonename)
// Now setup the prepend and postpend names for the new child zone device
def thiszonename = ""
if (prename != null) {thiszonename = thiszonename + prename + " "}
thiszonename = thiszonename + curzone.zonename
if (postname != null) {thiszonename = thiszonename + " " + postname}
log.debug "Trying to add child with name: ${thiszonename}, ID: alarmchildzone${curzone.zoneid} to ${device.hub.id}"
// Now try to find a child device with the right name - exception handler needed in case it fails to find any children
try
{
def curchildzone = getChildDevices()?.find { it.deviceNetworkId == "alarmchildzone${curzone.zoneid}"}
}
catch (e)
{
// Would reach here if it cant find any children or that child doesnt exist so we can try and create it
log.debug "Couldnt find device, probably doesn't exist so safe to add a new one: ${e}"
}
// If we don't have a matching child already, and the name isn't Unknown, then we can finally start creating the child device
if ((curchildzone == null) && (curzone.zonename != "Unknown"))
{
try
{
switch (curzone.zonetype)
{
case ["Magnet", "Contact", "Entry/Exit"]:
// If it is a magnetic sensor then add it as a contact sensor
addChildDevice("smartthings", "Open/Closed Sensor", "alarmchildzone${curzone.zoneid}", device.hub.id, [name: thiszonename])
log.debug "Created contact zone child device"
break
case ["Motion", "Interior", "Wired"]:
// If it is a motion or interior sensor then add it as a motion detector device
addChildDevice("smartthings", "Motion Detector", "alarmchildzone${curzone.zoneid}", device.hub.id, [name: thiszonename])
log.debug "Created motion zone child device"
break
case ["Smoke", "Fire"]:
try
{
addChildDevice("leefauto", "Smoke Detector", "alarmchildzone${curzone.zoneid}", device.hub.id, [name: thiszonename])
}
catch (e)
{
log.debug "Couldn't create Smoke Detector child device. Is device handler installed ? Creating motion detector zone instead."
addChildDevice("smartthings", "Motion Detector", "alarmchildzone${curzone.zoneid}", device.hub.id, [name: thiszonename])
}
log.debug "Created Smoke Alarm zone child device"
break
case ["Shock", "Vibration", "Gas", "Panic", "KeySwitch"]:
// Add the remainders as motion detectors for now - unfortunately this will display motion/no-motion instead of active/inactive
addChildDevice("smartthings", "Motion Detector", "alarmchildzone${curzone.zoneid}", device.hub.id, [name: thiszonename])
log.debug "Create motion zone child device for non-motion zone"
break
default:
log.debug "Unknown sensor found, we'll have to ignore for now"
break
}
}
catch (e)
{
log.error "Couldnt add device, probably already exists: ${e}"
}
}
}
}
private getAction(uri)
{
log.debug "uri ${uri}"
updateDNI()
def userpass
if(password != null && password != "")
userpass = encodeCredentials("admin", password)
def headers = getHeader(userpass)
def hubAction = new physicalgraph.device.HubAction(
method: "GET",
path: uri,
headers: headers
)
return hubAction
}
def parseDescriptionAsMap(description)
{
description.split(",").inject([:])
{
map, param ->
def nameAndValue = param.split(":")
if (nameAndValue.size() > 1)
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
private getHeader(userpass = null)
{
def headers = [:]
headers.put("Host", getHostAddress())
headers.put("Content-Type", "application/x-www-form-urlencoded")
if (userpass != null)
headers.put("Authorization", userpass)
return headers
}
private encodeCredentials(username, password)
{
def userpassascii = "${username}:${password}"
def userpass = "Basic " + userpassascii.encodeAsBase64().toString()
return userpass
}
private updateDNI()
{
if (state.dni != null && state.dni != "" && device.deviceNetworkId != state.dni)
{
device.deviceNetworkId = state.dni
}
}
private getHostAddress()
{
if(getDeviceDataByName("ip") && getDeviceDataByName("port"))
{
return "${getDeviceDataByName("ip")}:${getDeviceDataByName("port")}"
}
else
{
return "${ip}:80"
}
}