Sinope thermostats api - Working! Modes and presence driver in post #2

Reviving a slightly older thread here, but i'd like to know if this functionality will work to control the NON-ZigBee Sinope devices, or just the ZigBee devices?

1 Like

This is for the wifi neviweb thermostat and no longer work because Sinope updated the API.
I moved my Neviwb back to ST and use the new Sinope manager there.

Well, frack. Just when I thought i'd found a way to use my Sinope devices without looping through their servers. Guess i'll have to look for a ST hub after all. Can a ST hub work within your home WiFi and without sending all your data back to a server?

My hope is to future-proof the installation so I won't be screwed if Sinope goes out of business and Neviweb disappears.

You don't really need an ST hub since this is just a cloud service.
And yes, all your data will be beaming home to Samsung mothership with or without hub.

Double drat. Is there no way to control these Sinope controllers without beaming to someone's mothership in the cloud?

1 Like

Hi, I'm new to Hubitat (2 days old lol) and I have 7 thermostats that use the old Gt125. I saw the HA has a local implementation here GitHub - claudegel/sinope-gt125: Sinope custom component for Home Assistant to manage Sinopé devices directly via the GT125 gateway. Could this be ported to Hubitat as well?

Another thing I have these working on ST. Could this be ported to Hubitat as well?

Device Handler:

/**
Copyright Sinopé Technologies
1.3.2
 *  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. 
**/
metadata {
	preferences {
		input("locationname", "text", title: "Name of your neviweb® location", description: "Location name", required: true, displayDuringSetup: true)
		input("devicename", "text", title: "Name of your neviweb® thermostat", description: "Thermostat name", required: true, displayDuringSetup: true)
	}

	definition (name: "Sinopé Technologies Inc. Thermostat", namespace: "Sinopé Technologies Inc.", author: "Sinopé Technologies Inc.", ocfDeviceType: "oic.d.thermostat") {
		capability "ThermostatHeatingSetpoint"
		capability "thermostatOperatingState"
		capability "TemperatureMeasurement"
		capability "Refresh"

		attribute "outdoorTemp", "string"
		attribute "outputPercentDisplay", "number"

		command "heatingSetpointDown"
		command "heatingSetpointUp"

		command "StartCommunicationWithServer"

	}

	simulator {
		// TODO: define status and reply messages here
	}

	tiles(scale: 2) {
		multiAttributeTile(name:"temperature", type: "thermostat", width: 6, height: 4){
			tileAttribute ("device.temperature", key: "PRIMARY_CONTROL") {
				attributeState("temperatureMeasurement", label:'${currentValue}°', unit: "dF", backgroundColor:"#269bd2")
			}
			tileAttribute("device.heatingSetpoint", key: "VALUE_CONTROL") {
				attributeState("VALUE_UP", action: "heatingSetpointUp")
				attributeState("VALUE_DOWN", action: "heatingSetpointDown")
			}
			tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE",inactiveLabel: true) {
				attributeState("idle",backgroundColor:"#44b621")
				attributeState("heating",backgroundColor:"#ffa81e")
			}
			
			tileAttribute("outputPercentDisplay", key: "SECONDARY_CONTROL") {
				attributeState("outputPercentDisplay", label:'${currentValue}%', unit:"%", icon:"st.Weather.weather2", defaultState: true)
			}
			
			tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
				attributeState("default", label: '${currentValue}', unit: "dF")
			}
		}

		// //Heating Set Point Controls
		// controlTile("levelSliderControl", "device.heatingSetpoint", "slider", height: 1, width: 6, inactiveLabel: false, range:"(5..30)", decoration: "flat") {
        // state "heatingSetpoint", action:"heatingSetpoint"
    	// }
		
		standardTile("heatLevelDown", "device.heatingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
            state "heatLevelDown", action:"heatingSetpointDown", icon:"st.thermostat.thermostat-down"
        }
        standardTile("heatLevelUp", "device.heatingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
            state "heatLevelUp", action:"heatingSetpointUp", icon:"st.thermostat.thermostat-up"
        }
		controlTile("heatingSetpointSlider", "device.heatingSetpoint","slider", height: 2, width: 2, range:"5..86") {
        state "heatingSetpoint", label:'${currentValue}', action:"setHeatingSetpoint"
    	}
       	valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2, inactiveLabel: false) {
			state "heatingSetpoint", label:'${currentValue}', backgroundColor:"#153591"
		}
		standardTile("refresh", "device.thermostatMode", inactiveLabel: false, width: 2, height: 2, decoration: "flat") {
			state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
		}
		standardTile("error", "device.error", width: 4, height: 2, inactiveLabel: false, decoration: "flat") {
		    state "default", label:'${currentValue}', backgroundColor:"#ffffff", icon:"st.Office.office8"
		}

		main (["temperature"])
        details(["temperature", /*"heatLevelDown", "heatingSetpoint", "heatLevelUp", */"refresh", "error"])
	}
}


