Control “Fully Dashboard” via API with a DTH?

Has anyone created a DTH to control an android tablet that is running Fully Browser? It is often used to run action tiles as a control panel for SmartThings and has a very rich api. There seems to be an HA project that does this (https://community.home-assistant.io/t/fully-kiosk-browser/41675). I’d love to be able to change and schedule my fully screensaver and brightness based on location modes and more!

3 Likes

Great idea!

We have thought about doing a project like this, but believe that only a small percentage of customers would want this degree of sophistication (and some others might, after a lot of coaching…), so we’re… deferring the thought.

Interested in following your explorations, though.

I don’t think SmartThings has a browser capability yet… :sunglasses:
Probably would need to plan out how the features map to existing device capabilities and controls.

1 Like

At SDC, the official session presenters insisted they will have a streamlined process for adding new Capabilities as innovative / creative devices are devised that want to be integrated to the platform - especially for the cloud-to-cloud stuff that is easy to Certify.

I pressed the guy, though, and say that streamlining the approval of new standards is an oxymoron. Capabilities are a rather imperfect abstraction (there’s no polymorphism or inheritance, etc.), and yet SmartThings hasn’t come up with anything better. I am not optimistic.

I think new Capabilities are going to be added based on whatever high-profile device vendors demand (e.g., if iRobot wants a “lawn mower” Capability - they will get it; and SmartThings will not wait around for input from the couple dozen other robot lawn mower manufacturers in the world).

This is currently how I’m doing it, for screen on/off and dimming, but I’m thinking about moving this over to a virtual dimmer and core/webcore. IDK if this helps.

https://raw.githubusercontent.com/sidjohn1/smartthings/master/URIDimmer.groovy

Name Type Value
deviceDimPath text /?type=json&password=xxxxx&cmd=setStringSetting&key=screenBrightness&value=
deviceIp text 192.168.1.1
deviceOffPath text /?type=json&password=xxxxx&cmd=screenOff
deviceOnPath text /?type=json&password=xxxxx&cmd=screenOn
devicePort number 2323
deviceStatusPath text /?type=json&password=xxxxx
1 Like

Thanks - I’ll give this whirl! Reposting for fixed formatting so others can copy and paste more easily.

/**

URI Dimmer
Smartthings Devicetype
Copyright 2015 Sidney Johnson
If you like this device, please support the developer via PayPal: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=XKDRYZ3RUNR9Y
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.
Version: 1.0 - Initial Version
Version: 1.1 - Added device health check
Version: 1.2 - Added support for samsung connect
*/
preferences {
	input("deviceIp", "text", title: "IP Address", description: "Device IP Address", required: true, displayDuringSetup: true)
	input("devicePort", "number", title: "Port Number", description: "Device Port Number (Default:80)", defaultValue: "80", required: true, displayDuringSetup: true)
	input("deviceOnPath", "text", title: "On Path (/blah?q=this)", required: false)
	input("deviceOffPath", "text", title: "Off Path (/blah?q=this)", required: false)
	input("deviceDimPath", "text", title: "Dim Path (/blah?q=this)", required: false)
	input("deviceStatusPath", "text", title: "Status Path (/blah?q=this)", required: false)
}

metadata {
	definition (name: "URI Dimmer Switch", namespace: "sidjohn1", author: "sidjohn1", ocfDeviceType: "oic.d.switch", vid: "generic-dimmer") {
		capability "Actuator"
		capability "Switch"
		capability "Switch Level"
		capability "Sensor"
		capability "Refresh"
		capability "Health Check"
	}

	// UI tile definitions

	tiles(scale: 2) {
		multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
			tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
				attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"off"
				attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"on"
				attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
				attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
			}
			tileAttribute ("device.level", key: "SLIDER_CONTROL") {
				attributeState "level", action:"switch level.setLevel"
			}
		}
		standardTile("offButton", "device.button", width: 1, height: 1, canChangeIcon: true) {
			state "default", label: 'Force Off', action: "switch.off", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
		}
		standardTile("onButton", "device.switch", width: 1, height: 1, canChangeIcon: true) {
			state "default", label: 'Force On', action: "switch.on", icon: "st.switches.switch.on", backgroundColor: "#00A0DC"
		}
		standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
			state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
		}

		main "switch"
		details(["switch","onButton","offButton","refresh"])
	}
}

