Integrate Somfy in Hubitat

You would need to install the TaHomaBySomfy.SmartApp.groovy and also the appropriate driver.

image

Then install the App on HE, run through the adding Username and Password for Tahoma, and see if the devices are picked up.

Sorry, but I am completely new here, if you can explain that to me step by step, I will certainly try

So...I also know little about these blinds, but if you're able to use the Z-Wave link with your blinds, that would probably be much easier to make work with Hubitat. Apparently, however, it's a one-way street: the controller doesn't report back the blinds' current positions, so you can only send "up," "down," or "stop" (if you only used Hubitat to adjust them that would probably help, but I'm still not sure you'd ever have a way to know when they're fully open or closed). See: Somfy ZRTSI RTS 16 Channel Z-Wave Drivers? - #2 by sh1 and related threads for more.

Maybe an unofficial port of the above would give you more options. I'm not really sure. (But the Z-Wave device would likely work natively as a dimmer or blinds controller, possibly without "stop" unless someone has found a workaround for that yet.)

1 Like

How to add Apps here

How to add drivers here

But @bertabcd1234 would know better than I on the Somfy. I have the Zemismart Z-wave curtains here. Hope it works out though :+1:

1 Like

I probably don't--I hope someone out there knows more than either one of us, apparently! Haha. I've recently started using iBlinds, a Z-Wave retrofit motor for existing blinds (venetian blinds, not shades, as so many other product seem to be made for). They have their own manuacturer-official, Hubitat-unofficial driver but also seem to more less work with the generics. I was waiting for MoveZ, but I really like the look of these better (hidden in headrail), and unlike MoveZ, they've actually made it to the market, something I suspect I'll be waiting a lot longer for with those...

But in general, I'd say a Z-Wave (or Zigbee--I can dream, right?) device is much more likely to work better than an unofficial, ported LAN driver, which is what I'm assuming the above would end up being. It certainly depends on the device and quality of the code (and platform differences), so I could be wrong in this particular case. Good luck either way!

1 Like

Those are pretty cool. Any issues with not having any tilt mechanism other then the motor? Have you gotten any feeling for how long the batteries last between charges?....and did you add Solar?

I like the concept, as all I have are venetian blinds, which as you noted are not widely automated!

S.

I got my first motor about a month or so ago so can't say for sure about a lot yet--other than that it convinced me to buy a few more. Most are just battery. I did buy a solar panel and plan to put it in a harder-to-reach window. (They claim you'll only have to recharge it 2-3 times a year and don't recommend adjusting your blinds more than twice a day, presumably to obtain that, so we'll see...) I think you could also leave it plugged in if you don't mind a dangling, visible USB cable. But that's probably not necessary--with most blinds (unfortunately not some cheap ones I got couple years ago and had to chop up the headrail a bit to get them to fit at all...), the micro-USB port should be easily accessible from where the old tilt string was.

I'm using Pico remotes (I have a Lutron system) and Alexa/voice control to manipulate the blinds and don't miss the pull strings that much so far. Still trying to figure out the best way to handle this. (I wish the button, also exposed where the strings were, could be used to adjust; right now it's just for Z-Wave inclusion/exclusion, something testers apparently asked for, as if that's something I'd need/want to do all the time; they're open to changing this in future versions but would need different hardware since these don't support OTA).

I'm still open to other ideas for blinds automation, but this was the only thing I could find for venetian blinds that was retrofit, actually available, and natively compatible with Hubitat (Z-Wave) without any additional hubs required (otherwise I'm assuming MySmartBlinds, one of few others I can find, might too). Axis looks like it might work if you have a continuous chain, but I don't; and as I said before, MoveZ looks promising but way behind schedule (and would require you to mount something where it can manipulate the string, something I'm not sure would work everywhere for me). Since I have RA2, Lutron would work too, but I'm still not sure they have blinds, and this was a lot cheaper and involved talking to zero humans/dealers. :slight_smile:

1 Like

That's pretty impressive battery life really. I'm not sure whether I beleive it, but for $20 adding on the solar seems like a no-brainer, although I wonder how bad it would look from outside.

My blinds are non-chain (so no Axis), and I'm not a Lutron user, so...there's two that are out of scope...and as you pointed out, that only leaves iBlinds and the vaporware one...not exactly a market with loads of choices...

I like the installation method, my only reservation is the removal of the string. The WAF would plummet if the motor glitched and the blinds didn't open or close as appropriate...seems like high WAF risk to me

Also at $130-150 per window....a bit pricey....but may earn their $$$ back with smart use on sunny days in the summer....dunno.

Interesting find! Look forward to hearing how they last battery wise...

S

Thanks for your help!

Always get an error message (see 3) if I want to log in (see 1) and log in (see 4) It probably has to do with (see 2)!

1|585x469