def setHeatingSetpoint(newSetpoint) {
	log.trace("Connexion verifiction - ${device.name}")
	try{
		state.heatingSetpoint = newSetpoint;
		sendEvent(name: 'heatingSetpoint', value: FormatTemp(state.heatingSetpoint,null), unit: location?.getTemperatureScale())
		def timeInSeconds = (Math.round(now()/1000))
		sendEvent(name: "thermostat", value: device.id+": "+timeInSeconds, state: "${newSetpoint}", data: [deviceId: device.id, action: "setHeatingSetpoint", value: "${newSetpoint}", evtTime: timeInSeconds])
	}
	catch(NullPointerException e1){
		refresh();
	}
}

def heatingSetpointUp(){
	log.trace("Connexion verifiction - ${device.name}")
	try{
		state.heatingSetpoint = state.heatingSetpoint.toDouble() + 0.5
		sendEvent(name: 'heatingSetpoint', value: FormatTemp(""+state.heatingSetpoint,null), unit: location?.getTemperatureScale())
		def timeInSeconds = (Math.round(now()/1000))
		log.info("Setting ${device.name} to ${state.heatingSetpoint.toDouble() + 0.5}")
		sendEvent(name: "thermostat", value: device.id+": "+timeInSeconds, state: "heatingSetpointUp", data: [deviceId: device.id, action: "heatingSetpointUp", evtTime: timeInSeconds])
	}
	catch(NullPointerException e1){
		refresh();
	}
}

def heatingSetpointDown(){
	log.trace("Connexion verifiction - ${device.name}")
	try{
		state.heatingSetpoint = state.heatingSetpoint.toDouble() - 0.5
		sendEvent(name: 'heatingSetpoint', value: FormatTemp(""+state.heatingSetpoint,null), unit: location?.getTemperatureScale())
		def timeInSeconds = (Math.round(now()/1000))
		log.info("Setting ${device.name} to ${state.heatingSetpoint.toDouble() - 0.5}")
		sendEvent(name: "thermostat", value: device.id+": "+timeInSeconds, state: "heatingSetpointDown", data: [deviceId: device.id, action: "heatingSetpointDown", evtTime: timeInSeconds])
	}
	catch(NullPointerException e1){
		refresh();
	}
}


def refresh(){
	log.trace("Connexion verifiction - ${device.name}")
	def timeInSeconds = (Math.round(now()/1000))
	sendEvent(name: "thermostat", value:  device.id+": "+timeInSeconds, state: "refresh", data: [deviceId: device.id, action: "refresh", evtTime: timeInSeconds])
	
}

def StartCommunicationWithServer(data){
	if(data?.error){
			sendEvent(name: 'error', value: "${data.error}")
			log.warn("${data.error}")
	}else{
		
		log.info("Action \"${data?.action}\" - \"${device.name}\"")
		if( !state.deviceId || state.deviceId == true || state.deviceName != settings.devicename.toLowerCase().replaceAll("\\s", "") || state.locationName != settings.locationname.toLowerCase().replaceAll("\\s", "") ){
			state.deviceId = deviceId(data?.session)
		}
		def params = [
			path: "device/${state.deviceId}/attribute",
			headers: ['Session-Id' : data.session]
		]
		if(!state.deviceId){
			log.warn ("No device id found")
			sendEvent(name: 'temperature', value: null, unit: location?.getTemperatureScale())
			sendEvent(name: 'temperatureMeasurement', value: null, unit: location?.getTemperatureScale())
			sendEvent(name: 'heatingSetpoint', value: null, unit: location?.getTemperatureScale())
			sendEvent(name: 'outputPercentDisplay', value: null)
			sendEvent(name: 'thermostatOperatingState', value: null)
			state.heatingSetpoint = 0
			return sendEvent(name: 'error', value: "${error(1004)}")
		}else{
			switch(data.action){
				case "heatingSetpointUp":
					params.body = ['roomSetpoint' : state.heatingSetpoint.toDouble()]
					params.body.floorSetpoint = params.body.roomSetpoint
					params.contentType = 'application/json'
					requestApi("setDevice", params);
					data.action = "refresh"
					StartCommunicationWithServer(data)
					break;
				case "heatingSetpointDown":
					params.body = ['roomSetpoint' : state.heatingSetpoint.toDouble()]
					params.body.floorSetpoint = params.body.roomSetpoint
					params.contentType = 'application/json'
					requestApi("setDevice", params);
					data.action = "refresh"
					StartCommunicationWithServer(data)
					break;
				case "setHeatingSetpoint":
					params.body = ['roomSetpoint' : FormatTemp(data.value,true)]
					params.body.floorSetpoint = params.body.roomSetpoint
					params.contentType = 'application/json'
					requestApi("setDevice", params);
					data.action = "refresh"
					StartCommunicationWithServer(data)
					break;
				case "refresh":
					params.query = ['attributes' : "airFloorMode,floorTemperature,floorSetpoint,roomTemperature,roomSetpoint,outputPercentDisplay"]
					requestApi("deviceData", params);
					break;
				default: 
					log.warn "invalide action"
			}
		}
	}
}