def parse(String description) {
	def map
	def headerString

	map = stringToMap(description)
	headerString = new String(map.headers.decodeBase64())
	if (headerString.contains("200 OK")) {
		sendEvent(name: "${state.saveEvent[0]}", value: "${state.saveEvent[1]}", displayed: true)
		sendEvent(name: "status", value: "online", displayed: true, isStateChange: true)
	    sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true)
		log.debug "${state.saveEvent[0]} ${state.saveEvent[1]}"
		state.statusCount = 0
	}
}

def installed() {
	log.debug "Installed with settings: ${settings}"
	initialize()
}

def updated() {
	log.debug "Updated with settings: ${settings}"
	initialize()
}

def initialize() {
	log.info "URI Dimmer {textVersion()} {textCopyright()}"
	ipSetup()
	state.statusCount = 0
	sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}", displayed: false)
	sendEvent(name: "checkInterval", value: 32 * 60, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
	autoPoll()
}

def autoPoll() {
	unschedule()
	//	def sec = Math.round(Math.floor(Math.random() * 60))
	//	def cron = "$sec 0/8 * * * ?" // every 8 min
	//	schedule(cron, ping)
	runEvery15Minutes(ping)
}

def on() {
	sendCommand('on')
}

def off() {
	sendCommand('off')
}

def setLevel(value) {
	sendCommand("$value")
}

def ping() {
	sendCommand('refresh')
}

def refresh() {
	sendCommand('refresh')
}

def sendCommand(command) {
	if (isLocal(settings.deviceIp)==false){
		def cmd = "{settings.external_off_uri}"; log.debug "Sending request cmd[{cmd}]"
		httpGet(cmd) {resp ->
			if (resp.data) {
				log.info "{resp.data}" 
			} 
		} 
	} 
	if (isLocal(settings.deviceIp)==true) { 
		def hubAction 
		def sendPath 
		switch (command) { 
			case "on": sendPath = "{deviceOnPath}"
			state.saveEvent = ["switch","on"]
			log.debug "Executing On"
			break;

			case "off":
			sendPath = "${deviceOffPath}"
			state.saveEvent = ["switch","off"]
			log.debug "Executing Off" 
			break;
			
			case "refresh":
		    	sendPath = "${deviceStatusPath}"
			break;
			
			default:
			sendPath = "${deviceDimPath}${command}"
			state.saveEvent = ["level","${command}"]
			break;
		}
		state.statusCount = state.statusCount++
		if (state.statusCount >= 3) {
			sendEvent(name: "status", value: "offline", displayed: true, isStateChange: true)
		    sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true)
		}
		try {
			hubAction = [new physicalgraph.device.HubAction(
			method: "GET",
			path: sendPath,
			headers: [HOST: "${settings.deviceIp}:${settings.devicePort}"]
			)]
		}
		catch (Exception e) {
			log.debug "Hit Exception $e on $hubAction"
		}
		//	log.debug "${command}"
		return hubAction
	}
}

private def isLocal(ip) {
	String hubNetwork = "${location.hubs.localIP}".tokenize( '.' )[0,1].join().replace("[","")
	String deviceNetwork = ip.tokenize( '.' )[0,1].join().replace("[","")
	if (hubNetwork == deviceNetwork){
		return true
	}
	else {
		return false
	}
}

def ipSetup() {
	def hosthex
	def porthex
	if (settings.deviceIp) {
		hosthex = convertIPtoHex(settings.deviceIp)
	}
	if (settings.devicePort) {
		porthex = convertPortToHex(settings.devicePort)
	}
	if (settings.deviceIp && settings.devicePort) {
		device.deviceNetworkId = "$hosthex:$porthex"
		log.info "Setting up Network ID $hosthex:$porthex"
	}
	else {
		log.debug "Error Setting up Network ID"
	}
}

private String convertIPtoHex(ip) {
	String hexip = ip.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join()
	return hexip
}
private String convertPortToHex(port) {
	String hexport = port.toString().format( '%04x', port.toInteger() )
	return hexport
}
private String textVersion() {
	def text = "Version 1.2"
}

private String textCopyright() {
	def text = "Copyright © 2018 Sidjohn1"
}
1 Like

Would be cool if the DH could turn the tablet into a motion sensor / camera too :slight_smile:

edit: will try this out later
http://IPaddress:2323/?cmd=getCamshot&password={pass}

1 Like

It’s working well so far! Any interest in moving this to GitHub and allowing folks to help flesh this out? It looks like at least 2 people have ideas to contribute!

1 Like

This is why I love this community!!! I can definitely see how this would be useful.

1 Like

Crap, i totally forgot i already did that… some days
The Code:
https://raw.githubusercontent.com/sidjohn1/smartthings/master/URIDimmer.groovy