If this app uses http requests then it is going to have to be re-written to use the Hubitat schema for these requests. It won't work directly importing from ST unfortunately. The code is a little different for doing an HTTP Post from Hubitat vs. Smartthings.

Try removing this line from the app.

method: 'POST',

It is on line 454. Hubitat doesn't require you to declare the method when making a POST request because you use the command: httpPost(params). Declaring the method in the params is superfluous.

Update: You would also need to remove lines 665, 756, and 799.

Thanks, just delete them?

Now I get another error message

Did you delete the method from 665 and then save the changes? Because I still see the method in your logs.

1

yes, see photo, but the line above is another one !?:grinning:

Okay....the numbering changes once you remove the lines above. I was referring to the old numbering as in the link that is supplied above. You need to put the URI lines back in. As i said, the lines you have to remove are the METHOD lines.

Try this code for the app.

/**
 *  Copyright (c) 2016-2018 Tibor Jakab-Barthi
 *
 *  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.
 *
 */

 /**
  * TODO:
  * Store TaHoma commands in map and reference map from Device Types so that Device Types can be generic.
  */
definition(
		name: "TaHoma® by Somfy",
		namespace: "jbt",
		author: "Tibor Jakab-Barthi",
		description: "Connect your TaHoma® system to SmartThings.",
		category: "My Apps",
		iconUrl: "http://tahomabysomfyapi.azurewebsites.net/img/TaHomaBySomfy.square150.png",
		iconX2Url: "http://tahomabysomfyapi.azurewebsites.net/img/TaHomaBySomfy.square300.png",
		singleInstance: true
)

preferences {
	page(name: "loginPage", title: "Log in with your TaHoma® credentials", nextPage: "settingsPage", uninstall: true) {
		section  {
			input(name: "username", type: "text", title: "Username", required: true, displayDuringSetup: true)
			input(name: "password", type: "password", title: "Password", required: true, displayDuringSetup: true)
		}

		section("General") {
			input(name: "debugMode", type: "bool", title: "Enable debug logging", defaultValue: false, displayDuringSetup: true)
		}
	}

	page(name: "settingsPage")
}

def settingsPage() {
	debug("settingsPage()")

	setDefaultValues()

	updateDevices()

	def interiorRollerBlindNames = getInteriorRollerBlindNames()
	def lightSensorNames = getLightIOSystemSensorNames()
	def rollerShutterNames = getRollerShutterNames()
	def rollerShutterWithLowSpeedManagementIOComponentNames = getRollerShutterWithLowSpeedManagementIOComponentNames()

	debug("rollerShutterWithLowSpeedManagementIOComponentNames $rollerShutterWithLowSpeedManagementIOComponentNames")

	dynamicPage(name: "settingsPage", title: "", install: true, uninstall: true) {
		section ("TaHoma® devices to control", hideWhenEmpty: true) {
			input(name: "selectedInteriorRollerBlindNames", type: "enum", title: "Interior Roller Blinds", description: "Tap to choose", required: false, multiple: true, metadata: [values: interiorRollerBlindNames], displayDuringSetup: true)
			input(name: "selectedLightIOSystemSensorNames", type: "enum", title: "Light Sensors", description: "Tap to choose", required: false, multiple: true, metadata: [values: lightSensorNames], displayDuringSetup: true)
			input(name: "selectedRollerShutterNames", type: "enum", title: "Roller Shutters RTS", description: "Tap to choose", required: false, multiple: true, metadata: [values: rollerShutterNames], displayDuringSetup: true)
			input(name: "selectedRollerShutterWithLowSpeedManagementIOComponentNames", type: "enum", title: "Roller Shutters IO", description: "Tap to choose", required: false, multiple: true, metadata: [values: rollerShutterWithLowSpeedManagementIOComponentNames], displayDuringSetup: true)
			input(name: "selectedSwitchNames", type: "enum", title: "Switches", description: "Tap to choose", required: false, multiple: true, metadata: [values: switchNames], displayDuringSetup: true)
		}
	}
}

// Cloud-Connected Device properties
def getCloudApiEndpoint() {
	"https://www.tahomalink.com/enduser-mobile-web/enduserAPI/" 
}

def getSmartAppVersion() {
	"1.3.20180612" 
}

// Device specific methods
def getDeviceId(device) { 
	return device.deviceURL;
}

def getDeviceName(device) { 
	return device.label;
}

def installed() {
	debug("installed(): ${settings}")

	atomicState.installedAt = now()

	updateDevices()

	initialize()
}

def uninstalled() {
	debug("uninstalled()")

	if (getChildDevices()) {
		removeChildDevices(getChildDevices())
	}
}

def updated() {
	debug("updated(): ${settings}")

	unsubscribe()

	updateDevices()

	initialize()
}

def debug(message) {
	if (settings.debugMode) {
		log.debug("SA $smartAppVersion: $message")
	}
}