def askForSessionReset(error){
	if(error){
		sendEvent(name: 'error', value: "${error}")
	}
	def timeInSeconds = (Math.round(now()/1000))
	sendEvent(name: "thermostat", value:  device.id+": "+timeInSeconds, state: "resetSession", data: [deviceId: device.id, action: "resetSession", evtTime: timeInSeconds])
}

def deviceId(session){
	data.deviceId = null
	locationId(session)
	if(data.locationId){
		def params = [
			uri: "${data.server}",
			path: "devices",
			requestContentType: "application/json, text/javascript, */*; q=0.01",
			headers: ['Session-Id' : session]
		]
		if(data?.locationId){
			params.query = ['location$id' : data.locationId]
		}

		requestApi("deviceList", params);

		def deviceName=settings.devicename
		if (deviceName!=null){
			deviceName=deviceName.toLowerCase().replaceAll("\\s", "")
		}
		data?.deviceId = null
		data.devices_list.each{var ->
			try{
				def name_device=var.name
				name_device=name_device.toLowerCase().replaceAll("\\s", "")
				if(name_device==deviceName){
					data.deviceId=var.id
						data.error=false
						state.deviceName = deviceName;
						state.deviceLocation = settings?.locationname.toLowerCase().replaceAll("\\s", "");
						return data.deviceId;
				}else{
					data.code=4001
				}
			}catch(e){
				data.code=4003
			}
		}
		if (!data?.deviceId || data.error){
			data.error=error(data.code)
			sendEvent(name: 'error', value: "${data.error}")
			data.deviceId=null;
			log.warn("${data.error}")
			data.error=true
		}
		else{
			data.deviceId
		}
		return data.deviceId
	}else{
		log.warn("${error(3001)}")
		return null;
	}
}



def locationId(session){
	def params = [
        path: "locations",
       	requestContentType: "application/json, text/javascript, */*; q=0.01",
        headers: ['Session-Id' : session]
    ]
    requestApi("locationList",params)
    def locationName=settings?.locationname
	if(locationName){
		locationName = locationName.toLowerCase().replaceAll("\\s", "")
	}
	data.locationId = null
	data.location_list.each{var ->    	
    	def name_location
		try{
			name_location = var.name
		}catch(e){
			log.error(var)
		}	
		if(name_location){
    		name_location = name_location.toLowerCase().replaceAll("\\s", "")
		}else{
			name_location = "INVALID LOCATION"
		}

    	if(name_location==locationName){
    		data.locationId = var.id
    		// log.info("Location ID is :: ${data.locationId}")
			state.locationName = locationName
    		data.error=null
    	}
    }
	
    if (!data.locationId){
    	sendEvent(name: 'error', value: "${error(3001)}")
    	data.error=true
    }
}

def isExpiredSessionEvent(resp){
	if( resp?.data?.error && resp?.data?.error?.code && resp?.data?.error?.code=="USRSESSEXP" ){
		sendEvent(name: "switch", value:  device.id+": "+timeInSeconds, state: "resetSession", data: [action: "resetSession", evtTime: timeInSeconds]);
	}
}