With all the upcoming changes to Smartthings i’ve been trying to migrate away from custom DTH’s and find creative ways to use standard virtual devices. This is another way to do the samething.

1 Like

Perhaps I’m wrong about this but DTHs seem like a great way to package and encapsulate functionality neatly so it can be used by other pistons and apps and shared easily with others. The piston you’ve shared above seems to be specific to your environment and less portable (which is totally fine for the code which your wrote for yourself!) Also, adding additional capabilities would mean adding other virtual devices to mirror it which seems… messy.

What are the upcoming changes that you are referring to? The new app that doesn’t support DTHs? I can’t imagine that they will abandon the current ecosystem.

As some wise soul around here keeps saying choice is good… Fully has a lot of options and my use case my not be yours or anyone else’s which is why i posted both. Also my memory sucks so documenting something i struggled with may save me some time in the future and others too.

Both options in their current form do everything i need in my home (screen off when no one is home\ everyone is in bed, screens on when people are home, brighten screens at sunrise and dim at sunset)
but i fully :wink: appreciate that others will want fully to do more.

@iderdik i honestly don’t know whats not changing, people are still being migrating from SmartThings to Samsung AUTH, yes there is also the migrations from SmartThings Classic to Samsung Connect, but they’re are also changing their API and i don’t know how long the current API will be available. Also, while it may seam silly… i’m also trying to get as many devices as i can to run local. I find my environment is more stable that way.

2 Likes

@kevin holy moly that worked!

For me too, I had to mess with a bunch of Fully settings and eventually update Fully. Now I have it going into my TinyCamPro “server”

@kevin how do you redirect the Camshot? It would be good to get an email with photo if Home is in Away mode and the tablet senses movement, acoustic alarm or the screen comes on.

Infact I’m a little confused by this DTH, how anything can be automated, it doesn’t seem to be a 2 way communication, I tried a piston, IF screen comes on, set brightness to 50%, nothing happened.
Someone very clever really needs to go to town on this DTH, imagine having motion sensor capability, sound…

But finding the Fully Remote Admin was very convenient to change settings, rather than having to do it on the tablets.

Edit. there’s also a bug, if you change the screen brightness using the dimmer slider, Fully sets default screen brightness to 100, which is 40%, manually changing the brightness to 255, or leaving it blank/default and saving doesn’t make it stick. Tried deleting Fully and reinstalling, same behaviour. Sending my settings to Andrey, as he asked, see what he say’s.

I noticed this as well. I’m pretty sure that the setLevel command sends a value between 1-100 and thus doesn’t get the brightness correct. We probably need to change setLevel to do some simple translation like ($value / 100) * 255 to pass to sendCommand.

1 Like

I am not using the DH posted in this topic. If you just paste that getCamshot URL in web browser you will see the image. I use that URL in TinyCamPro that I have running on an old android phone. It is a “mini-NVR” for my various cheap/free cameras like this one. TinyCamPro can detect motion in the camera images and save images to the phone memory. I think it can email an image as well but I haven’t used that feature.

1 Like

@iderdik Good spot. I messaged the DTH author about this issue but he said he wasn’t providing any support for his DTH. I’ve spent far too long trying to troubleshoot this myself today. Now I just had to email the Fully Developer with an apology. So to get at least the brightness working correctly, all we need now is someone charming and clever with code like @tgauchat to help provide the missing code that changes 0-100 to 0-255.

1 Like

Ain’t so hard… I hope.

Please point me to the likely relevant section of code (I hope it is someplace convenient on GitHub) and I’ll provide a snip and y’all can test?

I suppose it might be a tad easier if I try the whole thing myself and point to the API address on my own Fully / tablet. I hope it isn’t too distracting.

As I mentioned - this is something I’ve actually been curious about offering - It is unfortunately just too obscure of a project to get our customer base amped up about.

1 Like

I found a workaround for setting the brightness to 100%, using WebCoRE, if you set level to 255 that sets brightness on the tablet to 100%!


The guys on Home Assistant really have a handle on Fullys’ capabilities, they are sending TTS messages to the tablet like…

TabletIP:2323/?cmd=textToSpeech&text=The+House+Alarm+Has+Been+Disarmed&password=FullyOpenKisokPassword

Who knew you could even do that? Monitoring battery levels on Actiontiles tablets so when battery gets low you turn on a smart plug thereby not damaging the tablet battery and it swelling.
Bringing up different Actiontiles Panels on the fly by a particular motion sensor changing to active.

1 Like