def setDefaultValues() {
	debug("setDefaultValues()")

	def minute = 60 * 1000 // seconds * milliseconds

	atomicState.authorizationFailed = false
	atomicState.authorizationHeaderValue = ""
	atomicState.authorizationRetries = 1
	atomicState.authorizationRetriesRemaining = atomicState.authorizationRetries
	atomicState.authorizationTimeoutInMilliseconds = 1 * minute
	atomicState.authorizedAt = 0

	debug("setDefaultValues: atomicState: $atomicState")
}

def initialize() {
	debug("initialize()")

	setDefaultValues();

	def selectedDeviceCount = processSelectedInteriorRollerBlinds()
	selectedDeviceCount += processSelectedLightIOSystemSensors()
	selectedDeviceCount += processSelectedRollerShutters()
	selectedDeviceCount += processSelectedRollerShuttersWithLowSpeedManagementIOComponent()
	selectedDeviceCount += processSelectedSwitches()

	if (selectedDeviceCount == 0){
		log.error("No devices were selected.")
	}

	deleteUnselectedDevices()
}

def processSelectedInteriorRollerBlinds() {
	debug("processSelectedInteriorRollerBlinds()")

	if (!settings.selectedInteriorRollerBlindNames){
		settings.selectedInteriorRollerBlindNames = []
	}

	def selectedInteriorRollerBlinds = settings.selectedInteriorRollerBlindNames.each { dni ->
		def interiorRollerBlind = atomicState.interiorRollerBlinds[dni]

		if (!interiorRollerBlind) {
			debug("initialize: interiorRollerBlinds: ${atomicState.interiorRollerBlinds}")

			def errorMessage = "Interior Roller Blind '$dni' not found."
			log.error(errorMessage)

			throw new org.json.JSONException(errorMessage)
		}

		debug("interiorRollerBlind $interiorRollerBlind")

		def deviceName = getDeviceName(interiorRollerBlind)
		def deviceId = getDeviceId(interiorRollerBlind)

		def virtualDevice = getChildDevice(dni)

		if (virtualDevice) {
			debug("Found ${virtualDevice.name} with network id '$dni' already exists.")
		} else {
			def deviceTypeName = "TaHoma Roller Shutter RTS"

			debug("Creating new '$deviceTypeName' device '$deviceName' with id '$dni'.")

			virtualDevice = addChildDevice(app.namespace, deviceTypeName, dni, null, ["name": deviceId, "label": deviceName, "completedSetup": true])

			debug("virtualDevice ${virtualDevice}")

			debug("Created '$deviceName' with network id '$dni'.")
		}

		return virtualDevice
	}

	debug("User selected ${selectedInteriorRollerBlinds.size()} Interior Roller Blinds.")

	return selectedInteriorRollerBlinds.size()
}

def processSelectedLightIOSystemSensors() {
	debug("processSelectedLightIOSystemSensors()")

	if (!settings.selectedLightIOSystemSensorNames){
		settings.selectedLightIOSystemSensorNames = []
	}

	log.debug("selectedLightIOSystemSensorNames $selectedLightIOSystemSensorNames")
	def selectedLightIOSystemSensors = selectedLightIOSystemSensorNames.each { dni ->
		def lightIOSystemSensor = atomicState.lightIOSystemSensors[dni]

		if (!lightIOSystemSensor) {
			debug("initialize: lightIOSystemSensors: ${atomicState.lightIOSystemSensors}")

			def errorMessage = "Light IO System Sensor '$dni' not found."
			log.error(errorMessage)

			throw new org.json.JSONException(errorMessage)
		}

		debug("lightIOSystemSensor lightIOSystemSensor")

		def deviceName = getDeviceName(lightIOSystemSensor)
		def deviceId = getDeviceId(lightIOSystemSensor)

		def virtualDevice = getChildDevice(dni)

		if (virtualDevice) {
			debug("Found ${virtualDevice.name} with network id '$dni' already exists.")
		} else {
			def deviceTypeName = "TaHoma Light Sensor IO"

			debug("Creating new '$deviceTypeName' device '$deviceName' with id '$dni'.")

			def capabilities = [:]

			virtualDevice = addChildDevice(app.namespace, deviceTypeName, dni, null, ["name": deviceId, "label": deviceName, "completedSetup": true])
			virtualDevice.setCapabilities(capabilities)

			debug("virtualDevice ${virtualDevice}")

			debug("Created '$deviceName' with network id '$dni'.")
		}

		return virtualDevice
	}

	debug("User selected ${selectedLightIOSystemSensors.size()} TaHoma Light Sensor IO devices.")

	return selectedLightIOSystemSensors.size()
}