def isDeviceIdValid(session){
	def oldDeviceId = state.deviceId;
	if(state.deviceId){
		state.deviceId = deviceId(session)
	}
	if(!state.deviceId){
		log.warn ("No device id found")
		sendEvent(name: 'error', value: "${error(1004)}")
	}else if( oldDeviceId == state.deviceId ){
		data.error=error(2001)
		sendEvent(name: 'error', value: "${data.error}")
		log.error("${data.error}")
	}else{
		data.error=error(2001)
		sendEvent(name: 'error', value: "${data.error}")
		log.error("${data.error}")
	}
	return state.deviceId;
} 

def requestApi(actionApi, params){
	params.uri = "https://smartthings.neviweb.com/"
	log.trace("api call : ${actionApi} - ${device.name}");
	switch(actionApi){
		case "deviceList":
			httpGet(params) {resp ->
				isExpiredSessionEvent(resp)
				data.devices_list = resp.data
				if(resp?.data?.error?.code == "USRSESSEXP"){
					askForSessionReset();
				}
				
			}
		break;
		case "locationList":
			httpGet(params) {resp ->
				isExpiredSessionEvent(resp)
				data.location_list = resp.data
				if(resp?.data?.error?.code == "USRSESSEXP"){
					askForSessionReset();
				}
				
			}
		break;
		case "deviceData":
			def temperature;
			def heatingSetpoint;
			try{
				httpGet(params) {resp ->
					isExpiredSessionEvent(resp)
					data.status = resp.data
					if (!resp.data.error){
						sendEvent(name: 'error', value: " ")
						sendEvent(name: 'status', value: "OK")
						def temperatureInput,setpointInput
						if(data.status.airFloorMode == "floor"){
							temperatureInput = data.status.floorTemperature
							setpointInput = data.status.floorSetpoint
						}
						else{
							temperatureInput = data.status.roomTemperature
							setpointInput = data.status.roomSetpoint
						}
						temperature = FormatTemp(temperatureInput?.value,null)
						if(!temperature){
							temperature = FormatTemp(temperatureInput,null)
						}
						heatingSetpoint = FormatTemp(setpointInput,null)
						if(temperature){
							sendEvent(name: 'temperature', value: temperature, unit: location?.getTemperatureScale())
						}
						if(temperature){
							sendEvent(name: 'temperatureMeasurement', value: temperature, unit: location?.getTemperatureScale())
						}
						if(heatingSetpoint){
							state.heatingSetpoint = setpointInput
							sendEvent(name: 'heatingSetpoint', value: heatingSetpoint, unit: location?.getTemperatureScale())
						}
						sendEvent(name: 'outputPercentDisplay', value: data.status.outputPercentDisplay)
						sendEvent(name: 'thermostatOperatingState', value: ((data.status.outputPercentDisplay > 10)?"heating":"idle"))
						// sendEvent(name: "thermostatMode", value: ((data.status.outputPercentDisplay > 0)?"Heat":"Idle"))
					}else{
						if(resp?.data?.error?.code == "USRSESSEXP"){
							askForSessionReset();
						}
						sendEvent(name: 'temperature', value: null, unit: location?.getTemperatureScale())
						sendEvent(name: 'temperatureMeasurement', value: null, unit: location?.getTemperatureScale())
						sendEvent(name: 'heatingSetpoint', value: null, unit: location?.getTemperatureScale())
						sendEvent(name: 'outputPercentDisplay', value: null)
						sendEvent(name: 'thermostatOperatingState', value: null)
						return isDeviceIdValid(params?.headers["Session-Id"]);
					}
					return resp.data
				}
			} catch (SocketTimeoutException e) {
				return isDeviceIdValid(params?.headers["Session-Id"]);
			} catch (e) {
				return isDeviceIdValid(params?.headers["Session-Id"]);
			}
		break;
		case "setDevice":
			try{
				httpPut(params){resp -> 
					isExpiredSessionEvent(resp)
					if (!resp.data.error){
						// if(resp?.data?.roomSetpoint){
						// 	state.heatingSetpoint = resp.data.roomSetpoint
						// 	sendEvent(name: 'heatingSetpoint', value: FormatTemp(resp.data.roomSetpoint,null), unit: location?.getTemperatureScale())
						// }else if(resp?.data?.floorSetpoint){
						// 	state.heatingSetpoint = resp.data.floorSetpoint
						// 	sendEvent(name: 'heatingSetpoint', value: FormatTemp(resp.data.floorSetpoint,null), unit: location?.getTemperatureScale())
						// }
						// else{
						// 	sendEvent(name: 'heatingSetpoint', value: FormatTemp(state.heatingSetpoint,null), unit: location?.getTemperatureScale())
						// }
						// params.remove("body");
						// params.query = ['attributes' : "outputPercentDisplay"]
						// requestApi("deviceData", params);
					}else{
						if(resp?.data?.error?.code == "USRSESSEXP"){
							askForSessionReset();
						}
						isDeviceIdValid(params?.headers["Session-Id"]);
					}
				}
			} catch (SocketTimeoutException e) {
				return isDeviceIdValid(params?.headers["Session-Id"]);
			} catch (e) {
				return isDeviceIdValid(params?.headers["Session-Id"]);
			}
		break;
	}
	return ;

}