def processSelectedRollerShutters() {
	debug("processSelectedRollerShutters()")

	if (!settings.selectedRollerShutterNames){
		settings.selectedRollerShutterNames = []
	}

	def selectedRollerShutters = selectedRollerShutterNames.each { dni ->
		def rollerShutter = atomicState.rollerShutters[dni]

		if (!rollerShutter) {
			debug("initialize: rollerShutters: ${atomicState.rollerShutters}")

			def errorMessage = "Roller Shutter '$dni' not found."
			log.error(errorMessage)

			throw new org.json.JSONException(errorMessage)
		}

		debug("rollerShutter $rollerShutter")

		def deviceName = getDeviceName(rollerShutter)
		def deviceId = getDeviceId(rollerShutter)

		def virtualDevice = getChildDevice(dni)

		if (virtualDevice) {
			debug("Found ${virtualDevice.name} with network id '$dni' already exists.")
		} else {
			def deviceTypeName = "TaHoma Roller Shutter RTS"

			debug("Creating new '$deviceTypeName' device '$deviceName' with id '$dni'.")

			virtualDevice = addChildDevice(app.namespace, deviceTypeName, dni, null, ["name": deviceId, "label": deviceName, "completedSetup": true])

			debug("virtualDevice ${virtualDevice}")

			debug("Created '$deviceName' with network id '$dni'.")
		}

		return virtualDevice
	}

	debug("User selected ${selectedRollerShutters.size()} Roller Shutters.")

	return selectedRollerShutters.size()
}

def processSelectedRollerShuttersWithLowSpeedManagementIOComponent() {
	debug("processSelectedRollerShuttersWithLowSpeedManagementIOComponent()")

	if (!settings.selectedRollerShutterWithLowSpeedManagementIOComponentNames){
		settings.selectedRollerShutterWithLowSpeedManagementIOComponentNames = []
	}

	log.debug("selectedRollerShutterWithLowSpeedManagementIOComponentNames $selectedRollerShutterWithLowSpeedManagementIOComponentNames")
	def selectedRollerShuttersWithLowSpeedManagementIOComponent = selectedRollerShutterWithLowSpeedManagementIOComponentNames.each { dni ->
		def rollerShutterWithLowSpeedManagementIOComponent = atomicState.rollerShuttersWithLowSpeedManagementIOComponent[dni]

		if (!rollerShutterWithLowSpeedManagementIOComponent) {
			debug("initialize: rollerShuttersWithLowSpeedManagementIOComponent: ${atomicState.rollerShuttersWithLowSpeedManagementIOComponent}")

			def errorMessage = "Roller Shutter With Low Speed Management IO Component '$dni' not found."
			log.error(errorMessage)

			throw new org.json.JSONException(errorMessage)
		}

		debug("rollerShutterWithLowSpeedManagementIOComponent $rollerShutterWithLowSpeedManagementIOComponent")

		def deviceName = getDeviceName(rollerShutterWithLowSpeedManagementIOComponent)
		def deviceId = getDeviceId(rollerShutterWithLowSpeedManagementIOComponent)

		def virtualDevice = getChildDevice(dni)

		if (virtualDevice) {
			debug("Found ${virtualDevice.name} with network id '$dni' already exists.")
		} else {
			def deviceTypeName = "TaHoma Roller Shutter IO"

			debug("Creating new '$deviceTypeName' device '$deviceName' with id '$dni'.")

			def supportsDiscrete = rollerShutterWithLowSpeedManagementIOComponent.controllableName == 'io:RollerShutterWithLowSpeedManagementIOComponent'

			def capabilities = [supportsDiscrete: supportsDiscrete]

			virtualDevice = addChildDevice(app.namespace, deviceTypeName, dni, null, ["name": deviceId, "label": deviceName, "completedSetup": true])
			virtualDevice.setCapabilities(capabilities)

			debug("virtualDevice ${virtualDevice}")

			debug("Created '$deviceName' with network id '$dni'.")
		}

		return virtualDevice
	}

	debug("User selected ${selectedRollerShuttersWithLowSpeedManagementIOComponent.size()} Roller Shutters With Low Speed Management IO Component.")

	return selectedRollerShuttersWithLowSpeedManagementIOComponent.size()
}

def processSelectedSwitches() {
	debug("processSelectedSwitches()")

	if (!settings.selectedSwitchNames){
		settings.selectedSwitchNames = []
	}

	def selectedSwitches = selectedSwitchNames.each { dni ->
		def currentSwitch = atomicState.switches[dni]

		if (!currentSwitch) {
			debug("initialize: switches: ${atomicState.switches}")

			def errorMessage = "Switch '$dni' not found."
			log.error(errorMessage)

			throw new org.json.JSONException(errorMessage)
		}

		debug("switch $currentSwitch")

		def deviceName = getDeviceName(currentSwitch)
		def deviceId = getDeviceId(currentSwitch)

		def virtualDevice = getChildDevice(dni)

		if (virtualDevice) {
			debug("Found ${virtualDevice.name} with network id '$dni' already exists.")
		} else {
			def deviceTypeName = "TaHoma Switch"

			debug("Creating new '$deviceTypeName' device '$deviceName' with id '$dni'.")

			virtualDevice = addChildDevice(app.namespace, deviceTypeName, dni, null, ["name": deviceId, "label": deviceName, "completedSetup": true])

			debug("virtualDevice ${virtualDevice}")

			debug("Created '$deviceName' with network id '$dni'.")
		}

		return virtualDevice
	}

	debug("User selected ${selectedSwitches.size()} switches.")

	return selectedSwitches.size()
}