def error(error){
	switch (error) {
		case 0: return ""
		case 1: return "Location name or Device name is wrong."
		case 100: return "Your session expired."
        case 1005: return "This action cannot be executed while in demonstration mode."
        case 1004: return "The resource you are trying to access could not be found."
        case 1003: return "You are not authorized to see this resource."
        case 1002: return "Wrong e-mail address or password. Please try again."
        case 1101: return "The e-mail you have entered is already used.  Please select another e-mail address."
        case 1102: return "The password you have provided is incorrect."
        case 1103: return "The password you have provided is not secure."
        case 1104: return "The account you are trying to log into is not activated. Please activate your account by clicking on the activation link located in the activation email you have received after registring. You can resend the activation email by pressing the following button."
        case 1105: return "Your account is disabled."
        case 1110: return "The maximum login retry has been reached. Your account has been locked. Please try again later."
        case 1111: return "Your account is presently locked. Please try again later."
        case 1120: return "The maximum simultaneous connections on the same IP address has been reached. Please try again later."
        case 2001: return "The device you are trying to access is temporarily unaccessible. Please try later."
        case 2002: return "The network you are trying to access is temporarily unavailable. Please try later."
        case 2003: return "The web interface (GT125) that you are trying to add is already present in your account."
        case 3001: return "Wrong location name. Please try again."
        case 4001: return "Wrong device name. Please try again."
        case 4002: return "This device is not Ligthswitch. Please change DeviceName."
        default: return "An error has occurred, please try again later."

    }
}
def FormatTemp(temp,invert){
	if (temp!=null){
		if(invert){
			float i=Float.parseFloat(temp+"")
			switch (location?.getTemperatureScale()) {
				case "C":
					return i.round(2)
				break;

				case "F":
					return (Math.round(fToC(i))).toDouble().round(2)
				break;
			}

		}else{

			float i=Float.parseFloat(temp+"")
			switch (location?.getTemperatureScale()) {
				case "C":
					return i.round(2)
				break;

				case "F":
					return (Math.round(cToF(i))).toDouble().round(0)
				break;
			}
		}
    }else{
    	return null
    }
}

def cToF(temp) {
	return ((( 9 * temp ) / 5 ) + 32)
}

def fToC(temp) {
	return ((( temp - 32 ) * 5 ) / 9)
}

App:

/**
Copyright Sinopé Technologies
1.3.2
 *  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. 
**/

definition(
    name: "Sinopé Technologies Inc. service manager",
    namespace: "Sinopé Technologies Inc.",
    author: "Sinopé Technologies Inc.",
    description: "Smart app to support Sinopé Miwi and Wi-Fi devices linked to Neviweb",
    category: "SmartThings Labs",
    iconUrl: "https://neviweb.com/assets/icons/icon-144x144.png",
    iconX2Url: "https://neviweb.com/assets/icons/icon-144x144.png"
    
)

preferences {
  	section ("Devices to connect") {
    	//input "switches", "capability.switch", title: "Select your Sinopé Technologies Inc. devices", multiple: true, required: false
    	input "thermostats", "capability.thermostatHeatingSetpoint", title: "Select your Sinopé Technologies Inc. thermostat?", multiple: true, required: false
    }
    section ("Account info") {
        input "email", "text", title: "Your neviweb® account login e-mail", description: "Your neviweb® account login e-mail"
        input "password", "password", title: "Your neviweb® account login password", description: "Your neviweb® account login password"
    }
}