def deleteUnselectedDevices() {
	debug("deleteUnselectedDevices()")

	def delete

	if ((settings.selectedInteriorRollerBlindNames && settings.selectedInteriorRollerBlindNames.size() > 0) 
		|| (settings.selectedLightIOSystemSensorNames && settings.selectedLightIOSystemSensorNames.size() > 0) 
		|| (settings.selectedRollerShutterNames && settings.selectedRollerShutterNames.size() > 0) 
		|| (settings.selectedRollerShutterWithLowSpeedManagementIOComponentNames && settings.selectedRollerShutterWithLowSpeedManagementIOComponentNames.size() > 0) 
		|| (settings.selectedSwitchNames && settings.selectedSwitchNames.size() > 0)) {
		debug("Delete devices not selected by user.")

		delete = getChildDevices().findAll { 
			!settings.selectedInteriorRollerBlindNames.contains(it.deviceNetworkId) &&
			!settings.selectedLightIOSystemSensorNames.contains(it.deviceNetworkId) &&
            !settings.selectedRollerShutterNames.contains(it.deviceNetworkId) &&
            !settings.selectedRollerShutterWithLowSpeedManagementIOComponentNames.contains(it.deviceNetworkId) &&
            !settings.selectedSwitchNames.contains(it.deviceNetworkId)
		}
	} else {
		delete = getAllChildDevices()
	}

	log.warn("Delete: ${delete}, deleting ${delete.size()} devices.")

	delete.each { deleteChildDevice(it.deviceNetworkId) }
}

def getAuthorizationHeaderValue() {
	debug("getAuthorizationHeaderValue()")

	if (atomicState.authorizationHeaderValue == "" || atomicState.authorizedAt < now() - atomicState.authorizationTimeoutInMilliseconds) {
		debug("getAuthorizationHeaderValue: Authorizing with server.")

		atomicState.authorizationHeaderValue = getAuthorizationHeaderValueCore()
	} else {
		debug("getAuthorizationHeaderValue: Using cached token.")
	}

	return atomicState.authorizationHeaderValue
}

def getAuthorizationHeaderValueCore() {
	debug("getAuthorizationHeaderValueCore()")

	atomicState.authorizationFailed = false
	atomicState.authorizationHeaderValue = ""

	def requestParams = [
		uri: cloudApiEndpoint,
		path: 'login',
		headers: [
			'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
		]
	]

	debug("$requestParams")

	requestParams.body = [
		'userId':	settings.username,
		'userPassword':	settings.password
	]

	try {
		httpPost(requestParams) { resp ->
			if (resp.status == 200) {
				debug("getAuthorizationHeaderValueCore(): resp.data ${resp.data}")

				if (resp.data && resp.data.success == true) {
					def cookieHeader = resp.headers.'Set-Cookie'

					if (cookieHeader.contains('JSESSIONID')) {
						def cookieParts = cookieHeader.split(';')
						if (cookieParts.length > 0) {
							atomicState.authorizationHeaderValue = cookieParts[0]

							debug("getAuthorizationHeaderValueCore(): authorizationHeaderValue ${atomicState.authorizationHeaderValue}")

							atomicState.authorizationFailed = false
							atomicState.authorizationRetriesRemaining = atomicState.authorizationRetries
							atomicState.authorizedAt = now()
						}
					}
				}
			}
			else {
				log.error("getAuthorizationHeaderValueCore(): Failed: ${resp.status}")
			}
		}
	} catch (groovyx.net.http.HttpResponseException e) {
		log.error("getAuthorizationHeaderValueCore(): Error: ${e}")

		atomicState.authorizationHeaderValue = ""

		if (e.statusCode == 401) {
			if (atomicState.authorizationRetriesRemaining > 0) {
				debug("Retry authorization $atomicState.authorizationRetriesRemaining time(s).")

				atomicState.authorizationRetriesRemaining = atomicState.authorizationRetriesRemaining - 1

				atomicState.authorizationHeaderValue = getAuthorizationHeaderValueCore()
			} else {
				atomicState.authorizationFailed = true
			}
		}
	}

	return atomicState.authorizationHeaderValue
}