def refreshDevices(data){
    def connection = getSessionId();
    def output;
    if(connection.session){
        output = ["session": connection.session, "action": "refresh", "deviceId": ""]
        log.info("Refreshing devices")
    }else if(connection.error){
        output = [error: connection.error, deviceId: ""]
        log.warn("Refreshing devices failed");
    }else{
        output = ["error": "Smart app configuration problem", deviceId: ""]
        log.warn("Refreshing devices failed");
    }
    switches.each{device ->
            output?.deviceId = device.id;
            startDevicesCommunicationsHandlerRefresh(output);
    }
    thermostats.each{device ->
        output?.deviceId = device.id;
        startDevicesCommunicationsHandlerRefresh(output);
    }
    return ;
}

def uninstalled() {
    unsubscribe();
    unschedule();
    logout([sessionId: true, refreshToken: true]);
    return ;
}

def updated() {
    // log.info "Updating Service Manager"
	state.server="https://smartthings.neviweb.com/";
	unsubscribe();
    unschedule();
    logout([sessionId: true, refreshToken: true]);

    runEvery15Minutes(refreshDevices);

    state.lockedUntil = null;
    state.error = null;
    login();
    state.sessionTime = 0;

    subscribe(switches, "switch", startDevicesCommunicationsHandler);
    subscribe(switches, "level", startDevicesCommunicationsHandler);
    subscribe(thermostats, "thermostat", startDevicesCommunicationsHandler);
    return ;
}

def installed() {
    subscribe(switches, "switch", startDevicesCommunicationsHandler);
    subscribe(switches, "level", startDevicesCommunicationsHandler);
    subscribe(thermostats, "thermostat", startDevicesCommunicationsHandler);
    return ;
}

def startDevicesCommunicationsHandler(evt){
    def data = parseJson(evt.data);
    def device = getDeviceObj(data?.deviceId);
    if(data?.action == "resetSession"){
        if(!state?.error){
            logout([sessionId: true]);
        }
    }else if(device){
        def connection = getSessionId();
        if(connection?.session){
            data?.session = connection.session;
            device?.StartCommunicationWithServer(data);
        }else if(connection.error){
            device?.StartCommunicationWithServer([error: connection.error]);
        }else{
            device?.StartCommunicationWithServer([error: "Invalid Smart App configuration"]);
        }
    }
    return;
}

def startDevicesCommunicationsHandlerRefresh(data){
    if((data?.session && data?.deviceId) || (data?.error && data?.deviceId)){
        try{
            getDeviceObj(data.deviceId).StartCommunicationWithServer(data);
        }catch(e){
            // caused by non-sinope devices. Ignored
        }
    }
    return;
}

private getDeviceObj(deviceId){
    def deviceToReturn = null;
    switches.each{device ->
    	if(device?.id == deviceId) {
            deviceToReturn = device;
        }
    }
    thermostats.each{device ->
    	if(device?.id == (deviceId)) {
            deviceToReturn = device;
        }
    }
    if(deviceToReturn){
        return deviceToReturn;
    }else{
        // log.error "no device with id  "+ deviceId
        return null;
    }
}

def getSessionId(){
	if(state?.session && state?.sessionTime && 
    state.sessionTime < now() && 
    now() < state.sessionTime+595000 ){
		state.sessionTime = now();
		return [session: state.session];
	}
    else{
        if(state?.lockedUntil && (state?.lockedUntil <= now())){
            state?.lockedUntil = null;
            state?.error = null;
            if(state.session || state.refreshToken){
                logout([sessionId: true, refreshToken: true]);
            }
            return login();
        }else if(state?.lockedUntil){
            return [error: "${state?.error} ${(state.lockedUntil - now())/1000} seconds."]
        }else if(state?.error){
            return [error: state?.error]
        }else{
            return connect();
        }
    }
}

def login() {
    def params = [
        uri: "${state.server}",
        path: 'login',
        requestContentType: "application/json; charset=UTF-8",
        body: ["username": settings.email, "password": settings.password, "interface": "neviweb", "stayConnected": true]
    ]
    def connection = requestApi("login", params);
    if(connection?.error){
        log.warn("Failed to login");
    }
    return connection;
}

def connect() {
    
    def params = [
        uri: "${state.server}",
        path: 'connect',
        requestContentType: "application/json; charset=UTF-8",
        headers: [
            'refreshToken' : state?.refreshToken,
            'Session-Id' : state?.session] 
    ];
    def connection = requestApi("connect", params);
    if(connection?.error){
        log.warn("Failed to connect");
    }
    return connection;
}