def removeChildDevices(childDevices) {
	debug("removeChildDevices($childDevices)")

    childDevices.each {
		try {
			deleteChildDevice(it.deviceNetworkId)
		} catch (Exception e) {
			debug("removeChildDevices ${it.deviceNetworkId} $e")
		}
    }
}

def getDeviceNames(devices) {
	debug("getDeviceNames($devices)")

	def names = [:]

	devices.each { dni, device ->
		names[dni] = getDeviceName(device)
	}

	debug("names $names")

	return names
}

def getInteriorRollerBlindNames() {
	debug("getInteriorRollerBlindNames()")

	def interiorRollerBlindNames = [:]

	atomicState.interiorRollerBlinds.each { dni, interiorRollerBlind ->
		interiorRollerBlindNames[dni] = getDeviceName(interiorRollerBlind)
	}

	debug("interiorRollerBlindNames $interiorRollerBlindNames")

	return interiorRollerBlindNames
}

def getLightIOSystemSensorNames() {
	debug("getLightIOSystemSensorNames()")

	def names = getDeviceNames(atomicState.lightIOSystemSensors)

	debug("names $names")

	return names
}

def getRollerShutterNames() {
	debug("getRollerShutterNames()")

	def rollerShutterNames = [:]

	atomicState.rollerShutters.each { dni, rollerShutter ->
		rollerShutterNames[dni] = getDeviceName(rollerShutter)
	}

	debug("rollerShutterNames $rollerShutterNames")

	return rollerShutterNames
}

def getRollerShutterWithLowSpeedManagementIOComponentNames() {
	debug("getRollerWithLowSpeedManagementIOComponentShutterNames()")

	def rollerShutterWithLowSpeedManagementIOComponentNames = [:]

	atomicState.rollerShuttersWithLowSpeedManagementIOComponent.each { dni, rollerShutterWithLowSpeedManagementIOComponent ->
		rollerShutterWithLowSpeedManagementIOComponentNames[dni] = getDeviceName(rollerShutterWithLowSpeedManagementIOComponent)
	}

	debug("rollerShutterWithLowSpeedManagementIOComponentNames $rollerShutterWithLowSpeedManagementIOComponentNames")

	return rollerShutterWithLowSpeedManagementIOComponentNames
}

def getSwitchNames() {
	debug("getSwitchNames()")

	def switchNames = [:]

	atomicState.switches.each { dni, currentSwitch ->
		switchNames[dni] = getDeviceName(currentSwitch)
	}

	debug("switchNames $switchNames")

	return switchNames
}

def poll() {
	debug("poll()")

	updateDevices()

	updateChildDevices()
}

def refresh() {
	debug("refresh()")

	poll()
}

def updateChildDevices() {
	debug("updateChildDevices()")

	try {
		def childDevices = getChildDevices()

		debug("updateChildDevices childDevices: ${childDevices}")

		childDevices.each { childDevice ->
			try {
				def eventData = [:]

				def deviceData = atomicState.rawDeviceData[childDevice.deviceNetworkId]
                
                log.debug("deviceData.states ${deviceData.states}")

				if (deviceData && deviceData.states) {
					deviceData.states.each { state ->
                    	//log.debug("state $state")
						if (state.name == "core:ClosureState") {
							eventData["level"] = state.value
						} else if (state.name == "core:LuminanceState") {
							eventData["illuminance"] = state.value
						}
					}
				}

				log.debug("Event data ${eventData}")

				childDevice.generateEvents(eventData)
			} catch (Exception e) {
				log.error("Exception updating device ${childDevice.name}: ${e}")
			}
		}
	} catch (Exception e) {
        log.error("Exception updating devices: ${e}")
    }
}

def updateDevices() {
	debug("updateDevices()")

	def requestParams = [
		uri: cloudApiEndpoint,
		path: 'setup',
		headers: [
			'Cookie': getAuthorizationHeaderValue()
		]
	]

	debug("$requestParams")

	try {
		httpGet(requestParams) { resp ->
			debug("updateDevices(): resp.data ${resp.data}")

			if (resp.status == 200 && resp.data) {
				def lightIOSystemSensors = [:]
				def rawDeviceData = [:]
				def rollerShutters = [:]
				def rollerShuttersWithLowSpeedManagementIOComponent = [:]
				def interiorRollerBlinds = [:]
				def switches = [:]

				//def slurper = new groovy.json.JsonSlurper()
				//def result = slurper.parseText('{"devices": [{"creationTime": 1524144461000,      "lastUpdateTime": 1524144461000,      "label": "Kidsroom east",      "deviceURL": "rts://1201-2886-4711/16776321",      "shortcut": false,      "controllableName": "io:RollerShutterWithLowSpeedManagementIOComponent",      "states": [        {          "name": "core:NameState",          "type": 3,          "value": "Kidsroom east"        },        {          "name": "core:PriorityLockTimerState",          "type": 1,          "value": 0        },        {          "name": "core:StatusState",          "type": 3,          "value": "available"        },        {          "name": "core:RSSILevelState",          "type": 2,          "value": 80.0        },        {          "name": "core:ClosureState",          "type": 1,          "value": 66        },        {          "name": "core:OpenClosedState",          "type": 3,          "value": "open"        }      ],      "attributes": [],      "available": true,      "enabled": true,      "placeOID": "45bf640c-b9fb-4703-8ec6-4d88e49abd2d",      "widget": "PositionableRollerShutterWithLowSpeedManagement",      "type": 1,      "oid": "56f526d7-cffc-4460-91a8-3bb53b6ed0ca",      "uiClass": "RollerShutter" }]}')
				//def result = slurper.parseText('{"devices": [{"creationTime": 1524144461000,      "lastUpdateTime": 1524144461000,      "label": "IO test blind",      "deviceURL": "rts://1201-2886-4711/16776321",      "shortcut": false,      "controllableName": "io:RollerShutterGenericIOComponent",      "states": [        {          "name": "core:NameState",          "type": 3,          "value": "IO test blind"        },        {          "name": "core:PriorityLockTimerState",          "type": 1,          "value": 0        },        {          "name": "core:StatusState",          "type": 3,          "value": "available"        },        {          "name": "core:RSSILevelState",          "type": 2,          "value": 80.0        },        {          "name": "core:ClosureState",          "type": 1,          "value": 66        },        {          "name": "core:OpenClosedState",          "type": 3,          "value": "open"        }      ],      "attributes": [],      "available": true,      "enabled": true,      "placeOID": "45bf640c-b9fb-4703-8ec6-4d88e49abd2d",      "widget": "PositionableRollerShutterWithLowSpeedManagement",      "type": 1,      "oid": "56f526d7-cffc-4460-91a8-3bb53b6ed0ca",      "uiClass": "RollerShutter" }]}')
				//def result = slurper.parseText('{"devices": [{"creationTime": 1524144461000,      "lastUpdateTime": 1524144461000,      "label": "IO test light sensor",      "deviceURL": "io://1201-2886-666/777",      "shortcut": false,      "controllableName": "io:LightIOSystemSensor",      "states": [{ "name": "core:LuminanceState", "type": 2, "value": 90000.0 }      ],      "attributes": [],      "available": true,      "enabled": true,      "placeOID": "45bf640c-b9fb-4703-8ec6-4d88e49abd2d",      "widget": "PositionableRollerShutterWithLowSpeedManagement",      "type": 1,      "oid": "56f526d7-cffc-4460-91a8-3bb53b6ed0ca",      "uiClass": "RollerShutter" }]}')
				//resp.data.devices = result.devices
				if (resp.data.devices) {
					resp.data.devices.each { device ->
                        //debug("-----------xxxxxxxxxxx---------- ${device}")

						def dni = [app.id, getDeviceId(device)].join('.')

						if (device.controllableName == 'rts:BlindRTSComponent') {
							interiorRollerBlinds[dni] = device
							rawDeviceData[dni] = device
						} else if (device.controllableName == 'rts:RollerShutterRTSComponent') {
							rollerShutters[dni] = device
							rawDeviceData[dni] = device
						} else if (device.controllableName == 'rts:OnOffRTSComponent') {
							switches[dni] = device
							rawDeviceData[dni] = device
						} else if (device.controllableName == 'io:LightIOSystemSensor') {
							lightIOSystemSensors[dni] = device
							rawDeviceData[dni] = device
						} else if (device.controllableName == 'io:RollerShutterWithLowSpeedManagementIOComponent') {
							rollerShuttersWithLowSpeedManagementIOComponent[dni] = device
							rawDeviceData[dni] = device
						} else if (device.controllableName == 'io:RollerShutterGenericIOComponent') {
							rollerShuttersWithLowSpeedManagementIOComponent[dni] = device
							rawDeviceData[dni] = device
						}
					}
				}

				debug("updateDevices(): interiorRollerBlinds ${interiorRollerBlinds}")
				debug("updateDevices(): lightIOSystemSensors ${lightIOSystemSensors}")
				debug("updateDevices(): rawDeviceData ${rawDeviceData}")
				debug("updateDevices(): rollerShutters ${rollerShutters}")
				debug("updateDevices(): rollerShuttersWithLowSpeedManagementIOComponent ${rollerShuttersWithLowSpeedManagementIOComponent}")
				debug("updateDevices(): switches ${switches}")

				atomicState.interiorRollerBlinds = interiorRollerBlinds
				atomicState.lightIOSystemSensors = lightIOSystemSensors
                atomicState.rawDeviceData = rawDeviceData
				atomicState.rollerShutters = rollerShutters
				atomicState.rollerShuttersWithLowSpeedManagementIOComponent = rollerShuttersWithLowSpeedManagementIOComponent
				atomicState.switches = switches
				atomicState.devicesUpdatedAt = now()
			}
			else {
				log.error("updateDevices(): Failed: ${resp.status}")
			}
		}
	} catch (groovyx.net.http.HttpResponseException e) {
		log.error("updateDevices(): Error: ${e}")

		if (e.statusCode == 401) {
			atomicState.authorizationFailed = true
		}
	}
}