def logout(data) {
    def params = [
		uri: "${state.server}",
        path: "logout",
       	requestContentType: "application/json, text/javascript, */*; q=0.01",
        headers: [
            'refreshToken' : (data.refreshToken?state?.refreshToken:null),
            'Session-Id' : (data.sessionId?state?.session:null)
        ]
   	];
    if(params.headers['refreshToken'] || params.headers['Session-Id'] ){
        requestApi("logout", params);
        if(data?.refreshToken){
            state?.refreshToken = null;
        }
        if(data?.sessionId){
            state?.session = null;
            state.sessionTime = 0;
        }
    }
    return ;
}

def requestApi(actionApi, params){
    params.uri = "https://smartthings.neviweb.com/"
	log.trace("api call - ${actionApi}");
	switch(actionApi){
		case "login":
			httpPost(params) { resp ->
			// // log.info(resp.data)
			if (!resp?.data?.session || resp?.data?.error){
                log.warn("Unable to login on neviweb");
                if(state.refreshToken){
                    logout([refreshToken: true, sessionId: true]);
                }
                if(resp?.data?.error?.code == "USRLOCKED" || resp?.data?.error?.code == "USRMAXLOGRETRY"){
                    state.lockedUntil = now()+(resp?.data?.error?.data?.time*1000)
                    state.error = "Max login retry exceeded. Your account is locked for ";
                    return [error: state.error +" ${(state.lockedUntil - now())/1000} seconds."];
                }else if(resp?.data?.error?.code == "ACCSESSEXC"){
                    state.lockedUntil = now()+(60*10*1000);
                    state.error = "You have too many open session on Neviweb. Next retry in in ";
                    return [error: state.error +" ${(state.lockedUntil - now())/1000} seconds."];
                }else if(resp?.data?.error?.code == "USRBADLOGIN"){
                    state.error = "Unable to connect to Neviweb, please verify your Sinopé Technologies Inc. service manager configuration. ";
                    return [error: state.error];
                }else{
                    state.lockedUntil = now()+(60*60*1000);//locked for the next hour for generic error
                    state.error = "Unable to connect to Neviweb, please verify your Sinopé Technologies Inc. service manager configuration. Next retry in ";
                    return [error: state.error+" ${(state.lockedUntil - now())/1000} seconds."];
                }
            }else{
                if(state.refreshToken && state.refreshToken!=resp.data.refreshToken){
                    logout([session:true, refreshToken: true]);
                }
				// log.info("New access token")
				state.session = resp.data.session;
				state.sessionTime = now();
				state.refreshToken = resp.data.refreshToken;
				// // log.info("Session : " + state.session)
                return [session: state.session];
			}
		}
		break;
		case "connect":
			httpPost(params) { resp ->
			// // log.info(resp.data)
			if (!resp?.data?.session || resp?.data?.error){
                if(state.session){
                    logout([sessionId: true]);
                }
                if(resp?.data?.error?.code == "USRLOCKED" || resp?.data?.error?.code == "USRMAXLOGRETRY"){
                    state.lockedUntil = now()+(resp?.data?.error?.data?.time*1000)
                    state.error = "Max login retry exceeded. Your account is locked for ";
                    return [error: state.error +" ${(state.lockedUntil - now())/1000} seconds."];
                }else if(resp?.data?.error?.code == "ACCSESSEXC"){
                    state.lockedUntil = now()+(60*10*1000);
                    state.error = "You have too many open session on Neviweb. Next retry in in ";
                    return [error: state.error +" ${(state.lockedUntil - now())/1000} seconds."];
                }else{
                    state.lockedUntil = now()+(60*60*1000);//locked for the next hour for generic error
                    state.error = "Unable to connect to Neviweb, please verify your Sinopé Technologies Inc. service manager configuration. Next retry in ";
                    return [error: state.error+" ${(state.lockedUntil - now())/1000} seconds."];
                }
			}else{
				// log.info("New access token")
                if(state.session && state.session!=resp.data.session){
                    logout([sessionId: true]);
                }
				state.session=resp.data.session;
				state.sessionTime = now();
				// // log.info("Session : " + state.session)
                return [session: state.session];
			}
		}
		break;
		case "logout":
			params.path = "logout";
			params.headers = ['Session-Id' : state?.session]
			httpGet(params) {resp ->
                return rsep?.data?.success;
			}
		break;
	}
}

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.