def executeCommand(commandId, rollerShutterId, label, parameters = "") {
	debug("executeCommand($commandId, $rollerShutterId, $label, $parameters)")

	label = "$label ($rollerShutterId; SmartThings $smartAppVersion)";

	def body = "{\"label\":\"$label\",\"actions\":[{\"deviceURL\":\"$rollerShutterId\",\"commands\":[{\"name\":\"$commandId\",\"parameters\":[$parameters]}]}]}"

	def requestParams = [
		uri: cloudApiEndpoint,
		path: 'exec/apply',
		headers: [
			'Cookie': getAuthorizationHeaderValue()
		],
		body: body
	]

	debug("requestParams: $requestParams")

	def executionId

	try {
		httpPostJson(requestParams) { resp ->
			if (resp.status == 200 && resp.data) {
				debug("executeCommand(): resp.data ${resp.data}")

				refresh()

				executionId = resp.data.execId
			}
			else {
				log.error("executeCommand(): Failed: ${resp.status}")
			}
		}
	} catch (groovyx.net.http.HttpResponseException e) {
		log.error("executeCommand(): Error: ${e}")

		if (e.statusCode == 401) {
			atomicState.authorizationFailed = true
		}
	}

	debug("executeCommand: $commandId done: executionId: $executionId")

	return executionId
}

def stopExecution(executionId, label) {
	debug("stopExecution($executionId, $label)")

	def requestParams = [
		uri: cloudApiEndpoint,
		path: "exec/current/setup/$executionId",
		headers: [
			'Cookie': getAuthorizationHeaderValue()
		],
	]

	debug("$requestParams")

	def result

	try {
		httpDelete(requestParams) { resp ->
			result = resp.status
		}
	} catch (groovyx.net.http.HttpResponseException e) {
		log.error("stopExecution(): Error: ${e}")

		result = e

		if (e.statusCode == 401) {
			atomicState.authorizationFailed = true
		}
	}

	return result
}

def close(rollerShutterId, label) {
	debug("close($rollerShutterId, $label)")

	def executionId = executeCommand("close", rollerShutterId, "Close $label")

	return executionId
}

def identify(rollerShutterId, label) {
	debug("identify($rollerShutterId, $label)")

	def executionId = executeCommand("identify", rollerShutterId, "Identify $label")

	return executionId
}

def open(rollerShutterId, label) {
	debug("open($rollerShutterId, $label)")

	def executionId = executeCommand("open", rollerShutterId, "Open $label")

	return executionId
}

def presetPosition(rollerShutterId, label) {
	debug("presetPosition($rollerShutterId, $label)")

	def executionId = executeCommand("my", rollerShutterId, "My position $label")

	return executionId
}

def stop(executionId, label) {
	debug("stop($executionId, $label)")

	stopExecution(executionId, "Stop $label")
}

def switchOff(switchId, label) {
	debug("switchOff($switchId, $label)")

	def executionId = executeCommand("off", switchId, "$label off")

	return executionId
}

def switchOn(switchId, label) {
	debug("switchOn($switchId, $label)")

	def executionId = executeCommand("on", switchId, "$label on")

	return executionId
}
1 Like

Thank you very much, now the app works !!
I am logged in but I still cannot see my sun tents!

That I can't help you with. I would recommend going over to the ST forum and posting on the original thread where the app came from. It's possible that it just doesn't work anymore because of changes in the API. Did you look at the main page for the repo in github?

There's also a driver that you have to install.

FYI...i did a quick look and for some reason the device driver is using parent settings but the device doesn't have a parent child relationship defined. So, I dunno how this thing works.

yes, I know, I am trying to install this 'TaHomaRollerShutterRTS.DeviceHandler.groovy' but I get another error. unexpected token: * @ line 8, column 2.

I'm new here and I don't understand all that well.:innocent::grinning:

sorry, I mean this: TaHomaSwitch.DeviceHandler.groovy and give this error: No signature of method: Script1.icon () is applicable for argument types: (java.util.LinkedHashMap) values: [[title: Update Icon]] Possible solutions: on (), run (), run (), main (java.lang.String), main (java.util.List), main ([Ljava.lang.String;)

Sorry...but you're basically asking for someone to develop an integration for this device at this point. And I don't really have any interest in this device. That said, TaHoma does have IFTTT integration. Have you looked at integrating them that way instead of with this driver/app which you can't use?

1 Like